From 65198f7c47559f2ff1f0f3f70d10cedb3bf4069d Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Sat, 7 Mar 2020 18:27:38 -0500 Subject: [PATCH 001/169] Fixes #91187 --- src/vs/workbench/common/views.ts | 2 ++ .../contrib/files/browser/fileActions.ts | 13 ++++++++++ .../files/browser/views/explorerViewer.ts | 26 ++++++++++++++++--- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index dbd8958dc5c..97666e22c7e 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -20,6 +20,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { flatten, mergeSort } from 'vs/base/common/arrays'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { SetMap } from 'vs/base/common/collections'; +import Severity from 'vs/base/common/severity'; export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test'; @@ -580,6 +581,7 @@ export interface ITreeViewDataProvider { export interface IEditableData { validationMessage: (value: string) => string | null; + notificationMessage?: (value: string) => { content: string, severity: Severity } | null; placeholder?: string | null; startingValue?: string | null; onFinish: (value: string, success: boolean) => void; diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 2bc6d031192..15000d979a9 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -767,6 +767,17 @@ export function validateFileName(item: ExplorerItem, name: string): string | nul return null; } +function fileNameNotificationMessage(name: string): { content: string, severity: Severity } | null { + if (/^\s|\s$/.test(name)) { + return { + content: nls.localize('fileNameWhitespaceWarning', "Leading or trailing whitespace detected."), + severity: Severity.Warning + }; + } + + return null; +} + function trimLongName(name: string): string { if (name?.length > 255) { return `${name.substr(0, 255)}...`; @@ -906,6 +917,7 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole explorerService.setEditable(newStat, { validationMessage: value => validateFileName(newStat, value), + notificationMessage: value => fileNameNotificationMessage(value), onFinish: (value, success) => { folder.removeChild(newStat); explorerService.setEditable(newStat, null); @@ -943,6 +955,7 @@ export const renameHandler = (accessor: ServicesAccessor) => { explorerService.setEditable(stat, { validationMessage: value => validateFileName(stat, value), + notificationMessage: value => fileNameNotificationMessage(value), onFinish: async (value, success) => { if (success) { const parentResource = stat.parent!.resource; diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 9ae1935e2cd..55778ec8f60 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -391,10 +391,6 @@ export class FilesRenderer implements ICompressibleTreeRenderer { - label.setFile(joinPath(parent, value || ' '), labelOptions); // update label icon while typing! - }); - const lastDot = value.lastIndexOf('.'); inputBox.value = value; @@ -411,8 +407,27 @@ export class FilesRenderer implements ICompressibleTreeRenderer { + if (editableData.notificationMessage && inputBox.isInputValid()) { + const message = editableData.notificationMessage(inputBox.value); + if (message) { + inputBox.showMessage({ + content: message.content, + formatContent: true, + type: message.severity === Severity.Info ? MessageType.INFO : message.severity === Severity.Warning ? MessageType.WARNING : MessageType.ERROR + }); + } else { + inputBox.hideMessage(); + } + } + }; + showInputBoxNotification(); + const toDispose = [ inputBox, + inputBox.onDidChange(value => { + label.setFile(joinPath(parent, value || ' '), labelOptions); // update label icon while typing! + }), DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: IKeyboardEvent) => { if (e.equals(KeyCode.Enter)) { if (inputBox.validate()) { @@ -422,6 +437,9 @@ export class FilesRenderer implements ICompressibleTreeRenderer { + showInputBoxNotification(); + }), DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, () => { done(inputBox.isInputValid(), true); }), From 37da787e91f01c6cfee3d4c50992782d9e072d92 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 9 Mar 2020 19:23:54 +0100 Subject: [PATCH 002/169] Implement toggle aware icons and labels in command actions --- src/vs/code/electron-main/window.ts | 10 ++-- .../browser/menuEntryActionViewItem.ts | 12 ++--- src/vs/platform/actions/common/actions.ts | 54 ++++++++++++++++--- .../electron-main/electronMainService.ts | 4 +- src/vs/platform/electron/node/electron.ts | 4 +- .../platform/windows/electron-main/windows.ts | 4 +- .../quickopen/browser/commandsHandler.ts | 12 ++--- .../electron-browser/remote.contribution.ts | 8 +-- src/vs/workbench/electron-browser/window.ts | 12 ++--- 9 files changed, 81 insertions(+), 39 deletions(-) diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index de0a7dff13a..a7f4792dc4e 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -22,7 +22,7 @@ import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; -import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; +import { ISerializableMenuItemAction } from 'vs/platform/actions/common/actions'; import * as perf from 'vs/base/common/performance'; import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; @@ -1094,7 +1094,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } } - updateTouchBar(groups: ISerializableCommandAction[][]): void { + updateTouchBar(groups: ISerializableMenuItemAction[][]): void { if (!isMacintosh) { return; // only supported on macOS } @@ -1123,10 +1123,10 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._win.setTouchBar(new TouchBar({ items: this.touchBarGroups })); } - private createTouchBarGroup(items: ISerializableCommandAction[] = []): TouchBarSegmentedControl { + private createTouchBarGroup(): TouchBarSegmentedControl { // Group Segments - const segments = this.createTouchBarGroupSegments(items); + const segments = this.createTouchBarGroupSegments(); // Group Control const control = new TouchBar.TouchBarSegmentedControl({ @@ -1141,7 +1141,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { return control; } - private createTouchBarGroupSegments(items: ISerializableCommandAction[] = []): ITouchBarSegment[] { + private createTouchBarGroupSegments(items: ISerializableMenuItemAction[] = []): ITouchBarSegment[] { const segments: ITouchBarSegment[] = items.map(item => { let icon: NativeImage | undefined; if (item.icon && !ThemeIcon.isThemeIcon(item.icon) && item.icon?.dark?.scheme === 'file') { diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 6b561aaef76..b66e2612d09 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -12,7 +12,7 @@ import { IdGenerator } from 'vs/base/common/idGenerator'; import { IDisposable, toDisposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { localize } from 'vs/nls'; -import { ICommandAction, IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -148,7 +148,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { @INotificationService protected _notificationService: INotificationService, @IContextMenuService _contextMenuService: IContextMenuService ) { - super(undefined, _action, { icon: !!(_action.class || _action.item.icon), label: !_action.class && !_action.item.icon }); + super(undefined, _action, { icon: !!(_action.class || _action.icon), label: !_action.class && !_action.icon }); this._altKey = AlternativeKeyEmitter.getInstance(_contextMenuService); } @@ -171,7 +171,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { render(container: HTMLElement): void { super.render(container); - this._updateItemClass(this._action.item); + this._updateItemClass(this._action); let mouseOver = false; @@ -226,15 +226,15 @@ export class MenuEntryActionViewItem extends ActionViewItem { if (this.options.icon) { if (this._commandAction !== this._action) { if (this._action.alt) { - this._updateItemClass(this._action.alt.item); + this._updateItemClass(this._action.alt); } } else if ((this._action).alt) { - this._updateItemClass(this._action.item); + this._updateItemClass(this._action); } } } - _updateItemClass(item: ICommandAction): void { + _updateItemClass(item: MenuItemAction): void { this._itemClassDispose.value = undefined; if (ThemeIcon.isThemeIcon(item.icon)) { diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index ab37df3e9fa..f4dcad91966 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -20,16 +20,37 @@ export interface ILocalizedString { original: string; } +export type Icon = { dark?: URI; light?: URI; } | ThemeIcon; +export type ToggleAwareIcon = { toggled?: Icon, untoggled?: Icon }; +export type ToggleAwareTitle = { toggled?: string | ILocalizedString, untoggled?: string | ILocalizedString }; + export interface ICommandAction { id: string; - title: string | ILocalizedString; + title: string | ILocalizedString | ToggleAwareTitle; category?: string | ILocalizedString; - icon?: { dark?: URI; light?: URI; } | ThemeIcon; + icon?: Icon | ToggleAwareIcon; precondition?: ContextKeyExpression; toggled?: ContextKeyExpression; } -export type ISerializableCommandAction = UriDto; +export function isToggleAwareTitle(thing: unknown): thing is ToggleAwareTitle { + return thing && typeof thing === 'object' + && ((typeof (thing as ToggleAwareTitle).toggled === 'string' || typeof (thing as ToggleAwareTitle).toggled === 'object') + || (typeof (thing as ToggleAwareTitle).untoggled === 'string' || typeof (thing as ToggleAwareTitle).untoggled === 'object')); +} + +export function isIcon(thing: unknown): thing is Icon { + if (ThemeIcon.isThemeIcon(thing)) { + return true; + } + return thing && typeof thing === 'object' + && ((thing as { dark?: URI, light?: URI }).dark instanceof URI || (thing as { dark?: URI, light?: URI }).light instanceof URI); +} + +export function isToggleAwareIcon(thing: unknown): thing is ToggleAwareIcon { + return thing && typeof thing === 'object' + && (isIcon((thing as ToggleAwareIcon).toggled) || isIcon((thing as ToggleAwareIcon).untoggled)); +} export interface IMenuItem { command: ICommandAction; @@ -260,9 +281,18 @@ export class SubmenuItemAction extends Action { } } +export type ISerializableMenuItemAction = UriDto<{ + id: string; + title: string | ILocalizedString; + category: string | ILocalizedString | undefined; + icon: Icon | undefined; +}>; + export class MenuItemAction extends ExecuteCommandAction { - readonly item: ICommandAction; + readonly title: string | ILocalizedString; + readonly category: string | ILocalizedString | undefined; + readonly icon: Icon | undefined; readonly alt: MenuItemAction | undefined; private _options: IMenuActionOptions; @@ -274,14 +304,17 @@ export class MenuItemAction extends ExecuteCommandAction { @IContextKeyService contextKeyService: IContextKeyService, @ICommandService commandService: ICommandService ) { - typeof item.title === 'string' ? super(item.id, item.title, commandService) : super(item.id, item.title.value, commandService); + super(item.id, '', commandService); + this.title = (isToggleAwareTitle(item.title) ? this._checked ? item.title.toggled : item.title.untoggled : item.title) || ''; + this._label = typeof this.title === 'string' ? this.title : this.title.value; this._cssClass = undefined; this._enabled = !item.precondition || contextKeyService.contextMatchesRules(item.precondition); this._checked = Boolean(item.toggled && contextKeyService.contextMatchesRules(item.toggled)); + this.category = item.category; + this.icon = isToggleAwareIcon(item.icon) ? this.checked ? item.icon.toggled : item.icon.untoggled : item.icon; this._options = options || {}; - this.item = item; this.alt = alt ? new MenuItemAction(alt, undefined, this._options, contextKeyService, commandService) : undefined; } @@ -305,6 +338,15 @@ export class MenuItemAction extends ExecuteCommandAction { return super.run(...runArgs); } + + serialize(): ISerializableMenuItemAction { + return { + id: this.id, + title: this.title, + category: this.category, + icon: this.icon + }; + } } export class SyncActionDescriptor { diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index 27ef89f17e2..4e7cf3e5e22 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -12,7 +12,7 @@ import { IOpenedWindow, OpenContext, IWindowOpenable, IOpenEmptyWindowOptions } import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { isMacintosh } from 'vs/base/common/platform'; import { IElectronService } from 'vs/platform/electron/node/electron'; -import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; +import { ISerializableMenuItemAction } from 'vs/platform/actions/common/actions'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { AddFirstParameterToFunctions } from 'vs/base/common/types'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; @@ -279,7 +279,7 @@ export class ElectronMainService implements IElectronMainService { return true; } - async updateTouchBar(windowId: number | undefined, items: ISerializableCommandAction[][]): Promise { + async updateTouchBar(windowId: number | undefined, items: ISerializableMenuItemAction[][]): Promise { const window = this.windowById(windowId); if (window) { window.updateTouchBar(items); diff --git a/src/vs/platform/electron/node/electron.ts b/src/vs/platform/electron/node/electron.ts index 8803fd16b39..41c1275befd 100644 --- a/src/vs/platform/electron/node/electron.ts +++ b/src/vs/platform/electron/node/electron.ts @@ -8,7 +8,7 @@ import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDial import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWindowOpenable, IOpenEmptyWindowOptions, IOpenedWindow } from 'vs/platform/windows/common/windows'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; -import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; +import { ISerializableMenuItemAction } from 'vs/platform/actions/common/actions'; import { INativeOpenWindowOptions } from 'vs/platform/windows/node/window'; export const IElectronService = createDecorator('electronService'); @@ -60,7 +60,7 @@ export interface IElectronService { setRepresentedFilename(path: string): Promise; setDocumentEdited(edited: boolean): Promise; openExternal(url: string): Promise; - updateTouchBar(items: ISerializableCommandAction[][]): Promise; + updateTouchBar(items: ISerializableMenuItemAction[][]): Promise; // macOS Touchbar newWindowTab(): Promise; diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 3e05c84fcd9..22c0b239887 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -10,7 +10,7 @@ import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; +import { ISerializableMenuItemAction } from 'vs/platform/actions/common/actions'; import { URI } from 'vs/base/common/uri'; import { Rectangle, BrowserWindow } from 'electron'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -82,7 +82,7 @@ export interface ICodeWindow extends IDisposable { handleTitleDoubleClick(): void; - updateTouchBar(items: ISerializableCommandAction[][]): void; + updateTouchBar(items: ISerializableMenuItemAction[][]): void; serializeWindowState(): IWindowState; } diff --git a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts index a41fb8930e9..19b4cba19a8 100644 --- a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts @@ -569,10 +569,10 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable { const entries: ActionCommandEntry[] = []; for (let action of actions) { - const title = typeof action.item.title === 'string' ? action.item.title : action.item.title.value; + const title = typeof action.title === 'string' ? action.title : action.title.value; let category, label = title; - if (action.item.category) { - category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value; + if (action.category) { + category = typeof action.category === 'string' ? action.category : action.category.value; label = localize('cat.title', "{0}: {1}", category, title); } @@ -580,8 +580,8 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable { const labelHighlights = wordFilter(searchValue, label); // Add an 'alias' in original language when running in different locale - const aliasTitle = (!Language.isDefaultVariant() && typeof action.item.title !== 'string') ? action.item.title.original : undefined; - const aliasCategory = (!Language.isDefaultVariant() && category && action.item.category && typeof action.item.category !== 'string') ? action.item.category.original : undefined; + const aliasTitle = (!Language.isDefaultVariant() && typeof action.title !== 'string') ? action.title.original : undefined; + const aliasCategory = (!Language.isDefaultVariant() && category && action.category && typeof action.category !== 'string') ? action.category.original : undefined; let alias; if (aliasTitle && category) { alias = aliasCategory ? `${aliasCategory}: ${aliasTitle}` : `${category}: ${aliasTitle}`; @@ -591,7 +591,7 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable { const aliasHighlights = alias ? wordFilter(searchValue, alias) : null; if (labelHighlights || aliasHighlights) { - entries.push(this.instantiationService.createInstance(ActionCommandEntry, action.id, this.keybindingService.lookupKeybinding(action.item.id), label, alias, { label: labelHighlights, alias: aliasHighlights }, action, (id: string) => this.onBeforeRunCommand(id))); + entries.push(this.instantiationService.createInstance(ActionCommandEntry, action.id, this.keybindingService.lookupKeybinding(action.id), label, alias, { label: labelHighlights, alias: aliasHighlights }, action, (id: string) => this.onBeforeRunCommand(id))); } } } diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index 67df9f76198..d6ea00c8a27 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -200,14 +200,14 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc } for (let action of actionGroup[1]) { if (action instanceof MenuItemAction) { - let label = typeof action.item.title === 'string' ? action.item.title : action.item.title.value; - if (action.item.category) { - const category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value; + let label = typeof action.title === 'string' ? action.title : action.title.value; + if (action.category) { + const category = typeof action.category === 'string' ? action.category : action.category.value; label = nls.localize('cat.title', "{0}: {1}", category, label); } items.push({ type: 'item', - id: action.item.id, + id: action.id, label }); } diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 6f7117b9068..094333c1055 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -23,7 +23,7 @@ import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; import { ipcRenderer as ipc, webFrame, crashReporter, CrashReporterStartOptions, Event as IpcEvent } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; -import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, IMenu, MenuItemAction, SubmenuItemAction, MenuRegistry, ISerializableMenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -68,7 +68,7 @@ export class NativeWindow extends Disposable { private touchBarMenu: IMenu | undefined; private readonly touchBarDisposables = this._register(new DisposableStore()); - private lastInstalledTouchedBar: ICommandAction[][] | undefined; + private lastInstalledTouchedBar: ISerializableMenuItemAction[][] | undefined; private readonly customTitleContextMenuDisposable = this._register(new DisposableStore()); @@ -504,18 +504,18 @@ export class NativeWindow extends Disposable { this.touchBarDisposables.add(createAndFillInActionBarActions(this.touchBarMenu, undefined, actions)); // Convert into command action multi array - const items: ICommandAction[][] = []; - let group: ICommandAction[] = []; + const items: ISerializableMenuItemAction[][] = []; + let group: ISerializableMenuItemAction[] = []; if (!disabled) { for (const action of actions) { // Command if (action instanceof MenuItemAction) { - if (ignoredItems.indexOf(action.item.id) >= 0) { + if (ignoredItems.indexOf(action.id) >= 0) { continue; // ignored } - group.push(action.item); + group.push(action.serialize()); } // Separator From 12979c45f17ca571cab023d3542626940d7f3ab6 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 10 Mar 2020 10:42:51 +0100 Subject: [PATCH 003/169] quick access - extensions picker --- .../browser/extensions.contribution.ts | 23 ++- .../browser/extensionsQuickAccess.ts | 184 ++++++++++++++++++ 2 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index cf17ac8d9c5..4d8f38e448b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -24,7 +24,7 @@ import { import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor'; import { StatusUpdater, MaliciousExtensionChecker, ExtensionsViewletViewsContribution, ExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; -import { IQuickOpenRegistry, Extensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; +import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import * as jsonContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; @@ -50,6 +50,8 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { CONTEXT_SYNC_ENABLEMENT } from 'vs/platform/userDataSync/common/userDataSync'; +import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { InstallExtensionQuickAccessProvider, ManageExtensionsQuickAccessProvider } from 'vs/workbench/contrib/extensions/browser/extensionsQuickAccess'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -59,7 +61,7 @@ Registry.as(OutputExtensions.OutputChannels) .registerChannel({ id: ExtensionsChannelId, label: ExtensionsLabel, log: false }); // Quickopen -Registry.as(Extensions.Quickopen).registerQuickOpenHandler( +Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( QuickOpenHandlerDescriptor.create( ExtensionsHandler, ExtensionsHandler.ID, @@ -70,6 +72,14 @@ Registry.as(Extensions.Quickopen).registerQuickOpenHandler( ) ); +// Quick Access +Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: ManageExtensionsQuickAccessProvider, + prefix: ManageExtensionsQuickAccessProvider.PREFIX, + placeholder: localize('manageExtensionsQuickAccessPlaceholder', "Press Enter to manage extensions."), + helpEntries: [{ description: localize('manageExtensionsHelp', "Manage Extensions"), needsEditor: false }] +}); + // Editor Registry.as(EditorExtensions.Editors).registerEditor( EditorDescriptor.create( @@ -476,7 +486,7 @@ class ExtensionsContributions implements IWorkbenchContribution { const canManageExtensions = extensionManagementServerService.localExtensionManagementServer || extensionManagementServerService.remoteExtensionManagementServer; if (canManageExtensions) { - Registry.as(Extensions.Quickopen).registerQuickOpenHandler( + Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( QuickOpenHandlerDescriptor.create( GalleryExtensionsHandler, GalleryExtensionsHandler.ID, @@ -486,6 +496,13 @@ class ExtensionsContributions implements IWorkbenchContribution { true ) ); + + Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: InstallExtensionQuickAccessProvider, + prefix: InstallExtensionQuickAccessProvider.PREFIX, + placeholder: localize('installExtensionQuickAccessPlaceholder', "Type the name of an extension to install or search."), + helpEntries: [{ description: localize('installExtensionQuickAccessHelp', "Install or Search Extensions"), needsEditor: false }] + }); } } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts b/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts new file mode 100644 index 00000000000..957afbdef1d --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts @@ -0,0 +1,184 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { localize } from 'vs/nls'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { ILogService } from 'vs/platform/log/common/log'; + +interface IInstallExtensionQuickPickItem extends IQuickPickItem { + extension?: { + name: string; + resolved?: IGalleryExtension; + } +} + +export class InstallExtensionQuickAccessProvider implements IQuickAccessProvider { + + static PREFIX = 'ext install '; + + constructor( + @IViewletService private readonly viewletService: IViewletService, + @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, + @IExtensionManagementService private readonly extensionsService: IExtensionManagementService, + @INotificationService private readonly notificationService: INotificationService, + @ILogService private readonly logService: ILogService + ) { } + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Disable filtering & sorting, we control the results + picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; + + // Update picker item + let extensionSearchToken: CancellationTokenSource | undefined = undefined; + const updatePickerItems = () => { + if (extensionSearchToken) { + extensionSearchToken.dispose(true); + } + + extensionSearchToken = new CancellationTokenSource(token); + + this.updateExtensionPickerItems(picker, extensionSearchToken.token); + }; + disposables.add(picker.onDidChangeValue(() => updatePickerItems())); + updatePickerItems(); + + // Open extensions view on accept + disposables.add(picker.onDidAccept(() => { + const [item] = picker.selectedItems; + if (item) { + picker.hide(); + if (item.extension) { + if (item.extension.resolved) { + this.installExtension(item.extension.resolved, item.extension.name); + } else { + this.searchExtension(item.extension.name); + } + } + } + })); + + return disposables; + } + + private async updateExtensionPickerItems(picker: IQuickPick, token: CancellationToken): Promise { + const value = picker.value.trim().substr(InstallExtensionQuickAccessProvider.PREFIX.length); + + // Nothing typed + if (!value) { + picker.busy = false; + picker.items = [{ + label: localize('type', "Type an extension name to install or search.") + }]; + + return; + } + + const genericSearchPickItem = { + label: localize('searchFor', "Press Enter to search for extension '{0}'.", value), + extension: { name: value } + }; + + // Extension ID typed: try to find it + if (/\./.test(value)) { + picker.busy = true; + + try { + const galleryResult = await this.galleryService.query({ names: [value], pageSize: 1 }, token); + if (token.isCancellationRequested) { + return; // return early if canceled + } + + const galleryExtension = galleryResult.firstPage[0]; + if (!galleryExtension) { + picker.items = [genericSearchPickItem]; + } else { + picker.items = [{ + label: localize('install', "Press Enter to install extension '{0}'.", value), + extension: { + name: value, + resolved: galleryExtension + } + }]; + } + } catch (error) { + if (token.isCancellationRequested) { + return; // expected error + } + + this.logService.error(error); + + picker.items = [genericSearchPickItem]; + } finally { + picker.busy = false; + } + } + + // Extension name typed: offer to search it + else { + picker.busy = false; + picker.items = [genericSearchPickItem]; + } + } + + private async installExtension(extension: IGalleryExtension, name: string): Promise { + try { + await openExtensionsViewlet(this.viewletService, `@id:${name}`); + await this.extensionsService.installFromGallery(extension); + } catch (error) { + this.notificationService.error(error); + } + } + + private async searchExtension(name: string): Promise { + openExtensionsViewlet(this.viewletService, name); + } +} + +export class ManageExtensionsQuickAccessProvider implements IQuickAccessProvider { + + static PREFIX = 'ext '; + + constructor(@IViewletService private readonly viewletService: IViewletService) { } + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Disable filtering & sorting, we control the results + picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; + + // Have just one static picker item + picker.items = [{ + label: localize('manage', "Press Enter to manage your extensions.") + }]; + + // Open extensions view on accept + disposables.add(picker.onDidAccept(() => { + const [item] = picker.selectedItems; + if (item) { + picker.hide(); + openExtensionsViewlet(this.viewletService); + } + })); + + return disposables; + } +} + +async function openExtensionsViewlet(viewletService: IViewletService, search = ''): Promise { + const viewlet = await viewletService.openViewlet(VIEWLET_ID, true); + const view = viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer | undefined; + view?.search(search); + view?.focus(); +} + From c2e6327356eed9cb12e2c319220dd42e480f9c4e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 10 Mar 2020 10:51:47 +0100 Subject: [PATCH 004/169] fix #92334 --- .../files/test/electron-browser/diskFileService.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/files/test/electron-browser/diskFileService.test.ts b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts index b0af12007e7..a5ed2443744 100644 --- a/src/vs/platform/files/test/electron-browser/diskFileService.test.ts +++ b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts @@ -132,10 +132,12 @@ suite('Disk File Service', function () { const disposables = new DisposableStore(); // Given issues such as https://github.com/microsoft/vscode/issues/78602 - // we see random test failures when accessing the native file system. To - // diagnose further, we retry node.js file access tests up to 3 times to - // rule out any random disk issue. + // and https://github.com/microsoft/vscode/issues/92334 we see random test + // failures when accessing the native file system. To diagnose further, we + // retry node.js file access tests up to 3 times to rule out any random disk + // issue and increase the timeout. this.retries(3); + this.timeout(1000 * 10); setup(async () => { const logService = new NullLogService(); From 42e26ac5bb2430637bd97dcffba409479490d265 Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 10 Mar 2020 10:53:45 +0100 Subject: [PATCH 005/169] Debug: make sure to open json settings when openning global configurations fixes #92264 --- .../contrib/debug/browser/debugConfigurationManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 2ce26e4be5f..319ccdb451e 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -697,7 +697,7 @@ class UserLaunch extends AbstractLaunch implements ILaunch { } async openConfigFile(_: boolean, preserveFocus: boolean): Promise<{ editor: IEditorPane | null, created: boolean }> { - const editor = await this.preferencesService.openGlobalSettings(false, { preserveFocus }); + const editor = await this.preferencesService.openGlobalSettings(true, { preserveFocus }); return ({ editor: withUndefinedAsNull(editor), created: false From 0defd915869beef3da8c0819071d268f28904a10 Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 10 Mar 2020 10:59:17 +0100 Subject: [PATCH 006/169] debug: skip start debugging test --- .../vscode-api-tests/src/singlefolder-tests/debug.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts index be3509a2bfa..7a60146353b 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts @@ -37,8 +37,7 @@ suite('Debug', function () { disposeAll(toDispose); }); - this.retries(2); - test('start debugging', async function () { + test.skip('start debugging', async function () { let stoppedEvents = 0; let variablesReceived: () => void; let initializedReceived: () => void; From b0791e5236f462fbb785e9a00319705607cefb9d Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 10 Mar 2020 11:31:36 +0100 Subject: [PATCH 007/169] quickInputList properly use AccessibilityProvider fixes #92324 --- .../base/parts/quickinput/browser/quickInputList.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/vs/base/parts/quickinput/browser/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts index c816f564689..2dee3651423 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputList.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts @@ -25,7 +25,7 @@ import { Action } from 'vs/base/common/actions'; import { getIconClass } from 'vs/base/parts/quickinput/browser/quickInputUtils'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IQuickInputOptions } from 'vs/base/parts/quickinput/browser/quickInput'; -import { IListOptions, List, IListStyles } from 'vs/base/browser/ui/list/listWidget'; +import { IListOptions, List, IListStyles, IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; const $ = dom.$; @@ -153,9 +153,6 @@ class ListElementRenderer implements IListRenderer element.saneLabel }, openController: { shouldOpen: () => false }, // Workaround #58124 setRowLineHeight: false, multipleSelectionSupport: false, horizontalScrolling: false, + accessibilityProvider } as IListOptions); this.list.getHTMLElement().id = id; this.disposables.push(this.list); @@ -606,3 +605,9 @@ function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: s return compareAnything(elementA.saneLabel, elementB.saneLabel, lookFor); } + +class QuickInputAccessibilityProvider implements IAccessibilityProvider { + getAriaLabel(element: ListElement): string | null { + return element.saneAriaLabel; + } +} From b8b1b243ced6cd9da9216f9337aece1420e352b1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 10 Mar 2020 11:53:21 +0100 Subject: [PATCH 008/169] quick access - debug picker --- src/vs/editor/contrib/quickAccess/gotoLine.ts | 6 +- .../standaloneGotoLineQuickAccess.ts | 4 +- .../quickinput/browser/helpQuickAccess.ts | 2 + .../quickaccess/gotoLineQuickAccess.ts | 4 +- .../debug/browser/debug.contribution.ts | 10 ++ .../contrib/debug/browser/debugQuickAccess.ts | 120 ++++++++++++++++++ .../browser/extensionsQuickAccess.ts | 1 - .../browser/quickAccess.contribution.ts | 10 +- .../quickaccess/browser/viewQuickAccess.ts | 6 +- 9 files changed, 147 insertions(+), 16 deletions(-) create mode 100644 src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts diff --git a/src/vs/editor/contrib/quickAccess/gotoLine.ts b/src/vs/editor/contrib/quickAccess/gotoLine.ts index 941c3f654f9..bc2cf02ef59 100644 --- a/src/vs/editor/contrib/quickAccess/gotoLine.ts +++ b/src/vs/editor/contrib/quickAccess/gotoLine.ts @@ -16,12 +16,12 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { AbstractEditorQuickAccessProvider } from 'vs/editor/contrib/quickAccess/quickAccess'; import { IPosition } from 'vs/editor/common/core/position'; -export const GOTO_LINE_PREFIX = ':'; - interface IGotoLineQuickPickItem extends IQuickPickItem, Partial { } export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditorQuickAccessProvider { + static PREFIX = ':'; + provide(picker: IQuickPick, token: CancellationToken): IDisposable { const disposables = new DisposableStore(); @@ -88,7 +88,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor // React to picker changes const updatePickerAndEditor = () => { - const position = this.parsePosition(editor, picker.value.trim().substr(GOTO_LINE_PREFIX.length)); + const position = this.parsePosition(editor, picker.value.trim().substr(AbstractGotoLineQuickAccessProvider.PREFIX.length)); const label = this.getPickLabel(editor, position.lineNumber, position.column); // Picker diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts index d513431044b..06495470f4d 100644 --- a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AbstractGotoLineQuickAccessProvider, GOTO_LINE_PREFIX } from 'vs/editor/contrib/quickAccess/gotoLine'; +import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLine'; import { Registry } from 'vs/platform/registry/common/platform'; import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -26,6 +26,6 @@ export class StandaloneGotoLineQuickAccessProvider extends AbstractGotoLineQuick Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ ctor: StandaloneGotoLineQuickAccessProvider, - prefix: GOTO_LINE_PREFIX, + prefix: AbstractGotoLineQuickAccessProvider.PREFIX, helpEntries: [{ description: GoToLineNLS.gotoLineActionLabel, needsEditor: true }] }); diff --git a/src/vs/platform/quickinput/browser/helpQuickAccess.ts b/src/vs/platform/quickinput/browser/helpQuickAccess.ts index d4a66d3556b..dbfd72a891a 100644 --- a/src/vs/platform/quickinput/browser/helpQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/helpQuickAccess.ts @@ -16,6 +16,8 @@ interface IHelpQuickAccessPickItem extends IQuickPickItem { export class HelpQuickAccessProvider implements IQuickAccessProvider { + static PREFIX = '?'; + private readonly registry = Registry.as(Extensions.Quickaccess); constructor(@IQuickInputService private readonly quickInputService: IQuickInputService) { } diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts index 6404881f956..9ba095db5c7 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts @@ -8,7 +8,7 @@ import { IKeyMods } from 'vs/platform/quickinput/common/quickInput'; import { IEditor } from 'vs/editor/common/editorCommon'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IRange } from 'vs/editor/common/core/range'; -import { AbstractGotoLineQuickAccessProvider, GOTO_LINE_PREFIX } from 'vs/editor/contrib/quickAccess/gotoLine'; +import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLine'; import { Registry } from 'vs/platform/registry/common/platform'; import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; @@ -40,7 +40,7 @@ export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProv Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ ctor: GotoLineQuickAccessProvider, - prefix: GOTO_LINE_PREFIX, + prefix: AbstractGotoLineQuickAccessProvider.PREFIX, placeholder: localize('gotoLineQuickAccessPlaceholder', "Type the line number and optional column to go to (e.g. 42:5 for line 42 and column 5)."), helpEntries: [{ description: localize('gotoLineQuickAccess', "Go to Line"), needsEditor: true }] }); diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 3cc92cc0715..8ca4ca5a4c9 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -52,6 +52,8 @@ import { CallStackEditorContribution } from 'vs/workbench/contrib/debug/browser/ import { BreakpointEditorContribution } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; +import { StartDebugQuickAccessProvider } from 'vs/workbench/contrib/debug/browser/debugQuickAccess'; class OpenDebugViewletAction extends ShowViewletAction { public static readonly ID = VIEWLET_ID; @@ -173,6 +175,14 @@ registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, nls.localize('inlin ) ); +// Register Quick Access +Registry.as(QuickAccessExtensions.Quickaccess).registerQuickAccessProvider({ + ctor: StartDebugQuickAccessProvider, + prefix: StartDebugQuickAccessProvider.PREFIX, + placeholder: nls.localize('startDebugPlaceholder', "Type the name of a launch configuration to run."), + helpEntries: [{ description: nls.localize('startDebugHelp', "Start Debug Configurations"), needsEditor: false }] +}); + // register service registerSingleton(IDebugService, service.DebugService); diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts new file mode 100644 index 00000000000..3aaf8394a62 --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts @@ -0,0 +1,120 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { localize } from 'vs/nls'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { matchesFuzzy } from 'vs/base/common/filters'; +import { StartAction } from 'vs/workbench/contrib/debug/browser/debugActions'; +import { withNullAsUndefined } from 'vs/base/common/types'; + +interface IDebugQuickPickItem extends IQuickPickItem { + run: () => Promise; +} + +export class StartDebugQuickAccessProvider implements IQuickAccessProvider { + + static PREFIX = 'debug '; + + constructor( + @IDebugService private readonly debugService: IDebugService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @ICommandService private readonly commandService: ICommandService, + @INotificationService private readonly notificationService: INotificationService + ) { } + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Disable filtering & sorting, we control the results + picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; + + // Add all view items & filter on type + const updatePickerItems = () => picker.items = this.getDebugPickItems(picker.value.trim().substr(StartDebugQuickAccessProvider.PREFIX.length)); + disposables.add(picker.onDidChangeValue(() => updatePickerItems())); + updatePickerItems(); + + // Open extensions view on accept + disposables.add(picker.onDidAccept(() => { + const [item] = picker.selectedItems; + if (item) { + picker.hide(); + item.run(); + } + })); + + return disposables; + } + + private getDebugPickItems(value: string): Array { + const picks: Array = []; + + const configManager = this.debugService.getConfigurationManager(); + + // Entries: configs + let lastGroup: string | undefined; + for (let config of configManager.getAllConfigurations()) { + const highlights = matchesFuzzy(value, config.name, true); + if (highlights) { + + // Separator + if (lastGroup !== config.presentation?.group) { + picks.push({ type: 'separator' }); + lastGroup = config.presentation?.group; + } + + // Launch entry + picks.push({ + label: config.name, + ariaLabel: localize('entryAriaLabel', "{0}, debug", config.name), + description: this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? config.launch.name : '', + highlights: { label: highlights }, + run: async () => { + if (StartAction.isEnabled(this.debugService)) { + this.debugService.getConfigurationManager().selectConfiguration(config.launch, config.name); + try { + await this.debugService.startDebugging(config.launch); + } catch (error) { + this.notificationService.error(error); + } + } + } + }); + } + } + + // Entries: launches + const visibleLaunches = configManager.getLaunches().filter(launch => !launch.hidden); + + // Separator + if (visibleLaunches.length > 0) { + picks.push({ type: 'separator' }); + } + + for (const launch of visibleLaunches) { + const label = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? + localize("addConfigTo", "Add Config ({0})...", launch.name) : + localize('addConfiguration', "Add Configuration..."); + + // Add Config entry + picks.push({ + label, + ariaLabel: localize('entryAriaLabel', "{0}, debug", label), + description: this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? launch.name : '', + highlights: { label: withNullAsUndefined(matchesFuzzy(value, label, true)) }, + run: async () => this.commandService.executeCommand('debug.addConfiguration', launch.uri.toString()) + }); + } + + return picks; + } +} + diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts b/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts index 957afbdef1d..f6e8cb4f4f8 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts @@ -181,4 +181,3 @@ async function openExtensionsViewlet(viewletService: IViewletService, search = ' view?.search(search); view?.focus(); } - diff --git a/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts b/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts index 8a03f8bd360..39fe3c5baf3 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; import { Registry } from 'vs/platform/registry/common/platform'; import { HelpQuickAccessProvider } from 'vs/platform/quickinput/browser/helpQuickAccess'; -import { ViewQuickAccessProvider, VIEW_QUICK_ACCESS_PREFIX } from 'vs/workbench/contrib/quickaccess/browser/viewQuickAccess'; +import { ViewQuickAccessProvider } from 'vs/workbench/contrib/quickaccess/browser/viewQuickAccess'; import { QUICK_ACCESS_COMMAND_ID } from 'vs/workbench/contrib/quickaccess/browser/quickAccessCommands'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; @@ -16,20 +16,20 @@ const registry = Registry.as(Extensions.Quickaccess); registry.defaultProvider = { ctor: HelpQuickAccessProvider, prefix: '', - placeholder: localize('helpQuickAccessPlaceholder', "Type '?' to get help on the actions you can take from here."), + placeholder: localize('defaultAccessPlaceholder', "Type the name of a file to open."), helpEntries: [{ description: localize('gotoFileQuickAccess', "Go to File"), needsEditor: false }] }; registry.registerQuickAccessProvider({ ctor: HelpQuickAccessProvider, - prefix: '?', - placeholder: localize('helpQuickAccessPlaceholder', "Type '?' to get help on the actions you can take from here."), + prefix: HelpQuickAccessProvider.PREFIX, + placeholder: localize('helpQuickAccessPlaceholder', "Type '{0}' to get help on the actions you can take from here.", HelpQuickAccessProvider.PREFIX), helpEntries: [{ description: localize('helpQuickAccess', "Show all Quick Access Providers"), needsEditor: false }] }); registry.registerQuickAccessProvider({ ctor: ViewQuickAccessProvider, - prefix: VIEW_QUICK_ACCESS_PREFIX, + prefix: ViewQuickAccessProvider.PREFIX, placeholder: localize('viewQuickAccessPlaceholder', "Type the name of a view, output channel or terminal to open."), helpEntries: [{ description: localize('viewQuickAccess', "Open View"), needsEditor: false }] }); diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts index d19c973f300..fd957338173 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -20,8 +20,6 @@ import { matchesFuzzy } from 'vs/base/common/filters'; import { fuzzyContains } from 'vs/base/common/strings'; import { withNullAsUndefined } from 'vs/base/common/types'; -export const VIEW_QUICK_ACCESS_PREFIX = 'view '; - interface IViewQuickPickItem extends IQuickPickItem { containerLabel: string; run: () => Promise; @@ -29,6 +27,8 @@ interface IViewQuickPickItem extends IQuickPickItem { export class ViewQuickAccessProvider implements IQuickAccessProvider { + static PREFIX = 'view '; + constructor( @IViewletService private readonly viewletService: IViewletService, @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @@ -47,7 +47,7 @@ export class ViewQuickAccessProvider implements IQuickAccessProvider { picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; // Add all view items & filter on type - const updatePickerItems = () => picker.items = this.getViewPickItems(picker.value.trim().substr(VIEW_QUICK_ACCESS_PREFIX.length)); + const updatePickerItems = () => picker.items = this.getViewPickItems(picker.value.trim().substr(ViewQuickAccessProvider.PREFIX.length)); disposables.add(picker.onDidChangeValue(() => updatePickerItems())); updatePickerItems(); From 14281cfc181ecf0b5fdbdee73db920a03d5776d1 Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Tue, 10 Mar 2020 14:54:57 +0100 Subject: [PATCH 009/169] Fixes #92352: Translation export fail --- build/lib/i18n.js | 11 +++++++---- build/lib/i18n.ts | 11 +++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/build/lib/i18n.js b/build/lib/i18n.js index ea1758ee57e..2e7415cd721 100644 --- a/build/lib/i18n.js +++ b/build/lib/i18n.js @@ -114,7 +114,7 @@ let XLF = /** @class */ (() => { for (let file in this.files) { this.appendNewLine(``, 2); for (let item of this.files[file]) { - this.addStringItem(item); + this.addStringItem(file, item); } this.appendNewLine('', 2); } @@ -154,9 +154,12 @@ let XLF = /** @class */ (() => { this.files[original].push({ id: realKey, message: message, comment: comment }); } } - addStringItem(item) { - if (!item.id || !item.message) { - throw new Error(`No item ID or value specified: ${JSON.stringify(item)}`); + addStringItem(file, item) { + if (!item.id || item.message === undefined || item.message === null) { + throw new Error(`No item ID or value specified: ${JSON.stringify(item)}. File: ${file}`); + } + if (item.message.length === 0) { + log(`Item with id ${item.id} in file ${file} has an empty message.`); } this.appendNewLine(``, 4); this.appendNewLine(`${item.message}`, 6); diff --git a/build/lib/i18n.ts b/build/lib/i18n.ts index d69109a9d05..b9fb3879872 100644 --- a/build/lib/i18n.ts +++ b/build/lib/i18n.ts @@ -201,7 +201,7 @@ export class XLF { for (let file in this.files) { this.appendNewLine(``, 2); for (let item of this.files[file]) { - this.addStringItem(item); + this.addStringItem(file, item); } this.appendNewLine('', 2); } @@ -243,9 +243,12 @@ export class XLF { } } - private addStringItem(item: Item): void { - if (!item.id || !item.message) { - throw new Error(`No item ID or value specified: ${JSON.stringify(item)}`); + private addStringItem(file: string, item: Item): void { + if (!item.id || item.message === undefined || item.message === null) { + throw new Error(`No item ID or value specified: ${JSON.stringify(item)}. File: ${file}`); + } + if (item.message.length === 0) { + log(`Item with id ${item.id} in file ${file} has an empty message.`); } this.appendNewLine(``, 4); From 683e0dfc9ba599cd93ad51e8f4a4b85163d6e2c5 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 10 Mar 2020 10:51:03 +0100 Subject: [PATCH 010/169] [theme] reload only when extension added --- .../services/themes/browser/workbenchThemeService.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 6f0d33b6f84..bc1cc740382 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -169,7 +169,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { // restore theme this.setColorTheme(prevColorId, 'auto'); prevColorId = undefined; - } else { + } else if (event.added.some(t => t.settingsId === this.currentColorTheme.settingsId)) { this.reloadCurrentColorTheme(); } } @@ -179,13 +179,12 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { let prevFileIconId: string | undefined = undefined; this.fileIconThemeRegistry.onDidChange(async event => { updateFileIconThemeConfigurationSchemas(event.themes); - if (await this.restoreFileIconTheme()) { // checks if theme from settings exists and is set // restore theme if (this.currentFileIconTheme.id === DEFAULT_FILE_ICON_THEME_ID && !types.isUndefined(prevFileIconId) && await this.fileIconThemeRegistry.findThemeById(prevFileIconId)) { this.setFileIconTheme(prevFileIconId, 'auto'); prevFileIconId = undefined; - } else { + } else if (event.added.some(t => t.settingsId === this.currentFileIconTheme.settingsId)) { this.reloadCurrentFileIconTheme(); } } else { @@ -193,18 +192,18 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { prevFileIconId = this.currentFileIconTheme.id; this.setFileIconTheme(DEFAULT_FILE_ICON_THEME_ID, 'auto'); } + }); let prevProductIconId: string | undefined = undefined; this.productIconThemeRegistry.onDidChange(async event => { updateProductIconThemeConfigurationSchemas(event.themes); - if (await this.restoreProductIconTheme()) { // checks if theme from settings exists and is set // restore theme if (this.currentProductIconTheme.id === DEFAULT_PRODUCT_ICON_THEME_ID && !types.isUndefined(prevProductIconId) && await this.productIconThemeRegistry.findThemeById(prevProductIconId)) { this.setProductIconTheme(prevProductIconId, 'auto'); prevProductIconId = undefined; - } else { + } else if (event.added.some(t => t.settingsId === this.currentProductIconTheme.settingsId)) { this.reloadCurrentProductIconTheme(); } } else { From 781dcc779edd7d5377f2d17265a8ff792097c27b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 10 Mar 2020 15:44:04 +0100 Subject: [PATCH 011/169] quick access - introduce a base PickerQuickAccessProvider helper to simplify all existing pickers --- .../platform/quickinput/common/quickAccess.ts | 79 ++++++++- .../browser/parts/editor/editorQuickAccess.ts | 75 ++++---- .../contrib/debug/browser/debugQuickAccess.ts | 46 +---- .../browser/extensionsQuickAccess.ts | 166 ++++++------------ .../quickaccess/browser/viewQuickAccess.ts | 37 +--- 5 files changed, 177 insertions(+), 226 deletions(-) diff --git a/src/vs/platform/quickinput/common/quickAccess.ts b/src/vs/platform/quickinput/common/quickAccess.ts index be8fba3f311..95d0145587e 100644 --- a/src/vs/platform/quickinput/common/quickAccess.ts +++ b/src/vs/platform/quickinput/common/quickAccess.ts @@ -4,12 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Registry } from 'vs/platform/registry/common/platform'; import { first } from 'vs/base/common/arrays'; import { startsWith } from 'vs/base/common/strings'; import { assertIsDefined } from 'vs/base/common/types'; -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IQuickPickSeparator } from 'vs/base/parts/quickinput/common/quickInput'; export interface IQuickAccessController { @@ -140,3 +141,77 @@ class QuickAccessRegistry implements IQuickAccessRegistry { } Registry.add(Extensions.Quickaccess, new QuickAccessRegistry()); + +//#region Helper class for simple picker based providers + +export interface IQuickPickItemRunnable extends IQuickPickItem { + + /** + * A method that will be executed when the pick item is accepted from the picker. + */ + run?: () => Promise; +} + +export abstract class PickerQuickAccessProvider implements IQuickAccessProvider { + + constructor(private prefix: string) { } + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Disable filtering & sorting, we control the results + picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; + + // Set initial picks and update on type + let picksCts: CancellationTokenSource | undefined = undefined; + const updatePickerItems = async () => { + + // Cancel any previous ask for picks and busy + picksCts?.dispose(true); + picker.busy = false; + + // Create new cancellation source for this run + picksCts = new CancellationTokenSource(token); + + // Collect picks and support both long running and short + const res = this.getPicks(picker.value.substr(this.prefix.length).trim(), picksCts.token); + if (Array.isArray(res)) { + picker.items = res; + } else { + picker.busy = true; + try { + picker.items = await res; + } finally { + picker.busy = false; + } + } + + this.getPicks(picker.value.substr(this.prefix.length).trim(), picksCts.token); + }; + disposables.add(picker.onDidChangeValue(() => updatePickerItems())); + updatePickerItems(); + + // Run the pick on accept and hide picker + disposables.add(picker.onDidAccept(() => { + const [item] = picker.selectedItems; + if (typeof item?.run === 'function') { + picker.hide(); + item.run(); + } + })); + + return disposables; + } + + /** + * Returns an array of picks and separators as needed. If the picks are resolved + * long running, the provided cancellation token should be used to cancel the + * operation when the token signals this. + * + * The implementor is responsible for filtering and sorting the picks given the + * provided `filter`. + */ + protected abstract getPicks(filter: string, token: CancellationToken): Array | Promise>; +} + +//#endregion diff --git a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts index 44b9bb08329..bbeac4604a8 100644 --- a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts +++ b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts @@ -4,60 +4,33 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IQuickPick, IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource } from 'vs/platform/quickinput/common/quickInput'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource } from 'vs/platform/quickinput/common/quickInput'; +import { PickerQuickAccessProvider, IQuickPickItemRunnable } from 'vs/platform/quickinput/common/quickAccess'; import { IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorsOrder, IEditorIdentifier, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; -import { prepareQuery, IPreparedQuery, ScorerCache, scoreItem, compareItemsByScore } from 'vs/base/common/fuzzyScorer'; -import { URI } from 'vs/base/common/uri'; +import { prepareQuery, scoreItem, compareItemsByScore } from 'vs/base/common/fuzzyScorer'; -interface IEditorQuickPickItem extends IQuickPickItemWithResource, IEditorIdentifier { - resource: URI | undefined; -} +interface IEditorQuickPickItem extends IQuickPickItemWithResource, IEditorIdentifier, IQuickPickItemRunnable { } -export abstract class BaseEditorQuickAccessProvider implements IQuickAccessProvider { - - protected abstract readonly prefix: string; +export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessProvider { constructor( + prefix: string, @IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService, @IEditorService protected readonly editorService: IEditorService, @IModelService private readonly modelService: IModelService, @IModeService private readonly modeService: IModeService ) { + super(prefix); } - provide(picker: IQuickPick, token: CancellationToken): IDisposable { - const disposables = new DisposableStore(); - - // Disable filtering & sorting, we control the results - picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; - - // Add all view items & filter on type + protected getPicks(filter: string): Array { + const query = prepareQuery(filter); const scorerCache = Object.create(null); - const updatePickerItems = () => picker.items = this.getEditorPickItems(prepareQuery(picker.value.trim().substr(this.prefix.length)), scorerCache); - disposables.add(picker.onDidChangeValue(() => updatePickerItems())); - updatePickerItems(); - - // Open the picked view on accept - disposables.add(picker.onDidAccept(() => { - const [item] = picker.selectedItems; - if (item) { - picker.hide(); - this.editorGroupService.getGroup(item.groupId)?.openEditor(item.editor); - } - })); - - return disposables; - } - - private getEditorPickItems(query: IPreparedQuery, scorerCache: ScorerCache): Array { const filteredEditorEntries = this.doGetEditorPickItems().filter(entry => { if (!query.value) { return true; @@ -121,7 +94,8 @@ export abstract class BaseEditorQuickAccessProvider implements IQuickAccessProvi ariaLabel: localize('entryAriaLabel', "{0}, editor picker", editor.getName()), description: editor.getDescription(), iconClasses: getIconClasses(this.modelService, this.modeService, resource), - italic: !this.editorGroupService.getGroup(groupId)?.isPinned(editor) + italic: !this.editorGroupService.getGroup(groupId)?.isPinned(editor), + run: async () => this.editorGroupService.getGroup(groupId)?.openEditor(editor) }; }); } @@ -135,7 +109,14 @@ export class ActiveGroupEditorsByMostRecentlyUsedQuickAccess extends BaseEditorQ static PREFIX = 'edt active '; - readonly prefix = ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX; + constructor( + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IEditorService editorService: IEditorService, + @IModelService modelService: IModelService, + @IModeService modeService: IModeService + ) { + super(ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX, editorGroupService, editorService, modelService, modeService); + } protected doGetEditors(): IEditorIdentifier[] { const group = this.editorGroupService.activeGroup; @@ -153,7 +134,14 @@ export class AllEditorsByAppearanceQuickAccess extends BaseEditorQuickAccessProv static PREFIX = 'edt '; - readonly prefix = AllEditorsByAppearanceQuickAccess.PREFIX; + constructor( + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IEditorService editorService: IEditorService, + @IModelService modelService: IModelService, + @IModeService modeService: IModeService + ) { + super(AllEditorsByAppearanceQuickAccess.PREFIX, editorGroupService, editorService, modelService, modeService); + } protected doGetEditors(): IEditorIdentifier[] { const entries: IEditorIdentifier[] = []; @@ -177,7 +165,14 @@ export class AllEditorsByMostRecentlyUsedQuickAccess extends BaseEditorQuickAcce static PREFIX = 'edt mru '; - readonly prefix = AllEditorsByMostRecentlyUsedQuickAccess.PREFIX; + constructor( + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IEditorService editorService: IEditorService, + @IModelService modelService: IModelService, + @IModeService modeService: IModeService + ) { + super(AllEditorsByMostRecentlyUsedQuickAccess.PREFIX, editorGroupService, editorService, modelService, modeService); + } protected doGetEditors(): IEditorIdentifier[] { const entries: IEditorIdentifier[] = []; diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts index 3aaf8394a62..b498b614358 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts @@ -3,11 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { PickerQuickAccessProvider, IQuickPickItemRunnable } from 'vs/platform/quickinput/common/quickAccess'; import { localize } from 'vs/nls'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; @@ -16,11 +14,7 @@ import { matchesFuzzy } from 'vs/base/common/filters'; import { StartAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { withNullAsUndefined } from 'vs/base/common/types'; -interface IDebugQuickPickItem extends IQuickPickItem { - run: () => Promise; -} - -export class StartDebugQuickAccessProvider implements IQuickAccessProvider { +export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { static PREFIX = 'debug '; @@ -29,40 +23,19 @@ export class StartDebugQuickAccessProvider implements IQuickAccessProvider { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @ICommandService private readonly commandService: ICommandService, @INotificationService private readonly notificationService: INotificationService - ) { } - - provide(picker: IQuickPick, token: CancellationToken): IDisposable { - const disposables = new DisposableStore(); - - // Disable filtering & sorting, we control the results - picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; - - // Add all view items & filter on type - const updatePickerItems = () => picker.items = this.getDebugPickItems(picker.value.trim().substr(StartDebugQuickAccessProvider.PREFIX.length)); - disposables.add(picker.onDidChangeValue(() => updatePickerItems())); - updatePickerItems(); - - // Open extensions view on accept - disposables.add(picker.onDidAccept(() => { - const [item] = picker.selectedItems; - if (item) { - picker.hide(); - item.run(); - } - })); - - return disposables; + ) { + super(StartDebugQuickAccessProvider.PREFIX); } - private getDebugPickItems(value: string): Array { - const picks: Array = []; + protected getPicks(filter: string): (IQuickPickSeparator | IQuickPickItemRunnable)[] { + const picks: Array = []; const configManager = this.debugService.getConfigurationManager(); // Entries: configs let lastGroup: string | undefined; for (let config of configManager.getAllConfigurations()) { - const highlights = matchesFuzzy(value, config.name, true); + const highlights = matchesFuzzy(filter, config.name, true); if (highlights) { // Separator @@ -109,7 +82,7 @@ export class StartDebugQuickAccessProvider implements IQuickAccessProvider { label, ariaLabel: localize('entryAriaLabel', "{0}, debug", label), description: this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? launch.name : '', - highlights: { label: withNullAsUndefined(matchesFuzzy(value, label, true)) }, + highlights: { label: withNullAsUndefined(matchesFuzzy(filter, label, true)) }, run: async () => this.commandService.executeCommand('debug.addConfiguration', launch.uri.toString()) }); } @@ -117,4 +90,3 @@ export class StartDebugQuickAccessProvider implements IQuickAccessProvider { return picks; } } - diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts b/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts index f6e8cb4f4f8..cf7f7454643 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts @@ -3,25 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickPickItemRunnable, PickerQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { localize } from 'vs/nls'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions'; import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ILogService } from 'vs/platform/log/common/log'; -interface IInstallExtensionQuickPickItem extends IQuickPickItem { - extension?: { - name: string; - resolved?: IGalleryExtension; - } -} - -export class InstallExtensionQuickAccessProvider implements IQuickAccessProvider { +export class InstallExtensionQuickAccessProvider extends PickerQuickAccessProvider { static PREFIX = 'ext install '; @@ -31,103 +23,59 @@ export class InstallExtensionQuickAccessProvider implements IQuickAccessProvider @IExtensionManagementService private readonly extensionsService: IExtensionManagementService, @INotificationService private readonly notificationService: INotificationService, @ILogService private readonly logService: ILogService - ) { } - - provide(picker: IQuickPick, token: CancellationToken): IDisposable { - const disposables = new DisposableStore(); - - // Disable filtering & sorting, we control the results - picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; - - // Update picker item - let extensionSearchToken: CancellationTokenSource | undefined = undefined; - const updatePickerItems = () => { - if (extensionSearchToken) { - extensionSearchToken.dispose(true); - } - - extensionSearchToken = new CancellationTokenSource(token); - - this.updateExtensionPickerItems(picker, extensionSearchToken.token); - }; - disposables.add(picker.onDidChangeValue(() => updatePickerItems())); - updatePickerItems(); - - // Open extensions view on accept - disposables.add(picker.onDidAccept(() => { - const [item] = picker.selectedItems; - if (item) { - picker.hide(); - if (item.extension) { - if (item.extension.resolved) { - this.installExtension(item.extension.resolved, item.extension.name); - } else { - this.searchExtension(item.extension.name); - } - } - } - })); - - return disposables; + ) { + super(InstallExtensionQuickAccessProvider.PREFIX); } - private async updateExtensionPickerItems(picker: IQuickPick, token: CancellationToken): Promise { - const value = picker.value.trim().substr(InstallExtensionQuickAccessProvider.PREFIX.length); + protected getPicks(filter: string, token: CancellationToken): Array | Promise> { // Nothing typed - if (!value) { - picker.busy = false; - picker.items = [{ + if (!filter) { + return [{ label: localize('type', "Type an extension name to install or search.") }]; - - return; } - const genericSearchPickItem = { - label: localize('searchFor', "Press Enter to search for extension '{0}'.", value), - extension: { name: value } + const genericSearchPickItem: IQuickPickItemRunnable = { + label: localize('searchFor', "Press Enter to search for extension '{0}'.", filter), + run: () => this.searchExtension(filter) }; // Extension ID typed: try to find it - if (/\./.test(value)) { - picker.busy = true; - - try { - const galleryResult = await this.galleryService.query({ names: [value], pageSize: 1 }, token); - if (token.isCancellationRequested) { - return; // return early if canceled - } - - const galleryExtension = galleryResult.firstPage[0]; - if (!galleryExtension) { - picker.items = [genericSearchPickItem]; - } else { - picker.items = [{ - label: localize('install', "Press Enter to install extension '{0}'.", value), - extension: { - name: value, - resolved: galleryExtension - } - }]; - } - } catch (error) { - if (token.isCancellationRequested) { - return; // expected error - } - - this.logService.error(error); - - picker.items = [genericSearchPickItem]; - } finally { - picker.busy = false; - } + if (/\./.test(filter)) { + return this.getPicksForExtensionId(filter, genericSearchPickItem, token); } // Extension name typed: offer to search it else { - picker.busy = false; - picker.items = [genericSearchPickItem]; + return [genericSearchPickItem]; + } + } + + protected async getPicksForExtensionId(filter: string, fallback: IQuickPickItemRunnable, token: CancellationToken): Promise> { + try { + const galleryResult = await this.galleryService.query({ names: [filter], pageSize: 1 }, token); + if (token.isCancellationRequested) { + return []; // return early if canceled + } + + const galleryExtension = galleryResult.firstPage[0]; + if (!galleryExtension) { + return [fallback]; + } else { + return [{ + label: localize('install', "Press Enter to install extension '{0}'.", filter), + run: () => this.installExtension(galleryExtension, filter) + }]; + } + } catch (error) { + if (token.isCancellationRequested) { + return []; // expected error + } + + this.logService.error(error); + + return [fallback]; } } @@ -145,33 +93,19 @@ export class InstallExtensionQuickAccessProvider implements IQuickAccessProvider } } -export class ManageExtensionsQuickAccessProvider implements IQuickAccessProvider { +export class ManageExtensionsQuickAccessProvider extends PickerQuickAccessProvider { static PREFIX = 'ext '; - constructor(@IViewletService private readonly viewletService: IViewletService) { } + constructor(@IViewletService private readonly viewletService: IViewletService) { + super(ManageExtensionsQuickAccessProvider.PREFIX); + } - provide(picker: IQuickPick, token: CancellationToken): IDisposable { - const disposables = new DisposableStore(); - - // Disable filtering & sorting, we control the results - picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; - - // Have just one static picker item - picker.items = [{ - label: localize('manage', "Press Enter to manage your extensions.") + protected getPicks(): Array { + return [{ + label: localize('manage', "Press Enter to manage your extensions."), + run: () => openExtensionsViewlet(this.viewletService) }]; - - // Open extensions view on accept - disposables.add(picker.onDidAccept(() => { - const [item] = picker.selectedItems; - if (item) { - picker.hide(); - openExtensionsViewlet(this.viewletService); - } - })); - - return disposables; } } diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts index fd957338173..f49065d0679 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -5,10 +5,8 @@ import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickPickItemRunnable, PickerQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewDescriptorService, IViewsService, ViewContainer, IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry } from 'vs/workbench/common/views'; import { IOutputService } from 'vs/workbench/contrib/output/common/output'; @@ -20,12 +18,11 @@ import { matchesFuzzy } from 'vs/base/common/filters'; import { fuzzyContains } from 'vs/base/common/strings'; import { withNullAsUndefined } from 'vs/base/common/types'; -interface IViewQuickPickItem extends IQuickPickItem { +interface IViewQuickPickItem extends IQuickPickItemRunnable { containerLabel: string; - run: () => Promise; } -export class ViewQuickAccessProvider implements IQuickAccessProvider { +export class ViewQuickAccessProvider extends PickerQuickAccessProvider { static PREFIX = 'view '; @@ -38,32 +35,10 @@ export class ViewQuickAccessProvider implements IQuickAccessProvider { @IPanelService private readonly panelService: IPanelService, @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { + super(ViewQuickAccessProvider.PREFIX); } - provide(picker: IQuickPick, token: CancellationToken): IDisposable { - const disposables = new DisposableStore(); - - // Disable filtering & sorting, we control the results - picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; - - // Add all view items & filter on type - const updatePickerItems = () => picker.items = this.getViewPickItems(picker.value.trim().substr(ViewQuickAccessProvider.PREFIX.length)); - disposables.add(picker.onDidChangeValue(() => updatePickerItems())); - updatePickerItems(); - - // Open the picked view on accept - disposables.add(picker.onDidAccept(() => { - const [item] = picker.selectedItems; - if (item) { - picker.hide(); - item.run(); - } - })); - - return disposables; - } - - private getViewPickItems(filter: string): Array { + protected getPicks(filter: string): Array { const filteredViewEntries = this.doGetViewPickItems().filter(entry => { if (!filter) { return true; From b737cc0861f05e157ca9e86ef5698777b94e221c Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 10 Mar 2020 08:41:32 -0700 Subject: [PATCH 012/169] debug: show errros from scopes request --- .../contrib/debug/browser/variablesView.ts | 5 +++-- src/vs/workbench/contrib/debug/common/debug.ts | 1 + .../contrib/debug/common/debugModel.ts | 17 ++++++++++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 6011bdbd99a..a2f1c8121d8 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -217,7 +217,7 @@ function isViewModel(obj: any): obj is IViewModel { export class VariablesDataSource implements IAsyncDataSource { hasChildren(element: IViewModel | IExpression | IScope): boolean { - if (isViewModel(element) || element instanceof Scope) { + if (isViewModel(element)) { return true; } @@ -270,7 +270,8 @@ class ScopesRenderer implements ITreeRenderer, index: number, templateData: IScopeTemplateData): void { - templateData.label.set(element.element.name, createMatches(element.filterData)); + const name = element.element.name; + templateData.label.set(name, element.element.reference === -1 ? [{ start: 0, end: name.length, extraClasses: 'error' }] : createMatches(element.filterData)); } disposeTemplate(templateData: IScopeTemplateData): void { diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 5ee3d8f4117..d989002308b 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -301,6 +301,7 @@ export interface IScope extends IExpressionContainer { readonly name: string; readonly expensive: boolean; readonly range?: IRange; + readonly hasChildren: boolean; } export interface IStackFrame extends ITreeElement { diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index eb63050bddf..d0ff48e420e 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -269,6 +269,21 @@ export class Scope extends ExpressionContainer implements IScope { } } +export class ErrorScope extends Scope { + + constructor( + stackFrame: IStackFrame, + index: number, + message: string, + ) { + super(stackFrame, index, message, -1, false); + } + + toString(): string { + return this.name; + } +} + export class StackFrame implements IStackFrame { private scopes: Promise | undefined; @@ -293,7 +308,7 @@ export class StackFrame implements IStackFrame { return response && response.body && response.body.scopes ? response.body.scopes.map((rs, index) => new Scope(this, index, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables, rs.line && rs.column && rs.endLine && rs.endColumn ? new Range(rs.line, rs.column, rs.endLine, rs.endColumn) : undefined)) : []; - }, err => []); + }, err => [new ErrorScope(this, 0, err.message)]); } return this.scopes; From cb361ac3a4d9a4fee8443690cdf98efd4f4af626 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 10 Mar 2020 16:49:38 +0100 Subject: [PATCH 013/169] quick access - add tasks picker --- .../platform/quickinput/common/quickAccess.ts | 39 ++++- .../browser/parts/editor/editorQuickAccess.ts | 6 +- .../contrib/debug/browser/debugQuickAccess.ts | 12 +- .../browser/extensionsQuickAccess.ts | 20 +-- .../quickaccess/browser/viewQuickAccess.ts | 18 +-- .../tasks/browser/task.contribution.ts | 13 ++ .../contrib/tasks/browser/tasksQuickAccess.ts | 140 ++++++++++++++++++ 7 files changed, 213 insertions(+), 35 deletions(-) create mode 100644 src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts diff --git a/src/vs/platform/quickinput/common/quickAccess.ts b/src/vs/platform/quickinput/common/quickAccess.ts index 95d0145587e..8197091879a 100644 --- a/src/vs/platform/quickinput/common/quickAccess.ts +++ b/src/vs/platform/quickinput/common/quickAccess.ts @@ -144,15 +144,27 @@ Registry.add(Extensions.Quickaccess, new QuickAccessRegistry()); //#region Helper class for simple picker based providers -export interface IQuickPickItemRunnable extends IQuickPickItem { +export interface IPickerQuickAccessItem extends IQuickPickItem { /** - * A method that will be executed when the pick item is accepted from the picker. + * A method that will be executed when the pick item is accepted from + * the picker. The picker will close automatically before running this. + */ + accept?(): void; + + /** + * A method that will be executed when a button of the pick item was + * clicked on. The picker will only close if `true` is returned. + * + * @param buttonIndex index of the button of the item that + * was clicked. + * + * @returns a valud indicating if the picker should close or not. */ - run?: () => Promise; + trigger?(buttonIndex: number): boolean; } -export abstract class PickerQuickAccessProvider implements IQuickAccessProvider { +export abstract class PickerQuickAccessProvider implements IQuickAccessProvider { constructor(private prefix: string) { } @@ -191,12 +203,25 @@ export abstract class PickerQuickAccessProvider updatePickerItems())); updatePickerItems(); - // Run the pick on accept and hide picker + // Accept the pick on accept and hide picker disposables.add(picker.onDidAccept(() => { const [item] = picker.selectedItems; - if (typeof item?.run === 'function') { + if (typeof item?.accept === 'function') { picker.hide(); - item.run(); + item.accept(); + } + })); + + // Trigger the pick with button index if button triggered + disposables.add(picker.onDidTriggerItemButton(({ button, item }) => { + if (typeof item.trigger === 'function') { + const buttonIndex = item.buttons?.indexOf(button) ?? -1; + if (buttonIndex >= 0) { + const hide = item.trigger(buttonIndex); + if (hide !== false) { + picker.hide(); + } + } } })); diff --git a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts index bbeac4604a8..3664da1b2d7 100644 --- a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts +++ b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource } from 'vs/platform/quickinput/common/quickInput'; -import { PickerQuickAccessProvider, IQuickPickItemRunnable } from 'vs/platform/quickinput/common/quickAccess'; +import { PickerQuickAccessProvider, IPickerQuickAccessItem } from 'vs/platform/quickinput/common/quickAccess'; import { IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorsOrder, IEditorIdentifier, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -14,7 +14,7 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { prepareQuery, scoreItem, compareItemsByScore } from 'vs/base/common/fuzzyScorer'; -interface IEditorQuickPickItem extends IQuickPickItemWithResource, IEditorIdentifier, IQuickPickItemRunnable { } +interface IEditorQuickPickItem extends IQuickPickItemWithResource, IEditorIdentifier, IPickerQuickAccessItem { } export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessProvider { @@ -95,7 +95,7 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro description: editor.getDescription(), iconClasses: getIconClasses(this.modelService, this.modeService, resource), italic: !this.editorGroupService.getGroup(groupId)?.isPinned(editor), - run: async () => this.editorGroupService.getGroup(groupId)?.openEditor(editor) + accept: () => this.editorGroupService.getGroup(groupId)?.openEditor(editor) }; }); } diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts index b498b614358..6bb5804dc23 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { PickerQuickAccessProvider, IQuickPickItemRunnable } from 'vs/platform/quickinput/common/quickAccess'; +import { PickerQuickAccessProvider, IPickerQuickAccessItem } from 'vs/platform/quickinput/common/quickAccess'; import { localize } from 'vs/nls'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; @@ -14,7 +14,7 @@ import { matchesFuzzy } from 'vs/base/common/filters'; import { StartAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { withNullAsUndefined } from 'vs/base/common/types'; -export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { +export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { static PREFIX = 'debug '; @@ -27,8 +27,8 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider = []; + protected getPicks(filter: string): (IQuickPickSeparator | IPickerQuickAccessItem)[] { + const picks: Array = []; const configManager = this.debugService.getConfigurationManager(); @@ -50,7 +50,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { + accept: async () => { if (StartAction.isEnabled(this.debugService)) { this.debugService.getConfigurationManager().selectConfiguration(config.launch, config.name); try { @@ -83,7 +83,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider this.commandService.executeCommand('debug.addConfiguration', launch.uri.toString()) + accept: () => this.commandService.executeCommand('debug.addConfiguration', launch.uri.toString()) }); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts b/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts index cf7f7454643..9af42ab31ed 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { IQuickPickItemRunnable, PickerQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; import { CancellationToken } from 'vs/base/common/cancellation'; import { localize } from 'vs/nls'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -13,7 +13,7 @@ import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtensio import { INotificationService } from 'vs/platform/notification/common/notification'; import { ILogService } from 'vs/platform/log/common/log'; -export class InstallExtensionQuickAccessProvider extends PickerQuickAccessProvider { +export class InstallExtensionQuickAccessProvider extends PickerQuickAccessProvider { static PREFIX = 'ext install '; @@ -27,7 +27,7 @@ export class InstallExtensionQuickAccessProvider extends PickerQuickAccessProvid super(InstallExtensionQuickAccessProvider.PREFIX); } - protected getPicks(filter: string, token: CancellationToken): Array | Promise> { + protected getPicks(filter: string, token: CancellationToken): Array | Promise> { // Nothing typed if (!filter) { @@ -36,9 +36,9 @@ export class InstallExtensionQuickAccessProvider extends PickerQuickAccessProvid }]; } - const genericSearchPickItem: IQuickPickItemRunnable = { + const genericSearchPickItem: IPickerQuickAccessItem = { label: localize('searchFor', "Press Enter to search for extension '{0}'.", filter), - run: () => this.searchExtension(filter) + accept: () => this.searchExtension(filter) }; // Extension ID typed: try to find it @@ -52,7 +52,7 @@ export class InstallExtensionQuickAccessProvider extends PickerQuickAccessProvid } } - protected async getPicksForExtensionId(filter: string, fallback: IQuickPickItemRunnable, token: CancellationToken): Promise> { + protected async getPicksForExtensionId(filter: string, fallback: IPickerQuickAccessItem, token: CancellationToken): Promise> { try { const galleryResult = await this.galleryService.query({ names: [filter], pageSize: 1 }, token); if (token.isCancellationRequested) { @@ -65,7 +65,7 @@ export class InstallExtensionQuickAccessProvider extends PickerQuickAccessProvid } else { return [{ label: localize('install', "Press Enter to install extension '{0}'.", filter), - run: () => this.installExtension(galleryExtension, filter) + accept: () => this.installExtension(galleryExtension, filter) }]; } } catch (error) { @@ -93,7 +93,7 @@ export class InstallExtensionQuickAccessProvider extends PickerQuickAccessProvid } } -export class ManageExtensionsQuickAccessProvider extends PickerQuickAccessProvider { +export class ManageExtensionsQuickAccessProvider extends PickerQuickAccessProvider { static PREFIX = 'ext '; @@ -101,10 +101,10 @@ export class ManageExtensionsQuickAccessProvider extends PickerQuickAccessProvid super(ManageExtensionsQuickAccessProvider.PREFIX); } - protected getPicks(): Array { + protected getPicks(): Array { return [{ label: localize('manage', "Press Enter to manage your extensions."), - run: () => openExtensionsViewlet(this.viewletService) + accept: () => openExtensionsViewlet(this.viewletService) }]; } } diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts index f49065d0679..0141925e954 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { IQuickPickItemRunnable, PickerQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewDescriptorService, IViewsService, ViewContainer, IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry } from 'vs/workbench/common/views'; import { IOutputService } from 'vs/workbench/contrib/output/common/output'; @@ -18,7 +18,7 @@ import { matchesFuzzy } from 'vs/base/common/filters'; import { fuzzyContains } from 'vs/base/common/strings'; import { withNullAsUndefined } from 'vs/base/common/types'; -interface IViewQuickPickItem extends IQuickPickItemRunnable { +interface IViewQuickPickItem extends IPickerQuickAccessItem { containerLabel: string; } @@ -33,7 +33,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider this.viewsService.openView(view.id, true) + accept: () => this.viewsService.openView(view.id, true) }); } } @@ -111,7 +111,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider this.viewletService.openViewlet(viewlet.id, true) + accept: () => this.viewletService.openViewlet(viewlet.id, true) }); } } @@ -122,7 +122,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider this.panelService.openPanel(panel.id, true) + accept: () => this.panelService.openPanel(panel.id, true) }); } @@ -140,10 +140,10 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider { + accept: async () => { await this.terminalService.showPanel(true); - return this.terminalService.setActiveInstance(terminal); + this.terminalService.setActiveInstance(terminal); } }); }); @@ -155,7 +155,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider this.outputService.showChannel(channel.id) + accept: () => this.outputService.showChannel(channel.id) }); } diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index ffeb8859239..58ccc008066 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -39,6 +39,8 @@ import { AbstractTaskService, ConfigureTaskAction } from 'vs/workbench/contrib/t import { tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; +import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; +import { TasksQuickAccessProvider } from 'vs/workbench/contrib/tasks/browser/tasksQuickAccess'; let tasksCategory = nls.localize('tasksCategory', "Tasks"); @@ -274,6 +276,17 @@ quickOpenRegistry.registerQuickOpenHandler( ) ); +// Register Quick Access +const quickAccessRegistry = (Registry.as(QuickAccessExtensions.Quickaccess)); + +quickAccessRegistry.registerQuickAccessProvider({ + ctor: TasksQuickAccessProvider, + prefix: TasksQuickAccessProvider.PREFIX, + contextKey: tasksPickerContextKey, + placeholder: nls.localize('tasksQuickAccessPlaceholder', "Type the name of a task to run."), + helpEntries: [{ description: nls.localize('tasksQuickAccessHelp', "Run Task"), needsEditor: false }] +}); + const actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar); actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionContributor); diff --git a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts new file mode 100644 index 00000000000..876a620b9ca --- /dev/null +++ b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts @@ -0,0 +1,140 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { matchesFuzzy } from 'vs/base/common/filters'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; +import { CustomTask, ContributedTask } from 'vs/workbench/contrib/tasks/common/tasks'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IStringDictionary } from 'vs/base/common/collections'; + +export class TasksQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = 'task '; + + private activationPromise: Promise; + + constructor( + @IExtensionService extensionService: IExtensionService, + @ITaskService private taskService: ITaskService + ) { + super(TasksQuickAccessProvider.PREFIX); + + this.activationPromise = extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'); + } + + protected async getPicks(filter: string, token: CancellationToken): Promise> { + + // always await extensions + await this.activationPromise; + + if (token.isCancellationRequested) { + return []; + } + + // Resolve custom and contributed tasks + const tasks = (await this.taskService.tasks()) + .filter((task): task is CustomTask | ContributedTask => ContributedTask.is(task) || CustomTask.is(task)); + + if (token.isCancellationRequested) { + return []; + } + + this.taskService.migrateRecentTasks(tasks); + + // Split up tasks across recently used, configured and detected + const recentlyUsedTasks = this.taskService.getRecentlyUsedTasks(); + const recent: Array = []; + const configured: CustomTask[] = []; + const detected: ContributedTask[] = []; + const taskMap: IStringDictionary = Object.create(null); + for (const task of tasks) { + const key = task.getRecentlyUsedKey(); + if (key) { + taskMap[key] = task; + } + } + + recentlyUsedTasks.keys().forEach(key => { + const task = taskMap[key]; + if (task) { + recent.push(task); + } + }); + + for (const task of tasks) { + const key = task.getRecentlyUsedKey(); + if (!key || !recentlyUsedTasks.has(key)) { + if (CustomTask.is(task)) { + configured.push(task); + } else { + detected.push(task); + } + } + } + + const taskPicks: Array = []; + const sorter = this.taskService.createSorter(); + + // Fill picks in sorted order + + this.fillPicks(taskPicks, filter, recent, localize('recentlyUsed', 'recently used tasks')); + + configured.sort((a, b) => sorter.compare(a, b)); + this.fillPicks(taskPicks, filter, configured, localize('configured', 'configured tasks')); + + detected.sort((a, b) => sorter.compare(a, b)); + this.fillPicks(taskPicks, filter, detected, localize('detected', 'detected tasks')); + + return taskPicks; + } + + private fillPicks(taskPicks: Array, input: string, tasks: Array, groupLabel: string): void { + let first = true; + for (const task of tasks) { + const highlights = matchesFuzzy(input, task._label); + if (!highlights) { + continue; + } + if (first) { + first = false; + taskPicks.push({ type: 'separator', label: groupLabel }); + } + taskPicks.push({ + label: task._label, + ariaLabel: localize('entryAriaLabel', "{0}, tasks", task._label), + description: this.taskService.getTaskDescription(task), + highlights: { label: highlights }, + buttons: (() => { + const buttons = []; + + if (ContributedTask.is(task) || CustomTask.is(task)) { + buttons.push({ + iconClass: 'codicon-gear', + tooltip: localize('customizeTask', "Configure Task") + }); + } + + return buttons; + })(), + accept: () => { + this.taskService.run(task, { attachProblemMatcher: true }); + }, + trigger: () => { + if (ContributedTask.is(task)) { + this.taskService.customize(task, undefined, true); + } else { + this.taskService.openConfig(task); + } + + return true; // close picker + } + }); + } + } +} From 89e1209b3326c1d0a932468155f844094b0cb5a1 Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 10 Mar 2020 17:10:10 +0100 Subject: [PATCH 014/169] explorer: deemphasize items which should be excluded but are shown since they are open --- src/vs/platform/theme/common/colorRegistry.ts | 1 + .../browser/views/explorerDecorationsProvider.ts | 7 ++++++- .../files/browser/views/explorerViewer.ts | 3 +++ .../contrib/files/common/explorerModel.ts | 16 ++++++++++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 35599b40d05..54575132334 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -365,6 +365,7 @@ export const listFilterWidgetNoMatchesOutline = registerColor('listFilterWidget. export const listFilterMatchHighlight = registerColor('list.filterMatchBackground', { dark: editorFindMatchHighlight, light: editorFindMatchHighlight, hc: null }, nls.localize('listFilterMatchHighlight', 'Background color of the filtered match.')); export const listFilterMatchHighlightBorder = registerColor('list.filterMatchBorder', { dark: editorFindMatchHighlightBorder, light: editorFindMatchHighlightBorder, hc: contrastBorder }, nls.localize('listFilterMatchHighlightBorder', 'Border color of the filtered match.')); export const treeIndentGuidesStroke = registerColor('tree.indentGuidesStroke', { dark: '#585858', light: '#a9a9a9', hc: '#a9a9a9' }, nls.localize('treeIndentGuidesStroke', "Tree stroke color for the indentation guides.")); +export const listDeemphasizedForeground = registerColor('list.deemphasizedForeground', { dark: '#8C8C8C', light: '#8E8E90', hc: '#A7A8A9' }, nls.localize('listDeemphasizedForeground', "List/Tree foreground color for items that are deemphasized. ")); /** * Menu colors diff --git a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts index 0f09459edf8..090962b14d7 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts @@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { localize } from 'vs/nls'; 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 { listInvalidItemForeground, listDeemphasizedForeground } from 'vs/platform/theme/common/colorRegistry'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { explorerRootErrorEmitter } from 'vs/workbench/contrib/files/browser/views/explorerViewer'; @@ -34,6 +34,11 @@ export function provideDecorations(fileStat: ExplorerItem): IDecorationData | un letter: '?' }; } + if (fileStat.isExcluded) { + return { + color: listDeemphasizedForeground, + }; + } return undefined; } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 107f5f1da8b..92f530aa5a6 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -563,7 +563,9 @@ export class FilesFilter implements ITreeFilter { } private isVisible(stat: ExplorerItem, parentVisibility: TreeVisibility): boolean { + stat.isExcluded = false; if (parentVisibility === TreeVisibility.Hidden) { + stat.isExcluded = true; return false; } if (this.explorerService.getEditableData(stat) || stat.isRoot) { @@ -573,6 +575,7 @@ export class FilesFilter implements ITreeFilter { // Hide those that match Hidden Patterns const cached = this.hiddenExpressionPerRoot.get(stat.root.resource.toString()); if (cached && cached.parsed(path.relative(stat.root.resource.path, stat.resource.path), stat.name, name => !!(stat.parent && stat.parent.getChild(name)))) { + stat.isExcluded = true; const editors = this.editorService.visibleEditors; const editor = editors.filter(e => e.resource && isEqualOrParent(e.resource, stat.resource)).pop(); if (editor) { diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index 90d81ae2e9d..dfaeb0945de 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -80,6 +80,7 @@ export class ExplorerItem { protected _isDirectoryResolved: boolean; private _isDisposed: boolean; public isError = false; + private _isExcluded = false; constructor( public resource: URI, @@ -95,6 +96,21 @@ export class ExplorerItem { this._isDisposed = false; } + get isExcluded(): boolean { + if (this._isExcluded) { + return true; + } + if (!this._parent) { + return false; + } + + return this._parent.isExcluded; + } + + set isExcluded(value: boolean) { + this._isExcluded = value; + } + get isDisposed(): boolean { return this._isDisposed; } From 4e605ec54a58213b2db2e0b2c2d19e17833a46bb Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Tue, 10 Mar 2020 17:58:28 +0100 Subject: [PATCH 015/169] propagate ext terminal errrors; fixes #92380 --- src/vs/workbench/api/browser/mainThreadDebugService.ts | 2 +- src/vs/workbench/api/node/extHostDebugService.ts | 2 +- src/vs/workbench/contrib/debug/node/terminals.ts | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 4e48c4bf864..9686820a36a 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -75,7 +75,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb } runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise { - return Promise.resolve(this._proxy.$runInTerminal(args)); + return this._proxy.$runInTerminal(args); } // RPC methods (MainThreadDebugServiceShape) diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index dab72ef96b4..23ec3e88954 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -113,7 +113,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { } else if (args.kind === 'external') { - runInExternalTerminal(args, await this._configurationService.getConfigProvider()); + return runInExternalTerminal(args, await this._configurationService.getConfigProvider()); } return super.$runInTerminal(args); } diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 6be7695e38f..89e850f23aa 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -12,7 +12,7 @@ import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfigurat let externalTerminalService: IExternalTerminalService | undefined = undefined; -export function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, configProvider: ExtHostConfigProvider): void { +export function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, configProvider: ExtHostConfigProvider): Promise { if (!externalTerminalService) { if (env.isWindows) { externalTerminalService = new WindowsExternalTerminalService(undefined); @@ -20,12 +20,12 @@ export function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestAr externalTerminalService = new MacExternalTerminalService(undefined); } else if (env.isLinux) { externalTerminalService = new LinuxExternalTerminalService(undefined); + } else { + throw new Error('external terminals not supported on this platform'); } } - if (externalTerminalService) { - const config = configProvider.getConfiguration('terminal'); - externalTerminalService.runInTerminal(args.title!, args.cwd, args.args, args.env || {}, config.external || {}); - } + const config = configProvider.getConfiguration('terminal'); + return externalTerminalService.runInTerminal(args.title!, args.cwd, args.args, args.env || {}, config.external || {}); } function spawnAsPromised(command: string, args: string[]): Promise { From c3a5ad4f73c1a4762702875eabb4cb2c2ad93f18 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 10 Mar 2020 11:21:40 -0700 Subject: [PATCH 016/169] fixup! pr comments --- .../contrib/debug/browser/media/debug.contribution.css | 9 ++++++--- .../contrib/debug/browser/media/debugViewlet.css | 8 ++++++++ src/vs/workbench/contrib/debug/browser/variablesView.ts | 9 +++++++-- src/vs/workbench/contrib/debug/common/debugModel.ts | 2 +- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index 2fb7b93bd76..fc93ad15e0b 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -125,7 +125,8 @@ font-style: italic; } -.monaco-workbench .monaco-list-row .expression .error { +.monaco-workbench .monaco-list-row .expression .error, +.monaco-workbench .debug-pane .debug-variables .scope .error { color: #e51400; } @@ -145,7 +146,8 @@ color: rgba(204, 204, 204, 0.6); } -.vs-dark .monaco-workbench .monaco-list-row .expression .error { +.vs-dark .monaco-workbench .monaco-list-row .expression .error, +.vs-dark .monaco-workbench .debug-pane .debug-variables .scope .error { color: #f48771; } @@ -173,7 +175,8 @@ color: #ce9178; } -.hc-black .monaco-workbench .monaco-list-row .expression .error { +.hc-black .monaco-workbench .monaco-list-row .expression .error, +.hc-black .monaco-workbench .debug-pane .debug-variables .scope .error { color: #f48771; } diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 518e7f07d7e..ff2cdd5e604 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -316,6 +316,14 @@ animation-name: debugViewletValueChanged; } +.debug-pane .debug-variables .scope .error { + font-style: italic; + text-overflow: ellipsis; + overflow: hidden; + font-family: var(--monaco-monospace-font); + font-weight: normal; +} + /* Breakpoints */ .debug-pane .monaco-list-row { diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index a2f1c8121d8..e93fb60302e 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -9,7 +9,7 @@ import * as dom from 'vs/base/browser/dom'; import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IDebugService, IExpression, IScope, CONTEXT_VARIABLES_FOCUSED, IViewModel } from 'vs/workbench/contrib/debug/common/debug'; -import { Variable, Scope } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Variable, Scope, ErrorScope } from 'vs/workbench/contrib/debug/common/debugModel'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; @@ -34,6 +34,7 @@ import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { escape } from 'vs/base/common/strings'; const $ = dom.$; let forgetScopes = true; @@ -271,7 +272,11 @@ class ScopesRenderer implements ITreeRenderer, index: number, templateData: IScopeTemplateData): void { const name = element.element.name; - templateData.label.set(name, element.element.reference === -1 ? [{ start: 0, end: name.length, extraClasses: 'error' }] : createMatches(element.filterData)); + if (element.element instanceof ErrorScope) { + templateData.name.innerHTML = `${escape(name)}`; + } else { + templateData.label.set(name, createMatches(element.filterData)); + } } disposeTemplate(templateData: IScopeTemplateData): void { diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index d0ff48e420e..2661d4e0f58 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -276,7 +276,7 @@ export class ErrorScope extends Scope { index: number, message: string, ) { - super(stackFrame, index, message, -1, false); + super(stackFrame, index, message, 0, false); } toString(): string { From 89fe6d2bfe1a4d82842763effd756372354f58ad Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 10 Mar 2020 12:31:37 -0700 Subject: [PATCH 017/169] Fix bad filter Fixes #92338 --- .../src/features/workspaceSymbols.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/src/features/workspaceSymbols.ts b/extensions/typescript-language-features/src/features/workspaceSymbols.ts index 7b23d385c1f..8d6788a4254 100644 --- a/extensions/typescript-language-features/src/features/workspaceSymbols.ts +++ b/extensions/typescript-language-features/src/features/workspaceSymbols.ts @@ -54,7 +54,7 @@ class TypeScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvide } return response.body - .filter(item => item.containerName && item.kind !== 'alias') + .filter(item => item.containerName || item.kind !== 'alias') .map(item => this.toSymbolInformation(item)); } From 690007dfc4e23d072a97b043f94afe00a78c1880 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Fri, 6 Mar 2020 01:32:28 -0500 Subject: [PATCH 018/169] Closes #92135 - Adds view-level progress indicator --- .../workbench/browser/parts/compositePart.ts | 4 + .../browser/parts/panel/panelPart.ts | 4 + .../browser/parts/sidebar/sidebarPart.ts | 4 + .../browser/parts/views/media/paneviewlet.css | 12 ++ .../browser/parts/views/viewPaneContainer.ts | 32 +++- src/vs/workbench/browser/parts/views/views.ts | 22 +++ src/vs/workbench/common/views.ts | 3 + .../services/panel/common/panelService.ts | 5 + .../progress/browser/progressIndicator.ts | 158 ++++++++++++++++++ .../progress/browser/progressService.ts | 58 +++++++ .../services/viewlet/browser/viewlet.ts | 5 + .../test/browser/workbenchTestServices.ts | 4 +- 12 files changed, 309 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 27db5400aa3..e269b9e899f 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -158,6 +158,10 @@ export abstract class CompositePart extends Part { return composite; } + protected getInstantiatedComposite(id: string) { + return this.instantiatedCompositeItems.get(id)?.composite; + } + protected createComposite(id: string, isActive?: boolean): Composite { // Check if composite is already created diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 131c81613e3..2302e3828b2 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -434,6 +434,10 @@ export class PanelPart extends CompositePart implements IPanelService { return this.getActiveComposite(); } + getInstantiatedPanel(id: string): IPanel | undefined { + return this.getInstantiatedComposite(id) as IPanel | undefined; + } + getLastActivePanelId(): string { return this.getLastActiveCompositetId(); } diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 76e0dcce519..a6a9e978651 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -225,6 +225,10 @@ export class SidebarPart extends CompositePart implements IViewletServi return this.getActiveComposite(); } + getInstantiatedViewlet(id: string): IViewlet | undefined { + return this.getInstantiatedComposite(id) as IViewlet | undefined; + } + getLastActiveViewletId(): string { return this.getLastActiveCompositetId(); } diff --git a/src/vs/workbench/browser/parts/views/media/paneviewlet.css b/src/vs/workbench/browser/parts/views/media/paneviewlet.css index 9ec114b965a..befcd315691 100644 --- a/src/vs/workbench/browser/parts/views/media/paneviewlet.css +++ b/src/vs/workbench/browser/parts/views/media/paneviewlet.css @@ -7,6 +7,10 @@ border-top: none !important; /* less clutter: do not show any border for first views in a pane */ } +.monaco-pane-view .pane > .pane-header { + position: relative; +} + .monaco-pane-view .pane > .pane-header > .actions.show { display: initial; } @@ -23,3 +27,11 @@ .monaco-pane-view .pane > .pane-header h3.title:first-child { margin-left: 7px; } + +.monaco-pane-view .pane > .pane-header .monaco-progress-container { + position: absolute; + left: 0; + bottom: 0; + z-index: 5; + height: 2px; +} diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index afc66e4979b..0b856d9027a 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/paneviewlet'; import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; -import { attachStyler, IColorMapping, attachButtonStyler, attachLinkStyler } from 'vs/platform/theme/common/styler'; +import { attachStyler, IColorMapping, attachButtonStyler, attachLinkStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener, removeClass, addClass } from 'vs/base/browser/dom'; import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -44,6 +44,9 @@ import { Button } from 'vs/base/browser/ui/button/button'; import { Link } from 'vs/platform/opener/browser/link'; import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; import { Orientation } from 'vs/base/browser/ui/sash/sash'; +import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; +import { ViewProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; +import { IProgressIndicator } from 'vs/platform/progress/common/progress'; export interface IPaneColors extends IColorMapping { dropBackground?: ColorIdentifier; @@ -181,6 +184,8 @@ export abstract class ViewPane extends Pane implements IView { title: string; private readonly menuActions: ViewMenuActions; + private progressBar!: ProgressBar; + private progressIndicator!: IProgressIndicator; private toolbar?: ToolBar; private readonly showActionsAlways: boolean = false; @@ -289,6 +294,13 @@ export abstract class ViewPane extends Pane implements IView { const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig)); this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this)); this.updateActionsVisibility(); + + if (this.progressBar !== undefined) { + // Progress bar + this.progressBar = this._register(new ProgressBar(this.headerContainer)); + this._register(attachProgressBarStyler(this.progressBar, this.themeService)); + this.progressBar.hide(); + } } protected renderTwisties(container: HTMLElement): void { @@ -320,6 +332,24 @@ export abstract class ViewPane extends Pane implements IView { // noop } + getProgressIndicator() { + if (!this.headerContainer) { + return undefined; + } + + if (this.progressBar === undefined) { + // Progress bar + this.progressBar = this._register(new ProgressBar(this.headerContainer)); + this._register(attachProgressBarStyler(this.progressBar, this.themeService)); + this.progressBar.hide(); + } + + if (this.progressIndicator === undefined) { + this.progressIndicator = this.instantiationService.createInstance(ViewProgressIndicator, this, assertIsDefined(this.progressBar)); + } + return this.progressIndicator; + } + protected getProgressLocation(): string { return this.viewDescriptorService.getViewContainer(this.id)!.id; } diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 4d4cb205a2d..bab2aeda649 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -34,6 +34,7 @@ import { Viewlet, ViewletDescriptor, ViewletRegistry, Extensions as ViewletExten import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { URI } from 'vs/base/common/uri'; +import { IProgressIndicator } from 'vs/platform/progress/common/progress'; export interface IViewState { visibleGlobal: boolean | undefined; @@ -699,6 +700,27 @@ export class ViewsService extends Disposable implements IViewsService { return null; } + getProgressIndicator(id: string): IProgressIndicator | undefined { + const viewContainer = this.viewDescriptorService.getViewContainer(id); + if (viewContainer === null) { + return undefined; + } + + const location = this.viewContainersRegistry.getViewContainerLocation(viewContainer); + + let viewPaneContainer; + if (location === ViewContainerLocation.Sidebar) { + const viewlet = this.viewletService.getInstantiatedViewlet(viewContainer.id); + viewPaneContainer = viewlet?.getViewPaneContainer(); + } else if (location === ViewContainerLocation.Panel) { + const panel = this.panelService.getInstantiatedPanel(viewContainer.id); + viewPaneContainer = (panel as IPaneComposite)?.getViewPaneContainer(); + } + + const view = viewPaneContainer?.getView(id); + return view?.getProgressIndicator(); + } + private registerViewletOrPanel(viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation): void { switch (viewContainerLocation) { case ViewContainerLocation.Panel: diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 14564a51d3b..5a73675c7fd 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -20,6 +20,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { flatten, mergeSort } from 'vs/base/common/arrays'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { SetMap } from 'vs/base/common/collections'; +import { IProgressIndicator } from 'vs/platform/progress/common/progress'; export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test'; @@ -402,6 +403,7 @@ export interface IView { setExpanded(expanded: boolean): boolean; + getProgressIndicator(): IProgressIndicator | undefined; } export interface IViewsViewlet extends IViewlet { @@ -426,6 +428,7 @@ export interface IViewsService { closeView(id: string): void; + getProgressIndicator(id: string): IProgressIndicator | undefined; } /** diff --git a/src/vs/workbench/services/panel/common/panelService.ts b/src/vs/workbench/services/panel/common/panelService.ts index 3045900a7c0..cdebe9b74d2 100644 --- a/src/vs/workbench/services/panel/common/panelService.ts +++ b/src/vs/workbench/services/panel/common/panelService.ts @@ -35,6 +35,11 @@ export interface IPanelService { */ getActivePanel(): IPanel | undefined; + /** + * Returns an instantiated panel by id, if any. + */ + getInstantiatedPanel(id: string): IPanel | undefined; + /** * Returns the panel by id. */ diff --git a/src/vs/workbench/services/progress/browser/progressIndicator.ts b/src/vs/workbench/services/progress/browser/progressIndicator.ts index dc0b538df55..3092539923c 100644 --- a/src/vs/workbench/services/progress/browser/progressIndicator.ts +++ b/src/vs/workbench/services/progress/browser/progressIndicator.ts @@ -10,6 +10,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IProgressRunner, IProgressIndicator, emptyProgressRunner } from 'vs/platform/progress/common/progress'; import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; export class ProgressBarIndicator extends Disposable implements IProgressIndicator { @@ -350,3 +351,160 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr } } } + +export class ViewProgressIndicator extends Disposable implements IProgressIndicator { + private progressState: ProgressIndicatorState.State = ProgressIndicatorState.None; + + private isVisible: boolean; + + constructor( + view: ViewPane, + private progressbar: ProgressBar, + ) { + super(); + + this.progressbar = progressbar; + this._register(view.onDidChangeBodyVisibility(this.onVisible, this)); + this.isVisible = view.isVisible(); + } + + onVisible(visible: boolean) { + this.isVisible = visible; + + // Return early if progress state indicates that progress is done + if (!visible || this.progressState.type === ProgressIndicatorState.Done.type) { + this.progressbar.stop().hide(); + return; + } + + // Replay Infinite Progress from Promise + if (this.progressState.type === ProgressIndicatorState.Type.While) { + let delay: number | undefined; + if (this.progressState.whileDelay > 0) { + const remainingDelay = this.progressState.whileDelay - (Date.now() - this.progressState.whileStart); + if (remainingDelay > 0) { + delay = remainingDelay; + } + } + + this.doShowWhile(delay); + } + + // Replay Infinite Progress + else if (this.progressState.type === ProgressIndicatorState.Type.Infinite) { + this.progressbar.infinite().show(); + } + + // Replay Finite Progress (Total & Worked) + else if (this.progressState.type === ProgressIndicatorState.Type.Work) { + if (this.progressState.total) { + this.progressbar.total(this.progressState.total).show(); + } + + if (this.progressState.worked) { + this.progressbar.worked(this.progressState.worked).show(); + } + } + } + + show(infinite: true, delay?: number): IProgressRunner; + show(total: number, delay?: number): IProgressRunner; + show(infiniteOrTotal: true | number, delay?: number): IProgressRunner { + + // Sort out Arguments + if (typeof infiniteOrTotal === 'boolean') { + this.progressState = ProgressIndicatorState.Infinite; + } else { + this.progressState = new ProgressIndicatorState.Work(infiniteOrTotal, undefined); + } + + // Active: Show Progress + if (this.isVisible) { + + // Infinite: Start Progressbar and Show after Delay + if (this.progressState.type === ProgressIndicatorState.Type.Infinite) { + this.progressbar.infinite().show(delay); + } + + // Finite: Start Progressbar and Show after Delay + else if (this.progressState.type === ProgressIndicatorState.Type.Work && typeof this.progressState.total === 'number') { + this.progressbar.total(this.progressState.total).show(delay); + } + } + + return { + total: (total: number) => { + this.progressState = new ProgressIndicatorState.Work( + total, + this.progressState.type === ProgressIndicatorState.Type.Work ? this.progressState.worked : undefined); + + if (this.isVisible) { + this.progressbar.total(total); + } + }, + + worked: (worked: number) => { + + // Verify first that we are either not active or the progressbar has a total set + if (!this.isVisible || this.progressbar.hasTotal()) { + this.progressState = new ProgressIndicatorState.Work( + this.progressState.type === ProgressIndicatorState.Type.Work ? this.progressState.total : undefined, + this.progressState.type === ProgressIndicatorState.Type.Work && typeof this.progressState.worked === 'number' ? this.progressState.worked + worked : worked); + + if (this.isVisible) { + this.progressbar.worked(worked); + } + } + + // Otherwise the progress bar does not support worked(), we fallback to infinite() progress + else { + this.progressState = ProgressIndicatorState.Infinite; + this.progressbar.infinite().show(); + } + }, + + done: () => { + this.progressState = ProgressIndicatorState.Done; + + this.progressbar.stop().hide(); + } + }; + } + + async showWhile(promise: Promise, delay?: number): Promise { + + // Join with existing running promise to ensure progress is accurate + if (this.progressState.type === ProgressIndicatorState.Type.While) { + promise = Promise.all([promise, this.progressState.whilePromise]); + } + + // Keep Promise in State + this.progressState = new ProgressIndicatorState.While(promise, delay || 0, Date.now()); + + try { + this.doShowWhile(delay); + + await promise; + } catch (error) { + // ignore + } finally { + + // If this is not the last promise in the list of joined promises, skip this + if (this.progressState.type !== ProgressIndicatorState.Type.While || this.progressState.whilePromise === promise) { + + // The while promise is either null or equal the promise we last hooked on + this.progressState = ProgressIndicatorState.None; + + this.progressbar.stop().hide(); + } + } + } + + private doShowWhile(delay?: number): void { + + // Show Progress when active + if (this.isVisible) { + this.progressbar.infinite().show(delay); + } + } +} diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index b96b974ea2d..315c9f45961 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -25,6 +25,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventHelper } from 'vs/base/browser/dom'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { parseLinkedText } from 'vs/base/common/linkedText'; +import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; export class ProgressService extends Disposable implements IProgressService { @@ -33,6 +34,8 @@ export class ProgressService extends Disposable implements IProgressService { constructor( @IActivityService private readonly activityService: IActivityService, @IViewletService private readonly viewletService: IViewletService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IViewsService private readonly viewsService: IViewsService, @IPanelService private readonly panelService: IPanelService, @INotificationService private readonly notificationService: INotificationService, @IStatusbarService private readonly statusbarService: IStatusbarService, @@ -54,6 +57,10 @@ export class ProgressService extends Disposable implements IProgressService { return this.withPanelProgress(location, task, { ...options, location }); } + if (this.viewsService.getProgressIndicator(location)) { + return this.withViewProgress(location, task, { ...options, location }); + } + throw new Error(`Bad progress location: ${location}`); } @@ -412,6 +419,57 @@ export class ProgressService extends Disposable implements IProgressService { return promise; } + private withViewProgress

, R = unknown>(viewId: string, task: (progress: IProgress) => P, options: IProgressCompositeOptions): P { + + // show in viewlet + const promise = this.withCompositeProgress(this.viewsService.getProgressIndicator(viewId), task, options); + + const location = this.viewDescriptorService.getViewLocation(viewId); + if (location !== ViewContainerLocation.Sidebar) { + return promise; + } + + const viewletId = this.viewDescriptorService.getViewContainer(viewId)?.id; + if (viewletId === undefined) { + return promise; + } + + // show activity bar + let activityProgress: IDisposable; + let delayHandle: any = setTimeout(() => { + delayHandle = undefined; + + const handle = this.activityService.showActivity( + viewletId, + new ProgressBadge(() => ''), + 'progress-badge', + 100 + ); + + const startTimeVisible = Date.now(); + const minTimeVisible = 300; + activityProgress = { + dispose() { + const d = Date.now() - startTimeVisible; + if (d < minTimeVisible) { + // should at least show for Nms + setTimeout(() => handle.dispose(), minTimeVisible - d); + } else { + // shown long enough + handle.dispose(); + } + } + }; + }, options.delay || 300); + + promise.finally(() => { + clearTimeout(delayHandle); + dispose(activityProgress); + }); + + return promise; + } + private withPanelProgress

, R = unknown>(panelid: string, task: (progress: IProgress) => P, options: IProgressCompositeOptions): P { // show in panel diff --git a/src/vs/workbench/services/viewlet/browser/viewlet.ts b/src/vs/workbench/services/viewlet/browser/viewlet.ts index deebb5b8584..b3ca3223dca 100644 --- a/src/vs/workbench/services/viewlet/browser/viewlet.ts +++ b/src/vs/workbench/services/viewlet/browser/viewlet.ts @@ -30,6 +30,11 @@ export interface IViewletService { */ getActiveViewlet(): IViewlet | undefined; + /** + * Returns an instantiated viewlet by id, if any. + */ + getInstantiatedViewlet(id: string): IViewlet | undefined; + /** * Returns the id of the default viewlet. */ diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 2e064554f57..14331032e99 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -433,6 +433,7 @@ export class TestViewletService implements IViewletService { getViewlets(): ViewletDescriptor[] { return []; } getAllViewlets(): ViewletDescriptor[] { return []; } getActiveViewlet(): IViewlet { return activeViewlet; } + getInstantiatedViewlet(): IViewlet | undefined { return undefined; } getDefaultViewletId(): string { return 'workbench.view.explorer'; } getViewlet(id: string): ViewletDescriptor | undefined { return undefined; } getProgressIndicator(id: string) { return undefined; } @@ -451,7 +452,8 @@ export class TestPanelService implements IPanelService { getPanel(id: string): any { return activeViewlet; } getPanels() { return []; } getPinnedPanels() { return []; } - getActivePanel(): IViewlet { return activeViewlet; } + getActivePanel(): IPanel { return activeViewlet; } + getInstantiatedPanel(): IPanel | undefined { return undefined; } setPanelEnablement(id: string, enabled: boolean): void { } dispose() { } showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable { throw new Error('Method not implemented.'); } From 101beaae6aca18fe45da448ced8f3e1ceac14c20 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Fri, 6 Mar 2020 03:57:20 -0500 Subject: [PATCH 019/169] Removes code duplication Consolidates ViewProgressIndicator into CompositeProgressIndicator --- .../workbench/browser/parts/compositePart.ts | 2 +- .../browser/parts/views/viewPaneContainer.ts | 4 +- .../progress/browser/progressIndicator.ts | 176 ++---------------- .../progress/browser/progressService.ts | 57 ++---- .../test/browser/progressIndicator.test.ts | 14 +- .../test/browser/workbenchTestServices.ts | 12 ++ 6 files changed, 49 insertions(+), 216 deletions(-) diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index e269b9e899f..27d290cb703 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -173,7 +173,7 @@ export abstract class CompositePart extends Part { // Instantiate composite from registry otherwise const compositeDescriptor = this.registry.getComposite(id); if (compositeDescriptor) { - const compositeProgressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), compositeDescriptor.id, !!isActive); + const compositeProgressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), compositeDescriptor.id, !!isActive, undefined); const compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection( [IEditorProgressService, compositeProgressIndicator] // provide the editor progress service for any editors instantiated within the composite )); diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 0b856d9027a..ca893688691 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -45,7 +45,7 @@ import { Link } from 'vs/platform/opener/browser/link'; import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; import { Orientation } from 'vs/base/browser/ui/sash/sash'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { ViewProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; +import { CompositeProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; import { IProgressIndicator } from 'vs/platform/progress/common/progress'; export interface IPaneColors extends IColorMapping { @@ -345,7 +345,7 @@ export abstract class ViewPane extends Pane implements IView { } if (this.progressIndicator === undefined) { - this.progressIndicator = this.instantiationService.createInstance(ViewProgressIndicator, this, assertIsDefined(this.progressBar)); + this.progressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), this.id, this.isVisible(), { hideWhenInactive: true }); } return this.progressIndicator; } diff --git a/src/vs/workbench/services/progress/browser/progressIndicator.ts b/src/vs/workbench/services/progress/browser/progressIndicator.ts index 3092539923c..91d42a15ff4 100644 --- a/src/vs/workbench/services/progress/browser/progressIndicator.ts +++ b/src/vs/workbench/services/progress/browser/progressIndicator.ts @@ -10,7 +10,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IProgressRunner, IProgressIndicator, emptyProgressRunner } from 'vs/platform/progress/common/progress'; import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; -import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IViewsService } from 'vs/workbench/common/views'; export class ProgressBarIndicator extends Disposable implements IProgressIndicator { @@ -154,6 +154,7 @@ export abstract class CompositeScope extends Disposable { constructor( private viewletService: IViewletService, private panelService: IPanelService, + private viewsService: IViewsService, private scopeId: string ) { super(); @@ -162,6 +163,8 @@ export abstract class CompositeScope extends Disposable { } registerListeners(): void { + this._register(this.viewsService.onDidChangeViewVisibility(e => e.visible ? this.onScopeOpened(e.id) : this.onScopeClosed(e.id))); + this._register(this.viewletService.onDidViewletOpen(viewlet => this.onScopeOpened(viewlet.getId()))); this._register(this.panelService.onDidPanelOpen(({ panel }) => this.onScopeOpened(panel.getId()))); @@ -195,10 +198,12 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr progressbar: ProgressBar, scopeId: string, isActive: boolean, + private readonly options: { hideWhenInactive?: boolean } | undefined, @IViewletService viewletService: IViewletService, - @IPanelService panelService: IPanelService + @IPanelService panelService: IPanelService, + @IViewsService viewsService: IViewsService ) { - super(viewletService, panelService, scopeId); + super(viewletService, panelService, viewsService, scopeId); this.progressbar = progressbar; this.isActive = isActive || isUndefinedOrNull(scopeId); // If service is unscoped, enable by default @@ -206,6 +211,10 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr onScopeDeactivated(): void { this.isActive = false; + + if (this.options?.hideWhenInactive) { + this.progressbar.stop().hide(); + } } onScopeActivated(): void { @@ -305,7 +314,7 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr done: () => { this.progressState = ProgressIndicatorState.Done; - if (this.isActive) { + if (this.isActive || this.options?.hideWhenInactive) { this.progressbar.stop().hide(); } } @@ -336,7 +345,7 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr // The while promise is either null or equal the promise we last hooked on this.progressState = ProgressIndicatorState.None; - if (this.isActive) { + if (this.isActive || this.options?.hideWhenInactive) { this.progressbar.stop().hide(); } } @@ -351,160 +360,3 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr } } } - -export class ViewProgressIndicator extends Disposable implements IProgressIndicator { - private progressState: ProgressIndicatorState.State = ProgressIndicatorState.None; - - private isVisible: boolean; - - constructor( - view: ViewPane, - private progressbar: ProgressBar, - ) { - super(); - - this.progressbar = progressbar; - this._register(view.onDidChangeBodyVisibility(this.onVisible, this)); - this.isVisible = view.isVisible(); - } - - onVisible(visible: boolean) { - this.isVisible = visible; - - // Return early if progress state indicates that progress is done - if (!visible || this.progressState.type === ProgressIndicatorState.Done.type) { - this.progressbar.stop().hide(); - return; - } - - // Replay Infinite Progress from Promise - if (this.progressState.type === ProgressIndicatorState.Type.While) { - let delay: number | undefined; - if (this.progressState.whileDelay > 0) { - const remainingDelay = this.progressState.whileDelay - (Date.now() - this.progressState.whileStart); - if (remainingDelay > 0) { - delay = remainingDelay; - } - } - - this.doShowWhile(delay); - } - - // Replay Infinite Progress - else if (this.progressState.type === ProgressIndicatorState.Type.Infinite) { - this.progressbar.infinite().show(); - } - - // Replay Finite Progress (Total & Worked) - else if (this.progressState.type === ProgressIndicatorState.Type.Work) { - if (this.progressState.total) { - this.progressbar.total(this.progressState.total).show(); - } - - if (this.progressState.worked) { - this.progressbar.worked(this.progressState.worked).show(); - } - } - } - - show(infinite: true, delay?: number): IProgressRunner; - show(total: number, delay?: number): IProgressRunner; - show(infiniteOrTotal: true | number, delay?: number): IProgressRunner { - - // Sort out Arguments - if (typeof infiniteOrTotal === 'boolean') { - this.progressState = ProgressIndicatorState.Infinite; - } else { - this.progressState = new ProgressIndicatorState.Work(infiniteOrTotal, undefined); - } - - // Active: Show Progress - if (this.isVisible) { - - // Infinite: Start Progressbar and Show after Delay - if (this.progressState.type === ProgressIndicatorState.Type.Infinite) { - this.progressbar.infinite().show(delay); - } - - // Finite: Start Progressbar and Show after Delay - else if (this.progressState.type === ProgressIndicatorState.Type.Work && typeof this.progressState.total === 'number') { - this.progressbar.total(this.progressState.total).show(delay); - } - } - - return { - total: (total: number) => { - this.progressState = new ProgressIndicatorState.Work( - total, - this.progressState.type === ProgressIndicatorState.Type.Work ? this.progressState.worked : undefined); - - if (this.isVisible) { - this.progressbar.total(total); - } - }, - - worked: (worked: number) => { - - // Verify first that we are either not active or the progressbar has a total set - if (!this.isVisible || this.progressbar.hasTotal()) { - this.progressState = new ProgressIndicatorState.Work( - this.progressState.type === ProgressIndicatorState.Type.Work ? this.progressState.total : undefined, - this.progressState.type === ProgressIndicatorState.Type.Work && typeof this.progressState.worked === 'number' ? this.progressState.worked + worked : worked); - - if (this.isVisible) { - this.progressbar.worked(worked); - } - } - - // Otherwise the progress bar does not support worked(), we fallback to infinite() progress - else { - this.progressState = ProgressIndicatorState.Infinite; - this.progressbar.infinite().show(); - } - }, - - done: () => { - this.progressState = ProgressIndicatorState.Done; - - this.progressbar.stop().hide(); - } - }; - } - - async showWhile(promise: Promise, delay?: number): Promise { - - // Join with existing running promise to ensure progress is accurate - if (this.progressState.type === ProgressIndicatorState.Type.While) { - promise = Promise.all([promise, this.progressState.whilePromise]); - } - - // Keep Promise in State - this.progressState = new ProgressIndicatorState.While(promise, delay || 0, Date.now()); - - try { - this.doShowWhile(delay); - - await promise; - } catch (error) { - // ignore - } finally { - - // If this is not the last promise in the list of joined promises, skip this - if (this.progressState.type !== ProgressIndicatorState.Type.While || this.progressState.whilePromise === promise) { - - // The while promise is either null or equal the promise we last hooked on - this.progressState = ProgressIndicatorState.None; - - this.progressbar.stop().hide(); - } - } - } - - private doShowWhile(delay?: number): void { - - // Show Progress when active - if (this.isVisible) { - this.progressbar.infinite().show(delay); - } - } -} diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 315c9f45961..af38fca3f91 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -383,38 +383,8 @@ export class ProgressService extends Disposable implements IProgressService { // show in viewlet const promise = this.withCompositeProgress(this.viewletService.getProgressIndicator(viewletId), task, options); - // show activity bar - let activityProgress: IDisposable; - let delayHandle: any = setTimeout(() => { - delayHandle = undefined; - - const handle = this.activityService.showActivity( - viewletId, - new ProgressBadge(() => ''), - 'progress-badge', - 100 - ); - - const startTimeVisible = Date.now(); - const minTimeVisible = 300; - activityProgress = { - dispose() { - const d = Date.now() - startTimeVisible; - if (d < minTimeVisible) { - // should at least show for Nms - setTimeout(() => handle.dispose(), minTimeVisible - d); - } else { - // shown long enough - handle.dispose(); - } - } - }; - }, options.delay || 300); - - promise.finally(() => { - clearTimeout(delayHandle); - dispose(activityProgress); - }); + // show on activity bar + this.showOnActivityBar(viewletId, options, promise); return promise; } @@ -434,18 +404,17 @@ export class ProgressService extends Disposable implements IProgressService { return promise; } - // show activity bar + // show on activity bar + this.showOnActivityBar(viewletId, options, promise); + + return promise; + } + + private showOnActivityBar

, R = unknown>(viewletId: string, options: IProgressCompositeOptions, promise: P) { let activityProgress: IDisposable; let delayHandle: any = setTimeout(() => { delayHandle = undefined; - - const handle = this.activityService.showActivity( - viewletId, - new ProgressBadge(() => ''), - 'progress-badge', - 100 - ); - + const handle = this.activityService.showActivity(viewletId, new ProgressBadge(() => ''), 'progress-badge', 100); const startTimeVisible = Date.now(); const minTimeVisible = 300; activityProgress = { @@ -454,20 +423,18 @@ export class ProgressService extends Disposable implements IProgressService { if (d < minTimeVisible) { // should at least show for Nms setTimeout(() => handle.dispose(), minTimeVisible - d); - } else { + } + else { // shown long enough handle.dispose(); } } }; }, options.delay || 300); - promise.finally(() => { clearTimeout(delayHandle); dispose(activityProgress); }); - - return promise; } private withPanelProgress

, R = unknown>(panelid: string, task: (progress: IProgress) => P, options: IProgressCompositeOptions): P { diff --git a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts index f5f47990d29..9047c26c67c 100644 --- a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts +++ b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts @@ -10,9 +10,9 @@ import { CompositeScope, CompositeProgressIndicator } from 'vs/workbench/service import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewlet } from 'vs/workbench/common/viewlet'; -import { TestViewletService, TestPanelService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestViewletService, TestPanelService, TestViewsService } from 'vs/workbench/test/browser/workbenchTestServices'; import { Event } from 'vs/base/common/event'; -import { IView, IViewPaneContainer } from 'vs/workbench/common/views'; +import { IView, IViewPaneContainer, IViewsService } from 'vs/workbench/common/views'; class TestViewlet implements IViewlet { @@ -38,8 +38,8 @@ class TestViewlet implements IViewlet { class TestCompositeScope extends CompositeScope { isActive: boolean = false; - constructor(viewletService: IViewletService, panelService: IPanelService, scopeId: string) { - super(viewletService, panelService, scopeId); + constructor(viewletService: IViewletService, panelService: IPanelService, viewsService: IViewsService, scopeId: string) { + super(viewletService, panelService, viewsService, scopeId); } onScopeActivated() { this.isActive = true; } @@ -106,7 +106,8 @@ suite('Progress Indicator', () => { test('CompositeScope', () => { let viewletService = new TestViewletService(); let panelService = new TestPanelService(); - let service = new TestCompositeScope(viewletService, panelService, 'test.scopeId'); + let viewsService = new TestViewsService(); + let service = new TestCompositeScope(viewletService, panelService, viewsService, 'test.scopeId'); const testViewlet = new TestViewlet('test.scopeId'); assert(!service.isActive); @@ -122,7 +123,8 @@ suite('Progress Indicator', () => { let testProgressBar = new TestProgressBar(); let viewletService = new TestViewletService(); let panelService = new TestPanelService(); - let service = new CompositeProgressIndicator((testProgressBar), 'test.scopeId', true, viewletService, panelService); + let viewsService = new TestViewsService(); + let service = new CompositeProgressIndicator((testProgressBar), 'test.scopeId', true, undefined, viewletService, panelService, viewsService); // Active: Show (Infinite) let fn = service.show(true); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 14331032e99..fa491026ba8 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -104,6 +104,7 @@ import { QuickInputService } from 'vs/workbench/services/quickinput/browser/quic import { IListService } from 'vs/platform/list/browser/listService'; import { win32, posix } from 'vs/base/common/path'; import { TestWorkingCopyService, TestContextService, TestStorageService, TestTextResourcePropertiesService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IViewsService, IView } from 'vs/workbench/common/views'; export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); @@ -462,6 +463,17 @@ export class TestPanelService implements IPanelService { getLastActivePanelId(): string { return undefined!; } } +export class TestViewsService implements IViewsService { + _serviceBrand: undefined; + + onDidChangeViewVisibility = new Emitter<{ id: string; visible: boolean; }>().event; + isViewVisible(id: string): boolean { return true; } + getActiveViewWithId(id: string): T | null { return null; } + openView(id: string, focus?: boolean | undefined): Promise { return Promise.resolve(null); } + closeView(id: string): void { } + getProgressIndicator(id: string) { return null!; } +} + export class TestEditorGroupsService implements IEditorGroupsService { _serviceBrand: undefined; From 44da9e136e01e847751100f7ccdf099668bb20bb Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Fri, 6 Mar 2020 12:11:48 -0500 Subject: [PATCH 020/169] Removes getInstantiated* methods --- .../workbench/browser/parts/compositePart.ts | 4 ---- .../browser/parts/panel/panelPart.ts | 4 ---- .../browser/parts/sidebar/sidebarPart.ts | 4 ---- src/vs/workbench/browser/parts/views/views.ts | 20 ++++++++----------- .../services/panel/common/panelService.ts | 5 ----- .../progress/browser/progressService.ts | 3 +-- .../services/viewlet/browser/viewlet.ts | 5 ----- .../test/browser/workbenchTestServices.ts | 2 -- 8 files changed, 9 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 27d290cb703..2a72d6fdb4d 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -158,10 +158,6 @@ export abstract class CompositePart extends Part { return composite; } - protected getInstantiatedComposite(id: string) { - return this.instantiatedCompositeItems.get(id)?.composite; - } - protected createComposite(id: string, isActive?: boolean): Composite { // Check if composite is already created diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 2302e3828b2..131c81613e3 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -434,10 +434,6 @@ export class PanelPart extends CompositePart implements IPanelService { return this.getActiveComposite(); } - getInstantiatedPanel(id: string): IPanel | undefined { - return this.getInstantiatedComposite(id) as IPanel | undefined; - } - getLastActivePanelId(): string { return this.getLastActiveCompositetId(); } diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index a6a9e978651..76e0dcce519 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -225,10 +225,6 @@ export class SidebarPart extends CompositePart implements IViewletServi return this.getActiveComposite(); } - getInstantiatedViewlet(id: string): IViewlet | undefined { - return this.getInstantiatedComposite(id) as IViewlet | undefined; - } - getLastActiveViewletId(): string { return this.getLastActiveCompositetId(); } diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index bab2aeda649..2cf36b3d452 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -466,6 +466,8 @@ export class ViewsService extends Disposable implements IViewsService { private readonly visibleViewContextKeys: Map>; + private readonly viewPaneContainers: Map; + constructor( @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IPanelService private readonly panelService: IPanelService, @@ -477,6 +479,7 @@ export class ViewsService extends Disposable implements IViewsService { this.viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); this.viewDisposable = new Map(); this.visibleViewContextKeys = new Map>(); + this.viewPaneContainers = new Map(); this._register(toDisposable(() => { this.viewDisposable.forEach(disposable => disposable.dispose()); @@ -485,12 +488,16 @@ export class ViewsService extends Disposable implements IViewsService { this.viewContainersRegistry.all.forEach(viewContainer => this.onDidRegisterViewContainer(viewContainer, this.viewContainersRegistry.getViewContainerLocation(viewContainer))); this._register(this.viewContainersRegistry.onDidRegister(({ viewContainer, viewContainerLocation }) => this.onDidRegisterViewContainer(viewContainer, viewContainerLocation))); + + this._register(this.viewContainersRegistry.onDidDeregister(e => this.viewPaneContainers.delete(e.viewContainer.id))); } private registerViewPaneContainer(viewPaneContainer: ViewPaneContainer): void { this._register(viewPaneContainer.onDidAddViews(views => this.onViewsAdded(views))); this._register(viewPaneContainer.onDidChangeViewVisibility(view => this.onViewsVisibilityChanged(view, view.isBodyVisible()))); this._register(viewPaneContainer.onDidRemoveViews(views => this.onViewsRemoved(views))); + + this.viewPaneContainers.set(viewPaneContainer.getId(), viewPaneContainer); } private onViewsAdded(added: IView[]): void { @@ -706,18 +713,7 @@ export class ViewsService extends Disposable implements IViewsService { return undefined; } - const location = this.viewContainersRegistry.getViewContainerLocation(viewContainer); - - let viewPaneContainer; - if (location === ViewContainerLocation.Sidebar) { - const viewlet = this.viewletService.getInstantiatedViewlet(viewContainer.id); - viewPaneContainer = viewlet?.getViewPaneContainer(); - } else if (location === ViewContainerLocation.Panel) { - const panel = this.panelService.getInstantiatedPanel(viewContainer.id); - viewPaneContainer = (panel as IPaneComposite)?.getViewPaneContainer(); - } - - const view = viewPaneContainer?.getView(id); + const view = this.viewPaneContainers.get(viewContainer.id)?.getView(id); return view?.getProgressIndicator(); } diff --git a/src/vs/workbench/services/panel/common/panelService.ts b/src/vs/workbench/services/panel/common/panelService.ts index cdebe9b74d2..3045900a7c0 100644 --- a/src/vs/workbench/services/panel/common/panelService.ts +++ b/src/vs/workbench/services/panel/common/panelService.ts @@ -35,11 +35,6 @@ export interface IPanelService { */ getActivePanel(): IPanel | undefined; - /** - * Returns an instantiated panel by id, if any. - */ - getInstantiatedPanel(id: string): IPanel | undefined; - /** * Returns the panel by id. */ diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index af38fca3f91..ed8b9499f69 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -423,8 +423,7 @@ export class ProgressService extends Disposable implements IProgressService { if (d < minTimeVisible) { // should at least show for Nms setTimeout(() => handle.dispose(), minTimeVisible - d); - } - else { + } else { // shown long enough handle.dispose(); } diff --git a/src/vs/workbench/services/viewlet/browser/viewlet.ts b/src/vs/workbench/services/viewlet/browser/viewlet.ts index b3ca3223dca..deebb5b8584 100644 --- a/src/vs/workbench/services/viewlet/browser/viewlet.ts +++ b/src/vs/workbench/services/viewlet/browser/viewlet.ts @@ -30,11 +30,6 @@ export interface IViewletService { */ getActiveViewlet(): IViewlet | undefined; - /** - * Returns an instantiated viewlet by id, if any. - */ - getInstantiatedViewlet(id: string): IViewlet | undefined; - /** * Returns the id of the default viewlet. */ diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index fa491026ba8..f53da4de909 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -434,7 +434,6 @@ export class TestViewletService implements IViewletService { getViewlets(): ViewletDescriptor[] { return []; } getAllViewlets(): ViewletDescriptor[] { return []; } getActiveViewlet(): IViewlet { return activeViewlet; } - getInstantiatedViewlet(): IViewlet | undefined { return undefined; } getDefaultViewletId(): string { return 'workbench.view.explorer'; } getViewlet(id: string): ViewletDescriptor | undefined { return undefined; } getProgressIndicator(id: string) { return undefined; } @@ -454,7 +453,6 @@ export class TestPanelService implements IPanelService { getPanels() { return []; } getPinnedPanels() { return []; } getActivePanel(): IPanel { return activeViewlet; } - getInstantiatedPanel(): IPanel | undefined { return undefined; } setPanelEnablement(id: string, enabled: boolean): void { } dispose() { } showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable { throw new Error('Method not implemented.'); } From a8130a02d151282e2e29518770094a79f5d8fb06 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Fri, 6 Mar 2020 12:37:28 -0500 Subject: [PATCH 021/169] Adds tests --- .../test/browser/progressIndicator.test.ts | 19 +++++++++++++++++++ .../test/browser/workbenchTestServices.ts | 4 +++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts index 9047c26c67c..8b886270626 100644 --- a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts +++ b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts @@ -117,6 +117,11 @@ suite('Progress Indicator', () => { viewletService.onDidViewletCloseEmitter.fire(testViewlet); assert(!service.isActive); + viewsService.onDidChangeViewVisibilityEmitter.fire({ id: 'test.scopeId', visible: true }); + assert(service.isActive); + + viewsService.onDidChangeViewVisibilityEmitter.fire({ id: 'test.scopeId', visible: false }); + assert(!service.isActive); }); test('CompositeProgressIndicator', async () => { @@ -171,5 +176,19 @@ suite('Progress Indicator', () => { assert.strictEqual(true, testProgressBar.fDone); viewletService.onDidViewletOpenEmitter.fire(testViewlet); assert.strictEqual(true, testProgressBar.fDone); + + // Visible view: Show (Infinite) + viewsService.onDidChangeViewVisibilityEmitter.fire({ id: 'test.scopeId', visible: true }); + fn = service.show(true); + assert.strictEqual(true, testProgressBar.fInfinite); + fn.done(); + assert.strictEqual(true, testProgressBar.fDone); + + // Hidden view: Show (Infinite) + viewsService.onDidChangeViewVisibilityEmitter.fire({ id: 'test.scopeId', visible: false }); + service.show(true); + assert.strictEqual(false, !!testProgressBar.fInfinite); + viewsService.onDidChangeViewVisibilityEmitter.fire({ id: 'test.scopeId', visible: true }); + assert.strictEqual(true, testProgressBar.fInfinite); }); }); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index f53da4de909..141b1ed5a3a 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -464,7 +464,9 @@ export class TestPanelService implements IPanelService { export class TestViewsService implements IViewsService { _serviceBrand: undefined; - onDidChangeViewVisibility = new Emitter<{ id: string; visible: boolean; }>().event; + onDidChangeViewVisibilityEmitter = new Emitter<{ id: string; visible: boolean; }>(); + + onDidChangeViewVisibility = this.onDidChangeViewVisibilityEmitter.event; isViewVisible(id: string): boolean { return true; } getActiveViewWithId(id: string): T | null { return null; } openView(id: string, focus?: boolean | undefined): Promise { return Promise.resolve(null); } From feb42fad54b34300b25f284691f99d93df553e83 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Tue, 10 Mar 2020 15:27:26 -0400 Subject: [PATCH 022/169] Renames option for a bit more clarity --- src/vs/workbench/browser/parts/views/viewPaneContainer.ts | 2 +- .../services/progress/browser/progressIndicator.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index ca893688691..acd6638aec2 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -345,7 +345,7 @@ export abstract class ViewPane extends Pane implements IView { } if (this.progressIndicator === undefined) { - this.progressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), this.id, this.isVisible(), { hideWhenInactive: true }); + this.progressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), this.id, this.isVisible(), { exclusiveProgressBar: true }); } return this.progressIndicator; } diff --git a/src/vs/workbench/services/progress/browser/progressIndicator.ts b/src/vs/workbench/services/progress/browser/progressIndicator.ts index 91d42a15ff4..468ddae9308 100644 --- a/src/vs/workbench/services/progress/browser/progressIndicator.ts +++ b/src/vs/workbench/services/progress/browser/progressIndicator.ts @@ -198,7 +198,7 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr progressbar: ProgressBar, scopeId: string, isActive: boolean, - private readonly options: { hideWhenInactive?: boolean } | undefined, + private readonly options: { exclusiveProgressBar?: boolean } | undefined, @IViewletService viewletService: IViewletService, @IPanelService panelService: IPanelService, @IViewsService viewsService: IViewsService @@ -212,7 +212,7 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr onScopeDeactivated(): void { this.isActive = false; - if (this.options?.hideWhenInactive) { + if (this.options?.exclusiveProgressBar) { this.progressbar.stop().hide(); } } @@ -314,7 +314,7 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr done: () => { this.progressState = ProgressIndicatorState.Done; - if (this.isActive || this.options?.hideWhenInactive) { + if (this.isActive || this.options?.exclusiveProgressBar) { this.progressbar.stop().hide(); } } @@ -345,7 +345,7 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr // The while promise is either null or equal the promise we last hooked on this.progressState = ProgressIndicatorState.None; - if (this.isActive || this.options?.hideWhenInactive) { + if (this.isActive || this.options?.exclusiveProgressBar) { this.progressbar.stop().hide(); } } From 9d25faad7fbf3255ca32959be47466107555cb08 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 10 Mar 2020 12:52:08 -0700 Subject: [PATCH 023/169] Show better symbolKinds for workspace symbols --- .../src/features/completions.ts | 4 ++-- .../src/features/documentSymbol.ts | 2 +- .../src/features/implementationsCodeLens.ts | 2 +- .../src/features/referencesCodeLens.ts | 2 +- .../src/features/workspaceSymbols.ts | 18 ++++++++++++------ .../src/protocol.const.ts | 2 +- .../src/utils/typeConverters.ts | 2 +- 7 files changed, 19 insertions(+), 13 deletions(-) diff --git a/extensions/typescript-language-features/src/features/completions.ts b/extensions/typescript-language-features/src/features/completions.ts index e2f8ba9fd37..6758b85b0f6 100644 --- a/extensions/typescript-language-features/src/features/completions.ts +++ b/extensions/typescript-language-features/src/features/completions.ts @@ -216,7 +216,7 @@ class MyCompletionItem extends vscode.CompletionItem { case PConst.Kind.function: case PConst.Kind.localFunction: return vscode.CompletionItemKind.Function; - case PConst.Kind.memberFunction: + case PConst.Kind.method: case PConst.Kind.constructSignature: case PConst.Kind.callSignature: case PConst.Kind.indexSignature: @@ -272,7 +272,7 @@ class MyCompletionItem extends vscode.CompletionItem { case PConst.Kind.memberVariable: case PConst.Kind.class: case PConst.Kind.function: - case PConst.Kind.memberFunction: + case PConst.Kind.method: case PConst.Kind.keyword: case PConst.Kind.parameter: commitCharacters.push('.', ',', ';'); diff --git a/extensions/typescript-language-features/src/features/documentSymbol.ts b/extensions/typescript-language-features/src/features/documentSymbol.ts index 02c1be917d3..e119b005bab 100644 --- a/extensions/typescript-language-features/src/features/documentSymbol.ts +++ b/extensions/typescript-language-features/src/features/documentSymbol.ts @@ -16,7 +16,7 @@ const getSymbolKind = (kind: string): vscode.SymbolKind => { case PConst.Kind.class: return vscode.SymbolKind.Class; case PConst.Kind.enum: return vscode.SymbolKind.Enum; case PConst.Kind.interface: return vscode.SymbolKind.Interface; - case PConst.Kind.memberFunction: return vscode.SymbolKind.Method; + case PConst.Kind.method: return vscode.SymbolKind.Method; case PConst.Kind.memberVariable: return vscode.SymbolKind.Property; case PConst.Kind.memberGetAccessor: return vscode.SymbolKind.Property; case PConst.Kind.memberSetAccessor: return vscode.SymbolKind.Property; diff --git a/extensions/typescript-language-features/src/features/implementationsCodeLens.ts b/extensions/typescript-language-features/src/features/implementationsCodeLens.ts index f7e325c819d..c6ea7ca6dee 100644 --- a/extensions/typescript-language-features/src/features/implementationsCodeLens.ts +++ b/extensions/typescript-language-features/src/features/implementationsCodeLens.ts @@ -75,7 +75,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip return getSymbolRange(document, item); case PConst.Kind.class: - case PConst.Kind.memberFunction: + case PConst.Kind.method: case PConst.Kind.memberVariable: case PConst.Kind.memberGetAccessor: case PConst.Kind.memberSetAccessor: diff --git a/extensions/typescript-language-features/src/features/referencesCodeLens.ts b/extensions/typescript-language-features/src/features/referencesCodeLens.ts index 7aa228f8f8e..0cf8d3f0a51 100644 --- a/extensions/typescript-language-features/src/features/referencesCodeLens.ts +++ b/extensions/typescript-language-features/src/features/referencesCodeLens.ts @@ -94,7 +94,7 @@ export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLens case PConst.Kind.enum: return getSymbolRange(document, item); - case PConst.Kind.memberFunction: + case PConst.Kind.method: case PConst.Kind.memberGetAccessor: case PConst.Kind.memberSetAccessor: case PConst.Kind.constructorImplementation: diff --git a/extensions/typescript-language-features/src/features/workspaceSymbols.ts b/extensions/typescript-language-features/src/features/workspaceSymbols.ts index 8d6788a4254..cf7724970c6 100644 --- a/extensions/typescript-language-features/src/features/workspaceSymbols.ts +++ b/extensions/typescript-language-features/src/features/workspaceSymbols.ts @@ -9,15 +9,21 @@ import { ITypeScriptServiceClient } from '../typescriptService'; import * as fileSchemes from '../utils/fileSchemes'; import { doesResourceLookLikeAJavaScriptFile, doesResourceLookLikeATypeScriptFile } from '../utils/languageDescription'; import * as typeConverters from '../utils/typeConverters'; +import * as PConst from '../protocol.const'; function getSymbolKind(item: Proto.NavtoItem): vscode.SymbolKind { switch (item.kind) { - case 'method': return vscode.SymbolKind.Method; - case 'enum': return vscode.SymbolKind.Enum; - case 'function': return vscode.SymbolKind.Function; - case 'class': return vscode.SymbolKind.Class; - case 'interface': return vscode.SymbolKind.Interface; - case 'var': return vscode.SymbolKind.Variable; + case PConst.Kind.method: return vscode.SymbolKind.Method; + case PConst.Kind.enum: return vscode.SymbolKind.Enum; + case PConst.Kind.enumMember: return vscode.SymbolKind.EnumMember; + case PConst.Kind.function: return vscode.SymbolKind.Function; + case PConst.Kind.class: return vscode.SymbolKind.Class; + case PConst.Kind.interface: return vscode.SymbolKind.Interface; + case PConst.Kind.type: return vscode.SymbolKind.Class; + case PConst.Kind.memberVariable: return vscode.SymbolKind.Field; + case PConst.Kind.memberGetAccessor: return vscode.SymbolKind.Field; + case PConst.Kind.memberSetAccessor: return vscode.SymbolKind.Field; + case PConst.Kind.variable: return vscode.SymbolKind.Variable; default: return vscode.SymbolKind.Variable; } } diff --git a/extensions/typescript-language-features/src/protocol.const.ts b/extensions/typescript-language-features/src/protocol.const.ts index a44c175f295..37fe42fd830 100644 --- a/extensions/typescript-language-features/src/protocol.const.ts +++ b/extensions/typescript-language-features/src/protocol.const.ts @@ -21,7 +21,7 @@ export class Kind { public static readonly let = 'let'; public static readonly localFunction = 'local function'; public static readonly localVariable = 'local var'; - public static readonly memberFunction = 'method'; + public static readonly method = 'method'; public static readonly memberGetAccessor = 'getter'; public static readonly memberSetAccessor = 'setter'; public static readonly memberVariable = 'property'; diff --git a/extensions/typescript-language-features/src/utils/typeConverters.ts b/extensions/typescript-language-features/src/utils/typeConverters.ts index 0026eaa1232..78333b2da0b 100644 --- a/extensions/typescript-language-features/src/utils/typeConverters.ts +++ b/extensions/typescript-language-features/src/utils/typeConverters.ts @@ -107,7 +107,7 @@ export namespace SymbolKind { case PConst.Kind.interface: return vscode.SymbolKind.Interface; case PConst.Kind.indexSignature: return vscode.SymbolKind.Method; case PConst.Kind.callSignature: return vscode.SymbolKind.Method; - case PConst.Kind.memberFunction: return vscode.SymbolKind.Method; + case PConst.Kind.method: return vscode.SymbolKind.Method; case PConst.Kind.memberVariable: return vscode.SymbolKind.Property; case PConst.Kind.memberGetAccessor: return vscode.SymbolKind.Property; case PConst.Kind.memberSetAccessor: return vscode.SymbolKind.Property; From 8c596fbf90576f5b48b00bd860979b37b5f4978c Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 10 Mar 2020 16:38:25 -0700 Subject: [PATCH 024/169] Mark arrays readonly --- .../src/features/workspaceSymbols.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/extensions/typescript-language-features/src/features/workspaceSymbols.ts b/extensions/typescript-language-features/src/features/workspaceSymbols.ts index cf7724970c6..e23c21eeb37 100644 --- a/extensions/typescript-language-features/src/features/workspaceSymbols.ts +++ b/extensions/typescript-language-features/src/features/workspaceSymbols.ts @@ -31,7 +31,7 @@ function getSymbolKind(item: Proto.NavtoItem): vscode.SymbolKind { class TypeScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider { public constructor( private readonly client: ITypeScriptServiceClient, - private readonly modeIds: string[] + private readonly modeIds: readonly string[] ) { } public async provideWorkspaceSymbols( @@ -121,7 +121,8 @@ class TypeScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvide export function register( client: ITypeScriptServiceClient, - modeIds: string[], + modeIds: readonly string[], ) { - return vscode.languages.registerWorkspaceSymbolProvider(new TypeScriptWorkspaceSymbolProvider(client, modeIds)); + return vscode.languages.registerWorkspaceSymbolProvider( + new TypeScriptWorkspaceSymbolProvider(client, modeIds)); } From 2b029679b5873f99f1b50bf6aa6070577503d460 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Tue, 10 Mar 2020 17:17:14 -0700 Subject: [PATCH 025/169] fix #92387 --- src/vs/base/browser/ui/splitview/paneview.ts | 22 ++++++++++++------- .../browser/parts/views/viewPaneContainer.ts | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index dd943d0ed32..904da1e1116 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -22,6 +22,7 @@ export interface IPaneOptions { minimumBodySize?: number; maximumBodySize?: number; expanded?: boolean; + orientation?: Orientation; title: string; } @@ -50,6 +51,7 @@ export abstract class Pane extends Disposable implements IView { private body!: HTMLElement; protected _expanded: boolean; + protected _orientation: Orientation; protected _preventCollapse?: boolean; private expandedSize: number | undefined = undefined; @@ -117,11 +119,12 @@ export abstract class Pane extends Disposable implements IView { return headerSize + maximumBodySize; } - width: number = 0; + orthogonalSize: number = 0; constructor(options: IPaneOptions) { super(); this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded; + this._orientation = typeof options.orientation === 'undefined' ? Orientation.VERTICAL : Orientation.HORIZONTAL; this.ariaHeaderLabel = localize('viewSection', "{0} Section", options.title); this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : 120; this._maximumBodySize = typeof options.maximumBodySize === 'number' ? options.maximumBodySize : Number.POSITIVE_INFINITY; @@ -208,12 +211,15 @@ export abstract class Pane extends Disposable implements IView { this.renderBody(this.body); } - layout(height: number): void { + layout(size: number): void { const headerSize = this.headerVisible ? Pane.HEADER_SIZE : 0; + const width = this._orientation === Orientation.VERTICAL ? this.orthogonalSize : size; + const height = this._orientation === Orientation.VERTICAL ? size - headerSize : this.orthogonalSize - headerSize; + if (this.isExpanded()) { - this.layoutBody(height - headerSize, this.width); - this.expandedSize = height; + this.layoutBody(height, width); + this.expandedSize = size; } } @@ -391,7 +397,7 @@ export class PaneView extends Disposable { private dndContext: IDndContext = { draggable: null }; private el: HTMLElement; private paneItems: IPaneItem[] = []; - private width: number = 0; + private orthogonalSize: number = 0; private splitview: SplitView; private orientation: Orientation; private animationTimer: number | undefined = undefined; @@ -417,7 +423,7 @@ export class PaneView extends Disposable { const paneItem = { pane: pane, disposable: disposables }; this.paneItems.splice(index, 0, paneItem); - pane.width = this.width; + pane.orthogonalSize = this.orthogonalSize; this.splitview.addView(pane, size, index); if (this.dnd) { @@ -474,10 +480,10 @@ export class PaneView extends Disposable { } layout(height: number, width: number): void { - this.width = width; + this.orthogonalSize = this.orientation === Orientation.VERTICAL ? width : height; for (const paneItem of this.paneItems) { - paneItem.pane.width = width; + paneItem.pane.orthogonalSize = this.orthogonalSize; } this.splitview.layout(this.orientation === Orientation.HORIZONTAL ? width : height); diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index acd6638aec2..fe24e2aab07 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -210,7 +210,7 @@ export abstract class ViewPane extends Pane implements IView { @IThemeService protected themeService: IThemeService, @ITelemetryService protected telemetryService: ITelemetryService, ) { - super(options); + super({ ...options, ...{ orientation: viewDescriptorService.getViewLocation(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } }); this.id = options.id; this.title = options.title; From cfc1ab4c5f816765b91fb7ead3c3427a7c8581a3 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Mon, 9 Mar 2020 13:59:56 -0700 Subject: [PATCH 026/169] Add providerId array to authentication namespace --- src/vs/vscode.proposed.d.ts | 4 ++-- src/vs/workbench/api/common/extHost.api.impl.ts | 4 ++-- src/vs/workbench/api/common/extHostAuthentication.ts | 9 +++++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 75ab9e81145..0d9833c52ab 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -76,9 +76,9 @@ declare module 'vscode' { export const onDidChangeAuthenticationProviders: Event; /** - * Returns whether a provider with providerId is currently registered. + * An array of the ids of authentication providers that are currently registered. */ - export function hasProvider(providerId: string): boolean; + export const providerIds: string[]; /** * Get existing authentication sessions. Rejects if a provider with providerId is not diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 39f0534293c..1204423f3b8 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -188,8 +188,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I get onDidChangeAuthenticationProviders(): Event { return extHostAuthentication.onDidChangeAuthenticationProviders; }, - hasProvider(providerId: string): boolean { - return extHostAuthentication.hasProvider(providerId); + get providerIds(): string[] { + return extHostAuthentication.providerIds; }, getSessions(providerId: string, scopes: string[]): Thenable { return extHostAuthentication.getSessions(extension, providerId, scopes); diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index baa486b5c5c..e57c6e13961 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -24,8 +24,13 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication); } - hasProvider(providerId: string): boolean { - return !!this._authenticationProviders.get(providerId); + get providerIds(): string[] { + const ids: string[] = []; + this._authenticationProviders.forEach(provider => { + ids.push(provider.id); + }); + + return ids; } async getSessions(requestingExtension: IExtensionDescription, providerId: string, scopes: string[]): Promise { From 6a356716709a9bd3b4e2116226b5e0e76c0b877f Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Tue, 10 Mar 2020 20:14:05 -0500 Subject: [PATCH 027/169] Update IEditableData.validationMessage return type --- src/vs/workbench/common/views.ts | 3 +- .../contrib/files/browser/fileActions.ts | 34 +++++++++++-------- .../files/browser/views/explorerViewer.ts | 10 +++--- .../contrib/remote/browser/tunnelView.ts | 32 +++++++++++++---- 4 files changed, 52 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 97666e22c7e..f80af4bdeb0 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -580,8 +580,7 @@ export interface ITreeViewDataProvider { } export interface IEditableData { - validationMessage: (value: string) => string | null; - notificationMessage?: (value: string) => { content: string, severity: Severity } | null; + validationMessage: (value: string) => { content: string, severity: Severity } | null; placeholder?: string | null; startingValue?: string | null; onFinish: (value: string, success: boolean) => void; diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 15000d979a9..4e7e11e8ebe 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -733,18 +733,24 @@ export class ShowOpenedFileInNewWindow extends Action { } } -export function validateFileName(item: ExplorerItem, name: string): string | null { +export function validateFileName(item: ExplorerItem, name: string): { content: string, severity: Severity } | null { // Produce a well formed file name name = getWellFormedFileName(name); // Name not provided if (!name || name.length === 0 || /^\s+$/.test(name)) { - return nls.localize('emptyFileNameError', "A file or folder name must be provided."); + return { + content: nls.localize('emptyFileNameError', "A file or folder name must be provided."), + severity: Severity.Error + }; } // Relative paths only if (name[0] === '/' || name[0] === '\\') { - return nls.localize('fileNameStartsWithSlashError', "A file or folder name cannot start with a slash."); + return { + content: nls.localize('fileNameStartsWithSlashError', "A file or folder name cannot start with a slash."), + severity: Severity.Error + }; } const names = coalesce(name.split(/[\\/]/)); @@ -754,23 +760,25 @@ export function validateFileName(item: ExplorerItem, name: string): string | nul // Do not allow to overwrite existing file const child = parent?.getChild(name); if (child && child !== item) { - return nls.localize('fileNameExistsError', "A file or folder **{0}** already exists at this location. Please choose a different name.", name); + return { + content: nls.localize('fileNameExistsError', "A file or folder **{0}** already exists at this location. Please choose a different name.", name), + severity: Severity.Error + }; } } // Invalid File name const windowsBasenameValidity = item.resource.scheme === Schemas.file && isWindows; if (names.some((folderName) => !extpath.isValidBasename(folderName, windowsBasenameValidity))) { - return nls.localize('invalidFileNameError', "The name **{0}** is not valid as a file or folder name. Please choose a different name.", trimLongName(name)); + return { + content: nls.localize('invalidFileNameError', "The name **{0}** is not valid as a file or folder name. Please choose a different name.", trimLongName(name)), + severity: Severity.Error + }; } - return null; -} - -function fileNameNotificationMessage(name: string): { content: string, severity: Severity } | null { - if (/^\s|\s$/.test(name)) { + if (names.some(name => /^\s|\s$/.test(name))) { return { - content: nls.localize('fileNameWhitespaceWarning', "Leading or trailing whitespace detected."), + content: nls.localize('fileNameWhitespaceWarning', "Leading or trailing whitespace detected in file or folder name."), severity: Severity.Warning }; } @@ -794,7 +802,7 @@ export function getWellFormedFileName(filename: string): string { // Trim tabs filename = strings.trim(filename, '\t'); - // Remove trailing dots, slashes, and spaces + // Remove trailing dots and slashes filename = strings.rtrim(filename, '.'); filename = strings.rtrim(filename, '/'); filename = strings.rtrim(filename, '\\'); @@ -917,7 +925,6 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole explorerService.setEditable(newStat, { validationMessage: value => validateFileName(newStat, value), - notificationMessage: value => fileNameNotificationMessage(value), onFinish: (value, success) => { folder.removeChild(newStat); explorerService.setEditable(newStat, null); @@ -955,7 +962,6 @@ export const renameHandler = (accessor: ServicesAccessor) => { explorerService.setEditable(stat, { validationMessage: value => validateFileName(stat, value), - notificationMessage: value => fileNameNotificationMessage(value), onFinish: async (value, success) => { if (success) { const parentResource = stat.parent!.resource; diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 55778ec8f60..8729ba474a4 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -375,13 +375,13 @@ export class FilesRenderer implements ICompressibleTreeRenderer { - const content = editableData.validationMessage(value); - if (!content) { + const message = editableData.validationMessage(value); + if (!message || message.severity !== Severity.Error) { return null; } return { - content, + content: message.content, formatContent: true, type: MessageType.ERROR }; @@ -408,8 +408,8 @@ export class FilesRenderer implements ICompressibleTreeRenderer { - if (editableData.notificationMessage && inputBox.isInputValid()) { - const message = editableData.notificationMessage(inputBox.value); + if (inputBox.isInputValid()) { + const message = editableData.validationMessage(inputBox.value); if (message) { inputBox.showMessage({ content: message.content, diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 37bab5fdc5d..949faefeb3c 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -28,7 +28,7 @@ import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction } from 'vs/pl import { createAndFillInContextMenuActions, createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IRemoteExplorerService, TunnelModel, MakeAddress, TunnelType, ITunnelItem, Tunnel } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { once } from 'vs/base/common/functional'; @@ -282,13 +282,13 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer { - const content = editableData.validationMessage(value); - if (!content) { + const message = editableData.validationMessage(value); + if (!message || message.severity !== Severity.Error) { return null; } return { - content, + content: message.content, formatContent: true, type: MessageType.ERROR }; @@ -733,7 +733,17 @@ namespace ForwardPortAction { } remoteExplorerService.setEditable(undefined, null); }, - validationMessage: validateInput, + validationMessage: (value) => { + const validationString = validateInput(value); + if (!validationString) { + return null; + } + + return { + content: validationString, + severity: Severity.Error + }; + }, placeholder: forwardPrompt }); } @@ -916,7 +926,17 @@ namespace ChangeLocalPortAction { } } }, - validationMessage: validateInput, + validationMessage: (value) => { + const validationString = validateInput(value); + if (!validationString) { + return null; + } + + return { + content: validationString, + severity: Severity.Error + }; + }, placeholder: nls.localize('remote.tunnelsView.changePort', "New local port") }); } From eb4ebee6a3310df8ff5f36f78f209c2658cfcdfc Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Wed, 11 Mar 2020 00:33:14 -0400 Subject: [PATCH 028/169] Closes #92421 - view-level progress api --- src/vs/platform/progress/common/progress.ts | 3 ++- src/vs/vscode.proposed.d.ts | 20 +++++++++++++++++++ .../workbench/api/common/extHost.api.impl.ts | 3 +++ .../workbench/api/common/extHostProgress.ts | 5 +++-- .../api/common/extHostTypeConverters.ts | 3 ++- src/vs/workbench/api/common/extHostTypes.ts | 3 ++- 6 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index b6ff5e5057b..5fa930eabfb 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -45,7 +45,8 @@ export const enum ProgressLocation { Extensions = 5, Window = 10, Notification = 15, - Dialog = 20 + Dialog = 20, + View = 25 } export interface IProgressOptions { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 0d9833c52ab..ad5823c8585 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1824,4 +1824,24 @@ declare module 'vscode' { } //#endregion + + //#region https://github.com/microsoft/vscode/issues/92421 + + export enum ProgressLocation { + /** + * Show progress for a view, as progress bar inside the view (when visible), + * and as an overlay on the activity bar icon. Doesn't support cancellation or discrete progress. + */ + View = 25, + } + + export interface ProgressOptions { + /** + * The target view identifier for showing progress when using [ProgressLocation.View](#ProgressLocation.View). + */ + viewId?: string + } + + //#endregion + } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 1204423f3b8..a95e4ac7b1e 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -540,6 +540,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostProgress.withProgress(extension, { location: extHostTypes.ProgressLocation.SourceControl }, (progress, token) => task({ report(n: number) { /*noop*/ } })); }, withProgress(options: vscode.ProgressOptions, task: (progress: vscode.Progress<{ message?: string; worked?: number }>, token: vscode.CancellationToken) => Thenable) { + if (options.location === extHostTypes.ProgressLocation.View) { + checkProposedApiEnabled(extension); + } return extHostProgress.withProgress(extension, options, task); }, createOutputChannel(name: string): vscode.OutputChannel { diff --git a/src/vs/workbench/api/common/extHostProgress.ts b/src/vs/workbench/api/common/extHostProgress.ts index 27a4e145c42..ef5e614e5b9 100644 --- a/src/vs/workbench/api/common/extHostProgress.ts +++ b/src/vs/workbench/api/common/extHostProgress.ts @@ -24,9 +24,10 @@ export class ExtHostProgress implements ExtHostProgressShape { withProgress(extension: IExtensionDescription, options: ProgressOptions, task: (progress: Progress, token: CancellationToken) => Thenable): Thenable { const handle = this._handles++; - const { title, location, cancellable } = options; + const { title, location, cancellable, viewId } = options; const source = localize('extensionSource', "{0} (Extension)", extension.displayName || extension.name); - this._proxy.$startProgress(handle, { location: ProgressLocation.from(location), title, source, cancellable }, extension); + + this._proxy.$startProgress(handle, { location: ProgressLocation.from(location, viewId), title, source, cancellable }, extension); return this._withProgress(handle, task, !!cancellable); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 38154ceca2e..e443f9335d7 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1093,11 +1093,12 @@ export namespace EndOfLine { } export namespace ProgressLocation { - export function from(loc: vscode.ProgressLocation): MainProgressLocation { + export function from(loc: vscode.ProgressLocation, viewId?: string): MainProgressLocation | string { switch (loc) { case types.ProgressLocation.SourceControl: return MainProgressLocation.Scm; case types.ProgressLocation.Window: return MainProgressLocation.Window; case types.ProgressLocation.Notification: return MainProgressLocation.Notification; + case types.ProgressLocation.View: return viewId ?? ''; } throw new Error(`Unknown 'ProgressLocation'`); } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 0c62c89ad01..2c6cc5423d6 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2082,7 +2082,8 @@ export class Task implements vscode.Task2 { export enum ProgressLocation { SourceControl = 1, Window = 10, - Notification = 15 + Notification = 15, + View = 25 } @es5ClassCompat From 7240a4313cf9808614e97d284df156ba7ed6ec10 Mon Sep 17 00:00:00 2001 From: Ilia Pozdnyakov Date: Wed, 11 Mar 2020 12:13:01 +0700 Subject: [PATCH 029/169] Add a default value to FileSystemError#code #90517 --- src/vs/vscode.proposed.d.ts | 4 ++-- src/vs/workbench/api/common/extHostTypes.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index ad5823c8585..421a285c25c 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1802,9 +1802,9 @@ declare module 'vscode' { * A code that identifies this error. * * Possible values are names of errors, like [`FileNotFound`](#FileSystemError.FileNotFound), - * or `undefined` for an unspecified error. + * or `Unknown` for an unspecified error. */ - readonly code?: string; + readonly code: string; } //#endregion diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 2c6cc5423d6..e89ded47b5f 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2334,12 +2334,12 @@ export class FileSystemError extends Error { return new FileSystemError(messageOrUri, FileSystemProviderErrorCode.Unavailable, FileSystemError.Unavailable); } - readonly code?: string; + readonly code: string; constructor(uriOrMessage?: string | URI, code: FileSystemProviderErrorCode = FileSystemProviderErrorCode.Unknown, terminator?: Function) { super(URI.isUri(uriOrMessage) ? uriOrMessage.toString(true) : uriOrMessage); - this.code = terminator?.name; + this.code = terminator?.name ?? 'Unknown'; // mark the error as file system provider error so that // we can extract the error code on the receiving side From a562dabe60934d497ed91a613643bb59a245e4b9 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Wed, 11 Mar 2020 02:07:04 -0400 Subject: [PATCH 030/169] Removes menu.command from registerAction2 --- src/vs/platform/actions/common/actions.ts | 16 ++--- .../contrib/timeline/browser/timelinePane.ts | 65 +++++++++---------- 2 files changed, 38 insertions(+), 43 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index ab37df3e9fa..bc0432b2cb7 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -373,7 +373,7 @@ export interface IAction2Options extends ICommandAction { /** * One or many menu items. */ - menu?: OneOrN<{ id: MenuId } & Omit & { command?: Partial> }>; + menu?: OneOrN<{ id: MenuId } & Omit>; /** * One keybinding. @@ -396,7 +396,7 @@ export function registerAction2(ctor: { new(): Action2 }): IDisposable { const disposables = new DisposableStore(); const action = new ctor(); - const { f1, menu: menus, keybinding, description, ...command } = action.desc; + const { f1, menu, keybinding, description, ...command } = action.desc; // command disposables.add(CommandsRegistry.registerCommand({ @@ -406,14 +406,12 @@ export function registerAction2(ctor: { new(): Action2 }): IDisposable { })); // menu - if (Array.isArray(menus)) { - for (let item of menus) { - const { command: commandOverrides, ...menu } = item; - disposables.add(MenuRegistry.appendMenuItem(item.id, { command: { ...command, ...commandOverrides }, ...menu })); + if (Array.isArray(menu)) { + for (let item of menu) { + disposables.add(MenuRegistry.appendMenuItem(item.id, { command: { ...command }, ...item })); } - } else if (menus) { - const { command: commandOverrides, ...menu } = menus; - disposables.add(MenuRegistry.appendMenuItem(menu.id, { command: { ...command, ...commandOverrides }, ...menu })); + } else if (menu) { + disposables.add(MenuRegistry.appendMenuItem(menu.id, { command: { ...command }, ...menu })); } if (f1) { disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: command })); diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index b96337ce6f5..997797575f7 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -24,7 +24,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { ITimelineService, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvidersChangeEvent, TimelineRequest, Timeline, TimelinePaneId } from 'vs/workbench/contrib/timeline/common/timeline'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { SideBySideEditor, toResource } from 'vs/workbench/common/editor'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IThemeService, LIGHT, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { basename } from 'vs/base/common/path'; @@ -34,7 +34,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IActionViewItemProvider, ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction, ActionRunner } from 'vs/base/common/actions'; import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { MenuItemAction, IMenuService, MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; +import { MenuItemAction, IMenuService, MenuId, registerAction2, Action2, MenuRegistry } from 'vs/platform/actions/common/actions'; import { fromNow } from 'vs/base/common/date'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; @@ -932,38 +932,35 @@ class TimelinePaneCommands extends Disposable { } })); - this._register(registerAction2(class extends Action2 { - constructor() { - super({ - id: 'timeline.toggleFollowActiveEditor', - title: { value: localize('timeline.toggleFollowActiveEditorCommand', "Toggle Active Editor Following"), original: 'Toggle Active Editor Following' }, - category: { value: localize('timeline', "Timeline"), original: 'Timeline' }, - menu: [{ - id: MenuId.TimelineTitle, - command: { - // title: localize(`timeline.toggleFollowActiveEditorCommand.stop`, "Stop following the Active Editor"), - icon: { id: 'codicon/eye' } - }, - group: 'navigation', - order: 98, - when: TimelineFollowActiveEditorContext - }, - { - id: MenuId.TimelineTitle, - command: { - // title: localize(`ToggleFollowActiveEditorCommand.follow`, "Follow the Active Editor"), - icon: { id: 'codicon/eye-closed' } - }, - group: 'navigation', - order: 98, - when: TimelineFollowActiveEditorContext.toNegated() - }] - }); - } - run(accessor: ServicesAccessor, ...args: any[]) { - pane.followActiveEditor = !pane.followActiveEditor; - } - })); + this._register(CommandsRegistry.registerCommand('timeline.toggleFollowActiveEditor', + (accessor: ServicesAccessor, ...args: any[]) => pane.followActiveEditor = !pane.followActiveEditor + )); + + this._register(MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ + command: { + id: 'timeline.toggleFollowActiveEditor', + title: { value: localize('timeline.toggleFollowActiveEditorCommand', "Toggle Active Editor Following"), original: 'Toggle Active Editor Following' }, + // title: localize(`timeline.toggleFollowActiveEditorCommand.stop`, "Stop following the Active Editor"), + icon: { id: 'codicon/eye' }, + category: { value: localize('timeline', "Timeline"), original: 'Timeline' }, + }, + group: 'navigation', + order: 98, + when: TimelineFollowActiveEditorContext + }))); + + this._register(MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ + command: { + id: 'timeline.toggleFollowActiveEditor', + title: { value: localize('timeline.toggleFollowActiveEditorCommand', "Toggle Active Editor Following"), original: 'Toggle Active Editor Following' }, + // title: localize(`timeline.toggleFollowActiveEditorCommand.stop`, "Stop following the Active Editor"), + icon: { id: 'codicon/eye-closed' }, + category: { value: localize('timeline', "Timeline"), original: 'Timeline' }, + }, + group: 'navigation', + order: 98, + when: TimelineFollowActiveEditorContext.toNegated() + }))); this._register(timelineService.onDidChangeProviders(() => this.updateTimelineSourceFilters())); this.updateTimelineSourceFilters(); From 33a47d52de5cccb475723fb59b9a806b7c873597 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 11 Mar 2020 08:09:38 +0100 Subject: [PATCH 031/169] quick access - terminals --- .../platform/quickinput/common/quickAccess.ts | 37 ++++++-- .../contrib/tasks/browser/tasksQuickAccess.ts | 4 +- .../terminal/browser/terminaQuickAccess.ts | 86 +++++++++++++++++++ .../terminal/browser/terminal.contribution.ts | 12 +++ 4 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 src/vs/workbench/contrib/terminal/browser/terminaQuickAccess.ts diff --git a/src/vs/platform/quickinput/common/quickAccess.ts b/src/vs/platform/quickinput/common/quickAccess.ts index 8197091879a..d457a7a1737 100644 --- a/src/vs/platform/quickinput/common/quickAccess.ts +++ b/src/vs/platform/quickinput/common/quickAccess.ts @@ -144,6 +144,24 @@ Registry.add(Extensions.Quickaccess, new QuickAccessRegistry()); //#region Helper class for simple picker based providers +export enum TriggerAction { + + /** + * Do nothing after the button was clicked. + */ + NO_ACTION, + + /** + * Close the picker. + */ + CLOSE_PICKER, + + /** + * Update the results of the picker. + */ + REFRESH_PICKER +} + export interface IPickerQuickAccessItem extends IQuickPickItem { /** @@ -154,14 +172,14 @@ export interface IPickerQuickAccessItem extends IQuickPickItem { /** * A method that will be executed when a button of the pick item was - * clicked on. The picker will only close if `true` is returned. + * clicked on. * * @param buttonIndex index of the button of the item that * was clicked. * - * @returns a valud indicating if the picker should close or not. + * @returns a value that indicates what should happen after the trigger. */ - trigger?(buttonIndex: number): boolean; + trigger?(buttonIndex: number): TriggerAction; } export abstract class PickerQuickAccessProvider implements IQuickAccessProvider { @@ -217,9 +235,16 @@ export abstract class PickerQuickAccessProvider= 0) { - const hide = item.trigger(buttonIndex); - if (hide !== false) { - picker.hide(); + const action = item.trigger(buttonIndex); + switch (action) { + case TriggerAction.NO_ACTION: + break; + case TriggerAction.CLOSE_PICKER: + picker.hide(); + break; + case TriggerAction.REFRESH_PICKER: + updatePickerItems(); + break; } } } diff --git a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts index 876a620b9ca..ed57a3eab38 100644 --- a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts +++ b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction } from 'vs/platform/quickinput/common/quickAccess'; import { matchesFuzzy } from 'vs/base/common/filters'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; @@ -132,7 +132,7 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = 'term '; + + constructor( + @ITerminalService private readonly terminalService: ITerminalService, + @ICommandService private readonly commandService: ICommandService, + ) { + super(TerminalQuickAccessProvider.PREFIX); + } + + protected getPicks(filter: string): Array { + const terminalPicks: Array = []; + + const terminalTabs = this.terminalService.terminalTabs; + for (let tabIndex = 0; tabIndex < terminalTabs.length; tabIndex++) { + const terminalTab = terminalTabs[tabIndex]; + for (let terminalIndex = 0; terminalIndex < terminalTab.terminalInstances.length; terminalIndex++) { + const terminal = terminalTab.terminalInstances[terminalIndex]; + const label = `${tabIndex + 1}.${terminalIndex + 1}: ${terminal.title}`; + + const highlights = matchesFuzzy(filter, label, true); + if (highlights) { + terminalPicks.push({ + label, + ariaLabel: localize('termEntryAriaLabel', "{0}, terminal picker", label), + highlights: { label: highlights }, + buttons: [ + { + iconClass: 'codicon-gear', + tooltip: localize('renameTerminal', "Rename Terminal") + }, + { + iconClass: 'codicon-close', + tooltip: localize('killTerminal', "Kill Terminal Instance") + } + ], + accept: () => { + this.terminalService.setActiveInstance(terminal); + this.terminalService.showPanel(true); + }, + trigger: buttonIndex => { + switch (buttonIndex) { + case 0: + this.commandService.executeCommand(TERMINAL_COMMAND_ID.RENAME, terminal); + return TriggerAction.NO_ACTION; + case 1: + terminal.dispose(true); + return TriggerAction.REFRESH_PICKER; + } + + return TriggerAction.NO_ACTION; + } + }); + } + } + } + + if (terminalPicks.length > 0) { + terminalPicks.push({ type: 'separator' }); + } + + const createTerminalLabel = localize("workbench.action.terminal.newplus", "Create New Integrated Terminal"); + terminalPicks.push({ + label: '$(plus) ' + createTerminalLabel, + ariaLabel: localize('termCreateEntryAriaLabel', "{0}, create new terminal", createTerminalLabel), + accept: () => this.commandService.executeCommand('workbench.action.terminal.new') + }); + + return terminalPicks; + + } +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index a355f69febd..3eb2b345f30 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -39,6 +39,8 @@ import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; +import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminaQuickAccess'; registerSingleton(ITerminalService, TerminalService, true); @@ -60,6 +62,16 @@ quickOpenRegistry.registerQuickOpenHandler( ) ); +const quickAccessRegistry = (Registry.as(QuickAccessExtensions.Quickaccess)); + +quickAccessRegistry.registerQuickAccessProvider({ + ctor: TerminalQuickAccessProvider, + prefix: TerminalQuickAccessProvider.PREFIX, + contextKey: inTerminalsPicker, + placeholder: nls.localize('tasksQuickAccessPlaceholder', "Type the name of a terminal to open."), + helpEntries: [{ description: nls.localize('tasksQuickAccessHelp', "Show All Opened Terminals"), needsEditor: false }] +}); + const quickOpenNavigateNextInTerminalPickerId = 'workbench.action.quickOpenNavigateNextInTerminalPicker'; CommandsRegistry.registerCommand( { id: quickOpenNavigateNextInTerminalPickerId, handler: getQuickNavigateHandler(quickOpenNavigateNextInTerminalPickerId, true) }); From b8c1023dd8114b8821c28ce558634fe8c0f5d04d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 11 Mar 2020 08:12:48 +0100 Subject: [PATCH 032/169] quick access - allow to configure launch config from picker --- .../contrib/debug/browser/debugQuickAccess.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts index 6bb5804dc23..6825a6ab0aa 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { PickerQuickAccessProvider, IPickerQuickAccessItem } from 'vs/platform/quickinput/common/quickAccess'; +import { PickerQuickAccessProvider, IPickerQuickAccessItem, TriggerAction } from 'vs/platform/quickinput/common/quickAccess'; import { localize } from 'vs/nls'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; @@ -50,6 +50,10 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { if (StartAction.isEnabled(this.debugService)) { this.debugService.getConfigurationManager().selectConfiguration(config.launch, config.name); @@ -59,6 +63,11 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { + config.launch.openConfigFile(false, false); + + return TriggerAction.CLOSE_PICKER; } }); } From 4c17333c5930937db4ce289b935c27a7dbdb1d26 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 11 Mar 2020 08:35:46 +0100 Subject: [PATCH 033/169] quick access - use correct icon for terminal kill --- src/vs/workbench/contrib/terminal/browser/terminaQuickAccess.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminaQuickAccess.ts b/src/vs/workbench/contrib/terminal/browser/terminaQuickAccess.ts index 22420fc2d3a..7ecc458527b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminaQuickAccess.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminaQuickAccess.ts @@ -44,7 +44,7 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider Date: Wed, 11 Mar 2020 09:40:08 +0100 Subject: [PATCH 034/169] Fix #92430 --- src/vs/base/browser/ui/splitview/paneview.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index 904da1e1116..4f379f4425c 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -124,7 +124,7 @@ export abstract class Pane extends Disposable implements IView { constructor(options: IPaneOptions) { super(); this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded; - this._orientation = typeof options.orientation === 'undefined' ? Orientation.VERTICAL : Orientation.HORIZONTAL; + this._orientation = typeof options.orientation === 'undefined' ? Orientation.VERTICAL : options.orientation; this.ariaHeaderLabel = localize('viewSection', "{0} Section", options.title); this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : 120; this._maximumBodySize = typeof options.maximumBodySize === 'number' ? options.maximumBodySize : Number.POSITIVE_INFINITY; From 0f8a68715893d0c8ab905b5cfe976ee34515e2e9 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Tue, 10 Mar 2020 23:09:09 +0100 Subject: [PATCH 035/169] Add StringSHA1 --- src/vs/base/common/hash.ts | 234 +++++++++++++++++++++++++++ src/vs/base/common/strings.ts | 32 ++-- src/vs/base/test/common/hash.test.ts | 26 ++- 3 files changed, 274 insertions(+), 18 deletions(-) diff --git a/src/vs/base/common/hash.ts b/src/vs/base/common/hash.ts index 1902e82c312..4b47073d8e5 100644 --- a/src/vs/base/common/hash.ts +++ b/src/vs/base/common/hash.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as strings from 'vs/base/common/strings'; + /** * Return a hash value for an object. */ @@ -70,3 +72,235 @@ export class Hasher { return this._value; } } + +const enum SHA1Constant { + BLOCK_SIZE = 64, // 512 / 8 + UNICODE_REPLACEMENT = 0xFFFD, +} + +function leftRotate(value: number, bits: number, totalBits: number = 32): number { + // delta + bits = totalBits + const delta = totalBits - bits; + + // All ones, expect `delta` zeros aligned to the right + const mask = ~((1 << delta) - 1); + + // Join (value left-shifted `bits` bits) with (masked value right-shifted `delta` bits) + return ((value << bits) | ((mask & value) >>> delta)) >>> 0; +} + +function fill(dest: Uint8Array, index: number = 0, count: number = dest.byteLength, value: number = 0): void { + for (let i = 0; i < count; i++) { + dest[index + i] = value; + } +} + +function leftPad(value: string, length: number, char: string = '0'): string { + while (value.length < length) { + value = char + value; + } + return value; +} + +function toHexString(value: number, bitsize: number = 32): string { + return leftPad((value >>> 0).toString(16), bitsize / 4); +} + +/** + * A SHA1 implementation that works with strings and does not allocate. + */ +export class StringSHA1 { + private static _bigBlock32 = new DataView(new ArrayBuffer(320)); // 80 * 4 = 320 + + private _h0 = 0x67452301; + private _h1 = 0xEFCDAB89; + private _h2 = 0x98BADCFE; + private _h3 = 0x10325476; + private _h4 = 0xC3D2E1F0; + + private readonly _buff: Uint8Array; + private readonly _buffDV: DataView; + private _buffLen: number; + private _totalLen: number; + private _leftoverHighSurrogate: number; + private _finished: boolean; + + constructor() { + this._buff = new Uint8Array(SHA1Constant.BLOCK_SIZE + 3 /* to fit any utf-8 */); + this._buffDV = new DataView(this._buff.buffer); + this._buffLen = 0; + this._totalLen = 0; + this._leftoverHighSurrogate = 0; + this._finished = false; + } + + public update(str: string): void { + const strLen = str.length; + if (strLen === 0) { + return; + } + + const buff = this._buff; + let buffLen = this._buffLen; + let leftoverHighSurrogate = this._leftoverHighSurrogate; + let charCode: number; + let offset: number; + + if (leftoverHighSurrogate !== 0) { + charCode = leftoverHighSurrogate; + offset = -1; + leftoverHighSurrogate = 0; + } else { + charCode = str.charCodeAt(0); + offset = 0; + } + + while (true) { + let codePoint = charCode; + if (strings.isHighSurrogate(charCode)) { + if (offset + 1 < strLen) { + const nextCharCode = str.charCodeAt(offset + 1); + if (strings.isLowSurrogate(nextCharCode)) { + offset++; + codePoint = strings.computeCodePoint(charCode, nextCharCode); + } else { + // illegal => unicode replacement character + codePoint = SHA1Constant.UNICODE_REPLACEMENT; + } + } else { + // last character is a surrogate pair + leftoverHighSurrogate = charCode; + break; + } + } else if (strings.isLowSurrogate(charCode)) { + // illegal => unicode replacement character + codePoint = SHA1Constant.UNICODE_REPLACEMENT; + } + + buffLen = this._push(buff, buffLen, codePoint); + offset++; + if (offset < strLen) { + charCode = str.charCodeAt(offset); + } else { + break; + } + } + + this._buffLen = buffLen; + this._leftoverHighSurrogate = leftoverHighSurrogate; + } + + private _push(buff: Uint8Array, buffLen: number, codePoint: number): number { + if (codePoint < 0x0080) { + buff[buffLen++] = codePoint; + } else if (codePoint < 0x0800) { + buff[buffLen++] = 0b11000000 | ((codePoint & 0b00000000000000000000011111000000) >>> 6); + buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0); + } else if (codePoint < 0x10000) { + buff[buffLen++] = 0b11100000 | ((codePoint & 0b00000000000000001111000000000000) >>> 12); + buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6); + buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0); + } else { + buff[buffLen++] = 0b11110000 | ((codePoint & 0b00000000000111000000000000000000) >>> 18); + buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000111111000000000000) >>> 12); + buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6); + buff[buffLen++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0); + } + + if (buffLen >= SHA1Constant.BLOCK_SIZE) { + this._step(); + buffLen -= SHA1Constant.BLOCK_SIZE; + this._totalLen += SHA1Constant.BLOCK_SIZE; + // take last 3 in case of UTF8 overflow + buff[0] = buff[SHA1Constant.BLOCK_SIZE + 0]; + buff[1] = buff[SHA1Constant.BLOCK_SIZE + 1]; + buff[2] = buff[SHA1Constant.BLOCK_SIZE + 2]; + } + + return buffLen; + } + + public digest(): string { + if (!this._finished) { + this._finished = true; + if (this._leftoverHighSurrogate) { + // illegal => unicode replacement character + this._leftoverHighSurrogate = 0; + this._buffLen = this._push(this._buff, this._buffLen, SHA1Constant.UNICODE_REPLACEMENT); + } + this._totalLen += this._buffLen; + this._wrapUp(); + } + + return toHexString(this._h0) + toHexString(this._h1) + toHexString(this._h2) + toHexString(this._h3) + toHexString(this._h4); + } + + private _wrapUp(): void { + this._buff[this._buffLen++] = 0x80; + fill(this._buff, this._buffLen); + + if (this._buffLen > 56) { + this._step(); + fill(this._buff); + } + + // this will fit because the mantissa can cover up to 52 bits + const ml = 8 * this._totalLen; + + this._buffDV.setUint32(56, Math.floor(ml / 4294967296), false); + this._buffDV.setUint32(60, ml % 4294967296, false); + + this._step(); + } + + private _step(): void { + const bigBlock32 = StringSHA1._bigBlock32; + const data = this._buffDV; + + for (let j = 0; j < 64 /* 16*4 */; j += 4) { + bigBlock32.setUint32(j, data.getUint32(j, false), false); + } + + for (let j = 64; j < 320 /* 80*4 */; j += 4) { + bigBlock32.setUint32(j, leftRotate((bigBlock32.getUint32(j - 12, false) ^ bigBlock32.getUint32(j - 32, false) ^ bigBlock32.getUint32(j - 56, false) ^ bigBlock32.getUint32(j - 64, false)), 1), false); + } + + let a = this._h0; + let b = this._h1; + let c = this._h2; + let d = this._h3; + let e = this._h4; + + let f: number, k: number; + let temp: number; + + for (let j = 0; j < 80; j++) { + if (j < 20) { + f = (b & c) | ((~b) & d); + k = 0x5A827999; + } else if (j < 40) { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } else if (j < 60) { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } else { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + + temp = (leftRotate(a, 5) + f + e + k + bigBlock32.getUint32(j * 4, false)) & 0xffffffff; + e = d; + d = c; + c = leftRotate(b, 30); + b = a; + a = temp; + } + + this._h0 = (this._h0 + a) & 0xffffffff; + this._h1 = (this._h1 + b) & 0xffffffff; + this._h2 = (this._h2 + c) & 0xffffffff; + this._h3 = (this._h3 + d) & 0xffffffff; + this._h4 = (this._h4 + e) & 0xffffffff; + } +} diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index 4a26a87ee2f..21277fc2e70 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -428,29 +428,27 @@ export function commonSuffixLength(a: string, b: string): number { return len; } -// --- unicode -// http://en.wikipedia.org/wiki/Surrogate_pair -// Returns the code point starting at a specified index in a string -// Code points U+0000 to U+D7FF and U+E000 to U+FFFF are represented on a single character -// Code points U+10000 to U+10FFFF are represented on two consecutive characters -//export function getUnicodePoint(str:string, index:number, len:number):number { -// const chrCode = str.charCodeAt(index); -// if (0xD800 <= chrCode && chrCode <= 0xDBFF && index + 1 < len) { -// const nextChrCode = str.charCodeAt(index + 1); -// if (0xDC00 <= nextChrCode && nextChrCode <= 0xDFFF) { -// return (chrCode - 0xD800) << 10 + (nextChrCode - 0xDC00) + 0x10000; -// } -// } -// return chrCode; -//} +/** + * See http://en.wikipedia.org/wiki/Surrogate_pair + */ export function isHighSurrogate(charCode: number): boolean { return (0xD800 <= charCode && charCode <= 0xDBFF); } +/** + * See http://en.wikipedia.org/wiki/Surrogate_pair + */ export function isLowSurrogate(charCode: number): boolean { return (0xDC00 <= charCode && charCode <= 0xDFFF); } +/** + * See http://en.wikipedia.org/wiki/Surrogate_pair + */ +export function computeCodePoint(highSurrogate: number, lowSurrogate: number): number { + return ((highSurrogate - 0xD800) << 10) + (lowSurrogate - 0xDC00) + 0x10000; +} + /** * get the code point that begins at offset `offset` */ @@ -459,7 +457,7 @@ export function getNextCodePoint(str: string, len: number, offset: number): numb if (isHighSurrogate(charCode) && offset + 1 < len) { const nextCharCode = str.charCodeAt(offset + 1); if (isLowSurrogate(nextCharCode)) { - return ((charCode - 0xD800) << 10) + (nextCharCode - 0xDC00) + 0x10000; + return computeCodePoint(charCode, nextCharCode); } } return charCode; @@ -473,7 +471,7 @@ function getPrevCodePoint(str: string, offset: number): number { if (isLowSurrogate(charCode) && offset > 1) { const prevCharCode = str.charCodeAt(offset - 2); if (isHighSurrogate(prevCharCode)) { - return ((prevCharCode - 0xD800) << 10) + (charCode - 0xDC00) + 0x10000; + return computeCodePoint(prevCharCode, charCode); } } return charCode; diff --git a/src/vs/base/test/common/hash.test.ts b/src/vs/base/test/common/hash.test.ts index 58b5904b63d..3225caf7b23 100644 --- a/src/vs/base/test/common/hash.test.ts +++ b/src/vs/base/test/common/hash.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { hash } from 'vs/base/common/hash'; +import { hash, StringSHA1 } from 'vs/base/common/hash'; suite('Hash', () => { test('string', () => { @@ -53,4 +53,28 @@ suite('Hash', () => { assert.notEqual(a, b); }); + function checkSHA1(strings: string[], expected: string) { + const hash = new StringSHA1(); + for (const str of strings) { + hash.update(str); + } + const actual = hash.digest(); + assert.equal(actual, expected); + } + + test('sha1-1', () => { + checkSHA1(['\udd56'], '9bdb77276c1852e1fb067820472812fcf6084024'); + }); + + test('sha1-2', () => { + checkSHA1(['\udb52'], '9bdb77276c1852e1fb067820472812fcf6084024'); + }); + + test('sha1-3', () => { + checkSHA1(['\uda02ꑍ'], '9b483a471f22fe7e09d83f221871a987244bbd3f'); + }); + + test('sha1-4', () => { + checkSHA1(['hello'], 'aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d'); + }); }); From 128f0c5950a619ef1d67f86abefe37a4b1435454 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 11 Mar 2020 09:46:34 +0100 Subject: [PATCH 036/169] Fixes #82631: Rename "Move Caret..." to "Move Selected Text..." --- src/vs/editor/contrib/caretOperations/caretOperations.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/caretOperations/caretOperations.ts b/src/vs/editor/contrib/caretOperations/caretOperations.ts index 3b715b871e7..abd6ab267fd 100644 --- a/src/vs/editor/contrib/caretOperations/caretOperations.ts +++ b/src/vs/editor/contrib/caretOperations/caretOperations.ts @@ -42,8 +42,8 @@ class MoveCaretLeftAction extends MoveCaretAction { constructor() { super(true, { id: 'editor.action.moveCarretLeftAction', - label: nls.localize('caret.moveLeft', "Move Caret Left"), - alias: 'Move Caret Left', + label: nls.localize('caret.moveLeft', "Move Selected Text Left"), + alias: 'Move Selected Text Left', precondition: EditorContextKeys.writable }); } @@ -53,8 +53,8 @@ class MoveCaretRightAction extends MoveCaretAction { constructor() { super(false, { id: 'editor.action.moveCarretRightAction', - label: nls.localize('caret.moveRight', "Move Caret Right"), - alias: 'Move Caret Right', + label: nls.localize('caret.moveRight', "Move Selected Text Right"), + alias: 'Move Selected Text Right', precondition: EditorContextKeys.writable }); } From 5890f03a7041df5d67a97ce6c6caa159373ee173 Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Wed, 11 Mar 2020 05:09:19 -0500 Subject: [PATCH 037/169] :lipstick: --- .../contrib/remote/browser/tunnelView.ts | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 949faefeb3c..ae1bac984fd 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -657,6 +657,17 @@ export class TunnelPanelDescriptor implements IViewDescriptor { } } +function validationMessage(validationString: string | null): { content: string, severity: Severity } | null { + if (!validationString) { + return null; + } + + return { + content: validationString, + severity: Severity.Error + }; +} + namespace LabelTunnelAction { export const ID = 'remote.tunnel.label'; export const LABEL = nls.localize('remote.tunnel.label', "Set Label"); @@ -733,17 +744,7 @@ namespace ForwardPortAction { } remoteExplorerService.setEditable(undefined, null); }, - validationMessage: (value) => { - const validationString = validateInput(value); - if (!validationString) { - return null; - } - - return { - content: validationString, - severity: Severity.Error - }; - }, + validationMessage: (value) => validationMessage(validateInput(value)), placeholder: forwardPrompt }); } @@ -926,17 +927,7 @@ namespace ChangeLocalPortAction { } } }, - validationMessage: (value) => { - const validationString = validateInput(value); - if (!validationString) { - return null; - } - - return { - content: validationString, - severity: Severity.Error - }; - }, + validationMessage: (value) => validationMessage(validateInput(value)), placeholder: nls.localize('remote.tunnelsView.changePort', "New local port") }); } From 9428cdb9cb1fc3741267e6bfb999205f6fb16ce1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 11 Mar 2020 12:12:50 +0100 Subject: [PATCH 038/169] quick input - add an option `buttonsAlwaysVisible` to always show buttons for an item --- src/vs/base/parts/quickinput/browser/media/quickInput.css | 1 + src/vs/base/parts/quickinput/browser/quickInputList.ts | 6 ++++++ src/vs/base/parts/quickinput/common/quickInput.ts | 6 ++++++ 3 files changed, 13 insertions(+) diff --git a/src/vs/base/parts/quickinput/browser/media/quickInput.css b/src/vs/base/parts/quickinput/browser/media/quickInput.css index 1a5f4d35f6c..015db56978b 100644 --- a/src/vs/base/parts/quickinput/browser/media/quickInput.css +++ b/src/vs/base/parts/quickinput/browser/media/quickInput.css @@ -239,6 +239,7 @@ margin-right: 8px; } +.quick-input-list .quick-input-list-entry.always-visible-actions .quick-input-list-entry-action-bar, .quick-input-list .quick-input-list-entry:hover .quick-input-list-entry-action-bar, .quick-input-list .monaco-list-row.focused .quick-input-list-entry-action-bar { display: flex; diff --git a/src/vs/base/parts/quickinput/browser/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts index 2dee3651423..cb80c1efd2d 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputList.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts @@ -185,6 +185,12 @@ class ListElementRenderer implements IListRenderer { From dc17cef27c4e368e75f2bd1a4e0da319bed9f67f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 11 Mar 2020 12:14:09 +0100 Subject: [PATCH 039/169] quick access - tweak aria labels --- .../quickinput/browser/helpQuickAccess.ts | 8 ++++---- .../contrib/debug/browser/debugQuickAccess.ts | 14 ++++++------- .../quickaccess/browser/viewQuickAccess.ts | 11 ++++++++-- .../contrib/tasks/browser/tasksQuickAccess.ts | 20 +++++++++---------- .../terminal/browser/terminaQuickAccess.ts | 12 +++++------ 5 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/vs/platform/quickinput/browser/helpQuickAccess.ts b/src/vs/platform/quickinput/browser/helpQuickAccess.ts index dbfd72a891a..18efb7a5a61 100644 --- a/src/vs/platform/quickinput/browser/helpQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/helpQuickAccess.ts @@ -22,7 +22,7 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider { constructor(@IQuickInputService private readonly quickInputService: IQuickInputService) { } - provide(picker: IQuickPick, token: CancellationToken): IDisposable { + provide(picker: IQuickPick): IDisposable { const disposables = new DisposableStore(); // Open a picker with the selected value if picked @@ -57,7 +57,7 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider { const globalProviders: IHelpQuickAccessPickItem[] = []; const editorProviders: IHelpQuickAccessPickItem[] = []; - for (const provider of this.registry.getQuickAccessProviders().sort((p1, p2) => p1.prefix.localeCompare(p2.prefix))) { + for (const provider of this.registry.getQuickAccessProviders().sort((providerA, providerB) => providerA.prefix.localeCompare(providerB.prefix))) { for (const helpEntry of provider.helpEntries) { const prefix = helpEntry.prefix || provider.prefix; const label = prefix || '\u2026' /* ... */; @@ -65,8 +65,8 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider { (helpEntry.needsEditor ? editorProviders : globalProviders).push({ prefix, label, - description: helpEntry.description, - ariaLabel: localize('entryAriaLabel', "{0}, picker help", label) + ariaLabel: localize('entryAriaLabel', "{0}, quick access help picker", label), + description: helpEntry.description }); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts index 6825a6ab0aa..7c39c372d52 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts @@ -47,13 +47,18 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { + config.launch.openConfigFile(false, false); + + return TriggerAction.CLOSE_PICKER; + }, accept: async () => { if (StartAction.isEnabled(this.debugService)) { this.debugService.getConfigurationManager().selectConfiguration(config.launch, config.name); @@ -63,11 +68,6 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { - config.launch.openConfigFile(false, false); - - return TriggerAction.CLOSE_PICKER; } }); } @@ -89,7 +89,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider this.commandService.executeCommand('debug.addConfiguration', launch.uri.toString()) diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts index 0141925e954..dfe5eaeb3d2 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -95,6 +95,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider this.viewsService.openView(view.id, true) }); @@ -110,6 +111,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider this.viewletService.openViewlet(viewlet.id, true) }); @@ -121,6 +123,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider this.panelService.openPanel(panel.id, true) }); @@ -137,8 +140,10 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider { tab.terminalInstances.forEach((terminal, terminalIndex) => { + const label = localize('terminalTitle', "{0}: {1}", `${tabIndex + 1}.${terminalIndex + 1}`, terminal.title); viewEntries.push({ - label: localize('terminalTitle', "{0}: {1}", `${tabIndex + 1}.${terminalIndex + 1}`, terminal.title), + label, + ariaLabel: localize('viewPickAriaLabel', "{0}, view picker", label), containerLabel: localize('terminals', "Terminal"), accept: async () => { await this.terminalService.showPanel(true); @@ -152,8 +157,10 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider this.outputService.showChannel(channel.id) }); diff --git a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts index ed57a3eab38..949d9a2c6a4 100644 --- a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts +++ b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts @@ -59,14 +59,12 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider { + for (const key of recentlyUsedTasks.keys()) { const task = taskMap[key]; if (task) { recent.push(task); } - }); - + } for (const task of tasks) { const key = task.getRecentlyUsedKey(); if (!key || !recentlyUsedTasks.has(key)) { @@ -83,13 +81,13 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider sorter.compare(a, b)); - this.fillPicks(taskPicks, filter, configured, localize('configured', 'configured tasks')); + this.fillPicks(taskPicks, filter, configured, localize('configured', "configured tasks")); detected.sort((a, b) => sorter.compare(a, b)); - this.fillPicks(taskPicks, filter, detected, localize('detected', 'detected tasks')); + this.fillPicks(taskPicks, filter, detected, localize('detected', "detected tasks")); return taskPicks; } @@ -107,7 +105,7 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider { @@ -122,9 +120,6 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider { - this.taskService.run(task, { attachProblemMatcher: true }); - }, trigger: () => { if (ContributedTask.is(task)) { this.taskService.customize(task, undefined, true); @@ -133,6 +128,9 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider { + this.taskService.run(task, { attachProblemMatcher: true }); } }); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminaQuickAccess.ts b/src/vs/workbench/contrib/terminal/browser/terminaQuickAccess.ts index 7ecc458527b..a9dc1ccfafe 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminaQuickAccess.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminaQuickAccess.ts @@ -48,10 +48,6 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider { - this.terminalService.setActiveInstance(terminal); - this.terminalService.showPanel(true); - }, trigger: buttonIndex => { switch (buttonIndex) { case 0: @@ -63,6 +59,10 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider { + this.terminalService.setActiveInstance(terminal); + this.terminalService.showPanel(true); } }); } @@ -75,8 +75,8 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider this.commandService.executeCommand('workbench.action.terminal.new') }); From 5788cf98ef3005adce1b559a07fd791da77faca2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 11 Mar 2020 12:14:45 +0100 Subject: [PATCH 040/169] quick access - switch to handler automatically from help when typing prefix to preserve todays behaviour --- src/vs/platform/quickinput/browser/helpQuickAccess.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/quickinput/browser/helpQuickAccess.ts b/src/vs/platform/quickinput/browser/helpQuickAccess.ts index 18efb7a5a61..9c05d923e43 100644 --- a/src/vs/platform/quickinput/browser/helpQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/helpQuickAccess.ts @@ -6,7 +6,6 @@ import { IQuickPick, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IQuickAccessProvider, IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; import { Registry } from 'vs/platform/registry/common/platform'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { localize } from 'vs/nls'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; @@ -33,6 +32,15 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider { } })); + // Also open a picker when we detect the user typed the exact + // name of a provider (e.g. `?term` for terminals) + disposables.add(picker.onDidChangeValue(value => { + const providerDescriptor = this.registry.getQuickAccessProvider(value.substr(HelpQuickAccessProvider.PREFIX.length)); + if (providerDescriptor && providerDescriptor.prefix !== HelpQuickAccessProvider.PREFIX) { + this.quickInputService.quickAccess.show(providerDescriptor.prefix); + } + })); + // Fill in all providers separated by editor/global scope const { editorProviders, globalProviders } = this.getQuickAccessProviders(); picker.items = editorProviders.length === 0 || globalProviders.length === 0 ? From a6d692e48370e19de701b1fc2c3f7b9e30af22f2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 11 Mar 2020 12:20:58 +0100 Subject: [PATCH 041/169] quick access - editors picker to have an action to close editors and indicate dirty state --- .../platform/quickinput/common/quickAccess.ts | 28 +++++++++++++------ .../browser/parts/editor/editorQuickAccess.ts | 23 +++++++++++---- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/vs/platform/quickinput/common/quickAccess.ts b/src/vs/platform/quickinput/common/quickAccess.ts index d457a7a1737..dab6c6e05a4 100644 --- a/src/vs/platform/quickinput/common/quickAccess.ts +++ b/src/vs/platform/quickinput/common/quickAccess.ts @@ -177,9 +177,10 @@ export interface IPickerQuickAccessItem extends IQuickPickItem { * @param buttonIndex index of the button of the item that * was clicked. * - * @returns a value that indicates what should happen after the trigger. + * @returns a value that indicates what should happen after the trigger + * which can be a `Promise` for long running operations. */ - trigger?(buttonIndex: number): TriggerAction; + trigger?(buttonIndex: number): TriggerAction | Promise; } export abstract class PickerQuickAccessProvider implements IQuickAccessProvider { @@ -210,13 +211,18 @@ export abstract class PickerQuickAccessProvider updatePickerItems())); updatePickerItems(); @@ -231,11 +237,17 @@ export abstract class PickerQuickAccessProvider { + disposables.add(picker.onDidTriggerItemButton(async ({ button, item }) => { if (typeof item.trigger === 'function') { const buttonIndex = item.buttons?.indexOf(button) ?? -1; if (buttonIndex >= 0) { - const action = item.trigger(buttonIndex); + const result = item.trigger(buttonIndex); + const action = (typeof result === 'number') ? result : await result; + + if (token.isCancellationRequested) { + return; + } + switch (action) { case TriggerAction.NO_ACTION: break; diff --git a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts index 3664da1b2d7..5dd721feb52 100644 --- a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts +++ b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource } from 'vs/platform/quickinput/common/quickInput'; -import { PickerQuickAccessProvider, IPickerQuickAccessItem } from 'vs/platform/quickinput/common/quickAccess'; +import { PickerQuickAccessProvider, IPickerQuickAccessItem, TriggerAction } from 'vs/platform/quickinput/common/quickAccess'; import { IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorsOrder, IEditorIdentifier, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -83,19 +83,32 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro } private doGetEditorPickItems(): Array { - return this.doGetEditors().map(({ editor, groupId }) => { + return this.doGetEditors().map(({ editor, groupId }): IEditorQuickPickItem => { const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); + const isDirty = editor.isDirty() && !editor.isSaving(); return { editor, groupId, resource, - label: editor.isDirty() && !editor.isSaving() ? `$(circle-filled) ${editor.getName()}` : editor.getName(), - ariaLabel: localize('entryAriaLabel', "{0}, editor picker", editor.getName()), + label: editor.getName(), + ariaLabel: localize('entryAriaLabel', "{0}, editors picker", editor.getName()), description: editor.getDescription(), iconClasses: getIconClasses(this.modelService, this.modeService, resource), italic: !this.editorGroupService.getGroup(groupId)?.isPinned(editor), - accept: () => this.editorGroupService.getGroup(groupId)?.openEditor(editor) + buttonsAlwaysVisible: isDirty, + buttons: [ + { + iconClass: isDirty ? 'codicon-circle-filled' : 'codicon-close', + tooltip: localize('closeEditor', "Close Editor") + } + ], + trigger: async () => { + await this.editorGroupService.getGroup(groupId)?.closeEditor(editor, { preserveFocus: true }); + + return TriggerAction.REFRESH_PICKER; + }, + accept: () => this.editorGroupService.getGroup(groupId)?.openEditor(editor), }; }); } From 5bb4f2507a69e2537c9b2ed412340d866589f96e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 11 Mar 2020 12:27:32 +0100 Subject: [PATCH 042/169] add tooltip add toggled icon and tooltip adopt above changes --- src/vs/code/electron-main/window.ts | 10 +-- .../browser/menuEntryActionViewItem.ts | 25 ++++--- src/vs/platform/actions/common/actions.ts | 69 ++++++------------- src/vs/platform/actions/common/menuService.ts | 3 +- .../electron-main/electronMainService.ts | 4 +- src/vs/platform/electron/node/electron.ts | 4 +- .../platform/windows/electron-main/windows.ts | 4 +- .../output/browser/output.contribution.ts | 35 +++------- .../quickopen/browser/commandsHandler.ts | 10 +-- .../electron-browser/remote.contribution.ts | 6 +- src/vs/workbench/electron-browser/window.ts | 10 +-- 11 files changed, 71 insertions(+), 109 deletions(-) diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index a7f4792dc4e..de0a7dff13a 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -22,7 +22,7 @@ import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; -import { ISerializableMenuItemAction } from 'vs/platform/actions/common/actions'; +import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import * as perf from 'vs/base/common/performance'; import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; @@ -1094,7 +1094,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } } - updateTouchBar(groups: ISerializableMenuItemAction[][]): void { + updateTouchBar(groups: ISerializableCommandAction[][]): void { if (!isMacintosh) { return; // only supported on macOS } @@ -1123,10 +1123,10 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._win.setTouchBar(new TouchBar({ items: this.touchBarGroups })); } - private createTouchBarGroup(): TouchBarSegmentedControl { + private createTouchBarGroup(items: ISerializableCommandAction[] = []): TouchBarSegmentedControl { // Group Segments - const segments = this.createTouchBarGroupSegments(); + const segments = this.createTouchBarGroupSegments(items); // Group Control const control = new TouchBar.TouchBarSegmentedControl({ @@ -1141,7 +1141,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { return control; } - private createTouchBarGroupSegments(items: ISerializableMenuItemAction[] = []): ITouchBarSegment[] { + private createTouchBarGroupSegments(items: ISerializableCommandAction[] = []): ITouchBarSegment[] { const segments: ITouchBarSegment[] = items.map(item => { let icon: NativeImage | undefined; if (item.icon && !ThemeIcon.isThemeIcon(item.icon) && item.icon?.dark?.scheme === 'file') { diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index b66e2612d09..41260c2dc6e 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -12,7 +12,7 @@ import { IdGenerator } from 'vs/base/common/idGenerator'; import { IDisposable, toDisposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { localize } from 'vs/nls'; -import { IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction, Icon } from 'vs/platform/actions/common/actions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -148,7 +148,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { @INotificationService protected _notificationService: INotificationService, @IContextMenuService _contextMenuService: IContextMenuService ) { - super(undefined, _action, { icon: !!(_action.class || _action.icon), label: !_action.class && !_action.icon }); + super(undefined, _action, { icon: !!(_action.class || _action.item.icon), label: !_action.class && !_action.item.icon }); this._altKey = AlternativeKeyEmitter.getInstance(_contextMenuService); } @@ -216,9 +216,10 @@ export class MenuEntryActionViewItem extends ActionViewItem { const keybinding = this._keybindingService.lookupKeybinding(this._commandAction.id); const keybindingLabel = keybinding && keybinding.getLabel(); + const tooltip = this._commandAction.tooltip || this._commandAction.label; this.label.title = keybindingLabel - ? localize('titleAndKb', "{0} ({1})", this._commandAction.label, keybindingLabel) - : this._commandAction.label; + ? localize('titleAndKb', "{0} ({1})", tooltip, keybindingLabel) + : tooltip; } } @@ -237,9 +238,11 @@ export class MenuEntryActionViewItem extends ActionViewItem { _updateItemClass(item: MenuItemAction): void { this._itemClassDispose.value = undefined; - if (ThemeIcon.isThemeIcon(item.icon)) { + const icon = item.checked && (item.item.toggled as { icon?: Icon })?.icon ? (item.item.toggled as { icon: Icon }).icon : item.item.icon; + + if (ThemeIcon.isThemeIcon(icon)) { // theme icons - const iconClass = ThemeIcon.asClassName(item.icon); + const iconClass = ThemeIcon.asClassName(icon); if (this.label && iconClass) { addClasses(this.label, iconClass); this._itemClassDispose.value = toDisposable(() => { @@ -249,20 +252,20 @@ export class MenuEntryActionViewItem extends ActionViewItem { }); } - } else if (item.icon) { + } else if (icon) { // icon path let iconClass: string; - if (item.icon?.dark?.scheme) { + if (icon?.dark?.scheme) { - const iconPathMapKey = item.icon.dark.toString(); + const iconPathMapKey = icon.dark.toString(); if (MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { iconClass = MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!; } else { iconClass = ids.nextId(); - createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.light || item.icon.dark)}`); - createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.dark)}`); + createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(icon.light || icon.dark)}`); + createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(icon.dark)}`); MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index f4dcad91966..0cf822b0156 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -21,36 +21,18 @@ export interface ILocalizedString { } export type Icon = { dark?: URI; light?: URI; } | ThemeIcon; -export type ToggleAwareIcon = { toggled?: Icon, untoggled?: Icon }; -export type ToggleAwareTitle = { toggled?: string | ILocalizedString, untoggled?: string | ILocalizedString }; export interface ICommandAction { id: string; - title: string | ILocalizedString | ToggleAwareTitle; + title: string | ILocalizedString; category?: string | ILocalizedString; - icon?: Icon | ToggleAwareIcon; + tooltip?: string | ILocalizedString; + icon?: Icon; precondition?: ContextKeyExpression; - toggled?: ContextKeyExpression; + toggled?: ContextKeyExpression | { condition: ContextKeyExpression, icon?: Icon, tooltip?: string | ILocalizedString }; } -export function isToggleAwareTitle(thing: unknown): thing is ToggleAwareTitle { - return thing && typeof thing === 'object' - && ((typeof (thing as ToggleAwareTitle).toggled === 'string' || typeof (thing as ToggleAwareTitle).toggled === 'object') - || (typeof (thing as ToggleAwareTitle).untoggled === 'string' || typeof (thing as ToggleAwareTitle).untoggled === 'object')); -} - -export function isIcon(thing: unknown): thing is Icon { - if (ThemeIcon.isThemeIcon(thing)) { - return true; - } - return thing && typeof thing === 'object' - && ((thing as { dark?: URI, light?: URI }).dark instanceof URI || (thing as { dark?: URI, light?: URI }).light instanceof URI); -} - -export function isToggleAwareIcon(thing: unknown): thing is ToggleAwareIcon { - return thing && typeof thing === 'object' - && (isIcon((thing as ToggleAwareIcon).toggled) || isIcon((thing as ToggleAwareIcon).untoggled)); -} +export type ISerializableCommandAction = UriDto; export interface IMenuItem { command: ICommandAction; @@ -281,18 +263,9 @@ export class SubmenuItemAction extends Action { } } -export type ISerializableMenuItemAction = UriDto<{ - id: string; - title: string | ILocalizedString; - category: string | ILocalizedString | undefined; - icon: Icon | undefined; -}>; - export class MenuItemAction extends ExecuteCommandAction { - readonly title: string | ILocalizedString; - readonly category: string | ILocalizedString | undefined; - readonly icon: Icon | undefined; + readonly item: ICommandAction; readonly alt: MenuItemAction | undefined; private _options: IMenuActionOptions; @@ -304,14 +277,21 @@ export class MenuItemAction extends ExecuteCommandAction { @IContextKeyService contextKeyService: IContextKeyService, @ICommandService commandService: ICommandService ) { - super(item.id, '', commandService); - this.title = (isToggleAwareTitle(item.title) ? this._checked ? item.title.toggled : item.title.untoggled : item.title) || ''; - this._label = typeof this.title === 'string' ? this.title : this.title.value; + super(item.id, typeof item.title === 'string' ? item.title : item.title.value, commandService); + this.item = item; this._cssClass = undefined; - this._enabled = !item.precondition || contextKeyService.contextMatchesRules(item.precondition); - this._checked = Boolean(item.toggled && contextKeyService.contextMatchesRules(item.toggled)); - this.category = item.category; - this.icon = isToggleAwareIcon(item.icon) ? this.checked ? item.icon.toggled : item.icon.untoggled : item.icon; + this._enabled = !this.item.precondition || contextKeyService.contextMatchesRules(this.item.precondition); + this._tooltip = this.item.tooltip ? typeof this.item.tooltip === 'string' ? this.item.tooltip : this.item.tooltip.value : undefined; + + if (this.item.toggled) { + const toggled = ((this.item.toggled as { condition: ContextKeyExpression }).condition ? this.item.toggled : { condition: this.item.toggled }) as { + condition: ContextKeyExpression, icon?: Icon, tooltip?: string | ILocalizedString + }; + this._checked = contextKeyService.contextMatchesRules(toggled.condition); + if (this._checked && toggled.tooltip) { + this._tooltip = typeof toggled.tooltip === 'string' ? toggled.tooltip : toggled.tooltip.value; + } + } this._options = options || {}; @@ -338,15 +318,6 @@ export class MenuItemAction extends ExecuteCommandAction { return super.run(...runArgs); } - - serialize(): ISerializableMenuItemAction { - return { - id: this.id, - title: this.title, - category: this.category, - icon: this.icon - }; - } } export class SyncActionDescriptor { diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index 2bfa84d15eb..96987a4c911 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -94,7 +94,8 @@ class Menu implements IMenu { // keep toggled keys for event if applicable if (isIMenuItem(item) && item.command.toggled) { - Menu._fillInKbExprKeys(item.command.toggled, this._contextKeys); + const toggledExpression: ContextKeyExpression = (item.command.toggled as { condition: ContextKeyExpression }).condition || item.command.toggled; + Menu._fillInKbExprKeys(toggledExpression, this._contextKeys); } } this._onDidChange.fire(this); diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index 4e7cf3e5e22..27ef89f17e2 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -12,7 +12,7 @@ import { IOpenedWindow, OpenContext, IWindowOpenable, IOpenEmptyWindowOptions } import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { isMacintosh } from 'vs/base/common/platform'; import { IElectronService } from 'vs/platform/electron/node/electron'; -import { ISerializableMenuItemAction } from 'vs/platform/actions/common/actions'; +import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { AddFirstParameterToFunctions } from 'vs/base/common/types'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; @@ -279,7 +279,7 @@ export class ElectronMainService implements IElectronMainService { return true; } - async updateTouchBar(windowId: number | undefined, items: ISerializableMenuItemAction[][]): Promise { + async updateTouchBar(windowId: number | undefined, items: ISerializableCommandAction[][]): Promise { const window = this.windowById(windowId); if (window) { window.updateTouchBar(items); diff --git a/src/vs/platform/electron/node/electron.ts b/src/vs/platform/electron/node/electron.ts index 41c1275befd..8803fd16b39 100644 --- a/src/vs/platform/electron/node/electron.ts +++ b/src/vs/platform/electron/node/electron.ts @@ -8,7 +8,7 @@ import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDial import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWindowOpenable, IOpenEmptyWindowOptions, IOpenedWindow } from 'vs/platform/windows/common/windows'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; -import { ISerializableMenuItemAction } from 'vs/platform/actions/common/actions'; +import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { INativeOpenWindowOptions } from 'vs/platform/windows/node/window'; export const IElectronService = createDecorator('electronService'); @@ -60,7 +60,7 @@ export interface IElectronService { setRepresentedFilename(path: string): Promise; setDocumentEdited(edited: boolean): Promise; openExternal(url: string): Promise; - updateTouchBar(items: ISerializableMenuItemAction[][]): Promise; + updateTouchBar(items: ISerializableCommandAction[][]): Promise; // macOS Touchbar newWindowTab(): Promise; diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 22c0b239887..3e05c84fcd9 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -10,7 +10,7 @@ import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { ISerializableMenuItemAction } from 'vs/platform/actions/common/actions'; +import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { URI } from 'vs/base/common/uri'; import { Rectangle, BrowserWindow } from 'electron'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -82,7 +82,7 @@ export interface ICodeWindow extends IDisposable { handleTitleDoubleClick(): void; - updateTouchBar(items: ISerializableMenuItemAction[][]): void; + updateTouchBar(items: ISerializableCommandAction[][]): void; serializeWindowState(): IWindowState; } diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index ea14e0d998b..1bbf4ad8b0f 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -143,39 +143,26 @@ registerAction2(class extends Action2 { registerAction2(class extends Action2 { constructor() { super({ - id: `workbench.output.action.turnOffAutoScroll`, - title: nls.localize('outputScrollOff', "Turn Auto Scrolling Off"), + id: `workbench.output.action.toggleAutoScroll`, + title: { value: nls.localize('toggleAutoScroll', "Toggle Auto Scrolling"), original: 'Toggle Auto Scrolling' }, + tooltip: { value: nls.localize('outputScrollOff', "Turn Auto Scrolling Off"), original: 'Turn Auto Scrolling Off' }, menu: { id: MenuId.ViewTitle, - when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', OUTPUT_VIEW_ID), CONTEXT_OUTPUT_SCROLL_LOCK.negate()), + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', OUTPUT_VIEW_ID)), group: 'navigation', order: 3, }, - icon: { id: 'codicon/unlock' } + icon: { id: 'codicon/unlock' }, + toggled: { + condition: CONTEXT_OUTPUT_SCROLL_LOCK, + icon: { id: 'codicon/lock' }, + tooltip: { value: nls.localize('outputScrollOn', "Turn Auto Scrolling On"), original: 'Turn Auto Scrolling On' } + } }); } async run(accessor: ServicesAccessor): Promise { const outputView = accessor.get(IViewsService).getActiveViewWithId(OUTPUT_VIEW_ID)!; - outputView.scrollLock = true; - } -}); -registerAction2(class extends Action2 { - constructor() { - super({ - id: `workbench.output.action.turnOnAutoScroll`, - title: nls.localize('outputScrollOn', "Turn Auto Scrolling On"), - menu: { - id: MenuId.ViewTitle, - when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', OUTPUT_VIEW_ID), CONTEXT_OUTPUT_SCROLL_LOCK), - group: 'navigation', - order: 3, - }, - icon: { id: 'codicon/lock' }, - }); - } - async run(accessor: ServicesAccessor): Promise { - const outputView = accessor.get(IViewsService).getActiveViewWithId(OUTPUT_VIEW_ID)!; - outputView.scrollLock = false; + outputView.scrollLock = !outputView.scrollLock; } }); registerAction2(class extends Action2 { diff --git a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts index 19b4cba19a8..8754e487ff4 100644 --- a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts @@ -569,10 +569,10 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable { const entries: ActionCommandEntry[] = []; for (let action of actions) { - const title = typeof action.title === 'string' ? action.title : action.title.value; + const title = typeof action.item.title === 'string' ? action.item.title : action.item.title.value; let category, label = title; - if (action.category) { - category = typeof action.category === 'string' ? action.category : action.category.value; + if (action.item.category) { + category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value; label = localize('cat.title', "{0}: {1}", category, title); } @@ -580,8 +580,8 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable { const labelHighlights = wordFilter(searchValue, label); // Add an 'alias' in original language when running in different locale - const aliasTitle = (!Language.isDefaultVariant() && typeof action.title !== 'string') ? action.title.original : undefined; - const aliasCategory = (!Language.isDefaultVariant() && category && action.category && typeof action.category !== 'string') ? action.category.original : undefined; + const aliasTitle = (!Language.isDefaultVariant() && typeof action.item.title !== 'string') ? action.item.title.original : undefined; + const aliasCategory = (!Language.isDefaultVariant() && category && action.item.category && typeof action.item.category !== 'string') ? action.item.category.original : undefined; let alias; if (aliasTitle && category) { alias = aliasCategory ? `${aliasCategory}: ${aliasTitle}` : `${category}: ${aliasTitle}`; diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index d6ea00c8a27..66e33929be5 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -200,9 +200,9 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc } for (let action of actionGroup[1]) { if (action instanceof MenuItemAction) { - let label = typeof action.title === 'string' ? action.title : action.title.value; - if (action.category) { - const category = typeof action.category === 'string' ? action.category : action.category.value; + let label = typeof action.item.title === 'string' ? action.item.title : action.item.title.value; + if (action.item.category) { + const category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value; label = nls.localize('cat.title', "{0}: {1}", category, label); } items.push({ diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 094333c1055..d5e9340d412 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -23,7 +23,7 @@ import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; import { ipcRenderer as ipc, webFrame, crashReporter, CrashReporterStartOptions, Event as IpcEvent } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; -import { IMenuService, MenuId, IMenu, MenuItemAction, SubmenuItemAction, MenuRegistry, ISerializableMenuItemAction } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, IMenu, MenuItemAction, SubmenuItemAction, MenuRegistry, ICommandAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -68,7 +68,7 @@ export class NativeWindow extends Disposable { private touchBarMenu: IMenu | undefined; private readonly touchBarDisposables = this._register(new DisposableStore()); - private lastInstalledTouchedBar: ISerializableMenuItemAction[][] | undefined; + private lastInstalledTouchedBar: ICommandAction[][] | undefined; private readonly customTitleContextMenuDisposable = this._register(new DisposableStore()); @@ -504,8 +504,8 @@ export class NativeWindow extends Disposable { this.touchBarDisposables.add(createAndFillInActionBarActions(this.touchBarMenu, undefined, actions)); // Convert into command action multi array - const items: ISerializableMenuItemAction[][] = []; - let group: ISerializableMenuItemAction[] = []; + const items: ICommandAction[][] = []; + let group: ICommandAction[] = []; if (!disabled) { for (const action of actions) { @@ -515,7 +515,7 @@ export class NativeWindow extends Disposable { continue; // ignored } - group.push(action.serialize()); + group.push(action.item); } // Separator From 5a53b4db1ef772c4a84cbda87e23b06f50f5fcde Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 11 Mar 2020 12:36:23 +0100 Subject: [PATCH 043/169] minimize changes --- .../actions/browser/menuEntryActionViewItem.ts | 12 ++++++------ src/vs/platform/actions/common/actions.ts | 15 ++++++++------- .../contrib/quickopen/browser/commandsHandler.ts | 2 +- .../electron-browser/remote.contribution.ts | 2 +- src/vs/workbench/electron-browser/window.ts | 4 ++-- 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 41260c2dc6e..4e3e1dec7a2 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -12,7 +12,7 @@ import { IdGenerator } from 'vs/base/common/idGenerator'; import { IDisposable, toDisposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { localize } from 'vs/nls'; -import { IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction, Icon } from 'vs/platform/actions/common/actions'; +import { ICommandAction, IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction, Icon } from 'vs/platform/actions/common/actions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -171,7 +171,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { render(container: HTMLElement): void { super.render(container); - this._updateItemClass(this._action); + this._updateItemClass(this._action.item); let mouseOver = false; @@ -227,18 +227,18 @@ export class MenuEntryActionViewItem extends ActionViewItem { if (this.options.icon) { if (this._commandAction !== this._action) { if (this._action.alt) { - this._updateItemClass(this._action.alt); + this._updateItemClass(this._action.alt.item); } } else if ((this._action).alt) { - this._updateItemClass(this._action); + this._updateItemClass(this._action.item); } } } - _updateItemClass(item: MenuItemAction): void { + _updateItemClass(item: ICommandAction): void { this._itemClassDispose.value = undefined; - const icon = item.checked && (item.item.toggled as { icon?: Icon })?.icon ? (item.item.toggled as { icon: Icon }).icon : item.item.icon; + const icon = this._commandAction.checked && (item.toggled as { icon?: Icon })?.icon ? (item.toggled as { icon: Icon }).icon : item.icon; if (ThemeIcon.isThemeIcon(icon)) { // theme icons diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 0cf822b0156..ef6c4eb014e 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -277,14 +277,14 @@ export class MenuItemAction extends ExecuteCommandAction { @IContextKeyService contextKeyService: IContextKeyService, @ICommandService commandService: ICommandService ) { - super(item.id, typeof item.title === 'string' ? item.title : item.title.value, commandService); - this.item = item; - this._cssClass = undefined; - this._enabled = !this.item.precondition || contextKeyService.contextMatchesRules(this.item.precondition); - this._tooltip = this.item.tooltip ? typeof this.item.tooltip === 'string' ? this.item.tooltip : this.item.tooltip.value : undefined; + typeof item.title === 'string' ? super(item.id, item.title, commandService) : super(item.id, item.title.value, commandService); - if (this.item.toggled) { - const toggled = ((this.item.toggled as { condition: ContextKeyExpression }).condition ? this.item.toggled : { condition: this.item.toggled }) as { + this._cssClass = undefined; + this._enabled = !item.precondition || contextKeyService.contextMatchesRules(item.precondition); + this._tooltip = item.tooltip ? typeof item.tooltip === 'string' ? item.tooltip : item.tooltip.value : undefined; + + if (item.toggled) { + const toggled = ((item.toggled as { condition: ContextKeyExpression }).condition ? item.toggled : { condition: item.toggled }) as { condition: ContextKeyExpression, icon?: Icon, tooltip?: string | ILocalizedString }; this._checked = contextKeyService.contextMatchesRules(toggled.condition); @@ -295,6 +295,7 @@ export class MenuItemAction extends ExecuteCommandAction { this._options = options || {}; + this.item = item; this.alt = alt ? new MenuItemAction(alt, undefined, this._options, contextKeyService, commandService) : undefined; } diff --git a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts index 8754e487ff4..de09fec3d45 100644 --- a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts @@ -591,7 +591,7 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable { const aliasHighlights = alias ? wordFilter(searchValue, alias) : null; if (labelHighlights || aliasHighlights) { - entries.push(this.instantiationService.createInstance(ActionCommandEntry, action.id, this.keybindingService.lookupKeybinding(action.id), label, alias, { label: labelHighlights, alias: aliasHighlights }, action, (id: string) => this.onBeforeRunCommand(id))); + entries.push(this.instantiationService.createInstance(ActionCommandEntry, action.item.id, this.keybindingService.lookupKeybinding(action.id), label, alias, { label: labelHighlights, alias: aliasHighlights }, action, (id: string) => this.onBeforeRunCommand(id))); } } } diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index 66e33929be5..67df9f76198 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -207,7 +207,7 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc } items.push({ type: 'item', - id: action.id, + id: action.item.id, label }); } diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index d5e9340d412..6f7117b9068 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -23,7 +23,7 @@ import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; import { ipcRenderer as ipc, webFrame, crashReporter, CrashReporterStartOptions, Event as IpcEvent } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; -import { IMenuService, MenuId, IMenu, MenuItemAction, SubmenuItemAction, MenuRegistry, ICommandAction } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -511,7 +511,7 @@ export class NativeWindow extends Disposable { // Command if (action instanceof MenuItemAction) { - if (ignoredItems.indexOf(action.id) >= 0) { + if (ignoredItems.indexOf(action.item.id) >= 0) { continue; // ignored } From 305fbc16388bda35c09fec258865bbdbd25b7485 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 11 Mar 2020 12:36:44 +0100 Subject: [PATCH 044/169] editors - untitled with associated resource do not emit dirty change properly --- .../common/textFileEditorModelManager.ts | 34 +++++++++++++------ .../common/untitledTextEditorService.ts | 20 +++++++---- .../test/browser/untitledTextEditor.test.ts | 15 ++++++-- 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 6f5824b53ed..b6b5919fe40 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -280,16 +280,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE const newModel = model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : undefined, options ? options.mode : undefined); modelPromise = model.load(options); - // Install model listeners - const modelListeners = new DisposableStore(); - modelListeners.add(model.onDidLoad(reason => this._onDidLoad.fire({ model: newModel, reason }))); - modelListeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(newModel))); - modelListeners.add(model.onDidSaveError(() => this._onDidSaveError.fire(newModel))); - modelListeners.add(model.onDidSave(reason => this._onDidSave.fire({ model: newModel, reason }))); - modelListeners.add(model.onDidRevert(() => this._onDidRevert.fire(newModel))); - modelListeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(newModel))); - - this.mapResourceToModelListeners.set(resource, modelListeners); + this.registerModel(newModel); } // Store pending loads to avoid race conditions @@ -298,9 +289,15 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // Make known to manager (if not already known) this.add(resource, model); - // Signal as event if we created the model + // Emit some events if we created the model if (didCreateModel) { this._onDidCreate.fire(model); + + // If the model is dirty right from the beginning, + // make sure to emit this as an event + if (model.isDirty()) { + this._onDidChangeDirty.fire(model); + } } try { @@ -335,6 +332,21 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE } } + private registerModel(model: TextFileEditorModel): void { + + // Install model listeners + const modelListeners = new DisposableStore(); + modelListeners.add(model.onDidLoad(reason => this._onDidLoad.fire({ model, reason }))); + modelListeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(model))); + modelListeners.add(model.onDidSaveError(() => this._onDidSaveError.fire(model))); + modelListeners.add(model.onDidSave(reason => this._onDidSave.fire({ model: model, reason }))); + modelListeners.add(model.onDidRevert(() => this._onDidRevert.fire(model))); + modelListeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(model))); + + // Keep for disposal + this.mapResourceToModelListeners.set(model.resource, modelListeners); + } + add(resource: URI, model: TextFileEditorModel): void { const knownModel = this.mapResourceToModel.get(resource); if (knownModel === model) { diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts index 10c06b14c5e..5c91d4512b6 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts @@ -219,11 +219,13 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe } private registerModel(model: UntitledTextEditorModel): void { - const modelDisposables = new DisposableStore(); - modelDisposables.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(model))); - modelDisposables.add(model.onDidChangeName(() => this._onDidChangeLabel.fire(model))); - modelDisposables.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(model))); - modelDisposables.add(model.onDispose(() => this._onDidDispose.fire(model))); + + // Install model listeners + const modelListeners = new DisposableStore(); + modelListeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(model))); + modelListeners.add(model.onDidChangeName(() => this._onDidChangeLabel.fire(model))); + modelListeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(model))); + modelListeners.add(model.onDispose(() => this._onDidDispose.fire(model))); // Remove from cache on dispose Event.once(model.onDispose)(() => { @@ -232,11 +234,17 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe this.mapResourceToModel.delete(model.resource); // Listeners - modelDisposables.dispose(); + modelListeners.dispose(); }); // Add to cache this.mapResourceToModel.set(model.resource, model); + + // If the model is dirty right from the beginning, + // make sure to emit this as an event + if (model.isDirty()) { + this._onDidChangeDirty.fire(model); + } } } diff --git a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts index cf3ce106c2a..bf8c715cb34 100644 --- a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts +++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts @@ -14,6 +14,7 @@ import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRe import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; suite('Untitled text editors', () => { @@ -120,15 +121,23 @@ suite('Untitled text editors', () => { const service = accessor.untitledTextEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); - const untitled = instantiationService.createInstance(UntitledTextEditorInput, service.create({ associatedResource: file })); + let onDidChangeDirtyModel: IUntitledTextEditorModel | undefined = undefined; + const listener = service.onDidChangeDirty(model => { + onDidChangeDirtyModel = model; + }); + + const model = service.create({ associatedResource: file }); + const untitled = instantiationService.createInstance(UntitledTextEditorInput, model); assert.ok(untitled.isDirty()); + assert.equal(model, onDidChangeDirtyModel); - const model = await untitled.resolve(); + const resolvedModel = await untitled.resolve(); - assert.ok(model.hasAssociatedFilePath); + assert.ok(resolvedModel.hasAssociatedFilePath); assert.equal(untitled.isDirty(), true); untitled.dispose(); + listener.dispose(); }); test('no longer dirty when content gets empty (not with associated resource)', async () => { From 35dfc70df8c485329fbfa298ddf30426b3af36e6 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 11 Mar 2020 12:40:46 +0100 Subject: [PATCH 045/169] Fixes #90915: Try to correct browser rounding errors --- src/vs/editor/browser/viewParts/lines/viewLine.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index 9a7011dc1d4..46d36912654 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -616,7 +616,15 @@ class RenderedViewLine implements IRenderedViewLine { if (!r || r.length === 0) { return -1; } - return r[0].left; + const result = r[0].left; + if (this.input.isBasicASCII) { + const charOffset = this._characterMapping.getAbsoluteOffsets(); + const expectedResult = Math.round(this.input.spaceWidth * charOffset[column - 1]); + if (Math.abs(expectedResult - result) <= 1) { + return expectedResult; + } + } + return result; } private _readRawVisibleRangesForRange(domNode: FastDomNode, startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { From 3511b288ceed19437c2d4bb016668763fb44fc53 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 11 Mar 2020 12:40:57 +0100 Subject: [PATCH 046/169] minimise changes --- src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts index de09fec3d45..a41fb8930e9 100644 --- a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts @@ -591,7 +591,7 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable { const aliasHighlights = alias ? wordFilter(searchValue, alias) : null; if (labelHighlights || aliasHighlights) { - entries.push(this.instantiationService.createInstance(ActionCommandEntry, action.item.id, this.keybindingService.lookupKeybinding(action.id), label, alias, { label: labelHighlights, alias: aliasHighlights }, action, (id: string) => this.onBeforeRunCommand(id))); + entries.push(this.instantiationService.createInstance(ActionCommandEntry, action.id, this.keybindingService.lookupKeybinding(action.item.id), label, alias, { label: labelHighlights, alias: aliasHighlights }, action, (id: string) => this.onBeforeRunCommand(id))); } } } From d2d9498c730482bfbabbf466c428b5ebc1be6855 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 11 Mar 2020 13:06:48 +0100 Subject: [PATCH 047/169] debugSession: more precise errors --- .../contrib/debug/browser/debugSession.ts | 70 +++++++++---------- .../contrib/debug/browser/rawDebugSession.ts | 2 +- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 63833c44c8b..fae348805d3 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -5,7 +5,6 @@ import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; -import * as nls from 'vs/nls'; import * as platform from 'vs/base/common/platform'; import severity from 'vs/base/common/severity'; import { Event, Emitter } from 'vs/base/common/event'; @@ -34,6 +33,7 @@ import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cance import { distinct } from 'vs/base/common/arrays'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { localize } from 'vs/nls'; export class DebugSession implements IDebugSession { @@ -232,7 +232,7 @@ export class DebugSession implements IDebugSession { */ async launchOrAttach(config: IConfig): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'launch or attach')); } // __sessionID only used for EH debugging (but we add it always for now...) @@ -250,7 +250,7 @@ export class DebugSession implements IDebugSession { */ async terminate(restart = false): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'terminate')); } this.cancelAllRequests(); @@ -266,7 +266,7 @@ export class DebugSession implements IDebugSession { */ async disconnect(restart = false): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'disconnect')); } this.cancelAllRequests(); @@ -278,7 +278,7 @@ export class DebugSession implements IDebugSession { */ async restart(): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'restart')); } this.cancelAllRequests(); @@ -287,7 +287,7 @@ export class DebugSession implements IDebugSession { async sendBreakpoints(modelUri: URI, breakpointsToSend: IBreakpoint[], sourceModified: boolean): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'breakpoints')); } if (!this.raw.readyForBreakpoints) { @@ -321,7 +321,7 @@ export class DebugSession implements IDebugSession { async sendFunctionBreakpoints(fbpts: IFunctionBreakpoint[]): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'function breakpoints')); } if (this.raw.readyForBreakpoints) { @@ -338,7 +338,7 @@ export class DebugSession implements IDebugSession { async sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'exception breakpoints')); } if (this.raw.readyForBreakpoints) { @@ -348,10 +348,10 @@ export class DebugSession implements IDebugSession { async dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null, description: string, canPersist?: boolean }> { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'data breakpoints info')); } if (!this.raw.readyForBreakpoints) { - throw new Error(nls.localize('sessionNotReadyForBreakpoints', "Session is not ready for breakpoints")); + throw new Error(localize('sessionNotReadyForBreakpoints', "Session is not ready for breakpoints")); } const response = await this.raw.dataBreakpointInfo({ name, variablesReference }); @@ -360,7 +360,7 @@ export class DebugSession implements IDebugSession { async sendDataBreakpoints(dataBreakpoints: IDataBreakpoint[]): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'data breakpoints')); } if (this.raw.readyForBreakpoints) { @@ -377,7 +377,7 @@ export class DebugSession implements IDebugSession { async breakpointsLocations(uri: URI, lineNumber: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'breakpoints locations')); } const source = this.getRawSource(uri); @@ -393,7 +393,7 @@ export class DebugSession implements IDebugSession { customRequest(request: string, args: any): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", request)); } return this.raw.custom(request, args); @@ -401,7 +401,7 @@ export class DebugSession implements IDebugSession { stackTrace(threadId: number, startFrame: number, levels: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'stackTrace')); } const token = this.getNewCancellationToken(threadId); @@ -410,7 +410,7 @@ export class DebugSession implements IDebugSession { async exceptionInfo(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'exceptionInfo')); } const response = await this.raw.exceptionInfo({ threadId }); @@ -428,7 +428,7 @@ export class DebugSession implements IDebugSession { scopes(frameId: number, threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'scopes')); } const token = this.getNewCancellationToken(threadId); @@ -437,7 +437,7 @@ export class DebugSession implements IDebugSession { variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'variables')); } const token = threadId ? this.getNewCancellationToken(threadId) : undefined; @@ -446,7 +446,7 @@ export class DebugSession implements IDebugSession { evaluate(expression: string, frameId: number, context?: string): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'evaluate')); } return this.raw.evaluate({ expression, frameId, context }); @@ -454,7 +454,7 @@ export class DebugSession implements IDebugSession { async restartFrame(frameId: number, threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'restartFrame')); } await this.raw.restartFrame({ frameId }, threadId); @@ -462,7 +462,7 @@ export class DebugSession implements IDebugSession { async next(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'next')); } await this.raw.next({ threadId }); @@ -470,7 +470,7 @@ export class DebugSession implements IDebugSession { async stepIn(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'stepIn')); } await this.raw.stepIn({ threadId }); @@ -478,7 +478,7 @@ export class DebugSession implements IDebugSession { async stepOut(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'stepOut')); } await this.raw.stepOut({ threadId }); @@ -486,7 +486,7 @@ export class DebugSession implements IDebugSession { async stepBack(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'stepBack')); } await this.raw.stepBack({ threadId }); @@ -494,7 +494,7 @@ export class DebugSession implements IDebugSession { async continue(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'continue')); } await this.raw.continue({ threadId }); @@ -502,7 +502,7 @@ export class DebugSession implements IDebugSession { async reverseContinue(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'reverse continue')); } await this.raw.reverseContinue({ threadId }); @@ -510,7 +510,7 @@ export class DebugSession implements IDebugSession { async pause(threadId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'pause')); } await this.raw.pause({ threadId }); @@ -518,7 +518,7 @@ export class DebugSession implements IDebugSession { async terminateThreads(threadIds?: number[]): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'terminateThreads')); } await this.raw.terminateThreads({ threadIds }); @@ -526,7 +526,7 @@ export class DebugSession implements IDebugSession { setVariable(variablesReference: number, name: string, value: string): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'setVariable')); } return this.raw.setVariable({ variablesReference, name, value }); @@ -534,7 +534,7 @@ export class DebugSession implements IDebugSession { gotoTargets(source: DebugProtocol.Source, line: number, column?: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'gotoTargets')); } return this.raw.gotoTargets({ source, line, column }); @@ -542,7 +542,7 @@ export class DebugSession implements IDebugSession { goto(threadId: number, targetId: number): Promise { if (!this.raw) { - throw new Error('no debug adapter'); + throw new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'goto')); } return this.raw.goto({ threadId, targetId }); @@ -550,7 +550,7 @@ export class DebugSession implements IDebugSession { loadSource(resource: URI): Promise { if (!this.raw) { - return Promise.reject(new Error('no debug adapter')); + return Promise.reject(new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'loadSource'))); } const source = this.getSourceForUri(resource); @@ -568,7 +568,7 @@ export class DebugSession implements IDebugSession { async getLoadedSources(): Promise { if (!this.raw) { - return Promise.reject(new Error('no debug adapter')); + return Promise.reject(new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'getLoadedSources'))); } const response = await this.raw.loadedSources({}); @@ -581,7 +581,7 @@ export class DebugSession implements IDebugSession { async completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise { if (!this.raw) { - return Promise.reject(new Error('no debug adapter')); + return Promise.reject(new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'completions'))); } return this.raw.completions({ @@ -700,7 +700,7 @@ export class DebugSession implements IDebugSession { } this.rawListeners.push(this.raw.onDidInitialize(async () => { - aria.status(nls.localize('debuggingStarted', "Debugging started.")); + aria.status(localize('debuggingStarted', "Debugging started.")); const sendConfigurationDone = async () => { if (this.raw && this.raw.capabilities.supportsConfigurationDoneRequest) { try { @@ -782,7 +782,7 @@ export class DebugSession implements IDebugSession { })); this.rawListeners.push(this.raw.onDidTerminateDebugee(async event => { - aria.status(nls.localize('debuggingStopped', "Debugging stopped.")); + aria.status(localize('debuggingStopped', "Debugging stopped.")); if (event.body && event.body.restart) { await this.debugService.restartSession(this, event.body.restart); } else if (this.raw) { diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 38e4f35cd63..6411c081038 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -230,7 +230,7 @@ export class RawDebugSession implements IDisposable { */ async start(): Promise { if (!this.debugAdapter) { - return Promise.reject(new Error('no debug adapter')); + return Promise.reject(new Error(nls.localize('noDebugAdapterStart', "No debug adapter, can not start debug session."))); } await this.debugAdapter.startSession(); From 0da9f3f250558ec00ce142f7031511bae8c31068 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 11 Mar 2020 15:28:10 +0100 Subject: [PATCH 048/169] [json] Trying to set local JSON Schema file, generates empty files while typing the filename. Fixes #92300 --- extensions/json-language-features/client/src/jsonMain.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extensions/json-language-features/client/src/jsonMain.ts b/extensions/json-language-features/client/src/jsonMain.ts index 5d41458931c..a3117e8fa5d 100644 --- a/extensions/json-language-features/client/src/jsonMain.ts +++ b/extensions/json-language-features/client/src/jsonMain.ts @@ -188,6 +188,9 @@ export function activate(context: ExtensionContext) { // handle content request client.onRequest(VSCodeContentRequest.type, (uriPath: string) => { const uri = Uri.parse(uriPath); + if (uri.scheme === 'untitled') { + return Promise.reject(new Error(localize('untitled.schema', 'Unable to load {0}', uri.toString()))); + } if (uri.scheme !== 'http' && uri.scheme !== 'https') { return workspace.openTextDocument(uri).then(doc => { schemaDocuments[uri.toString()] = true; From 14724591f9a9467a69b1b7b2e73aeddd66b45609 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 11 Mar 2020 15:33:33 +0100 Subject: [PATCH 049/169] classify constant properties (for #92469) --- src/vs/platform/theme/common/tokenClassificationRegistry.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index 2b3bebbb553..8911630ed91 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -433,6 +433,7 @@ function registerDefaultClassifications(): void { registerTokenStyleDefault('variable.readonly', [['variable.other.constant']]); + registerTokenStyleDefault('property.readonly', [['variable.other.constant.property']]); } export function getTokenClassificationRegistry(): ITokenClassificationRegistry { From 18ffc2c404a37ba82cf76ad5a6d7d96c132bb7cd Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 11 Mar 2020 17:17:35 +0100 Subject: [PATCH 050/169] Introduce SyncResource - Remove resourceKey and SyncSource and merge with SyncResource --- .../common/abstractSynchronizer.ts | 58 +++++----- .../userDataSync/common/extensionsSync.ts | 58 +++++----- .../userDataSync/common/globalStateSync.ts | 38 +++---- .../userDataSync/common/keybindingsSync.ts | 44 ++++---- .../userDataSync/common/settingsSync.ts | 42 ++++---- .../userDataSync/common/userDataSync.ts | 100 +++++++----------- .../common/userDataSyncBackupStoreService.ts | 20 ++-- .../common/userDataSyncEnablementService.ts | 20 ++-- .../common/userDataSyncService.ts | 46 ++++---- .../common/userDataSyncStoreService.ts | 36 +++---- .../test/common/settingsSync.test.ts | 20 ++-- .../test/common/synchronizer.test.ts | 32 +++--- .../test/common/userDataSyncClient.ts | 14 +-- .../test/common/userDataSyncService.test.ts | 4 +- .../userDataSync/browser/userDataSync.ts | 100 +++++++++--------- .../userDataSync/browser/userDataSyncView.ts | 20 ++-- .../electron-browser/settingsSyncService.ts | 4 +- .../userDataSyncBackupStoreService.ts | 8 +- .../electron-browser/userDataSyncService.ts | 26 ++--- .../userDataSyncStoreService.ts | 12 +-- 20 files changed, 339 insertions(+), 363 deletions(-) diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 8f79415fe07..2b6180c9ab2 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -7,7 +7,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IFileService, IFileContent, FileChangesEvent, FileSystemProviderError, FileSystemProviderErrorCode, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; import { VSBuffer } from 'vs/base/common/buffer'; import { URI } from 'vs/base/common/uri'; -import { SyncSource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, ResourceKey, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { joinPath, dirname } from 'vs/base/common/resources'; import { CancelablePromise } from 'vs/base/common/async'; @@ -19,6 +19,7 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { isString } from 'vs/base/common/types'; +import { uppercaseFirstLetter } from 'vs/base/common/strings'; type SyncSourceClassification = { source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -54,10 +55,10 @@ export abstract class AbstractSynchroniser extends Disposable { readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; protected readonly lastSyncResource: URI; + protected readonly syncResourceLogLabel: string; constructor( - readonly source: SyncSource, - readonly resourceKey: ResourceKey, + readonly resource: SyncResource, @IFileService protected readonly fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncStoreService protected readonly userDataSyncStoreService: IUserDataSyncStoreService, @@ -68,8 +69,9 @@ export abstract class AbstractSynchroniser extends Disposable { @IConfigurationService protected readonly configurationService: IConfigurationService, ) { super(); - this.syncFolder = joinPath(environmentService.userDataSyncHome, source); - this.lastSyncResource = joinPath(this.syncFolder, `lastSync${this.resourceKey}.json`); + this.syncResourceLogLabel = uppercaseFirstLetter(this.resource); + this.syncFolder = joinPath(environmentService.userDataSyncHome, resource); + this.lastSyncResource = joinPath(this.syncFolder, `lastSync${this.resource}.json`); } protected setStatus(status: SyncStatus): void { @@ -79,32 +81,32 @@ export abstract class AbstractSynchroniser extends Disposable { this._onDidChangStatus.fire(status); if (status === SyncStatus.HasConflicts) { // Log to telemetry when there is a sync conflict - this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsDetected', { source: this.source }); + this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsDetected', { source: this.resource }); } if (oldStatus === SyncStatus.HasConflicts && status === SyncStatus.Idle) { // Log to telemetry when conflicts are resolved - this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsResolved', { source: this.source }); + this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsResolved', { source: this.resource }); } } } - protected isEnabled(): boolean { return this.userDataSyncEnablementService.isResourceEnabled(this.resourceKey); } + protected isEnabled(): boolean { return this.userDataSyncEnablementService.isResourceEnabled(this.resource); } async sync(ref?: string): Promise { if (!this.isEnabled()) { - this.logService.info(`${this.source}: Skipped synchronizing ${this.source.toLowerCase()} as it is disabled.`); + this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as it is disabled.`); return; } if (this.status === SyncStatus.HasConflicts) { - this.logService.info(`${this.source}: Skipped synchronizing ${this.source.toLowerCase()} as there are conflicts.`); + this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as there are conflicts.`); return; } if (this.status === SyncStatus.Syncing) { - this.logService.info(`${this.source}: Skipped synchronizing ${this.source.toLowerCase()} as it is running already.`); + this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as it is running already.`); return; } - this.logService.trace(`${this.source}: Started synchronizing ${this.source.toLowerCase()}...`); + this.logService.trace(`${this.syncResourceLogLabel}: Started synchronizing ${this.resource.toLowerCase()}...`); this.setStatus(SyncStatus.Syncing); const lastSyncUserData = await this.getLastSyncUserData(); @@ -114,9 +116,9 @@ export abstract class AbstractSynchroniser extends Disposable { try { status = await this.doSync(remoteUserData, lastSyncUserData); if (status === SyncStatus.HasConflicts) { - this.logService.info(`${this.source}: Detected conflicts while synchronizing ${this.source.toLowerCase()}.`); + this.logService.info(`${this.syncResourceLogLabel}: Detected conflicts while synchronizing ${this.resource.toLowerCase()}.`); } else if (status === SyncStatus.Idle) { - this.logService.trace(`${this.source}: Finished synchronizing ${this.source.toLowerCase()}.`); + this.logService.trace(`${this.syncResourceLogLabel}: Finished synchronizing ${this.resource.toLowerCase()}.`); } } finally { this.setStatus(status); @@ -126,8 +128,8 @@ export abstract class AbstractSynchroniser extends Disposable { protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) { // current version is not compatible with cloud version - this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/incompatible', { source: this.source }); - throw new UserDataSyncError(localize('incompatible', "Cannot sync {0} as its version {1} is not compatible with cloud {2}", this.source, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.Incompatible, this.source); + this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/incompatible', { source: this.resource }); + throw new UserDataSyncError(localize('incompatible', "Cannot sync {0} as its version {1} is not compatible with cloud {2}", this.resource, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.Incompatible, this.resource); } try { const status = await this.performSync(remoteUserData, lastSyncUserData); @@ -137,7 +139,7 @@ export abstract class AbstractSynchroniser extends Disposable { switch (e.code) { case UserDataSyncErrorCode.RemotePreconditionFailed: // Rejected as there is a new remote version. Syncing again, - this.logService.info(`${this.source}: Failed to synchronize as there is a new remote version available. Synchronizing again...`); + this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize as there is a new remote version available. Synchronizing again...`); // Avoid cache and get latest remote user data - https://github.com/microsoft/vscode/issues/90624 remoteUserData = await this.getRemoteUserData(null); return this.doSync(remoteUserData, lastSyncUserData); @@ -163,7 +165,7 @@ export abstract class AbstractSynchroniser extends Disposable { } async getLocalBackupContent(ref?: string): Promise { - return this.userDataSyncBackupStoreService.resolveContent(this.resourceKey, ref); + return this.userDataSyncBackupStoreService.resolveContent(this.resource, ref); } async resetLocal(): Promise { @@ -225,23 +227,23 @@ export abstract class AbstractSynchroniser extends Disposable { private async getUserData(refOrLastSyncData: string | IRemoteUserData | null): Promise { if (isString(refOrLastSyncData)) { - const content = await this.userDataSyncStoreService.resolveContent(this.resourceKey, refOrLastSyncData); + const content = await this.userDataSyncStoreService.resolveContent(this.resource, refOrLastSyncData); return { ref: refOrLastSyncData, content }; } else { const lastSyncUserData: IUserData | null = refOrLastSyncData ? { ref: refOrLastSyncData.ref, content: refOrLastSyncData.syncData ? JSON.stringify(refOrLastSyncData.syncData) : null } : null; - return this.userDataSyncStoreService.read(this.resourceKey, lastSyncUserData, this.source); + return this.userDataSyncStoreService.read(this.resource, lastSyncUserData); } } protected async updateRemoteUserData(content: string, ref: string | null): Promise { const syncData: ISyncData = { version: this.version, content }; - ref = await this.userDataSyncStoreService.write(this.resourceKey, JSON.stringify(syncData), ref, this.source); + ref = await this.userDataSyncStoreService.write(this.resource, JSON.stringify(syncData), ref); return { ref, syncData }; } protected async backupLocal(content: string): Promise { const syncData: ISyncData = { version: this.version, content }; - return this.userDataSyncBackupStoreService.backup(this.resourceKey, JSON.stringify(syncData)); + return this.userDataSyncBackupStoreService.backup(this.resource, JSON.stringify(syncData)); } protected abstract readonly version: number; @@ -264,8 +266,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { constructor( protected readonly file: URI, - source: SyncSource, - resourceKey: ResourceKey, + resource: SyncResource, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @@ -275,14 +276,14 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService configurationService: IConfigurationService, ) { - super(source, resourceKey, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(resource, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register(this.fileService.watch(dirname(file))); this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e))); } async stop(): Promise { this.cancel(); - this.logService.trace(`${this.source}: Stopped synchronizing ${this.source.toLowerCase()}.`); + this.logService.trace(`${this.syncResourceLogLabel}: Stopped synchronizing ${this.resource.toLowerCase()}.`); try { await this.fileService.del(this.conflictsPreviewResource); } catch (e) { /* ignore */ } @@ -362,8 +363,7 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni constructor( file: URI, - source: SyncSource, - resourceKey: ResourceKey, + resource: SyncResource, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @@ -374,7 +374,7 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni @IUserDataSyncUtilService protected readonly userDataSyncUtilService: IUserDataSyncUtilService, @IConfigurationService configurationService: IConfigurationService, ) { - super(file, source, resourceKey, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(file, resource, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); } protected hasErrors(content: string): boolean { diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index cdc3d6faab5..ad91ac82436 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -50,7 +50,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, ) { - super(SyncSource.Extensions, 'extensions', fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(SyncResource.Extensions, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register( Event.debounce( Event.any( @@ -62,14 +62,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse async pull(): Promise { if (!this.isEnabled()) { - this.logService.info('Extensions: Skipped pulling extensions as it is disabled.'); + this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling extensions as it is disabled.`); return; } this.stop(); try { - this.logService.info('Extensions: Started pulling extensions...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pulling extensions...`); this.setStatus(SyncStatus.Syncing); const lastSyncUserData = await this.getLastSyncUserData(); @@ -84,10 +84,10 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse // No remote exists to pull else { - this.logService.info('Extensions: Remote extensions does not exist.'); + this.logService.info(`${this.syncResourceLogLabel}: Remote extensions does not exist.`); } - this.logService.info('Extensions: Finished pulling extensions.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pulling extensions.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -95,14 +95,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse async push(): Promise { if (!this.isEnabled()) { - this.logService.info('Extensions: Skipped pushing extensions as it is disabled.'); + this.logService.info(`${this.syncResourceLogLabel}: Skipped pushing extensions as it is disabled.`); return; } this.stop(); try { - this.logService.info('Extensions: Started pushing extensions...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pushing extensions...`); this.setStatus(SyncStatus.Syncing); const localExtensions = await this.getLocalExtensions(); @@ -111,7 +111,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const remoteUserData = await this.getRemoteUserData(lastSyncUserData); await this.apply({ added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData }, true); - this.logService.info('Extensions: Finished pushing extensions.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pushing extensions.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -148,7 +148,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } accept(content: string): Promise { - throw new Error('Extensions: Conflicts should not occur'); + throw new Error(`${this.syncResourceLogLabel}: Conflicts should not occur`); } async hasLocalData(): Promise { @@ -177,9 +177,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const localExtensions = await this.getLocalExtensions(); if (remoteExtensions) { - this.logService.trace('Extensions: Merging remote extensions with local extensions...'); + this.logService.trace(`${this.syncResourceLogLabel}: Merging remote extensions with local extensions...`); } else { - this.logService.trace('Extensions: Remote extensions does not exist. Synchronizing extensions for the first time.'); + this.logService.trace(`${this.syncResourceLogLabel}: Remote extensions does not exist. Synchronizing extensions for the first time.`); } const { added, removed, updated, remote } = merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions, this.getIgnoredExtensions()); @@ -196,7 +196,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const hasChanges = added.length || removed.length || updated.length || remote; if (!hasChanges) { - this.logService.info('Extensions: No changes found during synchronizing extensions.'); + this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing extensions.`); } if (added.length || removed.length || updated.length) { @@ -208,17 +208,17 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse if (remote) { // update remote - this.logService.trace('Extensions: Updating remote extensions...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating remote extensions...`); const content = JSON.stringify(remote); remoteUserData = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); - this.logService.info('Extensions: Updated remote extensions'); + this.logService.info(`${this.syncResourceLogLabel}: Updated remote extensions`); } if (lastSyncUserData?.ref !== remoteUserData.ref) { // update last sync - this.logService.trace('Extensions: Updating last synchronized extensions...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized extensions...`); await this.updateLastSyncUserData(remoteUserData, { skippedExtensions }); - this.logService.info('Extensions: Updated last synchronized extensions'); + this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized extensions`); } } @@ -230,9 +230,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User); const extensionsToRemove = installedExtensions.filter(({ identifier }) => removed.some(r => areSameExtensions(identifier, r))); await Promise.all(extensionsToRemove.map(async extensionToRemove => { - this.logService.trace('Extensions: Uninstalling local extension...', extensionToRemove.identifier.id); + this.logService.trace(`${this.syncResourceLogLabel}: Uninstalling local extension...', extensionToRemove.identifier.i`); await this.extensionManagementService.uninstall(extensionToRemove); - this.logService.info('Extensions: Uninstalled local extension.', extensionToRemove.identifier.id); + this.logService.info(`${this.syncResourceLogLabel}: Uninstalled local extension.', extensionToRemove.identifier.i`); removeFromSkipped.push(extensionToRemove.identifier); })); } @@ -245,13 +245,13 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse // Builtin Extension: Sync only enablement state if (installedExtension && installedExtension.type === ExtensionType.System) { if (e.disabled) { - this.logService.trace('Extensions: Disabling extension...', e.identifier.id); + this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...', e.identifier.i`); await this.extensionEnablementService.disableExtension(e.identifier); - this.logService.info('Extensions: Disabled extension', e.identifier.id); + this.logService.info(`${this.syncResourceLogLabel}: Disabled extension', e.identifier.i`); } else { - this.logService.trace('Extensions: Enabling extension...', e.identifier.id); + this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...', e.identifier.i`); await this.extensionEnablementService.enableExtension(e.identifier); - this.logService.info('Extensions: Enabled extension', e.identifier.id); + this.logService.info(`${this.syncResourceLogLabel}: Enabled extension', e.identifier.i`); } removeFromSkipped.push(e.identifier); return; @@ -261,19 +261,19 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse if (extension) { try { if (e.disabled) { - this.logService.trace('Extensions: Disabling extension...', e.identifier.id, extension.version); + this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...', e.identifier.id, extension.versio`); await this.extensionEnablementService.disableExtension(extension.identifier); - this.logService.info('Extensions: Disabled extension', e.identifier.id, extension.version); + this.logService.info(`${this.syncResourceLogLabel}: Disabled extension', e.identifier.id, extension.versio`); } else { - this.logService.trace('Extensions: Enabling extension...', e.identifier.id, extension.version); + this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...', e.identifier.id, extension.versio`); await this.extensionEnablementService.enableExtension(extension.identifier); - this.logService.info('Extensions: Enabled extension', e.identifier.id, extension.version); + this.logService.info(`${this.syncResourceLogLabel}: Enabled extension', e.identifier.id, extension.versio`); } // Install only if the extension does not exist if (!installedExtension || installedExtension.manifest.version !== extension.version) { - this.logService.trace('Extensions: Installing extension...', e.identifier.id, extension.version); + this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...', e.identifier.id, extension.versio`); await this.extensionManagementService.installFromGallery(extension); - this.logService.info('Extensions: Installed extension.', e.identifier.id, extension.version); + this.logService.info(`${this.syncResourceLogLabel}: Installed extension.', e.identifier.id, extension.versio`); removeFromSkipped.push(extension.identifier); } } catch (error) { diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index a095a09b482..0be9b802764 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -41,21 +41,21 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs @ITelemetryService telemetryService: ITelemetryService, @IConfigurationService configurationService: IConfigurationService, ) { - super(SyncSource.GlobalState, 'globalState', fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(SyncResource.GlobalState, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register(this.fileService.watch(dirname(this.environmentService.argvResource))); this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire())); } async pull(): Promise { if (!this.isEnabled()) { - this.logService.info('UI State: Skipped pulling ui state as it is disabled.'); + this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling ui state as it is disabled.`); return; } this.stop(); try { - this.logService.info('UI State: Started pulling ui state...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pulling ui state...`); this.setStatus(SyncStatus.Syncing); const lastSyncUserData = await this.getLastSyncUserData(); @@ -69,10 +69,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs // No remote exists to pull else { - this.logService.info('UI State: Remote UI state does not exist.'); + this.logService.info(`${this.syncResourceLogLabel}: Remote UI state does not exist.`); } - this.logService.info('UI State: Finished pulling UI state.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pulling UI state.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -80,14 +80,14 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs async push(): Promise { if (!this.isEnabled()) { - this.logService.info('UI State: Skipped pushing UI State as it is disabled.'); + this.logService.info(`${this.syncResourceLogLabel}: Skipped pushing UI State as it is disabled.`); return; } this.stop(); try { - this.logService.info('UI State: Started pushing UI State...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pushing UI State...`); this.setStatus(SyncStatus.Syncing); const localUserData = await this.getLocalGlobalState(); @@ -95,7 +95,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs const remoteUserData = await this.getRemoteUserData(lastSyncUserData); await this.apply({ local: undefined, remote: localUserData, remoteUserData, localUserData, lastSyncUserData }, true); - this.logService.info('UI State: Finished pushing UI State.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pushing UI State.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -132,7 +132,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } accept(content: string): Promise { - throw new Error('UI State: Conflicts should not occur'); + throw new Error(`${this.syncResourceLogLabel}: Conflicts should not occur`); } async hasLocalData(): Promise { @@ -160,9 +160,9 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs const localGloablState = await this.getLocalGlobalState(); if (remoteGlobalState) { - this.logService.trace('UI State: Merging remote ui state with local ui state...'); + this.logService.trace(`${this.syncResourceLogLabel}: Merging remote ui state with local ui state...`); } else { - this.logService.trace('UI State: Remote ui state does not exist. Synchronizing ui state for the first time.'); + this.logService.trace(`${this.syncResourceLogLabel}: Remote ui state does not exist. Synchronizing ui state for the first time.`); } const { local, remote } = merge(localGloablState, remoteGlobalState, lastSyncGlobalState); @@ -175,30 +175,30 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs const hasChanges = local || remote; if (!hasChanges) { - this.logService.info('UI State: No changes found during synchronizing ui state.'); + this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing ui state.`); } if (local) { // update local - this.logService.trace('UI State: Updating local ui state...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating local ui state...`); await this.backupLocal(JSON.stringify(localUserData)); await this.writeLocalGlobalState(local); - this.logService.info('UI State: Updated local ui state'); + this.logService.info(`${this.syncResourceLogLabel}: Updated local ui state`); } if (remote) { // update remote - this.logService.trace('UI State: Updating remote ui state...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating remote ui state...`); const content = JSON.stringify(remote); remoteUserData = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); - this.logService.info('UI State: Updated remote ui state'); + this.logService.info(`${this.syncResourceLogLabel}: Updated remote ui state`); } if (lastSyncUserData?.ref !== remoteUserData.ref) { // update last sync - this.logService.trace('UI State: Updating last synchronized ui state...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized ui state...`); await this.updateLastSyncUserData(remoteUserData); - this.logService.info('UI State: Updated last synchronized ui state'); + this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized ui state`); } } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index c3f02bac0da..c5248fe5a8b 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse } from 'vs/base/common/json'; @@ -43,19 +43,19 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @ITelemetryService telemetryService: ITelemetryService, ) { - super(environmentService.keybindingsResource, SyncSource.Keybindings, 'keybindings', fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); + super(environmentService.keybindingsResource, SyncResource.Keybindings, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); } async pull(): Promise { if (!this.isEnabled()) { - this.logService.info('Keybindings: Skipped pulling keybindings as it is disabled.'); + this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling keybindings as it is disabled.`); return; } this.stop(); try { - this.logService.info('Keybindings: Started pulling keybindings...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pulling keybindings...`); this.setStatus(SyncStatus.Syncing); const lastSyncUserData = await this.getLastSyncUserData(); @@ -78,10 +78,10 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem // No remote exists to pull else { - this.logService.info('Keybindings: Remote keybindings does not exist.'); + this.logService.info(`${this.syncResourceLogLabel}: Remote keybindings does not exist.`); } - this.logService.info('Keybindings: Finished pulling keybindings.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pulling keybindings.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -90,14 +90,14 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem async push(): Promise { if (!this.isEnabled()) { - this.logService.info('Keybindings: Skipped pushing keybindings as it is disabled.'); + this.logService.info(`${this.syncResourceLogLabel}: Skipped pushing keybindings as it is disabled.`); return; } this.stop(); try { - this.logService.info('Keybindings: Started pushing keybindings...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pushing keybindings...`); this.setStatus(SyncStatus.Syncing); const fileContent = await this.getLocalFileContent(); @@ -119,10 +119,10 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem // No local exists to push else { - this.logService.info('Keybindings: Local keybindings does not exist.'); + this.logService.info(`${this.syncResourceLogLabel}: Local keybindings does not exist.`); } - this.logService.info('Keybindings: Finished pushing keybindings.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pushing keybindings.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -202,7 +202,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem switch (e.code) { case UserDataSyncErrorCode.LocalPreconditionFailed: // Rejected as there is a new local version. Syncing again. - this.logService.info('Keybindings: Failed to synchronize keybindings as there is a new local version available. Synchronizing again...'); + this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize keybindings as there is a new local version available. Synchronizing again...`); return this.performSync(remoteUserData, lastSyncUserData); } } @@ -219,21 +219,21 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem if (content !== null) { if (this.hasErrors(content)) { - throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings as there are errors/warning in keybindings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); + throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings as there are errors/warning in keybindings file."), UserDataSyncErrorCode.LocalInvalidContent, this.resource); } if (hasLocalChanged) { - this.logService.trace('Keybindings: Updating local keybindings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating local keybindings...`); await this.backupLocal(this.toSyncContent(content, null)); await this.updateLocalFileContent(content, fileContent); - this.logService.info('Keybindings: Updated local keybindings'); + this.logService.info(`${this.syncResourceLogLabel}: Updated local keybindings`); } if (hasRemoteChanged) { - this.logService.trace('Keybindings: Updating remote keybindings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating remote keybindings...`); const remoteContents = this.toSyncContent(content, remoteUserData.syncData ? remoteUserData.syncData.content : null); remoteUserData = await this.updateRemoteUserData(remoteContents, forcePush ? null : remoteUserData.ref); - this.logService.info('Keybindings: Updated remote keybindings'); + this.logService.info(`${this.syncResourceLogLabel}: Updated remote keybindings`); } // Delete the preview @@ -241,14 +241,14 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem await this.fileService.del(this.conflictsPreviewResource); } catch (e) { /* ignore */ } } else { - this.logService.info('Keybindings: No changes found during synchronizing keybindings.'); + this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing keybindings.`); } if (lastSyncUserData?.ref !== remoteUserData.ref && (content !== null || fileContent !== null)) { - this.logService.trace('Keybindings: Updating last synchronized keybindings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized keybindings...`); const lastSyncContent = this.toSyncContent(content !== null ? content : fileContent!.value.toString(), null); await this.updateLastSyncUserData({ ref: remoteUserData.ref, syncData: { version: remoteUserData.syncData!.version, content: lastSyncContent } }); - this.logService.info('Keybindings: Updated last synchronized keybindings'); + this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized keybindings`); } this.syncPreviewResultPromise = null; @@ -276,14 +276,14 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem if (remoteContent) { const localContent: string = fileContent ? fileContent.value.toString() : '[]'; if (this.hasErrors(localContent)) { - throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings as there are errors/warning in keybindings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); + throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync keybindings as there are errors/warning in keybindings file."), UserDataSyncErrorCode.LocalInvalidContent, this.resource); } if (!lastSyncContent // First time sync || lastSyncContent !== localContent // Local has forwarded || lastSyncContent !== remoteContent // Remote has forwarded ) { - this.logService.trace('Keybindings: Merging remote keybindings with local keybindings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Merging remote keybindings with local keybindings...`); const result = await merge(localContent, remoteContent, lastSyncContent, formattingOptions, this.userDataSyncUtilService); // Sync only if there are changes if (result.hasChanges) { @@ -297,7 +297,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem // First time syncing to remote else if (fileContent) { - this.logService.trace('Keybindings: Remote keybindings does not exist. Synchronizing keybindings for the first time.'); + this.logService.trace(`${this.syncResourceLogLabel}: Remote keybindings does not exist. Synchronizing keybindings for the first time.`); content = fileContent.value.toString(); hasRemoteChanged = true; } diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 219f2c2a698..add06d81bbc 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse } from 'vs/base/common/json'; import { localize } from 'vs/nls'; @@ -57,7 +57,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement @ITelemetryService telemetryService: ITelemetryService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, ) { - super(environmentService.settingsResource, SyncSource.Settings, 'settings', fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); + super(environmentService.settingsResource, SyncResource.Settings, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); } protected setStatus(status: SyncStatus): void { @@ -78,14 +78,14 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement async pull(): Promise { if (!this.isEnabled()) { - this.logService.info('Settings: Skipped pulling settings as it is disabled.'); + this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling settings as it is disabled.`); return; } this.stop(); try { - this.logService.info('Settings: Started pulling settings...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pulling settings...`); this.setStatus(SyncStatus.Syncing); const lastSyncUserData = await this.getLastSyncUserData(); @@ -113,10 +113,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement // No remote exists to pull else { - this.logService.info('Settings: Remote settings does not exist.'); + this.logService.info(`${this.syncResourceLogLabel}: Remote settings does not exist.`); } - this.logService.info('Settings: Finished pulling settings.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pulling settings.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -124,14 +124,14 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement async push(): Promise { if (!this.isEnabled()) { - this.logService.info('Settings: Skipped pushing settings as it is disabled.'); + this.logService.info(`${this.syncResourceLogLabel}: Skipped pushing settings as it is disabled.`); return; } this.stop(); try { - this.logService.info('Settings: Started pushing settings...'); + this.logService.info(`${this.syncResourceLogLabel}: Started pushing settings...`); this.setStatus(SyncStatus.Syncing); const fileContent = await this.getLocalFileContent(); @@ -159,10 +159,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement // No local exists to push else { - this.logService.info('Settings: Local settings does not exist.'); + this.logService.info(`${this.syncResourceLogLabel}: Local settings does not exist.`); } - this.logService.info('Settings: Finished pushing settings.'); + this.logService.info(`${this.syncResourceLogLabel}: Finished pushing settings.`); } finally { this.setStatus(SyncStatus.Idle); } @@ -268,7 +268,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement switch (e.code) { case UserDataSyncErrorCode.LocalPreconditionFailed: // Rejected as there is a new local version. Syncing again. - this.logService.info('Settings: Failed to synchronize settings as there is a new local version available. Synchronizing again...'); + this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize settings as there is a new local version available. Synchronizing again...`); return this.performSync(remoteUserData, lastSyncUserData, resolvedConflicts); } } @@ -288,10 +288,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement this.validateContent(content); if (hasLocalChanged) { - this.logService.trace('Settings: Updating local settings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating local settings...`); await this.backupLocal(JSON.stringify(this.toSettingsSyncContent(content))); await this.updateLocalFileContent(content, fileContent); - this.logService.info('Settings: Updated local settings'); + this.logService.info(`${this.syncResourceLogLabel}: Updated local settings`); } if (hasRemoteChanged) { const formatUtils = await this.getFormattingOptions(); @@ -299,9 +299,9 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData); const ignoredSettings = await this.getIgnoredSettings(content); content = updateIgnoredSettings(content, remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : '{}', ignoredSettings, formatUtils); - this.logService.trace('Settings: Updating remote settings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating remote settings...`); remoteUserData = await this.updateRemoteUserData(JSON.stringify(this.toSettingsSyncContent(content)), forcePush ? null : remoteUserData.ref); - this.logService.info('Settings: Updated remote settings'); + this.logService.info(`${this.syncResourceLogLabel}: Updated remote settings`); } // Delete the preview @@ -309,13 +309,13 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement await this.fileService.del(this.conflictsPreviewResource); } catch (e) { /* ignore */ } } else { - this.logService.info('Settings: No changes found during synchronizing settings.'); + this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing settings.`); } if (lastSyncUserData?.ref !== remoteUserData.ref) { - this.logService.trace('Settings: Updating last synchronized settings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized settings...`); await this.updateLastSyncUserData(remoteUserData); - this.logService.info('Settings: Updated last synchronized settings'); + this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized settings`); } this.syncPreviewResultPromise = null; @@ -343,7 +343,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement if (remoteSettingsSyncContent) { const localContent: string = fileContent ? fileContent.value.toString() : '{}'; this.validateContent(localContent); - this.logService.trace('Settings: Merging remote settings with local settings...'); + this.logService.trace(`${this.syncResourceLogLabel}: Merging remote settings with local settings...`); const ignoredSettings = await this.getIgnoredSettings(); const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, ignoredSettings, resolvedConflicts, formattingOptions); content = result.localContent || result.remoteContent; @@ -355,7 +355,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement // First time syncing to remote else if (fileContent) { - this.logService.trace('Settings: Remote settings does not exist. Synchronizing settings for the first time.'); + this.logService.trace(`${this.syncResourceLogLabel}: Remote settings does not exist. Synchronizing settings for the first time.`); content = fileContent.value.toString(); hasRemoteChanged = true; } @@ -406,7 +406,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement private validateContent(content: string): void { if (this.hasErrors(content)) { - throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); + throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.resource); } } } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 0744514d2f5..33ff263dca7 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -135,11 +135,16 @@ export function getUserDataSyncStore(productService: IProductService, configurat return undefined; } -export const ALL_RESOURCE_KEYS: ResourceKey[] = ['settings', 'keybindings', 'extensions', 'globalState']; -export type ResourceKey = 'settings' | 'keybindings' | 'extensions' | 'globalState'; +export const enum SyncResource { + Settings = 'settings', + Keybindings = 'keybindings', + Extensions = 'extensions', + GlobalState = 'globalState' +} +export const ALL_SYNC_RESOURCES: SyncResource[] = [SyncResource.Settings, SyncResource.Keybindings, SyncResource.Extensions, SyncResource.GlobalState]; export interface IUserDataManifest { - latest?: Record + latest?: Record session: string; } @@ -152,21 +157,21 @@ export const IUserDataSyncStoreService = createDecorator; - write(key: ResourceKey, content: string, ref: string | null, source?: SyncSource): Promise; + read(resource: SyncResource, oldValue: IUserData | null): Promise; + write(resource: SyncResource, content: string, ref: string | null): Promise; manifest(): Promise; clear(): Promise; - getAllRefs(key: ResourceKey): Promise; - resolveContent(key: ResourceKey, ref: string): Promise; - delete(key: ResourceKey): Promise; + getAllRefs(resource: SyncResource): Promise; + resolveContent(resource: SyncResource, ref: string): Promise; + delete(resource: SyncResource): Promise; } export const IUserDataSyncBackupStoreService = createDecorator('IUserDataSyncBackupStoreService'); export interface IUserDataSyncBackupStoreService { _serviceBrand: undefined; - backup(resourceKey: ResourceKey, content: string): Promise; - getAllRefs(key: ResourceKey): Promise; - resolveContent(key: ResourceKey, ref?: string): Promise; + backup(resource: SyncResource, content: string): Promise; + getAllRefs(resource: SyncResource): Promise; + resolveContent(resource: SyncResource, ref?: string): Promise; } //#endregion @@ -195,9 +200,9 @@ export enum UserDataSyncErrorCode { export class UserDataSyncError extends Error { - constructor(message: string, public readonly code: UserDataSyncErrorCode, public readonly source?: SyncSource) { + constructor(message: string, public readonly code: UserDataSyncErrorCode, public readonly resource?: SyncResource) { super(message); - this.name = `${this.code} (UserDataSyncError) ${this.source}`; + this.name = `${this.code} (UserDataSyncError) ${this.resource}`; } static toUserDataSyncError(error: Error): UserDataSyncError { @@ -206,7 +211,7 @@ export class UserDataSyncError extends Error { } const match = /^(.+) \(UserDataSyncError\) (.+)?$/.exec(error.name); if (match && match[1]) { - return new UserDataSyncError(error.message, match[1], match[2]); + return new UserDataSyncError(error.message, match[1], match[2]); } return new UserDataSyncError(error.message, UserDataSyncErrorCode.Unknown); } @@ -230,13 +235,6 @@ export interface IGlobalState { storage: IStringDictionary; } -export const enum SyncSource { - Settings = 'Settings', - Keybindings = 'Keybindings', - Extensions = 'Extensions', - GlobalState = 'GlobalState' -} - export const enum SyncStatus { Uninitialized = 'uninitialized', Idle = 'idle', @@ -246,8 +244,7 @@ export const enum SyncStatus { export interface IUserDataSynchroniser { - readonly resourceKey: ResourceKey; - readonly source: SyncSource; + readonly resource: SyncResource; readonly status: SyncStatus; readonly onDidChangeStatus: Event; readonly onDidChangeLocal: Event; @@ -276,13 +273,13 @@ export interface IUserDataSyncEnablementService { _serviceBrand: any; readonly onDidChangeEnablement: Event; - readonly onDidChangeResourceEnablement: Event<[ResourceKey, boolean]>; + readonly onDidChangeResourceEnablement: Event<[SyncResource, boolean]>; isEnabled(): boolean; setEnablement(enabled: boolean): void; - isResourceEnabled(key: ResourceKey): boolean; - setResourceEnablement(key: ResourceKey, enabled: boolean): void; + isResourceEnabled(resource: SyncResource): boolean; + setResourceEnablement(resource: SyncResource, enabled: boolean): void; } export const IUserDataSyncService = createDecorator('IUserDataSyncService'); @@ -292,11 +289,11 @@ export interface IUserDataSyncService { readonly status: SyncStatus; readonly onDidChangeStatus: Event; - readonly conflictsSources: SyncSource[]; - readonly onDidChangeConflicts: Event; + readonly conflictsSources: SyncResource[]; + readonly onDidChangeConflicts: Event; - readonly onDidChangeLocal: Event; - readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]>; + readonly onDidChangeLocal: Event; + readonly onSyncErrors: Event<[SyncResource, UserDataSyncError][]>; readonly lastSyncTime: number | undefined; readonly onDidChangeLastSyncTime: Event; @@ -309,7 +306,7 @@ export interface IUserDataSyncService { isFirstTimeSyncWithMerge(): Promise; resolveContent(resource: URI): Promise; - accept(source: SyncSource, content: string): Promise; + accept(source: SyncResource, content: string): Promise; } export const IUserDataAutoSyncService = createDecorator('IUserDataAutoSyncService'); @@ -351,50 +348,29 @@ export const CONTEXT_SYNC_ENABLEMENT = new RawContextKey('syncEnabled', export const USER_DATA_SYNC_SCHEME = 'vscode-userdata-sync'; export const PREVIEW_QUERY = 'preview=true'; -export function toRemoteSyncResourceFromSource(source: SyncSource, ref?: string): URI { - return toRemoteSyncResource(getResourceKeyFromSyncSource(source), ref); +export function toRemoteSyncResource(resource: SyncResource, ref?: string): URI { + return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote', path: `/${resource}/${ref ? ref : 'latest'}` }); } -export function toRemoteSyncResource(resourceKey: ResourceKey, ref?: string): URI { - return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote', path: `/${resourceKey}/${ref ? ref : 'latest'}` }); -} -export function toLocalBackupSyncResource(resourceKey: ResourceKey, ref?: string): URI { - return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local-backup', path: `/${resourceKey}/${ref ? ref : 'latest'}` }); +export function toLocalBackupSyncResource(resource: SyncResource, ref?: string): URI { + return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local-backup', path: `/${resource}/${ref ? ref : 'latest'}` }); } -export function resolveSyncResource(resource: URI): { remote: boolean, resourceKey: ResourceKey, ref?: string } | null { +export function resolveSyncResource(resource: URI): { remote: boolean, resource: SyncResource, ref?: string } | null { const remote = resource.authority === 'remote'; - const resourceKey: ResourceKey = basename(dirname(resource)) as ResourceKey; + const resourceKey: SyncResource = basename(dirname(resource)) as SyncResource; const ref = basename(resource); if (resourceKey && ref) { - return { remote, resourceKey, ref: ref !== 'latest' ? ref : undefined }; + return { remote, resource: resourceKey, ref: ref !== 'latest' ? ref : undefined }; } return null; } -export function getSyncSourceFromPreviewResource(uri: URI, environmentService: IEnvironmentService): SyncSource | undefined { +export function getSyncSourceFromPreviewResource(uri: URI, environmentService: IEnvironmentService): SyncResource | undefined { if (isEqual(uri, environmentService.settingsSyncPreviewResource)) { - return SyncSource.Settings; + return SyncResource.Settings; } if (isEqual(uri, environmentService.keybindingsSyncPreviewResource)) { - return SyncSource.Keybindings; + return SyncResource.Keybindings; } return undefined; } - -export function getResourceKeyFromSyncSource(source: SyncSource): ResourceKey { - switch (source) { - case SyncSource.Settings: return 'settings'; - case SyncSource.Keybindings: return 'keybindings'; - case SyncSource.Extensions: return 'extensions'; - case SyncSource.GlobalState: return 'globalState'; - } -} - -export function getSyncSourceFromResourceKey(resourceKey: ResourceKey): SyncSource { - switch (resourceKey) { - case 'settings': return SyncSource.Settings; - case 'keybindings': return SyncSource.Keybindings; - case 'extensions': return SyncSource.Extensions; - case 'globalState': return SyncSource.GlobalState; - } -} diff --git a/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts index b439de0bc02..8f9333c9600 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, } from 'vs/base/common/lifecycle'; -import { IUserDataSyncLogService, ResourceKey, ALL_RESOURCE_KEYS, IUserDataSyncBackupStoreService, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncLogService, ALL_SYNC_RESOURCES, IUserDataSyncBackupStoreService, IResourceRefHandle, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { joinPath } from 'vs/base/common/resources'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileService, IFileStat } from 'vs/platform/files/common/files'; @@ -23,11 +23,11 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, ) { super(); - ALL_RESOURCE_KEYS.forEach(resourceKey => this.cleanUpBackup(resourceKey)); + ALL_SYNC_RESOURCES.forEach(resourceKey => this.cleanUpBackup(resourceKey)); } - async getAllRefs(resourceKey: ResourceKey): Promise { - const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey); + async getAllRefs(resource: SyncResource): Promise { + const folder = joinPath(this.environmentService.userDataSyncHome, resource); const stat = await this.fileService.resolve(folder); if (stat.children) { const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}(\.json)?$/.test(stat.name)).sort().reverse(); @@ -39,22 +39,22 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD return []; } - async resolveContent(resourceKey: ResourceKey, ref?: string): Promise { + async resolveContent(resource: SyncResource, ref?: string): Promise { if (!ref) { - const refs = await this.getAllRefs(resourceKey); + const refs = await this.getAllRefs(resource); if (refs.length) { ref = refs[refs.length - 1].ref; } } if (ref) { - const file = joinPath(this.environmentService.userDataSyncHome, resourceKey, ref); + const file = joinPath(this.environmentService.userDataSyncHome, resource, ref); const content = await this.fileService.readFile(file); return content.value.toString(); } return null; } - async backup(resourceKey: ResourceKey, content: string): Promise { + async backup(resourceKey: SyncResource, content: string): Promise { const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey); const resource = joinPath(folder, `${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}.json`); try { @@ -67,8 +67,8 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD } catch (e) { /* Ignore */ } } - private async cleanUpBackup(resourceKey: ResourceKey): Promise { - const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey); + private async cleanUpBackup(resource: SyncResource): Promise { + const folder = joinPath(this.environmentService.userDataSyncHome, resource); try { try { if (!(await this.fileService.exists(folder))) { diff --git a/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts b/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts index 7adbf97471b..7f9e8a23b87 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncEnablementService, ResourceKey, ALL_RESOURCE_KEYS } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncEnablementService, ALL_SYNC_RESOURCES, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage'; @@ -14,7 +14,7 @@ type SyncEnablementClassification = { }; const enablementKey = 'sync.enable'; -function getEnablementKey(resourceKey: ResourceKey) { return `${enablementKey}.${resourceKey}`; } +function getEnablementKey(resource: SyncResource) { return `${enablementKey}.${resource}`; } export class UserDataSyncEnablementService extends Disposable implements IUserDataSyncEnablementService { @@ -23,8 +23,8 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa private _onDidChangeEnablement = new Emitter(); readonly onDidChangeEnablement: Event = this._onDidChangeEnablement.event; - private _onDidChangeResourceEnablement = new Emitter<[ResourceKey, boolean]>(); - readonly onDidChangeResourceEnablement: Event<[ResourceKey, boolean]> = this._onDidChangeResourceEnablement.event; + private _onDidChangeResourceEnablement = new Emitter<[SyncResource, boolean]>(); + readonly onDidChangeResourceEnablement: Event<[SyncResource, boolean]> = this._onDidChangeResourceEnablement.event; constructor( @IStorageService private readonly storageService: IStorageService, @@ -45,13 +45,13 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa } } - isResourceEnabled(resourceKey: ResourceKey): boolean { - return this.storageService.getBoolean(getEnablementKey(resourceKey), StorageScope.GLOBAL, true); + isResourceEnabled(resource: SyncResource): boolean { + return this.storageService.getBoolean(getEnablementKey(resource), StorageScope.GLOBAL, true); } - setResourceEnablement(resourceKey: ResourceKey, enabled: boolean): void { - if (this.isResourceEnabled(resourceKey) !== enabled) { - const resourceEnablementKey = getEnablementKey(resourceKey); + setResourceEnablement(resource: SyncResource, enabled: boolean): void { + if (this.isResourceEnabled(resource) !== enabled) { + const resourceEnablementKey = getEnablementKey(resource); this.telemetryService.publicLog2<{ enabled: boolean }, SyncEnablementClassification>(resourceEnablementKey, { enabled }); this.storageService.store(resourceEnablementKey, enabled, StorageScope.GLOBAL); } @@ -63,7 +63,7 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa this._onDidChangeEnablement.fire(this.isEnabled()); return; } - const resourceKey = ALL_RESOURCE_KEYS.filter(resourceKey => getEnablementKey(resourceKey) === workspaceStorageChangeEvent.key)[0]; + const resourceKey = ALL_SYNC_RESOURCES.filter(resourceKey => getEnablementKey(resourceKey) === workspaceStorageChangeEvent.key)[0]; if (resourceKey) { this._onDidChangeResourceEnablement.fire([resourceKey, this.isEnabled()]); return; diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 4be50d8387a..a50cd1b369a 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError, resolveSyncResource, PREVIEW_QUERY } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, ISettingsSyncService, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError, resolveSyncResource, PREVIEW_QUERY } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; @@ -35,16 +35,16 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; - readonly onDidChangeLocal: Event; + readonly onDidChangeLocal: Event; - private _conflictsSources: SyncSource[] = []; - get conflictsSources(): SyncSource[] { return this._conflictsSources; } - private _onDidChangeConflicts: Emitter = this._register(new Emitter()); - readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + private _conflictsSources: SyncResource[] = []; + get conflictsSources(): SyncResource[] { return this._conflictsSources; } + private _onDidChangeConflicts: Emitter = this._register(new Emitter()); + readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; - private _syncErrors: [SyncSource, UserDataSyncError][] = []; - private _onSyncErrors: Emitter<[SyncSource, UserDataSyncError][]> = this._register(new Emitter<[SyncSource, UserDataSyncError][]>()); - readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]> = this._onSyncErrors.event; + private _syncErrors: [SyncResource, UserDataSyncError][] = []; + private _onSyncErrors: Emitter<[SyncResource, UserDataSyncError][]> = this._register(new Emitter<[SyncResource, UserDataSyncError][]>()); + readonly onSyncErrors: Event<[SyncResource, UserDataSyncError][]> = this._onSyncErrors.event; private _lastSyncTime: number | undefined = undefined; get lastSyncTime(): number | undefined { return this._lastSyncTime; } @@ -75,7 +75,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } this._lastSyncTime = this.storageService.getNumber(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL, undefined); - this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeLocal, () => s.source))); + this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeLocal, () => s.resource))); } async pull(): Promise { @@ -84,7 +84,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ try { await synchroniser.pull(); } catch (e) { - this.handleSyncError(e, synchroniser.source); + this.handleSyncError(e, synchroniser.resource); } } this.updateLastSyncTime(); @@ -96,7 +96,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ try { await synchroniser.push(); } catch (e) { - this.handleSyncError(e, synchroniser.source); + this.handleSyncError(e, synchroniser.resource); } } this.updateLastSyncTime(); @@ -129,10 +129,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ for (const synchroniser of this.synchronisers) { try { - await synchroniser.sync(manifest && manifest.latest ? manifest.latest[synchroniser.resourceKey] : undefined); + await synchroniser.sync(manifest && manifest.latest ? manifest.latest[synchroniser.resource] : undefined); } catch (e) { - this.handleSyncError(e, synchroniser.source); - this._syncErrors.push([synchroniser.source, UserDataSyncError.toUserDataSyncError(e)]); + this.handleSyncError(e, synchroniser.resource); + this._syncErrors.push([synchroniser.resource, UserDataSyncError.toUserDataSyncError(e)]); } } @@ -171,7 +171,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } - async accept(source: SyncSource, content: string): Promise { + async accept(source: SyncResource, content: string): Promise { await this.checkEnablement(); const synchroniser = this.getSynchroniser(source); await synchroniser.accept(content); @@ -180,7 +180,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ async resolveContent(resource: URI): Promise { const result = resolveSyncResource(resource); if (result) { - const synchronizer = this.synchronisers.filter(s => s.resourceKey === result.resourceKey)[0]; + const synchronizer = this.synchronisers.filter(s => s.resource === result.resource)[0]; if (synchronizer) { if (PREVIEW_QUERY === resource.query) { return result.remote ? synchronizer.getRemoteContentFromPreview() : null; @@ -216,7 +216,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ try { synchroniser.resetLocal(); } catch (e) { - this.logService.error(`${synchroniser.source}: ${toErrorMessage(e)}`); + this.logService.error(`${synchroniser.resource}: ${toErrorMessage(e)}`); this.logService.error(e); } } @@ -291,7 +291,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } - private handleSyncError(e: Error, source: SyncSource): void { + private handleSyncError(e: Error, source: SyncResource): void { if (e instanceof UserDataSyncStoreError) { switch (e.code) { case UserDataSyncErrorCode.TooLarge: @@ -303,12 +303,12 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.logService.error(`${source}: ${toErrorMessage(e)}`); } - private computeConflictsSources(): SyncSource[] { - return this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts).map(s => s.source); + private computeConflictsSources(): SyncResource[] { + return this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts).map(s => s.resource); } - getSynchroniser(source: SyncSource): IUserDataSynchroniser { - return this.synchronisers.filter(s => s.source === source)[0]; + getSynchroniser(source: SyncResource): IUserDataSynchroniser { + return this.synchronisers.filter(s => s.resource === source)[0]; } private async checkEnablement(): Promise { diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 091170c7abc..1de0f8351fd 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, } from 'vs/base/common/lifecycle'; -import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, SyncSource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, ResourceKey, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, SyncResource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync'; import { IRequestService, asText, isSuccess, asJson } from 'vs/platform/request/common/request'; import { joinPath, relativePath } from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -31,12 +31,12 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn this.userDataSyncStore = getUserDataSyncStore(productService, configurationService); } - async getAllRefs(key: ResourceKey): Promise { + async getAllRefs(resource: SyncResource): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } - const uri = joinPath(this.userDataSyncStore.url, 'resource', key); + const uri = joinPath(this.userDataSyncStore.url, 'resource', resource); const headers: IHeaders = {}; const context = await this.request({ type: 'GET', url: uri.toString(), headers }, undefined, CancellationToken.None); @@ -49,12 +49,12 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn return result.map(({ url, created }) => ({ ref: relativePath(uri, URI.parse(url))!, created: created })); } - async resolveContent(key: ResourceKey, ref: string): Promise { + async resolveContent(resource: SyncResource, ref: string): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } - const url = joinPath(this.userDataSyncStore.url, 'resource', key, ref).toString(); + const url = joinPath(this.userDataSyncStore.url, 'resource', resource, ref).toString(); const headers: IHeaders = {}; const context = await this.request({ type: 'GET', url, headers }, undefined, CancellationToken.None); @@ -67,12 +67,12 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn return content; } - async delete(key: ResourceKey): Promise { + async delete(resource: SyncResource): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } - const url = joinPath(this.userDataSyncStore.url, 'resource', key).toString(); + const url = joinPath(this.userDataSyncStore.url, 'resource', resource).toString(); const headers: IHeaders = {}; const context = await this.request({ type: 'DELETE', url, headers }, undefined, CancellationToken.None); @@ -82,12 +82,12 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn } } - async read(key: ResourceKey, oldValue: IUserData | null, source?: SyncSource): Promise { + async read(resource: SyncResource, oldValue: IUserData | null): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } - const url = joinPath(this.userDataSyncStore.url, 'resource', key, 'latest').toString(); + const url = joinPath(this.userDataSyncStore.url, 'resource', resource, 'latest').toString(); const headers: IHeaders = {}; // Disable caching as they are cached by synchronisers headers['Cache-Control'] = 'no-cache'; @@ -95,7 +95,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn headers['If-None-Match'] = oldValue.ref; } - const context = await this.request({ type: 'GET', url, headers }, source, CancellationToken.None); + const context = await this.request({ type: 'GET', url, headers }, resource, CancellationToken.None); if (context.res.statusCode === 304) { // There is no new value. Hence return the old value. @@ -103,37 +103,37 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn } if (!isSuccess(context)) { - throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, source); + throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, resource); } const ref = context.res.headers['etag']; if (!ref) { - throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, source); + throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, resource); } const content = await asText(context); return { ref, content }; } - async write(key: ResourceKey, data: string, ref: string | null, source?: SyncSource): Promise { + async write(resource: SyncResource, data: string, ref: string | null): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } - const url = joinPath(this.userDataSyncStore.url, 'resource', key).toString(); + const url = joinPath(this.userDataSyncStore.url, 'resource', resource).toString(); const headers: IHeaders = { 'Content-Type': 'text/plain' }; if (ref) { headers['If-Match'] = ref; } - const context = await this.request({ type: 'POST', url, data, headers }, source, CancellationToken.None); + const context = await this.request({ type: 'POST', url, data, headers }, resource, CancellationToken.None); if (!isSuccess(context)) { - throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, source); + throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, resource); } const newRef = context.res.headers['etag']; if (!newRef) { - throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, source); + throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, resource); } return newRef; } @@ -169,7 +169,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn } } - private async request(options: IRequestOptions, source: SyncSource | undefined, token: CancellationToken): Promise { + private async request(options: IRequestOptions, source: SyncResource | undefined, token: CancellationToken): Promise { const authToken = await this.authTokenService.getToken(); if (!authToken) { throw new UserDataSyncStoreError('No Auth Token Available', UserDataSyncErrorCode.Unauthorized, source); diff --git a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts index 7c40ae8d70d..c0f87712cf1 100644 --- a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IUserDataSyncStoreService, IUserDataSyncService, SyncSource, UserDataSyncError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncStoreService, IUserDataSyncService, SyncResource, UserDataSyncError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { SettingsSynchroniser, ISettingsSyncContent } from 'vs/platform/userDataSync/common/settingsSync'; @@ -44,7 +44,7 @@ suite('SettingsSync', () => { setup(async () => { client = disposableStore.add(new UserDataSyncClient(server)); await client.setUp(); - testObject = (client.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getSynchroniser(SyncSource.Settings) as SettingsSynchroniser; + testObject = (client.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getSynchroniser(SyncResource.Settings) as SettingsSynchroniser; disposableStore.add(toDisposable(() => client.instantiationService.get(IUserDataSyncStoreService).clear())); }); @@ -77,7 +77,7 @@ suite('SettingsSync', () => { await updateSettings(expected); await testObject.sync(); - const { content } = await client.read(testObject.resourceKey); + const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); assert.deepEqual(actual, expected); @@ -101,7 +101,7 @@ suite('SettingsSync', () => { await testObject.sync(); - const { content } = await client.read(testObject.resourceKey); + const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); assert.deepEqual(actual, `{ @@ -132,7 +132,7 @@ suite('SettingsSync', () => { await testObject.sync(); - const { content } = await client.read(testObject.resourceKey); + const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); assert.deepEqual(actual, `{ @@ -163,7 +163,7 @@ suite('SettingsSync', () => { await testObject.sync(); - const { content } = await client.read(testObject.resourceKey); + const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); assert.deepEqual(actual, `{ @@ -187,7 +187,7 @@ suite('SettingsSync', () => { await testObject.sync(); - const { content } = await client.read(testObject.resourceKey); + const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); assert.deepEqual(actual, `{ @@ -205,7 +205,7 @@ suite('SettingsSync', () => { await testObject.sync(); - const { content } = await client.read(testObject.resourceKey); + const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); assert.deepEqual(actual, `{ @@ -239,7 +239,7 @@ suite('SettingsSync', () => { await testObject.sync(); - const { content } = await client.read(testObject.resourceKey); + const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); assert.deepEqual(actual, `{ @@ -287,7 +287,7 @@ suite('SettingsSync', () => { await testObject.sync(); - const { content } = await client.read(testObject.resourceKey); + const { content } = await client.read(testObject.resource); assert.ok(content !== null); const actual = parseSettings(content!); assert.deepEqual(actual, `{ diff --git a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts index b2fa135d413..d8abc061edf 100644 --- a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts +++ b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ResourceKey, IUserDataSyncStoreService, SyncSource, SyncStatus, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncStoreService, SyncResource, SyncStatus, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; @@ -17,7 +17,7 @@ class TestSynchroniser extends AbstractSynchroniser { syncResult: { status?: SyncStatus, error?: boolean } = {}; onDoSyncCall: Emitter = this._register(new Emitter()); - readonly resourceKey: ResourceKey = 'settings'; + readonly resource: SyncResource = SyncResource.Settings; protected readonly version: number = 1; private cancelled: boolean = false; @@ -40,7 +40,7 @@ class TestSynchroniser extends AbstractSynchroniser { } async apply(ref: string): Promise { - ref = await this.userDataSyncStoreService.write(this.resourceKey, '', ref); + ref = await this.userDataSyncStoreService.write(this.resource, '', ref); await this.updateLastSyncUserData({ ref, syncData: { content: '', version: this.version } }); } @@ -68,7 +68,7 @@ suite('TestSynchronizer', () => { teardown(() => disposableStore.clear()); test('status is syncing', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings'); const actual: SyncStatus[] = []; disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); @@ -85,7 +85,7 @@ suite('TestSynchronizer', () => { }); test('status is set correctly when sync is finished', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings'); testObject.syncBarrier.open(); const actual: SyncStatus[] = []; @@ -97,7 +97,7 @@ suite('TestSynchronizer', () => { }); test('status is set correctly when sync has conflicts', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings'); testObject.syncResult = { status: SyncStatus.HasConflicts }; testObject.syncBarrier.open(); @@ -110,7 +110,7 @@ suite('TestSynchronizer', () => { }); test('status is set correctly when sync has errors', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings'); testObject.syncResult = { error: true }; testObject.syncBarrier.open(); @@ -127,7 +127,7 @@ suite('TestSynchronizer', () => { }); test('sync should not run if syncing already', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings'); const promise = Event.toPromise(testObject.onDoSyncCall.event); testObject.sync(); @@ -144,8 +144,8 @@ suite('TestSynchronizer', () => { }); test('sync should not run if disabled', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); - client.instantiationService.get(IUserDataSyncEnablementService).setResourceEnablement(testObject.resourceKey, false); + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings'); + client.instantiationService.get(IUserDataSyncEnablementService).setResourceEnablement(testObject.resource, false); const actual: SyncStatus[] = []; disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); @@ -157,7 +157,7 @@ suite('TestSynchronizer', () => { }); test('sync should not run if there are conflicts', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings'); testObject.syncResult = { status: SyncStatus.HasConflicts }; testObject.syncBarrier.open(); await testObject.sync(); @@ -171,7 +171,7 @@ suite('TestSynchronizer', () => { }); test('request latest data on precondition failure', async () => { - const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings'); // Sync once testObject.syncBarrier.open(); await testObject.sync(); @@ -186,13 +186,13 @@ suite('TestSynchronizer', () => { }); // Start sycing - const { ref } = await userDataSyncStoreService.read(testObject.resourceKey, null); + const { ref } = await userDataSyncStoreService.read(testObject.resource, null); await testObject.sync(ref); assert.deepEqual(server.requests, [ - { type: 'POST', url: `${server.url}/v1/resource/${testObject.resourceKey}`, headers: { 'If-Match': ref } }, - { type: 'GET', url: `${server.url}/v1/resource/${testObject.resourceKey}/latest`, headers: {} }, - { type: 'POST', url: `${server.url}/v1/resource/${testObject.resourceKey}`, headers: { 'If-Match': `${parseInt(ref) + 1}` } }, + { type: 'POST', url: `${server.url}/v1/resource/${testObject.resource}`, headers: { 'If-Match': ref } }, + { type: 'GET', url: `${server.url}/v1/resource/${testObject.resource}/latest`, headers: {} }, + { type: 'POST', url: `${server.url}/v1/resource/${testObject.resource}`, headers: { 'If-Match': `${parseInt(ref) + 1}` } }, ]); }); diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 181df5ae44f..009853494de 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -6,7 +6,7 @@ import { IRequestService } from 'vs/platform/request/common/request'; import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IUserData, ResourceKey, IUserDataManifest, ALL_RESOURCE_KEYS, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, ISettingsSyncService, IUserDataSyncService, getDefaultIgnoredSettings, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, IUserDataManifest, ALL_SYNC_RESOURCES, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, ISettingsSyncService, IUserDataSyncService, getDefaultIgnoredSettings, IUserDataSyncBackupStoreService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { bufferToStream, VSBuffer } from 'vs/base/common/buffer'; import { generateUuid } from 'vs/base/common/uuid'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; @@ -120,8 +120,8 @@ export class UserDataSyncClient extends Disposable { return this.instantiationService.get(IUserDataSyncService).sync(); } - read(key: ResourceKey): Promise { - return this.instantiationService.get(IUserDataSyncStoreService).read(key, null); + read(resource: SyncResource): Promise { + return this.instantiationService.get(IUserDataSyncStoreService).read(resource, null); } } @@ -132,7 +132,7 @@ export class UserDataSyncTestServer implements IRequestService { readonly url: string = 'http://host:3000'; private session: string | null = null; - private readonly data: Map = new Map(); + private readonly data: Map = new Map(); private _requests: { url: string, type: string, headers?: IHeaders }[] = []; get requests(): { url: string, type: string, headers?: IHeaders }[] { return this._requests; } @@ -180,7 +180,7 @@ export class UserDataSyncTestServer implements IRequestService { private async getManifest(headers?: IHeaders): Promise { if (this.session) { - const latest: Record = Object.create({}); + const latest: Record = Object.create({}); const manifest: IUserDataManifest = { session: this.session, latest }; this.data.forEach((value, key) => latest[key] = value.ref); return this.toResponse(200, { 'Content-Type': 'application/json' }, JSON.stringify(manifest)); @@ -189,7 +189,7 @@ export class UserDataSyncTestServer implements IRequestService { } private async getLatestData(resource: string, headers: IHeaders = {}): Promise { - const resourceKey = ALL_RESOURCE_KEYS.find(key => key === resource); + const resourceKey = ALL_SYNC_RESOURCES.find(key => key === resource); if (resourceKey) { const data = this.data.get(resourceKey); if (!data) { @@ -210,7 +210,7 @@ export class UserDataSyncTestServer implements IRequestService { if (!this.session) { this.session = generateUuid(); } - const resourceKey = ALL_RESOURCE_KEYS.find(key => key === resource); + const resourceKey = ALL_SYNC_RESOURCES.find(key => key === resource); if (resourceKey) { const data = this.data.get(resourceKey); if (headers['If-Match'] !== (data ? data.ref : '0')) { diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts index 86ca60f8a0a..6c5e02511da 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IUserDataSyncService, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IFileService } from 'vs/platform/files/common/files'; @@ -480,7 +480,7 @@ suite('UserDataSyncService', () => { await testObject.sync(); assert.deepEqual(testObject.status, SyncStatus.HasConflicts); - assert.deepEqual(testObject.conflictsSources, [SyncSource.Settings]); + assert.deepEqual(testObject.conflictsSources, [SyncResource.Settings]); }); test('test sync will sync other non conflicted areas', async () => { diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index d7f3ea4d80d..c4a99be9b0b 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -30,7 +30,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { CONTEXT_SYNC_STATE, getUserDataSyncStore, ISyncConfiguration, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, SyncSource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, ResourceKey, getSyncSourceFromPreviewResource, CONTEXT_SYNC_ENABLEMENT, toRemoteSyncResourceFromSource, PREVIEW_QUERY, resolveSyncResource, getSyncSourceFromResourceKey } from 'vs/platform/userDataSync/common/userDataSync'; +import { CONTEXT_SYNC_STATE, getUserDataSyncStore, ISyncConfiguration, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, getSyncSourceFromPreviewResource, CONTEXT_SYNC_ENABLEMENT, PREVIEW_QUERY, resolveSyncResource, toRemoteSyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -60,14 +60,14 @@ const enum AuthStatus { const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', AuthStatus.Initializing); const CONTEXT_CONFLICTS_SOURCES = new RawContextKey('conflictsSources', ''); -type ConfigureSyncQuickPickItem = { id: ResourceKey, label: string, description?: string }; +type ConfigureSyncQuickPickItem = { id: SyncResource, label: string, description?: string }; -function getSyncAreaLabel(source: SyncSource): string { +function getSyncAreaLabel(source: SyncResource): string { switch (source) { - case SyncSource.Settings: return localize('settings', "Settings"); - case SyncSource.Keybindings: return localize('keybindings', "Keyboard Shortcuts"); - case SyncSource.Extensions: return localize('extensions', "Extensions"); - case SyncSource.GlobalState: return localize('ui state label', "UI State"); + case SyncResource.Settings: return localize('settings', "Settings"); + case SyncResource.Keybindings: return localize('keybindings', "Keyboard Shortcuts"); + case SyncResource.Extensions: return localize('extensions', "Extensions"); + case SyncResource.GlobalState: return localize('ui state label', "UI State"); } } @@ -283,8 +283,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.updateBadge(); } - private readonly conflictsDisposables = new Map(); - private onDidChangeConflicts(conflicts: SyncSource[]) { + private readonly conflictsDisposables = new Map(); + private onDidChangeConflicts(conflicts: SyncResource[]) { this.updateBadge(); if (conflicts.length) { this.conflictsSources.set(this.userDataSyncService.conflictsSources.join(',')); @@ -352,22 +352,22 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private async acceptRemote(syncSource: SyncSource) { + private async acceptRemote(syncResource: SyncResource) { try { - const contents = await this.userDataSyncService.resolveContent(toRemoteSyncResourceFromSource(syncSource).with({ query: PREVIEW_QUERY })); + const contents = await this.userDataSyncService.resolveContent(toRemoteSyncResource(syncResource).with({ query: PREVIEW_QUERY })); if (contents) { - await this.userDataSyncService.accept(syncSource, contents); + await this.userDataSyncService.accept(syncResource, contents); } } catch (e) { this.notificationService.error(e); } } - private async acceptLocal(syncSource: SyncSource): Promise { + private async acceptLocal(syncSource: SyncResource): Promise { try { - const previewResource = syncSource === SyncSource.Settings + const previewResource = syncSource === SyncResource.Settings ? this.workbenchEnvironmentService.settingsSyncPreviewResource - : syncSource === SyncSource.Keybindings + : syncSource === SyncResource.Keybindings ? this.workbenchEnvironmentService.keybindingsSyncPreviewResource : null; if (previewResource) { @@ -415,15 +415,15 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); return; case UserDataSyncErrorCode.TooLarge: - if (error.source === SyncSource.Keybindings || error.source === SyncSource.Settings) { - this.disableSync(error.source); - const sourceArea = getSyncAreaLabel(error.source); + if (error.resource === SyncResource.Keybindings || error.resource === SyncResource.Settings) { + this.disableSync(error.resource); + const sourceArea = getSyncAreaLabel(error.resource); this.notificationService.notify({ severity: Severity.Error, message: localize('too large', "Disabled syncing {0} because size of the {1} file to sync is larger than {2}. Please open the file and reduce the size and enable sync", sourceArea.toLowerCase(), sourceArea.toLowerCase(), '100kb'), actions: { primary: [new Action('open sync file', localize('open file', "Open {0} File", sourceArea), undefined, true, - () => error.source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] + () => error.resource === SyncResource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] } }); } @@ -438,8 +438,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private readonly invalidContentErrorDisposables = new Map(); - private onSyncErrors(errors: [SyncSource, UserDataSyncError][]): void { + private readonly invalidContentErrorDisposables = new Map(); + private onSyncErrors(errors: [SyncResource, UserDataSyncError][]): void { if (errors.length) { for (const [source, error] of errors) { switch (error.code) { @@ -460,14 +460,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private handleInvalidContentError(source: SyncSource): void { + private handleInvalidContentError(source: SyncResource): void { if (this.invalidContentErrorDisposables.has(source)) { return; } - if (source !== SyncSource.Settings && source !== SyncSource.Keybindings) { + if (source !== SyncResource.Settings && source !== SyncResource.Keybindings) { return; } - const resource = source === SyncSource.Settings ? this.workbenchEnvironmentService.settingsResource : this.workbenchEnvironmentService.keybindingsResource; + const resource = source === SyncResource.Settings ? this.workbenchEnvironmentService.settingsResource : this.workbenchEnvironmentService.keybindingsResource; if (isEqual(resource, toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }))) { // Do not show notification if the file in error is active return; @@ -478,7 +478,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo message: localize('errorInvalidConfiguration', "Unable to sync {0} because there are some errors/warnings in the file. Please open the file to correct errors/warnings in it.", errorArea.toLowerCase()), actions: { primary: [new Action('open sync file', localize('open file', "Open {0} File", errorArea), undefined, true, - () => source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] + () => source === SyncResource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] } }); this.invalidContentErrorDisposables.set(source, toDisposable(() => { @@ -602,17 +602,17 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private getConfigureSyncQuickPickItems(): ConfigureSyncQuickPickItem[] { return [{ - id: 'settings', - label: getSyncAreaLabel(SyncSource.Settings) + id: SyncResource.Settings, + label: getSyncAreaLabel(SyncResource.Settings) }, { - id: 'keybindings', - label: getSyncAreaLabel(SyncSource.Keybindings) + id: SyncResource.Keybindings, + label: getSyncAreaLabel(SyncResource.Keybindings) }, { - id: 'extensions', - label: getSyncAreaLabel(SyncSource.Extensions) + id: SyncResource.Extensions, + label: getSyncAreaLabel(SyncResource.Extensions) }, { - id: 'globalState', - label: getSyncAreaLabel(SyncSource.GlobalState), + id: SyncResource.GlobalState, + label: getSyncAreaLabel(SyncResource.GlobalState), description: localize('ui state description', "only 'Display Language' for now") }]; } @@ -707,15 +707,15 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private disableSync(source?: SyncSource): void { + private disableSync(source?: SyncResource): void { if (source === undefined) { this.userDataSyncEnablementService.setEnablement(false); } else { switch (source) { - case SyncSource.Settings: return this.userDataSyncEnablementService.setResourceEnablement('settings', false); - case SyncSource.Keybindings: return this.userDataSyncEnablementService.setResourceEnablement('keybindings', false); - case SyncSource.Extensions: return this.userDataSyncEnablementService.setResourceEnablement('extensions', false); - case SyncSource.GlobalState: return this.userDataSyncEnablementService.setResourceEnablement('globalState', false); + case SyncResource.Settings: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.Settings, false); + case SyncResource.Keybindings: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.Keybindings, false); + case SyncResource.Extensions: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.Extensions, false); + case SyncResource.GlobalState: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.GlobalState, false); } } } @@ -729,9 +729,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private getConflictsEditorInput(source: SyncSource): IEditorInput | undefined { - const previewResource = source === SyncSource.Settings ? this.workbenchEnvironmentService.settingsSyncPreviewResource - : source === SyncSource.Keybindings ? this.workbenchEnvironmentService.keybindingsSyncPreviewResource + private getConflictsEditorInput(source: SyncResource): IEditorInput | undefined { + const previewResource = source === SyncResource.Settings ? this.workbenchEnvironmentService.settingsSyncPreviewResource + : source === SyncResource.Keybindings ? this.workbenchEnvironmentService.keybindingsSyncPreviewResource : null; return previewResource ? this.editorService.editors.filter(input => input instanceof DiffEditorInput && isEqual(previewResource, input.master.resource))[0] : undefined; } @@ -743,18 +743,18 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); } - private async handleConflicts(source: SyncSource): Promise { + private async handleConflicts(resource: SyncResource): Promise { let previewResource: URI | undefined = undefined; let label: string = ''; - if (source === SyncSource.Settings) { + if (resource === SyncResource.Settings) { previewResource = this.workbenchEnvironmentService.settingsSyncPreviewResource; label = localize('settings conflicts preview', "Settings Conflicts (Remote ↔ Local)"); - } else if (source === SyncSource.Keybindings) { + } else if (resource === SyncResource.Keybindings) { previewResource = this.workbenchEnvironmentService.keybindingsSyncPreviewResource; label = localize('keybindings conflicts preview', "Keybindings Conflicts (Remote ↔ Local)"); } if (previewResource) { - const remoteContentResource = toRemoteSyncResourceFromSource(source).with({ query: PREVIEW_QUERY }); + const remoteContentResource = toRemoteSyncResource(resource).with({ query: PREVIEW_QUERY }); await this.editorService.openEditor({ leftResource: remoteContentResource, rightResource: previewResource, @@ -846,7 +846,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private registerShowSettingsConflictsAction(): void { const resolveSettingsConflictsWhenContext = ContextKeyExpr.regex(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*settings.*/i); - CommandsRegistry.registerCommand(resolveSettingsConflictsCommand.id, () => this.handleConflicts(SyncSource.Settings)); + CommandsRegistry.registerCommand(resolveSettingsConflictsCommand.id, () => this.handleConflicts(SyncResource.Settings)); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', command: { @@ -873,7 +873,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private registerShowKeybindingsConflictsAction(): void { const resolveKeybindingsConflictsWhenContext = ContextKeyExpr.regex(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*keybindings.*/i); - CommandsRegistry.registerCommand(resolveKeybindingsConflictsCommand.id, () => this.handleConflicts(SyncSource.Keybindings)); + CommandsRegistry.registerCommand(resolveKeybindingsConflictsCommand.id, () => this.handleConflicts(SyncResource.Keybindings)); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', command: { @@ -934,10 +934,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo if (that.userDataSyncService.conflictsSources.length) { for (const source of that.userDataSyncService.conflictsSources) { switch (source) { - case SyncSource.Settings: + case SyncResource.Settings: items.push({ id: resolveSettingsConflictsCommand.id, label: resolveSettingsConflictsCommand.title }); break; - case SyncSource.Keybindings: + case SyncResource.Keybindings: items.push({ id: resolveKeybindingsConflictsCommand.id, label: resolveKeybindingsConflictsCommand.title }); break; } @@ -1130,7 +1130,7 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio this._register(this.acceptChangesButton.onClick(async () => { const model = this.editor.getModel(); if (model) { - const conflictsSource = (getSyncSourceFromPreviewResource(model.uri, this.environmentService) || getSyncSourceFromResourceKey(resolveSyncResource(model.uri)!.resourceKey))!; + const conflictsSource = (getSyncSourceFromPreviewResource(model.uri, this.environmentService) || resolveSyncResource(model.uri)!.resource)!; this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: conflictsSource, action: isRemote ? 'acceptRemote' : 'acceptLocal' }); const syncAreaLabel = getSyncAreaLabel(conflictsSource); const result = await this.dialogService.confirm({ diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts index b189b51863d..c02da6df9c7 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts @@ -10,7 +10,7 @@ import { localize } from 'vs/nls'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { TreeViewPane, TreeView } from 'vs/workbench/browser/parts/views/treeView'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { ALL_RESOURCE_KEYS, CONTEXT_SYNC_ENABLEMENT, IUserDataSyncStoreService, toRemoteSyncResource, resolveSyncResource, IUserDataSyncBackupStoreService, IResourceRefHandle, ResourceKey, toLocalBackupSyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { ALL_SYNC_RESOURCES, CONTEXT_SYNC_ENABLEMENT, IUserDataSyncStoreService, toRemoteSyncResource, resolveSyncResource, IUserDataSyncBackupStoreService, IResourceRefHandle, toLocalBackupSyncResource, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; import { IContextKeyService, RawContextKey, ContextKeyExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey'; import { URI } from 'vs/base/common/uri'; @@ -61,8 +61,8 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution { if (visible && !treeView.dataProvider) { disposable.dispose(); treeView.dataProvider = this.instantiationService.createInstance(UserDataSyncHistoryViewDataProvider, id, - (resourceKey: ResourceKey) => remote ? this.userDataSyncStoreService.getAllRefs(resourceKey) : this.userDataSyncBackupStoreService.getAllRefs(resourceKey), - (resourceKey: ResourceKey, ref: string) => remote ? toRemoteSyncResource(resourceKey, ref) : toLocalBackupSyncResource(resourceKey, ref)); + (resource: SyncResource) => remote ? this.userDataSyncStoreService.getAllRefs(resource) : this.userDataSyncBackupStoreService.getAllRefs(resource), + (resource: SyncResource, ref: string) => remote ? toRemoteSyncResource(resource, ref) : toLocalBackupSyncResource(resource, ref)); } }); const viewsRegistry = Registry.as(Extensions.ViewsRegistry); @@ -114,7 +114,7 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution { let resource = URI.parse(handle.$treeItemHandle); const result = resolveSyncResource(resource); if (result) { - resource = resource.with({ fragment: result.resourceKey }); + resource = resource.with({ fragment: result.resource }); await editorService.openEditor({ resource }); } } @@ -152,8 +152,8 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution { const resource = URI.parse(handle.$treeItemHandle); const result = resolveSyncResource(resource); if (result) { - const leftResource: URI = resource.with({ fragment: result.resourceKey }); - const rightResource: URI = result.resourceKey === 'settings' ? environmentService.settingsResource : environmentService.keybindingsResource; + const leftResource: URI = resource.with({ fragment: result.resource }); + const rightResource: URI = result.resource === 'settings' ? environmentService.settingsResource : environmentService.keybindingsResource; await editorService.openEditor({ leftResource, rightResource, @@ -174,8 +174,8 @@ class UserDataSyncHistoryViewDataProvider implements ITreeViewDataProvider { constructor( private readonly viewId: string, - private getAllRefs: (resourceKey: ResourceKey) => Promise, - private toResource: (resourceKey: ResourceKey, ref: string) => URI + private getAllRefs: (resource: SyncResource) => Promise, + private toResource: (resource: SyncResource, ref: string) => URI ) { } @@ -183,7 +183,7 @@ class UserDataSyncHistoryViewDataProvider implements ITreeViewDataProvider { if (element) { return this.getResources(element.handle); } - return ALL_RESOURCE_KEYS.map(resourceKey => ({ + return ALL_SYNC_RESOURCES.map(resourceKey => ({ handle: resourceKey, collapsibleState: TreeItemCollapsibleState.Collapsed, label: { label: resourceKey }, @@ -193,7 +193,7 @@ class UserDataSyncHistoryViewDataProvider implements ITreeViewDataProvider { } private async getResources(handle: string): Promise { - const resourceKey = ALL_RESOURCE_KEYS.filter(key => key === handle)[0]; + const resourceKey = ALL_SYNC_RESOURCES.filter(key => key === handle)[0]; if (resourceKey) { const refHandles = await this.getAllRefs(resourceKey); return refHandles.map(({ ref, created }) => { diff --git a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts index 7d48e03e84c..e7fc1a7af40 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncStatus, ISettingsSyncService, IConflictSetting, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, ISettingsSyncService, IConflictSetting, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; @@ -17,7 +17,7 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ private readonly channel: IChannel; readonly resourceKey = 'settings'; - readonly source = SyncSource.Settings; + readonly resource = SyncResource.Settings; private _status: SyncStatus = SyncStatus.Uninitialized; get status(): SyncStatus { return this._status; } diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService.ts index 3a395b1c5b4..7ce287cdc13 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ResourceKey, IResourceRefHandle, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IResourceRefHandle, IUserDataSyncBackupStoreService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -19,16 +19,16 @@ export class UserDataSyncBackupStoreService implements IUserDataSyncBackupStoreS this.channel = sharedProcessService.getChannel('userDataSyncBackupStoreService'); } - backup(key: ResourceKey, content: string): Promise { + backup(key: SyncResource, content: string): Promise { return this.channel.call('backup', [key, content]); } - getAllRefs(key: ResourceKey): Promise { + getAllRefs(key: SyncResource): Promise { return this.channel.call('getAllRefs', [key]); } - resolveContent(key: ResourceKey, ref: string): Promise { + resolveContent(key: SyncResource, ref: string): Promise { return this.channel.call('resolveContent', [key, ref]); } diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index 686866cfc79..d4838228ff0 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncStatus, SyncSource, IUserDataSyncService, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, SyncResource, IUserDataSyncService, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; @@ -23,20 +23,20 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; - get onDidChangeLocal(): Event { return this.channel.listen('onDidChangeLocal'); } + get onDidChangeLocal(): Event { return this.channel.listen('onDidChangeLocal'); } - private _conflictsSources: SyncSource[] = []; - get conflictsSources(): SyncSource[] { return this._conflictsSources; } - private _onDidChangeConflicts: Emitter = this._register(new Emitter()); - readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + private _conflictsSources: SyncResource[] = []; + get conflictsSources(): SyncResource[] { return this._conflictsSources; } + private _onDidChangeConflicts: Emitter = this._register(new Emitter()); + readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; private _lastSyncTime: number | undefined = undefined; get lastSyncTime(): number | undefined { return this._lastSyncTime; } private _onDidChangeLastSyncTime: Emitter = this._register(new Emitter()); readonly onDidChangeLastSyncTime: Event = this._onDidChangeLastSyncTime.event; - private _onSyncErrors: Emitter<[SyncSource, UserDataSyncError][]> = this._register(new Emitter<[SyncSource, UserDataSyncError][]>()); - readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]> = this._onSyncErrors.event; + private _onSyncErrors: Emitter<[SyncResource, UserDataSyncError][]> = this._register(new Emitter<[SyncResource, UserDataSyncError][]>()); + readonly onSyncErrors: Event<[SyncResource, UserDataSyncError][]> = this._onSyncErrors.event; constructor( @ISharedProcessService sharedProcessService: ISharedProcessService @@ -52,7 +52,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return userDataSyncChannel.listen(event, arg); } }; - this.channel.call<[SyncStatus, SyncSource[], number | undefined]>('_getInitialData').then(([status, conflicts, lastSyncTime]) => { + this.channel.call<[SyncStatus, SyncResource[], number | undefined]>('_getInitialData').then(([status, conflicts, lastSyncTime]) => { this.updateStatus(status); this.updateConflicts(conflicts); if (lastSyncTime) { @@ -61,8 +61,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); this._register(this.channel.listen('onDidChangeLastSyncTime')(lastSyncTime => this.updateLastSyncTime(lastSyncTime))); }); - this._register(this.channel.listen('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts))); - this._register(this.channel.listen<[SyncSource, Error][]>('onSyncErrors')(errors => this._onSyncErrors.fire(errors.map(([source, error]) => ([source, UserDataSyncError.toUserDataSyncError(error)]))))); + this._register(this.channel.listen('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts))); + this._register(this.channel.listen<[SyncResource, Error][]>('onSyncErrors')(errors => this._onSyncErrors.fire(errors.map(([source, error]) => ([source, UserDataSyncError.toUserDataSyncError(error)]))))); } pull(): Promise { @@ -73,7 +73,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return this.channel.call('sync'); } - accept(source: SyncSource, content: string): Promise { + accept(source: SyncResource, content: string): Promise { return this.channel.call('accept', [source, content]); } @@ -102,7 +102,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this._onDidChangeStatus.fire(status); } - private async updateConflicts(conflicts: SyncSource[]): Promise { + private async updateConflicts(conflicts: SyncResource[]): Promise { this._conflictsSources = conflicts; this._onDidChangeConflicts.fire(conflicts); } diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncStoreService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncStoreService.ts index dd3273ab5f0..21365024770 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncStoreService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncStoreService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncSource, IUserDataSyncStoreService, IUserDataSyncStore, getUserDataSyncStore, ResourceKey, IUserData, IUserDataManifest, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncResource, IUserDataSyncStoreService, IUserDataSyncStore, getUserDataSyncStore, IUserData, IUserDataManifest, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -25,11 +25,11 @@ export class UserDataSyncStoreService implements IUserDataSyncStoreService { this.userDataSyncStore = getUserDataSyncStore(productService, configurationService); } - read(key: ResourceKey, oldValue: IUserData | null, source?: SyncSource): Promise { + read(key: SyncResource, oldValue: IUserData | null, source?: SyncResource): Promise { throw new Error('Not Supported'); } - write(key: ResourceKey, content: string, ref: string | null, source?: SyncSource): Promise { + write(key: SyncResource, content: string, ref: string | null, source?: SyncResource): Promise { throw new Error('Not Supported'); } @@ -41,15 +41,15 @@ export class UserDataSyncStoreService implements IUserDataSyncStoreService { throw new Error('Not Supported'); } - getAllRefs(key: ResourceKey): Promise { + getAllRefs(key: SyncResource): Promise { return this.channel.call('getAllRefs', [key]); } - resolveContent(key: ResourceKey, ref: string): Promise { + resolveContent(key: SyncResource, ref: string): Promise { return this.channel.call('resolveContent', [key, ref]); } - delete(key: ResourceKey): Promise { + delete(key: SyncResource): Promise { return this.channel.call('delete', [key]); } From 70997e1c5db3c26cb94ef4d16e13c78c92a130a7 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 11 Mar 2020 19:07:44 +0100 Subject: [PATCH 051/169] fixes #92333 --- .../browser/accessibilityHelp/accessibilityHelp.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts index 57912317852..274ad8a2060 100644 --- a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts +++ b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts @@ -330,7 +330,11 @@ class ShowAccessibilityHelpAction extends EditorAction { kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyMod.Alt | KeyCode.F1, - weight: KeybindingWeight.EditorContrib + weight: KeybindingWeight.EditorContrib, + linux: { + primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F1, + secondary: [KeyMod.Alt | KeyCode.F1] + } } }); } From 2f3fe9c9c36f935ec83113ef749d1a62ff7d07b6 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 11 Mar 2020 20:26:23 +0100 Subject: [PATCH 052/169] fix accept local action --- src/vs/platform/userDataSync/common/userDataSync.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 33ff263dca7..f588b56508c 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -356,11 +356,13 @@ export function toLocalBackupSyncResource(resource: SyncResource, ref?: string): } export function resolveSyncResource(resource: URI): { remote: boolean, resource: SyncResource, ref?: string } | null { - const remote = resource.authority === 'remote'; - const resourceKey: SyncResource = basename(dirname(resource)) as SyncResource; - const ref = basename(resource); - if (resourceKey && ref) { - return { remote, resource: resourceKey, ref: ref !== 'latest' ? ref : undefined }; + if (resource.scheme === USER_DATA_SYNC_SCHEME) { + const remote = resource.authority === 'remote'; + const resourceKey: SyncResource = basename(dirname(resource)) as SyncResource; + const ref = basename(resource); + if (resourceKey && ref) { + return { remote, resource: resourceKey, ref: ref !== 'latest' ? ref : undefined }; + } } return null; } From 1df23554b2e3d5f1efc6fbc76ee61d3f7f186c6d Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Wed, 11 Mar 2020 17:08:59 -0400 Subject: [PATCH 053/169] Uses view-level progress --- src/vs/workbench/contrib/timeline/browser/timelinePane.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 997797575f7..587bda235a2 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -403,7 +403,7 @@ export class TimelinePane extends ViewPane { private async handleRequest(request: TimelineRequest) { let timeline: Timeline | undefined; try { - timeline = await this.progressService.withProgress({ location: this.getProgressLocation() }, () => request.result); + timeline = await this.progressService.withProgress({ location: this.id }, () => request.result); } finally { this._pendingRequests.delete(request.source); From 6d10ff5bcfc039f4e1c373be62cdf4b16a4c7347 Mon Sep 17 00:00:00 2001 From: chgagnon Date: Wed, 11 Mar 2020 16:34:14 -0700 Subject: [PATCH 054/169] Alert input box messages anytime shown --- src/vs/base/browser/ui/inputbox/inputBox.ts | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index e5ccf0f4bf2..c50b6c92b23 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -373,18 +373,6 @@ export class InputBox extends Widget { const styles = this.stylesForType(this.message.type); this.element.style.border = styles.border ? `1px solid ${styles.border}` : ''; - // ARIA Support - let alertText: string; - if (message.type === MessageType.ERROR) { - alertText = nls.localize('alertErrorMessage', "Error: {0}", message.content); - } else if (message.type === MessageType.WARNING) { - alertText = nls.localize('alertWarningMessage', "Warning: {0}", message.content); - } else { - alertText = nls.localize('alertInfoMessage', "Info: {0}", message.content); - } - - aria.alert(alertText); - if (this.hasFocus() || force) { this._showMessage(); } @@ -485,6 +473,18 @@ export class InputBox extends Widget { layout: layout }); + // ARIA Support + let alertText: string; + if (this.message.type === MessageType.ERROR) { + alertText = nls.localize('alertErrorMessage', "Error: {0}", this.message.content); + } else if (this.message.type === MessageType.WARNING) { + alertText = nls.localize('alertWarningMessage', "Warning: {0}", this.message.content); + } else { + alertText = nls.localize('alertInfoMessage', "Info: {0}", this.message.content); + } + + aria.alert(alertText); + this.state = 'open'; } From 9490b3d8141d527365e5e3f81bfb4957cf200bbe Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 12 Mar 2020 08:34:05 +0100 Subject: [PATCH 055/169] quick access - gotoLine => gotoLineQuickAccess --- .../contrib/quickAccess/{gotoLine.ts => gotoLineQuickAccess.ts} | 0 .../browser/quickAccess/standaloneGotoLineQuickAccess.ts | 2 +- .../codeEditor/browser/quickaccess/gotoLineQuickAccess.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/vs/editor/contrib/quickAccess/{gotoLine.ts => gotoLineQuickAccess.ts} (100%) diff --git a/src/vs/editor/contrib/quickAccess/gotoLine.ts b/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts similarity index 100% rename from src/vs/editor/contrib/quickAccess/gotoLine.ts rename to src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts index 06495470f4d..49ce48811ae 100644 --- a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLine'; +import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLineQuickAccess'; import { Registry } from 'vs/platform/registry/common/platform'; import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts index 9ba095db5c7..22822d123e6 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts @@ -8,7 +8,7 @@ import { IKeyMods } from 'vs/platform/quickinput/common/quickInput'; import { IEditor } from 'vs/editor/common/editorCommon'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IRange } from 'vs/editor/common/core/range'; -import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLine'; +import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLineQuickAccess'; import { Registry } from 'vs/platform/registry/common/platform'; import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; From 545c16d40a5ae4a517c19bec241eed65d32fc5e3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 12 Mar 2020 08:34:59 +0100 Subject: [PATCH 056/169] commands - ensure alias and label are the same --- .../editor/contrib/codelens/codelensController.ts | 2 +- src/vs/workbench/browser/actions/layoutActions.ts | 4 ++-- .../contrib/customEditor/browser/commands.ts | 2 +- .../contrib/debug/browser/debug.contribution.ts | 2 +- .../extensions/browser/extensionsActions.ts | 14 +++++++------- .../contrib/quickopen/browser/commandsHandler.ts | 2 +- .../browser/searchEditor.contribution.ts | 2 +- .../electron-browser/extensionService.ts | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 84f1ac852c1..2f2721cf150 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -419,7 +419,7 @@ registerEditorAction(class ShowLensesInCurrentLine extends EditorAction { super({ id: 'codelens.showLensesInCurrentLine', precondition: EditorContextKeys.hasCodeLensProvider, - label: localize('showLensOnLine', "Show Code Lens Command For Current Line"), + label: localize('showLensOnLine', "Show Code Lens Commands For Current Line"), alias: 'Show Code Lens Commands For Current Line', }); } diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index cb77aab68f5..3f1403df47d 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -55,7 +55,7 @@ export class CloseSidebarAction extends Action { } } -registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseSidebarAction, CloseSidebarAction.ID, CloseSidebarAction.LABEL), 'View: Close Side Bar ', viewCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseSidebarAction, CloseSidebarAction.ID, CloseSidebarAction.LABEL), 'View: Close Side Bar', viewCategory); // --- Toggle Activity Bar @@ -237,7 +237,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { export class ToggleEditorVisibilityAction extends Action { static readonly ID = 'workbench.action.toggleEditorVisibility'; - static readonly LABEL = nls.localize('toggleEditor', "Toggle Editor Area"); + static readonly LABEL = nls.localize('toggleEditor', "Toggle Editor Area Visibility"); constructor( id: string, diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index 6d2b997a4c0..ee79d227e26 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -40,7 +40,7 @@ CommandsRegistry.registerCommand('_workbench.openWith', (accessor: ServicesAcces // #region Reopen With const REOPEN_WITH_COMMAND_ID = 'reOpenWith'; -const REOPEN_WITH_TITLE = { value: nls.localize('reopenWith.title', 'Reopen With...'), original: 'Reopen With' }; +const REOPEN_WITH_TITLE = { value: nls.localize('reopenWith.title', 'Reopen With...'), original: 'Reopen With...' }; KeybindingsRegistry.registerCommandAndKeybindingRule({ id: REOPEN_WITH_COMMAND_ID, diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 8ca4ca5a4c9..6f93ec9f731 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -118,7 +118,7 @@ registerCommands(); // register action to open viewlet const registry = Registry.as(WorkbenchActionRegistryExtensions.WorkbenchActions); registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDebugPanelAction, OpenDebugPanelAction.ID, OpenDebugPanelAction.LABEL, openPanelKb), 'View: Debug Console', nls.localize('view', "View")); -registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDebugViewletAction, OpenDebugViewletAction.ID, OpenDebugViewletAction.LABEL, openViewletKb), 'View: Show Debug', nls.localize('view', "View")); +registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDebugViewletAction, OpenDebugViewletAction.ID, OpenDebugViewletAction.LABEL, openViewletKb), 'View: Show Run and Debug', nls.localize('view', "View")); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugToolBar, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugContentProvider, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index ca8928e5557..ddaaea85318 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -15,7 +15,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { dispose, Disposable } from 'vs/base/common/lifecycle'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG, TOGGLE_IGNORE_EXTENSION_ACTION_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; -import { ExtensionsLabel, IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation, IExtensionsConfigContent, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; @@ -1939,7 +1939,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: ConfigureWorkspaceRecommendedExtensionsAction.ID, - title: { value: `${ExtensionsLabel}: ${ConfigureWorkspaceRecommendedExtensionsAction.LABEL}`, original: 'Configure Recommended Extensions (Workspace)' }, + title: { value: ConfigureWorkspaceRecommendedExtensionsAction.LABEL, original: 'Configure Recommended Extensions (Workspace)' }, category: localize('extensions', "Extensions") }, when: this.workspaceContextKey @@ -1951,7 +1951,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, - title: { value: `${ExtensionsLabel}: ${ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL}`, original: 'Configure Recommended Extensions (Workspace Folder)' }, + title: { value: ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL, original: 'Configure Recommended Extensions (Workspace Folder)' }, category: localize('extensions', "Extensions") }, when: this.workspaceFolderContextKey @@ -1965,7 +1965,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: AddToWorkspaceRecommendationsAction.ADD_ID, - title: { value: `${ExtensionsLabel}: ${AddToWorkspaceRecommendationsAction.ADD_LABEL}`, original: 'Add to Recommended Extensions (Workspace)' }, + title: { value: AddToWorkspaceRecommendationsAction.ADD_LABEL, original: 'Add to Recommended Extensions (Workspace)' }, category: localize('extensions', "Extensions") }, when: this.addToWorkspaceRecommendationsContextKey @@ -1979,7 +1979,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: AddToWorkspaceFolderRecommendationsAction.ADD_ID, - title: { value: `${ExtensionsLabel}: ${AddToWorkspaceFolderRecommendationsAction.ADD_LABEL}`, original: 'Extensions: Add to Recommended Extensions (Workspace Folder)' }, + title: { value: AddToWorkspaceFolderRecommendationsAction.ADD_LABEL, original: 'Extensions: Add to Recommended Extensions (Workspace Folder)' }, category: localize('extensions', "Extensions") }, when: this.addToWorkspaceFolderRecommendationsContextKey @@ -1993,7 +1993,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: AddToWorkspaceRecommendationsAction.IGNORE_ID, - title: { value: `${ExtensionsLabel}: ${AddToWorkspaceRecommendationsAction.IGNORE_LABEL}`, original: 'Extensions: Ignore Recommended Extension (Workspace)' }, + title: { value: AddToWorkspaceRecommendationsAction.IGNORE_LABEL, original: 'Extensions: Ignore Recommended Extension (Workspace)' }, category: localize('extensions', "Extensions") }, when: this.addToWorkspaceRecommendationsContextKey @@ -2007,7 +2007,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: AddToWorkspaceFolderRecommendationsAction.IGNORE_ID, - title: { value: `${ExtensionsLabel}: ${AddToWorkspaceFolderRecommendationsAction.IGNORE_LABEL}`, original: 'Extensions: Ignore Recommended Extension (Workspace Folder)' }, + title: { value: AddToWorkspaceFolderRecommendationsAction.IGNORE_LABEL, original: 'Extensions: Ignore Recommended Extension (Workspace Folder)' }, category: localize('extensions', "Extensions") }, when: this.addToWorkspaceFolderRecommendationsContextKey diff --git a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts index a41fb8930e9..98c39f8386c 100644 --- a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts @@ -213,7 +213,7 @@ class CommandPaletteEditorAction extends EditorAction { super({ id: ShowAllCommandsAction.ID, label: localize('showCommands.label', "Command Palette..."), - alias: 'Command Palette', + alias: 'Command Palette...', precondition: EditorContextKeys.editorSimpleInput.toNegated(), contextMenuOpts: { group: 'z_commands', diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index e74e02bdbab..d9de089e8c4 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -207,7 +207,7 @@ registry.registerWorkbenchAction( registry.registerWorkbenchAction(SyncActionDescriptor.create(RerunSearchEditorSearchAction, RerunSearchEditorSearchAction.ID, RerunSearchEditorSearchAction.LABEL, { mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_R } }, ContextKeyExpr.and(SearchEditorConstants.InSearchEditor)), - 'Search Editor: Rerun', category); + 'Search Editor: Search Again', category); //#endregion diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 282f9307e70..74120ad40c3 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -666,7 +666,7 @@ registerSingleton(IExtensionService, ExtensionService); class RestartExtensionHostAction extends Action { public static readonly ID = 'workbench.action.restartExtensionHost'; - public static readonly LABEL = nls.localize('restartExtensionHost', "Developer: Restart Extension Host"); + public static readonly LABEL = nls.localize('restartExtensionHost', "Restart Extension Host"); constructor( id: string, @@ -682,4 +682,4 @@ class RestartExtensionHostAction extends Action { } const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(SyncActionDescriptor.create(RestartExtensionHostAction, RestartExtensionHostAction.ID, RestartExtensionHostAction.LABEL), 'Developer: Restart Extension Host'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RestartExtensionHostAction, RestartExtensionHostAction.ID, RestartExtensionHostAction.LABEL), 'Developer: Restart Extension Host', nls.localize('developer', "Developer")); From b62045619a23f134a515b72294b2eb98ca5f1c74 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 12 Mar 2020 08:39:44 +0100 Subject: [PATCH 057/169] quick access - basic commands picker --- src/vs/editor/common/standaloneStrings.ts | 1 + .../quickAccess/commandsQuickAccess.ts | 59 ++++++++++ src/vs/editor/editor.main.ts | 1 + .../standaloneCommandsQuickAccess.ts | 34 ++++++ .../quickinput/browser/commandsQuickAccess.ts | 65 +++++++++++ .../platform/quickinput/common/quickAccess.ts | 10 +- .../browser/extensionsQuickAccess.ts | 3 +- .../browser/commandsQuickAccess.ts | 108 ++++++++++++++++++ .../browser/quickAccess.contribution.ts | 8 ++ .../contrib/tasks/browser/tasksQuickAccess.ts | 3 +- 10 files changed, 288 insertions(+), 4 deletions(-) create mode 100644 src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts create mode 100644 src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts create mode 100644 src/vs/platform/quickinput/browser/commandsQuickAccess.ts create mode 100644 src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts diff --git a/src/vs/editor/common/standaloneStrings.ts b/src/vs/editor/common/standaloneStrings.ts index 164708bd894..872aa8aafb4 100644 --- a/src/vs/editor/common/standaloneStrings.ts +++ b/src/vs/editor/common/standaloneStrings.ts @@ -54,6 +54,7 @@ export namespace QuickCommandNLS { export const ariaLabelEntry = nls.localize('ariaLabelEntry', "{0}, commands"); export const quickCommandActionInput = nls.localize('quickCommandActionInput', "Type the name of an action you want to execute"); export const quickCommandActionLabel = nls.localize('quickCommandActionLabel', "Command Palette"); + export const quickCommandHelp = nls.localize('quickCommandActionHelp', "Show And Run Commands"); } export namespace QuickOutlineNLS { diff --git a/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts b/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts new file mode 100644 index 00000000000..e0c1eb7c5fd --- /dev/null +++ b/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AbstractCommandsQuickAccessProvider, ICommandQuickPick } from 'vs/platform/quickinput/browser/commandsQuickAccess'; +import { IEditor } from 'vs/editor/common/editorCommon'; + +export interface IEditorCommandsQuickAccessOptions { + alias: { + enable: boolean; + verify: boolean; + } +} + +export abstract class AbstractEditorCommandsQuickAccessProvider extends AbstractCommandsQuickAccessProvider { + + constructor(private options?: IEditorCommandsQuickAccessOptions) { + super(); + } + + /** + * Subclasses to provide the current active editor control. + */ + abstract activeTextEditorControl: IEditor | undefined; + + protected getCodeEditorCommandPicks(): ICommandQuickPick[] { + const activeTextEditorControl = this.activeTextEditorControl; + if (!activeTextEditorControl) { + return []; + } + + const editorCommandPicks: ICommandQuickPick[] = []; + for (const editorAction of activeTextEditorControl.getSupportedActions()) { + const label = editorAction.label || editorAction.id; + const alias: string | undefined = this.verifyAlias(editorAction.alias, label, editorAction.id); + + editorCommandPicks.push({ + label, + commandId: editorAction.id, + detail: alias + }); + } + + return editorCommandPicks; + } + + protected verifyAlias(alias: string | undefined, label: string, commandId: string): string | undefined { + if (this.options?.alias.verify && alias && alias !== label) { + console.warn(`Command alias '${label}' and label '${alias}' differ (${commandId})`); + } + + if (!this.options?.alias.enable) { + return undefined; // we unset the alias if it is not enabled + } + + return alias; + } +} diff --git a/src/vs/editor/editor.main.ts b/src/vs/editor/editor.main.ts index 875233ba7a8..302ff27f8a2 100644 --- a/src/vs/editor/editor.main.ts +++ b/src/vs/editor/editor.main.ts @@ -12,6 +12,7 @@ import 'vs/editor/standalone/browser/quickOpen/quickCommand'; import 'vs/editor/standalone/browser/quickOpen/quickOutline'; import 'vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess'; import 'vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess'; +import 'vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess'; import 'vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch'; import 'vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast'; diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts new file mode 100644 index 00000000000..082cc1faa9f --- /dev/null +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { QuickCommandNLS } from 'vs/editor/common/standaloneStrings'; +import { ICommandQuickPick } from 'vs/platform/quickinput/browser/commandsQuickAccess'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { AbstractEditorCommandsQuickAccessProvider } from 'vs/editor/contrib/quickAccess/commandsQuickAccess'; +import { IEditor } from 'vs/editor/common/editorCommon'; +import { withNullAsUndefined } from 'vs/base/common/types'; + +export class StandaloneCommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { + + get activeTextEditorControl(): IEditor | undefined { return withNullAsUndefined(this.codeEditorService.getFocusedCodeEditor()); } + + constructor( + @ICodeEditorService private readonly codeEditorService: ICodeEditorService + ) { + super(); + } + + protected async getCommandPicks(): Promise> { + return this.getCodeEditorCommandPicks(); + } +} + +Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: StandaloneCommandsQuickAccessProvider, + prefix: StandaloneCommandsQuickAccessProvider.PREFIX, + helpEntries: [{ description: QuickCommandNLS.quickCommandHelp, needsEditor: true }] +}); diff --git a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts new file mode 100644 index 00000000000..474969c304d --- /dev/null +++ b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { PickerQuickAccessProvider, IPickerQuickAccessItem } from 'vs/platform/quickinput/common/quickAccess'; +import { distinct } from 'vs/base/common/arrays'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { or, matchesPrefix, matchesWords, matchesContiguousSubString } from 'vs/base/common/filters'; +import { withNullAsUndefined } from 'vs/base/common/types'; + +export interface ICommandQuickPick extends IPickerQuickAccessItem { + commandId: string; +} + +export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = '>'; + + private static WORD_FILTER = or(matchesPrefix, matchesWords, matchesContiguousSubString); + + constructor() { + super(AbstractCommandsQuickAccessProvider.PREFIX); + } + + protected async getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise> { + + // Ask subclass for all command picks + const allCommandPicks = await this.getCommandPicks(disposables, token); + + // Filter + const filteredCommandPicks: ICommandQuickPick[] = []; + for (const commandPick of allCommandPicks) { + const labelHighlights = withNullAsUndefined(AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.label)); + const detailHighlights = commandPick.detail ? withNullAsUndefined(AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.detail)) : undefined; + + if (labelHighlights || detailHighlights) { + commandPick.highlights = { label: labelHighlights, detail: detailHighlights }; + filteredCommandPicks.push(commandPick); + } + } + + // Remove duplicates + const distinctCommandPicks = distinct(filteredCommandPicks, pick => `${pick.label}${pick.commandId}`); + + // Add description to commands that have duplicate labels + const mapLabelToCommand = new Map(); + for (const commandPick of distinctCommandPicks) { + const existingCommandForLabel = mapLabelToCommand.get(commandPick.label); + if (existingCommandForLabel) { + commandPick.description = commandPick.commandId; + existingCommandForLabel.description = existingCommandForLabel.commandId; + } else { + mapLabelToCommand.set(commandPick.label, commandPick); + } + } + + return distinctCommandPicks; + } + + protected abstract getCommandPicks(disposables: DisposableStore, token: CancellationToken): Promise>; +} + diff --git a/src/vs/platform/quickinput/common/quickAccess.ts b/src/vs/platform/quickinput/common/quickAccess.ts index dab6c6e05a4..116086d3e4e 100644 --- a/src/vs/platform/quickinput/common/quickAccess.ts +++ b/src/vs/platform/quickinput/common/quickAccess.ts @@ -205,7 +205,7 @@ export abstract class PickerQuickAccessProvider | Promise>; + protected abstract getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Array | Promise>; } //#endregion diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts b/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts index 9af42ab31ed..1086a4fb0e8 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts @@ -12,6 +12,7 @@ import { VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/e import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ILogService } from 'vs/platform/log/common/log'; +import { DisposableStore } from 'vs/base/common/lifecycle'; export class InstallExtensionQuickAccessProvider extends PickerQuickAccessProvider { @@ -27,7 +28,7 @@ export class InstallExtensionQuickAccessProvider extends PickerQuickAccessProvid super(InstallExtensionQuickAccessProvider.PREFIX); } - protected getPicks(filter: string, token: CancellationToken): Array | Promise> { + protected getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Array | Promise> { // Nothing typed if (!filter) { diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts new file mode 100644 index 00000000000..1d3490d7228 --- /dev/null +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -0,0 +1,108 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { ICommandQuickPick } from 'vs/platform/quickinput/browser/commandsQuickAccess'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { timeout } from 'vs/base/common/async'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { DisposableStore, toDisposable, dispose } from 'vs/base/common/lifecycle'; +import { AbstractEditorCommandsQuickAccessProvider } from 'vs/editor/contrib/quickAccess/commandsQuickAccess'; +import { IEditor } from 'vs/editor/common/editorCommon'; +import { Language } from 'vs/base/common/platform'; + +export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { + + // If extensions are not yet registered, we wait for a little moment to give them + // a chance to register so that the complete set of commands shows up as result + // We do not want to delay functionality beyond that time though to keep the commands + // functional. + private readonly extensionRegistrationRace = Promise.race([ + timeout(800), + this.extensionService.whenInstalledExtensionsRegistered() + ]); + + get activeTextEditorControl(): IEditor | undefined { return this.editorService.activeTextEditorControl; } + + constructor( + @IEditorService private readonly editorService: IEditorService, + @IMenuService private readonly menuService: IMenuService, + @IExtensionService private readonly extensionService: IExtensionService, + @IEnvironmentService readonly environmentService: IEnvironmentService + ) { + super({ + alias: { + + // Show alias only when not running in english + enable: !Language.isDefaultVariant(), + + // Print a warning if the alias does not match the english label + verify: !environmentService.isBuilt && Language.isDefaultVariant(), + } + }); + } + + protected async getCommandPicks(disposables: DisposableStore, token: CancellationToken): Promise> { + + // wait for extensions registration or 800ms once + await this.extensionRegistrationRace; + + if (token.isCancellationRequested) { + return []; + } + + return [ + ...this.getCodeEditorCommandPicks(), + ...this.getGlobalCommandPicks(disposables) + ]; + } + + private getGlobalCommandPicks(disposables: DisposableStore): ICommandQuickPick[] { + const globalCommandPicks: ICommandQuickPick[] = []; + + const globalCommandsMenu = this.editorService.invokeWithinEditorContext(accessor => + this.menuService.createMenu(MenuId.CommandPalette, accessor.get(IContextKeyService)) + ); + + const globalCommandsMenuActions = globalCommandsMenu.getActions() + .reduce((r, [, actions]) => [...r, ...actions], >[]) + .filter(action => action instanceof MenuItemAction) as MenuItemAction[]; + + for (const action of globalCommandsMenuActions) { + + // Label + let label = (typeof action.item.title === 'string' ? action.item.title : action.item.title.value) || action.item.id; + + // Category + const category = typeof action.item.category === 'string' ? action.item.category : action.item.category?.value; + if (category) { + label = localize('commandWithCategory', "{0}: {1}", category, label); + } + + // Alias + const aliasTitle = typeof action.item.title !== 'string' ? action.item.title.original : undefined; + const aliasCategory = (category && action.item.category && typeof action.item.category !== 'string') ? action.item.category.original : undefined; + let alias = this.verifyAlias((aliasTitle && category) ? + aliasCategory ? `${aliasCategory}: ${aliasTitle}` : `${category}: ${aliasTitle}` : + aliasTitle, label, action.item.id); + + globalCommandPicks.push({ + label, + commandId: action.item.id, + detail: alias + }); + } + + // Cleanup + globalCommandsMenu.dispose(); + disposables.add(toDisposable(() => dispose(globalCommandsMenuActions))); + + return globalCommandPicks; + } +} diff --git a/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts b/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts index 39fe3c5baf3..05301dae7f3 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts @@ -10,6 +10,7 @@ import { HelpQuickAccessProvider } from 'vs/platform/quickinput/browser/helpQuic import { ViewQuickAccessProvider } from 'vs/workbench/contrib/quickaccess/browser/viewQuickAccess'; import { QUICK_ACCESS_COMMAND_ID } from 'vs/workbench/contrib/quickaccess/browser/quickAccessCommands'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { CommandsQuickAccessProvider } from 'vs/workbench/contrib/quickaccess/browser/commandsQuickAccess'; const registry = Registry.as(Extensions.Quickaccess); @@ -34,6 +35,13 @@ registry.registerQuickAccessProvider({ helpEntries: [{ description: localize('viewQuickAccess', "Open View"), needsEditor: false }] }); +registry.registerQuickAccessProvider({ + ctor: CommandsQuickAccessProvider, + prefix: CommandsQuickAccessProvider.PREFIX, + placeholder: localize('commandsQuickAccessPlaceholder', "Type the name of a command to run."), + helpEntries: [{ description: localize('commandsQuickAccess', "Show and Run Commands"), needsEditor: false }] +}); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: QUICK_ACCESS_COMMAND_ID, title: { diff --git a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts index 949d9a2c6a4..7bf8ca9551d 100644 --- a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts +++ b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts @@ -12,6 +12,7 @@ import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; import { CustomTask, ContributedTask } from 'vs/workbench/contrib/tasks/common/tasks'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IStringDictionary } from 'vs/base/common/collections'; +import { DisposableStore } from 'vs/base/common/lifecycle'; export class TasksQuickAccessProvider extends PickerQuickAccessProvider { @@ -28,7 +29,7 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider> { + protected async getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise> { // always await extensions await this.activationPromise; From 3550aa8f9ab02ca3d337ad9b22fabb7daab20ccb Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 12 Mar 2020 01:38:52 -0700 Subject: [PATCH 058/169] Create copycat.yml --- .github/workflows/copycat.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/copycat.yml diff --git a/.github/workflows/copycat.yml b/.github/workflows/copycat.yml new file mode 100644 index 00000000000..6d0cc8ead46 --- /dev/null +++ b/.github/workflows/copycat.yml @@ -0,0 +1,20 @@ +name: CopyCat +on: + issues: + types: [created] + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'JacksonKearl/vscode-triage-github-actions' + ref: master + - name: Run CopyCat + uses: ./copycat + with: + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + owner: JacksonKearl + repo: testissues From 438c3b6e42e559feec0c56d58c5c3d6378e089f9 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 12 Mar 2020 01:43:08 -0700 Subject: [PATCH 059/169] Update copycat.yml --- .github/workflows/copycat.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/copycat.yml b/.github/workflows/copycat.yml index 6d0cc8ead46..fdd1046eeca 100644 --- a/.github/workflows/copycat.yml +++ b/.github/workflows/copycat.yml @@ -1,7 +1,7 @@ name: CopyCat on: issues: - types: [created] + types: [opened] jobs: main: From da3a711f356e133162085d580c7d8b14f290cad8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 12 Mar 2020 10:13:09 +0100 Subject: [PATCH 060/169] quick access - commands with MRU history and keybindings --- .../quickinput/browser/media/quickInput.css | 9 + .../quickinput/browser/quickInputList.ts | 14 ++ .../parts/quickinput/common/quickInput.ts | 1 + .../quickAccess/commandsQuickAccess.ts | 18 +- .../standaloneCommandsQuickAccess.ts | 14 +- .../quickinput/browser/commandsQuickAccess.ts | 237 +++++++++++++++++- .../browser/commandsQuickAccess.ts | 16 +- .../browser/quickAccess.contribution.ts | 10 +- .../browser/quickAccessCommands.ts | 6 +- 9 files changed, 309 insertions(+), 16 deletions(-) diff --git a/src/vs/base/parts/quickinput/browser/media/quickInput.css b/src/vs/base/parts/quickinput/browser/media/quickInput.css index 015db56978b..64e468d2017 100644 --- a/src/vs/base/parts/quickinput/browser/media/quickInput.css +++ b/src/vs/base/parts/quickinput/browser/media/quickInput.css @@ -186,6 +186,11 @@ align-items: center; } +.quick-input-list .quick-input-list-rows > .quick-input-list-row .monaco-icon-label, +.quick-input-list .quick-input-list-rows > .quick-input-list-row .monaco-icon-label .monaco-icon-label-container > .monaco-icon-name-container { + flex: 1; /* make sure the icon label grows within the row */ +} + .quick-input-list .quick-input-list-rows > .quick-input-list-row .codicon { vertical-align: sub; } @@ -194,6 +199,10 @@ opacity: 1; } +.quick-input-list-entry.quick-input-list-separator-label .quick-input-list-entry-keybinding { + margin-right: 8px; /* separate from the separator label if any */ +} + .quick-input-list .quick-input-list-label-meta { opacity: 0.7; line-height: normal; diff --git a/src/vs/base/parts/quickinput/browser/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts index cb80c1efd2d..c132351b323 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputList.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts @@ -26,6 +26,7 @@ import { getIconClass } from 'vs/base/parts/quickinput/browser/quickInputUtils'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IQuickInputOptions } from 'vs/base/parts/quickinput/browser/quickInput'; import { IListOptions, List, IListStyles, IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; const $ = dom.$; @@ -79,6 +80,7 @@ interface IListElementTemplateData { entry: HTMLDivElement; checkbox: HTMLInputElement; label: IconLabel; + keybinding: KeybindingLabel; detail: HighlightedLabel; separator: HTMLDivElement; actionBar: ActionBar; @@ -118,6 +120,10 @@ class ListElementRenderer implements IListRenderer> { diff --git a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts index 474969c304d..5c39c4b1051 100644 --- a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts @@ -3,25 +3,49 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { PickerQuickAccessProvider, IPickerQuickAccessItem } from 'vs/platform/quickinput/common/quickAccess'; import { distinct } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { or, matchesPrefix, matchesWords, matchesContiguousSubString } from 'vs/base/common/filters'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { LRUCache } from 'vs/base/common/map'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { isPromiseCanceledError } from 'vs/base/common/errors'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { isFirefox } from 'vs/base/browser/browser'; +import { timeout } from 'vs/base/common/async'; export interface ICommandQuickPick extends IPickerQuickAccessItem { commandId: string; } -export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAccessProvider { +export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAccessProvider implements IDisposable { static PREFIX = '>'; private static WORD_FILTER = or(matchesPrefix, matchesWords, matchesContiguousSubString); - constructor() { + private readonly disposables = new DisposableStore(); + + private readonly commandsHistory = this.disposables.add(this.instantiationService.createInstance(CommandsHistory)); + + constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IKeybindingService private readonly keybindingService: IKeybindingService, + @ICommandService private readonly commandService: ICommandService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @INotificationService private readonly notificationService: INotificationService + ) { super(AbstractCommandsQuickAccessProvider.PREFIX); } @@ -57,9 +81,214 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc } } - return distinctCommandPicks; + // Sort by MRU order and fallback to name otherwise + distinctCommandPicks.sort((commandPickA, commandPickB) => { + const commandACounter = this.commandsHistory.peek(commandPickA.commandId); + const commandBCounter = this.commandsHistory.peek(commandPickB.commandId); + + if (commandACounter && commandBCounter) { + return commandACounter > commandBCounter ? -1 : 1; // use more recently used command before older + } + + if (commandACounter) { + return -1; // first command was used, so it wins over the non used one + } + + if (commandBCounter) { + return 1; // other command was used so it wins over the command + } + + // both commands were never used, so we sort by name + return commandPickA.label.localeCompare(commandPickB.label); + }); + + const commandPicks: Array = []; + + let addSeparator = false; + for (let i = 0; i < distinctCommandPicks.length; i++) { + const commandPick = distinctCommandPicks[i]; + const keybinding = this.keybindingService.lookupKeybinding(commandPick.commandId); + const ariaLabel = keybinding ? + localize('commandPickAriaLabelWithKeybinding', "{0}, {1}, commands picker", commandPick.label, keybinding.getAriaLabel()) : + localize('commandPickAriaLabel', "{0}, commands picker", commandPick.label); + + // Separator: recently used + if (i === 0 && this.commandsHistory.peek(commandPick.commandId)) { + commandPicks.push({ type: 'separator', label: localize('recentlyUsed', "recently used") }); + addSeparator = true; + } + + // Separator: other commands + if (i !== 0 && addSeparator && !this.commandsHistory.peek(commandPick.commandId)) { + commandPicks.push({ type: 'separator', label: localize('morecCommands', "other commands") }); + addSeparator = false; // only once + } + + // Command + commandPicks.push({ + ...commandPick, + ariaLabel, + keybinding, + accept: async () => { + + // Add to history + this.commandsHistory.push(commandPick.commandId); + + if (!isFirefox) { + // Use a timeout to give the quick open widget a chance to close itself first + // Firefox: since the browser is quite picky for certain commands, we do not + // use a timeout (https://github.com/microsoft/vscode/issues/83288) + await timeout(50); + } + + // Telementry + this.telemetryService.publicLog2('workbenchActionExecuted', { + id: commandPick.commandId, + from: 'quick open' + }); + + // Run + try { + await this.commandService.executeCommand(commandPick.commandId); + } catch (error) { + if (!isPromiseCanceledError(error)) { + this.notificationService.error(localize('canNotRun', "Command '{0}' resulted in an error ({1})", commandPick.label, toErrorMessage(error))); + } + } + } + }); + } + + return commandPicks; } protected abstract getCommandPicks(disposables: DisposableStore, token: CancellationToken): Promise>; + + dispose(): void { + this.disposables.dispose(); + } +} + +interface ISerializedCommandHistory { + usesLRU?: boolean; + entries: { key: string; value: number }[]; +} + +interface ICommandsQuickAccessConfiguration { + workbench: { + commandPalette: { + history: number; + preserveInput: boolean; + } + }; +} + +class CommandsHistory extends Disposable { + + static readonly DEFAULT_COMMANDS_HISTORY_LENGTH = 50; + + private static readonly PREF_KEY_CACHE = 'commandPalette.mru.cache'; + private static readonly PREF_KEY_COUNTER = 'commandPalette.mru.counter'; + + private static cache: LRUCache | undefined; + private static counter = 1; + + private configuredCommandsHistoryLength = 0; + + constructor( + @IStorageService private readonly storageService: IStorageService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + super(); + + this.updateConfiguration(); + this.load(); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.configurationService.onDidChangeConfiguration(() => this.updateConfiguration())); + } + + private updateConfiguration(): void { + this.configuredCommandsHistoryLength = CommandsHistory.getConfiguredCommandHistoryLength(this.configurationService); + + if (CommandsHistory.cache && CommandsHistory.cache.limit !== this.configuredCommandsHistoryLength) { + CommandsHistory.cache.limit = this.configuredCommandsHistoryLength; + + CommandsHistory.saveState(this.storageService); + } + } + + private load(): void { + const raw = this.storageService.get(CommandsHistory.PREF_KEY_CACHE, StorageScope.GLOBAL); + let serializedCache: ISerializedCommandHistory | undefined; + if (raw) { + try { + serializedCache = JSON.parse(raw); + } catch (error) { + // invalid data + } + } + + const cache = CommandsHistory.cache = new LRUCache(this.configuredCommandsHistoryLength, 1); + if (serializedCache) { + let entries: { key: string; value: number }[]; + if (serializedCache.usesLRU) { + entries = serializedCache.entries; + } else { + entries = serializedCache.entries.sort((a, b) => a.value - b.value); + } + entries.forEach(entry => cache.set(entry.key, entry.value)); + } + + CommandsHistory.counter = this.storageService.getNumber(CommandsHistory.PREF_KEY_COUNTER, StorageScope.GLOBAL, CommandsHistory.counter); + } + + push(commandId: string): void { + if (!CommandsHistory.cache) { + return; + } + + CommandsHistory.cache.set(commandId, CommandsHistory.counter++); // set counter to command + + CommandsHistory.saveState(this.storageService); + } + + peek(commandId: string): number | undefined { + return CommandsHistory.cache?.peek(commandId); + } + + static saveState(storageService: IStorageService): void { + if (!CommandsHistory.cache) { + return; + } + + const serializedCache: ISerializedCommandHistory = { usesLRU: true, entries: [] }; + CommandsHistory.cache.forEach((value, key) => serializedCache.entries.push({ key, value })); + + storageService.store(CommandsHistory.PREF_KEY_CACHE, JSON.stringify(serializedCache), StorageScope.GLOBAL); + storageService.store(CommandsHistory.PREF_KEY_COUNTER, CommandsHistory.counter, StorageScope.GLOBAL); + } + + static getConfiguredCommandHistoryLength(configurationService: IConfigurationService): number { + const config = configurationService.getValue(); + + const configuredCommandHistoryLength = config.workbench?.commandPalette?.history; + if (typeof configuredCommandHistoryLength === 'number') { + return configuredCommandHistoryLength; + } + + return CommandsHistory.DEFAULT_COMMANDS_HISTORY_LENGTH; + } + + static clearHistory(configurationService: IConfigurationService, storageService: IStorageService): void { + const commandHistoryLength = CommandsHistory.getConfiguredCommandHistoryLength(configurationService); + CommandsHistory.cache = new LRUCache(commandHistoryLength); + CommandsHistory.counter = 1; + + CommandsHistory.saveState(storageService); + } } diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index 1d3490d7228..423be83e12c 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -16,6 +16,11 @@ import { DisposableStore, toDisposable, dispose } from 'vs/base/common/lifecycle import { AbstractEditorCommandsQuickAccessProvider } from 'vs/editor/contrib/quickAccess/commandsQuickAccess'; import { IEditor } from 'vs/editor/common/editorCommon'; import { Language } from 'vs/base/common/platform'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { @@ -34,9 +39,14 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce @IEditorService private readonly editorService: IEditorService, @IMenuService private readonly menuService: IMenuService, @IExtensionService private readonly extensionService: IExtensionService, - @IEnvironmentService readonly environmentService: IEnvironmentService + @IEnvironmentService environmentService: IEnvironmentService, + @IInstantiationService instantiationService: IInstantiationService, + @IKeybindingService keybindingService: IKeybindingService, + @ICommandService commandService: ICommandService, + @ITelemetryService telemetryService: ITelemetryService, + @INotificationService notificationService: INotificationService ) { - super({ + super(instantiationService, keybindingService, commandService, telemetryService, notificationService, { alias: { // Show alias only when not running in english @@ -93,8 +103,8 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce aliasTitle, label, action.item.id); globalCommandPicks.push({ - label, commandId: action.item.id, + label, detail: alias }); } diff --git a/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts b/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts index 05301dae7f3..d8d4ecad23f 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts @@ -8,9 +8,10 @@ import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/ import { Registry } from 'vs/platform/registry/common/platform'; import { HelpQuickAccessProvider } from 'vs/platform/quickinput/browser/helpQuickAccess'; import { ViewQuickAccessProvider } from 'vs/workbench/contrib/quickaccess/browser/viewQuickAccess'; -import { QUICK_ACCESS_COMMAND_ID } from 'vs/workbench/contrib/quickaccess/browser/quickAccessCommands'; +import { QUICK_ACCESS_COMMAND_ID, quickAccessCommand } from 'vs/workbench/contrib/quickaccess/browser/quickAccessCommands'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { CommandsQuickAccessProvider } from 'vs/workbench/contrib/quickaccess/browser/commandsQuickAccess'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; const registry = Registry.as(Extensions.Quickaccess); @@ -50,3 +51,10 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { category: localize('quickAccess', "Quick Access") } }); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: QUICK_ACCESS_COMMAND_ID, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + handler: quickAccessCommand.handler +}); diff --git a/src/vs/workbench/contrib/quickaccess/browser/quickAccessCommands.ts b/src/vs/workbench/contrib/quickaccess/browser/quickAccessCommands.ts index 23b2521b043..8761cc73f2c 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/quickAccessCommands.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/quickAccessCommands.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { ICommand } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; export const QUICK_ACCESS_COMMAND_ID = 'workbench.action.openQuickAccess'; -CommandsRegistry.registerCommand({ +export const quickAccessCommand: ICommand = { id: QUICK_ACCESS_COMMAND_ID, handler: async function (accessor: ServicesAccessor, prefix: string | null = null) { const quickInputService = accessor.get(IQuickInputService); @@ -25,4 +25,4 @@ CommandsRegistry.registerCommand({ } }] } -}); +}; From b5a2ab1e1093fd35c849f2096de1f8b85582b0e8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 12 Mar 2020 10:28:25 +0100 Subject: [PATCH 061/169] quick access - allow to search by command alias but not show it --- .../quickAccess/commandsQuickAccess.ts | 34 ++++--------------- .../standaloneCommandsQuickAccess.ts | 2 +- .../quickinput/browser/commandsQuickAccess.ts | 17 ++++++++-- .../browser/commandsQuickAccess.ts | 23 ++++--------- 4 files changed, 28 insertions(+), 48 deletions(-) diff --git a/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts b/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts index 6ac3327d48a..2f84c46a581 100644 --- a/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AbstractCommandsQuickAccessProvider, ICommandQuickPick } from 'vs/platform/quickinput/browser/commandsQuickAccess'; +import { AbstractCommandsQuickAccessProvider, ICommandQuickPick, ICommandsQuickAccessOptions } from 'vs/platform/quickinput/browser/commandsQuickAccess'; import { IEditor } from 'vs/editor/common/editorCommon'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -11,24 +11,17 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ICommandService } from 'vs/platform/commands/common/commands'; -export interface IEditorCommandsQuickAccessOptions { - alias: { - enable: boolean; - verify: boolean; - } -} - export abstract class AbstractEditorCommandsQuickAccessProvider extends AbstractCommandsQuickAccessProvider { constructor( + options: ICommandsQuickAccessOptions, instantiationService: IInstantiationService, keybindingService: IKeybindingService, commandService: ICommandService, telemetryService: ITelemetryService, - notificationService: INotificationService, - private options?: IEditorCommandsQuickAccessOptions + notificationService: INotificationService ) { - super(instantiationService, keybindingService, commandService, telemetryService, notificationService); + super(options, instantiationService, keybindingService, commandService, telemetryService, notificationService); } /** @@ -44,28 +37,13 @@ export abstract class AbstractEditorCommandsQuickAccessProvider extends Abstract const editorCommandPicks: ICommandQuickPick[] = []; for (const editorAction of activeTextEditorControl.getSupportedActions()) { - const label = editorAction.label || editorAction.id; - const alias: string | undefined = this.verifyAlias(editorAction.alias, label, editorAction.id); - editorCommandPicks.push({ commandId: editorAction.id, - label, - detail: alias + commandAlias: editorAction.alias, + label: editorAction.label || editorAction.id, }); } return editorCommandPicks; } - - protected verifyAlias(alias: string | undefined, label: string, commandId: string): string | undefined { - if (this.options?.alias.verify && alias && alias !== label) { - console.warn(`Command alias '${label}' and label '${alias}' differ (${commandId})`); - } - - if (!this.options?.alias.enable) { - return undefined; // we unset the alias if it is not enabled - } - - return alias; - } } diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts index 5d941137b9c..6cc42fa3eb1 100644 --- a/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts @@ -29,7 +29,7 @@ export class StandaloneCommandsQuickAccessProvider extends AbstractEditorCommand @ITelemetryService telemetryService: ITelemetryService, @INotificationService notificationService: INotificationService ) { - super(instantiationService, keybindingService, commandService, telemetryService, notificationService); + super({ showAlias: false }, instantiationService, keybindingService, commandService, telemetryService, notificationService); } protected async getCommandPicks(): Promise> { diff --git a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts index 5c39c4b1051..3ef94e57b9f 100644 --- a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts @@ -27,6 +27,11 @@ import { timeout } from 'vs/base/common/async'; export interface ICommandQuickPick extends IPickerQuickAccessItem { commandId: string; + commandAlias: string | undefined; +} + +export interface ICommandsQuickAccessOptions { + showAlias: boolean; } export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAccessProvider implements IDisposable { @@ -40,6 +45,7 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc private readonly commandsHistory = this.disposables.add(this.instantiationService.createInstance(CommandsHistory)); constructor( + private options: ICommandsQuickAccessOptions, @IInstantiationService private readonly instantiationService: IInstantiationService, @IKeybindingService private readonly keybindingService: IKeybindingService, @ICommandService private readonly commandService: ICommandService, @@ -58,10 +64,14 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc const filteredCommandPicks: ICommandQuickPick[] = []; for (const commandPick of allCommandPicks) { const labelHighlights = withNullAsUndefined(AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.label)); - const detailHighlights = commandPick.detail ? withNullAsUndefined(AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.detail)) : undefined; + const aliasHighlights = commandPick.commandAlias ? withNullAsUndefined(AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.commandAlias)) : undefined; + + if (labelHighlights || aliasHighlights) { + commandPick.highlights = { + label: labelHighlights, + detail: this.options.showAlias ? aliasHighlights : undefined + }; - if (labelHighlights || detailHighlights) { - commandPick.highlights = { label: labelHighlights, detail: detailHighlights }; filteredCommandPicks.push(commandPick); } } @@ -128,6 +138,7 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc commandPicks.push({ ...commandPick, ariaLabel, + detail: this.options.showAlias ? commandPick.commandAlias : undefined, keybinding, accept: async () => { diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index 423be83e12c..ac4a1820889 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -46,16 +46,7 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce @ITelemetryService telemetryService: ITelemetryService, @INotificationService notificationService: INotificationService ) { - super(instantiationService, keybindingService, commandService, telemetryService, notificationService, { - alias: { - - // Show alias only when not running in english - enable: !Language.isDefaultVariant(), - - // Print a warning if the alias does not match the english label - verify: !environmentService.isBuilt && Language.isDefaultVariant(), - } - }); + super({ showAlias: !Language.isDefaultVariant() }, instantiationService, keybindingService, commandService, telemetryService, notificationService); } protected async getCommandPicks(disposables: DisposableStore, token: CancellationToken): Promise> { @@ -96,16 +87,16 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce } // Alias - const aliasTitle = typeof action.item.title !== 'string' ? action.item.title.original : undefined; + const aliasLabel = typeof action.item.title !== 'string' ? action.item.title.original : undefined; const aliasCategory = (category && action.item.category && typeof action.item.category !== 'string') ? action.item.category.original : undefined; - let alias = this.verifyAlias((aliasTitle && category) ? - aliasCategory ? `${aliasCategory}: ${aliasTitle}` : `${category}: ${aliasTitle}` : - aliasTitle, label, action.item.id); + const commandAlias = (aliasLabel && category) ? + aliasCategory ? `${aliasCategory}: ${aliasLabel}` : `${category}: ${aliasLabel}` : + aliasLabel; globalCommandPicks.push({ commandId: action.item.id, - label, - detail: alias + commandAlias, + label }); } From 7613e734337f788845733f623b12b8774b136cb8 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 12 Mar 2020 10:39:34 +0100 Subject: [PATCH 062/169] debug: watch always offer copy value #69768 --- .../contrib/debug/browser/watchExpressionsView.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 0dbd60fc37c..02cdac7f1f9 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -190,9 +190,7 @@ export class WatchExpressionsView extends ViewPane { this.debugService.getViewModel().setSelectedExpression(expression); return Promise.resolve(); })); - if (!expression.hasChildren) { - actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression.value, 'watch')); - } + actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression.value, 'watch')); actions.push(new Separator()); actions.push(new Action('debug.removeWatchExpression', nls.localize('removeWatchExpression', "Remove Expression"), undefined, true, () => { @@ -204,9 +202,7 @@ export class WatchExpressionsView extends ViewPane { actions.push(new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService)); if (element instanceof Variable) { const variable = element as Variable; - if (!variable.hasChildren) { - actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable, 'watch')); - } + actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable, 'watch')); actions.push(new Separator()); } actions.push(new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService)); From 630ad91cdbd5ab707aebeb2a9906dcbabb5a5d29 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 12 Mar 2020 10:46:44 +0100 Subject: [PATCH 063/169] #92038 Remove unused code --- .../contrib/output/browser/outputView.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index c7fbe8223f5..17d4417f0df 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -116,22 +116,6 @@ export class OutputViewPane extends ViewPane { this.editor.layout({ height, width }); } - getActions(): IAction[] { - if (!this.actions) { - this.actions = [ - // this._register(this.instantiationService.createInstance(SwitchOutputAction)), - // this._register(this.instantiationService.createInstance(ClearOutputAction, ClearOutputAction.ID, ClearOutputAction.LABEL)), - // this._register(this.instantiationService.createInstance(ToggleOrSetOutputScrollLockAction, ToggleOrSetOutputScrollLockAction.ID, ToggleOrSetOutputScrollLockAction.LABEL)), - // this._register(this.instantiationService.createInstance(OpenLogOutputFile)) - ]; - } - return [...super.getActions(), ...this.actions]; - } - - getSecondaryActions(): IAction[] { - return [...super.getSecondaryActions(), ...this.editor.getSecondaryActions()]; - } - getActionViewItem(action: IAction): IActionViewItem | undefined { if (action.id === 'workbench.output.action.switchBetweenOutputs') { return this.instantiationService.createInstance(SwitchOutputActionViewItem, action); From 034b7e63e2cd50ba18f0439d778b12f758e902d8 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 12 Mar 2020 11:29:13 +0100 Subject: [PATCH 064/169] Welcome views: add comment for translators to not translate the word "command" #92534 --- .../workbench/contrib/debug/browser/welcomeView.ts | 12 ++++++++---- .../contrib/files/browser/explorerViewlet.ts | 9 ++++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/welcomeView.ts b/src/vs/workbench/contrib/debug/browser/welcomeView.ts index 333c01000bd..7bd746a4b5b 100644 --- a/src/vs/workbench/contrib/debug/browser/welcomeView.ts +++ b/src/vs/workbench/contrib/debug/browser/welcomeView.ts @@ -88,22 +88,26 @@ export class WelcomeView extends ViewPane { const viewsRegistry = Registry.as(Extensions.ViewsRegistry); viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { - content: localize('openAFileWhichCanBeDebugged', "[Open a file](command:{0}) which can be debugged or run.", isMacintosh ? OpenFileFolderAction.ID : OpenFileAction.ID), + content: localize({ key: 'openAFileWhichCanBeDebugged', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + "[Open a file](command:{0}) which can be debugged or run.", isMacintosh ? OpenFileFolderAction.ID : OpenFileAction.ID), when: CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR.toNegated() }); let debugKeybindingLabel = ''; viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { - content: localize('runAndDebugAction', "[Run and Debug{0}](command:{1})", debugKeybindingLabel, StartAction.ID), + content: localize({ key: 'runAndDebugAction', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + "[Run and Debug{0}](command:{1})", debugKeybindingLabel, StartAction.ID), preconditions: [CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR] }); viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { - content: localize('customizeRunAndDebug', "To customize Run and Debug [create a launch.json file](command:{0}).", ConfigureAction.ID), + content: localize({ key: 'customizeRunAndDebug', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + "To customize Run and Debug [create a launch.json file](command:{0}).", ConfigureAction.ID), when: WorkbenchStateContext.notEqualsTo('empty') }); viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { - content: localize('customizeRunAndDebugOpenFolder', "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID), + content: localize({ key: 'customizeRunAndDebugOpenFolder', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID), when: WorkbenchStateContext.isEqualTo('empty') }); diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 333072e183f..1aea2d3f0cb 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -67,18 +67,21 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor const viewsRegistry = Registry.as(Extensions.ViewsRegistry); this._register(viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize('noWorkspaceHelp', "You have not yet added a folder to the workspace.\n[Add Folder](command:{0})", AddRootFolderAction.ID), + content: localize({ key: 'noWorkspaceHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + "You have not yet added a folder to the workspace.\n[Add Folder](command:{0})", AddRootFolderAction.ID), when: WorkbenchStateContext.isEqualTo('workspace') })); const commandId = isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID; this._register(viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize('remoteNoFolderHelp', "Connected to remote.\n[Open Folder](command:{0})", commandId), + content: localize({ key: 'remoteNoFolderHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + "Connected to remote.\n[Open Folder](command:{0})", commandId), when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.notEqualsTo(''), IsWebContext.toNegated()) })); this._register(viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize('noFolderHelp', "You have not yet opened a folder.\n[Open Folder](command:{0})", commandId), + content: localize({ key: 'noFolderHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + "You have not yet opened a folder.\n[Open Folder](command:{0})", commandId), when: ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext)) })); From 70c47762f2c1cbb9fb7dc846035c48290bdb3f65 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 12 Mar 2020 11:59:36 +0100 Subject: [PATCH 065/169] refactor file service based configuration --- .../configuration/browser/configuration.ts | 135 ++++++++---------- 1 file changed, 59 insertions(+), 76 deletions(-) diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index a334a34a15e..e7414fcbb97 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -17,7 +17,7 @@ import { IStoredWorkspaceFolder, IWorkspaceIdentifier } from 'vs/platform/worksp import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService'; import { WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { extname, join } from 'vs/base/common/path'; +import { join } from 'vs/base/common/path'; import { equals } from 'vs/base/common/objects'; import { Schemas } from 'vs/base/common/network'; import { IConfigurationModel } from 'vs/platform/configuration/common/configuration'; @@ -30,7 +30,7 @@ export class UserConfiguration extends Disposable { private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; - private readonly userConfiguration: MutableDisposable = this._register(new MutableDisposable()); + private readonly userConfiguration: MutableDisposable = this._register(new MutableDisposable()); private readonly reloadConfigurationScheduler: RunOnceScheduler; constructor( @@ -52,8 +52,10 @@ export class UserConfiguration extends Disposable { } async reload(): Promise { - if (!(this.userConfiguration.value instanceof FileServiceBasedConfigurationWithNames)) { - this.userConfiguration.value = new FileServiceBasedConfigurationWithNames(resources.dirname(this.userSettingsResource), [FOLDER_SETTINGS_NAME, TASKS_CONFIGURATION_KEY], this.scopes, this.fileService); + if (!(this.userConfiguration.value instanceof FileServiceBasedConfiguration)) { + const folder = resources.dirname(this.userSettingsResource); + const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY].map(name => ([name, resources.joinPath(folder, `${name}.json`)])); + this.userConfiguration.value = new FileServiceBasedConfiguration(folder.toString(), [this.userSettingsResource], standAloneConfigurationResources, this.scopes, this.fileService); this._register(this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule())); } return this.userConfiguration.value!.loadConfiguration(); @@ -64,24 +66,27 @@ export class UserConfiguration extends Disposable { } } -class FileServiceBasedConfigurationWithNames extends Disposable { +class FileServiceBasedConfiguration extends Disposable { + private readonly allResources: URI[]; private _folderSettingsModelParser: ConfigurationModelParser; private _standAloneConfigurations: ConfigurationModel[]; private _cache: ConfigurationModel; - protected readonly configurationResources: URI[]; - protected changeEventTriggerScheduler: RunOnceScheduler; - protected readonly _onDidChange: Emitter = this._register(new Emitter()); + private readonly changeEventTriggerScheduler: RunOnceScheduler; + private readonly _onDidChange: Emitter = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; - constructor(protected readonly configurationFolder: URI, - private readonly configurationNames: string[], + constructor( + name: string, + private readonly settingsResources: URI[], + private readonly standAloneConfigurationResources: [string, URI][], private readonly scopes: ConfigurationScope[] | undefined, - private fileService: IFileService) { + private fileService: IFileService + ) { super(); - this.configurationResources = this.configurationNames.map(name => resources.joinPath(this.configurationFolder, `${name}.json`)); - this._folderSettingsModelParser = new ConfigurationModelParser(this.configurationFolder.toString(), this.scopes); + this.allResources = [...this.settingsResources, ...this.standAloneConfigurationResources.map(([, resource]) => resource)]; + this._folderSettingsModelParser = new ConfigurationModelParser(name, this.scopes); this._standAloneConfigurations = []; this._cache = new ConfigurationModel(); @@ -90,30 +95,37 @@ class FileServiceBasedConfigurationWithNames extends Disposable { } async loadConfiguration(): Promise { - const configurationContents = await Promise.all(this.configurationResources.map(async resource => { - try { - const content = await this.fileService.readFile(resource); - return content.value.toString(); - } catch (error) { - if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { - errors.onUnexpectedError(error); + const resolveContents = async (resources: URI[]): Promise<(string | undefined)[]> => { + return Promise.all(resources.map(async resource => { + try { + const content = await this.fileService.readFile(resource); + return content.value.toString(); + } catch (error) { + if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { + errors.onUnexpectedError(error); + } } - } - return undefined; - })); + return undefined; + })); + }; + + const [settingsContents, standAloneConfigurationContents] = await Promise.all([ + resolveContents(this.settingsResources), + resolveContents(this.standAloneConfigurationResources.map(([, resource]) => resource)), + ]); // reset this._standAloneConfigurations = []; this._folderSettingsModelParser.parseContent(''); // parse - if (configurationContents[0]) { - this._folderSettingsModelParser.parseContent(configurationContents[0]); + if (settingsContents[0]) { + this._folderSettingsModelParser.parseContent(settingsContents[0]); } - for (let index = 1; index < configurationContents.length; index++) { - const contents = configurationContents[index]; + for (let index = 0; index < standAloneConfigurationContents.length; index++) { + const contents = standAloneConfigurationContents[index]; if (contents) { - const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.configurationResources[index].toString(), this.configurationNames[index]); + const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.standAloneConfigurationResources[index][1].toString(), this.standAloneConfigurationResources[index][0]); standAloneConfigurationModelParser.parseContent(contents); this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel); } @@ -139,49 +151,22 @@ class FileServiceBasedConfigurationWithNames extends Disposable { } protected async handleFileEvents(event: FileChangesEvent): Promise { - const events = event.changes; - let affectedByChanges = false; - - // Find changes that affect workspace configuration files - for (let i = 0, len = events.length; i < len; i++) { - const resource = events[i].resource; - const basename = resources.basename(resource); - const isJson = extname(basename) === '.json'; - const isConfigurationFolderDeleted = (events[i].type === FileChangeType.DELETED && resources.isEqual(resource, this.configurationFolder)); - - if (!isJson && !isConfigurationFolderDeleted) { - continue; // only JSON files or the actual settings folder + const isAffectedByChanges = (): boolean => { + // One of the resources has changed + if (this.allResources.some(resource => event.contains(resource))) { + return true; } - - const folderRelativePath = this.toFolderRelativePath(resource); - if (!folderRelativePath) { - continue; // event is not inside folder + // One of the resource's parent got deleted + if (this.allResources.some(resource => event.contains(resources.dirname(resource), FileChangeType.DELETED))) { + return true; } - - // Handle case where ".vscode" got deleted - if (isConfigurationFolderDeleted) { - affectedByChanges = true; - break; - } - - // only valid workspace config files - if (this.configurationResources.some(configurationResource => resources.isEqual(configurationResource, resource))) { - affectedByChanges = true; - break; - } - } - - if (affectedByChanges) { + return false; + }; + if (isAffectedByChanges()) { this.changeEventTriggerScheduler.schedule(); } } - private toFolderRelativePath(resource: URI): string | undefined { - if (resources.isEqualOrParent(resource, this.configurationFolder)) { - return resources.relativePath(this.configurationFolder, resource); - } - return undefined; - } } export class RemoteUserConfiguration extends Disposable { @@ -667,14 +652,6 @@ export interface IFolderConfiguration extends IDisposable { reprocess(): ConfigurationModel; } -class FileServiceBasedFolderConfiguration extends FileServiceBasedConfigurationWithNames implements IFolderConfiguration { - - constructor(configurationFolder: URI, workbenchState: WorkbenchState, fileService: IFileService) { - super(configurationFolder, [FOLDER_SETTINGS_NAME /*First one should be settings */, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY], WorkbenchState.WORKSPACE === workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES, fileService); - } - -} - class CachedFolderConfiguration extends Disposable implements IFolderConfiguration { private readonly _onDidChange: Emitter = this._register(new Emitter()); @@ -742,13 +719,13 @@ export class FolderConfiguration extends Disposable implements IFolderConfigurat this.configurationFolder = resources.joinPath(workspaceFolder.uri, configFolderRelativePath); this.folderConfiguration = this.cachedFolderConfiguration = new CachedFolderConfiguration(workspaceFolder.uri, configFolderRelativePath, configurationCache); if (workspaceFolder.uri.scheme === Schemas.file) { - this.folderConfiguration = new FileServiceBasedFolderConfiguration(this.configurationFolder, this.workbenchState, fileService); + this.folderConfiguration = this.createFileServiceBasedConfiguration(fileService); } else { whenProviderRegistered(workspaceFolder.uri, fileService) .then(() => { this.folderConfiguration.dispose(); this.folderConfigurationDisposable.dispose(); - this.folderConfiguration = new FileServiceBasedFolderConfiguration(this.configurationFolder, this.workbenchState, fileService); + this.folderConfiguration = this.createFileServiceBasedConfiguration(fileService); this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange())); this.onDidFolderConfigurationChange(); }); @@ -769,8 +746,14 @@ export class FolderConfiguration extends Disposable implements IFolderConfigurat this._onDidChange.fire(); } + private createFileServiceBasedConfiguration(fileService: IFileService) { + const settingsResources = [resources.joinPath(this.configurationFolder, `${FOLDER_SETTINGS_NAME}.json`)]; + const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY].map(name => ([name, resources.joinPath(this.configurationFolder, `${name}.json`)])); + return new FileServiceBasedConfiguration(this.configurationFolder.toString(), settingsResources, standAloneConfigurationResources, WorkbenchState.WORKSPACE === this.workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES, fileService); + } + private updateCache(): Promise { - if (this.configurationFolder.scheme !== Schemas.file && this.folderConfiguration instanceof FileServiceBasedFolderConfiguration) { + if (this.configurationFolder.scheme !== Schemas.file && this.folderConfiguration instanceof FileServiceBasedConfiguration) { return this.folderConfiguration.loadConfiguration() .then(configurationModel => this.cachedFolderConfiguration.updateConfiguration(configurationModel)); } From 1c34370df24b63464f913033b982987de40a16f6 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 12 Mar 2020 12:32:32 +0100 Subject: [PATCH 066/169] Action bar: escape should not lose focus fixes #91085 --- src/vs/base/browser/ui/actionbar/actionbar.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 523a242daaa..a226019523b 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -510,7 +510,7 @@ export class ActionBar extends Disposable implements IActionRunner { } else if (event.equals(nextKey)) { this.focusNext(); } else if (event.equals(KeyCode.Escape)) { - this.cancel(); + this._onDidCancel.fire(); } else if (this.isTriggerKeyEvent(event)) { // Staying out of the else branch even if not triggered if (this.options.triggerKeys && this.options.triggerKeys.keyDown) { @@ -813,14 +813,6 @@ export class ActionBar extends Disposable implements IActionRunner { } } - private cancel(): void { - if (document.activeElement instanceof HTMLElement) { - document.activeElement.blur(); // remove focus from focused action - } - - this._onDidCancel.fire(); - } - run(action: IAction, context?: unknown): Promise { return this._actionRunner.run(action, context); } From d83252c8a936eecca8bbbf98e8422a1cc156bf63 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 12 Mar 2020 12:54:41 +0100 Subject: [PATCH 067/169] fixes #92381 --- .../contrib/files/browser/explorerViewlet.ts | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 1aea2d3f0cb..c2f3364c260 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -64,27 +64,6 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor } private registerViews(): void { - const viewsRegistry = Registry.as(Extensions.ViewsRegistry); - - this._register(viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize({ key: 'noWorkspaceHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, - "You have not yet added a folder to the workspace.\n[Add Folder](command:{0})", AddRootFolderAction.ID), - when: WorkbenchStateContext.isEqualTo('workspace') - })); - - const commandId = isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID; - this._register(viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize({ key: 'remoteNoFolderHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, - "Connected to remote.\n[Open Folder](command:{0})", commandId), - when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.notEqualsTo(''), IsWebContext.toNegated()) - })); - - this._register(viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize({ key: 'noFolderHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, - "You have not yet opened a folder.\n[Open Folder](command:{0})", commandId), - when: ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext)) - })); - const viewDescriptors = viewsRegistry.getViews(VIEW_CONTAINER); let viewDescriptorsToRegister: IViewDescriptor[] = []; @@ -278,3 +257,23 @@ export const VIEW_CONTAINER: ViewContainer = Registry.as(Extensions.ViewsRegistry); +viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { + content: localize({ key: 'noWorkspaceHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + "You have not yet added a folder to the workspace.\n[Add Folder](command:{0})", AddRootFolderAction.ID), + when: WorkbenchStateContext.isEqualTo('workspace') +}); + +const commandId = isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID; +viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { + content: localize({ key: 'remoteNoFolderHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + "Connected to remote.\n[Open Folder](command:{0})", commandId), + when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.notEqualsTo(''), IsWebContext.toNegated()) +}); + +viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { + content: localize({ key: 'noFolderHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + "You have not yet opened a folder.\n[Open Folder](command:{0})", commandId), + when: ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext)) +}); From fd3e5b204bd837ce550cfcbfe264cf2eeb4f77ba Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 12 Mar 2020 15:03:19 +0100 Subject: [PATCH 068/169] fix #92489 --- .../quickinput/browser/media/quickInput.css | 26 ++++++++----------- .../quickinput/browser/quickInputList.ts | 5 ---- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/vs/base/parts/quickinput/browser/media/quickInput.css b/src/vs/base/parts/quickinput/browser/media/quickInput.css index 64e468d2017..45c205f92a5 100644 --- a/src/vs/base/parts/quickinput/browser/media/quickInput.css +++ b/src/vs/base/parts/quickinput/browser/media/quickInput.css @@ -199,8 +199,8 @@ opacity: 1; } -.quick-input-list-entry.quick-input-list-separator-label .quick-input-list-entry-keybinding { - margin-right: 8px; /* separate from the separator label if any */ +.quick-input-list .quick-input-list-entry .quick-input-list-entry-keybinding { + margin-right: 8px; /* separate from the separator label or scrollbar if any */ } .quick-input-list .quick-input-list-label-meta { @@ -214,17 +214,13 @@ font-weight: bold; } -.quick-input-list .quick-input-list-separator { - margin-right: 18px; -} - -.quick-input-list .quick-input-list-entry.has-actions:hover .quick-input-list-separator, -.quick-input-list .monaco-list-row.focused .quick-input-list-entry.has-actions .quick-input-list-separator { - margin-right: 0; +.quick-input-list .quick-input-list-entry .quick-input-list-separator { + margin-right: 8px; /* separate from keybindings or actions */ } .quick-input-list .quick-input-list-entry-action-bar { - display: none; + display: flex; + visibility: hidden; /* not using display: none here to not flicker too much */ flex: 0; overflow: visible; } @@ -240,16 +236,16 @@ margin-top: 1px; } -.quick-input-list .quick-input-list-entry-action-bar ul:first-child .action-label.codicon { - margin-left: 2px; +.quick-input-list .quick-input-list-entry-action-bar { + margin-right: 4px; /* separate from scrollbar */ } -.quick-input-list .quick-input-list-entry-action-bar ul:last-child .action-label.codicon { - margin-right: 8px; +.quick-input-list .quick-input-list-entry-action-bar .action-label.codicon { + margin-right: 4px; /* separate actions */ } .quick-input-list .quick-input-list-entry.always-visible-actions .quick-input-list-entry-action-bar, .quick-input-list .quick-input-list-entry:hover .quick-input-list-entry-action-bar, .quick-input-list .monaco-list-row.focused .quick-input-list-entry-action-bar { - display: flex; + visibility: visible; } diff --git a/src/vs/base/parts/quickinput/browser/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts index c132351b323..509ac6f03a3 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputList.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts @@ -174,11 +174,6 @@ class ListElementRenderer implements IListRenderer Date: Thu, 12 Mar 2020 15:52:00 +0100 Subject: [PATCH 069/169] set role application on the window #92483 --- src/vs/workbench/browser/layout.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 3fa67173cc3..d63412525a9 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -892,6 +892,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi ); this.container.prepend(workbenchGrid.element); + this.container.setAttribute('role', 'application'); this.workbenchGrid = workbenchGrid; [titleBar, editorPart, activityBar, panelPart, sideBar, statusBar].forEach((part: Part) => { From a18a9bf6059654565dd27c112f20f2cd0530b3b5 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 12 Mar 2020 16:07:55 +0100 Subject: [PATCH 070/169] move role application to workbench.ts #92483 --- src/vs/workbench/browser/layout.ts | 1 - src/vs/workbench/browser/workbench.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index d63412525a9..3fa67173cc3 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -892,7 +892,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi ); this.container.prepend(workbenchGrid.element); - this.container.setAttribute('role', 'application'); this.workbenchGrid = workbenchGrid; [titleBar, editorPart, activityBar, panelPart, sideBar, statusBar].forEach((part: Part) => { diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 263d17ccba4..743441a13ee 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -333,6 +333,7 @@ export class Workbench extends Layout { addClasses(this.container, ...workbenchClasses); addClass(document.body, platformClass); // used by our fonts + this.container.setAttribute('role', 'application'); if (isWeb) { addClass(document.body, 'web'); From d46fa87e075b0c2556688f33823d506e60c78c7b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 12 Mar 2020 16:10:45 +0100 Subject: [PATCH 071/169] fix #91701 --- .../environment/common/environment.ts | 1 - src/vs/platform/environment/node/argv.ts | 1 - .../backup/electron-browser/backupTracker.ts | 60 +++++++++---------- .../electron-browser/backupTracker.test.ts | 13 ++-- .../files/test/browser/editorAutoSave.test.ts | 5 +- .../files/test/browser/textFileEditor.test.ts | 5 +- .../browser/abstractFileDialogService.ts | 4 +- .../electron-browser/fileDialogService.ts | 12 +--- .../common/filesConfigurationService.ts | 6 +- .../test/browser/workbenchTestServices.ts | 2 +- 10 files changed, 46 insertions(+), 63 deletions(-) diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 123c84684da..aa4e77e37d7 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -39,7 +39,6 @@ export interface ParsedArgs { 'builtin-extensions-dir'?: string; extensionDevelopmentPath?: string[]; // // undefined or array of 1 or more local paths or URIs extensionTestsPath?: string; // either a local path or a URI - 'extension-development-confirm-save'?: boolean; 'inspect-extensions'?: string; 'inspect-brk-extensions'?: string; debugId?: string; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index e68e0647c32..5f2498445f7 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -80,7 +80,6 @@ export const OPTIONS: OptionDescriptions> = { 'locate-extension': { type: 'string[]' }, 'extensionDevelopmentPath': { type: 'string[]' }, 'extensionTestsPath': { type: 'string' }, - 'extension-development-confirm-save': { type: 'boolean' }, 'debugId': { type: 'string' }, 'inspect-search': { type: 'string', deprecates: 'debugSearch' }, 'inspect-brk-search': { type: 'string', deprecates: 'debugBrkSearch' }, diff --git a/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts b/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts index b3b65134298..ea3b5f8d8b6 100644 --- a/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts +++ b/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts @@ -9,7 +9,6 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { ILifecycleService, LifecyclePhase, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ConfirmResult, IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -20,6 +19,7 @@ import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker' import { ILogService } from 'vs/platform/log/common/log'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { SaveReason } from 'vs/workbench/common/editor'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; export class NativeBackupTracker extends BackupTracker implements IWorkbenchContribution { @@ -28,13 +28,13 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @IWorkingCopyService workingCopyService: IWorkingCopyService, @ILifecycleService lifecycleService: ILifecycleService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @IDialogService private readonly dialogService: IDialogService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IElectronService private readonly electronService: IElectronService, @ILogService logService: ILogService, - @IEditorService private readonly editorService: IEditorService + @IEditorService private readonly editorService: IEditorService, + @IEnvironmentService private readonly environmentService: IEnvironmentService ) { super(backupFileService, filesConfigurationService, workingCopyService, logService, lifecycleService); } @@ -120,32 +120,36 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont // ever activated when quit is requested. let doBackup: boolean | undefined; - switch (reason) { - case ShutdownReason.CLOSE: - if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured - } else if (await this.electronService.getWindowCount() > 1 || isMacintosh) { - doBackup = false; // do not backup if a window is closed that does not cause quitting of the application - } else { - doBackup = true; // backup if last window is closed on win/linux where the application quits right after - } - break; + if (this.environmentService.isExtensionDevelopment) { + doBackup = true; // always backup closing extension development window without asking to speed up debugging + } else { + switch (reason) { + case ShutdownReason.CLOSE: + if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured + } else if (await this.electronService.getWindowCount() > 1 || isMacintosh) { + doBackup = false; // do not backup if a window is closed that does not cause quitting of the application + } else { + doBackup = true; // backup if last window is closed on win/linux where the application quits right after + } + break; - case ShutdownReason.QUIT: - doBackup = true; // backup because next start we restore all backups - break; + case ShutdownReason.QUIT: + doBackup = true; // backup because next start we restore all backups + break; - case ShutdownReason.RELOAD: - doBackup = true; // backup because after window reload, backups restore - break; + case ShutdownReason.RELOAD: + doBackup = true; // backup because after window reload, backups restore + break; - case ShutdownReason.LOAD: - if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured - } else { - doBackup = false; // do not backup because we are switching contexts - } - break; + case ShutdownReason.LOAD: + if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured + } else { + doBackup = false; // do not backup because we are switching contexts + } + break; + } } // Perform a backup of all dirty working copies unless a backup already exists @@ -247,10 +251,6 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont return false; // if editors have not restored, we are not up to speed with backups and thus should not discard them } - if (this.environmentService.isExtensionDevelopment) { - return false; // extension development does not track any backups - } - return Promise.all(backupsToDiscard.map(workingCopy => this.backupFileService.discardBackup(workingCopy.resource))).then(() => false, () => false); } } diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts index 1e7343260ea..ea8c85ea656 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts @@ -32,7 +32,6 @@ import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/wo import { ILogService } from 'vs/platform/log/common/log'; import { HotExitConfiguration } from 'vs/platform/files/common/files'; import { ShutdownReason, ILifecycleService, BeforeShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IFileDialogService, ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkspaceContextService, Workspace } from 'vs/platform/workspace/common/workspace'; import { IElectronService } from 'vs/platform/electron/node/electron'; @@ -42,9 +41,10 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { TestFilesConfigurationService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestFilesConfigurationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backuprestorer'); const backupHome = path.join(userdataDir, 'Backups'); @@ -60,15 +60,15 @@ class TestBackupTracker extends NativeBackupTracker { @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @IWorkingCopyService workingCopyService: IWorkingCopyService, @ILifecycleService lifecycleService: ILifecycleService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IFileDialogService fileDialogService: IFileDialogService, @IDialogService dialogService: IDialogService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IElectronService electronService: IElectronService, @ILogService logService: ILogService, - @IEditorService editorService: IEditorService + @IEditorService editorService: IEditorService, + @IEnvironmentService environmentService: IEnvironmentService ) { - super(backupFileService, filesConfigurationService, workingCopyService, lifecycleService, environmentService, fileDialogService, dialogService, contextService, electronService, logService, editorService); + super(backupFileService, filesConfigurationService, workingCopyService, lifecycleService, fileDialogService, dialogService, contextService, electronService, logService, editorService, environmentService); // Reduce timeout for tests BackupTracker.BACKUP_FROM_CONTENT_CHANGE_DELAY = 10; @@ -131,8 +131,7 @@ suite('BackupTracker', () => { instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService( instantiationService.createInstance(MockContextKeyService), - configurationService, - TestEnvironmentService + configurationService )); const part = instantiationService.createInstance(EditorPart); diff --git a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts index 33b1a26be9c..6d7d1cdf745 100644 --- a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { Event } from 'vs/base/common/event'; import { toResource } from 'vs/base/test/common/utils'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { TestFilesConfigurationService, TestEnvironmentService, workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestFilesConfigurationService, workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { IResolvedTextFileEditorModel, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; @@ -56,8 +56,7 @@ suite('EditorAutoSave', () => { instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService( instantiationService.createInstance(MockContextKeyService), - configurationService, - TestEnvironmentService + configurationService )); const part = instantiationService.createInstance(EditorPart); diff --git a/src/vs/workbench/contrib/files/test/browser/textFileEditor.test.ts b/src/vs/workbench/contrib/files/test/browser/textFileEditor.test.ts index 81e71fd750f..9325e95d69b 100644 --- a/src/vs/workbench/contrib/files/test/browser/textFileEditor.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/textFileEditor.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { toResource } from 'vs/base/test/common/utils'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { workbenchInstantiationService, TestServiceAccessor, TestFilesConfigurationService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor, TestFilesConfigurationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; @@ -56,8 +56,7 @@ suite('Files - TextFileEditor', () => { instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService( instantiationService.createInstance(MockContextKeyService), - configurationService, - TestEnvironmentService + configurationService )); const part = instantiationService.createInstance(EditorPart); diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts index bdcff69df4f..7dc7647ffbf 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -88,8 +88,8 @@ export abstract class AbstractFileDialogService implements IFileDialogService { } async showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise { - if (this.environmentService.isExtensionDevelopment) { - return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) + if (this.environmentService.isExtensionDevelopment && this.environmentService.extensionTestsLocationURI) { + return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev testing mode because we cannot assume we run interactive } return this.doShowSaveConfirm(fileNamesOrResources); diff --git a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts index 36e903296cb..a7c6d54baa4 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts @@ -6,7 +6,7 @@ import { SaveDialogOptions, OpenDialogOptions } from 'electron'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; +import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -190,16 +190,6 @@ export class FileDialogService extends AbstractFileDialogService implements IFil // Don't allow untitled schema through. return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]); } - - async showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise { - if (this.environmentService.isExtensionDevelopment) { - if (!this.environmentService.args['extension-development-confirm-save']) { - return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) - } - } - - return super.doShowSaveConfirm(fileNamesOrResources); - } } registerSingleton(IFileDialogService, FileDialogService, true); diff --git a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts index 7a9048cba8d..b40e932de44 100644 --- a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts +++ b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts @@ -11,7 +11,6 @@ import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/cont import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IFilesConfiguration, AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; import { isUndefinedOrNull } from 'vs/base/common/types'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { equals } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; import { isWeb } from 'vs/base/common/platform'; @@ -83,8 +82,7 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi constructor( @IContextKeyService contextKeyService: IContextKeyService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); @@ -203,7 +201,7 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi } get isHotExitEnabled(): boolean { - return !this.environmentService.isExtensionDevelopment && this.currentHotExitConfig !== HotExitConfiguration.OFF; + return this.currentHotExitConfig !== HotExitConfiguration.OFF; } get hotExitConfiguration(): string { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 141b1ed5a3a..fbe641543e7 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -126,7 +126,7 @@ export function workbenchInstantiationService(overrides?: { textFileService?: (i instantiationService.stub(IWorkspaceContextService, workspaceContextService); const configService = new TestConfigurationService(); instantiationService.stub(IConfigurationService, configService); - instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(contextKeyService, configService, TestEnvironmentService)); + instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(contextKeyService, configService)); instantiationService.stub(ITextResourceConfigurationService, new TestTextResourceConfigurationService(configService)); instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); instantiationService.stub(IStorageService, new TestStorageService()); From a049e8153ded7e9fe00a8f135b2316203b6e8784 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 12 Mar 2020 16:24:26 +0100 Subject: [PATCH 072/169] fixes #92333 --- .../codeEditor/browser/accessibility/accessibility.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index 85225bed6c1..f76fc93385e 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -284,7 +284,11 @@ class ShowAccessibilityHelpAction extends EditorAction { kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyMod.Alt | KeyCode.F1, - weight: KeybindingWeight.EditorContrib + weight: KeybindingWeight.EditorContrib, + linux: { + primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F1, + secondary: [KeyMod.Alt | KeyCode.F1] + } } }); } From 9610d997af92f3e7aa932416a0e8ebd67c8d7877 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 12 Mar 2020 08:36:31 -0700 Subject: [PATCH 073/169] fixup! more pr comments --- .../contrib/debug/browser/variablesView.ts | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index e93fb60302e..99b12d01db5 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -34,7 +34,6 @@ import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { escape } from 'vs/base/common/strings'; const $ = dom.$; let forgetScopes = true; @@ -98,7 +97,7 @@ export class VariablesView extends ViewPane { const treeContainer = renderViewTree(container); this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'VariablesView', treeContainer, new VariablesDelegate(), - [this.instantiationService.createInstance(VariablesRenderer), new ScopesRenderer()], + [this.instantiationService.createInstance(VariablesRenderer), new ScopesRenderer(), new ScopeErrorRenderer()], new VariablesDataSource(), { ariaLabel: nls.localize('variablesAriaTreeLabel', "Debug Variables"), accessibilityProvider: new VariablesAccessibilityProvider(), @@ -247,6 +246,10 @@ class VariablesDelegate implements IListVirtualDelegate { } getTemplateId(element: IExpression | IScope): string { + if (element instanceof ErrorScope) { + return ScopeErrorRenderer.ID; + } + if (element instanceof Scope) { return ScopesRenderer.ID; } @@ -271,12 +274,7 @@ class ScopesRenderer implements ITreeRenderer, index: number, templateData: IScopeTemplateData): void { - const name = element.element.name; - if (element.element instanceof ErrorScope) { - templateData.name.innerHTML = `${escape(name)}`; - } else { - templateData.label.set(name, createMatches(element.filterData)); - } + templateData.label.set(element.element.name, createMatches(element.filterData)); } disposeTemplate(templateData: IScopeTemplateData): void { @@ -284,6 +282,33 @@ class ScopesRenderer implements ITreeRenderer { + + static readonly ID = 'scopeError'; + + get templateId(): string { + return ScopeErrorRenderer.ID; + } + + renderTemplate(container: HTMLElement): IScopeErrorTemplateData { + const wrapper = dom.append(container, $('.scope')); + const error = dom.append(wrapper, $('.error')); + return { error }; + } + + renderElement(element: ITreeNode, index: number, templateData: IScopeErrorTemplateData): void { + templateData.error.innerText = element.element.name; + } + + disposeTemplate(): void { + // noop + } +} + export class VariablesRenderer extends AbstractExpressionsRenderer { static readonly ID = 'variable'; From bee8e036bd189b58559783ec4fe53aab62a512f4 Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Thu, 12 Mar 2020 16:59:48 +0100 Subject: [PATCH 074/169] experimental progress support for DAP --- .../contrib/debug/common/debugProtocol.d.ts | 68 ++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts index 5807fa4a3b6..c0d9524fd95 100644 --- a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts @@ -70,7 +70,9 @@ declare module DebugProtocol { } /** Cancel request; value of command field is 'cancel'. - The 'cancel' request is used by the frontend to indicate that it is no longer interested in the result produced by a specific request issued earlier. + The 'cancel' request is used by the frontend in two situations: + - to indicate that it is no longer interested in the result produced by a specific request issued earlier + - to cancel a progress indicator. This request has a hint characteristic: a debug adapter can only be expected to make a 'best effort' in honouring this request but there are no guarantees. The 'cancel' request may return an error if it could not cancel an operation but a frontend should refrain from presenting this error to end users. A frontend client should only call this request if the capability 'supportsCancelRequest' is true. @@ -85,8 +87,10 @@ declare module DebugProtocol { /** Arguments for 'cancel' request. */ export interface CancelArguments { - /** The ID (attribute 'seq') of the request to cancel. */ + /** The ID (attribute 'seq') of the request to cancel. If missing no request is cancelled. Both a 'requestId' and a 'progressId' can be specified in one request. */ requestId?: number; + /** The ID (attribute 'progressId') of the progress to cancel. If missing no progress is cancelled. Both a 'requestId' and a 'progressId' can be specified in one request. */ + progressId?: string; } /** Response to 'cancel' request. This is just an acknowledgement, so no body field is required. */ @@ -302,6 +306,64 @@ declare module DebugProtocol { }; } + /** Event message for 'progressStart' event type. + The event signals that a long running operation is about to start and + provides additional information for the client to set up a corresponding progress and cancellation UI. + The client is free to delay the showing of the UI in order to reduce flicker. + */ + export interface ProgressStartEvent extends Event { + // event: 'progressStart'; + body: { + /** An ID that must be used in subsequent 'progressUpdate' and 'progressEnd' events to make them refer to the same progress reporting. IDs must be unique within a debug session. */ + progressId: string; + /** Mandatory (short) title of the progress reporting. Shown in the UI to describe the long running operation. */ + title: string; + /** The request ID that this progress report is related to. If specified a debug adapter is expected to emit + progress events for the long running request until the request has been either completed or cancelled. + If the request ID is omitted, the progress report is assumed to be related to some general activity of the debug adapter. + */ + requestId?: number; + /** If true, the request that reports progress may be canceled with a 'cancel' request. + So this property basically controls whether the client should use UX that supports cancellation. + Clients that don't support cancellation are allowed to ignore the setting. + */ + cancellable?: boolean; + /** Optional, more detailed progress message. */ + message?: string; + /** Optional progress percentage to display (value range: 0 to 100). If omitted no percentage will be shown. */ + percentage?: number; + }; + } + + /** Event message for 'progressUpdate' event type. + The event signals that the progress reporting needs to updated with a new message and/or percentage. + The client does not have to update the UI immediately, but the clients needs to keep track of the message and/or percentage values. + */ + export interface ProgressUpdateEvent extends Event { + // event: 'progressUpdate'; + body: { + /** The ID that was introduced in the initial 'progressStart' event. */ + progressId: string; + /** Optional, more detailed progress message. If omitted, the previous message (if any) is used. */ + message?: string; + /** Optional progress percentage to display (value range: 0 to 100). If omitted no percentage will be shown. */ + percentage?: number; + }; + } + + /** Event message for 'progressEnd' event type. + The event signals the end of the progress reporting with an optional final message. + */ + export interface ProgressEndEvent extends Event { + // event: 'progressEnd'; + body: { + /** The ID that was introduced in the initial 'ProgressStartEvent'. */ + progressId: string; + /** Optional, more detailed progress message. If omitted, the previous message (if any) is used. */ + message?: string; + }; + } + /** 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. */ @@ -370,6 +432,8 @@ declare module DebugProtocol { supportsRunInTerminalRequest?: boolean; /** Client supports memory references. */ supportsMemoryReferences?: boolean; + /** Client supports progress reporting. */ + supportsProgressReporting?: boolean; } /** Response to 'initialize' request. */ From 474946572402d894bcd6adca9405998c513c6e3b Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Thu, 12 Mar 2020 11:58:40 -0400 Subject: [PATCH 075/169] Supports view progress in merged header & panel --- .../parts/sidebar/media/sidebarpart.css | 5 +++-- .../browser/parts/views/media/paneviewlet.css | 8 +++++-- .../browser/parts/views/viewPaneContainer.ts | 22 +++++++++---------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index 25b9dcd0625..c5953e53573 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .sidebar > .content { +/* Removed to allow progress bar positioning to escape */ +/* .monaco-workbench .sidebar > .content { overflow: hidden; -} +} */ .monaco-workbench.nosidebar > .part.sidebar { display: none !important; diff --git a/src/vs/workbench/browser/parts/views/media/paneviewlet.css b/src/vs/workbench/browser/parts/views/media/paneviewlet.css index befcd315691..58bae1c2809 100644 --- a/src/vs/workbench/browser/parts/views/media/paneviewlet.css +++ b/src/vs/workbench/browser/parts/views/media/paneviewlet.css @@ -28,10 +28,14 @@ margin-left: 7px; } -.monaco-pane-view .pane > .pane-header .monaco-progress-container { +.monaco-pane-view .pane .monaco-progress-container { position: absolute; left: 0; - bottom: 0; + top: -2px; z-index: 5; height: 2px; } + +.monaco-pane-view .pane:not(.merged-header) .monaco-progress-container { + top: 20px; +} diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index fe24e2aab07..963e2d1ed1a 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -225,6 +225,15 @@ export abstract class ViewPane extends Pane implements IView { this.viewWelcomeController = new ViewWelcomeController(this.id, contextKeyService); } + get headerVisible(): boolean { + return super.headerVisible; + } + + set headerVisible(visible: boolean) { + super.headerVisible = visible; + toggleClass(this.element, 'merged-header', !visible); + } + setVisible(visible: boolean): void { if (this._isVisible !== visible) { this._isVisible = visible; @@ -294,13 +303,6 @@ export abstract class ViewPane extends Pane implements IView { const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig)); this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this)); this.updateActionsVisibility(); - - if (this.progressBar !== undefined) { - // Progress bar - this.progressBar = this._register(new ProgressBar(this.headerContainer)); - this._register(attachProgressBarStyler(this.progressBar, this.themeService)); - this.progressBar.hide(); - } } protected renderTwisties(container: HTMLElement): void { @@ -333,13 +335,9 @@ export abstract class ViewPane extends Pane implements IView { } getProgressIndicator() { - if (!this.headerContainer) { - return undefined; - } - if (this.progressBar === undefined) { // Progress bar - this.progressBar = this._register(new ProgressBar(this.headerContainer)); + this.progressBar = this._register(new ProgressBar(this.element)); this._register(attachProgressBarStyler(this.progressBar, this.themeService)); this.progressBar.hide(); } From fe4accb996efa2c523342b4b99e38380350e44a1 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 12 Mar 2020 17:37:14 +0100 Subject: [PATCH 076/169] #40233 Introduce resource identity service - Service that provides an id for a given resource - Use for workspace folder identifier --- .../common/resourceIdentityService.ts | 22 ++++++++ .../node/resourceIdentityServiceImpl.ts | 54 ++++++++++++++++++ src/vs/workbench/browser/web.main.ts | 13 +++-- .../electron-browser/desktop.main.ts | 56 +++++-------------- 4 files changed, 99 insertions(+), 46 deletions(-) create mode 100644 src/vs/platform/resource/common/resourceIdentityService.ts create mode 100644 src/vs/platform/resource/node/resourceIdentityServiceImpl.ts diff --git a/src/vs/platform/resource/common/resourceIdentityService.ts b/src/vs/platform/resource/common/resourceIdentityService.ts new file mode 100644 index 00000000000..d81e3dc8e2f --- /dev/null +++ b/src/vs/platform/resource/common/resourceIdentityService.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; +import { hash } from 'vs/base/common/hash'; +import { Disposable } from 'vs/base/common/lifecycle'; + +export const IResourceIdentityService = createDecorator('IResourceIdentityService'); +export interface IResourceIdentityService { + _serviceBrand: undefined; + resolveResourceIdentity(resource: URI): Promise; +} + +export class WebResourceIdentityService extends Disposable implements IResourceIdentityService { + _serviceBrand: undefined; + async resolveResourceIdentity(resource: URI): Promise { + return hash(resource.toString()).toString(16); + } +} diff --git a/src/vs/platform/resource/node/resourceIdentityServiceImpl.ts b/src/vs/platform/resource/node/resourceIdentityServiceImpl.ts new file mode 100644 index 00000000000..71cb292b6ec --- /dev/null +++ b/src/vs/platform/resource/node/resourceIdentityServiceImpl.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createHash } from 'crypto'; +import { stat } from 'vs/base/node/pfs'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; +import { IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ResourceMap } from 'vs/base/common/map'; + +export class NativeResourceIdentityService extends Disposable implements IResourceIdentityService { + + _serviceBrand: undefined; + + private readonly cache: ResourceMap> = new ResourceMap>(); + + resolveResourceIdentity(resource: URI): Promise { + let promise = this.cache.get(resource); + if (!promise) { + promise = this.createIdentity(resource); + this.cache.set(resource, promise); + } + return promise; + } + + private async createIdentity(resource: URI): Promise { + // Return early the folder is not local + if (resource.scheme !== Schemas.file) { + return createHash('md5').update(resource.toString()).digest('hex'); + } + + const fileStat = await stat(resource.fsPath); + let ctime: number | undefined; + if (isLinux) { + ctime = fileStat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead! + } else if (isMacintosh) { + ctime = fileStat.birthtime.getTime(); // macOS: birthtime is fine to use as is + } else if (isWindows) { + if (typeof fileStat.birthtimeMs === 'number') { + ctime = Math.floor(fileStat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897) + } else { + ctime = fileStat.birthtime.getTime(); + } + } + + // we use the ctime as extra salt to the ID so that we catch the case of a folder getting + // deleted and recreated. in that case we do not want to carry over previous state + return createHash('md5').update(resource.fsPath).update(ctime ? String(ctime) : '').digest('hex'); + } +} diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 8973e3fc36c..fe12a0da800 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -33,7 +33,6 @@ import { WorkspaceService } from 'vs/workbench/services/configuration/browser/co import { ConfigurationCache } from 'vs/workbench/services/configuration/browser/configurationCache'; import { ISignService } from 'vs/platform/sign/common/sign'; import { SignService } from 'vs/platform/sign/browser/signService'; -import { hash } from 'vs/base/common/hash'; import { IWorkbenchConstructionOptions, IWorkspace } from 'vs/workbench/workbench.web.api'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { BACKUPS } from 'vs/platform/environment/common/environment'; @@ -51,6 +50,7 @@ import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/wi import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; import { coalesce } from 'vs/base/common/arrays'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { WebResourceIdentityService, IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService'; class BrowserMain extends Disposable { @@ -157,7 +157,11 @@ class BrowserMain extends Disposable { const logService = new BufferLogService(this.configuration.logLevel); serviceCollection.set(ILogService, logService); - const payload = this.resolveWorkspaceInitializationPayload(); + // Resource Identity + const resourceIdentityService = this._register(new WebResourceIdentityService()); + serviceCollection.set(IResourceIdentityService, resourceIdentityService); + + const payload = await this.resolveWorkspaceInitializationPayload(resourceIdentityService); // Environment const environmentService = new BrowserWorkbenchEnvironmentService({ workspaceId: payload.id, logsPath, ...this.configuration }); @@ -292,7 +296,7 @@ class BrowserMain extends Disposable { } } - private resolveWorkspaceInitializationPayload(): IWorkspaceInitializationPayload { + private async resolveWorkspaceInitializationPayload(resourceIdentityService: IResourceIdentityService): Promise { let workspace: IWorkspace | undefined = undefined; if (this.configuration.workspaceProvider) { workspace = this.configuration.workspaceProvider.workspace; @@ -305,7 +309,8 @@ class BrowserMain extends Disposable { // Single-folder workspace if (workspace && isFolderToOpen(workspace)) { - return { id: hash(workspace.folderUri.toString()).toString(16), folder: workspace.folderUri }; + const id = await resourceIdentityService.resolveResourceIdentity(workspace.folderUri); + return { id, folder: workspace.folderUri }; } return { id: 'empty-window' }; diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index e3d94dd92b4..800f1f03096 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -5,7 +5,6 @@ import * as fs from 'fs'; import * as gracefulFs from 'graceful-fs'; -import { createHash } from 'crypto'; import { webFrame } from 'electron'; import { importEntries, mark } from 'vs/base/common/performance'; import { Workbench } from 'vs/workbench/browser/workbench'; @@ -13,13 +12,11 @@ import { NativeWindow } from 'vs/workbench/electron-browser/window'; import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser'; import { domContentLoaded, addDisposableListener, EventType, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { stat } from 'vs/base/node/pfs'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; @@ -51,6 +48,8 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file import { basename } from 'vs/base/common/resources'; import { IProductService } from 'vs/platform/product/common/productService'; import product from 'vs/platform/product/common/product'; +import { NativeResourceIdentityService } from 'vs/platform/resource/node/resourceIdentityServiceImpl'; +import { IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService'; class DesktopMain extends Disposable { @@ -214,7 +213,10 @@ class DesktopMain extends Disposable { fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider); } - const payload = await this.resolveWorkspaceInitializationPayload(); + const resourceIdentityService = this._register(new NativeResourceIdentityService()); + serviceCollection.set(IResourceIdentityService, resourceIdentityService); + + const payload = await this.resolveWorkspaceInitializationPayload(resourceIdentityService); const services = await Promise.all([ this.createWorkspaceService(payload, fileService, remoteAgentService, logService).then(service => { @@ -240,7 +242,7 @@ class DesktopMain extends Disposable { return { serviceCollection, logService, storageService: services[1] }; } - private async resolveWorkspaceInitializationPayload(): Promise { + private async resolveWorkspaceInitializationPayload(resourceIdentityService: IResourceIdentityService): Promise { // Multi-root workspace if (this.environmentService.configuration.workspace) { @@ -250,7 +252,7 @@ class DesktopMain extends Disposable { // Single-folder workspace let workspaceInitializationPayload: IWorkspaceInitializationPayload | undefined; if (this.environmentService.configuration.folderUri) { - workspaceInitializationPayload = await this.resolveSingleFolderWorkspaceInitializationPayload(this.environmentService.configuration.folderUri); + workspaceInitializationPayload = await this.resolveSingleFolderWorkspaceInitializationPayload(this.environmentService.configuration.folderUri, resourceIdentityService); } // Fallback to empty workspace if we have no payload yet. @@ -270,46 +272,16 @@ class DesktopMain extends Disposable { return workspaceInitializationPayload; } - private async resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier): Promise { - - // Return early the folder is not local - if (folderUri.scheme !== Schemas.file) { - return { id: createHash('md5').update(folderUri.toString()).digest('hex'), folder: folderUri }; - } - - function computeLocalDiskFolderId(folder: URI, stat: fs.Stats): string { - let ctime: number | undefined; - if (isLinux) { - ctime = stat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead! - } else if (isMacintosh) { - ctime = stat.birthtime.getTime(); // macOS: birthtime is fine to use as is - } else if (isWindows) { - if (typeof stat.birthtimeMs === 'number') { - ctime = Math.floor(stat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897) - } else { - ctime = stat.birthtime.getTime(); - } - } - - // we use the ctime as extra salt to the ID so that we catch the case of a folder getting - // deleted and recreated. in that case we do not want to carry over previous state - return createHash('md5').update(folder.fsPath).update(ctime ? String(ctime) : '').digest('hex'); - } - - // For local: ensure path is absolute and exists + private async resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier, resourceIdentityService: IResourceIdentityService): Promise { try { - const sanitizedFolderPath = sanitizeFilePath(folderUri.fsPath, process.env['VSCODE_CWD'] || process.cwd()); - const fileStat = await stat(sanitizedFolderPath); - - const sanitizedFolderUri = URI.file(sanitizedFolderPath); - return { - id: computeLocalDiskFolderId(sanitizedFolderUri, fileStat), - folder: sanitizedFolderUri - }; + const folder = folderUri.scheme === Schemas.file + ? URI.file(sanitizeFilePath(folderUri.fsPath, process.env['VSCODE_CWD'] || process.cwd())) // For local: ensure path is absolute + : folderUri; + const id = await resourceIdentityService.resolveResourceIdentity(folderUri); + return { id, folder }; } catch (error) { onUnexpectedError(error); } - return; } From 1ae5bcf396f18a5489860d754a72f9a4f68a3fa4 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 12 Mar 2020 17:47:45 +0100 Subject: [PATCH 077/169] Fix #85619 --- .../workbench/contrib/userDataSync/browser/userDataSyncView.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts index c02da6df9c7..be02cbc7149 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts @@ -30,8 +30,7 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution { @IUserDataSyncBackupStoreService private readonly userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, ) { const container = this.registerSyncViewContainer(); - // Disable remote backup view until server is upgraded. - // this.registerBackupView(container, true); + this.registerBackupView(container, true); this.registerBackupView(container, false); } From a906625efbc2d0b19ff27eb870e478a047e431e3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 12 Mar 2020 17:59:07 +0100 Subject: [PATCH 078/169] #85619 revert --- .../workbench/contrib/userDataSync/browser/userDataSyncView.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts index be02cbc7149..d902e61625a 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts @@ -30,7 +30,8 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution { @IUserDataSyncBackupStoreService private readonly userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, ) { const container = this.registerSyncViewContainer(); - this.registerBackupView(container, true); + // Disable until server returns the correct timestamp + // this.registerBackupView(container, true); this.registerBackupView(container, false); } From 5c8a19c786cc838fdc20bd4872198e0ab08b1881 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Thu, 12 Mar 2020 10:43:41 -0700 Subject: [PATCH 079/169] Update Codicons: add 'run-all' icon --- .../ui/codiconLabel/codicon/codicon.css | 3 ++- .../ui/codiconLabel/codicon/codicon.ttf | Bin 57012 -> 57108 bytes 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css index 998b1e698a2..66d3d01872d 100644 --- a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css @@ -5,7 +5,7 @@ @font-face { font-family: "codicon"; - src: url("./codicon.ttf?df9e07bbeddc0cf98f4d7a7c92bef3d8") format("truetype"); + src: url("./codicon.ttf?5490083fcec741c6a0a08a366d2f9c98") format("truetype"); } .codicon[class*='codicon-'] { @@ -419,3 +419,4 @@ .codicon-bell-dot:before { content: "\f101" } .codicon-debug-alt-2:before { content: "\f102" } .codicon-debug-alt:before { content: "\f103" } +.codicon-run-all:before { content: "\f104" } diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf index 94b2533d5b943c8d320f9c0a0b96a906576f1788..5eac56a666cf56b9be1a6b784ba948e67711f83b 100644 GIT binary patch delta 1878 zcmYM#X>1f_90u^`+1=hJEytEZS=v&rQlO82V%bfP9!$2aMwGNdp?(IZ;08A zaw6adzZls+BN7{1I@kMLvIpJ-99Ns$mb+6smK*?LG5}k1i+g>$tqkAD`%byY)#`3p zG5+Z2%q_sh%W^?*ds|26u5)W#z@_Uzd%e?8>V{>><`A4VUd;V##^~3&rFIh4eu-}8j zX5B}m{MZ3whtXqvYnGV3X5W2>_o;v8qTx7?2RMnxNaHk^xQ_v>!UVaaW$@w{k{OL^ zgfST%sKYg!#aB4ZG1!TX=teqisAm-Z#Xbh&DT6tTq4dWNlHtC-05?6ia2vm&A9rBj zcihE2{DD957armf{>DFefn7{V|{a0Ewk6rGIWXvT6Z<2a5kj%Pd* zIe|%>$Vr^cDV)j_PUj5HWGd4*3kPushw&LQkcq-cC_*u^8HoavA`ec);5Ztv8a+%v zIObv=WM=9A>A@hm5g^nW=kPvjE2n3Bj|W$_440u>sbsidG%6W57^{>FA&k{Z1`|e; zLYic=k^zR%qGYIHv?>{N7;Opz-u95CjP_m*Nno<~K}aRXc=JN%d87iyy9)J^JCyaE zjrWvH1B~|-_DRYkV3DbTu}k5pWUr!B#MrGkOmdH+jDg`*GMO;;0&&&`)*t;qQMzb+ zsAR5PVTFM**Dwo|%sR|MCG!unNJ$f57Axrl%n~K-fLW%bCos#EGzMmclJ3B)RMH}t zRZ98tq|8YLxzxll>nVAd)r9?VyjR1oHCO3Daxk&;@%T&$#^Fke@ACb>je zkFdE^(GCyHWpW_skaR2hN!BR_NG?|ll9ZXWFhp{NVwhxuVuYkL&%zOsjfx{BrFj;P zl3Z=^$bqavW|N|*qZ zva?w+RdS1Bn&ei+S>90RHb#1To$Hw34U9<&y)Uz6u@(Yfa?E%1)MoT$+|G>5%**V~ z99S@G!QrgtDP_H71La4` z`^%qHgjU2=%&6F3@k{00_{!GG3spr`A6H$ic2?I{e_4IK1~nNqoi&dZIu{PqX4mfV z^~JVq&XP47zXg?ZO85#bKE)xW9$ZuaodJbA#qoC>Dt}L6p+_wqrzd^Cr`^6Y z?&GK31DTsAJy4aM`^lsv%I(G}?Jan)jw&pYR&cL%yw zP5)wY@lN3TUcMl?i)OghRb=BH=ZMfR+@Yx?(jvq8eyG?9Jg@f$=kto4elqaJZ3E59zlfb zBu`4>pPLu!v-|A}#%~79fjjQKQN>Hg;Vk~ZG5m!B$%BpC_#JDJ%}>(?3rCPA$!JD` z%*F<+z%WkZJDiYIypL^o2Ma~eE>B?;A4wD*NQ@*&oQy%AKoY_SqXH7TieGUJzu`J= zz+eP7aSL~F7k}a&?&EL#gNOJRkMLMr;+6=BlxT^ScuAD;GC?NFB=Ja!OqMA!Rnlad zq|0>4lq|`X9GM|AWtQa19GNTgWWFqrd?~;uIE+v68H!L0Zw6}MLz#?4HEK}_4^nUx ztI>ge$v`5W!=mt^m;{M&s=}Krw7!jP42|Qg)+i1Lc2IFtu-7UM4R(j(_+Y=PP{8a| z94YKB#o@y4Rva_zb&3Or-J>{q*y{nSFU}{))^J>=%(h138YR{8$1lou>hi8aJF`!5 z17N?WFlv1jKPeE!qYo4wFb9;xF!v})V(wKE$Fvl;5B5F<=L#Pxr?2*Y#m&u0@J4fU zVX76k8OE!);V?CdTMy$?ECGyPu?{eGip7AbSF8w3gJM}=8Wn2;)1+7+m}Vg4+_6hA zEsCvzS)$l4m{!H6!Mv#0IhdCe+Xu5$v4=1(D>f2lnPN9#UQu|=T&~205KJ2nNciP4 z0VNU46-pwRE0shuxor-KWv)^Z&s?n}k;&pYWIQvdWCD}raL7bvha<#;NnEgb4(I2` zbSg<?%cRP3fhwiDj*2=gX7I z2g;9^55I7&BCDdPqO)S4Vz4r)a4;oRgbC{dtKfl?-{tSPc|A_x?ZCP!1?U~x!b@6rGbwl;L>j&$9thehQHY7ImHk@trG}dG^9&XBQ z+SYWe>3(xb^S Date: Thu, 12 Mar 2020 19:12:43 +0100 Subject: [PATCH 080/169] DAP: add "clipboard" context value for "evaluate" request --- src/vs/workbench/contrib/debug/common/debugProtocol.d.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts index c0d9524fd95..051e962d9dc 100644 --- a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts @@ -1164,6 +1164,7 @@ declare module DebugProtocol { 'watch': evaluate is run in a watch. 'repl': evaluate is run from REPL console. 'hover': evaluate is run from a data hover. + 'clipboard': evaluate is run to generate the value that will be stored in the clipboard. etc. */ context?: string; @@ -1473,6 +1474,8 @@ declare module DebugProtocol { supportsCancelRequest?: boolean; /** The debug adapter supports the 'breakpointLocations' request. */ supportsBreakpointLocationsRequest?: boolean; + /** The debug adapter supports the 'clipboard' context value in the 'evaluate' request. */ + supportsClipboardContext?: boolean; } /** An ExceptionBreakpointsFilter is shown in the UI as an option for configuring how exceptions are dealt with. */ From 3ef02fe7b00ceea645ffe7bbcd17f9d4843cb7bc Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 12 Mar 2020 11:34:40 -0700 Subject: [PATCH 081/169] Use the UndoRedoService for CustomEditors (#92408) * Use the UndoRedoService for CustomEditors For #90110 Changes custom editors (the ones not based on text) to use the UndoRedoService. This involved: - Moving edit lifecycle back into the main process again (this is actually the bulk of the changes) - Removing the `undo`/`redo` methods on `CustomEditorModel` - Using the undoRedoService to trigger undo/redo --- .../api/browser/mainThreadWebview.ts | 133 ++++++++++++++---- .../workbench/api/common/extHost.protocol.ts | 10 +- src/vs/workbench/api/common/extHostWebview.ts | 107 +++++--------- .../customEditor/browser/customEditorInput.ts | 8 +- .../customEditor/common/customEditor.ts | 2 - .../common/customTextEditorModel.ts | 8 -- 6 files changed, 153 insertions(+), 115 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 9fd5f3d3b17..52ea58cb549 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -6,7 +6,7 @@ import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, IDisposable, IReference, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, dispose, IDisposable, IReference } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { basename } from 'vs/base/common/path'; import { isWeb } from 'vs/base/common/platform'; @@ -21,6 +21,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/productService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IUndoRedoService, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo'; import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; import { IEditorInput, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; @@ -365,12 +366,14 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma return this._customEditorService.models.add(resource, viewType, model); } - public async $onDidChangeCustomDocumentState(resource: UriComponents, viewType: string, state: { dirty: boolean }) { - const model = await this._customEditorService.models.get(URI.revive(resource), viewType); + public async $onDidEdit(resourceComponents: UriComponents, viewType: string, editId: number): Promise { + const resource = URI.revive(resourceComponents); + const model = await this._customEditorService.models.get(resource, viewType); if (!model || !(model instanceof MainThreadCustomEditorModel)) { throw new Error('Could not find model for webview editor'); } - model.setDirty(state.dirty); + + model.pushEdit(editId); } private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) { @@ -536,7 +539,9 @@ namespace HotExitState { class MainThreadCustomEditorModel extends Disposable implements ICustomEditorModel, IWorkingCopy { private _hotExitState: HotExitState.State = HotExitState.Allowed; - private _dirty = false; + private _currentEditIndex: number = -1; + private _savePoint: number = -1; + private readonly _edits: Array = []; public static async create( instantiationService: IInstantiationService, @@ -556,19 +561,25 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod @IWorkingCopyService workingCopyService: IWorkingCopyService, @ILabelService private readonly _labelService: ILabelService, @IFileService private readonly _fileService: IFileService, + @IUndoRedoService private readonly _undoService: IUndoRedoService, ) { super(); - this._register(workingCopyService.registerWorkingCopy(this)); + if (_editable) { + this._register(workingCopyService.registerWorkingCopy(this)); + } } dispose() { + if (this._editable) { + this._undoService.removeElements(this.resource); + } this._proxy.$disposeWebviewCustomEditorDocument(this.resource, this._viewType); super.dispose(); } //#region IWorkingCopy - public get resource() { return this._resource; } + public get resource() { return this._resource; } // custom://viewType/path/file public get name() { return basename(this._labelService.getUriLabel(this._resource)); @@ -579,7 +590,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod } public isDirty(): boolean { - return this._dirty; + return this._edits.length > 0 && this._savePoint !== this._currentEditIndex; } private readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); @@ -589,36 +600,106 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod readonly onDidChangeContent: Event = this._onDidChangeContent.event; //#endregion - public get viewType() { return this._viewType; } - public setDirty(dirty: boolean): void { + public pushEdit(editId: number) { + if (!this._editable) { + throw new Error('Document is not editable'); + } + + this.change(() => { + this.spliceEdits(editId); + this._currentEditIndex = this._edits.length - 1; + }); + + this._undoService.pushElement({ + type: UndoRedoElementType.Resource, + resource: this.resource, + label: 'Edit', // TODO: get this from extensions? + undo: async () => { + if (!this._editable) { + return; + } + + if (this._currentEditIndex < 0) { + // nothing to undo + return; + } + + const undoneEdit = this._edits[this._currentEditIndex]; + await this._proxy.$undo(this.resource, this.viewType, undoneEdit); + + this.change(() => { + --this._currentEditIndex; + }); + }, + redo: async () => { + if (!this._editable) { + return; + } + + if (this._currentEditIndex >= this._edits.length - 1) { + // nothing to redo + return; + } + + const redoneEdit = this._edits[this._currentEditIndex + 1]; + await this._proxy.$redo(this.resource, this.viewType, redoneEdit); + this.change(() => { + ++this._currentEditIndex; + }); + } + }); + } + + private spliceEdits(editToInsert?: number) { + const start = this._currentEditIndex + 1; + const toRemove = this._edits.length - this._currentEditIndex; + + const removedEdits = typeof editToInsert === 'number' + ? this._edits.splice(start, toRemove, editToInsert) + : this._edits.splice(start, toRemove); + + if (removedEdits.length) { + this._proxy.$disposeEdits(this.resource, this._viewType, removedEdits); + } + } + + private change(makeEdit: () => void): void { + const wasDirty = this.isDirty(); + makeEdit(); this._onDidChangeContent.fire(); - if (this._dirty !== dirty) { - this._dirty = dirty; + if (this.isDirty() !== wasDirty) { this._onDidChangeDirty.fire(); } } public async revert(_options?: IRevertOptions) { - if (this._editable) { - this._proxy.$revert(this.resource, this.viewType); + if (!this._editable) { + return; } - } - public undo() { - if (this._editable) { - this._proxy.$undo(this.resource, this.viewType); + if (this._currentEditIndex === this._savePoint) { + return; } - } - public redo() { - if (this._editable) { - this._proxy.$redo(this.resource, this.viewType); + let editsToUndo: number[] = []; + let editsToRedo: number[] = []; + + if (this._currentEditIndex >= this._savePoint) { + editsToUndo = this._edits.slice(this._savePoint, this._currentEditIndex).reverse(); + } else if (this._currentEditIndex < this._savePoint) { + editsToRedo = this._edits.slice(this._currentEditIndex, this._savePoint); } + + this._proxy.$revert(this.resource, this.viewType, { undoneEdits: editsToUndo, redoneEdits: editsToRedo }); + this.change(() => { + this._currentEditIndex = this._savePoint; + this.spliceEdits(); + }); } public async save(_options?: ISaveOptions): Promise { @@ -626,14 +707,18 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod return false; } await createCancelablePromise(token => this._proxy.$onSave(this.resource, this.viewType, token)); - this.setDirty(false); + this.change(() => { + this._savePoint = this._currentEditIndex; + }); return true; } public async saveAs(resource: URI, targetResource: URI, _options?: ISaveOptions): Promise { if (this._editable) { await this._proxy.$onSaveAs(this.resource, this.viewType, targetResource); - this.setDirty(false); + this.change(() => { + this._savePoint = this._currentEditIndex; + }); return true; } else { // Since the editor is readonly, just copy the file over diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index ec69c297aa0..2e93f3aa9b8 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -592,7 +592,7 @@ export interface MainThreadWebviewsShape extends IDisposable { $registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void; $unregisterEditorProvider(viewType: string): void; - $onDidChangeCustomDocumentState(resource: UriComponents, viewType: string, state: { dirty: boolean }): void; + $onDidEdit(resource: UriComponents, viewType: string, editId: number): void; } export interface WebviewPanelViewStateData { @@ -615,9 +615,11 @@ export interface ExtHostWebviewsShape { $createWebviewCustomEditorDocument(resource: UriComponents, viewType: string): Promise<{ editable: boolean }>; $disposeWebviewCustomEditorDocument(resource: UriComponents, viewType: string): Promise; - $undo(resource: UriComponents, viewType: string): void; - $redo(resource: UriComponents, viewType: string): void; - $revert(resource: UriComponents, viewType: string): void; + $undo(resource: UriComponents, viewType: string, editId: number): Promise; + $redo(resource: UriComponents, viewType: string, editId: number): Promise; + $revert(resource: UriComponents, viewType: string, changes: { undoneEdits: number[], redoneEdits: number[] }): Promise; + $disposeEdits(resourceComponents: UriComponents, viewType: string, editIds: number[]): void; + $onSave(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise; $onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise; diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 86588547cd2..3324e32c8ae 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -18,6 +18,7 @@ import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import type * as vscode from 'vscode'; +import { Cache } from './cache'; import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewExtensionDescription, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol'; import { Disposable as VSCodeDisposable } from './extHostTypes'; @@ -245,8 +246,6 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa } } -type EditType = unknown; - class CustomDocument extends Disposable implements vscode.CustomDocument { public static create(proxy: MainThreadWebviewsShape, viewType: string, uri: vscode.Uri) { @@ -255,9 +254,7 @@ class CustomDocument extends Disposable implements vscode.CustomDocument { // Explicitly initialize all properties as we seal the object after creation! - #currentEditIndex: number = -1; - #savePoint: number = -1; - readonly #edits: Array = []; + readonly #_edits = new Cache('edits'); readonly #proxy: MainThreadWebviewsShape; readonly #viewType: string; @@ -299,58 +296,28 @@ class CustomDocument extends Disposable implements vscode.CustomDocument { this.#capabilities = capabilities; capabilities.editing?.onDidEdit(edit => { - this.pushEdit(edit); + const id = this.#_edits.add([edit]); + this.#proxy.$onDidEdit(this.uri, this.viewType, id); }); } - /** @internal*/ async _revert() { + /** @internal*/ async _revert(changes: { undoneEdits: number[], redoneEdits: number[] }) { const editing = this.getEditingCapability(); - if (this.#currentEditIndex === this.#savePoint) { - return true; - } - - - let undoneEdits: EditType[] = []; - let appliedEdits: EditType[] = []; - if (this.#currentEditIndex >= this.#savePoint) { - undoneEdits = this.#edits.slice(this.#savePoint, this.#currentEditIndex).reverse(); - } else if (this.#currentEditIndex < this.#savePoint) { - appliedEdits = this.#edits.slice(this.#currentEditIndex, this.#savePoint); - } - - this.#currentEditIndex = this.#savePoint; - this.spliceEdits(); - - await editing.revert({ undoneEdits, appliedEdits }); - - this.updateState(); - return true; + const undoneEdits = changes.undoneEdits.map(id => this.#_edits.get(id, 0)); + const appliedEdits = changes.redoneEdits.map(id => this.#_edits.get(id, 0)); + return editing.revert({ undoneEdits, appliedEdits }); } - /** @internal*/ _undo() { + /** @internal*/ _undo(editId: number) { const editing = this.getEditingCapability(); - if (this.#currentEditIndex < 0) { - // nothing to undo - return; - } - - const undoneEdit = this.#edits[this.#currentEditIndex]; - --this.#currentEditIndex; - editing.undoEdits([undoneEdit]); - this.updateState(); + const edit = this.#_edits.get(editId, 0); + return editing.undoEdits([edit]); } - /** @internal*/ _redo() { + /** @internal*/ _redo(editId: number) { const editing = this.getEditingCapability(); - if (this.#currentEditIndex >= this.#edits.length - 1) { - // nothing to redo - return; - } - - ++this.#currentEditIndex; - const redoneEdit = this.#edits[this.#currentEditIndex]; - editing.applyEdits([redoneEdit]); - this.updateState(); + const edit = this.#_edits.get(editId, 0); + return editing.applyEdits([edit]); } /** @internal*/ _save(cancellation: CancellationToken) { @@ -365,29 +332,14 @@ class CustomDocument extends Disposable implements vscode.CustomDocument { return this.getEditingCapability().backup(cancellation); } + /** @internal*/ _disposeEdits(editIds: number[]) { + for (const editId of editIds) { + this.#_edits.delete(editId); + } + } + //#endregion - private pushEdit(edit: EditType) { - this.spliceEdits(edit); - - this.#currentEditIndex = this.#edits.length - 1; - this.updateState(); - } - - private updateState() { - const dirty = this.#edits.length > 0 && this.#savePoint !== this.#currentEditIndex; - this.#proxy.$onDidChangeCustomDocumentState(this.uri, this.viewType, { dirty }); - } - - private spliceEdits(editToInsert?: EditType) { - const start = this.#currentEditIndex + 1; - const toRemove = this.#edits.length - this.#currentEditIndex; - - editToInsert - ? this.#edits.splice(start, toRemove, editToInsert) - : this.#edits.splice(start, toRemove); - } - private getEditingCapability(): vscode.CustomEditorEditingCapability { if (!this.#capabilities?.editing) { throw new Error('Document is not editable'); @@ -702,24 +654,29 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } } - async $undo(resourceComponents: UriComponents, viewType: string): Promise { + $disposeEdits(resourceComponents: UriComponents, viewType: string, editIds: number[]): void { const document = this.getCustomDocument(viewType, resourceComponents); - document._undo(); + document._disposeEdits(editIds); } - async $redo(resourceComponents: UriComponents, viewType: string): Promise { + async $undo(resourceComponents: UriComponents, viewType: string, editId: number): Promise { const document = this.getCustomDocument(viewType, resourceComponents); - document._redo(); + return document._undo(editId); } - async $revert(resourceComponents: UriComponents, viewType: string): Promise { + async $redo(resourceComponents: UriComponents, viewType: string, editId: number): Promise { const document = this.getCustomDocument(viewType, resourceComponents); - document._revert(); + return document._redo(editId); + } + + async $revert(resourceComponents: UriComponents, viewType: string, changes: { undoneEdits: number[], redoneEdits: number[] }): Promise { + const document = this.getCustomDocument(viewType, resourceComponents); + return document._revert(changes); } async $onSave(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise { const document = this.getCustomDocument(viewType, resourceComponents); - document._save(cancellation); + return document._save(cancellation); } async $onSaveAs(resourceComponents: UriComponents, viewType: string, targetResource: UriComponents): Promise { diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 60e3d5a4ee3..2791509e32f 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -15,6 +15,7 @@ import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions, Verbosity } from 'vs/workbench/common/editor'; import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { IWebviewService, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; @@ -44,6 +45,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { @IFileDialogService private readonly fileDialogService: IFileDialogService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @IEditorService private readonly editorService: IEditorService, + @IUndoRedoService private readonly undoRedoService: IUndoRedoService, ) { super(id, viewType, '', webview, webviewService, webviewWorkbenchService); this._editorResource = resource; @@ -175,10 +177,12 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { } public undo(): void { - assertIsDefined(this._modelRef).object.undo(); + assertIsDefined(this._modelRef); + this.undoRedoService.undo(this.resource); } public redo(): void { - assertIsDefined(this._modelRef).object.redo(); + assertIsDefined(this._modelRef); + this.undoRedoService.redo(this.resource); } } diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index 72f3c0d0b6c..3bfb81c05e5 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -52,8 +52,6 @@ export interface ICustomEditorModel extends IDisposable { isDirty(): boolean; readonly onDidChangeDirty: Event; - undo(): void; - redo(): void; revert(options?: IRevertOptions): Promise; save(options?: ISaveOptions): Promise; diff --git a/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts index 1f0c33e633f..a1d8019684d 100644 --- a/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts +++ b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts @@ -64,14 +64,6 @@ export class CustomTextEditorModel extends Disposable implements ICustomEditorMo return this.textFileService.revert(this.resource, options); } - public undo() { - this.textFileService.files.get(this.resource)?.textEditorModel?.undo(); - } - - public redo() { - this.textFileService.files.get(this.resource)?.textEditorModel?.redo(); - } - public async save(options?: ISaveOptions): Promise { return !!await this.textFileService.save(this.resource, options); } From ee04d73ca9a83f422925738ca743e221b741623b Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Thu, 12 Mar 2020 11:45:33 -0700 Subject: [PATCH 082/169] Improve scm icon alignment in list --- src/vs/base/browser/ui/actionbar/actionbar.css | 3 ++- src/vs/workbench/contrib/scm/browser/media/scmViewlet.css | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css index a8ab18355b9..9b304e81a80 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css @@ -46,7 +46,8 @@ } .monaco-action-bar .action-item .codicon { - vertical-align: middle; + display: flex; + align-items: center; } .monaco-action-bar .action-label { diff --git a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css index 9c94a5c49eb..93d8bb916dc 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css +++ b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css @@ -153,6 +153,10 @@ background-repeat: no-repeat; } +.scm-viewlet .monaco-list-row .resource > .name > .monaco-icon-label > .actions .action-label.codicon { + height: 22px; +} + .scm-viewlet .scm-editor { box-sizing: border-box; padding: 5px 12px 5px 16px; From 473148c839bc5888339a01b12975dbb1ebe6df4b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 12 Mar 2020 20:20:15 +0100 Subject: [PATCH 083/169] test for web extensions enablement --- .../extensionEnablementService.test.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts index 7a4ee7ed3cc..5c9f2323425 100644 --- a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts @@ -490,6 +490,30 @@ suite('ExtensionEnablementService Test', () => { assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), true); }); + test('test web extension on local server is not disabled by kind', async () => { + instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`) }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(testObject.isEnabled(localWorkspaceExtension)); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + }); + + test('test web extension on remote server is not disabled by kind', async () => { + instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(testObject.isEnabled(localWorkspaceExtension)); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + }); + + test('test web extension with no server is not disabled by kind', async () => { + instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.https }) }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(testObject.isEnabled(localWorkspaceExtension)); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + }); + }); function anExtensionManagementServer(authority: string, instantiationService: TestInstantiationService): IExtensionManagementServer { From 18567cdcf89106d08873dc23645cb314d1267eb2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 12 Mar 2020 20:21:16 +0100 Subject: [PATCH 084/169] do not disabled web extensions by kind yet --- .../extensionManagement/common/extensionEnablementService.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts index d64573ec6dc..79fcfe42b56 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts @@ -146,6 +146,10 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench return false; } } + if (extensionKind === 'web') { + // Web extensions are not yet supported to be disabled by kind + return false; + } } return true; } From 7744c44570ec5a041ec31b47e28bceb5722e2ff0 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 12 Mar 2020 22:49:09 +0100 Subject: [PATCH 085/169] [semantic highlighting] functions in josef.rouge-theme get wrong color. Fixes #92536 --- .../services/themes/common/colorThemeData.ts | 4 ++- .../tokenStyleResolving.test.ts | 35 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index ca5fb5e9e3d..bacaa95f9f7 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -293,9 +293,11 @@ export class ColorThemeData implements IWorkbenchColorTheme { const settings = tokenColors[i].settings; if (score >= foregroundScore && settings.foreground) { foreground = settings.foreground; + foregroundScore = score; } if (score >= fontStyleScore && types.isString(settings.fontStyle)) { fontStyle = settings.fontStyle; + fontStyleScore = score; } } } @@ -657,7 +659,7 @@ function nameMatcher(identifers: string[], scope: ProbeScope): number { let lastScopeIndex = scope.length - 1; let lastIdentifierIndex = findInIdents(scope[lastScopeIndex--], identifers.length); if (lastIdentifierIndex >= 0) { - const score = (lastIdentifierIndex + 1) * 0x10000 + scope.length; + const score = (lastIdentifierIndex + 1) * 0x10000 + identifers[lastIdentifierIndex].length; while (lastScopeIndex >= 0) { lastIdentifierIndex = findInIdents(scope[lastScopeIndex--], lastIdentifierIndex); if (lastIdentifierIndex === -1) { diff --git a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts index 8f5da088288..242182ce537 100644 --- a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts +++ b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts @@ -289,6 +289,41 @@ suite('Themes - TokenStyleResolving', () => { }); + + test('resolveScopes - match most specific', async () => { + const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); + + const customTokenColors: ITokenColorCustomizations = { + textMateRules: [ + { + scope: 'entity.name.type', + settings: { + fontStyle: 'underline', + foreground: '#A6E22E' + } + }, + { + scope: 'entity.name.type.class', + settings: { + foreground: '#FF00FF' + } + }, + { + scope: 'entity.name', + settings: { + foreground: '#FFFFFF' + } + }, + ] + }; + + themeData.setCustomTokenColors(customTokenColors); + + const tokenStyle = themeData.resolveScopes([['entity.name.type.class']]); + assertTokenStyle(tokenStyle, ts('#FF00FF', { underline: true }), 'entity.name.type.class'); + + }); + test('rule matching', async () => { const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); themeData.setCustomColors({ 'editor.foreground': '#000000' }); From 11c130357bcecfd0bfd9a5f3e4400b87f1836b52 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 12 Mar 2020 23:14:11 +0100 Subject: [PATCH 086/169] [json] Problems loading reference with schema service. Fixes #92353 --- extensions/json-language-features/client/src/jsonMain.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extensions/json-language-features/client/src/jsonMain.ts b/extensions/json-language-features/client/src/jsonMain.ts index a3117e8fa5d..0f934004cdd 100644 --- a/extensions/json-language-features/client/src/jsonMain.ts +++ b/extensions/json-language-features/client/src/jsonMain.ts @@ -345,6 +345,9 @@ function getSchemaAssociations(_context: ExtensionContext): ISchemaAssociation[] fileMatch = [fileMatch]; } if (Array.isArray(fileMatch) && url) { + if (url[0] === '.' && url[1] === '/') { + url = Uri.file(path.join(extension.extensionPath, url)).toString(); + } fileMatch = fileMatch.map(fm => { if (fm[0] === '%') { fm = fm.replace(/%APP_SETTINGS_HOME%/, '/User'); From 713afe41f6d1a578a4fab73712206cf2f5e129ba Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 12 Mar 2020 15:55:49 -0700 Subject: [PATCH 087/169] Adopt action-powered commands, needs-version-info, and copycat --- .github/commands.json | 179 +++++++++++++++++++++++ .github/commands.yml | 141 ------------------ .github/copycat.yml | 5 - .github/workflows/commands.yml | 21 +++ .github/workflows/copycat.yml | 10 +- .github/workflows/needs-version-info.yml | 20 +++ 6 files changed, 228 insertions(+), 148 deletions(-) create mode 100644 .github/commands.json delete mode 100644 .github/commands.yml delete mode 100644 .github/copycat.yml create mode 100644 .github/workflows/commands.yml create mode 100644 .github/workflows/needs-version-info.yml diff --git a/.github/commands.json b/.github/commands.json new file mode 100644 index 00000000000..43c602207cf --- /dev/null +++ b/.github/commands.json @@ -0,0 +1,179 @@ +[ + { + "type": "comment", + "name": "question", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "updateLabels", + "addLabel": "*question" + }, + { + "type": "label", + "name": "*question", + "action": "close", + "comment": "Please ask your question on [StackOverflow](https://aka.ms/vscodestackoverflow). We have a great community over [there](https://aka.ms/vscodestackoverflow). They have already answered thousands of questions and are happy to answer yours as well. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "*dev-question", + "action": "close", + "comment": "We have a great developer community [over on slack](https://aka.ms/vscode-dev-community) where extension authors help each other. This is a great place for you to ask questions and find support.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "*extension-candidate", + "action": "close", + "comment": "We try to keep VS Code lean and we think the functionality you're asking for is great for a VS Code extension. Maybe you can already find one that suits you in the [VS Code Marketplace](https://aka.ms/vscodemarketplace). Just in case, in a few simple steps you can get started [writing your own extension](https://aka.ms/vscodewritingextensions). See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "*not-reproducible", + "action": "close", + "comment": "We closed this issue because we are unable to reproduce the problem with the steps you describe. Chances are we've already fixed your problem in a recent version of VS Code. If not, please ask us to reopen the issue and provide us with more detail. Our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines might help you with that.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "*out-of-scope", + "action": "close", + "comment": "We closed this issue because we don't plan to address it in the foreseeable future. You can find more detailed information about our decision-making process [here](https://aka.ms/vscode-out-of-scope). If you disagree and feel that this issue is crucial: We are happy to listen and to reconsider.\n\nIf you wonder what we are up to, please see our [roadmap](https://aka.ms/vscoderoadmap) and [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nThanks for your understanding and happy coding!" + }, + { + "type": "comment", + "name": "causedByExtension", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "updateLabels", + "addLabel": "*caused-by-extension" + }, + { + "type": "label", + "name": "*caused-by-extension", + "action": "close", + "comment": "This issue is caused by an extension, please file it with the repository (or contact) the extension has linked in its overview in VS Code or the [marketplace](https://aka.ms/vscodemarketplace) for VS Code. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "*as-designed", + "action": "close", + "comment": "The described behavior is how it is expected to work. If you disagree, please explain what is expected and what is not in more detail. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "*english-please", + "action": "close", + "comment": "This issue is being closed because its description is not in English, that makes it hard for us to work on it. Please open a new issue with an English description. You might find [Bing Translator](https://www.bing.com/translator) useful." + }, + { + "type": "comment", + "name": "duplicate", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "updateLabels", + "addLabel": "*duplicate" + }, + { + "type": "label", + "name": "*duplicate", + "action": "close", + "comment": "Thanks for creating this issue! We figured it's covering the same as another one we already have. Thus, we closed this one as a duplicate. You can search for existing issues [here](https://aka.ms/vscodeissuesearch). See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "confirm", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "updateLabels", + "addLabel": "confirmed", + "removeLabel": "confirmation-pending" + }, + { + "type": "comment", + "name": "confirmationPending", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "updateLabels", + "addLabel": "confirmation-pending", + "removeLabel": "confirmed" + }, + { + "type": "comment", + "name": "findDuplicates", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "comment", + "comment": "Potential duplicates:\n${potentialDuplicates}" + }, + { + "type": "comment", + "name": "needsMoreInfo", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "updateLabels", + "addLabel": "~needs more info" + }, + { + "type": "label", + "name": "~needs version info", + "action": "updateLabels", + "addLabel": "needs more info", + "removeLabel": "~needs version info", + "comment": "Thanks for creating this issue! We figured it's missing some basic information, such as a version number, or in some other way doesn't follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines. Please take the time to review these and update the issue.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "~needs more info", + "action": "updateLabels", + "addLabel": "needs more info", + "removeLabel": "~needs more info", + "comment": "Thanks for creating this issue! We figured it's missing some basic information or in some other way doesn't follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines. Please take the time to review these and update the issue.\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "a11ymas", + "allowUsers": [ + "AccessibilityTestingTeam-TCS", + "dixitsonali95", + "Mohini78", + "ChitrarupaSharma", + "mspatil110", + "umasarath52", + "v-umnaik" + ], + "action": "updateLabels", + "addLabel": "a11ymas" + }, + { + "type": "label", + "name": "*off-topic", + "action": "close", + "comment": "Thanks for creating this issue. We think this issue is unactionable or unrelated to the goals of this project. Please follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + } +] diff --git a/.github/commands.yml b/.github/commands.yml deleted file mode 100644 index 24ac951d6f0..00000000000 --- a/.github/commands.yml +++ /dev/null @@ -1,141 +0,0 @@ -{ - perform: true, - commands: [ - { - type: 'comment', - name: 'question', - allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], - action: 'updateLabels', - addLabel: '*question' - }, - { - type: 'label', - name: '*question', - allowTriggerByBot: true, - action: 'close', - comment: "Please ask your question on [StackOverflow](https://aka.ms/vscodestackoverflow). We have a great community over [there](https://aka.ms/vscodestackoverflow). They have already answered thousands of questions and are happy to answer yours as well. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" - }, - { - type: 'label', - name: '*dev-question', - allowTriggerByBot: true, - action: 'close', - comment: "We have a great developer community [over on slack](https://aka.ms/vscode-dev-community) where extension authors help each other. This is a great place for you to ask questions and find support.\n\nHappy Coding!" - }, - { - type: 'label', - name: '*extension-candidate', - allowTriggerByBot: true, - action: 'close', - comment: "We try to keep VS Code lean and we think the functionality you're asking for is great for a VS Code extension. Maybe you can already find one that suits you in the [VS Code Marketplace](https://aka.ms/vscodemarketplace). Just in case, in a few simple steps you can get started [writing your own extension](https://aka.ms/vscodewritingextensions). See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" - }, - { - type: 'label', - name: '*not-reproducible', - allowTriggerByBot: true, - action: 'close', - comment: "We closed this issue because we are unable to reproduce the problem with the steps you describe. Chances are we've already fixed your problem in a recent version of VS Code. If not, please ask us to reopen the issue and provide us with more detail. Our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines might help you with that.\n\nHappy Coding!" - }, - { - type: 'label', - name: '*out-of-scope', - allowTriggerByBot: true, - action: 'close', - comment: "We closed this issue because we don't plan to address it in the foreseeable future. You can find more detailed information about our decision-making process [here](https://aka.ms/vscode-out-of-scope). If you disagree and feel that this issue is crucial: We are happy to listen and to reconsider.\n\nIf you wonder what we are up to, please see our [roadmap](https://aka.ms/vscoderoadmap) and [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nThanks for your understanding and happy coding!" - }, - { - type: 'comment', - name: 'causedByExtension', - allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], - action: 'updateLabels', - addLabel: '*caused-by-extension' - }, - { - type: 'label', - name: '*caused-by-extension', - allowTriggerByBot: true, - action: 'close', - comment: "This issue is caused by an extension, please file it with the repository (or contact) the extension has linked in its overview in VS Code or the [marketplace](https://aka.ms/vscodemarketplace) for VS Code. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" - }, - { - type: 'label', - name: '*as-designed', - allowTriggerByBot: true, - action: 'close', - comment: "The described behavior is how it is expected to work. If you disagree, please explain what is expected and what is not in more detail. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" - }, - { - type: 'label', - name: '*english-please', - allowTriggerByBot: true, - action: 'close', - comment: "This issue is being closed because its description is not in English, that makes it hard for us to work on it. Please open a new issue with an English description. You might find [Bing Translator](https://www.bing.com/translator) useful." - }, - { - type: 'comment', - name: 'duplicate', - allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], - action: 'updateLabels', - addLabel: '*duplicate' - }, - { - type: 'label', - name: '*duplicate', - allowTriggerByBot: true, - action: 'close', - comment: "Thanks for creating this issue! We figured it's covering the same as another one we already have. Thus, we closed this one as a duplicate. You can search for existing issues [here](https://aka.ms/vscodeissuesearch). See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" - }, - { - type: 'comment', - name: 'confirm', - allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], - action: 'updateLabels', - addLabel: 'confirmed', - removeLabel: 'confirmation-pending' - }, - { - type: 'comment', - name: 'confirmationPending', - allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], - action: 'updateLabels', - addLabel: 'confirmation-pending', - removeLabel: 'confirmed' - }, - { - type: 'comment', - name: 'findDuplicates', - allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], - action: 'comment', - comment: "Potential duplicates:\n${potentialDuplicates}" - }, - { - type: 'comment', - name: 'needsMoreInfo', - allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], - action: 'updateLabels', - addLabel: 'needs more info', - comment: "Thanks for creating this issue! We figured it's missing some basic information or in some other way doesn't follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines. Please take the time to review these and update the issue.\n\nHappy Coding!" - }, - { - type: 'label', - name: '~needs more info', - action: 'updateLabels', - addLabel: 'needs more info', - removeLabel: '~needs more info', - comment: "Thanks for creating this issue! We figured it's missing some basic information or in some other way doesn't follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines. Please take the time to review these and update the issue.\n\nHappy Coding!" - }, - { - type: 'comment', - name: 'a11ymas', - allowUsers: ['AccessibilityTestingTeam-TCS', 'dixitsonali95', 'Mohini78', 'ChitrarupaSharma', 'mspatil110', 'umasarath52', 'v-umnaik'], - action: 'updateLabels', - addLabel: 'a11ymas' - }, - { - type: 'label', - name: '*off-topic', - action: 'close', - comment: "Thanks for creating this issue. We think this issue is unactionable or unrelated to the goals of this project. Please follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" - } - ] -} diff --git a/.github/copycat.yml b/.github/copycat.yml deleted file mode 100644 index 690c803bd0a..00000000000 --- a/.github/copycat.yml +++ /dev/null @@ -1,5 +0,0 @@ -{ - perform: true, - target_owner: 'chrmarti', - target_repo: 'testissues' -} diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml new file mode 100644 index 00000000000..9a31b98bfe2 --- /dev/null +++ b/.github/workflows/commands.yml @@ -0,0 +1,21 @@ +name: Commands +on: + issue_comment: + types: [created] + issues: + types: [labeled] + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'JacksonKearl/vscode-triage-github-actions' + ref: v2 + - name: Run Commands + uses: ./commands + with: + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + config-path: commands diff --git a/.github/workflows/copycat.yml b/.github/workflows/copycat.yml index fdd1046eeca..c4b706759f9 100644 --- a/.github/workflows/copycat.yml +++ b/.github/workflows/copycat.yml @@ -11,10 +11,16 @@ jobs: uses: actions/checkout@v2 with: repository: 'JacksonKearl/vscode-triage-github-actions' - ref: master - - name: Run CopyCat + ref: v2 + - name: Run CopyCat (JacksonKearl/testissues) uses: ./copycat with: token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} owner: JacksonKearl repo: testissues + - name: Run CopyCat (chrmarti/testissues) + uses: ./copycat + with: + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + owner: chrmarti + repo: testissues diff --git a/.github/workflows/needs-version-info.yml b/.github/workflows/needs-version-info.yml new file mode 100644 index 00000000000..b3cb0fc0a3c --- /dev/null +++ b/.github/workflows/needs-version-info.yml @@ -0,0 +1,20 @@ +name: Needs Version Info +on: + issues: + types: [opened] + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'JacksonKearl/vscode-triage-github-actions' + ref: v2 + - name: Run Needs Version Info + uses: ./needs-more-info + with: + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + matcher: '\b(\d\.\d{2,3}\.\d|insiders?|1\.\d\d\d?)\b' + label: ~needs version info From 265457655cdb1fb6dbf3bb1815f81f9bb6dde9cf Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 12 Mar 2020 16:01:45 -0700 Subject: [PATCH 088/169] Add version request to feature request template --- .github/ISSUE_TEMPLATE/feature_request.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 2dc1460b16f..012854c7309 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -8,3 +8,4 @@ about: Suggest an idea for this project + From c65ea4300d70afe962b24f281906c65a869d86d8 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 13 Mar 2020 00:42:15 +0100 Subject: [PATCH 089/169] update typescript-vscode-sh-plugin --- extensions/typescript-language-features/package.json | 2 +- extensions/typescript-language-features/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index b3f12439bc8..e803011bfb4 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -19,7 +19,7 @@ "jsonc-parser": "^2.2.1", "rimraf": "^2.6.3", "semver": "5.5.1", - "typescript-vscode-sh-plugin": "^0.6.8", + "typescript-vscode-sh-plugin": "^0.6.10", "vscode-extension-telemetry": "0.1.1", "vscode-nls": "^4.1.1" }, diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index 82daf1829dd..1934b8a6810 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -626,10 +626,10 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -typescript-vscode-sh-plugin@^0.6.8: - version "0.6.8" - resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.6.8.tgz#60d5025f2ab814496824ee997b5e9fc12c5b7f1a" - integrity sha512-XEh/GwBRsZKWQjPTODqWWiW8o8DyF7Yzfp/xvq1vyK5Z9JykFKAkx95BEmALv9x9dpc2RcLZHgVsKFXrtDABCw== +typescript-vscode-sh-plugin@^0.6.10: + version "0.6.10" + resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.6.10.tgz#f9fdac506a3adb698d52fd01723ec78e8a5fc09e" + integrity sha512-cYycpwLnYT2oS48tac+UvVRtIFHHTcHAz/g3N2HpYftuMEBvBcsGfe2SrlnrGCa1gMheTbo+twIHhsQu9ygdvg== uri-js@^4.2.2: version "4.2.2" From 9dae408dc32181e7725a4293e7a465cb57df91b1 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 12 Mar 2020 13:12:26 -0700 Subject: [PATCH 090/169] Switch webview api back to use delegate model For #77131 Going back the the delegate based model for a few reasons: - It gives us a better approach to add additional API hooks in the future (such as for rename) - In practive, the capabilities were almost always the same as the `userData` on the document. It is rather confusing to have both `userData` and the capabilities 'on' the document --- extensions/image-preview/src/preview.ts | 4 +- .../src/features/previewManager.ts | 4 +- src/vs/vscode.proposed.d.ts | 60 ++++++++----- .../api/browser/mainThreadWebview.ts | 8 +- .../workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostWebview.ts | 84 ++++++++++--------- 6 files changed, 92 insertions(+), 70 deletions(-) diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts index fc43a136a01..38f6f5231b5 100644 --- a/extensions/image-preview/src/preview.ts +++ b/extensions/image-preview/src/preview.ts @@ -27,8 +27,8 @@ export class PreviewManager implements vscode.CustomEditorProvider { private readonly zoomStatusBarEntry: ZoomStatusBarEntry, ) { } - public async resolveCustomDocument(_document: vscode.CustomDocument): Promise { - return {}; + public async resolveCustomDocument(_document: vscode.CustomDocument): Promise { + // noop } public async resolveCustomEditor( diff --git a/extensions/markdown-language-features/src/features/previewManager.ts b/extensions/markdown-language-features/src/features/previewManager.ts index a6fc8ec528c..a9403313931 100644 --- a/extensions/markdown-language-features/src/features/previewManager.ts +++ b/extensions/markdown-language-features/src/features/previewManager.ts @@ -148,8 +148,8 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview this.registerDynamicPreview(preview); } - public async resolveCustomDocument(_document: vscode.CustomDocument): Promise { - return {}; + public async resolveCustomDocument(_document: vscode.CustomDocument): Promise { + // noop } public async resolveCustomTextEditor( diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 421a285c25c..262702d2cc4 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1222,78 +1222,88 @@ declare module 'vscode' { // - Should we expose edits? // - More properties from `TextDocument`? - /** - * Defines the capabilities of a custom webview editor. - */ - interface CustomEditorCapabilities { - /** - * Defines the editing capability of a custom webview document. - * - * When not provided, the document is considered readonly. - */ - readonly editing?: CustomEditorEditingCapability; - } - /** * Defines the editing capability of a custom webview editor. This allows the webview editor to hook into standard * editor events such as `undo` or `save`. * * @param EditType Type of edits. */ - interface CustomEditorEditingCapability { + interface CustomEditorEditingDelegate { /** * Save the resource. * + * @param document Document to save. * @param cancellation Token that signals the save is no longer required (for example, if another save was triggered). * * @return Thenable signaling that the save has completed. */ - save(cancellation: CancellationToken): Thenable; + save(document: CustomDocument, cancellation: CancellationToken): Thenable; /** * Save the existing resource at a new path. * + * @param document Document to save. * @param targetResource Location to save to. * * @return Thenable signaling that the save has completed. */ - saveAs(targetResource: Uri): Thenable; + saveAs(document: CustomDocument, targetResource: Uri): Thenable; /** * Event triggered by extensions to signal to VS Code that an edit has occurred. */ - readonly onDidEdit: Event; + readonly onDidEdit: Event<{ + /** + * Document the edit is for. + */ + readonly document: CustomDocument; + + /** + * Object that describes the edit. + * + * Edit objects are passed back to your extension in `undoEdits`, `applyEdits`, and `revert`. + */ + readonly edit: EditType; + + /** + * Display name describing the edit. + */ + readonly label?: string; + }>; /** * Apply a set of edits. * * Note that is not invoked when `onDidEdit` is called because `onDidEdit` implies also updating the view to reflect the edit. * + * @param document Document to apply edits to. * @param edit Array of edits. Sorted from oldest to most recent. * * @return Thenable signaling that the change has completed. */ - applyEdits(edits: readonly EditType[]): Thenable; + applyEdits(document: CustomDocument, edits: readonly EditType[]): Thenable; /** * Undo a set of edits. * * This is triggered when a user undoes an edit. * + * @param document Document to undo edits from. * @param edit Array of edits. Sorted from most recent to oldest. * * @return Thenable signaling that the change has completed. */ - undoEdits(edits: readonly EditType[]): Thenable; + undoEdits(document: CustomDocument, edits: readonly EditType[]): Thenable; /** * Revert the file to its last saved state. * + * @param document Document to revert. * @param change Added or applied edits. * * @return Thenable signaling that the change has completed. */ - revert(change: { + revert(document: CustomDocument, change: { readonly undoneEdits: readonly EditType[]; readonly appliedEdits: readonly EditType[]; }): Thenable; @@ -1311,12 +1321,13 @@ declare module 'vscode' { * made in quick succession, `backup` is only triggered after the last one. `backup` is not invoked when * `auto save` is enabled (since auto save already persists resource ). * + * @param document Document to revert. * @param cancellation Token that signals the current backup since a new backup is coming in. It is up to your * extension to decided how to respond to cancellation. If for example your extension is backing up a large file * in an operation that takes time to complete, your extension may decide to finish the ongoing backup rather * than cancelling it to ensure that VS Code has some valid backup. */ - backup(cancellation: CancellationToken): Thenable; + backup(document: CustomDocument, cancellation: CancellationToken): Thenable; } /** @@ -1375,7 +1386,7 @@ declare module 'vscode' { * * @return The capabilities of the resolved document. */ - resolveCustomDocument(document: CustomDocument): Thenable; + resolveCustomDocument(document: CustomDocument): Thenable; /** * Resolve a webview editor for a given resource. @@ -1393,6 +1404,13 @@ declare module 'vscode' { * @return Thenable indicating that the webview editor has been resolved. */ resolveCustomEditor(document: CustomDocument, webviewPanel: WebviewPanel): Thenable; + + /** + * Defines the editing capability of a custom webview document. + * + * When not provided, the document is considered readonly. + */ + readonly editingDelegate?: CustomEditorEditingDelegate; } /** diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 52ea58cb549..77193cf90b1 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -366,14 +366,14 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma return this._customEditorService.models.add(resource, viewType, model); } - public async $onDidEdit(resourceComponents: UriComponents, viewType: string, editId: number): Promise { + public async $onDidEdit(resourceComponents: UriComponents, viewType: string, editId: number, label: string | undefined): Promise { const resource = URI.revive(resourceComponents); const model = await this._customEditorService.models.get(resource, viewType); if (!model || !(model instanceof MainThreadCustomEditorModel)) { throw new Error('Could not find model for webview editor'); } - model.pushEdit(editId); + model.pushEdit(editId, label); } private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) { @@ -604,7 +604,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod return this._viewType; } - public pushEdit(editId: number) { + public pushEdit(editId: number, label: string | undefined) { if (!this._editable) { throw new Error('Document is not editable'); } @@ -617,7 +617,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod this._undoService.pushElement({ type: UndoRedoElementType.Resource, resource: this.resource, - label: 'Edit', // TODO: get this from extensions? + label: label ?? 'Edit', undo: async () => { if (!this._editable) { return; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 2e93f3aa9b8..cbbba0f9265 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -592,7 +592,7 @@ export interface MainThreadWebviewsShape extends IDisposable { $registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void; $unregisterEditorProvider(viewType: string): void; - $onDidEdit(resource: UriComponents, viewType: string, editId: number): void; + $onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void; } export interface WebviewPanelViewStateData { diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 3324e32c8ae..f4dc637a2fe 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -5,7 +5,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import * as modes from 'vs/editor/common/modes'; @@ -248,25 +248,31 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa class CustomDocument extends Disposable implements vscode.CustomDocument { - public static create(proxy: MainThreadWebviewsShape, viewType: string, uri: vscode.Uri) { - return Object.seal(new CustomDocument(proxy, viewType, uri)); + public static create( + viewType: string, + uri: vscode.Uri, + editingDelegate: vscode.CustomEditorEditingDelegate | undefined + ) { + return Object.seal(new CustomDocument(viewType, uri, editingDelegate)); } // Explicitly initialize all properties as we seal the object after creation! readonly #_edits = new Cache('edits'); - readonly #proxy: MainThreadWebviewsShape; readonly #viewType: string; readonly #uri: vscode.Uri; + readonly #editingDelegate: vscode.CustomEditorEditingDelegate | undefined; - #capabilities: vscode.CustomEditorCapabilities | undefined = undefined; - - private constructor(proxy: MainThreadWebviewsShape, viewType: string, uri: vscode.Uri) { + private constructor( + viewType: string, + uri: vscode.Uri, + editingDelegate: vscode.CustomEditorEditingDelegate | undefined, + ) { super(); - this.#proxy = proxy; this.#viewType = viewType; this.#uri = uri; + this.#editingDelegate = editingDelegate; } dispose() { @@ -289,47 +295,35 @@ class CustomDocument extends Disposable implements vscode.CustomDocument { //#region Internal - /** @internal*/ _setCapabilities(capabilities: vscode.CustomEditorCapabilities) { - if (this.#capabilities) { - throw new Error('Capabilities already provided'); - } - - this.#capabilities = capabilities; - capabilities.editing?.onDidEdit(edit => { - const id = this.#_edits.add([edit]); - this.#proxy.$onDidEdit(this.uri, this.viewType, id); - }); - } - /** @internal*/ async _revert(changes: { undoneEdits: number[], redoneEdits: number[] }) { - const editing = this.getEditingCapability(); + const editing = this.getEditingDelegate(); const undoneEdits = changes.undoneEdits.map(id => this.#_edits.get(id, 0)); const appliedEdits = changes.redoneEdits.map(id => this.#_edits.get(id, 0)); - return editing.revert({ undoneEdits, appliedEdits }); + return editing.revert(this, { undoneEdits, appliedEdits }); } /** @internal*/ _undo(editId: number) { - const editing = this.getEditingCapability(); + const editing = this.getEditingDelegate(); const edit = this.#_edits.get(editId, 0); - return editing.undoEdits([edit]); + return editing.undoEdits(this, [edit]); } /** @internal*/ _redo(editId: number) { - const editing = this.getEditingCapability(); + const editing = this.getEditingDelegate(); const edit = this.#_edits.get(editId, 0); - return editing.applyEdits([edit]); + return editing.applyEdits(this, [edit]); } /** @internal*/ _save(cancellation: CancellationToken) { - return this.getEditingCapability().save(cancellation); + return this.getEditingDelegate().save(this, cancellation); } /** @internal*/ _saveAs(target: vscode.Uri) { - return this.getEditingCapability().saveAs(target); + return this.getEditingDelegate().saveAs(this, target); } /** @internal*/ _backup(cancellation: CancellationToken) { - return this.getEditingCapability().backup(cancellation); + return this.getEditingDelegate().backup(this, cancellation); } /** @internal*/ _disposeEdits(editIds: number[]) { @@ -338,13 +332,17 @@ class CustomDocument extends Disposable implements vscode.CustomDocument { } } + /** @internal*/ _pushEdit(edit: unknown): number { + return this.#_edits.add([edit]); + } + //#endregion - private getEditingCapability(): vscode.CustomEditorEditingCapability { - if (!this.#capabilities?.editing) { + private getEditingDelegate(): vscode.CustomEditorEditingDelegate { + if (!this.#editingDelegate) { throw new Error('Document is not editable'); } - return this.#capabilities.editing; + return this.#editingDelegate; } } @@ -487,17 +485,24 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { provider: vscode.CustomEditorProvider | vscode.CustomTextEditorProvider, options: vscode.WebviewPanelOptions | undefined = {} ): vscode.Disposable { - let disposable: vscode.Disposable; + const disposables = new DisposableStore(); if ('resolveCustomTextEditor' in provider) { - disposable = this._editorProviders.addTextProvider(viewType, extension, provider); + disposables.add(this._editorProviders.addTextProvider(viewType, extension, provider)); this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options); } else { - disposable = this._editorProviders.addCustomProvider(viewType, extension, provider); + disposables.add(this._editorProviders.addCustomProvider(viewType, extension, provider)); this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options); + if (provider.editingDelegate) { + disposables.add(provider.editingDelegate.onDidEdit(e => { + const document = e.document; + const editId = (document as CustomDocument)._pushEdit(e.edit); + this._proxy.$onDidEdit(document.uri, document.viewType, editId, e.label); + })); + } } return VSCodeDisposable.from( - disposable, + disposables, new VSCodeDisposable(() => { this._proxy.$unregisterEditorProvider(viewType); })); @@ -592,12 +597,11 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } const revivedResource = URI.revive(resource); - const document = CustomDocument.create(this._proxy, viewType, revivedResource); - const capabilities = await entry.provider.resolveCustomDocument(document); - document._setCapabilities(capabilities); + const document = CustomDocument.create(viewType, revivedResource, entry.provider.editingDelegate); + await entry.provider.resolveCustomDocument(document); this._documents.add(document); return { - editable: !!capabilities.editing + editable: !!entry.provider.editingDelegate, }; } From b66d56602fcd4f99875a24339b87fa578808abb0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 12 Mar 2020 18:17:18 -0700 Subject: [PATCH 091/169] Implement terminal link handler API Part of #91606 --- src/vs/vscode.proposed.d.ts | 16 +++++++ .../api/browser/mainThreadTerminalService.ts | 23 ++++++++- .../workbench/api/common/extHost.api.impl.ts | 4 ++ .../workbench/api/common/extHost.protocol.ts | 3 ++ .../api/common/extHostTerminalService.ts | 35 ++++++++++++++ .../contrib/terminal/browser/terminal.ts | 25 ++++++++++ .../terminal/browser/terminalInstance.ts | 31 +++++++----- .../terminal/browser/terminalLinkHandler.ts | 45 +++++++++++++++-- .../terminal/browser/terminalService.ts | 48 ++++++++++++++++++- .../test/browser/terminalLinkHandler.test.ts | 14 +++--- 10 files changed, 219 insertions(+), 25 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 421a285c25c..6dbe9fb8999 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1086,6 +1086,22 @@ declare module 'vscode' { //#endregion + //#region Terminal link handlers https://github.com/microsoft/vscode/issues/91606 + + export namespace window { + export function registerTerminalLinkHandler(handler: TerminalLinkHandler): Disposable; + } + + export interface TerminalLinkHandler { + /** + * @return true when the link was handled (and should not be considered by + * other providers including the default), false when the link was not handled. + */ + handleLink(terminal: Terminal, link: string): ProviderResult; + } + + //#endregion + //#region Joh -> exclusive document filters export interface DocumentFilter { diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 372db8a6af2..323cdc2fa47 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DisposableStore, Disposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest } from 'vs/workbench/contrib/terminal/common/terminal'; import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, IShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; import { StopWatch } from 'vs/base/common/stopwatch'; -import { ITerminalInstanceService, ITerminalService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstanceService, ITerminalService, ITerminalInstance, ITerminalBeforeHandleLinkEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; @@ -23,6 +23,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape private readonly _terminalProcesses = new Map>(); private readonly _terminalProcessesReady = new Map void>(); private _dataEventTracker: TerminalDataEventTracker | undefined; + private _linkHandler: IDisposable | undefined; constructor( extHostContext: IExtHostContext, @@ -146,6 +147,24 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } } + public $startHandlingLinks(): void { + console.log('start'); + this._linkHandler?.dispose(); + this._linkHandler = this._terminalService.addLinkHandler(this._remoteAuthority || '', e => this._handleLink(e)); + } + + public $stopHandlingLinks(): void { + console.log('stop'); + this._linkHandler?.dispose(); + } + + private async _handleLink(e: ITerminalBeforeHandleLinkEvent): Promise { + if (!e.terminal) { + return false; + } + return this._proxy.$handleLink(e.terminal.id, e.link); + } + private _onActiveTerminalChanged(terminalId: number | null): void { this._proxy.$acceptActiveTerminalChanged(terminalId); } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index a95e4ac7b1e..d8d58647f09 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -564,6 +564,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } return extHostTerminalService.createTerminal(nameOrOptions, shellPath, shellArgs); }, + registerTerminalLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable { + checkProposedApiEnabled(extension); + return extHostTerminalService.registerLinkHandler(handler); + }, registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider): vscode.Disposable { return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider, extension); }, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 2e93f3aa9b8..45812459a6f 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -431,6 +431,8 @@ export interface MainThreadTerminalServiceShape extends IDisposable { $show(terminalId: number, preserveFocus: boolean): void; $startSendingDataEvents(): void; $stopSendingDataEvents(): void; + $startHandlingLinks(): void; + $stopHandlingLinks(): void; // Process $sendProcessTitle(terminalId: number, title: string): void; @@ -1310,6 +1312,7 @@ export interface ExtHostTerminalServiceShape { $acceptWorkspacePermissionsChanged(isAllowed: boolean): void; $getAvailableShells(): Promise; $getDefaultShellAndArgs(useAutomationShell: boolean): Promise; + $handleLink(id: number, link: string): Promise; } export interface ExtHostSCMShape { diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index bccacd0543f..94eb74c8447 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -14,6 +14,7 @@ import { timeout } from 'vs/base/common/async'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable as VSCodeDisposable } from './extHostTypes'; export interface IExtHostTerminalService extends ExtHostTerminalServiceShape { @@ -34,6 +35,7 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape { attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void; getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string; getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string; + registerLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable } export const IExtHostTerminalService = createDecorator('IExtHostTerminalService'); @@ -535,6 +537,39 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ return id; } + private _linkHandlers: Set = new Set(); + public registerLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable { + this._linkHandlers.add(handler); + if (this._linkHandlers.size === 1) { + this._proxy.$startHandlingLinks(); + } + return new VSCodeDisposable(() => { + this._linkHandlers.delete(handler); + if (this._linkHandlers.size === 0) { + this._proxy.$stopHandlingLinks(); + } + }); + } + + public async $handleLink(id: number, link: string): Promise { + const terminal = this._getTerminalById(id); + if (!terminal) { + return false; + } + + // Call each handler synchronously so multiple handlers aren't triggered at once + const it = this._linkHandlers.values(); + let next = it.next(); + while (!next.done) { + const handled = await next.value.handleLink(terminal, link); + if (handled) { + return true; + } + next = it.next(); + } + return false; + } + private _onProcessExit(id: number, exitCode: number | undefined): void { this._bufferer.stopBuffering(id); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index bac468cd33a..e28e9e597e5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -131,6 +131,14 @@ export interface ITerminalService { findNext(): void; findPrevious(): void; + /** + * Link handlers can be registered here to allow intercepting links clicked in the terminal. + * When a link is clicked, the link will be considered handled when the first interceptor + * resolves with true. It will be considered not handled when _all_ link handlers resolve with + * false, or 3 seconds have elapsed. + */ + addLinkHandler(key: string, callback: TerminalLinkHandlerCallback): IDisposable; + selectDefaultWindowsShell(): Promise; setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; @@ -179,6 +187,18 @@ export enum WindowsShellType { } export type TerminalShellType = WindowsShellType | undefined; +export const LINK_INTERCEPT_THRESHOLD = 3000; + +export interface ITerminalBeforeHandleLinkEvent { + terminal?: ITerminalInstance; + /** The text of the link */ + link: string; + /** Call with whether the link was handled by the interceptor */ + resolve(wasHandled: boolean): void; +} + +export type TerminalLinkHandlerCallback = (e: ITerminalBeforeHandleLinkEvent) => Promise; + export interface ITerminalInstance { /** * The ID of the terminal instance, this is an arbitrary number only used to identify the @@ -240,6 +260,11 @@ export interface ITerminalInstance { */ onExit: Event; + /** + * Attach a listener to intercept and handle link clicks in the terminal. + */ + onBeforeHandleLink: Event; + readonly exitCode: number | undefined; processReady: Promise; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index c79d87d2150..153e95815ab 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -30,7 +30,7 @@ import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGR import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalLinkHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { ITerminalInstanceService, ITerminalInstance, TerminalShellType } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstanceService, ITerminalInstance, TerminalShellType, ITerminalBeforeHandleLinkEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; import { Terminal as XTermTerminal, IBuffer, ITerminalAddon } from 'xterm'; import { SearchAddon, ISearchOptions } from 'xterm-addon-search'; @@ -250,28 +250,30 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { public get commandTracker(): CommandTrackerAddon | undefined { return this._commandTrackerAddon; } public get navigationMode(): INavigationMode | undefined { return this._navigationModeAddon; } - private readonly _onExit = new Emitter(); + private readonly _onExit = this._register(new Emitter()); public get onExit(): Event { return this._onExit.event; } - private readonly _onDisposed = new Emitter(); + private readonly _onDisposed = this._register(new Emitter()); public get onDisposed(): Event { return this._onDisposed.event; } - private readonly _onFocused = new Emitter(); + private readonly _onFocused = this._register(new Emitter()); public get onFocused(): Event { return this._onFocused.event; } - private readonly _onProcessIdReady = new Emitter(); + private readonly _onProcessIdReady = this._register(new Emitter()); public get onProcessIdReady(): Event { return this._onProcessIdReady.event; } - private readonly _onTitleChanged = new Emitter(); + private readonly _onTitleChanged = this._register(new Emitter()); public get onTitleChanged(): Event { return this._onTitleChanged.event; } - private readonly _onData = new Emitter(); + private readonly _onData = this._register(new Emitter()); public get onData(): Event { return this._onData.event; } - private readonly _onLineData = new Emitter(); + private readonly _onLineData = this._register(new Emitter()); public get onLineData(): Event { return this._onLineData.event; } - private readonly _onRequestExtHostProcess = new Emitter(); + private readonly _onRequestExtHostProcess = this._register(new Emitter()); public get onRequestExtHostProcess(): Event { return this._onRequestExtHostProcess.event; } - private readonly _onDimensionsChanged = new Emitter(); + private readonly _onDimensionsChanged = this._register(new Emitter()); public get onDimensionsChanged(): Event { return this._onDimensionsChanged.event; } - private readonly _onMaximumDimensionsChanged = new Emitter(); + private readonly _onMaximumDimensionsChanged = this._register(new Emitter()); public get onMaximumDimensionsChanged(): Event { return this._onMaximumDimensionsChanged.event; } - private readonly _onFocus = new Emitter(); + private readonly _onFocus = this._register(new Emitter()); public get onFocus(): Event { return this._onFocus.event; } + private readonly _onBeforeHandleLink = this._register(new Emitter()); + public get onBeforeHandleLink(): Event { return this._onBeforeHandleLink.event; } public constructor( private readonly _terminalFocusContextKey: IContextKey, @@ -523,6 +525,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }); } this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, xterm, this._processManager, this._configHelper); + this._linkHandler.onBeforeHandleLink(e => { + console.log('terminalinstance fire'); + e.terminal = this; + this._onBeforeHandleLink.fire(e); + }); }); this._commandTrackerAddon = new CommandTrackerAddon(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts index 18169644b84..114ec81dbf4 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts @@ -16,9 +16,11 @@ import { IFileService } from 'vs/platform/files/common/files'; import { Terminal, ILinkMatcherOptions, IViewportRange } from 'xterm'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { posix, win32 } from 'vs/base/common/path'; -import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstanceService, ITerminalBeforeHandleLinkEvent, LINK_INTERCEPT_THRESHOLD } from 'vs/workbench/contrib/terminal/browser/terminal'; import { OperatingSystem, isMacintosh } from 'vs/base/common/platform'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { Emitter, Event } from 'vs/base/common/event'; +import { ILogService } from 'vs/platform/log/common/log'; const pathPrefix = '(\\.\\.?|\\~)'; const pathSeparatorClause = '\\/'; @@ -74,6 +76,18 @@ export class TerminalLinkHandler { private _gitDiffPostImagePattern: RegExp; private readonly _tooltipCallback: (event: MouseEvent, uri: string, location: IViewportRange) => boolean | void; private readonly _leaveCallback: () => void; + private _hasBeforeHandleLinkListeners = false; + + private readonly _onBeforeHandleLink = new Emitter({ + onFirstListenerAdd: () => this._hasBeforeHandleLinkListeners = true, + onLastListenerRemove: () => this._hasBeforeHandleLinkListeners = false + }); + /** + * Allows intercepting links and handling them outside of the default link handler. When fired + * the listener has a set amount of time to handle the link or the default handler will fire. + * This was designed to only be handled by a single listener. + */ + public get onBeforeHandleLink(): Event { return this._onBeforeHandleLink.event; } constructor( private _xterm: Terminal, @@ -83,7 +97,8 @@ export class TerminalLinkHandler { @IEditorService private readonly _editorService: IEditorService, @IConfigurationService private readonly _configurationService: IConfigurationService, @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, - @IFileService private readonly _fileService: IFileService + @IFileService private readonly _fileService: IFileService, + @ILogService private readonly _logService: ILogService ) { // Matches '--- a/src/file1', capturing 'src/file1' in group 1 this._gitDiffPreImagePattern = /^--- a\/(\S*)/; @@ -213,6 +228,7 @@ export class TerminalLinkHandler { public dispose(): void { this._hoverDisposables.dispose(); + this._onBeforeHandleLink.dispose(); } private _wrapLinkHandler(handler: (uri: string) => boolean | void): XtermLinkMatcherHandler { @@ -245,10 +261,33 @@ export class TerminalLinkHandler { } private _handleLocalLink(link: string): PromiseLike { - return this._resolvePath(link).then(resolvedLink => { + return this._resolvePath(link).then(async resolvedLink => { if (!resolvedLink) { return Promise.resolve(null); } + + // Allow the link to be intercepted if there are listeners + if (this._hasBeforeHandleLinkListeners) { + const wasHandled = await new Promise(r => { + const timeoutId = setTimeout(() => { + canceled = true; + this._logService.error('An extension intecepted a terminal link but did not return'); + r(false); + }, LINK_INTERCEPT_THRESHOLD); + let canceled = false; + const resolve = (handled: boolean) => { + if (!canceled) { + clearTimeout(timeoutId); + r(handled); + } + }; + this._onBeforeHandleLink.fire({ link, resolve }); + }); + if (wasHandled) { + return; + } + } + const lineColumnInfo: LineColumnInfo = this.extractLineColumnInfo(link); const selection: ITextEditorSelection = { startLineNumber: lineColumnInfo.lineNumber, diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 2dfb87a8d90..1ffb1e804f4 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -15,7 +15,7 @@ import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab'; import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; -import { ITerminalService, ITerminalInstance, ITerminalTab, TerminalShellType, WindowsShellType } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalService, ITerminalInstance, ITerminalTab, TerminalShellType, WindowsShellType, TerminalLinkHandlerCallback, LINK_INTERCEPT_THRESHOLD } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput'; @@ -30,6 +30,7 @@ import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; import { find } from 'vs/base/common/arrays'; import { timeout } from 'vs/base/common/async'; import { IViewsService } from 'vs/workbench/common/views'; +import { IDisposable } from 'vs/base/common/lifecycle'; interface IExtHostReadyEntry { promise: Promise; @@ -411,6 +412,51 @@ export class TerminalService implements ITerminalService { instance.addDisposable(instance.onDimensionsChanged(() => this._onInstanceDimensionsChanged.fire(instance))); instance.addDisposable(instance.onMaximumDimensionsChanged(() => this._onInstanceMaximumDimensionsChanged.fire(instance))); instance.addDisposable(instance.onFocus(this._onActiveInstanceChanged.fire, this._onActiveInstanceChanged)); + instance.addDisposable(instance.onBeforeHandleLink(async e => { + // No link handlers have been registered + const keys = Object.keys(this._linkHandlers); + if (keys.length === 0) { + e.resolve(false); + return; + } + + // Fire each link interceptor and wait for either a true, all false or the cancel time + let resolved = false; + const promises: Promise[] = []; + const timeout = setTimeout(() => { + resolved = true; + e.resolve(false); + }, LINK_INTERCEPT_THRESHOLD); + for (let i = 0; i < keys.length; i++) { + const p = this._linkHandlers[keys[i]](e); + p.then(handled => { + if (!resolved && handled) { + resolved = true; + clearTimeout(timeout); + e.resolve(true); + } + }); + promises.push(p); + } + await Promise.all(promises); + if (!resolved) { + resolved = true; + clearTimeout(timeout); + e.resolve(false); + } + })); + } + + private _linkHandlers: { [key: string]: TerminalLinkHandlerCallback } = {}; + public addLinkHandler(key: string, callback: TerminalLinkHandlerCallback): IDisposable { + this._linkHandlers[key] = callback; + return { + dispose: () => { + if (this._linkHandlers[key] === callback) { + delete this._linkHandlers[key]; + } + } + }; } private _getTabForInstance(instance: ITerminalInstance): ITerminalTab | undefined { diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts index dcfcf378d02..5e114e9d60a 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts @@ -81,7 +81,7 @@ suite('Workbench - TerminalLinkHandler', () => { const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Windows, userHome: '' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); function testLink(link: string, linkUrl: string, lineNo?: string, columnNo?: string) { assert.equal(terminalLinkHandler.extractLinkUrl(link), linkUrl); assert.equal(terminalLinkHandler.extractLinkUrl(`:${link}:`), linkUrl); @@ -157,7 +157,7 @@ suite('Workbench - TerminalLinkHandler', () => { const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); function testLink(link: string, linkUrl: string, lineNo?: string, columnNo?: string) { assert.equal(terminalLinkHandler.extractLinkUrl(link), linkUrl); assert.equal(terminalLinkHandler.extractLinkUrl(`:${link}:`), linkUrl); @@ -225,7 +225,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Windows, userHome: 'C:\\Users\\Me' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); linkHandler.processCwd = 'C:\\base'; assert.equal(linkHandler.preprocessPath('./src/file1'), 'C:\\base\\src\\file1'); @@ -238,7 +238,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Windows, userHome: 'C:\\Users\\M e' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); linkHandler.processCwd = 'C:\\base dir'; assert.equal(linkHandler.preprocessPath('./src/file1'), 'C:\\base dir\\src\\file1'); @@ -252,7 +252,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '/home/me' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); linkHandler.processCwd = '/base'; assert.equal(linkHandler.preprocessPath('./src/file1'), '/base/src/file1'); @@ -265,7 +265,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '/home/me' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); assert.equal(linkHandler.preprocessPath('./src/file1'), null); assert.equal(linkHandler.preprocessPath('src/file2'), null); @@ -279,7 +279,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); function assertAreGoodMatches(matches: RegExpMatchArray | null) { if (matches) { From efd7548df65de8e775015b67863190e6bd4934fc Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 12 Mar 2020 20:38:53 -0700 Subject: [PATCH 092/169] Extract sine types for custom editors For #77131 Extracts a few inline types so that extensions can use them --- src/vs/vscode.proposed.d.ts | 65 +++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 262702d2cc4..65b2f2caa88 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1252,24 +1252,7 @@ declare module 'vscode' { /** * Event triggered by extensions to signal to VS Code that an edit has occurred. */ - readonly onDidEdit: Event<{ - /** - * Document the edit is for. - */ - readonly document: CustomDocument; - - /** - * Object that describes the edit. - * - * Edit objects are passed back to your extension in `undoEdits`, `applyEdits`, and `revert`. - */ - readonly edit: EditType; - - /** - * Display name describing the edit. - */ - readonly label?: string; - }>; + readonly onDidEdit: Event>; /** * Apply a set of edits. @@ -1299,14 +1282,11 @@ declare module 'vscode' { * Revert the file to its last saved state. * * @param document Document to revert. - * @param change Added or applied edits. + * @param edits Added or applied edits. * * @return Thenable signaling that the change has completed. */ - revert(document: CustomDocument, change: { - readonly undoneEdits: readonly EditType[]; - readonly appliedEdits: readonly EditType[]; - }): Thenable; + revert(document: CustomDocument, edits: CustomDocumentRevert): Thenable; /** * Back up the resource in its current state. @@ -1330,6 +1310,43 @@ declare module 'vscode' { backup(document: CustomDocument, cancellation: CancellationToken): Thenable; } + /** + * Event triggered by extensions to signal to VS Code that an edit has occurred on a CustomDocument``. + */ + interface CustomDocumentEditEvent { + /** + * Document the edit is for. + */ + readonly document: CustomDocument; + + /** + * Object that describes the edit. + * + * Edit objects are passed back to your extension in `undoEdits`, `applyEdits`, and `revert`. + */ + readonly edit: EditType; + + /** + * Display name describing the edit. + */ + readonly label?: string; + } + + /** + * Data about a revert for a `CustomDocument`. + */ + interface CustomDocumentRevert { + /** + * List of edits that were undone to get the document back to its on disk state. + */ + readonly undoneEdits: readonly EditType[]; + + /** + * List of edits that were reapplied to get the document back to its on disk state. + */ + readonly appliedEdits: readonly EditType[]; + } + /** * Represents a custom document used by a `CustomEditorProvider`. * @@ -1456,7 +1473,7 @@ declare module 'vscode' { export function registerCustomEditorProvider( viewType: string, provider: CustomEditorProvider | CustomTextEditorProvider, - webviewOptions?: WebviewPanelOptions, + webviewOptions?: WebviewPanelOptions, // TODO: move this onto provider? ): Disposable; } From a79af6ef029fbbaac5c3012c6c1f253f5542346d Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 12 Mar 2020 20:48:36 -0700 Subject: [PATCH 093/169] Make sure we localize the default label --- src/vs/workbench/api/browser/mainThreadWebview.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 77193cf90b1..9c494d3f4d2 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -617,7 +617,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod this._undoService.pushElement({ type: UndoRedoElementType.Resource, resource: this.resource, - label: label ?? 'Edit', + label: label ?? localize('defaultEditLabel', "Edit"), undo: async () => { if (!this._editable) { return; From 8383eec1b6f1bed10e491fa6f8ea6b9eae8eeccb Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 12 Mar 2020 20:51:19 -0700 Subject: [PATCH 094/169] Extract undo/redo --- .../api/browser/mainThreadWebview.ts | 61 ++++++++++--------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 9c494d3f4d2..3f5ee9c4f52 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -600,6 +600,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod readonly onDidChangeContent: Event = this._onDidChangeContent.event; //#endregion + public get viewType() { return this._viewType; } @@ -618,39 +619,43 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod type: UndoRedoElementType.Resource, resource: this.resource, label: label ?? localize('defaultEditLabel', "Edit"), - undo: async () => { - if (!this._editable) { - return; - } + undo: () => this.undo(), + redo: () => this.redo(), + }); + } - if (this._currentEditIndex < 0) { - // nothing to undo - return; - } + private async undo(): Promise { + if (!this._editable) { + return; + } - const undoneEdit = this._edits[this._currentEditIndex]; - await this._proxy.$undo(this.resource, this.viewType, undoneEdit); + if (this._currentEditIndex < 0) { + // nothing to undo + return; + } - this.change(() => { - --this._currentEditIndex; - }); - }, - redo: async () => { - if (!this._editable) { - return; - } + const undoneEdit = this._edits[this._currentEditIndex]; + await this._proxy.$undo(this.resource, this.viewType, undoneEdit); - if (this._currentEditIndex >= this._edits.length - 1) { - // nothing to redo - return; - } + this.change(() => { + --this._currentEditIndex; + }); + } - const redoneEdit = this._edits[this._currentEditIndex + 1]; - await this._proxy.$redo(this.resource, this.viewType, redoneEdit); - this.change(() => { - ++this._currentEditIndex; - }); - } + private async redo(): Promise { + if (!this._editable) { + return; + } + + if (this._currentEditIndex >= this._edits.length - 1) { + // nothing to redo + return; + } + + const redoneEdit = this._edits[this._currentEditIndex + 1]; + await this._proxy.$redo(this.resource, this.viewType, redoneEdit); + this.change(() => { + ++this._currentEditIndex; }); } From fb5dc0083bfa9a0e3da7ed1f86e1ecb9836fcc8b Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 12 Mar 2020 21:17:45 -0700 Subject: [PATCH 095/169] Add onCustomEditor to documented activation events --- .../services/extensions/common/extensionsRegistry.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index 70282328684..32c51b41e3a 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -289,6 +289,11 @@ export const schema: IJSONSchema = { body: 'onUri', description: nls.localize('vscode.extension.activationEvents.onUri', 'An activation event emitted whenever a system-wide Uri directed towards this extension is open.'), }, + { + label: 'onCustomEditor', + body: 'onCustomEditor:${9:viewType}', + description: nls.localize('vscode.extension.activationEvents.onCustomEditor', 'An activation event emitted whenever the specified custom editor becomes visible.'), + }, { label: '*', description: nls.localize('vscode.extension.activationEvents.star', 'An activation event emitted on VS Code startup. To ensure a great end user experience, please use this activation event in your extension only when no other activation events combination works in your use-case.'), From 89d3b684a288ef65b8d4eaf47a92262fdbe8e72b Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 12 Mar 2020 22:15:21 -0700 Subject: [PATCH 096/169] Use unique resource for custom editor working copy For #92037 Ensures that we can open multiple types of custom editors are opened for the same resource, and that our dirty tracking works as expected in that case (by only marking editors of a given type dirty) --- .../api/browser/mainThreadWebview.ts | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 3f5ee9c4f52..332017e9ee0 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -536,6 +536,8 @@ namespace HotExitState { export type State = typeof Allowed | typeof NotAllowed | Pending; } +const customDocumentFileScheme = 'custom'; + class MainThreadCustomEditorModel extends Disposable implements ICustomEditorModel, IWorkingCopy { private _hotExitState: HotExitState.State = HotExitState.Allowed; @@ -556,7 +558,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod constructor( private readonly _proxy: extHostProtocol.ExtHostWebviewsShape, private readonly _viewType: string, - private readonly _resource: URI, + private readonly _realResource: URI, private readonly _editable: boolean, @IWorkingCopyService workingCopyService: IWorkingCopyService, @ILabelService private readonly _labelService: ILabelService, @@ -564,6 +566,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod @IUndoRedoService private readonly _undoService: IUndoRedoService, ) { super(); + if (_editable) { this._register(workingCopyService.registerWorkingCopy(this)); } @@ -571,18 +574,26 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod dispose() { if (this._editable) { - this._undoService.removeElements(this.resource); + this._undoService.removeElements(this._realResource); } - this._proxy.$disposeWebviewCustomEditorDocument(this.resource, this._viewType); + this._proxy.$disposeWebviewCustomEditorDocument(this._realResource, this._viewType); super.dispose(); } //#region IWorkingCopy - public get resource() { return this._resource; } // custom://viewType/path/file + public get resource() { + // Make sure each custom editor has a unique resource for backup and edits + return URI.from({ + scheme: customDocumentFileScheme, + authority: this._viewType, + path: this._realResource.path, + query: JSON.stringify(this._realResource.toJSON()) + }); + } public get name() { - return basename(this._labelService.getUriLabel(this._resource)); + return basename(this._labelService.getUriLabel(this._realResource)); } public get capabilities(): WorkingCopyCapabilities { @@ -617,7 +628,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod this._undoService.pushElement({ type: UndoRedoElementType.Resource, - resource: this.resource, + resource: this._realResource, label: label ?? localize('defaultEditLabel', "Edit"), undo: () => this.undo(), redo: () => this.redo(), @@ -635,7 +646,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod } const undoneEdit = this._edits[this._currentEditIndex]; - await this._proxy.$undo(this.resource, this.viewType, undoneEdit); + await this._proxy.$undo(this._realResource, this.viewType, undoneEdit); this.change(() => { --this._currentEditIndex; @@ -653,7 +664,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod } const redoneEdit = this._edits[this._currentEditIndex + 1]; - await this._proxy.$redo(this.resource, this.viewType, redoneEdit); + await this._proxy.$redo(this._realResource, this.viewType, redoneEdit); this.change(() => { ++this._currentEditIndex; }); @@ -668,7 +679,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod : this._edits.splice(start, toRemove); if (removedEdits.length) { - this._proxy.$disposeEdits(this.resource, this._viewType, removedEdits); + this._proxy.$disposeEdits(this._realResource, this._viewType, removedEdits); } } @@ -700,7 +711,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod editsToRedo = this._edits.slice(this._currentEditIndex, this._savePoint); } - this._proxy.$revert(this.resource, this.viewType, { undoneEdits: editsToUndo, redoneEdits: editsToRedo }); + this._proxy.$revert(this._realResource, this.viewType, { undoneEdits: editsToUndo, redoneEdits: editsToRedo }); this.change(() => { this._currentEditIndex = this._savePoint; this.spliceEdits(); @@ -711,7 +722,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod if (!this._editable) { return false; } - await createCancelablePromise(token => this._proxy.$onSave(this.resource, this.viewType, token)); + await createCancelablePromise(token => this._proxy.$onSave(this._realResource, this.viewType, token)); this.change(() => { this._savePoint = this._currentEditIndex; }); @@ -720,7 +731,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod public async saveAs(resource: URI, targetResource: URI, _options?: ISaveOptions): Promise { if (this._editable) { - await this._proxy.$onSaveAs(this.resource, this.viewType, targetResource); + await this._proxy.$onSaveAs(this._realResource, this.viewType, targetResource); this.change(() => { this._savePoint = this._currentEditIndex; }); @@ -749,7 +760,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod const pendingState = new HotExitState.Pending( createCancelablePromise(token => - this._proxy.$backup(this.resource.toJSON(), this.viewType, token))); + this._proxy.$backup(this._realResource.toJSON(), this.viewType, token))); this._hotExitState = pendingState; try { From b75adf372050fc294e7adcdfd4d48e76af92d166 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 12 Mar 2020 23:02:21 -0700 Subject: [PATCH 097/169] Mark fields readonly --- src/vs/workbench/services/backup/common/backup.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/backup/common/backup.ts b/src/vs/workbench/services/backup/common/backup.ts index 66750a668b8..2d3c0217589 100644 --- a/src/vs/workbench/services/backup/common/backup.ts +++ b/src/vs/workbench/services/backup/common/backup.ts @@ -10,8 +10,8 @@ import { ITextBufferFactory, ITextSnapshot } from 'vs/editor/common/model'; export const IBackupFileService = createDecorator('backupFileService'); export interface IResolvedBackup { - value: ITextBufferFactory; - meta?: T; + readonly value: ITextBufferFactory; + readonly meta?: T; } /** From 955ff025c39bcc919e7b828bc14239215f6b20ad Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 12 Mar 2020 23:26:31 -0700 Subject: [PATCH 098/169] Don't highlight URI diagram comment as javascript --- src/vs/base/common/uri.ts | 2 ++ src/vs/monaco.d.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts index bdc79116318..0108b531460 100644 --- a/src/vs/base/common/uri.ts +++ b/src/vs/base/common/uri.ts @@ -85,6 +85,7 @@ const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/; * (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation * and encoding. * + * ```txt * foo://example.com:8042/over/there?name=ferret#nose * \_/ \______________/\_________/ \_________/ \__/ * | | | | | @@ -92,6 +93,7 @@ const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/; * | _____________________|__ * / \ / \ * urn:example:animal:ferret:nose + * ``` */ export class URI implements UriComponents { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 8b4ab293ccc..08918576d0e 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -74,6 +74,7 @@ declare namespace monaco { * (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation * and encoding. * + * ```txt * foo://example.com:8042/over/there?name=ferret#nose * \_/ \______________/\_________/ \_________/ \__/ * | | | | | @@ -81,6 +82,7 @@ declare namespace monaco { * | _____________________|__ * / \ / \ * urn:example:animal:ferret:nose + * ``` */ export class Uri implements UriComponents { static isUri(thing: any): thing is Uri; From b36f377be6786625d141377e89d21344311d2541 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 13 Mar 2020 08:44:05 +0100 Subject: [PATCH 099/169] compare untitled files: title (fix #92556) --- src/vs/workbench/browser/labels.ts | 45 ++++++++++++++----- .../parts/editor/noTabsTitleControl.ts | 17 +++++-- .../browser/parts/editor/tabsTitleControl.ts | 7 ++- src/vs/workbench/common/editor.ts | 29 ++++++++---- .../files/browser/views/openEditorsView.ts | 2 +- src/vs/workbench/electron-browser/window.ts | 4 +- .../test/browser/parts/editor/editor.test.ts | 22 +++++++-- 7 files changed, 96 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index a394c7d34a4..8956c189255 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -24,11 +24,23 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { withNullAsUndefined } from 'vs/base/common/types'; export interface IResourceLabelProps { - resource?: URI; + resource?: URI | { master?: URI, detail?: URI }; name?: string | string[]; description?: string; } +function toResource(props: IResourceLabelProps | undefined): URI | undefined { + if (!props || !props.resource) { + return undefined; + } + + if (URI.isUri(props.resource)) { + return props.resource; + } + + return props.resource.master; +} + export interface IResourceLabelOptions extends IIconLabelValueOptions { fileKind?: FileKind; fileDecorations?: { colors: boolean, badges: boolean }; @@ -289,11 +301,16 @@ class ResourceLabelWidget extends IconLabel { } notifyFileDecorationsChanges(e: IResourceDecorationChangeEvent): void { - if (!this.options || !this.label || !this.label.resource) { + if (!this.options) { return; } - if (this.options.fileDecorations && e.affectsResource(this.label.resource)) { + const resource = toResource(this.label); + if (!resource) { + return; + } + + if (this.options.fileDecorations && e.affectsResource(resource)) { this.render(false); } } @@ -311,13 +328,13 @@ class ResourceLabelWidget extends IconLabel { } notifyFormattersChange(scheme: string): void { - if (this.label?.resource?.scheme === scheme) { + if (toResource(this.label)?.scheme === scheme) { this.render(false); } } notifyUntitledLabelChange(resource: URI): void { - if (isEqual(resource, this.label?.resource)) { + if (isEqual(resource, toResource(this.label))) { this.render(false); } } @@ -347,7 +364,10 @@ class ResourceLabelWidget extends IconLabel { } setResource(label: IResourceLabelProps, options: IResourceLabelOptions = Object.create(null)): void { - if (label.resource?.scheme === Schemas.untitled) { + const resource = toResource(this.label); + const isMasterDetail = this.label?.resource && !URI.isUri(this.label.resource); + + if (!isMasterDetail && resource?.scheme === Schemas.untitled) { // Untitled labels are very dynamic because they may change // whenever the content changes (unless a path is associated). // As such we always ask the actual editor for it's name and @@ -355,7 +375,11 @@ class ResourceLabelWidget extends IconLabel { // provided. If they are not provided from the label we got // we assume that the client does not want to display them // and as such do not override. - const untitledModel = this.textFileService.untitled.get(label.resource); + // + // We do not touch the label if it represents a master-detail + // because in that case we expect it to carry a proper label + // and description. + const untitledModel = this.textFileService.untitled.get(resource); if (untitledModel && !untitledModel.hasAssociatedFilePath) { if (typeof label.name === 'string') { label.name = untitledModel.name; @@ -415,7 +439,7 @@ class ResourceLabelWidget extends IconLabel { } private hasPathLabelChanged(newLabel: IResourceLabelProps, newOptions?: IResourceLabelOptions): boolean { - const newResource = newLabel ? newLabel.resource : undefined; + const newResource = toResource(newLabel); return !!newResource && this.computedPathLabel !== this.labelService.getUriLabel(newResource); } @@ -444,7 +468,8 @@ class ResourceLabelWidget extends IconLabel { } if (this.label) { - const detectedModeId = this.label.resource ? withNullAsUndefined(detectModeId(this.modelService, this.modeService, this.label.resource)) : undefined; + const resource = toResource(this.label); + const detectedModeId = resource ? withNullAsUndefined(detectModeId(this.modelService, this.modeService, resource)) : undefined; if (this.lastKnownDetectedModeId !== detectedModeId) { clearIconCache = true; this.lastKnownDetectedModeId = detectedModeId; @@ -470,7 +495,7 @@ class ResourceLabelWidget extends IconLabel { domId: this.options?.domId }; - const resource = this.label.resource; + const resource = toResource(this.label); const label = this.label.name; if (this.options && typeof this.options.title === 'string') { diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 211fdec8ce1..3af508ded81 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -260,9 +260,6 @@ export class NoTabsTitleControl extends TitleControl { this.updateEditorDirty(editor); // Editor Label - const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - const name = editor.getName(); - const { labelFormat } = this.accessor.partOptions; let description: string; if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { @@ -278,7 +275,19 @@ export class NoTabsTitleControl extends TitleControl { title = ''; // dont repeat what is already shown } - editorLabel.setResource({ name, description, resource }, { title: typeof title === 'string' ? title : undefined, italic: !isEditorPinned, extraClasses: ['no-tabs', 'title-label'] }); + editorLabel.setResource( + { + resource: toResource(editor, { supportSideBySide: SideBySideEditor.BOTH }), + name: editor.getName(), + description + }, + { + title, + italic: !isEditorPinned, + extraClasses: ['no-tabs', 'title-label'] + } + ); + if (isGroupActive) { editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND) || ''; } else { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index d5042465900..c85a70573e1 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -963,10 +963,13 @@ export class TabsTitleControl extends TitleControl { tabContainer.title = title; // Label - const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - tabLabelWidget.setResource({ name, description, resource }, { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) }); + tabLabelWidget.setResource( + { name, description, resource: toResource(editor, { supportSideBySide: SideBySideEditor.BOTH }) }, + { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) } + ); // Tests helper + const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource) { tabContainer.setAttribute('data-resource-name', basenameOrAuthority(resource)); } else { diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index a6ecc28c537..854a32e9e3c 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1319,7 +1319,8 @@ export interface IEditorPartOptionsChangeEvent { export enum SideBySideEditor { MASTER = 1, - DETAILS = 2 + DETAILS = 2, + BOTH = 3 } export interface IResourceOptions { @@ -1327,12 +1328,22 @@ export interface IResourceOptions { filterByScheme?: string | string[]; } -export function toResource(editor: IEditorInput | undefined, options?: IResourceOptions): URI | undefined { +export function toResource(editor: IEditorInput | undefined): URI | undefined; +export function toResource(editor: IEditorInput | undefined, options: IResourceOptions & { supportSideBySide?: SideBySideEditor.MASTER | SideBySideEditor.DETAILS }): URI | undefined; +export function toResource(editor: IEditorInput | undefined, options: IResourceOptions & { supportSideBySide: SideBySideEditor.BOTH }): URI | { master?: URI, detail?: URI } | undefined; +export function toResource(editor: IEditorInput | undefined, options?: IResourceOptions): URI | { master?: URI, detail?: URI } | undefined { if (!editor) { return undefined; } if (options?.supportSideBySide && editor instanceof SideBySideEditorInput) { + if (options?.supportSideBySide === SideBySideEditor.BOTH) { + return { + master: toResource(editor.master, { filterByScheme: options.filterByScheme }), + detail: toResource(editor.details, { filterByScheme: options.filterByScheme }) + }; + } + editor = options.supportSideBySide === SideBySideEditor.MASTER ? editor.master : editor.details; } @@ -1341,12 +1352,14 @@ export function toResource(editor: IEditorInput | undefined, options?: IResource return resource; } - if (Array.isArray(options.filterByScheme) && options.filterByScheme.some(scheme => resource.scheme === scheme)) { - return resource; - } - - if (options.filterByScheme === resource.scheme) { - return resource; + if (Array.isArray(options.filterByScheme)) { + if (options.filterByScheme.some(scheme => resource.scheme === scheme)) { + return resource; + } + } else { + if (options.filterByScheme === resource.scheme) { + return resource; + } } return undefined; diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 1c22f7201cd..9d089f8261b 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -592,7 +592,7 @@ class OpenEditorRenderer implements IListRenderer { assert.equal(toResource(untitled)!.toString(), untitled.resource.toString()); assert.equal(toResource(untitled, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), untitled.resource.toString()); + assert.equal(toResource(untitled, { supportSideBySide: SideBySideEditor.DETAILS })!.toString(), untitled.resource.toString()); + assert.equal(toResource(untitled, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), untitled.resource.toString()); assert.equal(toResource(untitled, { filterByScheme: Schemas.untitled })!.toString(), untitled.resource.toString()); assert.equal(toResource(untitled, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource.toString()); assert.ok(!toResource(untitled, { filterByScheme: Schemas.file })); @@ -43,6 +45,8 @@ suite('Workbench editor', () => { assert.equal(toResource(file)!.toString(), file.resource.toString()); assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), file.resource.toString()); + assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.DETAILS })!.toString(), file.resource.toString()); + assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), file.resource.toString()); assert.equal(toResource(file, { filterByScheme: Schemas.file })!.toString(), file.resource.toString()); assert.equal(toResource(file, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString()); assert.ok(!toResource(file, { filterByScheme: Schemas.untitled })); @@ -52,8 +56,20 @@ suite('Workbench editor', () => { assert.ok(!toResource(diffEditorInput)); assert.ok(!toResource(diffEditorInput, { filterByScheme: Schemas.file })); - assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), file.resource.toString()); - assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file })!.toString(), file.resource.toString()); - assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), file.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file })!.toString(), file.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString()); + + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.DETAILS })!.toString(), untitled.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.DETAILS, filterByScheme: Schemas.untitled })!.toString(), untitled.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.DETAILS, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource.toString()); + + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH }) as { master: URI, detail: URI }).master.toString(), file.resource.toString()); + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: Schemas.file }) as { master: URI, detail: URI }).master.toString(), file.resource.toString()); + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: [Schemas.file, Schemas.untitled] }) as { master: URI, detail: URI }).master.toString(), file.resource.toString()); + + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH }) as { master: URI, detail: URI }).detail.toString(), untitled.resource.toString()); + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: Schemas.untitled }) as { master: URI, detail: URI }).detail.toString(), untitled.resource.toString()); + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: [Schemas.file, Schemas.untitled] }) as { master: URI, detail: URI }).detail.toString(), untitled.resource.toString()); }); }); From 6ee37a600225d7314416afa9ea53bbf163684d02 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 13 Mar 2020 08:59:31 +0100 Subject: [PATCH 100/169] web - move extension code into web-playground folder --- extensions/vscode-api-tests/package.json | 2 +- .../vscode-api-tests/src/{ => web-playground}/extension.ts | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename extensions/vscode-api-tests/src/{ => web-playground}/extension.ts (100%) diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 8ac6b2806ca..c6fec9f7fb2 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -10,7 +10,7 @@ "onFileSystem:memfs", "onDebug" ], - "main": "./out/extension", + "main": "./out/web-playground/extension", "engines": { "vscode": "^1.25.0" }, diff --git a/extensions/vscode-api-tests/src/extension.ts b/extensions/vscode-api-tests/src/web-playground/extension.ts similarity index 100% rename from extensions/vscode-api-tests/src/extension.ts rename to extensions/vscode-api-tests/src/web-playground/extension.ts From 230cb190782bdac279b9075173cda5d6781ce730 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 13 Mar 2020 10:35:56 +0100 Subject: [PATCH 101/169] web - log info when unload is triggered --- .../services/lifecycle/browser/lifecycleService.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts index 41e6b4e34c4..0df88daa425 100644 --- a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts @@ -28,6 +28,9 @@ export class BrowserLifecycleService extends AbstractLifecycleService { } private onBeforeUnload(): string | null { + const logService = this.logService; + logService.info('[lifecycle] onBeforeUnload triggered'); + let veto = false; // Before Shutdown @@ -36,7 +39,7 @@ export class BrowserLifecycleService extends AbstractLifecycleService { if (value === true) { veto = true; } else if (value instanceof Promise && !veto) { - console.warn(new Error('Long running onBeforeShutdown currently not supported in the web')); + logService.error('[lifecycle] Long running onBeforeShutdown currently not supported in the web'); veto = true; } }, @@ -51,7 +54,7 @@ export class BrowserLifecycleService extends AbstractLifecycleService { // No Veto: continue with Will Shutdown this._onWillShutdown.fire({ join() { - console.warn(new Error('Long running onWillShutdown currently not supported in the web')); + logService.error('[lifecycle] Long running onWillShutdown currently not supported in the web'); }, reason: ShutdownReason.QUIT }); From e4cf6e1e4868badc1a7790ae403f35bfab66a943 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 13 Mar 2020 10:52:53 +0100 Subject: [PATCH 102/169] Respect capabilities.supportsClipboardContext fixes #69768 --- src/vs/workbench/contrib/debug/browser/debugActions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index ca025ac0c76..bc94f6616fb 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -400,7 +400,8 @@ export class CopyValueAction extends Action { if (stackFrame && session && this.value.evaluateName) { try { - const evaluation = await session.evaluate(this.value.evaluateName, stackFrame.frameId, this.context); + const context = session.capabilities.supportsClipboardContext ? 'clipboard' : this.context; + const evaluation = await session.evaluate(this.value.evaluateName, stackFrame.frameId, context); this.clipboardService.writeText(evaluation.body.result); } catch (e) { this.clipboardService.writeText(this.value.value); From 39424388650d92f0ede9ceee37cefa49e8171b4d Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 13 Mar 2020 11:30:24 +0100 Subject: [PATCH 103/169] [css] update ls-service (for #92417) --- extensions/css-language-features/server/package.json | 2 +- extensions/css-language-features/server/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index 7f439dd686a..593ecdff80a 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -9,7 +9,7 @@ }, "main": "./out/cssServerMain", "dependencies": { - "vscode-css-languageservice": "^4.1.0", + "vscode-css-languageservice": "^4.1.1", "vscode-languageserver": "^6.1.1" }, "devDependencies": { diff --git a/extensions/css-language-features/server/yarn.lock b/extensions/css-language-features/server/yarn.lock index e57ff40f62e..902123900d0 100644 --- a/extensions/css-language-features/server/yarn.lock +++ b/extensions/css-language-features/server/yarn.lock @@ -689,10 +689,10 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -vscode-css-languageservice@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.1.0.tgz#144c8274e0bf1719fa6f773ca684bd1c7ffd634f" - integrity sha512-iTX3dTp0Y0RFWhIux5jasI8r9swdiWVB1Z3OrZ10iDHxzkETjVPxAQ5BEQU4ag0Awc8TTg1C7sJriHQY2LO14g== +vscode-css-languageservice@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.1.1.tgz#9131dd465e4b20f3ba78ab9734b2c7cdb9237443" + integrity sha512-2r2bYbhscivRu1zqh5kNe8aYpFnfksMYC7wTpKX2HqFsSzSJYXk0sCqPaWsP5ptqz0OFBTXnzx2JlE+Nb5Edgw== dependencies: vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-types "^3.15.1" From bf8fb5f814846a1be11f3b46354a3a6c7abef5b2 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 13 Mar 2020 12:16:54 +0100 Subject: [PATCH 104/169] editor token inspect: resolve scopes like the sem highlight code --- .../inspectEditorTokens.ts | 14 ++++++------- .../services/themes/common/colorThemeData.ts | 20 ++++++++++++++++--- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index e50b43138e3..36ce9d91f9f 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -26,7 +26,7 @@ import { findMatchingThemeRule } from 'vs/workbench/services/textMate/common/TMH import { ITextMateService, IGrammar, IToken, StackElement } from 'vs/workbench/services/textMate/common/textMateService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { ColorThemeData, TokenStyleDefinitions, TokenStyleDefinition } from 'vs/workbench/services/themes/common/colorThemeData'; +import { ColorThemeData, TokenStyleDefinitions, TokenStyleDefinition, TextMateThemingRuleDefinitions } from 'vs/workbench/services/themes/common/colorThemeData'; import { TokenStylingRule, TokenStyleData, TokenStyle } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -303,7 +303,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { const allDefValues = []; // remember the order // first collect to detect when the same rule is used fro multiple properties for (let property of properties) { - if (semanticTokenInfo.metadata[property]) { + if (semanticTokenInfo.metadata[property] !== undefined) { const definition = semanticTokenInfo.definitions[property]; const defValue = this._renderTokenStyleDefinition(definition, property); let properties = propertiesByDefValue[defValue]; @@ -532,11 +532,11 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { const isTokenStylingRule = (d: any): d is TokenStylingRule => !!d.value; if (Array.isArray(definition)) { - for (const d of definition) { - const matchingRule = findMatchingThemeRule(theme, d, false); - if (matchingRule) { - return `${escape(d.join(' '))}
${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}`; - } + const scopesDefinition: TextMateThemingRuleDefinitions = {}; + theme.resolveScopes(definition, scopesDefinition); + const matchingRule = scopesDefinition[property]; + if (matchingRule && scopesDefinition.scope) { + return `${escape(scopesDefinition.scope.join(' '))}
${matchingRule.scope}\n${JSON.stringify(matchingRule.settings, null, '\t')}`; } return ''; } else if (isTokenStylingRule(definition)) { diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index bacaa95f9f7..80e070d65dd 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -44,6 +44,8 @@ const tokenGroupToScopesMap = { export type TokenStyleDefinition = TokenStylingRule | ProbeScope[] | TokenStyleValue; export type TokenStyleDefinitions = { [P in keyof TokenStyleData]?: TokenStyleDefinition | undefined }; +export type TextMateThemingRuleDefinitions = { [P in keyof TokenStyleData]?: ITextMateThemingRule | undefined; } & { scope?: ProbeScope; }; + const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData'; export class ColorThemeData implements IWorkbenchColorTheme { @@ -271,7 +273,8 @@ export class ColorThemeData implements IWorkbenchColorTheme { return colorRegistry.resolveDefaultColor(colorId, this); } - public resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined { + + public resolveScopes(scopes: ProbeScope[], definitions?: TextMateThemingRuleDefinitions): TokenStyle | undefined { if (!this.themeTokenScopeMatchers) { this.themeTokenScopeMatchers = this.themeTokenColors.map(getScopeMatcher); @@ -285,19 +288,24 @@ export class ColorThemeData implements IWorkbenchColorTheme { let fontStyle: string | undefined = undefined; let foregroundScore = -1; let fontStyleScore = -1; + let fontStyleThemingRule: ITextMateThemingRule | undefined = undefined; + let foregroundThemingRule: ITextMateThemingRule | undefined = undefined; - function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], tokenColors: ITextMateThemingRule[]) { + function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], themingRules: ITextMateThemingRule[]) { for (let i = 0; i < scopeMatchers.length; i++) { const score = scopeMatchers[i](scope); if (score >= 0) { - const settings = tokenColors[i].settings; + const themingRule = themingRules[i]; + const settings = themingRules[i].settings; if (score >= foregroundScore && settings.foreground) { foreground = settings.foreground; foregroundScore = score; + foregroundThemingRule = themingRule; } if (score >= fontStyleScore && types.isString(settings.fontStyle)) { fontStyle = settings.fontStyle; fontStyleScore = score; + fontStyleThemingRule = themingRule; } } } @@ -305,6 +313,12 @@ export class ColorThemeData implements IWorkbenchColorTheme { findTokenStyleForScopeInScopes(this.themeTokenScopeMatchers, this.themeTokenColors); findTokenStyleForScopeInScopes(this.customTokenScopeMatchers, this.customTokenColors); if (foreground !== undefined || fontStyle !== undefined) { + if (definitions) { + definitions.foreground = foregroundThemingRule; + definitions.bold = definitions.italic = definitions.underline = fontStyleThemingRule; + definitions.scope = scope; + } + return TokenStyle.fromSettings(foreground, fontStyle); } } From 14b5afc0ecfd2908e5f853a97117fa984bbd633d Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Fri, 13 Mar 2020 12:42:58 +0100 Subject: [PATCH 105/169] Add --use-host-proxy (microsoft/vscode-remote-release#2487) --- package.json | 2 +- .../workbench/api/common/extHost.protocol.ts | 1 + .../api/node/extHostExtensionService.ts | 2 +- .../node/extensionHostProcessSetup.ts | 5 ++++- .../services/extensions/node/proxyResolver.ts | 22 ++++++++++++++----- 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 6fc79f7aba1..dcc6b7a6341 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.44.0", - "distro": "07db0bb3dc2da82ce33f2871e0093df1b9085d06", + "distro": "de617fbc2d2b5e151b9e4f1713fcd5d29dda04ea", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index cbbba0f9265..1b1e36d9d61 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -67,6 +67,7 @@ export interface IEnvironment { userHome: URI; webviewResourceRoot: string; webviewCspSource: string; + useHostProxy?: boolean; } export interface IStaticWorkspaceData { diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 79189ba670b..3a02c5ce0b7 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -61,7 +61,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { // Do this when extension service exists, but extensions are not being activated yet. const configProvider = await this._extHostConfiguration.getConfigProvider(); - await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy); + await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy, this._initData); // Use IPC messages to forward console-calls, note that the console is // already patched to use`process.send()` diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 0f35c544319..e35e1736651 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -25,6 +25,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; interface ParsedExtHostArgs { uriTransformerPath?: string; + useHostProxy?: string; } // workaround for https://github.com/microsoft/vscode/issues/85490 @@ -40,7 +41,8 @@ interface ParsedExtHostArgs { const args = minimist(process.argv.slice(2), { string: [ - 'uriTransformerPath' + 'uriTransformerPath', + 'useHostProxy' ] }) as ParsedExtHostArgs; @@ -293,6 +295,7 @@ export async function startExtensionHostProcess(): Promise { const { initData } = renderer; // setup things patchProcess(!!initData.environment.extensionTestsLocationURI); // to support other test frameworks like Jasmin that use process.exit (https://github.com/Microsoft/vscode/issues/37708) + initData.environment.useHostProxy = args.useHostProxy !== undefined ? args.useHostProxy !== 'false' : undefined; // host abstraction const hostUtils = new class NodeHost implements IHostUtils { diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 4c72df591f5..ad9308548a7 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -16,7 +16,7 @@ import { endsWith } from 'vs/base/common/strings'; import { IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorkspace'; import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; import { ProxyAgent } from 'vscode-proxy-agent'; -import { MainThreadTelemetryShape } from 'vs/workbench/api/common/extHost.protocol'; +import { MainThreadTelemetryShape, IInitData } from 'vs/workbench/api/common/extHost.protocol'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; import { URI } from 'vs/base/common/uri'; @@ -35,9 +35,10 @@ export function connectProxyResolver( configProvider: ExtHostConfigProvider, extensionService: ExtHostExtensionService, extHostLogService: ILogService, - mainThreadTelemetry: MainThreadTelemetryShape + mainThreadTelemetry: MainThreadTelemetryShape, + initData: IInitData, ) { - const resolveProxy = setupProxyResolution(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry); + const resolveProxy = setupProxyResolution(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry, initData); const lookup = createPatchedModules(configProvider, resolveProxy); return configureModuleLoading(extensionService, lookup); } @@ -48,7 +49,8 @@ function setupProxyResolution( extHostWorkspace: IExtHostWorkspaceProvider, configProvider: ExtHostConfigProvider, extHostLogService: ILogService, - mainThreadTelemetry: MainThreadTelemetryShape + mainThreadTelemetry: MainThreadTelemetryShape, + initData: IInitData, ) { const env = process.env; @@ -139,12 +141,14 @@ function setupProxyResolution( timeout = setTimeout(logEvent, 10 * 60 * 1000); } + const useHostProxy = initData.environment.useHostProxy; + const doUseHostProxy = typeof useHostProxy === 'boolean' ? useHostProxy : !initData.remote.isRemote; useSystemCertificates(extHostLogService, flags.useSystemCertificates, opts, () => { - useProxySettings(flags.useProxySettings, req, opts, url, callback); + useProxySettings(doUseHostProxy, flags.useProxySettings, req, opts, url, callback); }); } - function useProxySettings(useProxySettings: boolean, req: http.ClientRequest, opts: http.RequestOptions, url: string, callback: (proxy?: string) => void) { + function useProxySettings(useHostProxy: boolean, useProxySettings: boolean, req: http.ClientRequest, opts: http.RequestOptions, url: string, callback: (proxy?: string) => void) { if (!useProxySettings) { callback('DIRECT'); @@ -192,6 +196,12 @@ function setupProxyResolution( return; } + if (!useHostProxy) { + callback('DIRECT'); + extHostLogService.trace('ProxyResolver#resolveProxy unconfigured', url, 'DIRECT'); + return; + } + const start = Date.now(); extHostWorkspace.resolveProxy(url) // Use full URL to ensure it is an actually used one. .then(proxy => { From 633e228dc7a9ce6762231fbf1a7b197d897851e7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 13 Mar 2020 13:45:29 +0100 Subject: [PATCH 106/169] quick access - first cut goto symbols --- src/vs/editor/common/standaloneStrings.ts | 1 + .../quickAccess/gotoLineQuickAccess.ts | 2 +- .../quickAccess/gotoSymbolQuickAccess.ts | 390 ++++++++++++++++++ src/vs/editor/editor.main.ts | 1 + .../standaloneGotoSymbolQuickAccess.ts | 34 ++ .../browser/codeEditor.contribution.ts | 1 + .../browser/quickaccess/gotoSymbolAccess.ts | 49 +++ .../browser/commandsQuickAccess.ts | 2 - 8 files changed, 477 insertions(+), 3 deletions(-) create mode 100644 src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts create mode 100644 src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts create mode 100644 src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts diff --git a/src/vs/editor/common/standaloneStrings.ts b/src/vs/editor/common/standaloneStrings.ts index 872aa8aafb4..8ca766d7240 100644 --- a/src/vs/editor/common/standaloneStrings.ts +++ b/src/vs/editor/common/standaloneStrings.ts @@ -61,6 +61,7 @@ export namespace QuickOutlineNLS { export const entryAriaLabel = nls.localize('entryAriaLabel', "{0}, symbols"); export const quickOutlineActionInput = nls.localize('quickOutlineActionInput', "Type the name of an identifier you wish to navigate to"); export const quickOutlineActionLabel = nls.localize('quickOutlineActionLabel', "Go to Symbol..."); + export const quickOutlineByCategoryActionLabel = nls.localize('quickOutlineByCategoryActionLabel', "Go to Symbol by Category..."); export const _symbols_ = nls.localize('symbols', "symbols ({0})"); export const _modules_ = nls.localize('modules', "modules ({0})"); export const _class_ = nls.localize('class', "classes ({0})"); diff --git a/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts index bc2cf02ef59..06847ad5d62 100644 --- a/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts @@ -53,7 +53,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor } private doProvideWithoutTextEditor(picker: IQuickPick): IDisposable { - const label = localize('cannotRunGotoLine', "Open a text file first to go to a line."); + const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line."); picker.items = [{ label }]; picker.ariaLabel = label; diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts new file mode 100644 index 00000000000..1c66d349e0a --- /dev/null +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -0,0 +1,390 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IQuickPick, IQuickPickItem, IKeyMods, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { DisposableStore, toDisposable, IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { once } from 'vs/base/common/functional'; +import { IEditor, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; +import { ITextModel } from 'vs/editor/common/model'; +import { isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { AbstractEditorQuickAccessProvider } from 'vs/editor/contrib/quickAccess/quickAccess'; +import { DocumentSymbol, SymbolKinds, SymbolTag, DocumentSymbolProviderRegistry, SymbolKind } from 'vs/editor/common/modes'; +import { OutlineModel, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; +import { values } from 'vs/base/common/collections'; +import { trim, format } from 'vs/base/common/strings'; +import { fuzzyScore, FuzzyScore, createMatches } from 'vs/base/common/filters'; + +interface IGotoSymbolQuickPickItem extends IQuickPickItem { + kind: SymbolKind, + index: number, + score?: FuzzyScore; + range?: { decoration: IRange, selection: IRange }, +} + +export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEditorQuickAccessProvider { + + static PREFIX = '@'; + static SCOPE_PREFIX = ':'; + static PREFIX_BY_CATEGORY = `${AbstractGotoSymbolQuickAccessProvider.PREFIX}${AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX}`; + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Disable filtering & sorting, we control the results + picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; + + // Provide based on current active editor + let pickerDisposable = this.doProvide(picker, token); + disposables.add(toDisposable(() => pickerDisposable.dispose())); + + // Re-create whenever the active editor changes + disposables.add(this.onDidActiveTextEditorControlChange(() => { + pickerDisposable.dispose(); + pickerDisposable = this.doProvide(picker, token); + })); + + return disposables; + } + + private doProvide(picker: IQuickPick, token: CancellationToken): IDisposable { + const activeTextEditorControl = this.activeTextEditorControl; + + // With text control + if (activeTextEditorControl) { + const model = this.getModel(activeTextEditorControl); + if (model && DocumentSymbolProviderRegistry.has(model)) { + return this.doProvideWithSymbols(activeTextEditorControl, model, picker, token); + } + } + + // Without text control + return this.doProvideWithoutSymbols(picker); + } + + private doProvideWithoutSymbols(picker: IQuickPick): IDisposable { + const label = localize('cannotRunGotoSymbol', "Open a text editor with symbol information first to go to a symbol."); + picker.items = [{ label, index: 0, kind: SymbolKind.String }]; + picker.ariaLabel = label; + + return Disposable.None; + } + + private doProvideWithSymbols(editor: IEditor, model: ITextModel, picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Restore any view state if this picker was closed + // without actually going to a symbol + const lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState()); + once(token.onCancellationRequested)(() => { + if (lastKnownEditorViewState) { + editor.restoreViewState(lastKnownEditorViewState); + } + }); + + // Goto symbol once picked + disposables.add(picker.onDidAccept(() => { + const [item] = picker.selectedItems; + if (item && item.range) { + this.gotoSymbol(editor, item.range.selection, picker.keyMods); + + picker.hide(); + } + })); + + // Set initial picks and update on type + let picksCts: CancellationTokenSource | undefined = undefined; + const updatePickerItems = async () => { + + // Cancel any previous ask for picks and busy + picksCts?.dispose(true); + picker.busy = false; + + // Create new cancellation source for this run + picksCts = new CancellationTokenSource(token); + + // Collect symbol picks + picker.busy = true; + try { + const items = await this.getSymbolPicks(model, picker.value.substr(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim(), picksCts.token); + if (token.isCancellationRequested) { + return; + } + + picker.items = items; + } finally { + if (!token.isCancellationRequested) { + picker.busy = false; + } + } + }; + disposables.add(picker.onDidChangeValue(() => updatePickerItems())); + updatePickerItems(); + + // Reveal and decorate when active item changes + disposables.add(picker.onDidChangeActive(() => { + const [item] = picker.activeItems; + if (item && item.range) { + + // Reveal + editor.revealRangeInCenter(item.range.selection, ScrollType.Smooth); + + // Decorate + this.addDecorations(editor, item.range.decoration); + } + })); + + // Clean up decorations on dispose + disposables.add(toDisposable(() => this.clearDecorations(editor))); + + return disposables; + } + + private async getSymbolPicks(model: ITextModel, filter: string, token: CancellationToken): Promise> { + + // Resolve symbols from document + const symbols = await this.getDocumentSymbols(model, true, token); + if (token.isCancellationRequested) { + return []; + } + + // Normalize filter + const filterBySymbolKind = filter.indexOf(AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX) === 0; + const filterLow = filter.toLowerCase(); + const filterPos = filterBySymbolKind ? 1 : 0; + + // Convert to symbol picks and apply filtering + const filteredSymbolPicks: IGotoSymbolQuickPickItem[] = []; + for (let index = 0; index < symbols.length; index++) { + const symbol = symbols[index]; + + const label = trim(symbol.name); + const deprecated = symbol.tags && symbol.tags.indexOf(SymbolTag.Deprecated) >= 0; + + let score: FuzzyScore | undefined = undefined; + let includeSymbol = true; + if (filter.length > filterPos) { + score = fuzzyScore(filter, filterLow, filterPos, label, label.toLowerCase(), 0, true); + includeSymbol = !!score; + } + + if (includeSymbol) { + const labelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${label}`; + + // Readjust matches to account for codicons + const labelOffset = labelWithIcon.length - label.length; + const matches = createMatches(score); + if (matches) { + for (const match of matches) { + match.start += labelOffset; + match.end += labelOffset; + } + } + + filteredSymbolPicks.push({ + index, + kind: symbol.kind, + score, + label: labelWithIcon, + ariaLabel: localize('symbolsAriaLabel', "{0}, symbols picker", label), + description: symbol.containerName, + highlights: deprecated ? undefined : { label: matches }, + range: { + selection: Range.collapseToStart(symbol.selectionRange), + decoration: symbol.range + }, + italic: deprecated + }); + } + } + + // Sort by score + const sortedFilteredSymbolPicks = filteredSymbolPicks.sort((symbolA, symbolB) => filterBySymbolKind ? + this.compareByKindAndScore(symbolA, symbolB) : + this.compareByScore(symbolA, symbolB) + ); + + // Add separator for types + // - @ only total number of symbols + // - @: grouped by symbol kind + let symbolPicks: Array = []; + if (filterBySymbolKind) { + let lastSymbolKind: SymbolKind | undefined = undefined; + let lastSeparator: IQuickPickSeparator | undefined = undefined; + let lastSymbolKindCounter = 0; + + function updateLastSeparatorLabel(): void { + if (lastSeparator && typeof lastSymbolKind === 'number' && lastSymbolKindCounter > 0) { + lastSeparator.label = format(NLS_SYMBOL_KIND_CACHE[lastSymbolKind] || FALLBACK_NLS_SYMBOL_KIND, lastSymbolKindCounter); + } + } + + for (const symbolPick of sortedFilteredSymbolPicks) { + + // Found new kind + if (lastSymbolKind !== symbolPick.kind) { + + // Update last separator with number of symbols we found for kind + updateLastSeparatorLabel(); + + lastSymbolKind = symbolPick.kind; + lastSymbolKindCounter = 1; + + // Add new separator for new kind + lastSeparator = { type: 'separator' }; + symbolPicks.push(lastSeparator); + } + + // Existing kind, keep counting + else { + lastSymbolKindCounter++; + } + + // Add to final result + symbolPicks.push(symbolPick); + } + + // Update last separator with number of symbols we found for kind + updateLastSeparatorLabel(); + } else { + symbolPicks = [ + { label: localize('symbols', "symbols ({0})", filteredSymbolPicks.length), type: 'separator' }, + ...sortedFilteredSymbolPicks + ]; + } + + return symbolPicks; + } + + private compareByScore(symbolA: IGotoSymbolQuickPickItem, symbolB: IGotoSymbolQuickPickItem): number { + if (!symbolA.score && symbolB.score) { + return 1; + } else if (symbolA.score && !symbolB.score) { + return -1; + } + + if (symbolA.score && symbolB.score) { + if (symbolA.score[0] > symbolB.score[0]) { + return -1; + } else if (symbolA.score[0] < symbolB.score[0]) { + return 1; + } + } + + if (symbolA.index < symbolB.index) { + return -1; + } else if (symbolA.index > symbolB.index) { + return 1; + } + + return 0; + } + + private compareByKindAndScore(symbolA: IGotoSymbolQuickPickItem, symbolB: IGotoSymbolQuickPickItem): number { + const kindA = NLS_SYMBOL_KIND_CACHE[symbolA.kind] || FALLBACK_NLS_SYMBOL_KIND; + const kindB = NLS_SYMBOL_KIND_CACHE[symbolB.kind] || FALLBACK_NLS_SYMBOL_KIND; + + // Sort by type first if scoped search + const result = kindA.localeCompare(kindB); + if (result === 0) { + return this.compareByScore(symbolA, symbolB); + } + + return result; + } + + private async getDocumentSymbols(document: ITextModel, flatten: boolean, token: CancellationToken): Promise { + const model = await OutlineModel.create(document, token); + if (token.isCancellationRequested) { + return []; + } + + const roots: DocumentSymbol[] = []; + for (const child of values(model.children)) { + if (child instanceof OutlineElement) { + roots.push(child.symbol); + } else { + roots.push(...values(child.children).map(child => child.symbol)); + } + } + + let flatEntries: DocumentSymbol[] = []; + if (flatten) { + this.flattenDocumentSymbols(flatEntries, roots, ''); + } else { + flatEntries = roots; + } + + return flatEntries.sort((symbolA, symbolB) => Range.compareRangesUsingStarts(symbolA.range, symbolB.range)); + } + + private flattenDocumentSymbols(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideContainerLabel: string): void { + for (const entry of entries) { + bucket.push({ + kind: entry.kind, + tags: entry.tags, + name: entry.name, + detail: entry.detail, + containerName: entry.containerName || overrideContainerLabel, + range: entry.range, + selectionRange: entry.selectionRange, + children: undefined, // we flatten it... + }); + + // Recurse over children + if (entry.children) { + this.flattenDocumentSymbols(bucket, entry.children, entry.name); + } + } + } + + private getModel(editor: IEditor | IDiffEditor): ITextModel | undefined { + return isDiffEditor(editor) ? + editor.getModel()?.modified : + editor.getModel() as ITextModel; + } + + protected gotoSymbol(editor: IEditor, range: IRange, keyMods: IKeyMods): void { + editor.setSelection(range); + editor.revealRangeInCenter(range, ScrollType.Smooth); + editor.focus(); + } +} + +// #region NLS Helpers + +const FALLBACK_NLS_SYMBOL_KIND = localize('property', "properties ({0})"); +const NLS_SYMBOL_KIND_CACHE: { [type: number]: string } = { + [SymbolKind.Method]: localize('method', "methods ({0})"), + [SymbolKind.Function]: localize('function', "functions ({0})"), + [SymbolKind.Constructor]: localize('_constructor', "constructors ({0})"), + [SymbolKind.Variable]: localize('variable', "variables ({0})"), + [SymbolKind.Class]: localize('class', "classes ({0})"), + [SymbolKind.Struct]: localize('struct', "structs ({0})"), + [SymbolKind.Event]: localize('event', "events ({0})"), + [SymbolKind.Operator]: localize('operator', "operators ({0})"), + [SymbolKind.Interface]: localize('interface', "interfaces ({0})"), + [SymbolKind.Namespace]: localize('namespace', "namespaces ({0})"), + [SymbolKind.Package]: localize('package', "packages ({0})"), + [SymbolKind.TypeParameter]: localize('typeParameter', "type parameters ({0})"), + [SymbolKind.Module]: localize('modules', "modules ({0})"), + [SymbolKind.Property]: localize('property', "properties ({0})"), + [SymbolKind.Enum]: localize('enum', "enumerations ({0})"), + [SymbolKind.EnumMember]: localize('enumMember', "enumeration members ({0})"), + [SymbolKind.String]: localize('string', "strings ({0})"), + [SymbolKind.File]: localize('file', "files ({0})"), + [SymbolKind.Array]: localize('array', "arrays ({0})"), + [SymbolKind.Number]: localize('number', "numbers ({0})"), + [SymbolKind.Boolean]: localize('boolean', "booleans ({0})"), + [SymbolKind.Object]: localize('object', "objects ({0})"), + [SymbolKind.Key]: localize('key', "keys ({0})"), + [SymbolKind.Field]: localize('field', "fields ({0})"), + [SymbolKind.Constant]: localize('constant', "constants ({0})") +}; + +//#endregion diff --git a/src/vs/editor/editor.main.ts b/src/vs/editor/editor.main.ts index 302ff27f8a2..6e3f581f70a 100644 --- a/src/vs/editor/editor.main.ts +++ b/src/vs/editor/editor.main.ts @@ -12,6 +12,7 @@ import 'vs/editor/standalone/browser/quickOpen/quickCommand'; import 'vs/editor/standalone/browser/quickOpen/quickOutline'; import 'vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess'; import 'vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess'; +import 'vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess'; import 'vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess'; import 'vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch'; import 'vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast'; diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts new file mode 100644 index 00000000000..7be920e5129 --- /dev/null +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AbstractGotoSymbolQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoSymbolQuickAccess'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { QuickOutlineNLS } from 'vs/editor/common/standaloneStrings'; +import { Event } from 'vs/base/common/event'; + +export class StandaloneGotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { + + readonly onDidActiveTextEditorControlChange = Event.None; + + constructor(@ICodeEditorService private readonly editorService: ICodeEditorService) { + super(); + } + + get activeTextEditorControl() { + return withNullAsUndefined(this.editorService.getFocusedCodeEditor()); + } +} + +Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: StandaloneGotoSymbolQuickAccessProvider, + prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, + helpEntries: [ + { description: QuickOutlineNLS.quickOutlineActionLabel, prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, needsEditor: true }, + { description: QuickOutlineNLS.quickOutlineByCategoryActionLabel, prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX_BY_CATEGORY, needsEditor: true } + ] +}); diff --git a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts index ddcd8ff16b4..e26553e54b7 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts @@ -10,6 +10,7 @@ import './inspectKeybindings'; import './largeFileOptimizations'; import './inspectEditorTokens/inspectEditorTokens'; import './quickaccess/gotoLineQuickAccess'; +import './quickaccess/gotoSymbolAccess'; import './saveParticipants'; import './toggleColumnSelection'; import './toggleMinimap'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts new file mode 100644 index 00000000000..eee2fd66239 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IKeyMods } from 'vs/platform/quickinput/common/quickInput'; +import { IEditor } from 'vs/editor/common/editorCommon'; +import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { IRange } from 'vs/editor/common/core/range'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { AbstractGotoSymbolQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoSymbolQuickAccess'; + +export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { + + readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange; + + constructor(@IEditorService private readonly editorService: IEditorService) { + super(); + } + + get activeTextEditorControl() { + return this.editorService.activeTextEditorControl; + } + + protected gotoSymbol(editor: IEditor, range: IRange, keyMods: IKeyMods): void { + + // Check for sideBySide use + if (keyMods.ctrlCmd && this.editorService.activeEditor) { + this.editorService.openEditor(this.editorService.activeEditor, { selection: range, pinned: keyMods.alt }, SIDE_GROUP); + } + + // Otherwise let parent handle it + else { + super.gotoSymbol(editor, range, keyMods); + } + } +} + +Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: GotoSymbolQuickAccessProvider, + prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, + placeholder: localize('gotoSymbolQuickAccessPlaceholder', "Type the name of a symbol to go to."), + helpEntries: [ + { description: localize('gotoSymbolQuickAccess', "Go to Symol in Editor"), prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, needsEditor: true }, + { description: localize('gotoSymbolByCategoryQuickAccess', "Go to Symol in Editor by Category"), prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX_BY_CATEGORY, needsEditor: true } + ] +}); diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index ac4a1820889..e6e2209921e 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -10,7 +10,6 @@ import { IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from 'vs/plat import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { CancellationToken } from 'vs/base/common/cancellation'; import { timeout } from 'vs/base/common/async'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { DisposableStore, toDisposable, dispose } from 'vs/base/common/lifecycle'; import { AbstractEditorCommandsQuickAccessProvider } from 'vs/editor/contrib/quickAccess/commandsQuickAccess'; @@ -39,7 +38,6 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce @IEditorService private readonly editorService: IEditorService, @IMenuService private readonly menuService: IMenuService, @IExtensionService private readonly extensionService: IExtensionService, - @IEnvironmentService environmentService: IEnvironmentService, @IInstantiationService instantiationService: IInstantiationService, @IKeybindingService keybindingService: IKeybindingService, @ICommandService commandService: ICommandService, From f7118fd368dd088d66ed95527e5c16e9504c4081 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 13 Mar 2020 14:33:30 +0100 Subject: [PATCH 107/169] [web] Welcome views: links trigger unload (fix #92633) (#92635) --- src/vs/platform/opener/browser/link.ts | 9 ++++++--- .../browser/parts/notifications/notificationsViewer.ts | 5 ++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/opener/browser/link.ts b/src/vs/platform/opener/browser/link.ts index daf424fa848..7b6863cd048 100644 --- a/src/vs/platform/opener/browser/link.ts +++ b/src/vs/platform/opener/browser/link.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { $ } from 'vs/base/browser/dom'; +import { $, EventHelper, EventLike } from 'vs/base/browser/dom'; import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -46,9 +46,12 @@ export class Link extends Disposable { .map(e => new StandardKeyboardEvent(e)) .filter(e => e.keyCode === KeyCode.Enter) .event; - const onOpen = Event.any(onClick, onEnterPress); + const onOpen = Event.any(onClick, onEnterPress); - this._register(onOpen(_ => openerService.open(link.href))); + this._register(onOpen(e => { + EventHelper.stop(e, true); + openerService.open(link.href); + })); this.applyStyles(); } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 19cd1f14f2b..d6384439f03 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -151,7 +151,10 @@ class NotificationMessageRenderer { const anchor = $('a', { href: node.href, title: title, }, node.label); if (actionHandler) { - actionHandler.toDispose.add(addDisposableListener(anchor, EventType.CLICK, () => actionHandler.callback(node.href))); + actionHandler.toDispose.add(addDisposableListener(anchor, EventType.CLICK, e => { + EventHelper.stop(e, true); + actionHandler.callback(node.href); + })); } messageContainer.appendChild(anchor); From a087c17b591b47da50b0d33c1bdba1cdbb754df8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 13 Mar 2020 07:05:53 -0700 Subject: [PATCH 108/169] Remove logs --- src/vs/workbench/api/browser/mainThreadTerminalService.ts | 2 -- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 323cdc2fa47..7018e5b4837 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -148,13 +148,11 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } public $startHandlingLinks(): void { - console.log('start'); this._linkHandler?.dispose(); this._linkHandler = this._terminalService.addLinkHandler(this._remoteAuthority || '', e => this._handleLink(e)); } public $stopHandlingLinks(): void { - console.log('stop'); this._linkHandler?.dispose(); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 153e95815ab..35a93509748 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -526,7 +526,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, xterm, this._processManager, this._configHelper); this._linkHandler.onBeforeHandleLink(e => { - console.log('terminalinstance fire'); e.terminal = this; this._onBeforeHandleLink.fire(e); }); From 9425f1224dd7e74a5ce3522804023389c52cd4ea Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 13 Mar 2020 07:07:51 -0700 Subject: [PATCH 109/169] Move prop to top of class --- src/vs/workbench/api/common/extHostTerminalService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 94eb74c8447..caf46e8a4ce 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -297,6 +297,9 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ protected _extensionTerminalAwaitingStart: { [id: number]: { initialDimensions: ITerminalDimensionsDto | undefined } | undefined } = {}; protected _getTerminalPromises: { [id: number]: Promise } = {}; + private readonly _bufferer: TerminalDataBufferer; + private readonly _linkHandlers: Set = new Set(); + public get activeTerminal(): ExtHostTerminal | undefined { return this._activeTerminal; } public get terminals(): ExtHostTerminal[] { return this._terminals; } @@ -311,8 +314,6 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ protected readonly _onDidWriteTerminalData: Emitter; public get onDidWriteTerminalData(): Event { return this._onDidWriteTerminalData && this._onDidWriteTerminalData.event; } - private readonly _bufferer: TerminalDataBufferer; - constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService ) { @@ -537,7 +538,6 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ return id; } - private _linkHandlers: Set = new Set(); public registerLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable { this._linkHandlers.add(handler); if (this._linkHandlers.size === 1) { From 4395ef43d26171646b89a9082a6a9eae1fdc6512 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 13 Mar 2020 07:11:35 -0700 Subject: [PATCH 110/169] Extend DisposableStore --- .../terminal/browser/terminalLinkHandler.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts index 114ec81dbf4..2a9d0d2ffd3 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts @@ -68,8 +68,7 @@ interface IPath { normalize(path: string): string; } -export class TerminalLinkHandler { - private readonly _hoverDisposables = new DisposableStore(); +export class TerminalLinkHandler extends DisposableStore { private _widgetManager: TerminalWidgetManager | undefined; private _processCwd: string | undefined; private _gitDiffPreImagePattern: RegExp; @@ -78,10 +77,10 @@ export class TerminalLinkHandler { private readonly _leaveCallback: () => void; private _hasBeforeHandleLinkListeners = false; - private readonly _onBeforeHandleLink = new Emitter({ + private readonly _onBeforeHandleLink = this.add(new Emitter({ onFirstListenerAdd: () => this._hasBeforeHandleLinkListeners = true, onLastListenerRemove: () => this._hasBeforeHandleLinkListeners = false - }); + })); /** * Allows intercepting links and handling them outside of the default link handler. When fired * the listener has a set amount of time to handle the link or the default handler will fire. @@ -100,6 +99,8 @@ export class TerminalLinkHandler { @IFileService private readonly _fileService: IFileService, @ILogService private readonly _logService: ILogService ) { + super(); + // Matches '--- a/src/file1', capturing 'src/file1' in group 1 this._gitDiffPreImagePattern = /^--- a\/(\S*)/; // Matches '+++ b/src/file1', capturing 'src/file1' in group 1 @@ -226,11 +227,6 @@ export class TerminalLinkHandler { this._xterm.registerLinkMatcher(this._gitDiffPostImagePattern, wrappedHandler, options); } - public dispose(): void { - this._hoverDisposables.dispose(); - this._onBeforeHandleLink.dispose(); - } - private _wrapLinkHandler(handler: (uri: string) => boolean | void): XtermLinkMatcherHandler { return (event: MouseEvent, uri: string) => { // Prevent default electron link handling so Alt+Click mode works normally From 34dac6556aec0b100116aa414a0376a0bb0231a4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 13 Mar 2020 07:14:09 -0700 Subject: [PATCH 111/169] Move prop to top of TerminalService --- src/vs/workbench/contrib/terminal/browser/terminalService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 1ffb1e804f4..cbf22e4c7d2 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -51,6 +51,7 @@ export class TerminalService implements ITerminalService { private _findState: FindReplaceState; private _extHostsReady: { [authority: string]: IExtHostReadyEntry | undefined } = {}; private _activeTabIndex: number; + private _linkHandlers: { [key: string]: TerminalLinkHandlerCallback } = {}; public get activeTabIndex(): number { return this._activeTabIndex; } public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; } @@ -447,7 +448,6 @@ export class TerminalService implements ITerminalService { })); } - private _linkHandlers: { [key: string]: TerminalLinkHandlerCallback } = {}; public addLinkHandler(key: string, callback: TerminalLinkHandlerCallback): IDisposable { this._linkHandlers[key] = callback; return { From 048e74c7dc201c650f85d5ad02ae6017143cf23b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 13 Mar 2020 07:30:28 -0700 Subject: [PATCH 112/169] Don't register emitters in this PR --- .../terminal/browser/terminalInstance.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 35a93509748..7a212f4369a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -250,29 +250,29 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { public get commandTracker(): CommandTrackerAddon | undefined { return this._commandTrackerAddon; } public get navigationMode(): INavigationMode | undefined { return this._navigationModeAddon; } - private readonly _onExit = this._register(new Emitter()); + private readonly _onExit = new Emitter(); public get onExit(): Event { return this._onExit.event; } - private readonly _onDisposed = this._register(new Emitter()); + private readonly _onDisposed = new Emitter(); public get onDisposed(): Event { return this._onDisposed.event; } - private readonly _onFocused = this._register(new Emitter()); + private readonly _onFocused = new Emitter(); public get onFocused(): Event { return this._onFocused.event; } - private readonly _onProcessIdReady = this._register(new Emitter()); + private readonly _onProcessIdReady = new Emitter(); public get onProcessIdReady(): Event { return this._onProcessIdReady.event; } - private readonly _onTitleChanged = this._register(new Emitter()); + private readonly _onTitleChanged = new Emitter(); public get onTitleChanged(): Event { return this._onTitleChanged.event; } - private readonly _onData = this._register(new Emitter()); + private readonly _onData = new Emitter(); public get onData(): Event { return this._onData.event; } - private readonly _onLineData = this._register(new Emitter()); + private readonly _onLineData = new Emitter(); public get onLineData(): Event { return this._onLineData.event; } - private readonly _onRequestExtHostProcess = this._register(new Emitter()); + private readonly _onRequestExtHostProcess = new Emitter(); public get onRequestExtHostProcess(): Event { return this._onRequestExtHostProcess.event; } - private readonly _onDimensionsChanged = this._register(new Emitter()); + private readonly _onDimensionsChanged = new Emitter(); public get onDimensionsChanged(): Event { return this._onDimensionsChanged.event; } - private readonly _onMaximumDimensionsChanged = this._register(new Emitter()); + private readonly _onMaximumDimensionsChanged = new Emitter(); public get onMaximumDimensionsChanged(): Event { return this._onMaximumDimensionsChanged.event; } - private readonly _onFocus = this._register(new Emitter()); + private readonly _onFocus = new Emitter(); public get onFocus(): Event { return this._onFocus.event; } - private readonly _onBeforeHandleLink = this._register(new Emitter()); + private readonly _onBeforeHandleLink = new Emitter(); public get onBeforeHandleLink(): Event { return this._onBeforeHandleLink.event; } public constructor( From bff2692f9e816b3415aa392ec238bc9a09cd6e94 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 13 Mar 2020 07:49:06 -0700 Subject: [PATCH 113/169] Remove PromiseLike from TerminalLinkHandler Part of #92646 --- .../terminal/browser/terminalLinkHandler.ts | 102 +++++++++--------- 1 file changed, 53 insertions(+), 49 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts index 2a9d0d2ffd3..2260378c8be 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts @@ -60,7 +60,7 @@ const CUSTOM_LINK_PRIORITY = -1; /** Lowest */ const LOCAL_LINK_PRIORITY = -2; -export type XtermLinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void; +export type XtermLinkMatcherHandler = (event: MouseEvent, link: string) => boolean | void; export type XtermLinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void; interface IPath { @@ -227,15 +227,41 @@ export class TerminalLinkHandler extends DisposableStore { this._xterm.registerLinkMatcher(this._gitDiffPostImagePattern, wrappedHandler, options); } - private _wrapLinkHandler(handler: (uri: string) => boolean | void): XtermLinkMatcherHandler { - return (event: MouseEvent, uri: string) => { + private _wrapLinkHandler(handler: (link: string) => void): XtermLinkMatcherHandler { + return (event: MouseEvent, link: string) => { // Prevent default electron link handling so Alt+Click mode works normally event.preventDefault(); // Require correct modifier on click if (!this._isLinkActivationModifierDown(event)) { - return false; + return; } - return handler(uri); + + // Allow the link to be intercepted if there are listeners + if (this._hasBeforeHandleLinkListeners) { + new Promise(r => { + const timeoutId = setTimeout(() => { + canceled = true; + this._logService.error('An extension intecepted a terminal link but did not return'); + r(false); + }, LINK_INTERCEPT_THRESHOLD); + let canceled = false; + const resolve = (handled: boolean) => { + if (!canceled) { + clearTimeout(timeoutId); + r(handled); + } + }; + this._onBeforeHandleLink.fire({ link, resolve }); + }).then(wasHandled => { + if (!wasHandled) { + handler(link); + } + }); + return; + } + + // Just call the handler if there is no before listener + handler(link); }; } @@ -256,41 +282,17 @@ export class TerminalLinkHandler extends DisposableStore { return this._gitDiffPostImagePattern; } - private _handleLocalLink(link: string): PromiseLike { - return this._resolvePath(link).then(async resolvedLink => { - if (!resolvedLink) { - return Promise.resolve(null); - } - - // Allow the link to be intercepted if there are listeners - if (this._hasBeforeHandleLinkListeners) { - const wasHandled = await new Promise(r => { - const timeoutId = setTimeout(() => { - canceled = true; - this._logService.error('An extension intecepted a terminal link but did not return'); - r(false); - }, LINK_INTERCEPT_THRESHOLD); - let canceled = false; - const resolve = (handled: boolean) => { - if (!canceled) { - clearTimeout(timeoutId); - r(handled); - } - }; - this._onBeforeHandleLink.fire({ link, resolve }); - }); - if (wasHandled) { - return; - } - } - - const lineColumnInfo: LineColumnInfo = this.extractLineColumnInfo(link); - const selection: ITextEditorSelection = { - startLineNumber: lineColumnInfo.lineNumber, - startColumn: lineColumnInfo.columnNumber - }; - return this._editorService.openEditor({ resource: resolvedLink, options: { pinned: true, selection } }); - }); + private async _handleLocalLink(link: string): Promise { + const resolvedLink = await this._resolvePath(link); + if (!resolvedLink) { + return; + } + const lineColumnInfo: LineColumnInfo = this.extractLineColumnInfo(link); + const selection: ITextEditorSelection = { + startLineNumber: lineColumnInfo.lineNumber, + startColumn: lineColumnInfo.columnNumber + }; + await this._editorService.openEditor({ resource: resolvedLink, options: { pinned: true, selection } }); } private _validateLocalLink(link: string, callback: (isValid: boolean) => void): void { @@ -381,19 +383,19 @@ export class TerminalLinkHandler extends DisposableStore { return link; } - private _resolvePath(link: string): PromiseLike { + private async _resolvePath(link: string): Promise { if (!this._processManager) { throw new Error('Process manager is required'); } const preprocessedLink = this._preprocessPath(link); if (!preprocessedLink) { - return Promise.resolve(null); + return undefined; } const linkUrl = this.extractLinkUrl(preprocessedLink); if (!linkUrl) { - return Promise.resolve(null); + return undefined; } try { @@ -408,18 +410,20 @@ export class TerminalLinkHandler extends DisposableStore { uri = URI.file(linkUrl); } - return this._fileService.resolve(uri).then(stat => { + try { + const stat = await this._fileService.resolve(uri); if (stat.isDirectory) { - return null; + return undefined; } return uri; - }).catch(() => { + } + catch (e) { // Does not exist - return null; - }); + return undefined; + } } catch { // Errors in parsing the path - return Promise.resolve(null); + return undefined; } } From 8c4c01fb800017fe36657ede98d594cef9405922 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 13 Mar 2020 16:17:44 +0100 Subject: [PATCH 114/169] enable web extension only in web --- .../common/extensionEnablementService.ts | 6 +++-- .../extensionEnablementService.test.ts | 22 +++++++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts index 79fcfe42b56..cfac383e8af 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts @@ -147,8 +147,10 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench } } if (extensionKind === 'web') { - // Web extensions are not yet supported to be disabled by kind - return false; + // Web extensions are not yet supported to be disabled by kind. Enable them always on web. + if (this.extensionManagementServerService.localExtensionManagementServer === null) { + return false; + } } } return true; diff --git a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts index 5c9f2323425..66bdd7eefae 100644 --- a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts @@ -490,24 +490,32 @@ suite('ExtensionEnablementService Test', () => { assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), true); }); - test('test web extension on local server is not disabled by kind', async () => { + test('test web extension on local server is disabled by kind', async () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`) }); testObject = new TestExtensionEnablementService(instantiationService); - assert.ok(testObject.isEnabled(localWorkspaceExtension)); - assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + assert.ok(!testObject.isEnabled(localWorkspaceExtension)); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); }); - test('test web extension on remote server is not disabled by kind', async () => { - instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); + test('test web extension on remote server is not disabled by kind when there is no local server', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(null, anExtensionManagementServer('vscode-remote', instantiationService))); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(testObject.isEnabled(localWorkspaceExtension)); assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); }); - test('test web extension with no server is not disabled by kind', async () => { - instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); + test('test web extension with no server is not disabled by kind when there is no local server', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(null, anExtensionManagementServer('vscode-remote', instantiationService))); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.https }) }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(testObject.isEnabled(localWorkspaceExtension)); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + }); + + test('test web extension with no server is not disabled by kind when there is no local and remote server', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(null, null)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.https }) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(testObject.isEnabled(localWorkspaceExtension)); From d59c618603a39af87f66194b8a53420afba1a4f8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 13 Mar 2020 08:19:33 -0700 Subject: [PATCH 115/169] Add unit test for link handler Ideally there would be an integration test as well but currently it's not possible as there's no extension API to activate links within the terminal --- .../terminal/browser/terminalLinkHandler.ts | 22 +++++----- .../test/browser/terminalLinkHandler.test.ts | 41 ++++++++++++++++++- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts index 2260378c8be..c323e7923ae 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts @@ -60,7 +60,7 @@ const CUSTOM_LINK_PRIORITY = -1; /** Lowest */ const LOCAL_LINK_PRIORITY = -2; -export type XtermLinkMatcherHandler = (event: MouseEvent, link: string) => boolean | void; +export type XtermLinkMatcherHandler = (event: MouseEvent, link: string) => Promise; export type XtermLinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void; interface IPath { @@ -77,6 +77,9 @@ export class TerminalLinkHandler extends DisposableStore { private readonly _leaveCallback: () => void; private _hasBeforeHandleLinkListeners = false; + protected static _LINK_INTERCEPT_THRESHOLD = LINK_INTERCEPT_THRESHOLD; + public static readonly LINK_INTERCEPT_THRESHOLD = TerminalLinkHandler._LINK_INTERCEPT_THRESHOLD; + private readonly _onBeforeHandleLink = this.add(new Emitter({ onFirstListenerAdd: () => this._hasBeforeHandleLinkListeners = true, onLastListenerRemove: () => this._hasBeforeHandleLinkListeners = false @@ -227,8 +230,8 @@ export class TerminalLinkHandler extends DisposableStore { this._xterm.registerLinkMatcher(this._gitDiffPostImagePattern, wrappedHandler, options); } - private _wrapLinkHandler(handler: (link: string) => void): XtermLinkMatcherHandler { - return (event: MouseEvent, link: string) => { + protected _wrapLinkHandler(handler: (link: string) => void): XtermLinkMatcherHandler { + return async (event: MouseEvent, link: string) => { // Prevent default electron link handling so Alt+Click mode works normally event.preventDefault(); // Require correct modifier on click @@ -238,12 +241,12 @@ export class TerminalLinkHandler extends DisposableStore { // Allow the link to be intercepted if there are listeners if (this._hasBeforeHandleLinkListeners) { - new Promise(r => { + const wasHandled = await new Promise(r => { const timeoutId = setTimeout(() => { canceled = true; this._logService.error('An extension intecepted a terminal link but did not return'); r(false); - }, LINK_INTERCEPT_THRESHOLD); + }, TerminalLinkHandler.LINK_INTERCEPT_THRESHOLD); let canceled = false; const resolve = (handled: boolean) => { if (!canceled) { @@ -252,11 +255,10 @@ export class TerminalLinkHandler extends DisposableStore { } }; this._onBeforeHandleLink.fire({ link, resolve }); - }).then(wasHandled => { - if (!wasHandled) { - handler(link); - } }); + if (!wasHandled) { + handler(link); + } return; } @@ -307,7 +309,7 @@ export class TerminalLinkHandler extends DisposableStore { this._openerService.open(url, { allowTunneling: !!(this._processManager && this._processManager.remoteAuthority) }); } - private _isLinkActivationModifierDown(event: MouseEvent): boolean { + protected _isLinkActivationModifierDown(event: MouseEvent): boolean { const editorConf = this._configurationService.getValue<{ multiCursorModifier: 'ctrlCmd' | 'alt' }>('editor'); if (editorConf.multiCursorModifier === 'ctrlCmd') { return !!event.altKey; diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts index 5e114e9d60a..e4c352297b3 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts @@ -5,11 +5,12 @@ import * as assert from 'assert'; import { OperatingSystem } from 'vs/base/common/platform'; -import { TerminalLinkHandler, LineColumnInfo } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; +import { TerminalLinkHandler, LineColumnInfo, XtermLinkMatcherHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; import * as strings from 'vs/base/common/strings'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { Event } from 'vs/base/common/event'; import { ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; class TestTerminalLinkHandler extends TerminalLinkHandler { public get localLinkRegex(): RegExp { @@ -24,6 +25,13 @@ class TestTerminalLinkHandler extends TerminalLinkHandler { public preprocessPath(link: string): string | null { return this._preprocessPath(link); } + protected _isLinkActivationModifierDown(event: MouseEvent): boolean { + return true; + } + public wrapLinkHandler(handler: (link: string) => void): XtermLinkMatcherHandler { + TerminalLinkHandler._LINK_INTERCEPT_THRESHOLD = 0; + return this._wrapLinkHandler(handler); + } } class TestXterm { @@ -302,4 +310,35 @@ suite('Workbench - TerminalLinkHandler', () => { assert.equal(linkHandler.gitDiffLinkPostImageRegex.test('+++ /dev/null'), false); assert.equal(linkHandler.gitDiffLinkPostImageRegex.test('+++ /dev/null '), false); }); + + suite.only('wrapLinkHandler', () => { + const nullMouseEvent: any = Object.freeze({ preventDefault: () => { } }); + + test('should allow intercepting of links with onBeforeHandleLink', async () => { + const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { + os: OperatingSystem.Linux, + userHome: '' + } as any, testConfigHelper, null!, null!, new TestConfigurationService(), new MockTerminalInstanceService(), null!, null!); + linkHandler.onBeforeHandleLink(e => { + if (e.link === 'https://www.microsoft.com') { + intercepted = true; + e.resolve(true); + } + e.resolve(false); + }); + const wrappedHandler = linkHandler.wrapLinkHandler(() => defaultHandled = true); + + let defaultHandled = false; + let intercepted = false; + await wrappedHandler(nullMouseEvent, 'https://www.visualstudio.com'); + assert.equal(intercepted, false); + assert.equal(defaultHandled, true); + + defaultHandled = false; + intercepted = false; + await wrappedHandler(nullMouseEvent, 'https://www.microsoft.com'); + assert.equal(intercepted, true); + assert.equal(defaultHandled, false); + }); + }); }); From d32c22d3f2ac85e5190606020f111a0f149c8b0f Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 13 Mar 2020 18:25:36 +0100 Subject: [PATCH 116/169] callstack deemphasized separators should not be so high #83986 --- src/vs/workbench/contrib/debug/browser/callStackView.ts | 5 +++++ .../workbench/contrib/debug/browser/media/debugViewlet.css | 1 + 2 files changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 77086ba5fc7..b2ed59e3764 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -643,6 +643,11 @@ class ShowMoreRenderer implements ITreeRenderer { getHeight(element: CallStackItem): number { + if (element instanceof StackFrame) { + if (!element.source || !element.source.available || isDeemphasized(element)) { + return 12; + } + } return 22; } diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index ff2cdd5e604..e988c45f478 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -83,6 +83,7 @@ .debug-pane .disabled { opacity: 0.65; + cursor: initial; } /* Call stack */ From 950ceb939b39207658534956b6c4643a4b0ba164 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 13 Mar 2020 10:47:26 -0700 Subject: [PATCH 117/169] debug: update js-debug-nightly to "2020.3.1117" @ 2020-03-12T00:05:48.77Z --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index 63c41b403da..c8607da9d4c 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -46,7 +46,7 @@ }, { "name": "ms-vscode.js-debug-nightly", - "version": "2020.3.317", + "version": "2020.3.1117", "forQualities": [ "insider" ], From fa282a8a1c3cf98f09e22369e55d17304d3eba3a Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 13 Mar 2020 19:03:18 +0100 Subject: [PATCH 118/169] fix #92261 --- src/vs/base/browser/ui/splitview/paneview.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index 4f379f4425c..5fc898e82f0 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -209,6 +209,10 @@ export abstract class Pane extends Disposable implements IView { this.body = append(this.element, $('.pane-body')); this.renderBody(this.body); + + if (!this.isExpanded()) { + this.body.remove(); + } } layout(size: number): void { From 842a86d666961d8559acb3cb78a23f947c5a7b2c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 13 Mar 2020 19:10:13 +0100 Subject: [PATCH 119/169] Fix #85619 --- .../platform/userDataSync/common/userDataSyncStoreService.ts | 2 +- .../workbench/contrib/userDataSync/browser/userDataSyncView.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 1de0f8351fd..68734c4975e 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -46,7 +46,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn } const result = await asJson<{ url: string, created: number }[]>(context) || []; - return result.map(({ url, created }) => ({ ref: relativePath(uri, URI.parse(url))!, created: created })); + return result.map(({ url, created }) => ({ ref: relativePath(uri, URI.parse(url))!, created: created * 1000 /* Server returns in seconds */ })); } async resolveContent(resource: SyncResource, ref: string): Promise { diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts index d902e61625a..be02cbc7149 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts @@ -30,8 +30,7 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution { @IUserDataSyncBackupStoreService private readonly userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, ) { const container = this.registerSyncViewContainer(); - // Disable until server returns the correct timestamp - // this.registerBackupView(container, true); + this.registerBackupView(container, true); this.registerBackupView(container, false); } From 398be372248c6f9d9b73de615fd2fb213972364c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 13 Mar 2020 12:28:25 -0700 Subject: [PATCH 120/169] Remove .only --- .../contrib/terminal/test/browser/terminalLinkHandler.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts index e4c352297b3..f817d6bb400 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts @@ -311,7 +311,7 @@ suite('Workbench - TerminalLinkHandler', () => { assert.equal(linkHandler.gitDiffLinkPostImageRegex.test('+++ /dev/null '), false); }); - suite.only('wrapLinkHandler', () => { + suite('wrapLinkHandler', () => { const nullMouseEvent: any = Object.freeze({ preventDefault: () => { } }); test('should allow intercepting of links with onBeforeHandleLink', async () => { From 075794da3cea395b9b60734e9ecb4e3e7e28389c Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 13 Mar 2020 13:02:33 -0700 Subject: [PATCH 121/169] Update TS versions Also replaces a weird use of `.bind` that was causing issues with the update. I've tried to refactor the code to preseve the existing behavior --- build/package.json | 2 +- build/yarn.lock | 10 +++++----- package.json | 2 +- src/vs/base/parts/tree/browser/treeViewModel.ts | 5 ++--- yarn.lock | 10 +++++----- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/build/package.json b/build/package.json index 2abecaa1279..36a8b905ed8 100644 --- a/build/package.json +++ b/build/package.json @@ -43,7 +43,7 @@ "minimist": "^1.2.0", "request": "^2.85.0", "terser": "4.3.8", - "typescript": "3.9.0-dev.20200304", + "typescript": "^3.9.0-dev.20200313", "vsce": "1.48.0", "vscode-telemetry-extractor": "^1.5.4", "xml2js": "^0.4.17" diff --git a/build/yarn.lock b/build/yarn.lock index ef17978a534..26886e94794 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -2453,16 +2453,16 @@ typed-rest-client@^0.9.0: tunnel "0.0.4" underscore "1.8.3" -typescript@3.9.0-dev.20200304: - version "3.9.0-dev.20200304" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200304.tgz#3cc35357eff29dc5604b4fa56d6597e13daf86ed" - integrity sha512-eUip/GgJmjp4qtHiJDxVhE5SDDiPzBUg7KBAFUgb7HgL/tv10JAHej7fnS1i+7xrq1eDtbkJyPaYOVnhL9db7Q== - typescript@^3.0.1: version "3.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== +typescript@^3.9.0-dev.20200313: + version "3.9.0-dev.20200313" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200313.tgz#f66aeb2c08268f2b1fc6d1d96e15554c6e7ed29b" + integrity sha512-85/IJPm1nEUbQDxK3aN+svIy4X3kPcAipihB3704NY1HXncJ1daNLJW1OktOacb8tD/URpIGs9nMgbUrKvglGg== + typical@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" diff --git a/package.json b/package.json index dcc6b7a6341..308165d4aa4 100644 --- a/package.json +++ b/package.json @@ -150,7 +150,7 @@ "source-map": "^0.4.4", "style-loader": "^1.0.0", "ts-loader": "^4.4.2", - "typescript": "3.9.0-dev.20200304", + "typescript": "^3.9.0-dev.20200313", "typescript-formatter": "7.1.0", "underscore": "^1.8.2", "vinyl": "^2.0.0", diff --git a/src/vs/base/parts/tree/browser/treeViewModel.ts b/src/vs/base/parts/tree/browser/treeViewModel.ts index 823f0bb0a9d..b78c4d8184f 100644 --- a/src/vs/base/parts/tree/browser/treeViewModel.ts +++ b/src/vs/base/parts/tree/browser/treeViewModel.ts @@ -45,8 +45,7 @@ export class HeightMap { totalSize = viewItem.top + viewItem.height; } - let boundSplice = this.heightMap.splice.bind(this.heightMap, i, 0); - + const startingIndex = i; let itemsToInsert: IViewItem[] = []; while (item = iterator.next()) { @@ -58,7 +57,7 @@ export class HeightMap { sizeDiff += viewItem.height; } - boundSplice.apply(this.heightMap, itemsToInsert); + this.heightMap.splice(startingIndex, 0, ...itemsToInsert); for (j = i; j < this.heightMap.length; j++) { viewItem = this.heightMap[j]; diff --git a/yarn.lock b/yarn.lock index c2f11789a2a..49f82d4de93 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9220,16 +9220,16 @@ typescript-formatter@7.1.0: commandpost "^1.0.0" editorconfig "^0.15.0" -typescript@3.9.0-dev.20200304: - version "3.9.0-dev.20200304" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200304.tgz#3cc35357eff29dc5604b4fa56d6597e13daf86ed" - integrity sha512-eUip/GgJmjp4qtHiJDxVhE5SDDiPzBUg7KBAFUgb7HgL/tv10JAHej7fnS1i+7xrq1eDtbkJyPaYOVnhL9db7Q== - typescript@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= +typescript@^3.9.0-dev.20200313: + version "3.9.0-dev.20200313" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200313.tgz#f66aeb2c08268f2b1fc6d1d96e15554c6e7ed29b" + integrity sha512-85/IJPm1nEUbQDxK3aN+svIy4X3kPcAipihB3704NY1HXncJ1daNLJW1OktOacb8tD/URpIGs9nMgbUrKvglGg== + uc.micro@^1.0.1, uc.micro@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192" From 41a4adb47b44fed360105ffb68d6ddf0778b25fb Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 13 Mar 2020 21:55:39 +0100 Subject: [PATCH 122/169] [themes] opt-in to semanticHighlighting --- .../theme-abyss/themes/abyss-color-theme.json | 3 +- .../theme-defaults/themes/dark_defaults.json | 5 ++- .../themes/hc_black_defaults.json | 5 ++- .../theme-defaults/themes/light_defaults.json | 3 +- .../themes/kimbie-dark-color-theme.json | 3 +- .../themes/dimmed-monokai-color-theme.json | 3 +- .../themes/monokai-color-theme.json | 3 +- .../themes/quietlight-color-theme.json | 3 +- .../theme-red/themes/Red-color-theme.json | 3 +- .../themes/solarized-dark-color-theme.json | 3 +- .../themes/solarized-light-color-theme.json | 3 +- .../themes/tomorrow-night-blue-theme.json | 3 +- .../common/services/modelServiceImpl.ts | 37 +++++++++++-------- .../browser/standaloneThemeServiceImpl.ts | 2 + .../test/browser/standaloneLanguages.test.ts | 2 + src/vs/platform/theme/common/themeService.ts | 5 +++ .../theme/test/common/testThemeService.ts | 2 + .../inspectEditorTokens.ts | 3 ++ .../test/common/terminalColorRegistry.test.ts | 4 +- .../services/themes/common/colorThemeData.ts | 28 +++++++++++--- .../themes/common/colorThemeSchema.ts | 4 ++ .../themes/common/themeConfiguration.ts | 5 +++ .../themes/common/workbenchThemeService.ts | 3 +- 23 files changed, 96 insertions(+), 39 deletions(-) diff --git a/extensions/theme-abyss/themes/abyss-color-theme.json b/extensions/theme-abyss/themes/abyss-color-theme.json index 1df39d31c90..39f93305b8d 100644 --- a/extensions/theme-abyss/themes/abyss-color-theme.json +++ b/extensions/theme-abyss/themes/abyss-color-theme.json @@ -434,5 +434,6 @@ "terminal.ansiBrightMagenta": "#d778ff", "terminal.ansiBrightCyan": "#78ffff", "terminal.ansiBrightWhite": "#ffffff" - } + }, + "semanticHighlighting": true } diff --git a/extensions/theme-defaults/themes/dark_defaults.json b/extensions/theme-defaults/themes/dark_defaults.json index 00c2ac8c36b..89f0a5beec8 100644 --- a/extensions/theme-defaults/themes/dark_defaults.json +++ b/extensions/theme-defaults/themes/dark_defaults.json @@ -18,5 +18,6 @@ "menu.foreground": "#CCCCCC", "statusBarItem.remoteForeground": "#FFF", "statusBarItem.remoteBackground": "#16825D" - } -} \ No newline at end of file + }, + "semanticHighlighting": true +} diff --git a/extensions/theme-defaults/themes/hc_black_defaults.json b/extensions/theme-defaults/themes/hc_black_defaults.json index 9d11138a99b..1a03010abff 100644 --- a/extensions/theme-defaults/themes/hc_black_defaults.json +++ b/extensions/theme-defaults/themes/hc_black_defaults.json @@ -337,5 +337,6 @@ "foreground": "#569cd6" } } - ] -} \ No newline at end of file + ], + "semanticHighlighting": true +} diff --git a/extensions/theme-defaults/themes/light_defaults.json b/extensions/theme-defaults/themes/light_defaults.json index 018da38e939..9ea03a9e317 100644 --- a/extensions/theme-defaults/themes/light_defaults.json +++ b/extensions/theme-defaults/themes/light_defaults.json @@ -18,5 +18,6 @@ "settings.numberInputBorder": "#CECECE", "statusBarItem.remoteForeground": "#FFF", "statusBarItem.remoteBackground": "#16825D" - } + }, + "semanticHighlighting": true } diff --git a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json index 111a4a23d9b..cdd22307117 100644 --- a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json +++ b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json @@ -394,5 +394,6 @@ "foreground": "#dc3958" } } - ] + ], + "semanticHighlighting": true } diff --git a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json index f0b6126d5fd..8b1fe2dd80e 100644 --- a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json +++ b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json @@ -572,5 +572,6 @@ "foreground": "#c7444a" } } - ] + ], + "semanticHighlighting": true } diff --git a/extensions/theme-monokai/themes/monokai-color-theme.json b/extensions/theme-monokai/themes/monokai-color-theme.json index c695640f299..a3050894657 100644 --- a/extensions/theme-monokai/themes/monokai-color-theme.json +++ b/extensions/theme-monokai/themes/monokai-color-theme.json @@ -476,5 +476,6 @@ "foreground": "#FD971F" } } - ] + ], + "semanticHighlighting": true } diff --git a/extensions/theme-quietlight/themes/quietlight-color-theme.json b/extensions/theme-quietlight/themes/quietlight-color-theme.json index ae19ba7889b..ffcb30cff03 100644 --- a/extensions/theme-quietlight/themes/quietlight-color-theme.json +++ b/extensions/theme-quietlight/themes/quietlight-color-theme.json @@ -494,5 +494,6 @@ "walkThrough.embeddedEditorBackground": "#00000014", "editorIndentGuide.background": "#aaaaaa60", "editorIndentGuide.activeBackground": "#777777b0" - } + }, + "semanticHighlighting": true } diff --git a/extensions/theme-red/themes/Red-color-theme.json b/extensions/theme-red/themes/Red-color-theme.json index 277e7a8db3f..8ebbf48c22b 100644 --- a/extensions/theme-red/themes/Red-color-theme.json +++ b/extensions/theme-red/themes/Red-color-theme.json @@ -385,5 +385,6 @@ "foreground": "#ec0d1e" } } - ] + ], + "semanticHighlighting": true } diff --git a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json index 682444485d5..b23ff8bb85c 100644 --- a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json +++ b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json @@ -477,5 +477,6 @@ "terminal.ansiBrightMagenta": "#6c71c4", "terminal.ansiBrightCyan": "#93a1a1", "terminal.ansiBrightWhite": "#fdf6e3" - } + }, + "semanticHighlighting": true } diff --git a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json index a29c8fb32f0..2c1f501d850 100644 --- a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json +++ b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json @@ -484,5 +484,6 @@ // Interactive Playground "walkThrough.embeddedEditorBackground": "#00000014" - } + }, + "semanticHighlighting": true } diff --git a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json index f8c47a29e7b..0baee6822ef 100644 --- a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json +++ b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json @@ -255,5 +255,6 @@ "foreground": "#b267e6" } } - ] + ], + "semanticHighlighting": true } diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 288ba00dedf..66b49a69224 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -466,15 +466,16 @@ class SemanticColoringFeature extends Disposable { private _watchers: Record; private _semanticStyling: SemanticStyling; - private _configurationService: IConfigurationService; constructor(modelService: IModelService, themeService: IThemeService, configurationService: IConfigurationService, logService: ILogService) { super(); - this._configurationService = configurationService; this._watchers = Object.create(null); this._semanticStyling = this._register(new SemanticStyling(themeService, logService)); const isSemanticColoringEnabled = (model: ITextModel) => { + if (!themeService.getColorTheme().semanticHighlighting) { + return false; + } const options = configurationService.getValue(SemanticColoringFeature.SETTING_ID, { overrideIdentifier: model.getLanguageIdentifier().language, resource: model.uri }); return options && options.enabled; }; @@ -485,6 +486,20 @@ class SemanticColoringFeature extends Disposable { modelSemanticColoring.dispose(); delete this._watchers[model.uri.toString()]; }; + const handleSettingOrThemeChange = () => { + for (let model of modelService.getModels()) { + const curr = this._watchers[model.uri.toString()]; + if (isSemanticColoringEnabled(model)) { + if (!curr) { + register(model); + } + } else { + if (curr) { + deregister(model, curr); + } + } + } + }; this._register(modelService.onModelAdded((model) => { if (isSemanticColoringEnabled(model)) { register(model); @@ -496,22 +511,12 @@ class SemanticColoringFeature extends Disposable { deregister(model, curr); } })); - this._configurationService.onDidChangeConfiguration(e => { + this._register(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(SemanticColoringFeature.SETTING_ID)) { - for (let model of modelService.getModels()) { - const curr = this._watchers[model.uri.toString()]; - if (isSemanticColoringEnabled(model)) { - if (!curr) { - register(model); - } - } else { - if (curr) { - deregister(model, curr); - } - } - } + handleSettingOrThemeChange(); } - }); + })); + this._register(themeService.onDidColorThemeChange(handleSettingOrThemeChange)); } } diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index 406410b2abe..2fb24d8a345 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -138,6 +138,8 @@ class StandaloneTheme implements IStandaloneTheme { public get tokenColorMap(): string[] { return []; } + + public readonly semanticHighlighting = false; } function isBuiltinTheme(themeName: string): themeName is BuiltinTheme { diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index 16b3d8c120c..fa53430b10e 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -60,6 +60,8 @@ suite('TokenizationSupport2Adapter', () => { return undefined; }, + semanticHighlighting: false, + tokenColorMap: [] }; } diff --git a/src/vs/platform/theme/common/themeService.ts b/src/vs/platform/theme/common/themeService.ts index 6a1fb3a6966..bca0e9bc03f 100644 --- a/src/vs/platform/theme/common/themeService.ts +++ b/src/vs/platform/theme/common/themeService.ts @@ -112,6 +112,11 @@ export interface IColorTheme { * List of all colors used with tokens. getTokenStyleMetadata references the colors by index into this list. */ readonly tokenColorMap: string[]; + + /** + * Defines whether semantic highlighting should be enabled for the theme. + */ + readonly semanticHighlighting: boolean; } export interface IFileIconTheme { diff --git a/src/vs/platform/theme/test/common/testThemeService.ts b/src/vs/platform/theme/test/common/testThemeService.ts index c50b168605c..acada67c302 100644 --- a/src/vs/platform/theme/test/common/testThemeService.ts +++ b/src/vs/platform/theme/test/common/testThemeService.ts @@ -28,6 +28,8 @@ export class TestColorTheme implements IColorTheme { return undefined; } + readonly semanticHighlighting = false; + get tokenColorMap(): string[] { return []; } diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index 36ce9d91f9f..6bb934a241d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -260,6 +260,9 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { } private _isSemanticColoringEnabled() { + if (!this._themeService.getColorTheme().semanticHighlighting) { + return false; + } const options = this._configurationService.getValue('editor.semanticHighlighting', { overrideIdentifier: this._model.getLanguageIdentifier().language, resource: this._model.uri }); return options && options.enabled; } diff --git a/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts b/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts index 7862e19ba67..48c52ffb3a8 100644 --- a/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts +++ b/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts @@ -21,8 +21,8 @@ function getMockTheme(type: ThemeType): IColorTheme { getColor: (colorId: ColorIdentifier): Color | undefined => themingRegistry.resolveDefaultColor(colorId, theme), defines: () => true, getTokenStyleMetadata: () => undefined, - tokenColorMap: [] - + tokenColorMap: [], + semanticHighlighting: false }; return theme; } diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 80e070d65dd..9bbc2c94622 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -59,6 +59,9 @@ export class ColorThemeData implements IWorkbenchColorTheme { watch?: boolean; extensionData?: ExtensionData; + private themeSemanticHighlighting: boolean; + private customSemanticHighlightSupport: boolean | undefined; + private themeTokenColors: ITextMateThemingRule[] = []; private customTokenColors: ITextMateThemingRule[] = []; private colorMap: IColorMap = {}; @@ -78,6 +81,12 @@ export class ColorThemeData implements IWorkbenchColorTheme { this.label = label; this.settingsId = settingsId; this.isLoaded = false; + this.themeSemanticHighlighting = false; + this.customSemanticHighlightSupport = false; + } + + get semanticHighlighting(): boolean { + return this.customSemanticHighlightSupport !== undefined ? this.customSemanticHighlightSupport : this.themeSemanticHighlighting; } get tokenColors(): ITextMateThemingRule[] { @@ -360,6 +369,7 @@ export class ColorThemeData implements IWorkbenchColorTheme { public setCustomTokenColors(customTokenColors: ITokenColorCustomizations) { this.customTokenColors = []; + this.customSemanticHighlightSupport = undefined; // first add the non-theme specific settings this.addCustomTokenColors(customTokenColors); @@ -411,6 +421,9 @@ export class ColorThemeData implements IWorkbenchColorTheme { } } } + if (customTokenColors.semanticHighlighting !== undefined) { + this.customSemanticHighlightSupport = customTokenColors.semanticHighlighting; + } } public ensureLoaded(extensionResourceLoaderService: IExtensionResourceLoaderService): Promise { @@ -431,13 +444,15 @@ export class ColorThemeData implements IWorkbenchColorTheme { const result = { colors: {}, textMateRules: [], - stylingRules: undefined + stylingRules: undefined, + semanticHighlighting: false }; return _loadColorTheme(extensionResourceLoaderService, this.location, result).then(_ => { this.isLoaded = true; this.tokenStylingRules = result.stylingRules; this.colorMap = result.colors; this.themeTokenColors = result.textMateRules; + this.themeSemanticHighlighting = result.semanticHighlighting; }); } @@ -562,7 +577,7 @@ function toCSSSelector(extensionId: string, path: string) { return str; } -function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap, stylingRules: TokenStylingRule[] | undefined }): Promise { +function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap, stylingRules: TokenStylingRule[] | undefined, semanticHighlighting: boolean }): Promise { if (resources.extname(themeLocation) === '.json') { return extensionResourceLoaderService.readExtensionResource(themeLocation).then(content => { let errors: Json.ParseError[] = []; @@ -581,6 +596,7 @@ function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoade convertSettings(contentValue.settings, result); return null; } + result.semanticHighlighting = result.semanticHighlighting || contentValue.semanticHighlighting; let colors = contentValue.colors; if (colors) { if (typeof colors !== 'object') { @@ -605,10 +621,10 @@ function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoade return Promise.reject(new Error(nls.localize({ key: 'error.invalidformat.tokenColors', comment: ['{0} will be replaced by a path. Values in quotes should not be translated.'] }, "Problem parsing color theme file: {0}. Property 'tokenColors' should be either an array specifying colors or a path to a TextMate theme file", themeLocation.toString()))); } } - let tokenStylingRules = contentValue.tokenStylingRules; - if (tokenStylingRules && typeof tokenStylingRules === 'object') { - result.stylingRules = readCustomTokenStyleRules(tokenStylingRules, result.stylingRules); - } + // let tokenStylingRules = contentValue.tokenStylingRules; + // if (tokenStylingRules && typeof tokenStylingRules === 'object') { + // result.stylingRules = readCustomTokenStyleRules(tokenStylingRules, result.stylingRules); + // } return null; }); }); diff --git a/src/vs/workbench/services/themes/common/colorThemeSchema.ts b/src/vs/workbench/services/themes/common/colorThemeSchema.ts index 7f5d0f5c59a..163ee66ef5c 100644 --- a/src/vs/workbench/services/themes/common/colorThemeSchema.ts +++ b/src/vs/workbench/services/themes/common/colorThemeSchema.ts @@ -222,6 +222,10 @@ const colorThemeSchema: IJSONSchema = { $ref: textmateColorsSchemaId } ] + }, + semanticHighlighting: { + type: 'boolean', + description: nls.localize('schema.supportsSemanticHighlighting', 'Whether semantic highlighting should be enabled for this theme.') } } }; diff --git a/src/vs/workbench/services/themes/common/themeConfiguration.ts b/src/vs/workbench/services/themes/common/themeConfiguration.ts index b45ebe90434..a5aac2386f4 100644 --- a/src/vs/workbench/services/themes/common/themeConfiguration.ts +++ b/src/vs/workbench/services/themes/common/themeConfiguration.ts @@ -132,6 +132,10 @@ const tokenColorSchema: IJSONSchema = { textMateRules: { description: nls.localize('editorColors.textMateRules', 'Sets colors and styles using textmate theming rules (advanced).'), $ref: textmateColorsSchemaId + }, + semanticHighlighting: { + description: nls.localize('editorColors.semanticHighlighting', 'Whether semantic highlighting should be enabled for this theme.'), + type: 'boolean' } } }; @@ -154,6 +158,7 @@ const tokenColorCustomizationConfiguration: IConfigurationNode = { [ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS_EXPERIMENTAL]: experimentalTokenStylingCustomizationSchema } }; + configurationRegistry.registerConfiguration(tokenColorCustomizationConfiguration); export function updateColorThemeConfigurationSchemas(themes: IWorkbenchColorTheme[]) { diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index 3e897cd371b..cf95aeb4451 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -97,7 +97,7 @@ export interface IColorCustomizations { } export interface ITokenColorCustomizations { - [groupIdOrThemeSettingsId: string]: string | ITokenColorizationSetting | ITokenColorCustomizations | undefined | ITextMateThemingRule[]; + [groupIdOrThemeSettingsId: string]: string | ITokenColorizationSetting | ITokenColorCustomizations | undefined | ITextMateThemingRule[] | boolean; comments?: string | ITokenColorizationSetting; strings?: string | ITokenColorizationSetting; numbers?: string | ITokenColorizationSetting; @@ -106,6 +106,7 @@ export interface ITokenColorCustomizations { functions?: string | ITokenColorizationSetting; variables?: string | ITokenColorizationSetting; textMateRules?: ITextMateThemingRule[]; + semanticHighlighting?: boolean; } export interface IExperimentalTokenStyleCustomizations { From 48fa5b2a398fd6f3036d7c0327d365464f537868 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 13 Mar 2020 22:29:50 +0100 Subject: [PATCH 123/169] Fix #92516 --- src/vs/platform/environment/common/environment.ts | 1 + src/vs/platform/environment/node/argv.ts | 1 + .../common/userDataSyncEnablementService.ts | 10 ++++++++++ 3 files changed, 12 insertions(+) diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index aa4e77e37d7..9a0cc09ce5d 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -73,6 +73,7 @@ export interface ParsedArgs { 'disable-user-env-probe'?: boolean; 'force'?: boolean; 'force-user-env'?: boolean; + 'sync'?: 'on' | 'off'; // chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches 'no-proxy-server'?: boolean; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 5f2498445f7..f6f818485d9 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -52,6 +52,7 @@ export const OPTIONS: OptionDescriptions> = { 'telemetry': { type: 'boolean', cat: 'o', description: localize('telemetry', "Shows all telemetry events which VS code collects.") }, 'folder-uri': { type: 'string[]', cat: 'o', args: 'uri', description: localize('folderUri', "Opens a window with given folder uri(s)") }, 'file-uri': { type: 'string[]', cat: 'o', args: 'uri', description: localize('fileUri', "Opens a window with given file uri(s)") }, + 'sync': { type: 'string', cat: 'o', description: localize('turn sync', "Turn sync on or off"), args: ['on', 'off'] }, 'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") }, 'builtin-extensions-dir': { type: 'string' }, diff --git a/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts b/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts index 7f9e8a23b87..250dcbe66eb 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts @@ -8,6 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; type SyncEnablementClassification = { enabled?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -29,8 +30,17 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa constructor( @IStorageService private readonly storageService: IStorageService, @ITelemetryService private readonly telemetryService: ITelemetryService, + @IEnvironmentService environmentService: IEnvironmentService, ) { super(); + switch (environmentService.args['sync']) { + case 'on': + this.setEnablement(true); + break; + case 'off': + this.setEnablement(false); + break; + } this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); } From ce5506487379e897f208b4afed1939449514361d Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Fri, 13 Mar 2020 15:11:07 -0700 Subject: [PATCH 124/169] Revert to old bot for find-duplicates. --- .github/commands.json | 12 ------------ .github/commands.yml | 12 ++++++++++++ 2 files changed, 12 insertions(+), 12 deletions(-) create mode 100644 .github/commands.yml diff --git a/.github/commands.json b/.github/commands.json index 43c602207cf..10d2daa7da4 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -115,18 +115,6 @@ "addLabel": "confirmation-pending", "removeLabel": "confirmed" }, - { - "type": "comment", - "name": "findDuplicates", - "allowUsers": [ - "cleidigh", - "usernamehw", - "gjsjohnmurray", - "IllusionMH" - ], - "action": "comment", - "comment": "Potential duplicates:\n${potentialDuplicates}" - }, { "type": "comment", "name": "needsMoreInfo", diff --git a/.github/commands.yml b/.github/commands.yml new file mode 100644 index 00000000000..64fdf683bfe --- /dev/null +++ b/.github/commands.yml @@ -0,0 +1,12 @@ +{ + perform: true, + commands: [ + { + type: 'comment', + name: 'findDuplicates', + allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], + action: 'comment', + comment: "Potential duplicates:\n${potentialDuplicates}" + } + ] +} From cd620d7ea1c61852067ab76bd9b9381d9948979c Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 13 Mar 2020 15:13:38 -0700 Subject: [PATCH 125/169] Note how exceptions from code action commands are handled --- src/vs/vscode.d.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 53826e67839..4b26a5e6ebb 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2116,6 +2116,9 @@ declare module 'vscode' { /** * A [command](#Command) this code action executes. + * + * If this command throws an exception, VS Code displays the exception message to users in the editor at the + * current cursor position. */ command?: Command; @@ -2145,8 +2148,8 @@ declare module 'vscode' { * of code action, such as refactorings. * * - If the user has a [keybinding](https://code.visualstudio.com/docs/editor/refactoring#_keybindings-for-code-actions) - * that auto applies a code action and only a disabled code actions are returned, VS Code will show the user a - * message with `reason` in the editor. + * that auto applies a code action and only a disabled code actions are returned, VS Code will show the user an + * error message with `reason` in the editor. */ disabled?: { /** From 4493125aafa585271cdea454aedf08f1405fba3d Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 13 Mar 2020 15:46:14 -0700 Subject: [PATCH 126/169] Working on handling custom editor move Delegate to custom editor input on move. Eventually this will be hooked up to go back to extensions --- .../customEditor/browser/customEditorInput.ts | 16 ++++++++++++---- .../customEditor/browser/customEditors.ts | 8 +++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 2791509e32f..8effe6aab9e 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -10,7 +10,6 @@ import { basename } from 'vs/base/common/path'; import { isEqual } from 'vs/base/common/resources'; import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { generateUuid } from 'vs/base/common/uuid'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -139,7 +138,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { return undefined; } - return this.handleMove(groupId, target) || this.editorService.createEditorInput({ resource: target, forceFile: true }); + return this.tryMoveWebview(groupId, target) || this.editorService.createEditorInput({ resource: target, forceFile: true }); } public async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { @@ -161,14 +160,22 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { return null; } - public handleMove(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { + move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined { + const newEditor = this.tryMoveWebview(group, newResource); + if (!newEditor) { + return; + } + return { editor: newEditor }; + } + + private tryMoveWebview(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { const editorInfo = this.customEditorService.getCustomEditor(this.viewType); if (editorInfo?.matches(uri)) { const webview = assertIsDefined(this.takeOwnershipOfWebview()); const newInput = this.instantiationService.createInstance(CustomEditorInput, uri, this.viewType, - generateUuid(), + this.id, new Lazy(() => webview)); newInput.updateGroup(groupId); return newInput; @@ -176,6 +183,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { return undefined; } + public undo(): void { assertIsDefined(this._modelRef); this.undoRedoService.undo(this.resource); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 30c7de0b5b8..630f17dd7b7 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -317,12 +317,18 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ continue; } + if (!isEqual(editor.resource, oldResource)) { + continue; + } + const editorInfo = this._editorInfoStore.get(editor.viewType); if (!editorInfo?.matches(newResource)) { continue; } - const replacement = this.createInput(newResource, editor.viewType, group); + const moveResult = editor.move(group.id, newResource); + const replacement = moveResult ? moveResult.editor : this.createInput(newResource, editor.viewType, group); + this.editorService.replaceEditors([{ editor: editor, replacement: replacement, From 57278dbe4d02e8f339730b65f2c5682d5ea7523a Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 13 Mar 2020 17:25:50 -0700 Subject: [PATCH 127/169] Hook up isReadonly for custom editor input --- .../api/browser/mainThreadWebview.ts | 45 ++++++++++++------- .../customEditor/browser/customEditorInput.ts | 2 +- .../customEditor/common/customEditor.ts | 2 + .../common/customTextEditorModel.ts | 8 +++- 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 332017e9ee0..70f129c7a70 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -279,12 +279,12 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._revivers.delete(viewType); } - public $registerTextEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void { - return this.registerEditorProvider(ModelType.Text, extensionData, viewType, options); + public $registerTextEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: extHostProtocol.CustomTextEditorCapabilities): void { + this.registerEditorProvider(ModelType.Text, extensionData, viewType, options, capabilities); } public $registerCustomEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void { - return this.registerEditorProvider(ModelType.Custom, extensionData, viewType, options); + this.registerEditorProvider(ModelType.Custom, extensionData, viewType, options, {}); } private registerEditorProvider( @@ -292,41 +292,45 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, - ): void { + capabilities: extHostProtocol.CustomTextEditorCapabilities, + ): DisposableStore { if (this._editorProviders.has(viewType)) { throw new Error(`Provider for ${viewType} already registered`); } const extension = reviveWebviewExtension(extensionData); - this._editorProviders.set(viewType, this._webviewWorkbenchService.registerResolver({ + const disposables = new DisposableStore(); + disposables.add(this._webviewWorkbenchService.registerResolver({ canResolve: (webviewInput) => { return webviewInput instanceof CustomEditorInput && webviewInput.viewType === viewType; }, resolveWebview: async (webviewInput: CustomEditorInput) => { const handle = webviewInput.id; this._webviewInputs.add(handle, webviewInput); - this.hookupWebviewEventDelegate(handle, webviewInput); + this.hookupWebviewEventDelegate(handle, webviewInput); webviewInput.webview.options = options; webviewInput.webview.extension = extension; const resource = webviewInput.resource; + let modelRef = await this.getOrCreateCustomEditorModel(modelType, resource, viewType); - const modelRef = await this.getOrCreateCustomEditorModel(modelType, webviewInput, resource, viewType); webviewInput.webview.onDispose(() => { modelRef.dispose(); }); + if (capabilities.supportsMove) { + webviewInput.onMove(async (newResource: URI) => { + const oldModel = modelRef; + modelRef = await this.getOrCreateCustomEditorModel(modelType, newResource, viewType); + this._proxy.$onMoveCustomEditor(handle, newResource, viewType); + oldModel.dispose(); + }); + } + try { - await this._proxy.$resolveWebviewEditor( - resource, - handle, - viewType, - webviewInput.getTitle(), - editorGroupToViewColumn(this._editorGroupService, webviewInput.group || 0), - webviewInput.webview.options - ); + await this._proxy.$resolveWebviewEditor(resource, handle, viewType, webviewInput.getTitle(), editorGroupToViewColumn(this._editorGroupService, webviewInput.group || 0), webviewInput.webview.options); } catch (error) { onUnexpectedError(error); webviewInput.webview.html = MainThreadWebviews.getDeserializationFailedContents(viewType); @@ -334,6 +338,10 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma } } })); + + this._editorProviders.set(viewType, disposables); + + return disposables; } public $unregisterEditorProvider(viewType: string): void { @@ -350,11 +358,10 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma private async getOrCreateCustomEditorModel( modelType: ModelType, - webviewInput: WebviewInput, resource: URI, viewType: string, ): Promise> { - const existingModel = this._customEditorService.models.tryRetain(webviewInput.resource, webviewInput.viewType); + const existingModel = this._customEditorService.models.tryRetain(resource, viewType); if (existingModel) { return existingModel; } @@ -612,6 +619,10 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod //#endregion + public isReadonly() { + return this._editable; + } + public get viewType() { return this._viewType; } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 8effe6aab9e..8073a67f522 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -97,7 +97,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { } public isReadonly(): boolean { - return false; // TODO + return this._modelRef ? this._modelRef.object.isReadonly() : false; } public isDirty(): boolean { diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index 3bfb81c05e5..5893a51979d 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -49,6 +49,8 @@ export interface ICustomEditorModel extends IDisposable { readonly viewType: string; readonly resource: URI; + isReadonly(): boolean; + isDirty(): boolean; readonly onDidChangeDirty: Event; diff --git a/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts index a1d8019684d..a041fa249df 100644 --- a/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts +++ b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts @@ -31,12 +31,12 @@ export class CustomTextEditorModel extends Disposable implements ICustomEditorMo private constructor( public readonly viewType: string, private readonly _resource: URI, - model: IReference, + private readonly _model: IReference, @ITextFileService private readonly textFileService: ITextFileService, ) { super(); - this._register(model); + this._register(_model); this._register(this.textFileService.files.onDidChangeDirty(e => { if (isEqual(this.resource, e.resource)) { @@ -50,6 +50,10 @@ export class CustomTextEditorModel extends Disposable implements ICustomEditorMo return this._resource; } + public isReadonly(): boolean { + return this._model.object.isReadonly(); + } + public isDirty(): boolean { return this.textFileService.isDirty(this.resource); } From c81074fb701652c43c0a2878119d3d6d86ef0667 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 13 Mar 2020 17:35:53 -0700 Subject: [PATCH 128/169] Fix webview handlers potentially not being hooked up after move --- src/vs/workbench/api/browser/mainThreadWebview.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 70f129c7a70..8549130f30e 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -390,14 +390,13 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma disposables.add(input.webview.onMessage((message: any) => { this._proxy.$onMessage(handle, message); })); disposables.add(input.webview.onMissingCsp((extension: ExtensionIdentifier) => this._proxy.$onMissingCsp(handle, extension.value))); - input.onDispose(() => { + disposables.add(input.webview.onDispose(() => { disposables.dispose(); - }); - input.webview.onDispose(() => { + this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => { this._webviewInputs.delete(handle); }); - }); + })); } private registerWebviewFromDiffEditorListeners(diffEditorInput: DiffEditorInput): void { From 041a5907b1d3437344fda16da784aba7828af520 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 13 Mar 2020 17:49:52 -0700 Subject: [PATCH 129/169] Add experimental moveCustomTextEditor hook For #77131 Adds a hook that lets extensions preserve the webview for a custom editor across a rename --- src/vs/vscode.proposed.d.ts | 15 +++++++++++ .../workbench/api/common/extHost.protocol.ts | 8 +++++- src/vs/workbench/api/common/extHostWebview.ts | 24 ++++++++++++++++- .../contrib/customEditor/browser/commands.ts | 2 +- .../customEditor/browser/customEditorInput.ts | 27 +++++++++++++++++-- .../customEditor/browser/customEditors.ts | 14 +++++----- .../customEditor/common/customEditor.ts | 6 ++--- .../contrib/webview/browser/webviewEditor.ts | 7 +++-- .../webview/browser/webviewEditorInput.ts | 18 +++++++------ .../browser/webviewWorkbenchService.ts | 16 ++++++++++- 10 files changed, 111 insertions(+), 26 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index c525746b88c..426f7fc6f42 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1473,6 +1473,21 @@ declare module 'vscode' { * @return Thenable indicating that the webview editor has been resolved. */ resolveCustomTextEditor(document: TextDocument, webviewPanel: WebviewPanel): Thenable; + + /** + * TODO: discuss this at api sync. + * + * Handle when the underlying resource for a custom editor is renamed. + * + * This allows the webview for the editor be preserved throughout the rename. If this method is not implemented, + * VS Code will destory the previous custom editor and create a replacement one. + * + * @param newDocument New text document to use for the custom editor. + * @param existingWebviewPanel Webview panel for the custom editor. + * + * @return Thenable indicating that the webview editor has been moved. + */ + moveCustomTextEditor?(newDocument: TextDocument, existingWebviewPanel: WebviewPanel): Thenable; } namespace window { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 8efa4f7e543..9730d689c30 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -576,6 +576,10 @@ export interface WebviewExtensionDescription { readonly location: UriComponents; } +export interface CustomTextEditorCapabilities { + readonly supportsMove?: boolean; +} + export interface MainThreadWebviewsShape extends IDisposable { $createWebviewPanel(extension: WebviewExtensionDescription, handle: WebviewPanelHandle, viewType: string, title: string, showOptions: WebviewPanelShowOptions, options: modes.IWebviewPanelOptions & modes.IWebviewOptions): void; $disposeWebview(handle: WebviewPanelHandle): void; @@ -591,7 +595,7 @@ export interface MainThreadWebviewsShape extends IDisposable { $registerSerializer(viewType: string): void; $unregisterSerializer(viewType: string): void; - $registerTextEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void; + $registerTextEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: CustomTextEditorCapabilities): void; $registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void; $unregisterEditorProvider(viewType: string): void; @@ -627,6 +631,8 @@ export interface ExtHostWebviewsShape { $onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise; $backup(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise; + + $onMoveCustomEditor(handle: WebviewPanelHandle, newResource: UriComponents, viewType: string): Promise; } export interface MainThreadUrlsShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index f4dc637a2fe..81cba4cd4b6 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -488,7 +488,9 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { const disposables = new DisposableStore(); if ('resolveCustomTextEditor' in provider) { disposables.add(this._editorProviders.addTextProvider(viewType, extension, provider)); - this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options); + this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options, { + supportsMove: !!provider.moveCustomTextEditor, + }); } else { disposables.add(this._editorProviders.addCustomProvider(viewType, extension, provider)); this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options); @@ -663,6 +665,26 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { document._disposeEdits(editIds); } + async $onMoveCustomEditor(handle: string, newResourceComponents: UriComponents, viewType: string): Promise { + const entry = this._editorProviders.get(viewType); + if (!entry) { + throw new Error(`No provider found for '${viewType}'`); + } + + if (!(entry.provider as vscode.CustomTextEditorProvider).moveCustomTextEditor) { + throw new Error(`Provider does not implement move '${viewType}'`); + } + + const webview = this.getWebviewPanel(handle); + if (!webview) { + throw new Error(`No webview found`); + } + + const resource = URI.revive(newResourceComponents); + const document = this._extHostDocuments.getDocument(resource); + await (entry.provider as vscode.CustomTextEditorProvider).moveCustomTextEditor!(document, webview); + } + async $undo(resourceComponents: UriComponents, viewType: string, editId: number): Promise { const document = this.getCustomDocument(viewType, resourceComponents); return document._undo(editId); diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index ee79d227e26..94b8ea8294a 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -186,7 +186,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { } } - const newEditorInput = customEditorService.createInput(targetResource, toggleView, activeGroup); + const newEditorInput = customEditorService.createInput(targetResource, toggleView, activeGroup.id); editorService.replaceEditors([{ editor: activeEditor, diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 8073a67f522..39730562c4c 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -161,6 +161,12 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { } move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined { + if (!this._moveHandler) { + return { + editor: this.customEditorService.createInput(newResource, this.viewType, group) + }; + } + this._moveHandler(newResource); const newEditor = this.tryMoveWebview(group, newResource); if (!newEditor) { return; @@ -171,12 +177,12 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { private tryMoveWebview(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { const editorInfo = this.customEditorService.getCustomEditor(this.viewType); if (editorInfo?.matches(uri)) { - const webview = assertIsDefined(this.takeOwnershipOfWebview()); const newInput = this.instantiationService.createInstance(CustomEditorInput, uri, this.viewType, this.id, - new Lazy(() => webview)); + new Lazy(() => undefined!)); // this webview is replaced in the transfer call + this.transfer(newInput); newInput.updateGroup(groupId); return newInput; } @@ -193,4 +199,21 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { assertIsDefined(this._modelRef); this.undoRedoService.redo(this.resource); } + + private _moveHandler?: (newResource: URI) => void; + + public onMove(handler: (newResource: URI) => void): void { + // TODO: Move this to the service + this._moveHandler = handler; + } + + protected transfer(other: CustomEditorInput): CustomEditorInput | undefined { + if (!super.transfer(other)) { + return; + } + + other._moveHandler = this._moveHandler; + this._moveHandler = undefined; + return other; + } } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 630f17dd7b7..f5399eb8fe5 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -21,7 +21,7 @@ import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { EditorInput, EditorOptions, IEditorInput, IEditorPane } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, IEditorInput, IEditorPane, GroupIdentifier } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { webviewEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/browser/extensionPoint'; import { CONTEXT_CUSTOM_EDITORS, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CustomEditorInfo, CustomEditorInfoCollection, CustomEditorPriority, CustomEditorSelector, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; @@ -238,14 +238,14 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ return this.promptOpenWith(resource, options, group); } - const input = this.createInput(resource, viewType, group); + const input = this.createInput(resource, viewType, group?.id); return this.openEditorForResource(resource, input, options, group); } public createInput( resource: URI, viewType: string, - group: IEditorGroup | undefined, + group: GroupIdentifier | undefined, options?: { readonly customClasses: string; }, ): IEditorInput { if (viewType === defaultEditorId) { @@ -257,8 +257,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ return this.webviewService.createWebviewOverlay(id, { customClasses: options?.customClasses }, {}); }); const input = this.instantiationService.createInstance(CustomEditorInput, resource, viewType, id, webview); - if (group) { - input.updateGroup(group.id); + if (typeof group !== 'undefined') { + input.updateGroup(group); } return input; } @@ -327,7 +327,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ } const moveResult = editor.move(group.id, newResource); - const replacement = moveResult ? moveResult.editor : this.createInput(newResource, editor.viewType, group); + const replacement = moveResult ? moveResult.editor : this.createInput(newResource, editor.viewType, group.id); this.editorService.replaceEditors([{ editor: editor, @@ -492,7 +492,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo return undefined; } - const input = this.customEditorService.createInput(resource, bestAvailableEditor.id, group, { customClasses }); + const input = this.customEditorService.createInput(resource, bestAvailableEditor.id, group.id, { customClasses }); if (input instanceof EditorInput) { return input; } diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index 5893a51979d..78369fec628 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -6,14 +6,14 @@ import { distinct, mergeSort } from 'vs/base/common/arrays'; import { Event } from 'vs/base/common/event'; import * as glob from 'vs/base/common/glob'; +import { IDisposable, IReference } from 'vs/base/common/lifecycle'; import { basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorPane, IEditorInput, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; +import { GroupIdentifier, IEditorInput, IEditorPane, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IDisposable, IReference } from 'vs/base/common/lifecycle'; export const ICustomEditorService = createDecorator('customEditorService'); @@ -29,7 +29,7 @@ export interface ICustomEditorService { getContributedCustomEditors(resource: URI): CustomEditorInfoCollection; getUserConfiguredCustomEditors(resource: URI): CustomEditorInfoCollection; - createInput(resource: URI, viewType: string, group: IEditorGroup | undefined, options?: { readonly customClasses: string }): IEditorInput; + createInput(resource: URI, viewType: string, group: GroupIdentifier | undefined, options?: { readonly customClasses: string }): IEditorInput; openWith(resource: URI, customEditorViewType: string, options?: ITextEditorOptions, group?: IEditorGroup): Promise; promptOpenWith(resource: URI, options?: ITextEditorOptions, group?: IEditorGroup): Promise; diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts index 22dc38e88e9..dd29d133c52 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts @@ -109,7 +109,8 @@ export class WebviewEditor extends BaseEditor { return; } - if (this.webview) { + const alreadyOwnsWebview = input instanceof WebviewInput && input.webview === this.webview; + if (this.webview && !alreadyOwnsWebview) { this.webview.release(this); } @@ -125,7 +126,9 @@ export class WebviewEditor extends BaseEditor { input.updateGroup(this.group.id); } - this.claimWebview(input); + if (!alreadyOwnsWebview) { + this.claimWebview(input); + } if (this._dimension) { this.layout(this._dimension); } diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index 98ca51637c8..3da701349a1 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -18,8 +18,9 @@ export class WebviewInput extends EditorInput { private _iconPath?: WebviewIcons; private _group?: GroupIdentifier; - private readonly _webview: Lazy; - private _didSomeoneTakeMyWebview = false; + private _webview: Lazy; + + private _hasTransfered = false; get resource() { return URI.from({ @@ -42,8 +43,8 @@ export class WebviewInput extends EditorInput { dispose() { if (!this.isDisposed()) { - if (!this._didSomeoneTakeMyWebview) { - this._webview?.rawValue?.dispose(); + if (!this._hasTransfered) { + this._webview.rawValue?.dispose(); } } super.dispose(); @@ -107,11 +108,12 @@ export class WebviewInput extends EditorInput { return false; } - protected takeOwnershipOfWebview(): WebviewOverlay | undefined { - if (this._didSomeoneTakeMyWebview) { + protected transfer(other: WebviewInput): WebviewInput | undefined { + if (this._hasTransfered) { return undefined; } - this._didSomeoneTakeMyWebview = true; - return this.webview; + this._hasTransfered = true; + other._webview = this._webview; + return other; } } diff --git a/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts b/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts index 3ffe992a8e1..b32a09cb805 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts @@ -113,11 +113,25 @@ export class LazilyResolvedWebviewEditorInput extends WebviewInput { super(id, viewType, name, webview, webviewService); } + #resolved = false; + @memoize public async resolve() { - await this._webviewWorkbenchService.resolveWebview(this); + if (!this.#resolved) { + this.#resolved = true; + await this._webviewWorkbenchService.resolveWebview(this); + } return super.resolve(); } + + protected transfer(other: LazilyResolvedWebviewEditorInput): WebviewInput | undefined { + if (!super.transfer(other)) { + return; + } + + other.#resolved = this.#resolved; + return other; + } } From 839383dc1d843d8dd94823c5f1c99da7500bc1ee Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sat, 14 Mar 2020 02:18:30 +0100 Subject: [PATCH 130/169] remove settings sync service --- .../sharedProcess/sharedProcessMain.ts | 10 +- .../userDataSync/common/settingsSync.ts | 4 +- .../userDataSync/common/userDataSync.ts | 8 -- .../userDataSync/common/userDataSyncIpc.ts | 36 +----- .../common/userDataSyncService.ts | 6 +- .../test/common/userDataSyncClient.ts | 4 +- .../electron-browser/settingsSyncService.ts | 111 ------------------ src/vs/workbench/workbench.desktop.main.ts | 1 - src/vs/workbench/workbench.web.main.ts | 4 +- 9 files changed, 11 insertions(+), 173 deletions(-) delete mode 100644 src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 0e6c2358199..e8532f91590 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -49,17 +49,16 @@ import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; -import { UserDataSyncChannel, UserDataSyncUtilServiceClient, SettingsSyncChannel, UserDataAutoSyncChannel, UserDataSyncStoreServiceChannel, UserDataSyncBackupStoreServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; +import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, UserDataSyncStoreServiceChannel, UserDataSyncBackupStoreServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { LoggerService } from 'vs/platform/log/node/loggerService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-browser/userDataAutoSyncService'; -import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { NativeStorageService } from 'vs/platform/storage/node/storageService'; import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -197,7 +196,6 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); services.set(IUserDataSyncBackupStoreService, new SyncDescriptor(UserDataSyncBackupStoreService)); services.set(IUserDataSyncEnablementService, new SyncDescriptor(UserDataSyncEnablementService)); - services.set(ISettingsSyncService, new SyncDescriptor(SettingsSynchroniser)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); registerConfiguration(); @@ -229,10 +227,6 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const userDataSyncBackupStoreServiceChannel = new UserDataSyncBackupStoreServiceChannel(userDataSyncBackupStoreService); server.registerChannel('userDataSyncBackupStoreService', userDataSyncBackupStoreServiceChannel); - const settingsSyncService = accessor.get(ISettingsSyncService); - const settingsSyncChannel = new SettingsSyncChannel(settingsSyncService); - server.registerChannel('settingsSync', settingsSyncChannel); - const userDataSyncService = accessor.get(IUserDataSyncService); const userDataSyncChannel = new UserDataSyncChannel(userDataSyncService); server.registerChannel('userDataSync', userDataSyncChannel); diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index add06d81bbc..6704fa622c3 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, CONFIGURATION_SYNC_STORE_KEY, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse } from 'vs/base/common/json'; import { localize } from 'vs/nls'; @@ -33,7 +33,7 @@ function isSettingsSyncContent(thing: any): thing is ISettingsSyncContent { && Object.keys(thing).length === 1; } -export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements ISettingsSyncService { +export class SettingsSynchroniser extends AbstractJsonFileSynchroniser { _serviceBrand: any; diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index f588b56508c..260ca1554d8 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -333,14 +333,6 @@ export interface IConflictSetting { remoteValue: any | undefined; } -export const ISettingsSyncService = createDecorator('ISettingsSyncService'); -export interface ISettingsSyncService extends IUserDataSynchroniser { - _serviceBrand: any; - readonly onDidChangeConflicts: Event; - readonly conflicts: IConflictSetting[]; - resolveSettingsConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise; -} - //#endregion export const CONTEXT_SYNC_STATE = new RawContextKey('syncStatus', SyncStatus.Uninitialized); diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index de2736a2fe3..6b8ef2b6710 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -5,7 +5,7 @@ import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; -import { IUserDataSyncService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataAutoSyncService, IUserDataSyncStoreService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncUtilService, IUserDataAutoSyncService, IUserDataSyncStoreService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { URI } from 'vs/base/common/uri'; import { IStringDictionary } from 'vs/base/common/collections'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; @@ -41,40 +41,6 @@ export class UserDataSyncChannel implements IServerChannel { } } -export class SettingsSyncChannel implements IServerChannel { - - constructor(private readonly service: ISettingsSyncService) { } - - listen(_: unknown, event: string): Event { - switch (event) { - case 'onDidChangeStatus': return this.service.onDidChangeStatus; - case 'onDidChangeLocal': return this.service.onDidChangeLocal; - case 'onDidChangeConflicts': return this.service.onDidChangeConflicts; - } - throw new Error(`Event not found: ${event}`); - } - - call(context: any, command: string, args?: any): Promise { - switch (command) { - case 'sync': return this.service.sync(); - case 'accept': return this.service.accept(args[0]); - case 'pull': return this.service.pull(); - case 'push': return this.service.push(); - case '_getInitialStatus': return Promise.resolve(this.service.status); - case '_getInitialConflicts': return Promise.resolve(this.service.conflicts); - case 'stop': this.service.stop(); return Promise.resolve(); - case 'resetLocal': return this.service.resetLocal(); - case 'hasPreviouslySynced': return this.service.hasPreviouslySynced(); - case 'hasLocalData': return this.service.hasLocalData(); - case 'resolveSettingsConflicts': return this.service.resolveSettingsConflicts(args[0]); - case 'getRemoteContentFromPreview': return this.service.getRemoteContentFromPreview(); - case 'getRemoteContent': return this.service.getRemoteContent(args[0], args[1]); - case 'getLocalBackupContent': return this.service.getLocalBackupContent(args[0], args[1]); - } - throw new Error('Invalid call'); - } -} - export class UserDataAutoSyncChannel implements IServerChannel { constructor(private readonly service: IUserDataAutoSyncService) { } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index a50cd1b369a..fd8d3a93cea 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, ISettingsSyncService, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError, resolveSyncResource, PREVIEW_QUERY } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError, resolveSyncResource, PREVIEW_QUERY } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; @@ -16,6 +16,7 @@ import { equals } from 'vs/base/common/arrays'; import { localize } from 'vs/nls'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { URI } from 'vs/base/common/uri'; +import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; type SyncErrorClassification = { source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -51,6 +52,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _onDidChangeLastSyncTime: Emitter = this._register(new Emitter()); readonly onDidChangeLastSyncTime: Event = this._onDidChangeLastSyncTime.event; + private readonly settingsSynchroniser: SettingsSynchroniser; private readonly keybindingsSynchroniser: KeybindingsSynchroniser; private readonly extensionsSynchroniser: ExtensionsSynchroniser; private readonly globalStateSynchroniser: GlobalStateSynchroniser; @@ -58,12 +60,12 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ constructor( @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @ISettingsSyncService private readonly settingsSynchroniser: ISettingsSyncService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IStorageService private readonly storageService: IStorageService, ) { super(); + this.settingsSynchroniser = this._register(this.instantiationService.createInstance(SettingsSynchroniser)); this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser)); this.globalStateSynchroniser = this._register(this.instantiationService.createInstance(GlobalStateSynchroniser)); this.extensionsSynchroniser = this._register(this.instantiationService.createInstance(ExtensionsSynchroniser)); diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 009853494de..d5e2515eb86 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -6,7 +6,7 @@ import { IRequestService } from 'vs/platform/request/common/request'; import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IUserData, IUserDataManifest, ALL_SYNC_RESOURCES, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, ISettingsSyncService, IUserDataSyncService, getDefaultIgnoredSettings, IUserDataSyncBackupStoreService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, IUserDataManifest, ALL_SYNC_RESOURCES, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncService, getDefaultIgnoredSettings, IUserDataSyncBackupStoreService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { bufferToStream, VSBuffer } from 'vs/base/common/buffer'; import { generateUuid } from 'vs/base/common/uuid'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; @@ -31,7 +31,6 @@ import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagemen import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; import { Disposable } from 'vs/base/common/lifecycle'; -import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { Emitter } from 'vs/base/common/event'; import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; import product from 'vs/platform/product/common/product'; @@ -105,7 +104,6 @@ export class UserDataSyncClient extends Disposable { async getCompatibleExtension() { return null; } }); - this.instantiationService.stub(ISettingsSyncService, this.instantiationService.createInstance(SettingsSynchroniser)); this.instantiationService.stub(IUserDataSyncService, this.instantiationService.createInstance(UserDataSyncService)); if (!empty) { diff --git a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts deleted file mode 100644 index e7fc1a7af40..00000000000 --- a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts +++ /dev/null @@ -1,111 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SyncStatus, ISettingsSyncService, IConflictSetting, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { Emitter, Event } from 'vs/base/common/event'; -import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; - -export class SettingsSyncService extends Disposable implements ISettingsSyncService { - - _serviceBrand: undefined; - - private readonly channel: IChannel; - - readonly resourceKey = 'settings'; - readonly resource = SyncResource.Settings; - - private _status: SyncStatus = SyncStatus.Uninitialized; - get status(): SyncStatus { return this._status; } - private _onDidChangeStatus: Emitter = this._register(new Emitter()); - readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; - - private _conflicts: IConflictSetting[] = []; - get conflicts(): IConflictSetting[] { return this._conflicts; } - private _onDidChangeConflicts: Emitter = this._register(new Emitter()); - readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; - - get onDidChangeLocal(): Event { return this.channel.listen('onDidChangeLocal'); } - - constructor( - @ISharedProcessService sharedProcessService: ISharedProcessService - ) { - super(); - this.channel = sharedProcessService.getChannel('settingsSync'); - this.channel.call('_getInitialStatus').then(status => { - this.updateStatus(status); - this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); - }); - this.channel.call('_getInitialConflicts').then(conflicts => { - if (conflicts.length) { - this.updateConflicts(conflicts); - } - this._register(this.channel.listen('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts))); - }); - } - - pull(): Promise { - return this.channel.call('pull'); - } - - push(): Promise { - return this.channel.call('push'); - } - - sync(): Promise { - return this.channel.call('sync'); - } - - stop(): Promise { - return this.channel.call('stop'); - } - - resetLocal(): Promise { - return this.channel.call('resetLocal'); - } - - hasPreviouslySynced(): Promise { - return this.channel.call('hasPreviouslySynced'); - } - - hasLocalData(): Promise { - return this.channel.call('hasLocalData'); - } - - accept(content: string): Promise { - return this.channel.call('accept', [content]); - } - - resolveSettingsConflicts(conflicts: { key: string, value: any | undefined }[]): Promise { - return this.channel.call('resolveConflicts', [conflicts]); - } - - getRemoteContent(ref?: string, fragment?: string): Promise { - return this.channel.call('getRemoteContent', [ref, fragment]); - } - - getLocalBackupContent(ref?: string, fragment?: string): Promise { - return this.channel.call('getLocalBackupContent', [ref, fragment]); - } - - getRemoteContentFromPreview(): Promise { - return this.channel.call('getRemoteContentFromPreview', []); - } - - private async updateStatus(status: SyncStatus): Promise { - this._status = status; - this._onDidChangeStatus.fire(status); - } - - private async updateConflicts(conflicts: IConflictSetting[]): Promise { - this._conflicts = conflicts; - this._onDidChangeConflicts.fire(conflicts); - } - -} - -registerSingleton(ISettingsSyncService, SettingsSyncService); diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index a8f926566d4..d5ab3c6eb15 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -50,7 +50,6 @@ import 'vs/workbench/services/url/electron-browser/urlService'; import 'vs/workbench/services/workspaces/electron-browser/workspacesService'; import 'vs/workbench/services/workspaces/electron-browser/workspaceEditingService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncService'; -import 'vs/workbench/services/userDataSync/electron-browser/settingsSyncService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncStoreService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService'; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index df598774a44..400cce7151c 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -62,13 +62,12 @@ import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { TunnelService } from 'vs/workbench/services/remote/common/tunnelService'; import { ILoggerService } from 'vs/platform/log/common/log'; import { FileLoggerService } from 'vs/platform/log/common/fileLogService'; -import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService, ISettingsSyncService, IUserDataAutoSyncService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService, IUserDataAutoSyncService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { AuthenticationService, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; -import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { IAuthenticationTokenService, AuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; import { UserDataAutoSyncService } from 'vs/workbench/contrib/userDataSync/browser/userDataAutoSyncService'; import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; @@ -87,7 +86,6 @@ registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService); registerSingleton(IUserDataSyncBackupStoreService, UserDataSyncBackupStoreService); registerSingleton(IAuthenticationTokenService, AuthenticationTokenService); registerSingleton(IUserDataAutoSyncService, UserDataAutoSyncService); -registerSingleton(ISettingsSyncService, SettingsSynchroniser); registerSingleton(IUserDataSyncService, UserDataSyncService); registerSingleton(ITitleService, TitlebarPart); From 57436a5e6562422d5098a56374de3a5fb06462c4 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Sun, 15 Mar 2020 17:05:53 +0100 Subject: [PATCH 131/169] [theme] store themeSemanticHighlighting and update after load --- .../services/themes/common/colorThemeData.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 9bbc2c94622..05992b71044 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -59,8 +59,8 @@ export class ColorThemeData implements IWorkbenchColorTheme { watch?: boolean; extensionData?: ExtensionData; - private themeSemanticHighlighting: boolean; - private customSemanticHighlightSupport: boolean | undefined; + private themeSemanticHighlighting: boolean | undefined; + private customSemanticHighlighting: boolean | undefined; private themeTokenColors: ITextMateThemingRule[] = []; private customTokenColors: ITextMateThemingRule[] = []; @@ -81,12 +81,10 @@ export class ColorThemeData implements IWorkbenchColorTheme { this.label = label; this.settingsId = settingsId; this.isLoaded = false; - this.themeSemanticHighlighting = false; - this.customSemanticHighlightSupport = false; } get semanticHighlighting(): boolean { - return this.customSemanticHighlightSupport !== undefined ? this.customSemanticHighlightSupport : this.themeSemanticHighlighting; + return this.customSemanticHighlighting !== undefined ? this.customSemanticHighlighting : !!this.themeSemanticHighlighting; } get tokenColors(): ITextMateThemingRule[] { @@ -369,7 +367,7 @@ export class ColorThemeData implements IWorkbenchColorTheme { public setCustomTokenColors(customTokenColors: ITokenColorCustomizations) { this.customTokenColors = []; - this.customSemanticHighlightSupport = undefined; + this.customSemanticHighlighting = undefined; // first add the non-theme specific settings this.addCustomTokenColors(customTokenColors); @@ -422,7 +420,7 @@ export class ColorThemeData implements IWorkbenchColorTheme { } } if (customTokenColors.semanticHighlighting !== undefined) { - this.customSemanticHighlightSupport = customTokenColors.semanticHighlighting; + this.customSemanticHighlighting = customTokenColors.semanticHighlighting; } } @@ -476,6 +474,7 @@ export class ColorThemeData implements IWorkbenchColorTheme { selector: this.id.split(' ').join('.'), // to not break old clients themeTokenColors: this.themeTokenColors, extensionData: this.extensionData, + themeSemanticHighlighting: this.themeSemanticHighlighting, colorMap: colorMapData, watch: this.watch }); @@ -483,7 +482,7 @@ export class ColorThemeData implements IWorkbenchColorTheme { } hasEqualData(other: ColorThemeData) { - return objects.equals(this.colorMap, other.colorMap) && objects.equals(this.themeTokenColors, other.themeTokenColors); + return objects.equals(this.colorMap, other.colorMap) && objects.equals(this.themeTokenColors, other.themeTokenColors) && this.themeSemanticHighlighting === other.themeSemanticHighlighting; } get baseTheme(): string { @@ -533,7 +532,7 @@ export class ColorThemeData implements IWorkbenchColorTheme { } break; case 'themeTokenColors': - case 'id': case 'label': case 'settingsId': case 'extensionData': case 'watch': + case 'id': case 'label': case 'settingsId': case 'extensionData': case 'watch': case 'themeSemanticHighlighting': (theme as any)[key] = data[key]; break; } From 556eb1ee3d57d1bdb00846e97d1f62e98da8a6a0 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 15 Mar 2020 08:59:04 -0700 Subject: [PATCH 132/169] Fix "replace all" breaking after opening search editor Fix #92443 --- .../contrib/search/browser/searchWidget.ts | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 706c368d91a..f133d15905d 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -27,7 +27,7 @@ import { ISearchConfigurationProperties } from 'vs/workbench/services/search/com import { attachFindReplaceInputBoxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget'; -import { appendKeyBindingLabel, isSearchViewFocused } from 'vs/workbench/contrib/search/browser/searchActions'; +import { appendKeyBindingLabel, isSearchViewFocused, getSearchView } from 'vs/workbench/contrib/search/browser/searchActions'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { isMacintosh } from 'vs/base/common/platform'; @@ -52,19 +52,9 @@ export interface ISearchWidgetOptions { class ReplaceAllAction extends Action { - private static fgInstance: ReplaceAllAction | null = null; static readonly ID: string = 'search.action.replaceAll'; - static get INSTANCE(): ReplaceAllAction { - if (ReplaceAllAction.fgInstance === null) { - ReplaceAllAction.fgInstance = new ReplaceAllAction(); - } - return ReplaceAllAction.fgInstance; - } - - private _searchWidget: SearchWidget | null = null; - - constructor() { + constructor(private _searchWidget: SearchWidget) { super(ReplaceAllAction.ID, '', 'codicon-replace-all', false); } @@ -417,8 +407,7 @@ export class SearchWidget extends Widget { this._register(this.replaceInput.inputBox.onDidChange(() => this._onReplaceValueChanged.fire())); this._register(this.replaceInput.inputBox.onDidHeightChange(() => this._onDidHeightChange.fire())); - this.replaceAllAction = ReplaceAllAction.INSTANCE; - this.replaceAllAction.searchWidget = this; + this.replaceAllAction = new ReplaceAllAction(this); this.replaceAllAction.label = SearchWidget.REPLACE_ALL_DISABLED_LABEL; this.replaceActionBar = this._register(new ActionBar(this.replaceContainer)); this.replaceActionBar.push([this.replaceAllAction], { icon: true, label: false }); @@ -667,8 +656,12 @@ export function registerContributions() { when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, CONTEXT_FIND_WIDGET_NOT_VISIBLE), primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.Enter, handler: accessor => { - if (isSearchViewFocused(accessor.get(IViewsService))) { - ReplaceAllAction.INSTANCE.run(); + const viewsService = accessor.get(IViewsService); + if (isSearchViewFocused(viewsService)) { + const searchView = getSearchView(viewsService); + if (searchView) { + new ReplaceAllAction(searchView.searchAndReplaceWidget).run(); + } } } }); From 4b800f014dc067fbbbc93fa6ebea6bb23aaea2de Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Sun, 15 Mar 2020 23:21:21 +0100 Subject: [PATCH 133/169] fix missing IThemeService in tests --- .../contrib/search/test/browser/searchActions.test.ts | 3 +++ .../contrib/search/test/browser/searchViewlet.test.ts | 3 +++ .../workbench/contrib/search/test/common/searchModel.test.ts | 3 +++ .../workbench/contrib/search/test/common/searchResult.test.ts | 3 +++ .../test/electron-browser/keybindingEditing.test.ts | 3 +++ .../workbench/test/browser/parts/editor/editorModel.test.ts | 3 +++ .../test/browser/parts/editor/rangeDecorations.test.ts | 3 +++ src/vs/workbench/test/browser/workbenchTestServices.ts | 4 ++-- 8 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts index a2eb0ba8458..1b212bb21cb 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts @@ -18,6 +18,8 @@ import { IFileMatch } from 'vs/workbench/services/search/common/search'; import { ReplaceAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { FileMatch, FileMatchOrMatch, Match } from 'vs/workbench/contrib/search/common/searchModel'; import { MockObjectTree } from 'vs/workbench/contrib/search/test/browser/mockSearchTree'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; suite('Search Actions', () => { @@ -153,6 +155,7 @@ suite('Search Actions', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IThemeService, new TestThemeService()); return instantiationService.createInstance(ModelServiceImpl); } }); diff --git a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts index c8f8bbbccac..39499ce4639 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts @@ -15,6 +15,8 @@ import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { FileMatch, Match, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import { isWindows } from 'vs/base/common/platform'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; suite('Search - Viewlet', () => { let instantiation: TestInstantiationService; @@ -105,6 +107,7 @@ suite('Search - Viewlet', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IThemeService, new TestThemeService()); return instantiationService.createInstance(ModelServiceImpl); } }); diff --git a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts index 4bd8e74e94e..545d7de9943 100644 --- a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts @@ -19,6 +19,8 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { SearchModel } from 'vs/workbench/contrib/search/common/searchModel'; import * as process from 'vs/base/common/process'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; const nullEvent = new class { id: number = -1; @@ -328,6 +330,7 @@ suite('SearchModel', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IThemeService, new TestThemeService()); return instantiationService.createInstance(ModelServiceImpl); } diff --git a/src/vs/workbench/contrib/search/test/common/searchResult.test.ts b/src/vs/workbench/contrib/search/test/common/searchResult.test.ts index 82e8ecc9fdd..f46f158ac29 100644 --- a/src/vs/workbench/contrib/search/test/common/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/common/searchResult.test.ts @@ -16,6 +16,8 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; const lineOneRange = new OneLineRange(1, 0, 1); @@ -352,6 +354,7 @@ suite('SearchResult', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IThemeService, new TestThemeService()); return instantiationService.createInstance(ModelServiceImpl); } }); diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index fc22f855180..1e2b68241c2 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -55,6 +55,8 @@ import { WorkingCopyFileService, IWorkingCopyFileService } from 'vs/workbench/se import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; import { TestTextResourcePropertiesService, TestContextService, TestWorkingCopyService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; class TestEnvironmentService extends NativeWorkbenchEnvironmentService { @@ -107,6 +109,7 @@ suite('KeybindingsEditing', () => { instantiationService.stub(IFilesConfigurationService, instantiationService.createInstance(FilesConfigurationService)); instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(instantiationService.get(IConfigurationService))); instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService)); + instantiationService.stub(IThemeService, new TestThemeService()); instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl)); const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); diff --git a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts index e74010b7c6e..4742a1c19d0 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts @@ -24,6 +24,8 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TestTextResourcePropertiesService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; class MyEditorModel extends EditorModel { } class MyTextEditorModel extends BaseTextEditorModel { @@ -84,6 +86,7 @@ suite('Workbench editor model', () => { instantiationService.stub(IDialogService, dialogService); instantiationService.stub(INotificationService, notificationService); instantiationService.stub(IUndoRedoService, undoRedoService); + instantiationService.stub(IThemeService, new TestThemeService()); return instantiationService.createInstance(ModelServiceImpl); } }); diff --git a/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts b/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts index 82285eeb738..224d20cfa21 100644 --- a/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts @@ -22,6 +22,8 @@ import { CoreNavigationCommands } from 'vs/editor/browser/controller/coreCommand import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; suite('Editor - Range decorations', () => { @@ -157,6 +159,7 @@ suite('Editor - Range decorations', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IThemeService, new TestThemeService()); return instantiationService.createInstance(ModelServiceImpl); } }); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index fbe641543e7..7910881aa2b 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -141,6 +141,8 @@ export function workbenchInstantiationService(overrides?: { textFileService?: (i instantiationService.stub(IHistoryService, new TestHistoryService()); instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(configService)); instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService)); + const themeService = new TestThemeService(); + instantiationService.stub(IThemeService, themeService); instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl)); instantiationService.stub(IFileService, new TestFileService()); instantiationService.stub(IBackupFileService, new TestBackupFileService()); @@ -156,8 +158,6 @@ export function workbenchInstantiationService(overrides?: { textFileService?: (i instantiationService.stub(ITextFileService, overrides?.textFileService ? overrides.textFileService(instantiationService) : instantiationService.createInstance(TestTextFileService)); instantiationService.stub(IHostService, instantiationService.createInstance(TestHostService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); - const themeService = new TestThemeService(); - instantiationService.stub(IThemeService, themeService); instantiationService.stub(ILogService, new NullLogService()); const editorGroupService = new TestEditorGroupsService([new TestEditorGroupView(0)]); instantiationService.stub(IEditorGroupsService, editorGroupService); From 493499aeb597f4d1b6db5ffd4fc71d4a3a943550 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Sun, 15 Mar 2020 23:52:09 +0100 Subject: [PATCH 134/169] remove workarounds for missing themeServices in tests --- .../common/services/modelServiceImpl.ts | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 66b49a69224..0db7b1795d6 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -530,12 +530,9 @@ class SemanticStyling extends Disposable { ) { super(); this._caches = new WeakMap(); - if (this._themeService) { - // workaround for tests which use undefined... :/ - this._register(this._themeService.onDidColorThemeChange(() => { - this._caches = new WeakMap(); - })); - } + this._register(this._themeService.onDidColorThemeChange(() => { + this._caches = new WeakMap(); + })); } public get(provider: DocumentSemanticTokensProvider): SemanticColoringProviderStyling { @@ -773,14 +770,12 @@ class ModelSemanticColoring extends Disposable { this._fetchSemanticTokens.schedule(); })); - if (themeService) { - // workaround for tests which use undefined... :/ - this._register(themeService.onDidColorThemeChange(_ => { - // clear out existing tokens - this._setSemanticTokens(null, null, null, []); - this._fetchSemanticTokens.schedule(); - })); - } + this._register(themeService.onDidColorThemeChange(_ => { + // clear out existing tokens + this._setSemanticTokens(null, null, null, []); + this._fetchSemanticTokens.schedule(); + })); + this._fetchSemanticTokens.schedule(0); } From 078b57357ebf1b2af671f9a73864deadbc66f51f Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Sun, 15 Mar 2020 16:22:37 -0700 Subject: [PATCH 135/169] Temporarily revert to GitHub Apps due to Actions account being frozen. --- .github/commands.yml | 142 +++++++++++++++++++++++ .github/workflows/commands.yml | 40 +++---- .github/workflows/copycat.yml | 50 ++++---- .github/workflows/needs-version-info.yml | 38 +++--- 4 files changed, 206 insertions(+), 64 deletions(-) diff --git a/.github/commands.yml b/.github/commands.yml index 64fdf683bfe..d2bac8edb1f 100644 --- a/.github/commands.yml +++ b/.github/commands.yml @@ -1,12 +1,154 @@ +# { +# perform: true, +# commands: [ +# { +# type: 'comment', +# name: 'findDuplicates', +# allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], +# action: 'comment', +# comment: "Potential duplicates:\n${potentialDuplicates}" +# } +# ] +# } + { perform: true, commands: [ + { + type: 'comment', + name: 'question', + allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], + action: 'updateLabels', + addLabel: '*question' + }, + { + type: 'label', + name: '*question', + allowTriggerByBot: true, + action: 'close', + comment: "Please ask your question on [StackOverflow](https://aka.ms/vscodestackoverflow). We have a great community over [there](https://aka.ms/vscodestackoverflow). They have already answered thousands of questions and are happy to answer yours as well. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + }, + { + type: 'label', + name: '*dev-question', + allowTriggerByBot: true, + action: 'close', + comment: "We have a great developer community [over on slack](https://aka.ms/vscode-dev-community) where extension authors help each other. This is a great place for you to ask questions and find support.\n\nHappy Coding!" + }, + { + type: 'label', + name: '*extension-candidate', + allowTriggerByBot: true, + action: 'close', + comment: "We try to keep VS Code lean and we think the functionality you're asking for is great for a VS Code extension. Maybe you can already find one that suits you in the [VS Code Marketplace](https://aka.ms/vscodemarketplace). Just in case, in a few simple steps you can get started [writing your own extension](https://aka.ms/vscodewritingextensions). See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + }, + { + type: 'label', + name: '*not-reproducible', + allowTriggerByBot: true, + action: 'close', + comment: "We closed this issue because we are unable to reproduce the problem with the steps you describe. Chances are we've already fixed your problem in a recent version of VS Code. If not, please ask us to reopen the issue and provide us with more detail. Our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines might help you with that.\n\nHappy Coding!" + }, + { + type: 'label', + name: '*out-of-scope', + allowTriggerByBot: true, + action: 'close', + comment: "We closed this issue because we don't plan to address it in the foreseeable future. You can find more detailed information about our decision-making process [here](https://aka.ms/vscode-out-of-scope). If you disagree and feel that this issue is crucial: We are happy to listen and to reconsider.\n\nIf you wonder what we are up to, please see our [roadmap](https://aka.ms/vscoderoadmap) and [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nThanks for your understanding and happy coding!" + }, + { + type: 'comment', + name: 'causedByExtension', + allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], + action: 'updateLabels', + addLabel: '*caused-by-extension' + }, + { + type: 'label', + name: '*caused-by-extension', + allowTriggerByBot: true, + action: 'close', + comment: "This issue is caused by an extension, please file it with the repository (or contact) the extension has linked in its overview in VS Code or the [marketplace](https://aka.ms/vscodemarketplace) for VS Code. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + }, + { + type: 'label', + name: '*as-designed', + allowTriggerByBot: true, + action: 'close', + comment: "The described behavior is how it is expected to work. If you disagree, please explain what is expected and what is not in more detail. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + }, + { + type: 'label', + name: '*english-please', + allowTriggerByBot: true, + action: 'close', + comment: "This issue is being closed because its description is not in English, that makes it hard for us to work on it. Please open a new issue with an English description. You might find [Bing Translator](https://www.bing.com/translator) useful." + }, + { + type: 'comment', + name: 'duplicate', + allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], + action: 'updateLabels', + addLabel: '*duplicate' + }, + { + type: 'label', + name: '*duplicate', + allowTriggerByBot: true, + action: 'close', + comment: "Thanks for creating this issue! We figured it's covering the same as another one we already have. Thus, we closed this one as a duplicate. You can search for existing issues [here](https://aka.ms/vscodeissuesearch). See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + }, + { + type: 'comment', + name: 'confirm', + allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], + action: 'updateLabels', + addLabel: 'confirmed', + removeLabel: 'confirmation-pending' + }, + { + type: 'comment', + name: 'confirmationPending', + allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], + action: 'updateLabels', + addLabel: 'confirmation-pending', + removeLabel: 'confirmed' + }, { type: 'comment', name: 'findDuplicates', allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], action: 'comment', comment: "Potential duplicates:\n${potentialDuplicates}" + }, + { + type: 'comment', + name: 'needsMoreInfo', + allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], + action: 'updateLabels', + addLabel: 'needs more info', + comment: "Thanks for creating this issue! We figured it's missing some basic information or in some other way doesn't follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines. Please take the time to review these and update the issue.\n\nHappy Coding!" + }, + { + type: 'label', + name: '~needs more info', + action: 'updateLabels', + addLabel: 'needs more info', + removeLabel: '~needs more info', + comment: "Thanks for creating this issue! We figured it's missing some basic information or in some other way doesn't follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines. Please take the time to review these and update the issue.\n\nHappy Coding!" + }, + { + type: 'comment', + name: 'a11ymas', + allowUsers: ['AccessibilityTestingTeam-TCS', 'dixitsonali95', 'Mohini78', 'ChitrarupaSharma', 'mspatil110', 'umasarath52', 'v-umnaik'], + action: 'updateLabels', + addLabel: 'a11ymas' + }, + { + type: 'label', + name: '*off-topic', + action: 'close', + comment: "Thanks for creating this issue. We think this issue is unactionable or unrelated to the goals of this project. Please follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" } ] } diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index 9a31b98bfe2..dfe4dd699ef 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -1,21 +1,21 @@ -name: Commands -on: - issue_comment: - types: [created] - issues: - types: [labeled] +# name: Commands +# on: +# issue_comment: +# types: [created] +# issues: +# types: [labeled] -jobs: - main: - runs-on: ubuntu-latest - steps: - - name: Checkout Actions - uses: actions/checkout@v2 - with: - repository: 'JacksonKearl/vscode-triage-github-actions' - ref: v2 - - name: Run Commands - uses: ./commands - with: - token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} - config-path: commands +# jobs: +# main: +# runs-on: ubuntu-latest +# steps: +# - name: Checkout Actions +# uses: actions/checkout@v2 +# with: +# repository: 'JacksonKearl/vscode-triage-github-actions' +# ref: v2 +# - name: Run Commands +# uses: ./commands +# with: +# token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} +# config-path: commands diff --git a/.github/workflows/copycat.yml b/.github/workflows/copycat.yml index c4b706759f9..4140255d4d2 100644 --- a/.github/workflows/copycat.yml +++ b/.github/workflows/copycat.yml @@ -1,26 +1,26 @@ -name: CopyCat -on: - issues: - types: [opened] +# name: CopyCat +# on: +# issues: +# types: [opened] -jobs: - main: - runs-on: ubuntu-latest - steps: - - name: Checkout Actions - uses: actions/checkout@v2 - with: - repository: 'JacksonKearl/vscode-triage-github-actions' - ref: v2 - - name: Run CopyCat (JacksonKearl/testissues) - uses: ./copycat - with: - token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} - owner: JacksonKearl - repo: testissues - - name: Run CopyCat (chrmarti/testissues) - uses: ./copycat - with: - token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} - owner: chrmarti - repo: testissues +# jobs: +# main: +# runs-on: ubuntu-latest +# steps: +# - name: Checkout Actions +# uses: actions/checkout@v2 +# with: +# repository: 'JacksonKearl/vscode-triage-github-actions' +# ref: v2 +# - name: Run CopyCat (JacksonKearl/testissues) +# uses: ./copycat +# with: +# token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} +# owner: JacksonKearl +# repo: testissues +# - name: Run CopyCat (chrmarti/testissues) +# uses: ./copycat +# with: +# token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} +# owner: chrmarti +# repo: testissues diff --git a/.github/workflows/needs-version-info.yml b/.github/workflows/needs-version-info.yml index b3cb0fc0a3c..d8fa692d96a 100644 --- a/.github/workflows/needs-version-info.yml +++ b/.github/workflows/needs-version-info.yml @@ -1,20 +1,20 @@ -name: Needs Version Info -on: - issues: - types: [opened] +# name: Needs Version Info +# on: +# issues: +# types: [opened] -jobs: - main: - runs-on: ubuntu-latest - steps: - - name: Checkout Actions - uses: actions/checkout@v2 - with: - repository: 'JacksonKearl/vscode-triage-github-actions' - ref: v2 - - name: Run Needs Version Info - uses: ./needs-more-info - with: - token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} - matcher: '\b(\d\.\d{2,3}\.\d|insiders?|1\.\d\d\d?)\b' - label: ~needs version info +# jobs: +# main: +# runs-on: ubuntu-latest +# steps: +# - name: Checkout Actions +# uses: actions/checkout@v2 +# with: +# repository: 'JacksonKearl/vscode-triage-github-actions' +# ref: v2 +# - name: Run Needs Version Info +# uses: ./needs-more-info +# with: +# token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} +# matcher: '\b(\d\.\d{2,3}\.\d|insiders?|1\.\d\d\d?)\b' +# label: ~needs version info From 65a594e847140889947d8362cf9ba6deaf49c040 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Sun, 15 Mar 2020 19:54:32 -0700 Subject: [PATCH 136/169] Fix #92643, vertically align codicons in list views --- src/vs/workbench/browser/parts/views/media/views.css | 4 ++++ src/vs/workbench/contrib/debug/browser/media/debugViewlet.css | 1 + src/vs/workbench/contrib/scm/browser/media/scmViewlet.css | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index d2ef7ff154a..f706855932b 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -171,6 +171,10 @@ background-repeat: no-repeat; } +.customview-tree .monaco-list .custom-view-tree-node-item .actions .action-label.codicon { + line-height: 22px; +} + .customview-tree .monaco-list .custom-view-tree-node-item .actions .action-label.codicon::before { vertical-align: middle; } diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index e988c45f478..7a67a067a43 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -164,6 +164,7 @@ .monaco-workbench .debug-pane .debug-call-stack .monaco-action-bar .action-item > .action-label { width: 16px; height: 100%; + line-height: 22px; margin-right: 8px; vertical-align: text-top; } diff --git a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css index 93d8bb916dc..5ea3fd924b9 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css +++ b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css @@ -154,7 +154,7 @@ } .scm-viewlet .monaco-list-row .resource > .name > .monaco-icon-label > .actions .action-label.codicon { - height: 22px; + line-height: 22px; } .scm-viewlet .scm-editor { From 798481c7978af034b054e14ace4e471e7a83f665 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Mar 2020 07:38:45 +0100 Subject: [PATCH 137/169] fix tests --- src/vs/platform/userDataSync/test/common/userDataSyncClient.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index d5e2515eb86..7f6e210866e 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -56,6 +56,7 @@ export class UserDataSyncClient extends Disposable { keybindingsResource: joinPath(userDataDirectory, 'keybindings.json'), keybindingsSyncPreviewResource: joinPath(userDataSyncHome, 'keybindings.json'), argvResource: joinPath(userDataDirectory, 'argv.json'), + args: {} }); const logService = new NullLogService(); From eac4cc2221fb07266c49a2360c6583426c589931 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 16 Mar 2020 08:30:16 +0100 Subject: [PATCH 138/169] quick access - introduce and reuse editor navigation base type --- .../editorNavigationQuickAccess.ts | 192 ++++++++++++++++++ .../quickAccess/gotoLineQuickAccess.ts | 74 +------ .../quickAccess/gotoSymbolQuickAccess.ts | 82 ++------ .../editor/contrib/quickAccess/quickAccess.ts | 104 ---------- .../standaloneGotoLineQuickAccess.ts | 4 +- .../standaloneGotoSymbolQuickAccess.ts | 4 +- .../quickaccess/gotoLineQuickAccess.ts | 6 +- .../browser/quickaccess/gotoSymbolAccess.ts | 6 +- 8 files changed, 226 insertions(+), 246 deletions(-) create mode 100644 src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts delete mode 100644 src/vs/editor/contrib/quickAccess/quickAccess.ts diff --git a/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts b/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts new file mode 100644 index 00000000000..4ed5f0cd426 --- /dev/null +++ b/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts @@ -0,0 +1,192 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IEditor, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; +import { IModelDeltaDecoration, OverviewRulerLane, ITextModel } from 'vs/editor/common/model'; +import { IRange } from 'vs/editor/common/core/range'; +import { themeColorFromId } from 'vs/platform/theme/common/themeService'; +import { overviewRulerRangeHighlight } from 'vs/editor/common/view/editorColorRegistry'; +import { IQuickPick, IQuickPickItem, IKeyMods } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; +import { isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { once } from 'vs/base/common/functional'; + +interface IEditorLineDecoration { + rangeHighlightId: string; + overviewRulerDecorationId: string; +} + +/** + * A reusable quick access provider for the editor with support + * for adding decorations for navigating in the currently active file + * (for example "Go to line", "Go to symbol"). + */ +export abstract class AbstractEditorNavigationQuickAccessProvider implements IQuickAccessProvider { + + //#region Provider methods + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Disable filtering & sorting, we control the results + picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; + + // Provide based on current active editor + let pickerDisposable = this.doProvide(picker, token); + disposables.add(toDisposable(() => pickerDisposable.dispose())); + + // Re-create whenever the active editor changes + disposables.add(this.onDidActiveTextEditorControlChange(() => { + pickerDisposable.dispose(); + pickerDisposable = this.doProvide(picker, token); + })); + + return disposables; + } + + private doProvide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // With text control + const editor = this.activeTextEditorControl; + if (editor && this.canProvideWithTextEditor(editor)) { + + // Restore any view state if this picker was closed + // without actually going to a line + const lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState()); + once(token.onCancellationRequested)(() => { + if (lastKnownEditorViewState) { + editor.restoreViewState(lastKnownEditorViewState); + } + }); + + // Clean up decorations on dispose + disposables.add(toDisposable(() => this.clearDecorations(editor))); + + // Ask subclass for entries + disposables.add(this.provideWithTextEditor(editor, picker, token)); + } + + // Without text control + else { + disposables.add(this.provideWithoutTextEditor(picker, token)); + } + + return disposables; + } + + /** + * Subclasses to implement if they can operate on the text editor. + */ + protected canProvideWithTextEditor(editor: IEditor): boolean { + return true; + } + + /** + * Subclasses to implement to provide picks for the picker when an editor is active. + */ + protected abstract provideWithTextEditor(editor: IEditor, picker: IQuickPick, token: CancellationToken): IDisposable; + + /** + * Subclasses to implement to provide picks for the picker when no editor is active. + */ + protected abstract provideWithoutTextEditor(picker: IQuickPick, token: CancellationToken): IDisposable; + + protected gotoLocation(editor: IEditor, range: IRange, keyMods: IKeyMods): void { + editor.setSelection(range); + editor.revealRangeInCenter(range, ScrollType.Smooth); + editor.focus(); + } + + protected getModel(editor: IEditor | IDiffEditor): ITextModel | undefined { + return isDiffEditor(editor) ? + editor.getModel()?.modified : + editor.getModel() as ITextModel; + } + + //#endregion + + + //#region Editor access + + /** + * Subclasses to provide an event when the active editor control changes. + */ + protected abstract readonly onDidActiveTextEditorControlChange: Event; + + /** + * Subclasses to provide the current active editor control. + */ + protected abstract activeTextEditorControl: IEditor | undefined; + + //#endregion + + + //#region Decorations Utils + + private rangeHighlightDecorationId: IEditorLineDecoration | undefined = undefined; + + protected addDecorations(editor: IEditor, range: IRange): void { + editor.changeDecorations(changeAccessor => { + + // Reset old decorations if any + const deleteDecorations: string[] = []; + if (this.rangeHighlightDecorationId) { + deleteDecorations.push(this.rangeHighlightDecorationId.overviewRulerDecorationId); + deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId); + + this.rangeHighlightDecorationId = undefined; + } + + // Add new decorations for the range + const newDecorations: IModelDeltaDecoration[] = [ + + // highlight the entire line on the range + { + range, + options: { + className: 'rangeHighlight', + isWholeLine: true + } + }, + + // also add overview ruler highlight + { + range, + options: { + overviewRuler: { + color: themeColorFromId(overviewRulerRangeHighlight), + position: OverviewRulerLane.Full + } + } + } + ]; + + const [rangeHighlightId, overviewRulerDecorationId] = changeAccessor.deltaDecorations(deleteDecorations, newDecorations); + + this.rangeHighlightDecorationId = { rangeHighlightId, overviewRulerDecorationId }; + }); + } + + protected clearDecorations(editor: IEditor): void { + const rangeHighlightDecorationId = this.rangeHighlightDecorationId; + if (rangeHighlightDecorationId) { + editor.changeDecorations(changeAccessor => { + changeAccessor.deltaDecorations([ + rangeHighlightDecorationId.overviewRulerDecorationId, + rangeHighlightDecorationId.rangeHighlightId + ], []); + }); + + this.rangeHighlightDecorationId = undefined; + } + } + + //#endregion +} diff --git a/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts index 06847ad5d62..5e0598a4f2b 100644 --- a/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts @@ -4,55 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IQuickPick, IQuickPickItem, IKeyMods } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { DisposableStore, toDisposable, IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { once } from 'vs/base/common/functional'; -import { IEditor, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; -import { ITextModel } from 'vs/editor/common/model'; -import { isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IEditor, ScrollType } from 'vs/editor/common/editorCommon'; import { IRange } from 'vs/editor/common/core/range'; -import { withNullAsUndefined } from 'vs/base/common/types'; -import { AbstractEditorQuickAccessProvider } from 'vs/editor/contrib/quickAccess/quickAccess'; +import { AbstractEditorNavigationQuickAccessProvider } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess'; import { IPosition } from 'vs/editor/common/core/position'; interface IGotoLineQuickPickItem extends IQuickPickItem, Partial { } -export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditorQuickAccessProvider { +export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider { static PREFIX = ':'; - provide(picker: IQuickPick, token: CancellationToken): IDisposable { - const disposables = new DisposableStore(); - - // Disable filtering & sorting, we control the results - picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; - - // Provide based on current active editor - let pickerDisposable = this.doProvide(picker, token); - disposables.add(toDisposable(() => pickerDisposable.dispose())); - - // Re-create whenever the active editor changes - disposables.add(this.onDidActiveTextEditorControlChange(() => { - pickerDisposable.dispose(); - pickerDisposable = this.doProvide(picker, token); - })); - - return disposables; - } - - private doProvide(picker: IQuickPick, token: CancellationToken): IDisposable { - - // With text control - if (this.activeTextEditorControl) { - return this.doProvideWithTextEditor(this.activeTextEditorControl, picker, token); - } - - // Without text control - return this.doProvideWithoutTextEditor(picker); - } - - private doProvideWithoutTextEditor(picker: IQuickPick): IDisposable { + protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line."); picker.items = [{ label }]; picker.ariaLabel = label; @@ -60,18 +26,9 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor return Disposable.None; } - private doProvideWithTextEditor(editor: IEditor, picker: IQuickPick, token: CancellationToken): IDisposable { + protected provideWithTextEditor(editor: IEditor, picker: IQuickPick, token: CancellationToken): IDisposable { const disposables = new DisposableStore(); - // Restore any view state if this picker was closed - // without actually going to a line - const lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState()); - once(token.onCancellationRequested)(() => { - if (lastKnownEditorViewState) { - editor.restoreViewState(lastKnownEditorViewState); - } - }); - // Goto line once picked disposables.add(picker.onDidAccept(() => { const [item] = picker.selectedItems; @@ -80,7 +37,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor return; } - this.gotoLine(editor, this.toRange(item.lineNumber, item.column), picker.keyMods); + this.gotoLocation(editor, this.toRange(item.lineNumber, item.column), picker.keyMods); picker.hide(); } @@ -117,9 +74,6 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor updatePickerAndEditor(); disposables.add(picker.onDidChangeValue(() => updatePickerAndEditor())); - // Clean up decorations on dispose - disposables.add(toDisposable(() => this.clearDecorations(editor))); - return disposables; } @@ -191,16 +145,4 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor private lineCount(editor: IEditor): number { return this.getModel(editor)?.getLineCount() ?? 0; } - - private getModel(editor: IEditor | IDiffEditor): ITextModel | undefined { - return isDiffEditor(editor) ? - editor.getModel()?.modified : - editor.getModel() as ITextModel; - } - - protected gotoLine(editor: IEditor, range: IRange, keyMods: IKeyMods): void { - editor.setSelection(range); - editor.revealRangeInCenter(range, ScrollType.Smooth); - editor.focus(); - } } diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts index 1c66d349e0a..4df4f4feb99 100644 --- a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -4,16 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IQuickPick, IQuickPickItem, IKeyMods, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { DisposableStore, toDisposable, IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { once } from 'vs/base/common/functional'; -import { IEditor, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; +import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IEditor, ScrollType } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { withNullAsUndefined } from 'vs/base/common/types'; -import { AbstractEditorQuickAccessProvider } from 'vs/editor/contrib/quickAccess/quickAccess'; +import { AbstractEditorNavigationQuickAccessProvider } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess'; import { DocumentSymbol, SymbolKinds, SymbolTag, DocumentSymbolProviderRegistry, SymbolKind } from 'vs/editor/common/modes'; import { OutlineModel, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { values } from 'vs/base/common/collections'; @@ -27,47 +24,19 @@ interface IGotoSymbolQuickPickItem extends IQuickPickItem { range?: { decoration: IRange, selection: IRange }, } -export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEditorQuickAccessProvider { +export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider { static PREFIX = '@'; static SCOPE_PREFIX = ':'; static PREFIX_BY_CATEGORY = `${AbstractGotoSymbolQuickAccessProvider.PREFIX}${AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX}`; - provide(picker: IQuickPick, token: CancellationToken): IDisposable { - const disposables = new DisposableStore(); + protected canProvideWithTextEditor(editor: IEditor): boolean { + const model = this.getModel(editor); - // Disable filtering & sorting, we control the results - picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; - - // Provide based on current active editor - let pickerDisposable = this.doProvide(picker, token); - disposables.add(toDisposable(() => pickerDisposable.dispose())); - - // Re-create whenever the active editor changes - disposables.add(this.onDidActiveTextEditorControlChange(() => { - pickerDisposable.dispose(); - pickerDisposable = this.doProvide(picker, token); - })); - - return disposables; + return !!model && DocumentSymbolProviderRegistry.has(model); } - private doProvide(picker: IQuickPick, token: CancellationToken): IDisposable { - const activeTextEditorControl = this.activeTextEditorControl; - - // With text control - if (activeTextEditorControl) { - const model = this.getModel(activeTextEditorControl); - if (model && DocumentSymbolProviderRegistry.has(model)) { - return this.doProvideWithSymbols(activeTextEditorControl, model, picker, token); - } - } - - // Without text control - return this.doProvideWithoutSymbols(picker); - } - - private doProvideWithoutSymbols(picker: IQuickPick): IDisposable { + protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { const label = localize('cannotRunGotoSymbol', "Open a text editor with symbol information first to go to a symbol."); picker.items = [{ label, index: 0, kind: SymbolKind.String }]; picker.ariaLabel = label; @@ -75,23 +44,19 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit return Disposable.None; } - private doProvideWithSymbols(editor: IEditor, model: ITextModel, picker: IQuickPick, token: CancellationToken): IDisposable { - const disposables = new DisposableStore(); + protected provideWithTextEditor(editor: IEditor, picker: IQuickPick, token: CancellationToken): IDisposable { + const model = this.getModel(editor); + if (!model) { + return Disposable.None; + } - // Restore any view state if this picker was closed - // without actually going to a symbol - const lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState()); - once(token.onCancellationRequested)(() => { - if (lastKnownEditorViewState) { - editor.restoreViewState(lastKnownEditorViewState); - } - }); + const disposables = new DisposableStore(); // Goto symbol once picked disposables.add(picker.onDidAccept(() => { const [item] = picker.selectedItems; if (item && item.range) { - this.gotoSymbol(editor, item.range.selection, picker.keyMods); + this.gotoLocation(editor, item.range.selection, picker.keyMods); picker.hide(); } @@ -139,9 +104,6 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit } })); - // Clean up decorations on dispose - disposables.add(toDisposable(() => this.clearDecorations(editor))); - return disposables; } @@ -342,18 +304,6 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit } } } - - private getModel(editor: IEditor | IDiffEditor): ITextModel | undefined { - return isDiffEditor(editor) ? - editor.getModel()?.modified : - editor.getModel() as ITextModel; - } - - protected gotoSymbol(editor: IEditor, range: IRange, keyMods: IKeyMods): void { - editor.setSelection(range); - editor.revealRangeInCenter(range, ScrollType.Smooth); - editor.focus(); - } } // #region NLS Helpers diff --git a/src/vs/editor/contrib/quickAccess/quickAccess.ts b/src/vs/editor/contrib/quickAccess/quickAccess.ts deleted file mode 100644 index 3b315010d9c..00000000000 --- a/src/vs/editor/contrib/quickAccess/quickAccess.ts +++ /dev/null @@ -1,104 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; -import { IEditor } from 'vs/editor/common/editorCommon'; -import { IModelDeltaDecoration, OverviewRulerLane } from 'vs/editor/common/model'; -import { IRange } from 'vs/editor/common/core/range'; -import { themeColorFromId } from 'vs/platform/theme/common/themeService'; -import { overviewRulerRangeHighlight } from 'vs/editor/common/view/editorColorRegistry'; -import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { Event } from 'vs/base/common/event'; - -interface IEditorLineDecoration { - rangeHighlightId: string; - overviewRulerDecorationId: string; -} - -/** - * A reusable quick access provider for the editor with support for adding decorations. - */ -export abstract class AbstractEditorQuickAccessProvider implements IQuickAccessProvider { - - /** - * Subclasses to provide an event when the active editor control changes. - */ - abstract readonly onDidActiveTextEditorControlChange: Event; - - /** - * Subclasses to provide the current active editor control. - */ - abstract activeTextEditorControl: IEditor | undefined; - - /** - * Subclasses to implement the quick access picker. - */ - abstract provide(picker: IQuickPick, token: CancellationToken): IDisposable; - - - //#region Decorations Utils - - private rangeHighlightDecorationId: IEditorLineDecoration | undefined = undefined; - - protected addDecorations(editor: IEditor, range: IRange): void { - editor.changeDecorations(changeAccessor => { - - // Reset old decorations if any - const deleteDecorations: string[] = []; - if (this.rangeHighlightDecorationId) { - deleteDecorations.push(this.rangeHighlightDecorationId.overviewRulerDecorationId); - deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId); - - this.rangeHighlightDecorationId = undefined; - } - - // Add new decorations for the range - const newDecorations: IModelDeltaDecoration[] = [ - - // highlight the entire line on the range - { - range, - options: { - className: 'rangeHighlight', - isWholeLine: true - } - }, - - // also add overview ruler highlight - { - range, - options: { - overviewRuler: { - color: themeColorFromId(overviewRulerRangeHighlight), - position: OverviewRulerLane.Full - } - } - } - ]; - - const [rangeHighlightId, overviewRulerDecorationId] = changeAccessor.deltaDecorations(deleteDecorations, newDecorations); - - this.rangeHighlightDecorationId = { rangeHighlightId, overviewRulerDecorationId }; - }); - } - - protected clearDecorations(editor: IEditor): void { - const rangeHighlightDecorationId = this.rangeHighlightDecorationId; - if (rangeHighlightDecorationId) { - editor.changeDecorations(changeAccessor => { - changeAccessor.deltaDecorations([ - rangeHighlightDecorationId.overviewRulerDecorationId, - rangeHighlightDecorationId.rangeHighlightId - ], []); - }); - - this.rangeHighlightDecorationId = undefined; - } - } - - //#endregion -} diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts index 49ce48811ae..58933c776e1 100644 --- a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts @@ -13,13 +13,13 @@ import { Event } from 'vs/base/common/event'; export class StandaloneGotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvider { - readonly onDidActiveTextEditorControlChange = Event.None; + protected readonly onDidActiveTextEditorControlChange = Event.None; constructor(@ICodeEditorService private readonly editorService: ICodeEditorService) { super(); } - get activeTextEditorControl() { + protected get activeTextEditorControl() { return withNullAsUndefined(this.editorService.getFocusedCodeEditor()); } } diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts index 7be920e5129..08287295aba 100644 --- a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts @@ -13,13 +13,13 @@ import { Event } from 'vs/base/common/event'; export class StandaloneGotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { - readonly onDidActiveTextEditorControlChange = Event.None; + protected readonly onDidActiveTextEditorControlChange = Event.None; constructor(@ICodeEditorService private readonly editorService: ICodeEditorService) { super(); } - get activeTextEditorControl() { + protected get activeTextEditorControl() { return withNullAsUndefined(this.editorService.getFocusedCodeEditor()); } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts index 22822d123e6..a5cd2bf23f9 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts @@ -14,13 +14,13 @@ import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/ export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvider { - readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange; + protected readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange; constructor(@IEditorService private readonly editorService: IEditorService) { super(); } - get activeTextEditorControl() { + protected get activeTextEditorControl() { return this.editorService.activeTextEditorControl; } @@ -33,7 +33,7 @@ export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProv // Otherwise let parent handle it else { - super.gotoLine(editor, range, keyMods); + super.gotoLocation(editor, range, keyMods); } } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts index eee2fd66239..ef6957e0178 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts @@ -14,13 +14,13 @@ import { AbstractGotoSymbolQuickAccessProvider } from 'vs/editor/contrib/quickAc export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { - readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange; + protected readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange; constructor(@IEditorService private readonly editorService: IEditorService) { super(); } - get activeTextEditorControl() { + protected get activeTextEditorControl() { return this.editorService.activeTextEditorControl; } @@ -33,7 +33,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess // Otherwise let parent handle it else { - super.gotoSymbol(editor, range, keyMods); + super.gotoLocation(editor, range, keyMods); } } } From be64126a743a32be722092a85194150ce4d44a16 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 16 Mar 2020 08:32:01 +0100 Subject: [PATCH 139/169] quick access - wrap up goto symbol --- src/vs/base/common/filters.ts | 4 +-- .../quickAccess/gotoSymbolQuickAccess.ts | 32 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index 3740a128f1a..e16f6f4c708 100644 --- a/src/vs/base/common/filters.ts +++ b/src/vs/base/common/filters.ts @@ -392,7 +392,7 @@ export function anyScore(pattern: string, lowPattern: string, _patternPos: numbe //#region --- fuzzyScore --- -export function createMatches(score: undefined | FuzzyScore): IMatch[] { +export function createMatches(score: undefined | FuzzyScore, offset = 0): IMatch[] { if (typeof score === 'undefined') { return []; } @@ -407,7 +407,7 @@ export function createMatches(score: undefined | FuzzyScore): IMatch[] { if (last && last.end === pos) { last.end = pos + 1; } else { - res.push({ start: pos, end: pos + 1 }); + res.push({ start: pos + offset, end: pos + 1 + offset }); } } } diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts index 4df4f4feb99..02c6ea27a99 100644 --- a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -62,6 +62,10 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit } })); + // Resolve symbols from document once and reuse this + // request for all filtering and typing then on + const symbolsPromise = this.getDocumentSymbols(model, true, token); + // Set initial picks and update on type let picksCts: CancellationTokenSource | undefined = undefined; const updatePickerItems = async () => { @@ -76,7 +80,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit // Collect symbol picks picker.busy = true; try { - const items = await this.getSymbolPicks(model, picker.value.substr(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim(), picksCts.token); + const items = await this.getSymbolPicks(symbolsPromise, picker.value.substr(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim(), picksCts.token); if (token.isCancellationRequested) { return; } @@ -92,9 +96,17 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit updatePickerItems(); // Reveal and decorate when active item changes + // However, ignore the very first event so that + // opening the picker is not immediately revealing + // and decorating the first entry. + let ignoreFirstActiveEvent = true; disposables.add(picker.onDidChangeActive(() => { const [item] = picker.activeItems; if (item && item.range) { + if (ignoreFirstActiveEvent) { + ignoreFirstActiveEvent = false; + return; + } // Reveal editor.revealRangeInCenter(item.range.selection, ScrollType.Smooth); @@ -107,10 +119,8 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit return disposables; } - private async getSymbolPicks(model: ITextModel, filter: string, token: CancellationToken): Promise> { - - // Resolve symbols from document - const symbols = await this.getDocumentSymbols(model, true, token); + private async getSymbolPicks(symbolsPromise: Promise, filter: string, token: CancellationToken): Promise> { + const symbols = await symbolsPromise; if (token.isCancellationRequested) { return []; } @@ -138,16 +148,6 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit if (includeSymbol) { const labelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${label}`; - // Readjust matches to account for codicons - const labelOffset = labelWithIcon.length - label.length; - const matches = createMatches(score); - if (matches) { - for (const match of matches) { - match.start += labelOffset; - match.end += labelOffset; - } - } - filteredSymbolPicks.push({ index, kind: symbol.kind, @@ -155,7 +155,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit label: labelWithIcon, ariaLabel: localize('symbolsAriaLabel', "{0}, symbols picker", label), description: symbol.containerName, - highlights: deprecated ? undefined : { label: matches }, + highlights: deprecated ? undefined : { label: createMatches(score, labelWithIcon.length - label.length /* Readjust matches to account for codicons */) }, range: { selection: Range.collapseToStart(symbol.selectionRange), decoration: symbol.range From 1ee18550a9cab6b42704af4051c8edfd9f335685 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 16 Mar 2020 08:34:06 +0100 Subject: [PATCH 140/169] quick access - render deprecated as strikethrough --- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 5 +++++ src/vs/base/browser/ui/iconLabel/iconlabel.css | 5 +++++ src/vs/base/parts/quickinput/browser/quickInputList.ts | 1 + src/vs/base/parts/quickinput/common/quickInput.ts | 1 + src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts | 2 +- src/vs/workbench/browser/labels.ts | 5 +++-- 6 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index af06f86e2fa..3266a55df19 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -23,6 +23,7 @@ export interface IIconLabelValueOptions { hideIcon?: boolean; extraClasses?: string[]; italic?: boolean; + strikethrough?: boolean; matches?: IMatch[]; labelEscapeNewLines?: boolean; descriptionMatches?: IMatch[]; @@ -136,6 +137,10 @@ export class IconLabel extends Disposable { if (options.italic) { classes.push('italic'); } + + if (options.strikethrough) { + classes.push('strikethrough'); + } } this.domNode.className = classes.join(' '); diff --git a/src/vs/base/browser/ui/iconLabel/iconlabel.css b/src/vs/base/browser/ui/iconLabel/iconlabel.css index 8ee16195b5c..63a9056621c 100644 --- a/src/vs/base/browser/ui/iconLabel/iconlabel.css +++ b/src/vs/base/browser/ui/iconLabel/iconlabel.css @@ -60,6 +60,11 @@ font-style: italic; } +.monaco-icon-label.strikethrough > .monaco-icon-label-container > .monaco-icon-name-container > .label-name, +.monaco-icon-label.strikethrough > .monaco-icon-description-container > .label-description { + text-decoration: line-through; +} + .monaco-icon-label::after { opacity: 0.75; font-size: 90%; diff --git a/src/vs/base/parts/quickinput/browser/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts index 509ac6f03a3..c7214c0e59d 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputList.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts @@ -154,6 +154,7 @@ class ListElementRenderer implements IListRenderer Date: Mon, 16 Mar 2020 08:37:59 +0100 Subject: [PATCH 141/169] quick access - load file symbols after registry is ready (fix #70607) --- .../quickAccess/gotoLineQuickAccess.ts | 1 + .../quickAccess/gotoSymbolQuickAccess.ts | 43 +++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts index 5e0598a4f2b..2595d0f804d 100644 --- a/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts @@ -20,6 +20,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line."); + picker.items = [{ label }]; picker.ariaLabel = label; diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts index f5d5d7eecdd..57563b665cd 100644 --- a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -31,13 +31,12 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit static PREFIX_BY_CATEGORY = `${AbstractGotoSymbolQuickAccessProvider.PREFIX}${AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX}`; protected canProvideWithTextEditor(editor: IEditor): boolean { - const model = this.getModel(editor); - - return !!model && DocumentSymbolProviderRegistry.has(model); + return !!this.getModel(editor); } protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { - const label = localize('cannotRunGotoSymbol', "Open a text editor with symbol information first to go to a symbol."); + const label = localize('cannotRunGotoSymbolWithoutEditor', "Open a text editor first to go to a symbol."); + picker.items = [{ label, index: 0, kind: SymbolKind.String }]; picker.ariaLabel = label; @@ -50,6 +49,42 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit return Disposable.None; } + // Provide symbols from model if available in registry + if (DocumentSymbolProviderRegistry.has(model)) { + return this.doProvideWithEditorSymbols(editor, model, picker, token); + } + + // Otherwise show an entry for a model without registry + // But give a chance to resolve the symbols at a later + // point if possible + return this.doProvideWithoutEditorSymbols(editor, model, picker, token); + } + + private doProvideWithoutEditorSymbols(editor: IEditor, model: ITextModel, picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Generic pick for not having any symbol information + const label = localize('cannotRunGotoSymbolWithoutSymbolProvider', "Open a text editor with symbol information first to go to a symbol."); + picker.items = [{ label, index: 0, kind: SymbolKind.String }]; + picker.ariaLabel = label; + + // Listen to changes to the registry and see if eventually + // we do get symbols. This can happen if the picker is opened + // very early after the model has loaded but before the + // language registry is ready. + // https://github.com/microsoft/vscode/issues/70607 + const symbolProviderListener = disposables.add(DocumentSymbolProviderRegistry.onDidChange(() => { + if (DocumentSymbolProviderRegistry.has(model)) { + symbolProviderListener.dispose(); + + disposables.add(this.doProvideWithEditorSymbols(editor, model, picker, token)); + } + })); + + return disposables; + } + + private doProvideWithEditorSymbols(editor: IEditor, model: ITextModel, picker: IQuickPick, token: CancellationToken): IDisposable { const disposables = new DisposableStore(); // Goto symbol once picked From a3baf9af130b0c36bef891eb2145958b915ec3f1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 16 Mar 2020 08:44:32 +0100 Subject: [PATCH 142/169] quick access - allow symbol container matches Separate the query params using space to search in container. --- .../quickAccess/gotoSymbolQuickAccess.ts | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts index 57563b665cd..04c6cb44eaf 100644 --- a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -162,35 +162,54 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit // Normalize filter const filterBySymbolKind = filter.indexOf(AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX) === 0; - const filterLow = filter.toLowerCase(); const filterPos = filterBySymbolKind ? 1 : 0; + const [symbolFilter, containerFilter] = filter.split(' ') as [string, string | undefined]; + const symbolFilterLow = symbolFilter.toLowerCase(); + const containerFilterLow = containerFilter?.toLowerCase(); // Convert to symbol picks and apply filtering const filteredSymbolPicks: IGotoSymbolQuickPickItem[] = []; for (let index = 0; index < symbols.length; index++) { const symbol = symbols[index]; - const label = trim(symbol.name); - const deprecated = symbol.tags && symbol.tags.indexOf(SymbolTag.Deprecated) >= 0; + const symbolLabel = trim(symbol.name); + const containerLabel = symbol.containerName; + + let symbolScore: FuzzyScore | undefined = undefined; + let containerScore: FuzzyScore | undefined = undefined; - let score: FuzzyScore | undefined = undefined; let includeSymbol = true; if (filter.length > filterPos) { - score = fuzzyScore(filter, filterLow, filterPos, label, label.toLowerCase(), 0, true); - includeSymbol = !!score; + + // Score by symbol + symbolScore = fuzzyScore(symbolFilter, symbolFilterLow, filterPos, symbolLabel, symbolLabel.toLowerCase(), 0, true); + includeSymbol = !!symbolScore; + + // Score by container if specified + if (includeSymbol && containerFilter && containerFilterLow) { + if (containerLabel) { + containerScore = fuzzyScore(containerFilter, containerFilterLow, filterPos, containerLabel, containerLabel.toLowerCase(), 0, true); + } + + includeSymbol = !!containerScore; + } } if (includeSymbol) { - const labelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${label}`; + const labelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${symbolLabel}`; + const deprecated = symbol.tags && symbol.tags.indexOf(SymbolTag.Deprecated) >= 0; filteredSymbolPicks.push({ index, kind: symbol.kind, - score, + score: symbolScore, label: labelWithIcon, - ariaLabel: localize('symbolsAriaLabel', "{0}, symbols picker", label), - description: symbol.containerName, - highlights: deprecated ? undefined : { label: createMatches(score, labelWithIcon.length - label.length /* Readjust matches to account for codicons */) }, + ariaLabel: localize('symbolsAriaLabel', "{0}, symbols picker", symbolLabel), + description: containerLabel, + highlights: deprecated ? undefined : { + label: createMatches(symbolScore, labelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */), + description: createMatches(containerScore) + }, range: { selection: Range.collapseToStart(symbol.selectionRange), decoration: symbol.range From 0e587865bfbc73c4249bdaf2bac60102012fcce4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 16 Mar 2020 08:46:52 +0100 Subject: [PATCH 143/169] quick access - first cut workspace symbols --- .../quickAccess/gotoSymbolQuickAccess.ts | 6 +- .../platform/quickinput/common/quickAccess.ts | 14 +- .../quickaccess/gotoLineQuickAccess.ts | 13 +- .../browser/quickaccess/gotoSymbolAccess.ts | 17 +- .../contrib/debug/browser/debugQuickAccess.ts | 2 +- .../search/browser/search.contribution.ts | 12 ++ .../search/browser/symbolsQuickAccess.ts | 201 ++++++++++++++++++ 7 files changed, 250 insertions(+), 15 deletions(-) create mode 100644 src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts index 04c6cb44eaf..1f2dc6c6210 100644 --- a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -196,18 +196,18 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit } if (includeSymbol) { - const labelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${symbolLabel}`; + const symbolLabelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${symbolLabel}`; const deprecated = symbol.tags && symbol.tags.indexOf(SymbolTag.Deprecated) >= 0; filteredSymbolPicks.push({ index, kind: symbol.kind, score: symbolScore, - label: labelWithIcon, + label: symbolLabelWithIcon, ariaLabel: localize('symbolsAriaLabel', "{0}, symbols picker", symbolLabel), description: containerLabel, highlights: deprecated ? undefined : { - label: createMatches(symbolScore, labelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */), + label: createMatches(symbolScore, symbolLabelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */), description: createMatches(containerScore) }, range: { diff --git a/src/vs/platform/quickinput/common/quickAccess.ts b/src/vs/platform/quickinput/common/quickAccess.ts index 116086d3e4e..a16ca553935 100644 --- a/src/vs/platform/quickinput/common/quickAccess.ts +++ b/src/vs/platform/quickinput/common/quickAccess.ts @@ -10,7 +10,7 @@ import { first } from 'vs/base/common/arrays'; import { startsWith } from 'vs/base/common/strings'; import { assertIsDefined } from 'vs/base/common/types'; import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IQuickPickSeparator } from 'vs/base/parts/quickinput/common/quickInput'; +import { IQuickPickSeparator, IKeyMods } from 'vs/base/parts/quickinput/common/quickInput'; export interface IQuickAccessController { @@ -167,8 +167,10 @@ export interface IPickerQuickAccessItem extends IQuickPickItem { /** * A method that will be executed when the pick item is accepted from * the picker. The picker will close automatically before running this. + * + * @param keyMods the state of modifier keys when the item was accepted. */ - accept?(): void; + accept?(keyMods: IKeyMods): void; /** * A method that will be executed when a button of the pick item was @@ -177,10 +179,12 @@ export interface IPickerQuickAccessItem extends IQuickPickItem { * @param buttonIndex index of the button of the item that * was clicked. * + * @param the state of modifier keys when the button was triggered. + * * @returns a value that indicates what should happen after the trigger * which can be a `Promise` for long running operations. */ - trigger?(buttonIndex: number): TriggerAction | Promise; + trigger?(buttonIndex: number, keyMods: IKeyMods): TriggerAction | Promise; } export abstract class PickerQuickAccessProvider implements IQuickAccessProvider { @@ -232,7 +236,7 @@ export abstract class PickerQuickAccessProvider= 0) { - const result = item.trigger(buttonIndex); + const result = item.trigger(buttonIndex, picker.keyMods); const action = (typeof result === 'number') ? result : await result; if (token.isCancellationRequested) { diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts index a5cd2bf23f9..b33ec1ba9e2 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts @@ -11,12 +11,17 @@ import { IRange } from 'vs/editor/common/core/range'; import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLineQuickAccess'; import { Registry } from 'vs/platform/registry/common/platform'; import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvider { protected readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange; - constructor(@IEditorService private readonly editorService: IEditorService) { + constructor( + @IEditorService private readonly editorService: IEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { super(); } @@ -25,10 +30,14 @@ export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProv } protected gotoLine(editor: IEditor, range: IRange, keyMods: IKeyMods): void { + const enablePreviewFromQuickAccess = this.configurationService.getValue().workbench.editor.enablePreviewFromQuickOpen; // Check for sideBySide use if (keyMods.ctrlCmd && this.editorService.activeEditor) { - this.editorService.openEditor(this.editorService.activeEditor, { selection: range, pinned: keyMods.alt }, SIDE_GROUP); + this.editorService.openEditor(this.editorService.activeEditor, { + selection: range, + pinned: keyMods.alt || !enablePreviewFromQuickAccess + }, SIDE_GROUP); } // Otherwise let parent handle it diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts index ef6957e0178..a7f8a7237b5 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts @@ -11,12 +11,17 @@ import { IRange } from 'vs/editor/common/core/range'; import { Registry } from 'vs/platform/registry/common/platform'; import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; import { AbstractGotoSymbolQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoSymbolQuickAccess'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { protected readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange; - constructor(@IEditorService private readonly editorService: IEditorService) { + constructor( + @IEditorService private readonly editorService: IEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { super(); } @@ -25,10 +30,14 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess } protected gotoSymbol(editor: IEditor, range: IRange, keyMods: IKeyMods): void { + const enablePreviewFromQuickAccess = this.configurationService.getValue().workbench.editor.enablePreviewFromQuickOpen; // Check for sideBySide use if (keyMods.ctrlCmd && this.editorService.activeEditor) { - this.editorService.openEditor(this.editorService.activeEditor, { selection: range, pinned: keyMods.alt }, SIDE_GROUP); + this.editorService.openEditor(this.editorService.activeEditor, { + selection: range, + pinned: keyMods.alt || !enablePreviewFromQuickAccess + }, SIDE_GROUP); } // Otherwise let parent handle it @@ -43,7 +52,7 @@ Registry.as(Extensions.Quickaccess).registerQuickAccessPro prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, placeholder: localize('gotoSymbolQuickAccessPlaceholder', "Type the name of a symbol to go to."), helpEntries: [ - { description: localize('gotoSymbolQuickAccess', "Go to Symol in Editor"), prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, needsEditor: true }, - { description: localize('gotoSymbolByCategoryQuickAccess', "Go to Symol in Editor by Category"), prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX_BY_CATEGORY, needsEditor: true } + { description: localize('gotoSymbolQuickAccess', "Go to Symbol in Editor"), prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, needsEditor: true }, + { description: localize('gotoSymbolByCategoryQuickAccess', "Go to Symbol in Editor by Category"), prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX_BY_CATEGORY, needsEditor: true } ] }); diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts index 7c39c372d52..6b4f8127c3b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts @@ -52,7 +52,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { config.launch.openConfigFile(false, false); diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 3d9deddf982..e918cce82d5 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -55,6 +55,8 @@ import { assertType, assertIsDefined } from 'vs/base/common/types'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; +import { SymbolsQuickAccessProvider } from 'vs/workbench/contrib/search/browser/symbolsQuickAccess'; registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true); registerSingleton(ISearchHistoryService, SearchHistoryService, true); @@ -651,6 +653,16 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen ) ); +// Register Quick Access Handler + +Registry.as(QuickAccessExtensions.Quickaccess).registerQuickAccessProvider({ + ctor: SymbolsQuickAccessProvider, + prefix: SymbolsQuickAccessProvider.PREFIX, + placeholder: nls.localize('symbolsQuickAccessPlaceholder', "Type the name of a symbol to open."), + contextKey: 'inWorkspaceSymbolsPicker', + helpEntries: [{ description: nls.localize('symbolsQuickAccess', "Go to Symbol in Workspace"), needsEditor: false }] +}); + // Configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ diff --git a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts new file mode 100644 index 00000000000..4f41ad1dffe --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts @@ -0,0 +1,201 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction } from 'vs/platform/quickinput/common/quickAccess'; +import { fuzzyScore, createMatches, FuzzyScore } from 'vs/base/common/filters'; +import { stripWildcards } from 'vs/base/common/strings'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ThrottledDelayer } from 'vs/base/common/async'; +import { getWorkspaceSymbols, IWorkspaceSymbol, IWorkspaceSymbolProvider } from 'vs/workbench/contrib/search/common/search'; +import { SymbolKinds, SymbolTag } from 'vs/editor/common/modes'; +import { basename } from 'vs/base/common/resources'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { Schemas } from 'vs/base/common/network'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { Range } from 'vs/editor/common/core/range'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; +import { IKeyMods } from 'vs/platform/quickinput/common/quickInput'; + +interface ISymbolsQuickPickItem extends IPickerQuickAccessItem { + score: FuzzyScore; + symbol: IWorkspaceSymbol; +} + +export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = '#'; + + private static readonly TYPING_SEARCH_DELAY = 200; // this delay accommodates for the user typing a word and then stops typing to start searching + + private delayer = new ThrottledDelayer(SymbolsQuickAccessProvider.TYPING_SEARCH_DELAY); + + constructor( + @ILabelService private readonly labelService: ILabelService, + @IOpenerService private readonly openerService: IOpenerService, + @IEditorService private readonly editorService: IEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + super(SymbolsQuickAccessProvider.PREFIX); + } + + private get configuration() { + const editorConfig = this.configurationService.getValue().workbench.editor; + + return { + openEditorPinned: !editorConfig.enablePreviewFromQuickOpen, + openSideBySideDirection: editorConfig.openSideBySideDirection + }; + } + + protected getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise> { + return this.delayer.trigger(async () => { + if (token.isCancellationRequested) { + return []; + } + + return this.doGetSymbolPicks(filter, token); + }); + } + + private async doGetSymbolPicks(filter: string, token: CancellationToken): Promise> { + const workspaceSymbols = await getWorkspaceSymbols(filter, token); + if (token.isCancellationRequested) { + return []; + } + + const symbolPicks: Array = []; + + // Normalize filter + const [symbolFilter, containerFilter] = stripWildcards(filter).split(' ') as [string, string | undefined]; + const symbolFilterLow = symbolFilter.toLowerCase(); + const containerFilterLow = containerFilter?.toLowerCase(); + + // Convert to symbol picks and apply filtering + const openSideBySideDirection = this.configuration.openSideBySideDirection; + for (const [provider, symbols] of workspaceSymbols) { + for (const symbol of symbols) { + const symbolLabel = symbol.name; + const symbolLabelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${symbolLabel}`; + + let containerLabel: string | undefined = undefined; + if (symbol.location.uri) { + if (symbol.containerName) { + containerLabel = `${symbol.containerName} — ${basename(symbol.location.uri)}`; + } else { + containerLabel = this.labelService.getUriLabel(symbol.location.uri, { relative: true }); + } + } + + // Score by symbol + const symbolScore = fuzzyScore(symbolFilter, symbolFilterLow, 0, symbolLabel, symbolLabel.toLowerCase(), 0, true); + let containerScore: FuzzyScore | undefined = undefined; + if (!symbolScore) { + continue; + } + + // Score by container if specified + if (containerFilter && containerFilterLow) { + if (containerLabel) { + containerScore = fuzzyScore(containerFilter, containerFilterLow, 0, containerLabel, containerLabel.toLowerCase(), 0, true); + } + + if (!containerScore) { + continue; + } + } + + const deprecated = symbol.tags ? symbol.tags.indexOf(SymbolTag.Deprecated) >= 0 : false; + + symbolPicks.push({ + symbol, + score: symbolScore, + label: symbolLabelWithIcon, + ariaLabel: localize('symbolAriaLabel', "{0}, symbols picker", symbolLabel), + highlights: deprecated ? undefined : { + label: createMatches(symbolScore, symbolLabelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */), + description: createMatches(containerScore) + }, + description: containerLabel, + strikethrough: deprecated, + buttons: [ + { + iconClass: openSideBySideDirection === 'right' ? 'codicon-split-horizontal' : 'codicon-split-vertical', + tooltip: openSideBySideDirection === 'right' ? localize('openToSide', "Open to the Side") : localize('openToBottom', "Open to the Bottom") + } + ], + accept: async keyMods => this.openSymbol(provider, symbol, token, keyMods), + trigger: async (buttonIndex, keyMods) => { + this.openSymbol(provider, symbol, token, keyMods, true); + + return TriggerAction.CLOSE_PICKER; + } + }); + } + } + + // Sort picks + symbolPicks.sort((symbolA, symbolB) => this.compareSymbols(symbolA, symbolB)); + + return symbolPicks; + } + + private async openSymbol(provider: IWorkspaceSymbolProvider, symbol: IWorkspaceSymbol, token: CancellationToken, keyMods: IKeyMods, forceOpenSideBySide = false): Promise { + + // Resolve actual symbol to open for providers that can resolve + let symbolToOpen = symbol; + if (typeof provider.resolveWorkspaceSymbol === 'function' && !symbol.location.range) { + symbolToOpen = await provider.resolveWorkspaceSymbol(symbol, token) || symbol; + + if (token.isCancellationRequested) { + return; + } + } + + // Open HTTP(s) links with opener service + if (symbolToOpen.location.uri.scheme === Schemas.http || symbolToOpen.location.uri.scheme === Schemas.https) { + this.openerService.open(symbolToOpen.location.uri, { fromUserGesture: true }); + } + + // Otherwise open as editor + else { + this.editorService.openEditor({ + resource: symbolToOpen.location.uri, + options: { + pinned: keyMods.alt || forceOpenSideBySide || this.configuration.openEditorPinned, + selection: symbolToOpen.location.range ? Range.collapseToStart(symbolToOpen.location.range) : undefined + } + }, keyMods.ctrlCmd || forceOpenSideBySide ? SIDE_GROUP : ACTIVE_GROUP); + } + } + + private compareSymbols(symbolA: ISymbolsQuickPickItem, symbolB: ISymbolsQuickPickItem): number { + + // By score + if (symbolA.score && symbolB.score) { + if (symbolA.score[0] > symbolB.score[0]) { + return -1; + } else if (symbolA.score[0] < symbolB.score[0]) { + return 1; + } + } + + // By name + const symbolAName = symbolA.symbol.name.toLowerCase(); + const symbolBName = symbolB.symbol.name.toLowerCase(); + const res = symbolAName.localeCompare(symbolBName); + if (res !== 0) { + return res; + } + + // By kind + const symbolAKind = SymbolKinds.toCssClassName(symbolA.symbol.kind); + const symbolBKind = SymbolKinds.toCssClassName(symbolB.symbol.kind); + return symbolAKind.localeCompare(symbolBKind); + } +} From 06742106a1526d8875bb584ef61f7888f250b517 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 16 Mar 2020 08:51:00 +0100 Subject: [PATCH 144/169] quick access - "open to side" button for symbols --- .../editorNavigationQuickAccess.ts | 2 +- .../quickAccess/gotoSymbolQuickAccess.ts | 32 +++++++++++++++++-- .../quickaccess/gotoLineQuickAccess.ts | 15 ++++++--- .../browser/quickaccess/gotoSymbolAccess.ts | 20 +++++++++--- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts b/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts index 4ed5f0cd426..ccebded8af4 100644 --- a/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts @@ -98,7 +98,7 @@ export abstract class AbstractEditorNavigationQuickAccessProvider, token: CancellationToken): IDisposable; - protected gotoLocation(editor: IEditor, range: IRange, keyMods: IKeyMods): void { + protected gotoLocation(editor: IEditor, range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean): void { editor.setSelection(range); editor.revealRangeInCenter(range, ScrollType.Smooth); editor.focus(); diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts index 1f2dc6c6210..8ebb953f331 100644 --- a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -24,14 +24,18 @@ interface IGotoSymbolQuickPickItem extends IQuickPickItem { range?: { decoration: IRange, selection: IRange }, } +export interface IGotoSymbolQuickAccessProviderOptions { + openSideBySideDirection: () => undefined | 'right' | 'down' +} + export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider { static PREFIX = '@'; static SCOPE_PREFIX = ':'; static PREFIX_BY_CATEGORY = `${AbstractGotoSymbolQuickAccessProvider.PREFIX}${AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX}`; - protected canProvideWithTextEditor(editor: IEditor): boolean { - return !!this.getModel(editor); + constructor(private options?: IGotoSymbolQuickAccessProviderOptions) { + super(); } protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { @@ -97,6 +101,15 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit } })); + // Goto symbol side by side if enabled + disposables.add(picker.onDidTriggerItemButton(({ item }) => { + if (item && item.range) { + this.gotoLocation(editor, item.range.selection, picker.keyMods, true); + + picker.hide(); + } + })); + // Resolve symbols from document once and reuse this // request for all filtering and typing then on const symbolsPromise = this.getDocumentSymbols(model, true, token); @@ -214,7 +227,20 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit selection: Range.collapseToStart(symbol.selectionRange), decoration: symbol.range }, - strikethrough: deprecated + strikethrough: deprecated, + buttons: (() => { + const openSideBySideDirection = this.options?.openSideBySideDirection(); + if (!openSideBySideDirection) { + return undefined; + } + + return [ + { + iconClass: openSideBySideDirection === 'right' ? 'codicon-split-horizontal' : 'codicon-split-vertical', + tooltip: openSideBySideDirection === 'right' ? localize('openToSide', "Open to the Side") : localize('openToBottom', "Open to the Bottom") + } + ]; + })() }); } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts index b33ec1ba9e2..0e8c8f29aa5 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts @@ -25,18 +25,25 @@ export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProv super(); } + private get configuration() { + const editorConfig = this.configurationService.getValue().workbench.editor; + + return { + openEditorPinned: !editorConfig.enablePreviewFromQuickOpen, + }; + } + protected get activeTextEditorControl() { return this.editorService.activeTextEditorControl; } - protected gotoLine(editor: IEditor, range: IRange, keyMods: IKeyMods): void { - const enablePreviewFromQuickAccess = this.configurationService.getValue().workbench.editor.enablePreviewFromQuickOpen; + protected gotoLocation(editor: IEditor, range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean): void { // Check for sideBySide use - if (keyMods.ctrlCmd && this.editorService.activeEditor) { + if ((keyMods.ctrlCmd || forceSideBySide) && this.editorService.activeEditor) { this.editorService.openEditor(this.editorService.activeEditor, { selection: range, - pinned: keyMods.alt || !enablePreviewFromQuickAccess + pinned: keyMods.alt || this.configuration.openEditorPinned }, SIDE_GROUP); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts index a7f8a7237b5..e3a213f9084 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts @@ -22,21 +22,31 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService ) { - super(); + super({ + openSideBySideDirection: () => this.configuration.openSideBySideDirection + }); + } + + private get configuration() { + const editorConfig = this.configurationService.getValue().workbench.editor; + + return { + openEditorPinned: !editorConfig.enablePreviewFromQuickOpen, + openSideBySideDirection: editorConfig.openSideBySideDirection + }; } protected get activeTextEditorControl() { return this.editorService.activeTextEditorControl; } - protected gotoSymbol(editor: IEditor, range: IRange, keyMods: IKeyMods): void { - const enablePreviewFromQuickAccess = this.configurationService.getValue().workbench.editor.enablePreviewFromQuickOpen; + protected gotoLocation(editor: IEditor, range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean): void { // Check for sideBySide use - if (keyMods.ctrlCmd && this.editorService.activeEditor) { + if ((keyMods.ctrlCmd || forceSideBySide) && this.editorService.activeEditor) { this.editorService.openEditor(this.editorService.activeEditor, { selection: range, - pinned: keyMods.alt || !enablePreviewFromQuickAccess + pinned: keyMods.alt || this.configuration.openEditorPinned }, SIDE_GROUP); } From b792db2714543456d8a8b6a781377a478adb367d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 16 Mar 2020 08:51:46 +0100 Subject: [PATCH 145/169] quick access - always show relative path in symbol picker (fix #76661) --- .../workbench/contrib/search/browser/symbolsQuickAccess.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts index 4f41ad1dffe..9cd63f3f5a5 100644 --- a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts @@ -12,7 +12,6 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { ThrottledDelayer } from 'vs/base/common/async'; import { getWorkspaceSymbols, IWorkspaceSymbol, IWorkspaceSymbolProvider } from 'vs/workbench/contrib/search/common/search'; import { SymbolKinds, SymbolTag } from 'vs/editor/common/modes'; -import { basename } from 'vs/base/common/resources'; import { ILabelService } from 'vs/platform/label/common/label'; import { Schemas } from 'vs/base/common/network'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -85,10 +84,11 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider Date: Mon, 16 Mar 2020 08:58:03 +0100 Subject: [PATCH 146/169] quick access - enable support to open in background Via arrow right or mouse middle button. --- src/vs/base/browser/ui/inputbox/inputBox.ts | 4 ++ .../parts/quickinput/browser/quickInput.ts | 37 ++++++++++++++++--- .../parts/quickinput/browser/quickInputBox.ts | 12 ++++-- .../quickinput/browser/quickInputList.ts | 2 +- .../parts/quickinput/common/quickInput.ts | 24 +++++++++++- .../platform/quickinput/common/quickAccess.ts | 23 +++++++++--- .../browser/parts/editor/editorQuickAccess.ts | 10 ++++- .../search/browser/symbolsQuickAccess.ts | 25 ++++++++----- .../terminal/browser/terminaQuickAccess.ts | 12 ++++-- 9 files changed, 117 insertions(+), 32 deletions(-) diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index c50b6c92b23..b188a00df7e 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -295,6 +295,10 @@ export class InputBox extends Widget { } } + public isSelectionAtEnd(): boolean { + return this.input.selectionEnd === this.input.value.length && this.input.selectionStart === this.input.selectionEnd; + } + public enable(): void { this.input.removeAttribute('disabled'); } diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 7defd5cf056..372fe2882c1 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/quickInput'; -import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods } from 'vs/base/parts/quickinput/common/quickInput'; +import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent } from 'vs/base/parts/quickinput/common/quickInput'; import * as dom from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; import { QuickInputList } from './quickInputList'; @@ -379,11 +379,12 @@ class QuickPick extends QuickInput implements IQuickPi private _ariaLabel = QuickPick.DEFAULT_ARIA_LABEL; private _placeholder: string | undefined; private readonly onDidChangeValueEmitter = this._register(new Emitter()); - private readonly onDidAcceptEmitter = this._register(new Emitter()); + private readonly onDidAcceptEmitter = this._register(new Emitter()); private readonly onDidCustomEmitter = this._register(new Emitter()); private _items: Array = []; private itemsUpdated = false; private _canSelectMany = false; + private _canAcceptInBackground = false; private _matchOnDescription = false; private _matchOnDetail = false; private _matchOnLabel = true; @@ -462,6 +463,14 @@ class QuickPick extends QuickInput implements IQuickPi this.update(); } + get canAcceptInBackground() { + return this._canAcceptInBackground; + } + + set canAcceptInBackground(canAcceptInBackground: boolean) { + this._canAcceptInBackground = canAcceptInBackground; + } + get matchOnDescription() { return this._matchOnDescription; } @@ -663,6 +672,22 @@ class QuickPick extends QuickInput implements IQuickPi this.ui.list.domFocus(); } event.preventDefault(); + break; + case KeyCode.RightArrow: + if (!this._canAcceptInBackground) { + return; // needs to be enabled + } + + if (!this.ui.inputBox.isSelectionAtEnd()) { + return; // ensure input box selection at end + } + + if (this.activeItems[0]) { + this._selectedItems = [this.activeItems[0]]; + this.onDidChangeSelectionEmitter.fire(this.selectedItems); + this.onDidAcceptEmitter.fire({ inBackground: true }); + } + break; } })); @@ -671,7 +696,7 @@ class QuickPick extends QuickInput implements IQuickPi this._selectedItems = [this.activeItems[0]]; this.onDidChangeSelectionEmitter.fire(this.selectedItems); } - this.onDidAcceptEmitter.fire(undefined); + this.onDidAcceptEmitter.fire({ inBackground: false }); })); this.visibleDisposables.add(this.ui.onDidCustom(() => { this.onDidCustomEmitter.fire(undefined); @@ -686,7 +711,7 @@ class QuickPick extends QuickInput implements IQuickPi this._activeItems = focusedItems as T[]; this.onDidChangeActiveEmitter.fire(focusedItems as T[]); })); - this.visibleDisposables.add(this.ui.list.onDidChangeSelection(selectedItems => { + this.visibleDisposables.add(this.ui.list.onDidChangeSelection(({ items: selectedItems, event }) => { if (this.canSelectMany) { if (selectedItems.length) { this.ui.list.setSelectedElements([]); @@ -699,7 +724,7 @@ class QuickPick extends QuickInput implements IQuickPi this._selectedItems = selectedItems as T[]; this.onDidChangeSelectionEmitter.fire(selectedItems as T[]); if (selectedItems.length) { - this.onDidAcceptEmitter.fire(undefined); + this.onDidAcceptEmitter.fire({ inBackground: (event as MouseEvent).button === 1 /* mouse middle click */ }); } })); this.visibleDisposables.add(this.ui.list.onChangedCheckedElements(checkedItems => { @@ -762,7 +787,7 @@ class QuickPick extends QuickInput implements IQuickPi if (wasTriggerKeyPressed && this.activeItems[0]) { this._selectedItems = [this.activeItems[0]]; this.onDidChangeSelectionEmitter.fire(this.selectedItems); - this.onDidAcceptEmitter.fire(undefined); + this.onDidAcceptEmitter.fire({ inBackground: false }); } }); } diff --git a/src/vs/base/parts/quickinput/browser/quickInputBox.ts b/src/vs/base/parts/quickinput/browser/quickInputBox.ts index 6ae2b873034..2e796764a6e 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputBox.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputBox.ts @@ -54,7 +54,11 @@ export class QuickInputBox extends Disposable { this.inputBox.select(range); } - setPlaceholder(placeholder: string) { + isSelectionAtEnd(): boolean { + return this.inputBox.isSelectionAtEnd(); + } + + setPlaceholder(placeholder: string): void { this.inputBox.setPlaceHolder(placeholder); } @@ -90,11 +94,11 @@ export class QuickInputBox extends Disposable { return this.inputBox.hasFocus(); } - setAttribute(name: string, value: string) { + setAttribute(name: string, value: string): void { this.inputBox.inputElement.setAttribute(name, value); } - removeAttribute(name: string) { + removeAttribute(name: string): void { this.inputBox.inputElement.removeAttribute(name); } @@ -118,7 +122,7 @@ export class QuickInputBox extends Disposable { this.inputBox.layout(); } - style(styles: IInputBoxStyles) { + style(styles: IInputBoxStyles): void { this.inputBox.style(styles); } } diff --git a/src/vs/base/parts/quickinput/browser/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts index c7214c0e59d..d339c421185 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputList.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts @@ -318,7 +318,7 @@ export class QuickInputList { @memoize get onDidChangeSelection() { - return Event.map(this.list.onDidChangeSelection, e => e.elements.map(e => e.item)); + return Event.map(this.list.onDidChangeSelection, e => ({ items: e.elements.map(e => e.item), event: e.browserEvent })); } getAllVisibleChecked() { diff --git a/src/vs/base/parts/quickinput/common/quickInput.ts b/src/vs/base/parts/quickinput/common/quickInput.ts index 2d5dfd1dd31..c6ad0c93b76 100644 --- a/src/vs/base/parts/quickinput/common/quickInput.ts +++ b/src/vs/base/parts/quickinput/common/quickInput.ts @@ -24,6 +24,11 @@ export interface IQuickPickItem { ariaLabel?: string; description?: string; detail?: string; + /** + * Allows to show a keybinding next to the item to indicate + * how the item can be triggered outside of the picker using + * keyboard shortcut. + */ keybinding?: ResolvedKeybinding; iconClasses?: string[]; italic?: boolean; @@ -171,6 +176,15 @@ export interface IQuickInput extends IDisposable { hide(): void; } +export interface IQuickPickAcceptEvent { + + /** + * Signals if the picker item is to be accepted + * in the background while keeping the picker open. + */ + inBackground: boolean; +} + export interface IQuickPick extends IQuickInput { value: string; @@ -187,7 +201,14 @@ export interface IQuickPick extends IQuickInput { readonly onDidChangeValue: Event; - readonly onDidAccept: Event; + readonly onDidAccept: Event; + + /** + * If enabled, will fire the `onDidAccept` event when + * pressing the arrow-right key with the idea of accepting + * the selected item without closing the picker. + */ + canAcceptInBackground: boolean; ok: boolean | 'default'; @@ -269,7 +290,6 @@ export interface IQuickInputButton { /** iconPath or iconClass required */ iconClass?: string; tooltip?: string; - alwaysShow?: boolean; } export interface IQuickPickItemButtonEvent { diff --git a/src/vs/platform/quickinput/common/quickAccess.ts b/src/vs/platform/quickinput/common/quickAccess.ts index a16ca553935..7df8dcc09c4 100644 --- a/src/vs/platform/quickinput/common/quickAccess.ts +++ b/src/vs/platform/quickinput/common/quickAccess.ts @@ -10,7 +10,7 @@ import { first } from 'vs/base/common/arrays'; import { startsWith } from 'vs/base/common/strings'; import { assertIsDefined } from 'vs/base/common/types'; import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IQuickPickSeparator, IKeyMods } from 'vs/base/parts/quickinput/common/quickInput'; +import { IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent } from 'vs/base/parts/quickinput/common/quickInput'; export interface IQuickAccessController { @@ -169,8 +169,9 @@ export interface IPickerQuickAccessItem extends IQuickPickItem { * the picker. The picker will close automatically before running this. * * @param keyMods the state of modifier keys when the item was accepted. + * @param event the underlying event that caused the accept to trigger. */ - accept?(keyMods: IKeyMods): void; + accept?(keyMods: IKeyMods, event: IQuickPickAcceptEvent): void; /** * A method that will be executed when a button of the pick item was @@ -194,6 +195,9 @@ export abstract class PickerQuickAccessProvider, token: CancellationToken): IDisposable { const disposables = new DisposableStore(); + // Allow subclasses to configure picker + this.configure(picker); + // Disable filtering & sorting, we control the results picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; @@ -232,11 +236,13 @@ export abstract class PickerQuickAccessProvider { + disposables.add(picker.onDidAccept(event => { const [item] = picker.selectedItems; if (typeof item?.accept === 'function') { - picker.hide(); - item.accept(picker.keyMods); + if (!event.inBackground) { + picker.hide(); // hide picker unless we accept in background + } + item.accept(picker.keyMods, event); } })); @@ -269,6 +275,13 @@ export abstract class PickerQuickAccessProvider): void { } + /** * Returns an array of picks and separators as needed. If the picks are resolved * long running, the provided cancellation token should be used to cancel the diff --git a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts index 5dd721feb52..6d46ec2b4cf 100644 --- a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts +++ b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource, IQuickPick } from 'vs/platform/quickinput/common/quickInput'; import { PickerQuickAccessProvider, IPickerQuickAccessItem, TriggerAction } from 'vs/platform/quickinput/common/quickAccess'; import { IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorsOrder, IEditorIdentifier, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; @@ -28,6 +28,12 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro super(prefix); } + protected configure(picker: IQuickPick): void { + + // Allow to open editors in background without closing picker + picker.canAcceptInBackground = true; + } + protected getPicks(filter: string): Array { const query = prepareQuery(filter); const scorerCache = Object.create(null); @@ -108,7 +114,7 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro return TriggerAction.REFRESH_PICKER; }, - accept: () => this.editorGroupService.getGroup(groupId)?.openEditor(editor), + accept: (keyMods, event) => this.editorGroupService.getGroup(groupId)?.openEditor(editor, { preserveFocus: event.inBackground }), }; }); } diff --git a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts index 9cd63f3f5a5..2fa952ed199 100644 --- a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts @@ -19,7 +19,7 @@ import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/ import { Range } from 'vs/editor/common/core/range'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; -import { IKeyMods } from 'vs/platform/quickinput/common/quickInput'; +import { IKeyMods, IQuickPick } from 'vs/platform/quickinput/common/quickInput'; interface ISymbolsQuickPickItem extends IPickerQuickAccessItem { score: FuzzyScore; @@ -43,6 +43,12 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider): void { + + // Allow to open symbols in background without closing picker + picker.canAcceptInBackground = true; + } + private get configuration() { const editorConfig = this.configurationService.getValue().workbench.editor; @@ -129,9 +135,9 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider this.openSymbol(provider, symbol, token, keyMods), - trigger: async (buttonIndex, keyMods) => { - this.openSymbol(provider, symbol, token, keyMods, true); + accept: async (keyMods, event) => this.openSymbol(provider, symbol, token, keyMods, { preserveFocus: event.inBackground }), + trigger: (buttonIndex, keyMods) => { + this.openSymbol(provider, symbol, token, keyMods, { forceOpenSideBySide: true }); return TriggerAction.CLOSE_PICKER; } @@ -145,7 +151,7 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider { + private async openSymbol(provider: IWorkspaceSymbolProvider, symbol: IWorkspaceSymbol, token: CancellationToken, keyMods: IKeyMods, options: { forceOpenSideBySide?: boolean, preserveFocus?: boolean }): Promise { // Resolve actual symbol to open for providers that can resolve let symbolToOpen = symbol; @@ -159,18 +165,19 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider): void { + + // Allow to open terminals in background without closing picker + picker.canAcceptInBackground = true; + } + protected getPicks(filter: string): Array { const terminalPicks: Array = []; @@ -60,9 +66,9 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider { + accept: (keyMod, event) => { this.terminalService.setActiveInstance(terminal); - this.terminalService.showPanel(true); + this.terminalService.showPanel(!event.inBackground); } }); } From 89906c073a8fc8e60f344c5623f5c73adcf039f1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 16 Mar 2020 09:01:38 +0100 Subject: [PATCH 147/169] quick access - apply configured excludes to symbols --- .../platform/quickinput/common/quickAccess.ts | 8 ++-- src/vs/workbench/common/resources.ts | 4 +- .../files/browser/files.contribution.ts | 10 ++--- .../search/browser/search.contribution.ts | 6 +-- .../search/browser/symbolsQuickAccess.ts | 45 ++++++++++++++----- .../services/history/browser/history.ts | 28 ++++-------- .../services/search/common/search.ts | 16 ++++++- 7 files changed, 70 insertions(+), 47 deletions(-) diff --git a/src/vs/platform/quickinput/common/quickAccess.ts b/src/vs/platform/quickinput/common/quickAccess.ts index 7df8dcc09c4..b144530b605 100644 --- a/src/vs/platform/quickinput/common/quickAccess.ts +++ b/src/vs/platform/quickinput/common/quickAccess.ts @@ -9,7 +9,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { first } from 'vs/base/common/arrays'; import { startsWith } from 'vs/base/common/strings'; import { assertIsDefined } from 'vs/base/common/types'; -import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent } from 'vs/base/parts/quickinput/common/quickInput'; export interface IQuickAccessController { @@ -188,9 +188,11 @@ export interface IPickerQuickAccessItem extends IQuickPickItem { trigger?(buttonIndex: number, keyMods: IKeyMods): TriggerAction | Promise; } -export abstract class PickerQuickAccessProvider implements IQuickAccessProvider { +export abstract class PickerQuickAccessProvider extends Disposable implements IQuickAccessProvider { - constructor(private prefix: string) { } + constructor(private prefix: string) { + super(); + } provide(picker: IQuickPick, token: CancellationToken): IDisposable { const disposables = new DisposableStore(); diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts index 597dd5d96f9..2a7844da48f 100644 --- a/src/vs/workbench/common/resources.ts +++ b/src/vs/workbench/common/resources.ts @@ -113,8 +113,8 @@ export class ResourceGlobMatcher extends Disposable { private readonly _onExpressionChange = this._register(new Emitter()); readonly onExpressionChange = this._onExpressionChange.event; - private readonly mapRootToParsedExpression: Map = new Map(); - private readonly mapRootToExpressionConfig: Map = new Map(); + private readonly mapRootToParsedExpression = new Map(); + private readonly mapRootToExpressionConfig = new Map(); constructor( private globFn: (root?: URI) => IExpression, diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index f92fbd96463..7f560e77ee5 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -13,7 +13,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IEditorInputFactory, EditorInput, IFileEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor'; -import { AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; +import { AutoSaveConfiguration, HotExitConfiguration, FILES_EXCLUDE_CONFIG, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files'; import { VIEWLET_ID, SortOrder, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { TextFileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/textFileEditorTracker'; import { TextFileSaveErrorHandler } from 'vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler'; @@ -202,9 +202,9 @@ configurationRegistry.registerConfiguration({ 'title': nls.localize('filesConfigurationTitle', "Files"), 'type': 'object', 'properties': { - 'files.exclude': { + [FILES_EXCLUDE_CONFIG]: { 'type': 'object', - 'markdownDescription': nls.localize('exclude', "Configure glob patterns for excluding files and folders. For example, the files explorer decides which files and folders to show or hide based on this setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), + 'markdownDescription': nls.localize('exclude', "Configure glob patterns for excluding files and folders. For example, the files explorer decides which files and folders to show or hide based on this setting. Refer to the `#search.exclude#` setting to define search specific excludes. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), 'default': { '**/.git': true, '**/.svn': true, '**/.hg': true, '**/CVS': true, '**/.DS_Store': true }, 'scope': ConfigurationScope.RESOURCE, 'additionalProperties': { @@ -227,7 +227,7 @@ configurationRegistry.registerConfiguration({ ] } }, - 'files.associations': { + [FILES_ASSOCIATIONS_CONFIG]: { 'type': 'object', 'markdownDescription': nls.localize('associations', "Configure file associations to languages (e.g. `\"*.extension\": \"html\"`). These have precedence over the default associations of the languages installed."), }, @@ -306,7 +306,7 @@ configurationRegistry.registerConfiguration({ 'files.watcherExclude': { 'type': 'object', 'default': platform.isWindows /* https://github.com/Microsoft/vscode/issues/23954 */ ? { '**/.git/objects/**': true, '**/.git/subtree-cache/**': true, '**/node_modules/*/**': true, '**/.hg/store/**': true } : { '**/.git/objects/**': true, '**/.git/subtree-cache/**': true, '**/node_modules/**': true, '**/.hg/store/**': true }, - 'description': nls.localize('watcherExclude', "Configure glob patterns of file paths to exclude from file watching. Patterns must match on absolute paths (i.e. prefix with ** or the full path to match properly). Changing this setting requires a restart. When you experience Code consuming lots of cpu time on startup, you can exclude large folders to reduce the initial load."), + 'description': nls.localize('watcherExclude', "Configure glob patterns of file paths to exclude from file watching. Patterns must match on absolute paths (i.e. prefix with ** or the full path to match properly). Changing this setting requires a restart. When you experience Code consuming lots of CPU time on startup, you can exclude large folders to reduce the initial load."), 'scope': ConfigurationScope.RESOURCE }, 'files.hotExit': hotExitConfiguration, diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index e918cce82d5..b57f2b35e6b 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -47,7 +47,7 @@ import { getWorkspaceSymbols } from 'vs/workbench/contrib/search/common/search'; import { ISearchHistoryService, SearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { FileMatchOrMatch, ISearchWorkbenchService, RenderableMatch, SearchWorkbenchService, FileMatch, Match, FolderMatch } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { VIEWLET_ID, VIEW_ID, SearchSortOrder } from 'vs/workbench/services/search/common/search'; +import { VIEWLET_ID, VIEW_ID, SEARCH_EXCLUDE_CONFIG, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; @@ -671,9 +671,9 @@ configurationRegistry.registerConfiguration({ title: nls.localize('searchConfigurationTitle', "Search"), type: 'object', properties: { - 'search.exclude': { + [SEARCH_EXCLUDE_CONFIG]: { type: 'object', - markdownDescription: nls.localize('exclude', "Configure glob patterns for excluding files and folders in searches. Inherits all glob patterns from the `#files.exclude#` setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), + markdownDescription: nls.localize('exclude', "Configure glob patterns for excluding files and folders in fulltext searches and quick open. Inherits all glob patterns from the `#files.exclude#` setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), default: { '**/node_modules': true, '**/bower_components': true, '**/*.code-search': true }, additionalProperties: { anyOf: [ diff --git a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts index 2fa952ed199..edea9286375 100644 --- a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts @@ -20,6 +20,9 @@ import { Range } from 'vs/editor/common/core/range'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; import { IKeyMods, IQuickPick } from 'vs/platform/quickinput/common/quickInput'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { createResourceExcludeMatcher } from 'vs/workbench/services/search/common/search'; +import { ResourceMap } from 'vs/base/common/map'; interface ISymbolsQuickPickItem extends IPickerQuickAccessItem { score: FuzzyScore; @@ -34,11 +37,14 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider(SymbolsQuickAccessProvider.TYPING_SEARCH_DELAY); + private readonly resourceExcludeMatcher = this._register(createResourceExcludeMatcher(this.instantiationService, this.configurationService)); + constructor( @ILabelService private readonly labelService: ILabelService, @IOpenerService private readonly openerService: IOpenerService, @IEditorService private readonly editorService: IEditorService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(SymbolsQuickAccessProvider.PREFIX); } @@ -83,14 +89,21 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider(); for (const [provider, symbols] of workspaceSymbols) { for (const symbol of symbols) { - const symbolLabel = symbol.name; - const symbolLabelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${symbolLabel}`; + // Score by symbol label + const symbolLabel = symbol.name; + const symbolScore = fuzzyScore(symbolFilter, symbolFilterLow, 0, symbolLabel, symbolLabel.toLowerCase(), 0, true); + if (!symbolScore) { + continue; + } + + const symbolUri = symbol.location.uri; let containerLabel: string | undefined = undefined; - if (symbol.location.uri) { - const containerPath = this.labelService.getUriLabel(symbol.location.uri, { relative: true }); + if (symbolUri) { + const containerPath = this.labelService.getUriLabel(symbolUri, { relative: true }); if (symbol.containerName) { containerLabel = `${symbol.containerName} • ${containerPath}`; } else { @@ -98,14 +111,8 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider= 0 : false; symbolPicks.push({ diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 40e61072372..7e6f234fbf6 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -9,21 +9,19 @@ import { ITextEditorOptions, IResourceEditorInput, TextEditorSelectionRevealType import { IEditorInput, IEditorPane, Extensions as EditorExtensions, EditorInput, IEditorCloseEvent, IEditorInputFactoryRegistry, toResource, IEditorIdentifier, GroupIdentifier, EditorsOrder } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { FileChangesEvent, IFileService, FileChangeType, FILES_EXCLUDE_CONFIG } from 'vs/platform/files/common/files'; +import { FileChangesEvent, IFileService, FileChangeType } from 'vs/platform/files/common/files'; import { Selection } from 'vs/editor/common/core/selection'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; import { Event } from 'vs/base/common/event'; -import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { getExcludes, ISearchConfiguration } from 'vs/workbench/services/search/common/search'; -import { IExpression } from 'vs/base/common/glob'; +import { createResourceExcludeMatcher } from 'vs/workbench/services/search/common/search'; import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -98,8 +96,8 @@ export class HistoryService extends Disposable implements IHistoryService { private readonly activeEditorListeners = this._register(new DisposableStore()); private lastActiveEditor?: IEditorIdentifier; - private readonly editorHistoryListeners: Map = new Map(); - private readonly editorStackListeners: Map = new Map(); + private readonly editorHistoryListeners = new Map(); + private readonly editorStackListeners = new Map(); constructor( @IEditorService private readonly editorService: EditorServiceImpl, @@ -124,7 +122,7 @@ export class HistoryService extends Disposable implements IHistoryService { this._register(this.editorService.onDidCloseEditor(event => this.onEditorClosed(event))); this._register(this.storageService.onWillSaveState(() => this.saveState())); this._register(this.fileService.onDidFilesChange(event => this.onDidFilesChange(event))); - this._register(this.resourceFilter.onExpressionChange(() => this.removeExcludedFromHistory())); + this._register(this.resourceExcludeMatcher.onExpressionChange(() => this.removeExcludedFromHistory())); this._register(this.editorService.onDidMostRecentlyActiveEditorsChange(() => this.handleEditorEventInRecentEditorsStack())); // if the service is created late enough that an editor is already opened @@ -718,17 +716,7 @@ export class HistoryService extends Disposable implements IHistoryService { private history: Array | undefined = undefined; - private readonly resourceFilter = this._register(this.instantiationService.createInstance( - ResourceGlobMatcher, - (root?: URI) => this.getExcludes(root), - (event: IConfigurationChangeEvent) => event.affectsConfiguration(FILES_EXCLUDE_CONFIG) || event.affectsConfiguration('search.exclude') - )); - - private getExcludes(root?: URI): IExpression { - const scope = root ? { resource: root } : undefined; - - return getExcludes(scope ? this.configurationService.getValue(scope) : this.configurationService.getValue())!; - } + private readonly resourceExcludeMatcher = this._register(createResourceExcludeMatcher(this.instantiationService, this.configurationService)); private handleEditorEventInHistory(editor?: IEditorPane): void { @@ -764,7 +752,7 @@ export class HistoryService extends Disposable implements IHistoryService { const resourceEditorInput = input as IResourceEditorInput; - return !this.resourceFilter.matches(resourceEditorInput.resource); + return !this.resourceExcludeMatcher.matches(resourceEditorInput.resource); } private removeExcludedFromHistory(): void { diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index df5cdda37dd..582fb63b3b5 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -11,16 +11,20 @@ import * as objects from 'vs/base/common/objects'; import * as extpath from 'vs/base/common/extpath'; import { fuzzyContains, getNLines } from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { IFilesConfiguration } from 'vs/platform/files/common/files'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IFilesConfiguration, FILES_EXCLUDE_CONFIG } from 'vs/platform/files/common/files'; +import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { Event } from 'vs/base/common/event'; import { relative } from 'vs/base/common/path'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; export const VIEWLET_ID = 'workbench.view.search'; export const PANEL_ID = 'workbench.panel.search'; export const VIEW_ID = 'workbench.view.search'; +export const SEARCH_EXCLUDE_CONFIG = 'search.exclude'; + export const ISearchService = createDecorator('searchService'); /** @@ -372,6 +376,14 @@ export function getExcludes(configuration: ISearchConfiguration, includeSearchEx return allExcludes; } +export function createResourceExcludeMatcher(instantiationService: IInstantiationService, configurationService: IConfigurationService): ResourceGlobMatcher { + return instantiationService.createInstance( + ResourceGlobMatcher, + root => getExcludes(root ? configurationService.getValue({ resource: root }) : configurationService.getValue()) || Object.create(null), + event => event.affectsConfiguration(FILES_EXCLUDE_CONFIG) || event.affectsConfiguration(SEARCH_EXCLUDE_CONFIG) + ); +} + export function pathIncludedInQuery(queryProps: ICommonQueryProps, fsPath: string): boolean { if (queryProps.excludePattern && glob.match(queryProps.excludePattern, fsPath)) { return false; From b3787fb148a7088631e56842b8cd92883d40de2b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 16 Mar 2020 09:42:03 +0100 Subject: [PATCH 148/169] quick access - some cleanup --- .../quickAccess/commandsQuickAccess.ts | 2 +- .../standaloneCommandsQuickAccess.ts | 2 +- .../quickinput/browser/commandsQuickAccess.ts | 13 +- .../quickinput/browser/pickerQuickAccess.ts | 167 ++++++++++++++++++ .../platform/quickinput/common/quickAccess.ts | 166 +---------------- .../browser/parts/editor/editorQuickAccess.ts | 2 +- .../contrib/debug/browser/debugQuickAccess.ts | 2 +- .../browser/extensionsQuickAccess.ts | 2 +- .../browser/commandsQuickAccess.ts | 2 +- .../quickaccess/browser/viewQuickAccess.ts | 2 +- .../search/browser/symbolsQuickAccess.ts | 4 +- .../contrib/tasks/browser/tasksQuickAccess.ts | 2 +- .../terminal/browser/terminaQuickAccess.ts | 2 +- 13 files changed, 185 insertions(+), 183 deletions(-) create mode 100644 src/vs/platform/quickinput/browser/pickerQuickAccess.ts diff --git a/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts b/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts index 2f84c46a581..3354dd93e55 100644 --- a/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts @@ -27,7 +27,7 @@ export abstract class AbstractEditorCommandsQuickAccessProvider extends Abstract /** * Subclasses to provide the current active editor control. */ - abstract activeTextEditorControl: IEditor | undefined; + protected abstract activeTextEditorControl: IEditor | undefined; protected getCodeEditorCommandPicks(): ICommandQuickPick[] { const activeTextEditorControl = this.activeTextEditorControl; diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts index 6cc42fa3eb1..a954f12426e 100644 --- a/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts @@ -19,7 +19,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati export class StandaloneCommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { - get activeTextEditorControl(): IEditor | undefined { return withNullAsUndefined(this.codeEditorService.getFocusedCodeEditor()); } + protected get activeTextEditorControl(): IEditor | undefined { return withNullAsUndefined(this.codeEditorService.getFocusedCodeEditor()); } constructor( @IInstantiationService instantiationService: IInstantiationService, diff --git a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts index 3ef94e57b9f..d3e1a048da2 100644 --- a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { PickerQuickAccessProvider, IPickerQuickAccessItem } from 'vs/platform/quickinput/common/quickAccess'; +import { PickerQuickAccessProvider, IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { distinct } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle'; @@ -40,9 +40,7 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc private static WORD_FILTER = or(matchesPrefix, matchesWords, matchesContiguousSubString); - private readonly disposables = new DisposableStore(); - - private readonly commandsHistory = this.disposables.add(this.instantiationService.createInstance(CommandsHistory)); + private readonly commandsHistory = this._register(this.instantiationService.createInstance(CommandsHistory)); constructor( private options: ICommandsQuickAccessOptions, @@ -173,11 +171,10 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc return commandPicks; } + /** + * Subclasses to provide the actual command entries. + */ protected abstract getCommandPicks(disposables: DisposableStore, token: CancellationToken): Promise>; - - dispose(): void { - this.disposables.dispose(); - } } interface ISerializedCommandHistory { diff --git a/src/vs/platform/quickinput/browser/pickerQuickAccess.ts b/src/vs/platform/quickinput/browser/pickerQuickAccess.ts new file mode 100644 index 00000000000..fca14db0d98 --- /dev/null +++ b/src/vs/platform/quickinput/browser/pickerQuickAccess.ts @@ -0,0 +1,167 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent } from 'vs/base/parts/quickinput/common/quickInput'; +import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; + +export enum TriggerAction { + + /** + * Do nothing after the button was clicked. + */ + NO_ACTION, + + /** + * Close the picker. + */ + CLOSE_PICKER, + + /** + * Update the results of the picker. + */ + REFRESH_PICKER +} + +export interface IPickerQuickAccessItem extends IQuickPickItem { + + /** + * A method that will be executed when the pick item is accepted from + * the picker. The picker will close automatically before running this. + * + * @param keyMods the state of modifier keys when the item was accepted. + * @param event the underlying event that caused the accept to trigger. + */ + accept?(keyMods: IKeyMods, event: IQuickPickAcceptEvent): void; + + /** + * A method that will be executed when a button of the pick item was + * clicked on. + * + * @param buttonIndex index of the button of the item that + * was clicked. + * + * @param the state of modifier keys when the button was triggered. + * + * @returns a value that indicates what should happen after the trigger + * which can be a `Promise` for long running operations. + */ + trigger?(buttonIndex: number, keyMods: IKeyMods): TriggerAction | Promise; +} + +export abstract class PickerQuickAccessProvider extends Disposable implements IQuickAccessProvider { + + constructor(private prefix: string) { + super(); + } + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Allow subclasses to configure picker + this.configure(picker); + + // Disable filtering & sorting, we control the results + picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; + + // Set initial picks and update on type + let picksCts: CancellationTokenSource | undefined = undefined; + const updatePickerItems = async () => { + + // Cancel any previous ask for picks and busy + picksCts?.dispose(true); + picker.busy = false; + + // Create new cancellation source for this run + picksCts = new CancellationTokenSource(token); + + // Collect picks and support both long running and short + const res = this.getPicks(picker.value.substr(this.prefix.length).trim(), disposables.add(new DisposableStore()), picksCts.token); + if (Array.isArray(res)) { + picker.items = res; + } else { + picker.busy = true; + try { + const items = await res; + if (token.isCancellationRequested) { + return; + } + + picker.items = items; + } finally { + if (!token.isCancellationRequested) { + picker.busy = false; + } + } + } + }; + disposables.add(picker.onDidChangeValue(() => updatePickerItems())); + updatePickerItems(); + + // Accept the pick on accept and hide picker + disposables.add(picker.onDidAccept(event => { + const [item] = picker.selectedItems; + if (typeof item?.accept === 'function') { + if (!event.inBackground) { + picker.hide(); // hide picker unless we accept in background + } + item.accept(picker.keyMods, event); + } + })); + + // Trigger the pick with button index if button triggered + disposables.add(picker.onDidTriggerItemButton(async ({ button, item }) => { + if (typeof item.trigger === 'function') { + const buttonIndex = item.buttons?.indexOf(button) ?? -1; + if (buttonIndex >= 0) { + const result = item.trigger(buttonIndex, picker.keyMods); + const action = (typeof result === 'number') ? result : await result; + + if (token.isCancellationRequested) { + return; + } + + switch (action) { + case TriggerAction.NO_ACTION: + break; + case TriggerAction.CLOSE_PICKER: + picker.hide(); + break; + case TriggerAction.REFRESH_PICKER: + updatePickerItems(); + break; + } + } + } + })); + + return disposables; + } + + /** + * Subclasses can override this method to configure the picker before showing it. + * + * @param picker the picker instance used for the quick access before it opens. + */ + protected configure(picker: IQuickPick): void { } + + /** + * Returns an array of picks and separators as needed. If the picks are resolved + * long running, the provided cancellation token should be used to cancel the + * operation when the token signals this. + * + * The implementor is responsible for filtering and sorting the picks given the + * provided `filter`. + * + * @param filter a filter to apply to the picks. + * @param disposables can be used to register disposables that should be cleaned + * up when the picker closes. + * @param token for long running tasks, implementors need to check on cancellation + * through this token. + */ + protected abstract getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Array | Promise>; +} diff --git a/src/vs/platform/quickinput/common/quickAccess.ts b/src/vs/platform/quickinput/common/quickAccess.ts index b144530b605..be8fba3f311 100644 --- a/src/vs/platform/quickinput/common/quickAccess.ts +++ b/src/vs/platform/quickinput/common/quickAccess.ts @@ -4,13 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { Registry } from 'vs/platform/registry/common/platform'; import { first } from 'vs/base/common/arrays'; import { startsWith } from 'vs/base/common/strings'; import { assertIsDefined } from 'vs/base/common/types'; -import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; -import { IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent } from 'vs/base/parts/quickinput/common/quickInput'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; export interface IQuickAccessController { @@ -141,164 +140,3 @@ class QuickAccessRegistry implements IQuickAccessRegistry { } Registry.add(Extensions.Quickaccess, new QuickAccessRegistry()); - -//#region Helper class for simple picker based providers - -export enum TriggerAction { - - /** - * Do nothing after the button was clicked. - */ - NO_ACTION, - - /** - * Close the picker. - */ - CLOSE_PICKER, - - /** - * Update the results of the picker. - */ - REFRESH_PICKER -} - -export interface IPickerQuickAccessItem extends IQuickPickItem { - - /** - * A method that will be executed when the pick item is accepted from - * the picker. The picker will close automatically before running this. - * - * @param keyMods the state of modifier keys when the item was accepted. - * @param event the underlying event that caused the accept to trigger. - */ - accept?(keyMods: IKeyMods, event: IQuickPickAcceptEvent): void; - - /** - * A method that will be executed when a button of the pick item was - * clicked on. - * - * @param buttonIndex index of the button of the item that - * was clicked. - * - * @param the state of modifier keys when the button was triggered. - * - * @returns a value that indicates what should happen after the trigger - * which can be a `Promise` for long running operations. - */ - trigger?(buttonIndex: number, keyMods: IKeyMods): TriggerAction | Promise; -} - -export abstract class PickerQuickAccessProvider extends Disposable implements IQuickAccessProvider { - - constructor(private prefix: string) { - super(); - } - - provide(picker: IQuickPick, token: CancellationToken): IDisposable { - const disposables = new DisposableStore(); - - // Allow subclasses to configure picker - this.configure(picker); - - // Disable filtering & sorting, we control the results - picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; - - // Set initial picks and update on type - let picksCts: CancellationTokenSource | undefined = undefined; - const updatePickerItems = async () => { - - // Cancel any previous ask for picks and busy - picksCts?.dispose(true); - picker.busy = false; - - // Create new cancellation source for this run - picksCts = new CancellationTokenSource(token); - - // Collect picks and support both long running and short - const res = this.getPicks(picker.value.substr(this.prefix.length).trim(), disposables.add(new DisposableStore()), picksCts.token); - if (Array.isArray(res)) { - picker.items = res; - } else { - picker.busy = true; - try { - const items = await res; - if (token.isCancellationRequested) { - return; - } - - picker.items = items; - } finally { - if (!token.isCancellationRequested) { - picker.busy = false; - } - } - } - }; - disposables.add(picker.onDidChangeValue(() => updatePickerItems())); - updatePickerItems(); - - // Accept the pick on accept and hide picker - disposables.add(picker.onDidAccept(event => { - const [item] = picker.selectedItems; - if (typeof item?.accept === 'function') { - if (!event.inBackground) { - picker.hide(); // hide picker unless we accept in background - } - item.accept(picker.keyMods, event); - } - })); - - // Trigger the pick with button index if button triggered - disposables.add(picker.onDidTriggerItemButton(async ({ button, item }) => { - if (typeof item.trigger === 'function') { - const buttonIndex = item.buttons?.indexOf(button) ?? -1; - if (buttonIndex >= 0) { - const result = item.trigger(buttonIndex, picker.keyMods); - const action = (typeof result === 'number') ? result : await result; - - if (token.isCancellationRequested) { - return; - } - - switch (action) { - case TriggerAction.NO_ACTION: - break; - case TriggerAction.CLOSE_PICKER: - picker.hide(); - break; - case TriggerAction.REFRESH_PICKER: - updatePickerItems(); - break; - } - } - } - })); - - return disposables; - } - - /** - * Subclasses can override this method to configure the picker before showing it. - * - * @param picker the picker instance used for the quick access before it opens. - */ - protected configure(picker: IQuickPick): void { } - - /** - * Returns an array of picks and separators as needed. If the picks are resolved - * long running, the provided cancellation token should be used to cancel the - * operation when the token signals this. - * - * The implementor is responsible for filtering and sorting the picks given the - * provided `filter`. - * - * @param filter a filter to apply to the picks. - * @param disposables can be used to register disposables that should be cleaned - * up when the picker closes. - * @param token for long running tasks, implementors need to check on cancellation - * through this token. - */ - protected abstract getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Array | Promise>; -} - -//#endregion diff --git a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts index 6d46ec2b4cf..0194b0a3b3b 100644 --- a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts +++ b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource, IQuickPick } from 'vs/platform/quickinput/common/quickInput'; -import { PickerQuickAccessProvider, IPickerQuickAccessItem, TriggerAction } from 'vs/platform/quickinput/common/quickAccess'; +import { PickerQuickAccessProvider, IPickerQuickAccessItem, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorsOrder, IEditorIdentifier, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts index 6b4f8127c3b..c2de911e0f6 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { PickerQuickAccessProvider, IPickerQuickAccessItem, TriggerAction } from 'vs/platform/quickinput/common/quickAccess'; +import { PickerQuickAccessProvider, IPickerQuickAccessItem, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { localize } from 'vs/nls'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts b/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts index 1086a4fb0e8..d45a0ebb62a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { CancellationToken } from 'vs/base/common/cancellation'; import { localize } from 'vs/nls'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index e6e2209921e..47bf09f848b 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -32,7 +32,7 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce this.extensionService.whenInstalledExtensionsRegistered() ]); - get activeTextEditorControl(): IEditor | undefined { return this.editorService.activeTextEditorControl; } + protected get activeTextEditorControl(): IEditor | undefined { return this.editorService.activeTextEditorControl; } constructor( @IEditorService private readonly editorService: IEditorService, diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts index dfe5eaeb3d2..502a6c9b9be 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewDescriptorService, IViewsService, ViewContainer, IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry } from 'vs/workbench/common/views'; import { IOutputService } from 'vs/workbench/contrib/output/common/output'; diff --git a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts index edea9286375..19c7c4d083d 100644 --- a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction } from 'vs/platform/quickinput/common/quickAccess'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { fuzzyScore, createMatches, FuzzyScore } from 'vs/base/common/filters'; import { stripWildcards } from 'vs/base/common/strings'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -195,7 +195,7 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider Date: Mon, 16 Mar 2020 10:01:26 +0100 Subject: [PATCH 149/169] - rename gallery machined it to service machine id - extract getting service machine id --- src/vs/code/electron-main/window.ts | 10 +++- .../environment/common/environment.ts | 2 +- .../environment/node/environmentService.ts | 2 +- .../common/extensionGalleryService.ts | 47 ++++--------------- .../test/node/extensionGalleryService.test.ts | 7 ++- .../common/serviceMachineId.ts | 42 +++++++++++++++++ 6 files changed, 66 insertions(+), 44 deletions(-) create mode 100644 src/vs/platform/serviceMachineId/common/serviceMachineId.ts diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index de0a7dff13a..5ee3e79c644 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -28,12 +28,13 @@ import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/commo import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { endsWith } from 'vs/base/common/strings'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { IFileService } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; +import { IStorageMainService } from 'vs/platform/storage/node/storageMainService'; +import { IFileService } from 'vs/platform/files/common/files'; const RUN_TEXTMATE_IN_WORKER = false; @@ -97,6 +98,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { @ILogService private readonly logService: ILogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IFileService private readonly fileService: IFileService, + @IStorageMainService private readonly storageService: IStorageMainService, @IConfigurationService private readonly configurationService: IConfigurationService, @IThemeMainService private readonly themeMainService: IThemeMainService, @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, @@ -226,7 +228,11 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.createTouchBar(); // Request handling - this.marketplaceHeadersPromise = resolveMarketplaceHeaders(product.version, this.environmentService, this.fileService); + const that = this; + this.marketplaceHeadersPromise = resolveMarketplaceHeaders(product.version, this.environmentService, this.fileService, { + get(key) { return that.storageService.get(key); }, + store(key, value) { that.storageService.store(key, value); } + }); // Eventing this.registerListeners(); diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 9a0cc09ce5d..5df93a4f3f2 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -167,5 +167,5 @@ export interface IEnvironmentService extends IUserHomeProvider { driverHandle?: string; driverVerbose: boolean; - galleryMachineIdResource?: URI; + serviceMachineIdResource?: URI; } diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 58b8f6ffcd1..4738addf8c7 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -255,7 +255,7 @@ export class EnvironmentService implements IEnvironmentService { get nodeCachedDataDir(): string | undefined { return process.env['VSCODE_NODE_CACHED_DATA_DIR'] || undefined; } @memoize - get galleryMachineIdResource(): URI { return resources.joinPath(URI.file(this.userDataPath), 'machineid'); } + get serviceMachineIdResource(): URI { return resources.joinPath(URI.file(this.userDataPath), 'machineid'); } get disableUpdates(): boolean { return !!this._args['disable-updates']; } get disableCrashReporter(): boolean { return !!this._args['disable-crash-reporter']; } diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index f3fb5ecca84..a0b43ec818c 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -13,7 +13,7 @@ import { IRequestService, asJson, asText } from 'vs/platform/request/common/requ import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request'; import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { generateUuid, isUUID } from 'vs/base/common/uuid'; +import { generateUuid } from 'vs/base/common/uuid'; import { values } from 'vs/base/common/map'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ILogService } from 'vs/platform/log/common/log'; @@ -21,10 +21,9 @@ import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; -import { VSBuffer } from 'vs/base/common/buffer'; import { IProductService } from 'vs/platform/product/common/productService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { optional } from 'vs/platform/instantiation/common/instantiation'; +import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId'; interface IRawGalleryExtensionFile { assetType: string; @@ -341,7 +340,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { @ITelemetryService private readonly telemetryService: ITelemetryService, @IFileService private readonly fileService: IFileService, @IProductService private readonly productService: IProductService, - @optional(IStorageService) private readonly storageService: IStorageService, + @IStorageService private readonly storageService: IStorageService, ) { const config = productService.extensionsGallery; this.extensionsGalleryUrl = config && config.serviceUrl; @@ -760,43 +759,15 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } } -export async function resolveMarketplaceHeaders(version: string, environmentService: IEnvironmentService, fileService: IFileService, storageService?: IStorageService): Promise<{ [key: string]: string; }> { +export async function resolveMarketplaceHeaders(version: string, environmentService: IEnvironmentService, fileService: IFileService, storageService: { + get: (key: string, scope: StorageScope) => string | undefined, + store: (key: string, value: string, scope: StorageScope) => void +}): Promise<{ [key: string]: string; }> { const headers: IHeaders = { 'X-Market-Client-Id': `VSCode ${version}`, 'User-Agent': `VSCode ${version}` }; - let uuid: string | null = null; - if (environmentService.galleryMachineIdResource) { - try { - const contents = await fileService.readFile(environmentService.galleryMachineIdResource); - const value = contents.value.toString(); - uuid = isUUID(value) ? value : null; - } catch (e) { - uuid = null; - } - - if (!uuid) { - uuid = generateUuid(); - try { - await fileService.writeFile(environmentService.galleryMachineIdResource, VSBuffer.fromString(uuid)); - } catch (error) { - //noop - } - } - } - - if (storageService) { - uuid = storageService.get('marketplace.userid', StorageScope.GLOBAL) || null; - if (!uuid) { - uuid = generateUuid(); - storageService.store('marketplace.userid', uuid, StorageScope.GLOBAL); - } - } - - if (uuid) { - headers['X-Market-User-Id'] = uuid; - } - + const uuid: string = await getServiceMachineId(environmentService, fileService, storageService); + headers['X-Market-User-Id'] = uuid; return headers; - } diff --git a/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts b/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts index 07bb0a2e00f..377a92abbe9 100644 --- a/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts +++ b/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts @@ -19,6 +19,8 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import product from 'vs/platform/product/common/product'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IStorageService } from 'vs/platform/storage/common/storage'; suite('Extension Gallery Service', () => { const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'extensiongalleryservice'); @@ -52,11 +54,12 @@ suite('Extension Gallery Service', () => { test('marketplace machine id', () => { const args = ['--user-data-dir', marketplaceHome]; const environmentService = new EnvironmentService(parseArgs(args, OPTIONS), process.execPath); + const storageService: IStorageService = new TestStorageService(); - return resolveMarketplaceHeaders(product.version, environmentService, fileService).then(headers => { + return resolveMarketplaceHeaders(product.version, environmentService, fileService, storageService).then(headers => { assert.ok(isUUID(headers['X-Market-User-Id'])); - return resolveMarketplaceHeaders(product.version, environmentService, fileService).then(headers2 => { + return resolveMarketplaceHeaders(product.version, environmentService, fileService, storageService).then(headers2 => { assert.equal(headers['X-Market-User-Id'], headers2['X-Market-User-Id']); }); }); diff --git a/src/vs/platform/serviceMachineId/common/serviceMachineId.ts b/src/vs/platform/serviceMachineId/common/serviceMachineId.ts new file mode 100644 index 00000000000..04957244a2a --- /dev/null +++ b/src/vs/platform/serviceMachineId/common/serviceMachineId.ts @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IFileService } from 'vs/platform/files/common/files'; +import { StorageScope } from 'vs/platform/storage/common/storage'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { isUUID, generateUuid } from 'vs/base/common/uuid'; +import { VSBuffer } from 'vs/base/common/buffer'; + +export async function getServiceMachineId(environmentService: IEnvironmentService, fileService: IFileService, storageService: { + get: (key: string, scope: StorageScope, fallbackValue?: string | undefined) => string | undefined, + store: (key: string, value: string, scope: StorageScope) => void +}): Promise { + let uuid: string | null = storageService.get('storage.serviceMachineId', StorageScope.GLOBAL) || null; + if (uuid) { + return uuid; + } + if (environmentService.serviceMachineIdResource) { + try { + const contents = await fileService.readFile(environmentService.serviceMachineIdResource); + const value = contents.value.toString(); + uuid = isUUID(value) ? value : null; + } catch (e) { + uuid = null; + } + + if (!uuid) { + uuid = generateUuid(); + try { + await fileService.writeFile(environmentService.serviceMachineIdResource, VSBuffer.fromString(uuid)); + } catch (error) { + //noop + } + } + } else { + uuid = generateUuid(); + } + storageService.store('storage.serviceMachineId', uuid, StorageScope.GLOBAL); + return uuid; +} From d16702e61c604eb292cf209446ad5d465d0fcd29 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 16 Mar 2020 10:06:17 +0100 Subject: [PATCH 150/169] quick access - fix monaco editor treeshaking --- .../contrib/quickAccess/editorNavigationQuickAccess.ts | 10 +++++----- .../editor/contrib/quickAccess/gotoLineQuickAccess.ts | 2 +- .../contrib/quickAccess/gotoSymbolQuickAccess.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts b/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts index ccebded8af4..5926c74dce5 100644 --- a/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts @@ -27,11 +27,11 @@ interface IEditorLineDecoration { * for adding decorations for navigating in the currently active file * (for example "Go to line", "Go to symbol"). */ -export abstract class AbstractEditorNavigationQuickAccessProvider implements IQuickAccessProvider { +export abstract class AbstractEditorNavigationQuickAccessProvider implements IQuickAccessProvider { //#region Provider methods - provide(picker: IQuickPick, token: CancellationToken): IDisposable { + provide(picker: IQuickPick, token: CancellationToken): IDisposable { const disposables = new DisposableStore(); // Disable filtering & sorting, we control the results @@ -50,7 +50,7 @@ export abstract class AbstractEditorNavigationQuickAccessProvider, token: CancellationToken): IDisposable { + private doProvide(picker: IQuickPick, token: CancellationToken): IDisposable { const disposables = new DisposableStore(); // With text control @@ -91,12 +91,12 @@ export abstract class AbstractEditorNavigationQuickAccessProvider, token: CancellationToken): IDisposable; + protected abstract provideWithTextEditor(editor: IEditor, picker: IQuickPick, token: CancellationToken): IDisposable; /** * Subclasses to implement to provide picks for the picker when no editor is active. */ - protected abstract provideWithoutTextEditor(picker: IQuickPick, token: CancellationToken): IDisposable; + protected abstract provideWithoutTextEditor(picker: IQuickPick, token: CancellationToken): IDisposable; protected gotoLocation(editor: IEditor, range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean): void { editor.setSelection(range); diff --git a/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts index 2595d0f804d..e24cf811a69 100644 --- a/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts @@ -14,7 +14,7 @@ import { IPosition } from 'vs/editor/common/core/position'; interface IGotoLineQuickPickItem extends IQuickPickItem, Partial { } -export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider { +export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider { static PREFIX = ':'; diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts index 8ebb953f331..c70d4e06383 100644 --- a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -28,7 +28,7 @@ export interface IGotoSymbolQuickAccessProviderOptions { openSideBySideDirection: () => undefined | 'right' | 'down' } -export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider { +export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider { static PREFIX = '@'; static SCOPE_PREFIX = ':'; From 3797e47d60c88c033da4d4a22872345dd825cccc Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 16 Mar 2020 10:07:30 +0100 Subject: [PATCH 151/169] gotoSymbolAccess => gotoSymbolQuickAccess --- .../contrib/codeEditor/browser/codeEditor.contribution.ts | 2 +- .../{gotoSymbolAccess.ts => gotoSymbolQuickAccess.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/vs/workbench/contrib/codeEditor/browser/quickaccess/{gotoSymbolAccess.ts => gotoSymbolQuickAccess.ts} (100%) diff --git a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts index e26553e54b7..30a3c94fdf1 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts @@ -10,7 +10,7 @@ import './inspectKeybindings'; import './largeFileOptimizations'; import './inspectEditorTokens/inspectEditorTokens'; import './quickaccess/gotoLineQuickAccess'; -import './quickaccess/gotoSymbolAccess'; +import './quickaccess/gotoSymbolQuickAccess'; import './saveParticipants'; import './toggleColumnSelection'; import './toggleMinimap'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts similarity index 100% rename from src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts rename to src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts From 7f18a4bfeeb6374ea2e6c9f1c11ef8e29097fb2f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 16 Mar 2020 10:31:09 +0100 Subject: [PATCH 152/169] quick input - fix integration tests --- src/vs/base/parts/quickinput/browser/quickInput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 372fe2882c1..3479a6bbead 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -724,7 +724,7 @@ class QuickPick extends QuickInput implements IQuickPi this._selectedItems = selectedItems as T[]; this.onDidChangeSelectionEmitter.fire(selectedItems as T[]); if (selectedItems.length) { - this.onDidAcceptEmitter.fire({ inBackground: (event as MouseEvent).button === 1 /* mouse middle click */ }); + this.onDidAcceptEmitter.fire({ inBackground: event instanceof MouseEvent && event.button === 1 /* mouse middle click */ }); } })); this.visibleDisposables.add(this.ui.list.onChangedCheckedElements(checkedItems => { From ab3f677438060d118f04ebe0e5e50690e3c12a84 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 16 Mar 2020 11:27:54 +0100 Subject: [PATCH 153/169] quick access - drop default handler --- .../quickAccess/standaloneHelpQuickAccess.ts | 4 +- .../quickinput/browser/quickAccess.ts | 19 ++-- .../platform/quickinput/common/quickAccess.ts | 50 +++++--- .../browser/quickAccess.contribution.ts | 7 -- .../test/browser/quickAccess.test.ts | 107 ++++++++++-------- 5 files changed, 106 insertions(+), 81 deletions(-) diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.ts index 94deb2ba64e..9eecd4a9a4c 100644 --- a/src/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.ts +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.ts @@ -8,8 +8,8 @@ import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/ import { QuickHelpNLS } from 'vs/editor/common/standaloneStrings'; import { HelpQuickAccessProvider } from 'vs/platform/quickinput/browser/helpQuickAccess'; -Registry.as(Extensions.Quickaccess).defaultProvider = { +Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ ctor: HelpQuickAccessProvider, prefix: '', helpEntries: [{ description: QuickHelpNLS.helpQuickAccessActionLabel, needsEditor: true }] -}; +}); diff --git a/src/vs/platform/quickinput/browser/quickAccess.ts b/src/vs/platform/quickinput/browser/quickAccess.ts index 377db89fbd3..ca96d93fdbe 100644 --- a/src/vs/platform/quickinput/browser/quickAccess.ts +++ b/src/vs/platform/quickinput/browser/quickAccess.ts @@ -37,11 +37,11 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon // Create a picker for the provider to use with the initial value // and adjust the filtering to exclude the prefix from filtering const picker = disposables.add(this.quickInputService.createQuickPick()); - picker.placeholder = descriptor.placeholder; + picker.placeholder = descriptor?.placeholder; picker.value = value; picker.valueSelection = [value.length, value.length]; - picker.contextKey = descriptor.contextKey; - picker.filterValue = (value: string) => value.substring(descriptor.prefix.length); + picker.contextKey = descriptor?.contextKey; + picker.filterValue = (value: string) => value.substring(descriptor ? descriptor.prefix.length : 0); // Remember as last active picker and clean up once picker get's disposed this.lastActivePicker = picker; @@ -72,8 +72,10 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon } })); - // Ask provider to fill the picker as needed - disposables.add(provider.provide(picker, cts.token)); + // Ask provider to fill the picker as needed if we have one + if (provider) { + disposables.add(provider.provide(picker, cts.token)); + } // Finally, show the picker. This is important because a provider // may not call this and then our disposables would leak that rely @@ -81,8 +83,11 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon picker.show(); } - private getOrInstantiateProvider(value: string): [IQuickAccessProvider, IQuickAccessProviderDescriptor] { - const providerDescriptor = this.registry.getQuickAccessProvider(value) || this.registry.defaultProvider; + private getOrInstantiateProvider(value: string): [IQuickAccessProvider | undefined, IQuickAccessProviderDescriptor | undefined] { + const providerDescriptor = this.registry.getQuickAccessProvider(value); + if (!providerDescriptor) { + return [undefined, undefined]; + } let provider = this.mapProviderToDescriptor.get(providerDescriptor); if (!provider) { diff --git a/src/vs/platform/quickinput/common/quickAccess.ts b/src/vs/platform/quickinput/common/quickAccess.ts index be8fba3f311..9da4698d02b 100644 --- a/src/vs/platform/quickinput/common/quickAccess.ts +++ b/src/vs/platform/quickinput/common/quickAccess.ts @@ -6,9 +6,8 @@ import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Registry } from 'vs/platform/registry/common/platform'; -import { first } from 'vs/base/common/arrays'; +import { first, coalesce } from 'vs/base/common/arrays'; import { startsWith } from 'vs/base/common/strings'; -import { assertIsDefined } from 'vs/base/common/types'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; export interface IQuickAccessController { @@ -92,11 +91,6 @@ export const Extensions = { export interface IQuickAccessRegistry { - /** - * The default provider to use when no other provider matches. - */ - defaultProvider: IQuickAccessProviderDescriptor; - /** * Registers a quick access provider to the platform. */ @@ -113,29 +107,53 @@ export interface IQuickAccessRegistry { getQuickAccessProvider(prefix: string): IQuickAccessProviderDescriptor | undefined; } -class QuickAccessRegistry implements IQuickAccessRegistry { +export class QuickAccessRegistry implements IQuickAccessRegistry { private providers: IQuickAccessProviderDescriptor[] = []; - - private _defaultProvider: IQuickAccessProviderDescriptor | undefined = undefined; - get defaultProvider(): IQuickAccessProviderDescriptor { return assertIsDefined(this._defaultProvider); } - set defaultProvider(provider: IQuickAccessProviderDescriptor) { this._defaultProvider = provider; } + private defaultProvider: IQuickAccessProviderDescriptor | undefined = undefined; registerQuickAccessProvider(provider: IQuickAccessProviderDescriptor): IDisposable { - this.providers.push(provider); + + // Extract the default provider when no prefix is present + if (provider.prefix.length === 0) { + this.defaultProvider = provider; + } else { + this.providers.push(provider); + } // sort the providers by decreasing prefix length, such that longer // prefixes take priority: 'ext' vs 'ext install' - the latter should win this.providers.sort((providerA, providerB) => providerB.prefix.length - providerA.prefix.length); - return toDisposable(() => this.providers.splice(this.providers.indexOf(provider), 1)); + return toDisposable(() => { + this.providers.splice(this.providers.indexOf(provider), 1); + + if (this.defaultProvider === provider) { + this.defaultProvider = undefined; + } + }); } getQuickAccessProviders(): IQuickAccessProviderDescriptor[] { - return [this.defaultProvider, ...this.providers]; + return coalesce([this.defaultProvider, ...this.providers]); } getQuickAccessProvider(prefix: string): IQuickAccessProviderDescriptor | undefined { - return prefix ? (first(this.providers, provider => startsWith(prefix, provider.prefix)) || undefined) : undefined; + const result = prefix ? (first(this.providers, provider => startsWith(prefix, provider.prefix)) || undefined) : undefined; + + return result || this.defaultProvider; + } + + clear(): Function { + const providers = [...this.providers]; + const defaultProvider = this.defaultProvider; + + this.providers = []; + this.defaultProvider = undefined; + + return () => { + this.providers = providers; + this.defaultProvider = defaultProvider; + }; } } diff --git a/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts b/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts index d8d4ecad23f..615cfa95982 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts @@ -15,13 +15,6 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co const registry = Registry.as(Extensions.Quickaccess); -registry.defaultProvider = { - ctor: HelpQuickAccessProvider, - prefix: '', - placeholder: localize('defaultAccessPlaceholder', "Type the name of a file to open."), - helpEntries: [{ description: localize('gotoFileQuickAccess', "Go to File"), needsEditor: false }] -}; - registry.registerQuickAccessProvider({ ctor: HelpQuickAccessProvider, prefix: HelpQuickAccessProvider.PREFIX, diff --git a/src/vs/workbench/test/browser/quickAccess.test.ts b/src/vs/workbench/test/browser/quickAccess.test.ts index bd9990491e4..d265406e99b 100644 --- a/src/vs/workbench/test/browser/quickAccess.test.ts +++ b/src/vs/workbench/test/browser/quickAccess.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IQuickAccessRegistry, Extensions, IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IQuickAccessRegistry, Extensions, IQuickAccessProvider, QuickAccessRegistry } from 'vs/platform/quickinput/common/quickAccess'; import { IQuickPick, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -18,6 +18,10 @@ suite('QuickAccess', () => { let instantiationService: IInstantiationService; let accessor: TestServiceAccessor; + let providerDefaultCalled = false; + let providerDefaultCanceled = false; + let providerDefaultDisposed = false; + let provider1Called = false; let provider1Canceled = false; let provider1Disposed = false; @@ -30,9 +34,21 @@ suite('QuickAccess', () => { let provider3Canceled = false; let provider3Disposed = false; - let provider4Called = false; - let provider4Canceled = false; - let provider4Disposed = false; + class TestProviderDefault implements IQuickAccessProvider { + + constructor(@IQuickInputService private readonly quickInputService: IQuickInputService, disposables: DisposableStore) { } + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + assert.ok(picker); + providerDefaultCalled = true; + token.onCancellationRequested(() => providerDefaultCanceled = true); + + // bring up provider #3 + setTimeout(() => this.quickInputService.quickAccess.show(providerDescriptor3.prefix)); + + return toDisposable(() => providerDefaultDisposed = true); + } + } class TestProvider1 implements IQuickAccessProvider { provide(picker: IQuickPick, token: CancellationToken): IDisposable { @@ -55,39 +71,22 @@ suite('QuickAccess', () => { } class TestProvider3 implements IQuickAccessProvider { - - constructor(@IQuickInputService private readonly quickInputService: IQuickInputService, disposables: DisposableStore) { } - provide(picker: IQuickPick, token: CancellationToken): IDisposable { assert.ok(picker); provider3Called = true; token.onCancellationRequested(() => provider3Canceled = true); - // bring up provider #4 - setTimeout(() => this.quickInputService.quickAccess.show(providerDescriptor4.prefix)); + // hide without picking + setTimeout(() => picker.hide()); return toDisposable(() => provider3Disposed = true); } } - class TestProvider4 implements IQuickAccessProvider { - provide(picker: IQuickPick, token: CancellationToken): IDisposable { - assert.ok(picker); - provider4Called = true; - token.onCancellationRequested(() => provider4Canceled = true); - - // hide without picking - setTimeout(() => picker.hide()); - - return toDisposable(() => provider4Disposed = true); - } - } - - const defaultProviderDescriptor = { ctor: TestProvider1, prefix: '', helpEntries: [] }; + const providerDescriptorDefault = { ctor: TestProviderDefault, prefix: '', helpEntries: [] }; const providerDescriptor1 = { ctor: TestProvider1, prefix: 'test', helpEntries: [] }; const providerDescriptor2 = { ctor: TestProvider2, prefix: 'test something', helpEntries: [] }; - const providerDescriptor3 = { ctor: TestProvider3, prefix: 'default', helpEntries: [] }; - const providerDescriptor4 = { ctor: TestProvider4, prefix: 'changed', helpEntries: [] }; + const providerDescriptor3 = { ctor: TestProvider3, prefix: 'changed', helpEntries: [] }; setup(() => { instantiationService = workbenchInstantiationService(); @@ -96,91 +95,101 @@ suite('QuickAccess', () => { test('registry', () => { const registry = (Registry.as(Extensions.Quickaccess)); - registry.defaultProvider = defaultProviderDescriptor; + const restore = (registry as QuickAccessRegistry).clear(); - const initialSize = registry.getQuickAccessProviders().length; + assert.ok(!registry.getQuickAccessProvider('test')); - const disposable = registry.registerQuickAccessProvider(providerDescriptor1); + const disposables = new DisposableStore(); + disposables.add(registry.registerQuickAccessProvider(providerDescriptorDefault)); + assert(registry.getQuickAccessProvider('') === providerDescriptorDefault); + assert(registry.getQuickAccessProvider('test') === providerDescriptorDefault); + + const disposable = disposables.add(registry.registerQuickAccessProvider(providerDescriptor1)); assert(registry.getQuickAccessProvider('test') === providerDescriptor1); const providers = registry.getQuickAccessProviders(); assert(providers.some(provider => provider.prefix === 'test')); disposable.dispose(); + assert(registry.getQuickAccessProvider('test') === providerDescriptorDefault); + + disposables.dispose(); assert.ok(!registry.getQuickAccessProvider('test')); - assert.equal(registry.getQuickAccessProviders().length - initialSize, 0); + + restore(); }); test('provider', async () => { const registry = (Registry.as(Extensions.Quickaccess)); - const defaultProvider = registry.defaultProvider; + const restore = (registry as QuickAccessRegistry).clear(); const disposables = new DisposableStore(); + disposables.add(registry.registerQuickAccessProvider(providerDescriptorDefault)); disposables.add(registry.registerQuickAccessProvider(providerDescriptor1)); disposables.add(registry.registerQuickAccessProvider(providerDescriptor2)); - disposables.add(registry.registerQuickAccessProvider(providerDescriptor4)); - registry.defaultProvider = providerDescriptor3; + disposables.add(registry.registerQuickAccessProvider(providerDescriptor3)); accessor.quickInputService.quickAccess.show('test'); + assert.equal(providerDefaultCalled, false); assert.equal(provider1Called, true); assert.equal(provider2Called, false); assert.equal(provider3Called, false); - assert.equal(provider4Called, false); + assert.equal(providerDefaultCanceled, false); assert.equal(provider1Canceled, false); assert.equal(provider2Canceled, false); assert.equal(provider3Canceled, false); - assert.equal(provider4Canceled, false); + assert.equal(providerDefaultDisposed, false); assert.equal(provider1Disposed, false); assert.equal(provider2Disposed, false); assert.equal(provider3Disposed, false); - assert.equal(provider4Disposed, false); provider1Called = false; accessor.quickInputService.quickAccess.show('test something'); + assert.equal(providerDefaultCalled, false); assert.equal(provider1Called, false); assert.equal(provider2Called, true); assert.equal(provider3Called, false); - assert.equal(provider4Called, false); + assert.equal(providerDefaultCanceled, false); assert.equal(provider1Canceled, true); assert.equal(provider2Canceled, false); assert.equal(provider3Canceled, false); - assert.equal(provider4Canceled, false); + assert.equal(providerDefaultDisposed, false); assert.equal(provider1Disposed, true); assert.equal(provider2Disposed, false); assert.equal(provider3Disposed, false); - assert.equal(provider4Disposed, false); provider2Called = false; provider1Canceled = false; provider1Disposed = false; accessor.quickInputService.quickAccess.show('usedefault'); + assert.equal(providerDefaultCalled, true); assert.equal(provider1Called, false); assert.equal(provider2Called, false); - assert.equal(provider3Called, true); - assert.equal(provider4Called, false); + assert.equal(provider3Called, false); + assert.equal(providerDefaultCanceled, false); assert.equal(provider1Canceled, false); assert.equal(provider2Canceled, true); assert.equal(provider3Canceled, false); - assert.equal(provider4Canceled, false); + assert.equal(providerDefaultDisposed, false); assert.equal(provider1Disposed, false); assert.equal(provider2Disposed, true); assert.equal(provider3Disposed, false); - assert.equal(provider4Disposed, false); + + await timeout(1); + + assert.equal(providerDefaultCanceled, true); + assert.equal(providerDefaultDisposed, true); + assert.equal(provider3Called, true); await timeout(1); assert.equal(provider3Canceled, true); assert.equal(provider3Disposed, true); - assert.equal(provider4Called, true); - - await timeout(1); - - assert.equal(provider4Canceled, true); - assert.equal(provider4Disposed, true); disposables.dispose(); - registry.defaultProvider = defaultProvider; + + restore(); }); }); From e2982af33d7b8baf794d14a006088d29c321644f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 16 Mar 2020 11:50:18 +0100 Subject: [PATCH 154/169] quick access - remove quick open from standalone editor and use quick access instead :rocket: --- src/vs/editor/common/standaloneStrings.ts | 22 -- src/vs/editor/editor.main.ts | 3 - .../standaloneCommandsQuickAccess.ts | 34 +- .../standaloneGotoLineQuickAccess.ts | 31 +- .../standaloneGotoSymbolQuickAccess.ts | 33 ++ .../browser/quickOpen/editorQuickOpen.css | 19 - .../browser/quickOpen/editorQuickOpen.ts | 172 --------- .../standalone/browser/quickOpen/gotoLine.css | 8 - .../standalone/browser/quickOpen/gotoLine.ts | 177 ---------- .../browser/quickOpen/quickCommand.ts | 144 -------- .../quickOpen/quickOpenEditorWidget.ts | 107 ------ .../browser/quickOpen/quickOutline.css | 8 - .../browser/quickOpen/quickOutline.ts | 325 ------------------ 13 files changed, 96 insertions(+), 987 deletions(-) delete mode 100644 src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.css delete mode 100644 src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts delete mode 100644 src/vs/editor/standalone/browser/quickOpen/gotoLine.css delete mode 100644 src/vs/editor/standalone/browser/quickOpen/gotoLine.ts delete mode 100644 src/vs/editor/standalone/browser/quickOpen/quickCommand.ts delete mode 100644 src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts delete mode 100644 src/vs/editor/standalone/browser/quickOpen/quickOutline.css delete mode 100644 src/vs/editor/standalone/browser/quickOpen/quickOutline.ts diff --git a/src/vs/editor/common/standaloneStrings.ts b/src/vs/editor/common/standaloneStrings.ts index 8ca766d7240..e915d340fb3 100644 --- a/src/vs/editor/common/standaloneStrings.ts +++ b/src/vs/editor/common/standaloneStrings.ts @@ -36,12 +36,6 @@ export namespace InspectTokensNLS { } export namespace GoToLineNLS { - export const gotoLineLabelValidLineAndColumn = nls.localize('gotoLineLabelValidLineAndColumn', "Go to line {0} and character {1}"); - export const gotoLineLabelValidLine = nls.localize('gotoLineLabelValidLine', "Go to line {0}"); - export const gotoLineLabelEmptyWithLineLimit = nls.localize('gotoLineLabelEmptyWithLineLimit', "Type a line number between 1 and {0} to navigate to"); - export const gotoLineLabelEmptyWithLineAndColumnLimit = nls.localize('gotoLineLabelEmptyWithLineAndColumnLimit', "Type a character between 1 and {0} to navigate to"); - export const gotoLineAriaLabel = nls.localize('gotoLineAriaLabel', "Current Line: {0}. Go to line {1}."); - export const gotoLineActionInput = nls.localize('gotoLineActionInput', "Type a line number, followed by an optional colon and a character number to navigate to"); export const gotoLineActionLabel = nls.localize('gotoLineActionLabel', "Go to Line..."); } @@ -50,29 +44,13 @@ export namespace QuickHelpNLS { } export namespace QuickCommandNLS { - export const ariaLabelEntryWithKey = nls.localize('ariaLabelEntryWithKey', "{0}, {1}, commands"); - export const ariaLabelEntry = nls.localize('ariaLabelEntry', "{0}, commands"); - export const quickCommandActionInput = nls.localize('quickCommandActionInput', "Type the name of an action you want to execute"); export const quickCommandActionLabel = nls.localize('quickCommandActionLabel', "Command Palette"); export const quickCommandHelp = nls.localize('quickCommandActionHelp', "Show And Run Commands"); } export namespace QuickOutlineNLS { - export const entryAriaLabel = nls.localize('entryAriaLabel', "{0}, symbols"); - export const quickOutlineActionInput = nls.localize('quickOutlineActionInput', "Type the name of an identifier you wish to navigate to"); export const quickOutlineActionLabel = nls.localize('quickOutlineActionLabel', "Go to Symbol..."); export const quickOutlineByCategoryActionLabel = nls.localize('quickOutlineByCategoryActionLabel', "Go to Symbol by Category..."); - export const _symbols_ = nls.localize('symbols', "symbols ({0})"); - export const _modules_ = nls.localize('modules', "modules ({0})"); - export const _class_ = nls.localize('class', "classes ({0})"); - export const _interface_ = nls.localize('interface', "interfaces ({0})"); - export const _method_ = nls.localize('method', "methods ({0})"); - export const _function_ = nls.localize('function', "functions ({0})"); - export const _property_ = nls.localize('property', "properties ({0})"); - export const _variable_ = nls.localize('variable', "variables ({0})"); - export const _variable2_ = nls.localize('variable2', "variables ({0})"); - export const _constructor_ = nls.localize('_constructor', "constructors ({0})"); - export const _call_ = nls.localize('call', "calls ({0})"); } export namespace StandaloneCodeEditorNLS { diff --git a/src/vs/editor/editor.main.ts b/src/vs/editor/editor.main.ts index 6e3f581f70a..0dceb4a9e8a 100644 --- a/src/vs/editor/editor.main.ts +++ b/src/vs/editor/editor.main.ts @@ -7,9 +7,6 @@ import 'vs/editor/editor.all'; import 'vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp'; import 'vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard'; import 'vs/editor/standalone/browser/inspectTokens/inspectTokens'; -import 'vs/editor/standalone/browser/quickOpen/gotoLine'; -import 'vs/editor/standalone/browser/quickOpen/quickCommand'; -import 'vs/editor/standalone/browser/quickOpen/quickOutline'; import 'vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess'; import 'vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess'; import 'vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess'; diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts index a954f12426e..f4839b34f21 100644 --- a/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts @@ -11,11 +11,16 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { AbstractEditorCommandsQuickAccessProvider } from 'vs/editor/contrib/quickAccess/commandsQuickAccess'; import { IEditor } from 'vs/editor/common/editorCommon'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; export class StandaloneCommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { @@ -42,3 +47,30 @@ Registry.as(Extensions.Quickaccess).registerQuickAccessPro prefix: StandaloneCommandsQuickAccessProvider.PREFIX, helpEntries: [{ description: QuickCommandNLS.quickCommandHelp, needsEditor: true }] }); + +export class GotoLineAction extends EditorAction { + + constructor() { + super({ + id: 'editor.action.quickCommand', + label: QuickCommandNLS.quickCommandActionLabel, + alias: 'Command Palette', + precondition: undefined, + kbOpts: { + kbExpr: EditorContextKeys.focus, + primary: KeyCode.F1, + weight: KeybindingWeight.EditorContrib + }, + contextMenuOpts: { + group: 'z_commands', + order: 1 + } + }); + } + + run(accessor: ServicesAccessor): void { + accessor.get(IQuickInputService).quickAccess.show(StandaloneCommandsQuickAccessProvider.PREFIX); + } +} + +registerEditorAction(GotoLineAction); diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts index 58933c776e1..ab7735e221c 100644 --- a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts @@ -10,6 +10,11 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { withNullAsUndefined } from 'vs/base/common/types'; import { GoToLineNLS } from 'vs/editor/common/standaloneStrings'; import { Event } from 'vs/base/common/event'; +import { EditorAction, registerEditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; export class StandaloneGotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvider { @@ -26,6 +31,30 @@ export class StandaloneGotoLineQuickAccessProvider extends AbstractGotoLineQuick Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ ctor: StandaloneGotoLineQuickAccessProvider, - prefix: AbstractGotoLineQuickAccessProvider.PREFIX, + prefix: StandaloneGotoLineQuickAccessProvider.PREFIX, helpEntries: [{ description: GoToLineNLS.gotoLineActionLabel, needsEditor: true }] }); + +export class GotoLineAction extends EditorAction { + + constructor() { + super({ + id: 'editor.action.gotoLine', + label: GoToLineNLS.gotoLineActionLabel, + alias: 'Go to Line...', + precondition: undefined, + kbOpts: { + kbExpr: EditorContextKeys.focus, + primary: KeyMod.CtrlCmd | KeyCode.KEY_G, + mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_G }, + weight: KeybindingWeight.EditorContrib + } + }); + } + + run(accessor: ServicesAccessor): void { + accessor.get(IQuickInputService).quickAccess.show(StandaloneGotoLineQuickAccessProvider.PREFIX); + } +} + +registerEditorAction(GotoLineAction); diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts index 08287295aba..b5850c3f761 100644 --- a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts @@ -10,6 +10,12 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { withNullAsUndefined } from 'vs/base/common/types'; import { QuickOutlineNLS } from 'vs/editor/common/standaloneStrings'; import { Event } from 'vs/base/common/event'; +import { EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; export class StandaloneGotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { @@ -32,3 +38,30 @@ Registry.as(Extensions.Quickaccess).registerQuickAccessPro { description: QuickOutlineNLS.quickOutlineByCategoryActionLabel, prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX_BY_CATEGORY, needsEditor: true } ] }); + +export class GotoLineAction extends EditorAction { + + constructor() { + super({ + id: 'editor.action.quickOutline', + label: QuickOutlineNLS.quickOutlineActionLabel, + alias: 'Go to Symbol...', + precondition: EditorContextKeys.hasDocumentSymbolProvider, + kbOpts: { + kbExpr: EditorContextKeys.focus, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O, + weight: KeybindingWeight.EditorContrib + }, + contextMenuOpts: { + group: 'navigation', + order: 3 + } + }); + } + + run(accessor: ServicesAccessor): void { + accessor.get(IQuickInputService).quickAccess.show(AbstractGotoSymbolQuickAccessProvider.PREFIX); + } +} + +registerEditorAction(GotoLineAction); diff --git a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.css b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.css deleted file mode 100644 index fdc3b7e9a24..00000000000 --- a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.css +++ /dev/null @@ -1,19 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-quick-open-widget .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight, -.monaco-quick-open-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { - color: #0066BF; -} - -.vs-dark .monaco-quick-open-widget .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight, -.vs-dark .monaco-quick-open-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { - color: #0097fb; -} - -.hc-black .monaco-quick-open-widget .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight, -.hc-black .monaco-quick-open-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { - color: #F38518; -} \ No newline at end of file diff --git a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts deleted file mode 100644 index b5d6aea1470..00000000000 --- a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts +++ /dev/null @@ -1,172 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import 'vs/css!./editorQuickOpen'; -import { QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, IActionOptions, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; -import { IEditorContribution, ScrollType, IEditor } from 'vs/editor/common/editorCommon'; -import { IModelDeltaDecoration } from 'vs/editor/common/model'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { QuickOpenEditorWidget } from 'vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; - -export interface IQuickOpenControllerOpts { - inputAriaLabel: string; - getModel(value: string): QuickOpenModel; - getAutoFocus(searchValue: string): IAutoFocus; -} - -export class QuickOpenController implements IEditorContribution, IDecorator { - - public static readonly ID = 'editor.controller.quickOpenController'; - - public static get(editor: ICodeEditor): QuickOpenController { - return editor.getContribution(QuickOpenController.ID); - } - - private readonly editor: ICodeEditor; - private widget: QuickOpenEditorWidget | null = null; - private rangeHighlightDecorationId: string | null = null; - private lastKnownEditorSelection: Selection | null = null; - - constructor(editor: ICodeEditor, @IThemeService private readonly themeService: IThemeService) { - this.editor = editor; - } - - public dispose(): void { - // Dispose widget - if (this.widget) { - this.widget.destroy(); - this.widget = null; - } - } - - public run(opts: IQuickOpenControllerOpts): void { - if (this.widget) { - this.widget.destroy(); - this.widget = null; - } - - // Create goto line widget - let onClose = (canceled: boolean) => { - // Clear Highlight Decorations if present - this.clearDecorations(); - - // Restore selection if canceled - if (canceled && this.lastKnownEditorSelection) { - this.editor.setSelection(this.lastKnownEditorSelection); - this.editor.revealRangeInCenterIfOutsideViewport(this.lastKnownEditorSelection, ScrollType.Smooth); - } - - this.lastKnownEditorSelection = null; - - // Return focus to the editor if - // - focus is back on the element because no other focusable element was clicked - // - a command was picked from the picker which indicates the editor should get focused - if (document.activeElement === document.body || !canceled) { - this.editor.focus(); - } - }; - - this.widget = new QuickOpenEditorWidget( - this.editor, - () => onClose(false), - () => onClose(true), - (value: string) => { - this.widget!.setInput(opts.getModel(value), opts.getAutoFocus(value)); - }, - { - inputAriaLabel: opts.inputAriaLabel - }, - this.themeService - ); - - // Remember selection to be able to restore on cancel - if (!this.lastKnownEditorSelection) { - this.lastKnownEditorSelection = this.editor.getSelection(); - } - - // Show - this.widget.show(''); - } - - private static readonly _RANGE_HIGHLIGHT_DECORATION = ModelDecorationOptions.register({ - className: 'rangeHighlight', - isWholeLine: true - }); - - public decorateLine(range: Range, editor: ICodeEditor): void { - const oldDecorations: string[] = []; - if (this.rangeHighlightDecorationId) { - oldDecorations.push(this.rangeHighlightDecorationId); - this.rangeHighlightDecorationId = null; - } - - const newDecorations: IModelDeltaDecoration[] = [ - { - range: range, - options: QuickOpenController._RANGE_HIGHLIGHT_DECORATION - } - ]; - - const decorations = editor.deltaDecorations(oldDecorations, newDecorations); - this.rangeHighlightDecorationId = decorations[0]; - } - - public clearDecorations(): void { - if (this.rangeHighlightDecorationId) { - this.editor.deltaDecorations([this.rangeHighlightDecorationId], []); - this.rangeHighlightDecorationId = null; - } - } -} - -export interface IQuickOpenOpts { - /** - * provide the quick open model for the given search value. - */ - getModel(value: string): QuickOpenModel; - - /** - * provide the quick open auto focus mode for the given search value. - */ - getAutoFocus(searchValue: string): IAutoFocus; -} - -/** - * Base class for providing quick open in the editor. - */ -export abstract class BaseEditorQuickOpenAction extends EditorAction { - - private readonly _inputAriaLabel: string; - - constructor(inputAriaLabel: string, opts: IActionOptions) { - super(opts); - this._inputAriaLabel = inputAriaLabel; - } - - protected getController(editor: ICodeEditor): QuickOpenController { - return QuickOpenController.get(editor); - } - - protected _show(controller: QuickOpenController, opts: IQuickOpenOpts): void { - controller.run({ - inputAriaLabel: this._inputAriaLabel, - getModel: (value: string): QuickOpenModel => opts.getModel(value), - getAutoFocus: (searchValue: string): IAutoFocus => opts.getAutoFocus(searchValue) - }); - } -} - -export interface IDecorator { - decorateLine(range: Range, editor: IEditor): void; - clearDecorations(): void; -} - -registerEditorContribution(QuickOpenController.ID, QuickOpenController); diff --git a/src/vs/editor/standalone/browser/quickOpen/gotoLine.css b/src/vs/editor/standalone/browser/quickOpen/gotoLine.css deleted file mode 100644 index e71a5e1dd76..00000000000 --- a/src/vs/editor/standalone/browser/quickOpen/gotoLine.css +++ /dev/null @@ -1,8 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-quick-open-widget { - font-size: 13px; -} \ No newline at end of file diff --git a/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts b/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts deleted file mode 100644 index 5274a3ae2c3..00000000000 --- a/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts +++ /dev/null @@ -1,177 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import 'vs/css!./gotoLine'; -import * as strings from 'vs/base/common/strings'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { QuickOpenEntry, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; -import { ICodeEditor, IDiffEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; -import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; -import { IEditor, ScrollType } from 'vs/editor/common/editorCommon'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ITextModel } from 'vs/editor/common/model'; -import { BaseEditorQuickOpenAction, IDecorator } from 'vs/editor/standalone/browser/quickOpen/editorQuickOpen'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { GoToLineNLS } from 'vs/editor/common/standaloneStrings'; - -interface ParseResult { - position: Position; - isValid: boolean; - label: string; -} - -export class GotoLineEntry extends QuickOpenEntry { - private readonly parseResult: ParseResult; - private readonly decorator: IDecorator; - private readonly editor: IEditor; - - constructor(line: string, editor: IEditor, decorator: IDecorator) { - super(); - - this.editor = editor; - this.decorator = decorator; - this.parseResult = this.parseInput(line); - } - - private parseInput(line: string): ParseResult { - const numbers = line.split(',').map(part => parseInt(part, 10)).filter(part => !isNaN(part)); - let position: Position; - - if (numbers.length === 0) { - position = new Position(-1, -1); - } else if (numbers.length === 1) { - position = new Position(numbers[0], 1); - } else { - position = new Position(numbers[0], numbers[1]); - } - - let model: ITextModel | null; - if (isCodeEditor(this.editor)) { - model = this.editor.getModel(); - } else { - const diffModel = (this.editor).getModel(); - model = diffModel ? diffModel.modified : null; - } - - const isValid = model ? model.validatePosition(position).equals(position) : false; - let label: string; - - if (isValid) { - if (position.column && position.column > 1) { - label = strings.format(GoToLineNLS.gotoLineLabelValidLineAndColumn, position.lineNumber, position.column); - } else { - label = strings.format(GoToLineNLS.gotoLineLabelValidLine, position.lineNumber); - } - } else if (position.lineNumber < 1 || position.lineNumber > (model ? model.getLineCount() : 0)) { - label = strings.format(GoToLineNLS.gotoLineLabelEmptyWithLineLimit, model ? model.getLineCount() : 0); - } else { - label = strings.format(GoToLineNLS.gotoLineLabelEmptyWithLineAndColumnLimit, model ? model.getLineMaxColumn(position.lineNumber) : 0); - } - - return { - position: position, - isValid: isValid, - label: label - }; - } - - getLabel(): string { - return this.parseResult.label; - } - - getAriaLabel(): string { - const position = this.editor.getPosition(); - const currentLine = position ? position.lineNumber : 0; - return strings.format(GoToLineNLS.gotoLineAriaLabel, currentLine, this.parseResult.label); - } - - run(mode: Mode, _context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - return this.runOpen(); - } - - return this.runPreview(); - } - - runOpen(): boolean { - - // No-op if range is not valid - if (!this.parseResult.isValid) { - return false; - } - - // Apply selection and focus - const range = this.toSelection(); - (this.editor).setSelection(range); - (this.editor).revealRangeInCenter(range, ScrollType.Smooth); - this.editor.focus(); - - return true; - } - - runPreview(): boolean { - - // No-op if range is not valid - if (!this.parseResult.isValid) { - this.decorator.clearDecorations(); - return false; - } - - // Select Line Position - const range = this.toSelection(); - this.editor.revealRangeInCenter(range, ScrollType.Smooth); - - // Decorate if possible - this.decorator.decorateLine(range, this.editor); - - return false; - } - - private toSelection(): Range { - return new Range( - this.parseResult.position.lineNumber, - this.parseResult.position.column, - this.parseResult.position.lineNumber, - this.parseResult.position.column - ); - } -} - -export class GotoLineAction extends BaseEditorQuickOpenAction { - - constructor() { - super(GoToLineNLS.gotoLineActionInput, { - id: 'editor.action.gotoLine', - label: GoToLineNLS.gotoLineActionLabel, - alias: 'Go to Line...', - precondition: undefined, - kbOpts: { - kbExpr: EditorContextKeys.focus, - primary: KeyMod.CtrlCmd | KeyCode.KEY_G, - mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_G }, - weight: KeybindingWeight.EditorContrib - } - }); - } - - run(accessor: ServicesAccessor, editor: ICodeEditor): void { - this._show(this.getController(editor), { - getModel: (value: string): QuickOpenModel => { - return new QuickOpenModel([new GotoLineEntry(value, editor, this.getController(editor))]); - }, - - getAutoFocus: (searchValue: string): IAutoFocus => { - return { - autoFocusFirstEntry: searchValue.length > 0 - }; - } - }); - } -} - -registerEditorAction(GotoLineAction); diff --git a/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts b/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts deleted file mode 100644 index 2e49917211b..00000000000 --- a/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts +++ /dev/null @@ -1,144 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as strings from 'vs/base/common/strings'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { matchesFuzzy } from 'vs/base/common/filters'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { IHighlight, QuickOpenEntryGroup, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; -import { IEditor, IEditorAction } from 'vs/editor/common/editorCommon'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { BaseEditorQuickOpenAction } from 'vs/editor/standalone/browser/quickOpen/editorQuickOpen'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { QuickCommandNLS } from 'vs/editor/common/standaloneStrings'; - -export class EditorActionCommandEntry extends QuickOpenEntryGroup { - private readonly key: string; - private readonly action: IEditorAction; - private readonly editor: IEditor; - private readonly keyAriaLabel: string; - - constructor(key: string, keyAriaLabel: string, highlights: IHighlight[], action: IEditorAction, editor: IEditor) { - super(); - - this.key = key; - this.keyAriaLabel = keyAriaLabel; - this.setHighlights(highlights); - this.action = action; - this.editor = editor; - } - - public getLabel(): string { - return this.action.label; - } - - public getAriaLabel(): string { - if (this.keyAriaLabel) { - return strings.format(QuickCommandNLS.ariaLabelEntryWithKey, this.getLabel(), this.keyAriaLabel); - } - - return strings.format(QuickCommandNLS.ariaLabelEntry, this.getLabel()); - } - - public getGroupLabel(): string { - return this.key; - } - - public run(mode: Mode, context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - - // Use a timeout to give the quick open widget a chance to close itself first - setTimeout(() => { - - // Some actions are enabled only when editor has focus - this.editor.focus(); - - try { - let promise = this.action.run() || Promise.resolve(); - promise.then(undefined, onUnexpectedError); - } catch (error) { - onUnexpectedError(error); - } - }, 50); - - return true; - } - - return false; - } -} - -export class QuickCommandAction extends BaseEditorQuickOpenAction { - - constructor() { - super(QuickCommandNLS.quickCommandActionInput, { - id: 'editor.action.quickCommand', - label: QuickCommandNLS.quickCommandActionLabel, - alias: 'Command Palette', - precondition: undefined, - kbOpts: { - kbExpr: EditorContextKeys.focus, - primary: KeyCode.F1, - weight: KeybindingWeight.EditorContrib - }, - contextMenuOpts: { - group: 'z_commands', - order: 1 - } - }); - } - - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - const keybindingService = accessor.get(IKeybindingService); - - this._show(this.getController(editor), { - getModel: (value: string): QuickOpenModel => { - return new QuickOpenModel(this._editorActionsToEntries(keybindingService, editor, value)); - }, - - getAutoFocus: (searchValue: string): IAutoFocus => { - return { - autoFocusFirstEntry: true, - autoFocusPrefixMatch: searchValue - }; - } - }); - } - - private _sort(elementA: QuickOpenEntryGroup, elementB: QuickOpenEntryGroup): number { - let elementAName = (elementA.getLabel() || '').toLowerCase(); - let elementBName = (elementB.getLabel() || '').toLowerCase(); - - return elementAName.localeCompare(elementBName); - } - - private _editorActionsToEntries(keybindingService: IKeybindingService, editor: ICodeEditor, searchValue: string): EditorActionCommandEntry[] { - let actions: IEditorAction[] = editor.getSupportedActions(); - let entries: EditorActionCommandEntry[] = []; - - for (const action of actions) { - - let keybinding = keybindingService.lookupKeybinding(action.id); - - if (action.label) { - let highlights = matchesFuzzy(searchValue, action.label); - if (highlights) { - entries.push(new EditorActionCommandEntry(keybinding ? keybinding.getLabel() || '' : '', keybinding ? keybinding.getAriaLabel() || '' : '', highlights, action, editor)); - } - } - } - - // Sort by name - entries = entries.sort(this._sort); - - return entries; - } -} - -registerEditorAction(QuickCommandAction); diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts b/src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts deleted file mode 100644 index 17fcaf121bf..00000000000 --- a/src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts +++ /dev/null @@ -1,107 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Dimension } from 'vs/base/browser/dom'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { QuickOpenWidget } from 'vs/base/parts/quickopen/browser/quickOpenWidget'; -import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; -import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; -import { foreground } from 'vs/platform/theme/common/colorRegistry'; -import { attachQuickOpenStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; - -export interface IQuickOpenEditorWidgetOptions { - inputAriaLabel: string; -} - -export class QuickOpenEditorWidget implements IOverlayWidget { - - private static readonly ID = 'editor.contrib.quickOpenEditorWidget'; - - private readonly codeEditor: ICodeEditor; - private readonly themeService: IThemeService; - private visible: boolean | undefined; - private quickOpenWidget: QuickOpenWidget; - private domNode: HTMLElement; - private styler: IDisposable; - - constructor(codeEditor: ICodeEditor, onOk: () => void, onCancel: () => void, onType: (value: string) => void, configuration: IQuickOpenEditorWidgetOptions, themeService: IThemeService) { - this.codeEditor = codeEditor; - this.themeService = themeService; - this.visible = false; - - this.domNode = document.createElement('div'); - - this.quickOpenWidget = new QuickOpenWidget( - this.domNode, - { - onOk: onOk, - onCancel: onCancel, - onType: onType - }, { - inputPlaceHolder: undefined, - inputAriaLabel: configuration.inputAriaLabel, - keyboardSupport: true - } - ); - this.styler = attachQuickOpenStyler(this.quickOpenWidget, this.themeService, { - pickerGroupForeground: foreground - }); - - this.quickOpenWidget.create(); - this.codeEditor.addOverlayWidget(this); - } - - setInput(model: QuickOpenModel, focus: IAutoFocus): void { - this.quickOpenWidget.setInput(model, focus); - } - - getId(): string { - return QuickOpenEditorWidget.ID; - } - - getDomNode(): HTMLElement { - return this.domNode; - } - - destroy(): void { - this.codeEditor.removeOverlayWidget(this); - this.quickOpenWidget.dispose(); - this.styler.dispose(); - } - - isVisible(): boolean { - return !!this.visible; - } - - show(value: string): void { - this.visible = true; - - const editorLayout = this.codeEditor.getLayoutInfo(); - if (editorLayout) { - this.quickOpenWidget.layout(new Dimension(editorLayout.width, editorLayout.height)); - } - - this.quickOpenWidget.show(value); - this.codeEditor.layoutOverlayWidget(this); - } - - hide(): void { - this.visible = false; - this.quickOpenWidget.hide(); - this.codeEditor.layoutOverlayWidget(this); - } - - getPosition(): IOverlayWidgetPosition | null { - if (this.visible) { - return { - preference: OverlayWidgetPositionPreference.TOP_CENTER - }; - } - - return null; - } -} diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOutline.css b/src/vs/editor/standalone/browser/quickOpen/quickOutline.css deleted file mode 100644 index 75309ce3318..00000000000 --- a/src/vs/editor/standalone/browser/quickOpen/quickOutline.css +++ /dev/null @@ -1,8 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-quick-open-widget { - font-size: 13px; -} diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts deleted file mode 100644 index 772073939ef..00000000000 --- a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts +++ /dev/null @@ -1,325 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import 'vs/css!./quickOutline'; -import 'vs/base/browser/ui/codiconLabel/codiconLabel'; // The codicon symbol styles are defined here and must be loaded -import 'vs/editor/contrib/documentSymbols/outlineTree'; // The codicon symbol colors are defined here and must be loaded -import { CancellationToken } from 'vs/base/common/cancellation'; -import { matchesFuzzy } from 'vs/base/common/filters'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import * as strings from 'vs/base/common/strings'; -import { IHighlight, QuickOpenEntryGroup, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; -import { IRange, Range } from 'vs/editor/common/core/range'; -import { ScrollType } from 'vs/editor/common/editorCommon'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { DocumentSymbol, DocumentSymbolProviderRegistry, SymbolKinds } from 'vs/editor/common/modes'; -import { getDocumentSymbols } from 'vs/editor/contrib/quickOpen/quickOpen'; -import { BaseEditorQuickOpenAction, IDecorator } from 'vs/editor/standalone/browser/quickOpen/editorQuickOpen'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { QuickOutlineNLS } from 'vs/editor/common/standaloneStrings'; - -let SCOPE_PREFIX = ':'; - -export class SymbolEntry extends QuickOpenEntryGroup { - private readonly name: string; - private readonly type: string; - private readonly description: string | undefined; - private readonly range: Range; - private readonly editor: ICodeEditor; - private readonly decorator: IDecorator; - - constructor(name: string, type: string, description: string | undefined, range: Range, highlights: IHighlight[], editor: ICodeEditor, decorator: IDecorator) { - super(); - - this.name = name; - this.type = type; - this.description = description; - this.range = range; - this.setHighlights(highlights); - this.editor = editor; - this.decorator = decorator; - } - - public getLabel(): string { - return this.name; - } - - public getAriaLabel(): string { - return strings.format(QuickOutlineNLS.entryAriaLabel, this.name); - } - - public getIcon(): string { - return this.type; - } - - public getDescription(): string | undefined { - return this.description; - } - - public getType(): string { - return this.type; - } - - public getRange(): Range { - return this.range; - } - - public run(mode: Mode, context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - return this.runOpen(context); - } - - return this.runPreview(); - } - - private runOpen(_context: IEntryRunContext): boolean { - - // Apply selection and focus - let range = this.toSelection(); - this.editor.setSelection(range); - this.editor.revealRangeInCenter(range, ScrollType.Smooth); - this.editor.focus(); - - return true; - } - - private runPreview(): boolean { - - // Select Outline Position - let range = this.toSelection(); - this.editor.revealRangeInCenter(range, ScrollType.Smooth); - - // Decorate if possible - this.decorator.decorateLine(this.range, this.editor); - - return false; - } - - private toSelection(): Range { - return new Range( - this.range.startLineNumber, - this.range.startColumn || 1, - this.range.startLineNumber, - this.range.startColumn || 1 - ); - } -} - -export class QuickOutlineAction extends BaseEditorQuickOpenAction { - - constructor() { - super(QuickOutlineNLS.quickOutlineActionInput, { - id: 'editor.action.quickOutline', - label: QuickOutlineNLS.quickOutlineActionLabel, - alias: 'Go to Symbol...', - precondition: EditorContextKeys.hasDocumentSymbolProvider, - kbOpts: { - kbExpr: EditorContextKeys.focus, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O, - weight: KeybindingWeight.EditorContrib - }, - contextMenuOpts: { - group: 'navigation', - order: 3 - } - }); - } - - public run(accessor: ServicesAccessor, editor: ICodeEditor) { - if (!editor.hasModel()) { - return undefined; - } - - const model = editor.getModel(); - - if (!DocumentSymbolProviderRegistry.has(model)) { - return undefined; - } - - // Resolve outline - return getDocumentSymbols(model, true, CancellationToken.None).then((result: DocumentSymbol[]) => { - if (result.length === 0) { - return; - } - - this._run(editor, result); - }); - } - - private _run(editor: ICodeEditor, result: DocumentSymbol[]): void { - this._show(this.getController(editor), { - getModel: (value: string): QuickOpenModel => { - return new QuickOpenModel(this.toQuickOpenEntries(editor, result, value)); - }, - - getAutoFocus: (searchValue: string): IAutoFocus => { - // Remove any type pattern (:) from search value as needed - if (searchValue.indexOf(SCOPE_PREFIX) === 0) { - searchValue = searchValue.substr(SCOPE_PREFIX.length); - } - - return { - autoFocusPrefixMatch: searchValue, - autoFocusFirstEntry: !!searchValue - }; - } - }); - } - - private symbolEntry(name: string, type: string, description: string | undefined, range: IRange, highlights: IHighlight[], editor: ICodeEditor, decorator: IDecorator): SymbolEntry { - return new SymbolEntry(name, type, description, Range.lift(range), highlights, editor, decorator); - } - - private toQuickOpenEntries(editor: ICodeEditor, flattened: DocumentSymbol[], searchValue: string): SymbolEntry[] { - const controller = this.getController(editor); - - let results: SymbolEntry[] = []; - - // Convert to Entries - let normalizedSearchValue = searchValue; - if (searchValue.indexOf(SCOPE_PREFIX) === 0) { - normalizedSearchValue = normalizedSearchValue.substr(SCOPE_PREFIX.length); - } - - for (const element of flattened) { - let label = strings.trim(element.name); - - // Check for meatch - let highlights = matchesFuzzy(normalizedSearchValue, label); - if (highlights) { - - // Show parent scope as description - let description: string | undefined = undefined; - if (element.containerName) { - description = element.containerName; - } - - // Add - results.push(this.symbolEntry(label, SymbolKinds.toCssClassName(element.kind), description, element.range, highlights, editor, controller)); - } - } - - // Sort properly if actually searching - if (searchValue) { - if (searchValue.indexOf(SCOPE_PREFIX) === 0) { - results = results.sort(this.sortScoped.bind(this, searchValue.toLowerCase())); - } else { - results = results.sort(this.sortNormal.bind(this, searchValue.toLowerCase())); - } - } - - // Mark all type groups - if (results.length > 0 && searchValue.indexOf(SCOPE_PREFIX) === 0) { - let currentType: string | null = null; - let currentResult: SymbolEntry | null = null; - let typeCounter = 0; - - for (let i = 0; i < results.length; i++) { - let result = results[i]; - - // Found new type - if (currentType !== result.getType()) { - - // Update previous result with count - if (currentResult) { - currentResult.setGroupLabel(this.typeToLabel(currentType || '', typeCounter)); - } - - currentType = result.getType(); - currentResult = result; - typeCounter = 1; - - result.setShowBorder(i > 0); - } - - // Existing type, keep counting - else { - typeCounter++; - } - } - - // Update previous result with count - if (currentResult) { - currentResult.setGroupLabel(this.typeToLabel(currentType || '', typeCounter)); - } - } - - // Mark first entry as outline - else if (results.length > 0) { - results[0].setGroupLabel(strings.format(QuickOutlineNLS._symbols_, results.length)); - } - - return results; - } - - private typeToLabel(type: string, count: number): string { - switch (type) { - case 'module': return strings.format(QuickOutlineNLS._modules_, count); - case 'class': return strings.format(QuickOutlineNLS._class_, count); - case 'interface': return strings.format(QuickOutlineNLS._interface_, count); - case 'method': return strings.format(QuickOutlineNLS._method_, count); - case 'function': return strings.format(QuickOutlineNLS._function_, count); - case 'property': return strings.format(QuickOutlineNLS._property_, count); - case 'variable': return strings.format(QuickOutlineNLS._variable_, count); - case 'var': return strings.format(QuickOutlineNLS._variable2_, count); - case 'constructor': return strings.format(QuickOutlineNLS._constructor_, count); - case 'call': return strings.format(QuickOutlineNLS._call_, count); - } - - return type; - } - - private sortNormal(searchValue: string, elementA: SymbolEntry, elementB: SymbolEntry): number { - let elementAName = elementA.getLabel().toLowerCase(); - let elementBName = elementB.getLabel().toLowerCase(); - - // Compare by name - let r = elementAName.localeCompare(elementBName); - if (r !== 0) { - return r; - } - - // If name identical sort by range instead - let elementARange = elementA.getRange(); - let elementBRange = elementB.getRange(); - return elementARange.startLineNumber - elementBRange.startLineNumber; - } - - private sortScoped(searchValue: string, elementA: SymbolEntry, elementB: SymbolEntry): number { - - // Remove scope char - searchValue = searchValue.substr(SCOPE_PREFIX.length); - - // Sort by type first if scoped search - let elementAType = elementA.getType(); - let elementBType = elementB.getType(); - let r = elementAType.localeCompare(elementBType); - if (r !== 0) { - return r; - } - - // Special sort when searching in scoped mode - if (searchValue) { - let elementAName = elementA.getLabel().toLowerCase(); - let elementBName = elementB.getLabel().toLowerCase(); - - // Compare by name - let r = elementAName.localeCompare(elementBName); - if (r !== 0) { - return r; - } - } - - // Default to sort by range - let elementARange = elementA.getRange(); - let elementBRange = elementB.getRange(); - return elementARange.startLineNumber - elementBRange.startLineNumber; - } -} - -registerEditorAction(QuickOutlineAction); From e983827722e95b7cfc0279884d37c55d5e69d8f0 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 16 Mar 2020 12:03:18 +0100 Subject: [PATCH 155/169] quick access - only switch to provider from help if it is not the default one --- src/vs/platform/quickinput/browser/helpQuickAccess.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/quickinput/browser/helpQuickAccess.ts b/src/vs/platform/quickinput/browser/helpQuickAccess.ts index 9c05d923e43..22e0879e475 100644 --- a/src/vs/platform/quickinput/browser/helpQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/helpQuickAccess.ts @@ -36,7 +36,7 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider { // name of a provider (e.g. `?term` for terminals) disposables.add(picker.onDidChangeValue(value => { const providerDescriptor = this.registry.getQuickAccessProvider(value.substr(HelpQuickAccessProvider.PREFIX.length)); - if (providerDescriptor && providerDescriptor.prefix !== HelpQuickAccessProvider.PREFIX) { + if (providerDescriptor && providerDescriptor.prefix && providerDescriptor.prefix !== HelpQuickAccessProvider.PREFIX) { this.quickInputService.quickAccess.show(providerDescriptor.prefix); } })); From 2243766d3effa5381bc4e3b378aae40c395f3ea1 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Sun, 15 Mar 2020 17:21:59 +0100 Subject: [PATCH 156/169] Show semantic token help --- src/vs/editor/common/model/textModel.ts | 4 + src/vs/editor/common/model/textModelEvents.ts | 1 + .../browser/codeEditor.contribution.ts | 1 + .../codeEditor/browser/semanticTokensHelp.ts | 86 +++++++++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.ts diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 5e32ac79f3c..9bc722c047c 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -1754,6 +1754,7 @@ export class TextModel extends Disposable implements model.ITextModel { if (ranges.length > 0) { this._emitModelTokensChangedEvent({ tokenizationSupportChanged: false, + semanticTokensApplied: false, ranges: ranges }); } @@ -1764,6 +1765,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._emitModelTokensChangedEvent({ tokenizationSupportChanged: false, + semanticTokensApplied: tokens !== null, ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }] }); } @@ -1778,6 +1780,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._tokens.flush(); this._emitModelTokensChangedEvent({ tokenizationSupportChanged: true, + semanticTokensApplied: false, ranges: [{ fromLineNumber: 1, toLineNumber: this._buffer.getLineCount() @@ -1790,6 +1793,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._emitModelTokensChangedEvent({ tokenizationSupportChanged: false, + semanticTokensApplied: true, ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }] }); } diff --git a/src/vs/editor/common/model/textModelEvents.ts b/src/vs/editor/common/model/textModelEvents.ts index 17f45db65ad..6511d02173f 100644 --- a/src/vs/editor/common/model/textModelEvents.ts +++ b/src/vs/editor/common/model/textModelEvents.ts @@ -87,6 +87,7 @@ export interface IModelDecorationsChangedEvent { */ export interface IModelTokensChangedEvent { readonly tokenizationSupportChanged: boolean; + readonly semanticTokensApplied: boolean; readonly ranges: { /** * The start of the range (inclusive) diff --git a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts index 30a3c94fdf1..b9992fde979 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts @@ -12,6 +12,7 @@ import './inspectEditorTokens/inspectEditorTokens'; import './quickaccess/gotoLineQuickAccess'; import './quickaccess/gotoSymbolQuickAccess'; import './saveParticipants'; +import './semanticTokensHelp'; import './toggleColumnSelection'; import './toggleMinimap'; import './toggleMultiCursorModifier'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.ts b/src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.ts new file mode 100644 index 00000000000..91858b6c440 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.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 nls from 'vs/nls'; +import * as path from 'vs/base/common/path'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { URI } from 'vs/base/common/uri'; +import { ITextModel } from 'vs/editor/common/model'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; + +/** + * Shows a message when semantic tokens are shown the first time. + */ +export class SemanticTokensHelp extends Disposable implements IEditorContribution { + + public static readonly ID = 'editor.contrib.semanticHighlightHelp'; + + constructor( + _editor: ICodeEditor, + @INotificationService _notificationService: INotificationService, + @IOpenerService _openerService: IOpenerService, + @IWorkbenchThemeService _themeService: IWorkbenchThemeService + ) { + super(); + + const toDispose = this._register(new DisposableStore()); + const localToDispose = toDispose.add(new DisposableStore()); + const installChangeTokenListener = (model: ITextModel) => { + localToDispose.add(model.onDidChangeTokens((e) => { + if (!e.semanticTokensApplied) { + return; + } + + toDispose.dispose(); // uninstall all listeners, makes sure the notification is only shown once per window + + const message = nls.localize( + { + key: 'semanticTokensHelp', + comment: [ + 'Variable 0 will be a file name.', + 'Variable 1 will be a theme name.' + ] + }, + "Semantic highlighting has been applied on top of the syntax highlighting of {0} as current the theme ({1}) has semantic highlighting enabled.", + path.basename(model.uri.path), _themeService.getColorTheme().label + ); + + _notificationService.prompt(Severity.Info, message, [ + { + label: nls.localize('learnMoreButton', "Learn More"), + run: () => { + const url = 'https://go.microsoft.com/fwlink/?linkid=852450'; + + _openerService.open(URI.parse(url)); + } + } + ], { neverShowAgain: { id: 'editor.contrib.semanticTokensHelp' } }); + })); + }; + + + const model = _editor.getModel(); + if (model !== null) { + installChangeTokenListener(model); + } + + toDispose.add(_editor.onDidChangeModel((e) => { + localToDispose.clear(); + + const model = _editor.getModel(); + if (!model) { + return; + } + installChangeTokenListener(model); + })); + } +} + +registerEditorContribution(SemanticTokensHelp.ID, SemanticTokensHelp); From 23759742a31392b67b460034e1d0e0e610e3b7bb Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 16 Mar 2020 13:50:43 +0100 Subject: [PATCH 157/169] semanticTokensApplied only when tokens are added --- src/vs/editor/common/model/textModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 9bc722c047c..f8d97c2b598 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -1793,7 +1793,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._emitModelTokensChangedEvent({ tokenizationSupportChanged: false, - semanticTokensApplied: true, + semanticTokensApplied: false, ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }] }); } From 66f4239e77d730611293aa2e2dba34d863f5ed49 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 16 Mar 2020 13:50:53 +0100 Subject: [PATCH 158/169] improve message --- .../contrib/codeEditor/browser/semanticTokensHelp.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.ts b/src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.ts index 91858b6c440..4d260368d25 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.ts @@ -48,7 +48,7 @@ export class SemanticTokensHelp extends Disposable implements IEditorContributio 'Variable 1 will be a theme name.' ] }, - "Semantic highlighting has been applied on top of the syntax highlighting of {0} as current the theme ({1}) has semantic highlighting enabled.", + "Semantic highlighting has been applied to '{0}' as the theme '{1}' has semantic highlighting enabled.", path.basename(model.uri.path), _themeService.getColorTheme().label ); @@ -56,7 +56,7 @@ export class SemanticTokensHelp extends Disposable implements IEditorContributio { label: nls.localize('learnMoreButton', "Learn More"), run: () => { - const url = 'https://go.microsoft.com/fwlink/?linkid=852450'; + const url = 'https://go.microsoft.com/fwlink/?linkid=2122588'; _openerService.open(URI.parse(url)); } From 33f8b720d18fff6364541126432c9ba35cf04462 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 16 Mar 2020 14:11:26 +0100 Subject: [PATCH 159/169] Fix tree shaking --- build/lib/treeshaking.js | 2 +- build/lib/treeshaking.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index bc739120444..bf16c0fa839 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -333,7 +333,7 @@ function markNodes(languageService, options) { } setColor(node, 2 /* Black */); black_queue.push(node); - if (options.shakeLevel === 2 /* ClassMembers */ && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertySignature(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node))) { + if (options.shakeLevel === 2 /* ClassMembers */ && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertySignature(node) || ts.isPropertyDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node))) { const references = languageService.getReferencesAtPosition(node.getSourceFile().fileName, node.name.pos + node.name.getLeadingTriviaWidth()); if (references) { for (let i = 0, len = references.length; i < len; i++) { diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index c1551e8143d..16d9ab6e3e9 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -436,7 +436,7 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt setColor(node, NodeColor.Black); black_queue.push(node); - if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertySignature(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node))) { + if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertySignature(node) || ts.isPropertyDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node))) { const references = languageService.getReferencesAtPosition(node.getSourceFile().fileName, node.name.pos + node.name.getLeadingTriviaWidth()); if (references) { for (let i = 0, len = references.length; i < len; i++) { From c11c47317d672f80aba0a6d3b885fe5c4dac929c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 14 Mar 2020 11:06:58 -0700 Subject: [PATCH 160/169] allowMenubarMnemonics -> allowMnemonics --- .../contrib/terminal/browser/terminal.contribution.ts | 4 ++-- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 2 +- src/vs/workbench/contrib/terminal/common/terminal.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 3eb2b345f30..8b354bf3d01 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -294,8 +294,8 @@ configurationRegistry.registerConfiguration({ type: 'boolean', default: true }, - 'terminal.integrated.allowMenubarMnemonics': { - markdownDescription: nls.localize('terminal.integrated.allowMenubarMnemonics', "Whether to allow menubar mnemonics (eg. alt+f) to trigger the open the menubar. Note that this will cause all alt keystrokes will skip the shell when true."), + 'terminal.integrated.allowMnemonics': { + markdownDescription: nls.localize('terminal.integrated.allowMnemonics', "Whether to allow menubar mnemonics (eg. alt+f) to trigger the open the menubar. Note that this will cause all alt keystrokes will skip the shell when true."), type: 'boolean', default: false }, diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 7a212f4369a..6b4d0453bc1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -621,7 +621,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } // Skip processing by xterm.js of keyboard events that match menu bar mnemonics - if (this._configHelper.config.allowMenubarMnemonics && event.altKey) { + if (this._configHelper.config.allowMnemonics && event.altKey) { return false; } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 4b9abc01604..0d2d6cc069d 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -107,7 +107,7 @@ export interface ITerminalConfiguration { scrollback: number; commandsToSkipShell: string[]; allowChords: boolean; - allowMenubarMnemonics: boolean; + allowMnemonics: boolean; cwd: string; confirmOnExit: boolean; enableBell: boolean; From d79cf8ada5da2336186c02e70184d8bede490c74 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 16 Mar 2020 15:37:48 +0100 Subject: [PATCH 161/169] fixes #69768 --- .../contrib/debug/browser/debugActions.ts | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index bc94f6616fb..df048219fac 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -393,21 +393,18 @@ export class CopyValueAction extends Action { async run(): Promise { const stackFrame = this.debugService.getViewModel().focusedStackFrame; const session = this.debugService.getViewModel().focusedSession; - - if (typeof this.value === 'string') { - return this.clipboardService.writeText(this.value); + if (!stackFrame || !session) { + return; } - if (stackFrame && session && this.value.evaluateName) { - try { - const context = session.capabilities.supportsClipboardContext ? 'clipboard' : this.context; - const evaluation = await session.evaluate(this.value.evaluateName, stackFrame.frameId, context); - this.clipboardService.writeText(evaluation.body.result); - } catch (e) { - this.clipboardService.writeText(this.value.value); - } - } else { - this.clipboardService.writeText(this.value.value); + const context = session.capabilities.supportsClipboardContext ? 'clipboard' : this.context; + const toEvaluate = typeof this.value === 'string' ? this.value : this.value.evaluateName || this.value.value; + + try { + const evaluation = await session.evaluate(toEvaluate, stackFrame.frameId, context); + this.clipboardService.writeText(evaluation.body.result); + } catch (e) { + this.clipboardService.writeText(typeof this.value === 'string' ? this.value : this.value.value); } } } From 08097a230e4e0b4075a3353e0bc39761fa6d7558 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 16 Mar 2020 15:55:39 +0100 Subject: [PATCH 162/169] fixes #92719 --- src/vs/base/browser/ui/list/list.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index 5f86ea5989b..bb35d213a06 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -65,7 +65,7 @@ export interface IIdentityProvider { export enum ListAriaRootRole { /** default list structure role */ - LIST = 'list', + LIST = 'listbox', /** default tree structure role */ TREE = 'tree', From 733f9f8c5a552f07122a0a83e4f5976b021e2f89 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Mon, 16 Mar 2020 08:12:54 -0700 Subject: [PATCH 163/169] Properly no-op Actions --- .github/workflows/commands.yml | 40 +++++++++---------- .github/workflows/copycat.yml | 50 ++++++++++++------------ .github/workflows/needs-version-info.yml | 38 +++++++++--------- 3 files changed, 64 insertions(+), 64 deletions(-) diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index dfe4dd699ef..35974e43535 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -1,21 +1,21 @@ -# name: Commands -# on: -# issue_comment: -# types: [created] -# issues: -# types: [labeled] +name: Commands +on: + issue_comment: + types: [created] + issues: + types: [labeled] -# jobs: -# main: -# runs-on: ubuntu-latest -# steps: -# - name: Checkout Actions -# uses: actions/checkout@v2 -# with: -# repository: 'JacksonKearl/vscode-triage-github-actions' -# ref: v2 -# - name: Run Commands -# uses: ./commands -# with: -# token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} -# config-path: commands +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'JacksonKearl/vscode-triage-github-actions' + ref: v2 + # - name: Run Commands + # uses: ./commands + # with: + # token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + # config-path: commands diff --git a/.github/workflows/copycat.yml b/.github/workflows/copycat.yml index 4140255d4d2..34fb291329a 100644 --- a/.github/workflows/copycat.yml +++ b/.github/workflows/copycat.yml @@ -1,26 +1,26 @@ -# name: CopyCat -# on: -# issues: -# types: [opened] +name: CopyCat +on: + issues: + types: [opened] -# jobs: -# main: -# runs-on: ubuntu-latest -# steps: -# - name: Checkout Actions -# uses: actions/checkout@v2 -# with: -# repository: 'JacksonKearl/vscode-triage-github-actions' -# ref: v2 -# - name: Run CopyCat (JacksonKearl/testissues) -# uses: ./copycat -# with: -# token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} -# owner: JacksonKearl -# repo: testissues -# - name: Run CopyCat (chrmarti/testissues) -# uses: ./copycat -# with: -# token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} -# owner: chrmarti -# repo: testissues +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'JacksonKearl/vscode-triage-github-actions' + ref: v2 + # - name: Run CopyCat (JacksonKearl/testissues) + # uses: ./copycat + # with: + # token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + # owner: JacksonKearl + # repo: testissues + # - name: Run CopyCat (chrmarti/testissues) + # uses: ./copycat + # with: + # token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + # owner: chrmarti + # repo: testissues diff --git a/.github/workflows/needs-version-info.yml b/.github/workflows/needs-version-info.yml index d8fa692d96a..3b62b3c6947 100644 --- a/.github/workflows/needs-version-info.yml +++ b/.github/workflows/needs-version-info.yml @@ -1,20 +1,20 @@ -# name: Needs Version Info -# on: -# issues: -# types: [opened] +name: Needs Version Info +on: + issues: + types: [opened] -# jobs: -# main: -# runs-on: ubuntu-latest -# steps: -# - name: Checkout Actions -# uses: actions/checkout@v2 -# with: -# repository: 'JacksonKearl/vscode-triage-github-actions' -# ref: v2 -# - name: Run Needs Version Info -# uses: ./needs-more-info -# with: -# token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} -# matcher: '\b(\d\.\d{2,3}\.\d|insiders?|1\.\d\d\d?)\b' -# label: ~needs version info +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'JacksonKearl/vscode-triage-github-actions' + ref: v2 + # - name: Run Needs Version Info + # uses: ./needs-more-info + # with: + # token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + # matcher: '\b(\d\.\d{2,3}\.\d|insiders?|1\.\d\d\d?)\b' + # label: ~needs version info From ca4ceeb87d4ff935c52a7af0671ed9779657e7bd Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Mar 2020 16:29:30 +0100 Subject: [PATCH 164/169] Fix #90074 --- .../common/userDataSyncStoreService.ts | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 68734c4975e..344fca00b30 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -13,12 +13,19 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; import { IProductService } from 'vs/platform/product/common/productService'; import { URI } from 'vs/base/common/uri'; +import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { assign } from 'vs/base/common/objects'; + export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService { _serviceBrand: any; readonly userDataSyncStore: IUserDataSyncStore | undefined; + private readonly commonHeadersPromise: Promise<{ [key: string]: string; }>; constructor( @IProductService productService: IProductService, @@ -26,9 +33,17 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn @IRequestService private readonly requestService: IRequestService, @IAuthenticationTokenService private readonly authTokenService: IAuthenticationTokenService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + @IEnvironmentService environmentService: IEnvironmentService, + @IFileService fileService: IFileService, + @IStorageService storageService: IStorageService, ) { super(); this.userDataSyncStore = getUserDataSyncStore(productService, configurationService); + this.commonHeadersPromise = getServiceMachineId(environmentService, fileService, storageService) + .then(uuid => ({ + 'X-Sync-Client-Id': productService.version, + 'X-Sync-Machine-Id': uuid + })); } async getAllRefs(resource: SyncResource): Promise { @@ -174,8 +189,11 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn if (!authToken) { throw new UserDataSyncStoreError('No Auth Token Available', UserDataSyncErrorCode.Unauthorized, source); } - options.headers = options.headers || {}; - options.headers['authorization'] = `Bearer ${authToken}`; + + const commonHeaders = await this.commonHeadersPromise; + options.headers = assign(options.headers || {}, commonHeaders, { + 'authorization': `Bearer ${authToken}`, + }); this.logService.trace('Sending request to server', { url: options.url, type: options.type, headers: { ...options.headers, ...{ authorization: undefined } } }); From c160edc4d10d5dff1205e01d36120467cf344ffc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2020 10:16:13 -0700 Subject: [PATCH 165/169] Bump acorn from 5.7.1 to 5.7.4 (#92711) Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.1 to 5.7.4. - [Release notes](https://github.com/acornjs/acorn/releases) - [Commits](https://github.com/acornjs/acorn/compare/5.7.1...5.7.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daniel Imms --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 49f82d4de93..f7743cbccd1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -574,9 +574,9 @@ acorn-jsx@^5.1.0: integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw== acorn@^5.0.0, acorn@^5.6.2: - version "5.7.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8" - integrity sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ== + version "5.7.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" + integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== acorn@^6.0.2: version "6.0.7" From 83c5b1c9bdf565a596323d0301ef313491feb994 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2020 10:16:40 -0700 Subject: [PATCH 166/169] Bump acorn from 6.3.0 to 6.4.1 in /extensions/markdown-language-features (#92742) Bumps [acorn](https://github.com/acornjs/acorn) from 6.3.0 to 6.4.1. - [Release notes](https://github.com/acornjs/acorn/releases) - [Commits](https://github.com/acornjs/acorn/compare/6.3.0...6.4.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- extensions/markdown-language-features/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index 5c7a22c74f0..23cd1744005 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -191,9 +191,9 @@ abbrev@1: integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== acorn@^6.2.1: - version "6.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e" - integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA== + version "6.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" + integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== ajv-errors@^1.0.0: version "1.0.1" From 7fcc44cd1976a83d3e72fe55ed1b3a791bb5e06b Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Mon, 16 Mar 2020 15:50:19 -0400 Subject: [PATCH 167/169] Updates view progress proposal - #92421 --- src/vs/platform/progress/common/progress.ts | 3 +-- src/vs/vscode.d.ts | 2 +- src/vs/vscode.proposed.d.ts | 19 ------------------- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHostProgress.ts | 4 ++-- .../api/common/extHostTypeConverters.ts | 7 +++++-- src/vs/workbench/api/common/extHostTypes.ts | 3 +-- 7 files changed, 11 insertions(+), 29 deletions(-) diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index 5fa930eabfb..b6ff5e5057b 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -45,8 +45,7 @@ export const enum ProgressLocation { Extensions = 5, Window = 10, Notification = 15, - Dialog = 20, - View = 25 + Dialog = 20 } export interface IProgressOptions { diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 4b26a5e6ebb..c5b6fe3f2dd 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -7932,7 +7932,7 @@ declare module 'vscode' { /** * The location at which progress should show. */ - location: ProgressLocation; + location: ProgressLocation | { viewId: string }; /** * A human-readable string which will be used to describe the diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 426f7fc6f42..7a413553ad7 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1891,23 +1891,4 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/92421 - - export enum ProgressLocation { - /** - * Show progress for a view, as progress bar inside the view (when visible), - * and as an overlay on the activity bar icon. Doesn't support cancellation or discrete progress. - */ - View = 25, - } - - export interface ProgressOptions { - /** - * The target view identifier for showing progress when using [ProgressLocation.View](#ProgressLocation.View). - */ - viewId?: string - } - - //#endregion - } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index d8d58647f09..89663f4f22a 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -540,7 +540,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostProgress.withProgress(extension, { location: extHostTypes.ProgressLocation.SourceControl }, (progress, token) => task({ report(n: number) { /*noop*/ } })); }, withProgress(options: vscode.ProgressOptions, task: (progress: vscode.Progress<{ message?: string; worked?: number }>, token: vscode.CancellationToken) => Thenable) { - if (options.location === extHostTypes.ProgressLocation.View) { + if (typeof options.location !== 'number') { checkProposedApiEnabled(extension); } return extHostProgress.withProgress(extension, options, task); diff --git a/src/vs/workbench/api/common/extHostProgress.ts b/src/vs/workbench/api/common/extHostProgress.ts index ef5e614e5b9..5db3edf0862 100644 --- a/src/vs/workbench/api/common/extHostProgress.ts +++ b/src/vs/workbench/api/common/extHostProgress.ts @@ -24,10 +24,10 @@ export class ExtHostProgress implements ExtHostProgressShape { withProgress(extension: IExtensionDescription, options: ProgressOptions, task: (progress: Progress, token: CancellationToken) => Thenable): Thenable { const handle = this._handles++; - const { title, location, cancellable, viewId } = options; + const { title, location, cancellable } = options; const source = localize('extensionSource', "{0} (Extension)", extension.displayName || extension.name); - this._proxy.$startProgress(handle, { location: ProgressLocation.from(location, viewId), title, source, cancellable }, extension); + this._proxy.$startProgress(handle, { location: ProgressLocation.from(location), title, source, cancellable }, extension); return this._withProgress(handle, task, !!cancellable); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index e443f9335d7..0a0034e0b44 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1093,12 +1093,15 @@ export namespace EndOfLine { } export namespace ProgressLocation { - export function from(loc: vscode.ProgressLocation, viewId?: string): MainProgressLocation | string { + export function from(loc: vscode.ProgressLocation | { viewId: string }): MainProgressLocation | string { + if (typeof loc === 'string') { + return loc; + } + switch (loc) { case types.ProgressLocation.SourceControl: return MainProgressLocation.Scm; case types.ProgressLocation.Window: return MainProgressLocation.Window; case types.ProgressLocation.Notification: return MainProgressLocation.Notification; - case types.ProgressLocation.View: return viewId ?? ''; } throw new Error(`Unknown 'ProgressLocation'`); } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index e89ded47b5f..1dac637a08f 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2082,8 +2082,7 @@ export class Task implements vscode.Task2 { export enum ProgressLocation { SourceControl = 1, Window = 10, - Notification = 15, - View = 25 + Notification = 15 } @es5ClassCompat From 8be261b49d8c3789f7fa51af22edb661cd9e8fea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2020 19:54:33 +0000 Subject: [PATCH 168/169] Bump minimist from 1.2.0 to 1.2.2 in /build Bumps [minimist](https://github.com/substack/minimist) from 1.2.0 to 1.2.2. - [Release notes](https://github.com/substack/minimist/releases) - [Commits](https://github.com/substack/minimist/compare/1.2.0...1.2.2) Signed-off-by: dependabot[bot] --- build/package.json | 2 +- build/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/package.json b/build/package.json index 36a8b905ed8..dbaed69b320 100644 --- a/build/package.json +++ b/build/package.json @@ -40,7 +40,7 @@ "iconv-lite": "0.4.23", "mime": "^1.3.4", "minimatch": "3.0.4", - "minimist": "^1.2.0", + "minimist": "^1.2.2", "request": "^2.85.0", "terser": "4.3.8", "typescript": "^3.9.0-dev.20200313", diff --git a/build/yarn.lock b/build/yarn.lock index 26886e94794..9b0fe8e7861 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -1780,10 +1780,10 @@ minimatch@3.0.4, minimatch@^3.0.3, minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@^1.1.0, minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= +minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.2.tgz#b00a00230a1108c48c169e69a291aafda3aacd63" + integrity sha512-rIqbOrKb8GJmx/5bc2M0QchhUouMXSpd1RTclXsB41JdL+VtnojfaJR+h7F9k18/4kHUsBFgk80Uk+q569vjPA== minimist@~0.0.1: version "0.0.10" From 379e0e3971346f166a95fc081e82b29d15d5a982 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 11 Mar 2020 09:19:04 +0100 Subject: [PATCH 169/169] progress - remove options --- src/vs/workbench/browser/parts/compositePart.ts | 2 +- .../workbench/browser/parts/views/viewPaneContainer.ts | 2 +- .../services/progress/browser/progressIndicator.ts | 9 +++------ .../progress/test/browser/progressIndicator.test.ts | 2 +- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 2a72d6fdb4d..27db5400aa3 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -169,7 +169,7 @@ export abstract class CompositePart extends Part { // Instantiate composite from registry otherwise const compositeDescriptor = this.registry.getComposite(id); if (compositeDescriptor) { - const compositeProgressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), compositeDescriptor.id, !!isActive, undefined); + const compositeProgressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), compositeDescriptor.id, !!isActive); const compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection( [IEditorProgressService, compositeProgressIndicator] // provide the editor progress service for any editors instantiated within the composite )); diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 963e2d1ed1a..8cc4285aa63 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -343,7 +343,7 @@ export abstract class ViewPane extends Pane implements IView { } if (this.progressIndicator === undefined) { - this.progressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), this.id, this.isVisible(), { exclusiveProgressBar: true }); + this.progressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), this.id, this.isVisible()); } return this.progressIndicator; } diff --git a/src/vs/workbench/services/progress/browser/progressIndicator.ts b/src/vs/workbench/services/progress/browser/progressIndicator.ts index 468ddae9308..ebbfd5bc0e4 100644 --- a/src/vs/workbench/services/progress/browser/progressIndicator.ts +++ b/src/vs/workbench/services/progress/browser/progressIndicator.ts @@ -198,7 +198,6 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr progressbar: ProgressBar, scopeId: string, isActive: boolean, - private readonly options: { exclusiveProgressBar?: boolean } | undefined, @IViewletService viewletService: IViewletService, @IPanelService panelService: IPanelService, @IViewsService viewsService: IViewsService @@ -212,9 +211,7 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr onScopeDeactivated(): void { this.isActive = false; - if (this.options?.exclusiveProgressBar) { - this.progressbar.stop().hide(); - } + this.progressbar.stop().hide(); } onScopeActivated(): void { @@ -314,7 +311,7 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr done: () => { this.progressState = ProgressIndicatorState.Done; - if (this.isActive || this.options?.exclusiveProgressBar) { + if (this.isActive) { this.progressbar.stop().hide(); } } @@ -345,7 +342,7 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr // The while promise is either null or equal the promise we last hooked on this.progressState = ProgressIndicatorState.None; - if (this.isActive || this.options?.exclusiveProgressBar) { + if (this.isActive) { this.progressbar.stop().hide(); } } diff --git a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts index 8b886270626..6893e8114cd 100644 --- a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts +++ b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts @@ -129,7 +129,7 @@ suite('Progress Indicator', () => { let viewletService = new TestViewletService(); let panelService = new TestPanelService(); let viewsService = new TestViewsService(); - let service = new CompositeProgressIndicator((testProgressBar), 'test.scopeId', true, undefined, viewletService, panelService, viewsService); + let service = new CompositeProgressIndicator((testProgressBar), 'test.scopeId', true, viewletService, panelService, viewsService); // Active: Show (Infinite) let fn = service.show(true);