VS Code merge to df8fe74bd55313de0dd2303bc47a4aab0ca56b0e (#17979)

* Merge from vscode 504f934659740e9d41501cad9f162b54d7745ad9

* delete unused folders

* distro

* Bump build node version

* update chokidar

* FIx hygiene errors

* distro

* Fix extension lint issues

* Remove strict-vscode

* Add copyright header exemptions

* Bump vscode-extension-telemetry to fix webpacking issue with zone.js

* distro

* Fix failing tests (revert marked.js back to current one until we decide to update)

* Skip searchmodel test

* Fix mac build

* temp debug script loading

* Try disabling coverage

* log error too

* Revert "log error too"

This reverts commit af0183e5d4ab458fdf44b88fbfab9908d090526f.

* Revert "temp debug script loading"

This reverts commit 3d687d541c76db2c5b55626c78ae448d3c25089c.

* Add comments explaining coverage disabling

* Fix ansi_up loading issue

* Merge latest from ads

* Use newer option

* Fix compile

* add debug logging warn

* Always log stack

* log more

* undo debug

* Update to use correct base path (+cleanup)

* distro

* fix compile errors

* Remove strict-vscode

* Fix sql editors not showing

* Show db dropdown input & fix styling

* Fix more info in gallery

* Fix gallery asset requests

* Delete unused workflow

* Fix tapable resolutions for smoke test compile error

* Fix smoke compile

* Disable crash reporting

* Disable interactive

Co-authored-by: ADS Merger <karlb@microsoft.com>
This commit is contained in:
Charles Gagnon
2022-01-06 09:06:56 -08:00
committed by GitHub
parent fd2736b6a6
commit 2bc6a0cd01
2099 changed files with 79520 additions and 43813 deletions

View File

@@ -3,9 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const IAccessibilityService = createDecorator<IAccessibilityService>('accessibilityService');

View File

@@ -3,11 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IAccessibilityService, AccessibilitySupport, CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility';
import { Event, Emitter } from 'vs/base/common/event';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { AccessibilitySupport, CONTEXT_ACCESSIBILITY_MODE_ENABLED, IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
export class AccessibilityService extends Disposable implements IAccessibilityService {
declare readonly _serviceBrand: undefined;

View File

@@ -10,12 +10,17 @@ import { ActionViewItem, BaseActionViewItem } from 'vs/base/browser/ui/actionbar
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
import { IAction } from 'vs/base/common/actions';
import { Event } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { MenuItemAction } from 'vs/platform/actions/common/actions';
import { KeyCode, ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { MenuItemAction } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INotificationService } from 'vs/platform/notification/common/notification';
export interface IDropdownWithPrimaryActionViewItemOptions {
getKeyBinding?: (action: IAction) => ResolvedKeybinding | undefined;
}
export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem {
private _primaryAction: ActionViewItem;
private _dropdown: DropdownMenuActionViewItem;
@@ -32,14 +37,17 @@ export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem {
dropdownMenuActions: IAction[],
className: string,
private readonly _contextMenuProvider: IContextMenuProvider,
_keybindingService: IKeybindingService,
_notificationService: INotificationService
private readonly _options: IDropdownWithPrimaryActionViewItemOptions | undefined,
@IKeybindingService _keybindingService: IKeybindingService,
@INotificationService _notificationService: INotificationService,
@IContextKeyService _contextKeyService: IContextKeyService
) {
super(null, primaryAction);
this._primaryAction = new MenuEntryActionViewItem(primaryAction, _keybindingService, _notificationService);
this._primaryAction = new MenuEntryActionViewItem(primaryAction, undefined, _keybindingService, _notificationService, _contextKeyService);
this._dropdown = new DropdownMenuActionViewItem(dropdownAction, dropdownMenuActions, this._contextMenuProvider, {
menuAsChild: true,
classNames: ['codicon', 'codicon-chevron-down']
classNames: ['codicon', 'codicon-chevron-down'],
keybindingProvider: this._options?.getKeyBinding
});
}

View File

@@ -19,3 +19,46 @@
.hc-black .monaco-action-bar .action-item.menu-entry .action-label {
background-image: var(--menu-entry-icon-dark);
}
.monaco-dropdown-with-default {
display: flex !important;
flex-direction: row;
border-radius: 5px;
}
.monaco-dropdown-with-default > .action-container > .action-label {
margin-right: 0;
}
.monaco-dropdown-with-default > .action-container.menu-entry > .action-label.icon {
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-position: 50%;
background-size: 16px;
}
.monaco-dropdown-with-default > .action-container.menu-entry > .action-label {
background-image: var(--menu-entry-icon-light);
}
.vs-dark .monaco-dropdown-with-default > .action-container.menu-entry > .action-label,
.hc-black .monaco-dropdown-with-default > .action-container.menu-entry > .action-label {
background-image: var(--menu-entry-icon-dark);
}
.monaco-dropdown-with-default > .dropdown-action-container > .monaco-dropdown > .dropdown-label .codicon[class*='codicon-'] {
font-size: 12px;
padding-left: 0px;
padding-right: 0px;
line-height: 16px;
margin-left: -3px;
}
.monaco-dropdown-with-default > .dropdown-action-container > .monaco-dropdown > .dropdown-label > .action-label {
display: block;
background-size: 16px;
background-position: center center;
background-repeat: no-repeat;
}

View File

@@ -3,22 +3,26 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./menuEntryActionViewItem';
import { asCSSUrl, ModifierKeyEmitter } from 'vs/base/browser/dom';
import { domEvent } from 'vs/base/browser/event';
import { IAction, Separator, SubmenuAction } from 'vs/base/common/actions';
import { IDisposable, toDisposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
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 { $, addDisposableListener, append, asCSSUrl, EventType, ModifierKeyEmitter, prepend } from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ActionViewItem, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { DropdownMenuActionViewItem, IDropdownMenuActionViewItemOptions } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
import { ActionRunner, IAction, IRunEvent, Separator, SubmenuAction } from 'vs/base/common/actions';
import { Event } from 'vs/base/common/event';
import { UILabelProvider } from 'vs/base/common/keybindingLabels';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
import { isWindows, isLinux, OS } from 'vs/base/common/platform';
import { KeyCode } from 'vs/base/common/keyCodes';
import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { isLinux, isWindows, OS } from 'vs/base/common/platform';
import 'vs/css!./menuEntryActionViewItem';
import { localize } from 'vs/nls';
import { ICommandAction, Icon, IMenu, IMenuActionOptions, IMenuService, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, primaryGroup?: string): IDisposable {
const groups = menu.getActions(options);
@@ -115,6 +119,10 @@ export function fillInActions( // {{SQL CARBON EDIT}} add export modifier
}
}
export interface IMenuEntryActionViewItemOptions {
draggable?: boolean;
}
export class MenuEntryActionViewItem extends ActionViewItem {
private _wantsAltCommand: boolean = false;
@@ -123,10 +131,12 @@ export class MenuEntryActionViewItem extends ActionViewItem {
constructor(
_action: MenuItemAction,
options: IMenuEntryActionViewItemOptions | undefined,
@IKeybindingService protected readonly _keybindingService: IKeybindingService,
@INotificationService protected _notificationService: INotificationService
@INotificationService protected _notificationService: INotificationService,
@IContextKeyService protected _contextKeyService: IContextKeyService
) {
super(undefined, _action, { icon: !!(_action.class || _action.item.icon), label: !_action.class && !_action.item.icon });
super(undefined, _action, { icon: !!(_action.class || _action.item.icon), label: !_action.class && !_action.item.icon, draggable: options?.draggable });
this._altKey = ModifierKeyEmitter.getInstance();
}
@@ -176,12 +186,12 @@ export class MenuEntryActionViewItem extends ActionViewItem {
}));
}
this._register(domEvent(container, 'mouseleave')(_ => {
this._register(addDisposableListener(container, 'mouseleave', _ => {
mouseOver = false;
updateAltState();
}));
this._register(domEvent(container, 'mouseenter')(e => {
this._register(addDisposableListener(container, 'mouseenter', _ => {
mouseOver = true;
updateAltState();
}));
@@ -195,7 +205,7 @@ export class MenuEntryActionViewItem extends ActionViewItem {
override updateTooltip(): void {
if (this.label) {
const keybinding = this._keybindingService.lookupKeybinding(this._commandAction.id);
const keybinding = this._keybindingService.lookupKeybinding(this._commandAction.id, this._contextKeyService);
const keybindingLabel = keybinding && keybinding.getLabel();
const tooltip = this._commandAction.tooltip || this._commandAction.label;
@@ -204,7 +214,7 @@ export class MenuEntryActionViewItem extends ActionViewItem {
: tooltip;
if (!this._wantsAltCommand && this._menuItemAction.alt) {
const altTooltip = this._menuItemAction.alt.tooltip || this._menuItemAction.alt.label;
const altKeybinding = this._keybindingService.lookupKeybinding(this._menuItemAction.alt.id);
const altKeybinding = this._keybindingService.lookupKeybinding(this._menuItemAction.alt.id, this._contextKeyService);
const altKeybindingLabel = altKeybinding && altKeybinding.getLabel();
const altTitleSection = altKeybindingLabel
? localize('titleAndKb', "{0} ({1})", altTooltip, altKeybindingLabel)
@@ -271,12 +281,15 @@ export class SubmenuEntryActionViewItem extends DropdownMenuActionViewItem {
constructor(
action: SubmenuItemAction,
options: IDropdownMenuActionViewItemOptions | undefined,
@IContextMenuService contextMenuService: IContextMenuService
) {
super(action, { getActions: () => action.actions }, contextMenuService, {
menuAsChild: true,
classNames: ThemeIcon.isThemeIcon(action.item.icon) ? ThemeIcon.asClassName(action.item.icon) : undefined,
const dropdownOptions = Object.assign({}, options ?? Object.create(null), {
menuAsChild: options?.menuAsChild ?? true,
classNames: options?.classNames ?? (ThemeIcon.isThemeIcon(action.item.icon) ? ThemeIcon.asClassName(action.item.icon) : undefined),
});
super(action, { getActions: () => action.actions }, contextMenuService, dropdownOptions);
}
override render(container: HTMLElement): void {
@@ -297,14 +310,151 @@ export class SubmenuEntryActionViewItem extends DropdownMenuActionViewItem {
}
}
class DropdownWithDefaultActionViewItem extends BaseActionViewItem {
private _defaultAction: ActionViewItem;
private _dropdown: DropdownMenuActionViewItem;
private _container: HTMLElement | null = null;
private _storageKey: string;
get onDidChangeDropdownVisibility(): Event<boolean> {
return this._dropdown.onDidChangeVisibility;
}
constructor(
submenuAction: SubmenuItemAction,
options: IDropdownMenuActionViewItemOptions | undefined,
@IKeybindingService protected readonly _keybindingService: IKeybindingService,
@INotificationService protected _notificationService: INotificationService,
@IContextMenuService protected _contextMenuService: IContextMenuService,
@IMenuService protected _menuService: IMenuService,
@IInstantiationService protected _instaService: IInstantiationService,
@IStorageService protected _storageService: IStorageService
) {
super(null, submenuAction);
this._storageKey = `${submenuAction.item.submenu._debugName}_lastActionId`;
// determine default action
let defaultAction: IAction | undefined;
let defaultActionId = _storageService.get(this._storageKey, StorageScope.WORKSPACE);
if (defaultActionId) {
defaultAction = submenuAction.actions.find(a => defaultActionId === a.id);
}
if (!defaultAction) {
defaultAction = submenuAction.actions[0];
}
this._defaultAction = this._instaService.createInstance(MenuEntryActionViewItem, <MenuItemAction>defaultAction, undefined);
const dropdownOptions = Object.assign({}, options ?? Object.create(null), {
menuAsChild: options?.menuAsChild ?? true,
classNames: options?.classNames ?? ['codicon', 'codicon-chevron-down'],
actionRunner: options?.actionRunner ?? new ActionRunner()
});
this._dropdown = new DropdownMenuActionViewItem(submenuAction, submenuAction.actions, this._contextMenuService, dropdownOptions);
this._dropdown.actionRunner.onDidRun((e: IRunEvent) => {
if (e.action instanceof MenuItemAction) {
this.update(e.action);
}
});
}
private update(lastAction: MenuItemAction): void {
this._storageService.store(this._storageKey, lastAction.id, StorageScope.WORKSPACE, StorageTarget.USER);
this._defaultAction.dispose();
this._defaultAction = this._instaService.createInstance(MenuEntryActionViewItem, lastAction, undefined);
this._defaultAction.actionRunner = new class extends ActionRunner {
override async runAction(action: IAction, context?: unknown): Promise<void> {
await action.run(undefined);
}
}();
if (this._container) {
this._defaultAction.render(prepend(this._container, $('.action-container')));
}
}
override setActionContext(newContext: unknown): void {
super.setActionContext(newContext);
this._defaultAction.setActionContext(newContext);
this._dropdown.setActionContext(newContext);
}
override render(container: HTMLElement): void {
this._container = container;
super.render(this._container);
this._container.classList.add('monaco-dropdown-with-default');
const primaryContainer = $('.action-container');
this._defaultAction.render(append(this._container, primaryContainer));
this._register(addDisposableListener(primaryContainer, EventType.KEY_DOWN, (e: KeyboardEvent) => {
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.RightArrow)) {
this._defaultAction.element!.tabIndex = -1;
this._dropdown.focus();
event.stopPropagation();
}
}));
const dropdownContainer = $('.dropdown-action-container');
this._dropdown.render(append(this._container, dropdownContainer));
this._register(addDisposableListener(dropdownContainer, EventType.KEY_DOWN, (e: KeyboardEvent) => {
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.LeftArrow)) {
this._defaultAction.element!.tabIndex = 0;
this._dropdown.setFocusable(false);
this._defaultAction.element?.focus();
event.stopPropagation();
}
}));
}
override focus(fromRight?: boolean): void {
if (fromRight) {
this._dropdown.focus();
} else {
this._defaultAction.element!.tabIndex = 0;
this._defaultAction.element!.focus();
}
}
override blur(): void {
this._defaultAction.element!.tabIndex = -1;
this._dropdown.blur();
this._container!.blur();
}
override setFocusable(focusable: boolean): void {
if (focusable) {
this._defaultAction.element!.tabIndex = 0;
} else {
this._defaultAction.element!.tabIndex = -1;
this._dropdown.setFocusable(false);
}
}
override dispose() {
this._defaultAction.dispose();
this._dropdown.dispose();
super.dispose();
}
}
/**
* Creates action view items for menu actions or submenu actions.
*/
export function createActionViewItem(instaService: IInstantiationService, action: IAction): undefined | MenuEntryActionViewItem | SubmenuEntryActionViewItem {
export function createActionViewItem(instaService: IInstantiationService, action: IAction, options?: IDropdownMenuActionViewItemOptions): undefined | MenuEntryActionViewItem | SubmenuEntryActionViewItem | BaseActionViewItem {
if (action instanceof MenuItemAction) {
return instaService.createInstance(MenuEntryActionViewItem, action);
return instaService.createInstance(MenuEntryActionViewItem, action, undefined);
} else if (action instanceof SubmenuItemAction) {
return instaService.createInstance(SubmenuEntryActionViewItem, action);
if (action.item.rememberDefaultAction) {
return instaService.createInstance(DropdownWithDefaultActionViewItem, action, options);
} else {
return instaService.createInstance(SubmenuEntryActionViewItem, action, options);
}
} else {
return undefined;
}

View File

@@ -4,19 +4,19 @@
*--------------------------------------------------------------------------------------------*/
import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions';
import { SyncDescriptor0, createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IConstructorSignature2, createDecorator, BrandedService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindings, KeybindingsRegistry, IKeybindingRule } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ContextKeyExpr, IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
import { ICommandService, CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
import { IDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { UriDto } from 'vs/base/common/types';
import { Iterable } from 'vs/base/common/iterator';
import { LinkedList } from 'vs/base/common/linkedList';
import { CSSIcon } from 'vs/base/common/codicons';
import { Emitter, Event } from 'vs/base/common/event';
import { Iterable } from 'vs/base/common/iterator';
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { LinkedList } from 'vs/base/common/linkedList';
import { UriDto } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { CommandsRegistry, ICommandHandlerDescription, ICommandService } from 'vs/platform/commands/common/commands';
import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { SyncDescriptor, SyncDescriptor0 } from 'vs/platform/instantiation/common/descriptors';
import { BrandedService, createDecorator, IConstructorSignature2, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingRule, IKeybindings, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
export interface ILocalizedString {
/**
@@ -43,8 +43,9 @@ export interface ICommandAction {
title: string | ICommandActionTitle;
shortTitle?: string | ICommandActionTitle;
category?: string | ILocalizedString;
tooltip?: string;
tooltip?: string | ILocalizedString;
icon?: Icon;
source?: string;
precondition?: ContextKeyExpression;
toggled?: ContextKeyExpression | { condition: ContextKeyExpression, icon?: Icon, tooltip?: string, title?: string | ILocalizedString };
}
@@ -66,6 +67,7 @@ export interface ISubmenuItem {
when?: ContextKeyExpression;
group?: 'navigation' | string;
order?: number;
rememberDefaultAction?: boolean; // for dropdown menu: if true the last executed action is remembered as the default action
}
export function isIMenuItem(item: IMenuItem | ISubmenuItem): item is IMenuItem {
@@ -94,6 +96,7 @@ export class MenuId {
static readonly EditorTitle = new MenuId('EditorTitle');
static readonly EditorTitleRun = new MenuId('EditorTitleRun');
static readonly EditorTitleContext = new MenuId('EditorTitleContext');
static readonly EmptyEditorGroup = new MenuId('EmptyEditorGroup');
static readonly EmptyEditorGroupContext = new MenuId('EmptyEditorGroupContext');
static readonly ExplorerContext = new MenuId('ExplorerContext');
static readonly ExtensionContext = new MenuId('ExtensionContext');
@@ -128,6 +131,7 @@ export class MenuId {
static readonly StatusBarWindowIndicatorMenu = new MenuId('StatusBarWindowIndicatorMenu');
static readonly StatusBarRemoteIndicatorMenu = new MenuId('StatusBarRemoteIndicatorMenu');
static readonly TestItem = new MenuId('TestItem');
static readonly TestItemGutter = new MenuId('TestItemGutter');
static readonly TestPeekElement = new MenuId('TestPeekElement');
static readonly TestPeekTitle = new MenuId('TestPeekTitle');
static readonly TouchBarContext = new MenuId('TouchBarContext');
@@ -147,8 +151,11 @@ export class MenuId {
static readonly CommentThreadActions = new MenuId('CommentThreadActions');
static readonly CommentTitle = new MenuId('CommentTitle');
static readonly CommentActions = new MenuId('CommentActions');
static readonly InteractiveToolbar = new MenuId('InteractiveToolbar');
static readonly InteractiveCellTitle = new MenuId('InteractiveCellTitle');
static readonly InteractiveCellExecute = new MenuId('InteractiveCellExecute');
static readonly InteractiveInputExecute = new MenuId('InteractiveInputExecute');
// static readonly NotebookToolbar = new MenuId('NotebookToolbar'); {{SQL CARBON EDIT}} We have our own toolbar
static readonly NotebookRightToolbar = new MenuId('NotebookRightToolbar');
static readonly NotebookCellTitle = new MenuId('NotebookCellTitle');
static readonly NotebookCellInsert = new MenuId('NotebookCellInsert');
static readonly NotebookCellBetween = new MenuId('NotebookCellBetween');
@@ -158,6 +165,7 @@ export class MenuId {
static readonly NotebookDiffCellMetadataTitle = new MenuId('NotebookDiffCellMetadataTitle');
static readonly NotebookDiffCellOutputsTitle = new MenuId('NotebookDiffCellOutputsTitle');
static readonly NotebookOutputToolbar = new MenuId('NotebookOutputToolbar');
static readonly NotebookEditorLayoutConfigure = new MenuId('NotebookEditorLayoutConfigure');
static readonly BulkEditTitle = new MenuId('BulkEditTitle');
static readonly BulkEditContext = new MenuId('BulkEditContext');
static readonly ObjectExplorerItemContext = new MenuId('ObjectExplorerItemContext'); // {{SQL CARBON EDIT}}
@@ -181,6 +189,7 @@ export class MenuId {
static readonly TerminalInlineTabContext = new MenuId('TerminalInlineTabContext');
static readonly WebviewContext = new MenuId('WebviewContext');
static readonly InlineCompletionsActions = new MenuId('InlineCompletionsActions');
static readonly NewFile = new MenuId('NewFile');
readonly id: number;
readonly _debugName: string;
@@ -409,7 +418,7 @@ export class MenuItemAction implements IAction {
this.label = options?.renderShortTitle && item.shortTitle
? (typeof item.shortTitle === 'string' ? item.shortTitle : item.shortTitle.value)
: (typeof item.title === 'string' ? item.title : item.title.value);
this.tooltip = item.tooltip ?? '';
this.tooltip = (typeof item.tooltip === 'string' ? item.tooltip : item.tooltip?.value) ?? '';
this.enabled = !item.precondition || contextKeyService.contextMatchesRules(item.precondition);
this.checked = false;
@@ -491,7 +500,7 @@ export class SyncActionDescriptor {
this._keybindings = keybindings;
this._keybindingContext = keybindingContext;
this._keybindingWeight = keybindingWeight;
this._descriptor = createSyncDescriptor(ctor, this._id, this._label);
this._descriptor = new SyncDescriptor(ctor, [this._id, this._label]) as unknown as SyncDescriptor0<Action>;
}
public get syncDescriptor(): SyncDescriptor0<Action> {

View File

@@ -6,9 +6,9 @@
import { RunOnceScheduler } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IMenu, IMenuActionOptions, IMenuItem, IMenuService, isIMenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction, ILocalizedString } from 'vs/platform/actions/common/actions';
import { ILocalizedString, IMenu, IMenuActionOptions, IMenuItem, IMenuService, isIMenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
import { ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
export class MenuService implements IMenuService {
@@ -36,10 +36,10 @@ type MenuItemGroup = [string, Array<IMenuItem | ISubmenuItem>];
class Menu implements IMenu {
private readonly _dispoables = new DisposableStore();
private readonly _disposables = new DisposableStore();
private readonly _onDidChange = new Emitter<IMenu>();
readonly onDidChange: Event<IMenu> = this._onDidChange.event;
private readonly _onDidChange: Emitter<IMenu>;
readonly onDidChange: Event<IMenu>;
private _menuGroups: MenuItemGroup[] = [];
private _contextKeys: Set<string> = new Set();
@@ -53,29 +53,45 @@ class Menu implements IMenu {
) {
this._build();
// rebuild this menu whenever the menu registry reports an
// event for this MenuId
const rebuildMenuSoon = new RunOnceScheduler(() => this._build(), 50);
this._dispoables.add(rebuildMenuSoon);
this._dispoables.add(MenuRegistry.onDidChangeMenu(e => {
// Rebuild this menu whenever the menu registry reports an event for this MenuId.
// This usually happen while code and extensions are loaded and affects the over
// structure of the menu
const rebuildMenuSoon = new RunOnceScheduler(() => {
this._build();
this._onDidChange.fire(this);
}, 50);
this._disposables.add(rebuildMenuSoon);
this._disposables.add(MenuRegistry.onDidChangeMenu(e => {
if (e.has(_id)) {
rebuildMenuSoon.schedule();
}
}));
// when context keys change we need to check if the menu also
// has changed
const fireChangeSoon = new RunOnceScheduler(() => this._onDidChange.fire(this), 50);
this._dispoables.add(fireChangeSoon);
this._dispoables.add(_contextKeyService.onDidChangeContext(e => {
if (e.affectsSome(this._contextKeys)) {
fireChangeSoon.schedule();
}
}));
// When context keys change we need to check if the menu also has changed. However,
// we only do that when someone listens on this menu because (1) context key events are
// firing often and (2) menu are often leaked
const contextKeyListener = this._disposables.add(new DisposableStore());
const startContextKeyListener = () => {
const fireChangeSoon = new RunOnceScheduler(() => this._onDidChange.fire(this), 50);
contextKeyListener.add(fireChangeSoon);
contextKeyListener.add(_contextKeyService.onDidChangeContext(e => {
if (e.affectsSome(this._contextKeys)) {
fireChangeSoon.schedule();
}
}));
};
this._onDidChange = new Emitter({
// start/stop context key listener
onFirstListenerAdd: startContextKeyListener,
onLastListenerRemove: contextKeyListener.clear.bind(contextKeyListener)
});
this.onDidChange = this._onDidChange.event;
}
dispose(): void {
this._dispoables.dispose();
this._disposables.dispose();
this._onDidChange.dispose();
}
@@ -90,7 +106,7 @@ class Menu implements IMenu {
let group: MenuItemGroup | undefined;
menuItems.sort(Menu._compareMenuItems);
for (let item of menuItems) {
for (const item of menuItems) {
// group by groupId
const groupName = item.group || '';
if (!group || group[0] !== groupName) {
@@ -102,7 +118,6 @@ class Menu implements IMenu {
// keep keys for eventing
this._collectContextKeys(item);
}
this._onDidChange.fire(this);
}
private _collectContextKeys(item: IMenuItem | ISubmenuItem): void {

View File

@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { MenuRegistry, MenuId, isIMenuItem } from 'vs/platform/actions/common/actions';
import { MenuService } from 'vs/platform/actions/common/menuService';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { isIMenuItem, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
import { MenuService } from 'vs/platform/actions/common/menuService';
import { NullCommandService } from 'vs/platform/commands/common/commands';
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';

View File

@@ -3,10 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { URI } from 'vs/base/common/uri';
import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
export const IBackupMainService = createDecorator<IBackupMainService>('backupMainService');

View File

@@ -3,22 +3,22 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import { createHash } from 'crypto';
import { join } from 'vs/base/common/path';
import { isLinux } from 'vs/base/common/platform';
import { writeFileSync, RimRafMode, Promises } from 'vs/base/node/pfs';
import { IBackupMainService, IWorkspaceBackupInfo, isWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup';
import { IBackupWorkspacesFormat, IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFilesConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files';
import { ILogService } from 'vs/platform/log/common/log';
import { IWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { URI } from 'vs/base/common/uri';
import * as fs from 'fs';
import { isEqual } from 'vs/base/common/extpath';
import { Schemas } from 'vs/base/common/network';
import { join } from 'vs/base/common/path';
import { isLinux } from 'vs/base/common/platform';
import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { Promises, RimRafMode, writeFileSync } from 'vs/base/node/pfs';
import { IBackupMainService, isWorkspaceBackupInfo, IWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup';
import { IBackupWorkspacesFormat, IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { HotExitConfiguration, IFilesConfiguration } from 'vs/platform/files/common/files';
import { ILogService } from 'vs/platform/log/common/log';
import { isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
export class BackupMainService implements IBackupMainService {

View File

@@ -4,26 +4,26 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as platform from 'vs/base/common/platform';
import { createHash } from 'crypto';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'vs/base/common/path';
import * as pfs from 'vs/base/node/pfs';
import { URI } from 'vs/base/common/uri';
import { EnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService';
import { IWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup';
import { IBackupWorkspacesFormat, ISerializedWorkspace } from 'vs/platform/backup/node/backup';
import { HotExitConfiguration } from 'vs/platform/files/common/files';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { ConsoleMainLogger, LogService } from 'vs/platform/log/common/log';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { createHash } from 'crypto';
import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils';
import { Schemas } from 'vs/base/common/network';
import * as path from 'vs/base/common/path';
import * as platform from 'vs/base/common/platform';
import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import * as pfs from 'vs/base/node/pfs';
import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils';
import { IWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup';
import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService';
import { IBackupWorkspacesFormat, ISerializedWorkspace } from 'vs/platform/backup/node/backup';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { EnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { OPTIONS, parseArgs } from 'vs/platform/environment/node/argv';
import { HotExitConfiguration } from 'vs/platform/files/common/files';
import { ConsoleMainLogger, LogService } from 'vs/platform/log/common/log';
import product from 'vs/platform/product/common/product';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
flakySuite('BackupMainService', () => {

View File

@@ -3,17 +3,18 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey';
import { HistoryInputBox, IHistoryInputOptions } from 'vs/base/browser/ui/inputbox/inputBox';
import { FindInput, IFindInputOptions } from 'vs/base/browser/ui/findinput/findInput';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { IHistoryNavigationWidget } from 'vs/base/browser/history';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { FindInput, IFindInputOptions } from 'vs/base/browser/ui/findinput/findInput';
import { IReplaceInputOptions, ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput';
import { HistoryInputBox, IHistoryInputOptions } from 'vs/base/browser/ui/inputbox/inputBox';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { ReplaceInput, IReplaceInputOptions } from 'vs/base/browser/ui/findinput/replaceInput';
import { ContextKeyExpr, IContextKey, IContextKeyService, IContextKeyServiceTarget, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
export const HistoryNavigationWidgetContext = 'historyNavigationWidget';
export const HistoryNavigationEnablementContext = 'historyNavigationEnabled';
const HistoryNavigationForwardsEnablementContext = 'historyNavigationForwardsEnabled';
const HistoryNavigationBackwardsEnablementContext = 'historyNavigationBackwardsEnabled';
function bindContextScopedWidget(contextKeyService: IContextKeyService, widget: IContextScopedWidget, contextKey: string): void {
new RawContextKey<IContextScopedWidget>(contextKey, widget).bindTo(contextKeyService);
@@ -35,11 +36,22 @@ interface IContextScopedHistoryNavigationWidget extends IContextScopedWidget {
historyNavigator: IHistoryNavigationWidget;
}
export function createAndBindHistoryNavigationWidgetScopedContextKeyService(contextKeyService: IContextKeyService, widget: IContextScopedHistoryNavigationWidget): { scopedContextKeyService: IContextKeyService, historyNavigationEnablement: IContextKey<boolean> } {
export interface IHistoryNavigationContext {
scopedContextKeyService: IContextKeyService,
historyNavigationForwardsEnablement: IContextKey<boolean>,
historyNavigationBackwardsEnablement: IContextKey<boolean>,
}
export function createAndBindHistoryNavigationWidgetScopedContextKeyService(contextKeyService: IContextKeyService, widget: IContextScopedHistoryNavigationWidget): IHistoryNavigationContext {
const scopedContextKeyService = createWidgetScopedContextKeyService(contextKeyService, widget);
bindContextScopedWidget(scopedContextKeyService, widget, HistoryNavigationWidgetContext);
const historyNavigationEnablement = new RawContextKey<boolean>(HistoryNavigationEnablementContext, true).bindTo(scopedContextKeyService);
return { scopedContextKeyService, historyNavigationEnablement };
const historyNavigationForwardsEnablement = new RawContextKey<boolean>(HistoryNavigationForwardsEnablementContext, true).bindTo(scopedContextKeyService);
const historyNavigationBackwardsEnablement = new RawContextKey<boolean>(HistoryNavigationBackwardsEnablementContext, true).bindTo(scopedContextKeyService);
return {
scopedContextKeyService,
historyNavigationForwardsEnablement,
historyNavigationBackwardsEnablement,
};
}
export class ContextScopedHistoryInputBox extends HistoryInputBox {
@@ -77,10 +89,10 @@ export class ContextScopedReplaceInput extends ReplaceInput {
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'history.showPrevious',
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(ContextKeyExpr.has(HistoryNavigationWidgetContext), ContextKeyExpr.equals(HistoryNavigationEnablementContext, true)),
when: ContextKeyExpr.and(ContextKeyExpr.has(HistoryNavigationWidgetContext), ContextKeyExpr.equals(HistoryNavigationBackwardsEnablementContext, true)),
primary: KeyCode.UpArrow,
secondary: [KeyMod.Alt | KeyCode.UpArrow],
handler: (accessor, arg2) => {
handler: (accessor) => {
const widget = getContextScopedWidget<IContextScopedHistoryNavigationWidget>(accessor.get(IContextKeyService), HistoryNavigationWidgetContext);
if (widget) {
const historyInputBox: IHistoryNavigationWidget = widget.historyNavigator;
@@ -92,10 +104,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'history.showNext',
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(ContextKeyExpr.has(HistoryNavigationWidgetContext), ContextKeyExpr.equals(HistoryNavigationEnablementContext, true)),
when: ContextKeyExpr.and(ContextKeyExpr.has(HistoryNavigationWidgetContext), ContextKeyExpr.equals(HistoryNavigationForwardsEnablementContext, true)),
primary: KeyCode.DownArrow,
secondary: [KeyMod.Alt | KeyCode.DownArrow],
handler: (accessor, arg2) => {
handler: (accessor) => {
const widget = getContextScopedWidget<IContextScopedHistoryNavigationWidget>(accessor.get(IContextKeyService), HistoryNavigationWidgetContext);
if (widget) {
const historyInputBox: IHistoryNavigationWidget = widget.historyNavigator;

View File

@@ -3,8 +3,8 @@
* Licensed under the Source EULA. 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 { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const IChecksumService = createDecorator<IChecksumService>('checksumService');

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerSharedProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services';
import { IChecksumService } from 'vs/platform/checksum/common/checksumService';
import { registerSharedProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services';
registerSharedProcessRemoteService(IChecksumService, 'checksum', { supportsDelayedInstantiation: true });

View File

@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { getPathFromAmdModule } from 'vs/base/test/node/testUtils';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { getPathFromAmdModule } from 'vs/base/test/node/testUtils';
import { ChecksumService } from 'vs/platform/checksum/node/checksumService';
import { IFileService } from 'vs/platform/files/common/files';
import { FileService } from 'vs/platform/files/common/fileService';

View File

@@ -3,9 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ClipboardData, IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; // {{SQL CARBON EDIT}}
import { URI } from 'vs/base/common/uri';
import { $ } from 'vs/base/browser/dom';
import { URI } from 'vs/base/common/uri';
import { ClipboardData, IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; // {{SQL CARBON EDIT}}
export class BrowserClipboardService implements IClipboardService {

View File

@@ -3,8 +3,8 @@
* Licensed under the Source EULA. 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 { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const IClipboardService = createDecorator<IClipboardService>('clipboardService');

View File

@@ -3,13 +3,13 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle';
import { TypeConstraint, validateConstraints } from 'vs/base/common/types';
import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event, Emitter } from 'vs/base/common/event';
import { LinkedList } from 'vs/base/common/linkedList';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { Emitter, Event } from 'vs/base/common/event';
import { Iterable } from 'vs/base/common/iterator';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { LinkedList } from 'vs/base/common/linkedList';
import { TypeConstraint, validateConstraints } from 'vs/base/common/types';
import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
export const ICommandService = createDecorator<ICommandService>('commandService');

View File

@@ -3,15 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IStringDictionary } from 'vs/base/common/collections';
import { Event } from 'vs/base/common/event';
import * as objects from 'vs/base/common/objects';
import * as types from 'vs/base/common/types';
import { URI, UriComponents } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { Extensions, IConfigurationRegistry, overrideIdentifierFromKey, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configurationRegistry';
import { IStringDictionary } from 'vs/base/common/collections';
export const IConfigurationService = createDecorator<IConfigurationService>('configurationService');

View File

@@ -3,20 +3,20 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as json from 'vs/base/common/json';
import { ResourceMap, getOrSet } from 'vs/base/common/map';
import * as arrays from 'vs/base/common/arrays';
import * as types from 'vs/base/common/types';
import * as objects from 'vs/base/common/objects';
import { URI, UriComponents } from 'vs/base/common/uri';
import { OVERRIDE_PROPERTY_PATTERN, ConfigurationScope, IConfigurationRegistry, Extensions, IConfigurationPropertySchema, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configurationRegistry';
import { IOverrides, addToValueTree, toValuesTree, IConfigurationModel, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, removeFromValueTree, toOverrides, IConfigurationValue, ConfigurationTarget, compare, IConfigurationChangeEvent, IConfigurationChange } from 'vs/platform/configuration/common/configuration';
import { Workspace } from 'vs/platform/workspace/common/workspace';
import { Registry } from 'vs/platform/registry/common/platform';
import { Disposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { IFileService } from 'vs/platform/files/common/files';
import * as json from 'vs/base/common/json';
import { Disposable } from 'vs/base/common/lifecycle';
import { getOrSet, ResourceMap } from 'vs/base/common/map';
import * as objects from 'vs/base/common/objects';
import { IExtUri } from 'vs/base/common/resources';
import * as types from 'vs/base/common/types';
import { URI, UriComponents } from 'vs/base/common/uri';
import { addToValueTree, compare, ConfigurationTarget, getConfigurationKeys, getConfigurationValue, getDefaultValues, IConfigurationChange, IConfigurationChangeEvent, IConfigurationData, IConfigurationModel, IConfigurationOverrides, IConfigurationValue, IOverrides, removeFromValueTree, toOverrides, toValuesTree } from 'vs/platform/configuration/common/configuration';
import { ConfigurationScope, Extensions, IConfigurationPropertySchema, IConfigurationRegistry, overrideIdentifierFromKey, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
import { IFileService } from 'vs/platform/files/common/files';
import { Registry } from 'vs/platform/registry/common/platform';
import { Workspace } from 'vs/platform/workspace/common/workspace';
export class ConfigurationModel implements IConfigurationModel {
@@ -270,7 +270,7 @@ export class ConfigurationModelParser {
function onValue(value: any) {
if (Array.isArray(currentParent)) {
(<any[]>currentParent).push(value);
} else if (currentProperty) {
} else if (currentProperty !== null) {
currentParent[currentProperty] = value;
}
}

View File

@@ -3,13 +3,18 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { Registry } from 'vs/platform/registry/common/platform';
import * as types from 'vs/base/common/types';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { IStringDictionary } from 'vs/base/common/collections';
import { Emitter, Event } from 'vs/base/common/event';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import * as types from 'vs/base/common/types';
import * as nls from 'vs/nls';
import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
export enum EditPresentationTypes {
Multiline = 'multilineText',
Singleline = 'singlelineText'
}
export const Extensions = {
Configuration: 'base.contributions.configuration'
@@ -133,6 +138,12 @@ export interface IConfigurationPropertySchema extends IJSONSchema {
disallowSyncIgnore?: boolean;
enumItemLabels?: string[];
/**
* When specified, controls the presentation format of string settings.
* Otherwise, the presentation format defaults to `singleline`.
*/
editPresentation?: EditPresentationTypes;
}
export interface IConfigurationExtensionInfo {

View File

@@ -3,16 +3,16 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { IConfigurationService, IConfigurationChangeEvent, IConfigurationOverrides, ConfigurationTarget, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange } from 'vs/platform/configuration/common/configuration';
import { DefaultConfigurationModel, Configuration, ConfigurationModel, ConfigurationChangeEvent, UserSettings } from 'vs/platform/configuration/common/configurationModels';
import { Event, Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
import { RunOnceScheduler } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { ConfigurationTarget, IConfigurationChange, IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration';
import { Configuration, ConfigurationChangeEvent, ConfigurationModel, DefaultConfigurationModel, UserSettings } from 'vs/platform/configuration/common/configurationModels';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { IFileService } from 'vs/platform/files/common/files';
import { Registry } from 'vs/platform/registry/common/platform';
export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable {

View File

@@ -0,0 +1,92 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Queue } from 'vs/base/common/async';
import { VSBuffer } from 'vs/base/common/buffer';
import { JSONPath, parse, ParseError } from 'vs/base/common/json';
import { setProperty } from 'vs/base/common/jsonEdit';
import { Edit, FormattingOptions } from 'vs/base/common/jsonFormatter';
import { URI } from 'vs/base/common/uri';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
export const enum UserConfigurationErrorCode {
ERROR_INVALID_FILE = 'ERROR_INVALID_FILE',
ERROR_FILE_MODIFIED_SINCE = 'ERROR_FILE_MODIFIED_SINCE'
}
export interface IJSONValue {
path: JSONPath;
value: any;
}
export const UserConfigurationFileServiceId = 'IUserConfigurationFileService';
export const IUserConfigurationFileService = createDecorator<IUserConfigurationFileService>(UserConfigurationFileServiceId);
export interface IUserConfigurationFileService {
readonly _serviceBrand: undefined;
updateSettings(value: IJSONValue, formattingOptions: FormattingOptions): Promise<void>;
}
export class UserConfigurationFileService implements IUserConfigurationFileService {
readonly _serviceBrand: undefined;
private readonly queue: Queue<void>;
constructor(
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IFileService private readonly fileService: IFileService,
@ILogService private readonly logService: ILogService,
) {
this.queue = new Queue<void>();
}
async updateSettings(value: IJSONValue, formattingOptions: FormattingOptions): Promise<void> {
return this.queue.queue(() => this.doWrite(this.environmentService.settingsResource, value, formattingOptions)); // queue up writes to prevent race conditions
}
private async doWrite(resource: URI, jsonValue: IJSONValue, formattingOptions: FormattingOptions): Promise<void> {
this.logService.trace(`${UserConfigurationFileServiceId}#write`, resource.toString(), jsonValue);
const { value, mtime, etag } = await this.fileService.readFile(resource, { atomic: true });
let content = value.toString();
const parseErrors: ParseError[] = [];
parse(content, parseErrors, { allowTrailingComma: true, allowEmptyContent: true });
if (parseErrors.length) {
throw new Error(UserConfigurationErrorCode.ERROR_INVALID_FILE);
}
const edit = this.getEdits(jsonValue, content, formattingOptions)[0];
if (edit) {
content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length);
try {
await this.fileService.writeFile(resource, VSBuffer.fromString(content), { etag, mtime });
} catch (error) {
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE) {
throw new Error(UserConfigurationErrorCode.ERROR_FILE_MODIFIED_SINCE);
}
}
}
}
private getEdits({ value, path }: IJSONValue, modelContent: string, formattingOptions: FormattingOptions): Edit[] {
if (path.length) {
return setProperty(modelContent, path, value, formattingOptions);
}
// Without jsonPath, the entire configuration file is being replaced, so we just use JSON.stringify
const content = JSON.stringify(value, null, formattingOptions.insertSpaces && formattingOptions.tabSize ? ' '.repeat(formattingOptions.tabSize) : '\t');
return [{
content,
length: modelContent.length,
offset: 0
}];
}
}

View File

@@ -3,13 +3,13 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, ConfigurationModelParser, Configuration, mergeChanges, AllKeysConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels';
import { join } from 'vs/base/common/path';
import { URI } from 'vs/base/common/uri';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { AllKeysConfigurationChangeEvent, Configuration, ConfigurationChangeEvent, ConfigurationModel, ConfigurationModelParser, DefaultConfigurationModel, mergeChanges } from 'vs/platform/configuration/common/configurationModels';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { URI } from 'vs/base/common/uri';
import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { join } from 'vs/base/common/path';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace';
suite('ConfigurationModel', () => {
@@ -334,6 +334,16 @@ suite('CustomConfigurationModel', () => {
assert.deepStrictEqual(testObject.configurationModel.keys, []);
});
test('Test empty property is not ignored', () => {
const testObject = new ConfigurationModelParser('test');
testObject.parse(JSON.stringify({ '': 1 }));
// deepStrictEqual seems to ignore empty properties, fall back
// to comparing the output of JSON.stringify
assert.strictEqual(JSON.stringify(testObject.configurationModel.contents), JSON.stringify({ '': 1 }));
assert.deepStrictEqual(testObject.configurationModel.keys, ['']);
});
test('Test registering the same property again', () => {
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
'id': 'a',

View File

@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
suite('ConfigurationRegistry', () => {

View File

@@ -4,20 +4,20 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Registry } from 'vs/platform/registry/common/platform';
import { ConfigurationService } from 'vs/platform/configuration/common/configurationService';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { URI } from 'vs/base/common/uri';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { VSBuffer } from 'vs/base/common/buffer';
import { Event } from 'vs/base/common/event';
import { NullLogService } from 'vs/platform/log/common/log';
import { FileService } from 'vs/platform/files/common/fileService';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { ConfigurationService } from 'vs/platform/configuration/common/configurationService';
import { IFileService } from 'vs/platform/files/common/files';
import { VSBuffer } from 'vs/base/common/buffer';
import { FileService } from 'vs/platform/files/common/fileService';
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
import { NullLogService } from 'vs/platform/log/common/log';
import { Registry } from 'vs/platform/registry/common/platform';
suite('ConfigurationService', () => {

View File

@@ -3,10 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from 'vs/base/common/event';
import { TernarySearchTree } from 'vs/base/common/map';
import { URI } from 'vs/base/common/uri';
import { getConfigurationKeys, IConfigurationOverrides, IConfigurationService, getConfigurationValue, isConfigurationOverrides, IConfigurationValue } from 'vs/platform/configuration/common/configuration';
import { Emitter } from 'vs/base/common/event';
import { getConfigurationKeys, getConfigurationValue, IConfigurationOverrides, IConfigurationService, IConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration';
export class TestConfigurationService implements IConfigurationService {
public _serviceBrand: undefined;

View File

@@ -5,13 +5,13 @@
import { Emitter, Event, PauseableEmitter } from 'vs/base/common/event';
import { Iterable } from 'vs/base/common/iterator';
import { IDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { TernarySearchTree } from 'vs/base/common/map';
import { distinct } from 'vs/base/common/objects';
import { localize } from 'vs/nls';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContext, IContextKey, IContextKeyChangeEvent, IContextKeyService, IContextKeyServiceTarget, IReadableSet, SET_CONTEXT_COMMAND_ID, ContextKeyExpression, RawContextKey, ContextKeyInfo } from 'vs/platform/contextkey/common/contextkey';
import { ContextKeyExpression, ContextKeyInfo, IContext, IContextKey, IContextKeyChangeEvent, IContextKeyService, IContextKeyServiceTarget, IReadableSet, RawContextKey, SET_CONTEXT_COMMAND_ID } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver';
const KEYBINDING_CONTEXT_ATTR = 'data-keybinding-context';

View File

@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { isLinux, isMacintosh, isWeb, isWindows, userAgent } from 'vs/base/common/platform';
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { userAgent, isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform';
let _userAgent = userAgent || '';
const STATIC_VALUES = new Map<string, boolean>();

View File

@@ -3,9 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isIOS, isLinux, isMacintosh, isWeb, isWindows } from 'vs/base/common/platform';
import { localize } from 'vs/nls';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { isMacintosh, isLinux, isWindows, isWeb, isIOS } from 'vs/base/common/platform';
export const IsMacContext = new RawContextKey<boolean>('isMac', isMacintosh, localize('isMac', "Whether the operating system is macOS"));
export const IsLinuxContext = new RawContextKey<boolean>('isLinux', isLinux, localize('isLinux', "Whether the operating system is Linux"));

View File

@@ -2,9 +2,9 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService';
import * as assert from 'assert';
suite('ContextKeyService', () => {
test('updateParent', () => {

View File

@@ -3,8 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { isMacintosh, isLinux, isWindows } from 'vs/base/common/platform';
function createContext(ctx: any) {
return {

View File

@@ -3,22 +3,21 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./contextMenuHandler';
import { ActionRunner, IRunEvent, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';
import { combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Menu } from 'vs/base/browser/ui/menu/menu';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';
import { EventType, $, isHTMLElement } from 'vs/base/browser/dom';
import { attachMenuStyler } from 'vs/platform/theme/common/styler';
import { domEvent } from 'vs/base/browser/event';
import { $, addDisposableListener, EventType, isHTMLElement } from 'vs/base/browser/dom';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { Menu } from 'vs/base/browser/ui/menu/menu';
import { ActionRunner, IRunEvent, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import 'vs/css!./contextMenuHandler';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { attachMenuStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
export interface IContextMenuHandlerOptions {
blockMouse: boolean;
@@ -75,7 +74,9 @@ export class ContextMenuHandler {
this.block.style.width = '100%';
this.block.style.height = '100%';
this.block.style.zIndex = '-1';
domEvent(this.block, EventType.MOUSE_DOWN)((e: MouseEvent) => e.stopPropagation());
// TODO@Steven: this is never getting disposed
addDisposableListener(this.block, EventType.MOUSE_DOWN, e => e.stopPropagation());
}
const menuDisposables = new DisposableStore();
@@ -94,8 +95,8 @@ export class ContextMenuHandler {
menu.onDidCancel(() => this.contextViewService.hideContextView(true), null, menuDisposables);
menu.onDidBlur(() => this.contextViewService.hideContextView(true), null, menuDisposables);
domEvent(window, EventType.BLUR)(() => { this.contextViewService.hideContextView(true); }, null, menuDisposables);
domEvent(window, EventType.MOUSE_DOWN)((e: MouseEvent) => {
menuDisposables.add(addDisposableListener(window, EventType.BLUR, () => this.contextViewService.hideContextView(true)));
menuDisposables.add(addDisposableListener(window, EventType.MOUSE_DOWN, (e: MouseEvent) => {
if (e.defaultPrevented) {
return;
}
@@ -117,7 +118,7 @@ export class ContextMenuHandler {
}
this.contextViewService.hideContextView(true);
}, null, menuDisposables);
}));
return combinedDisposable(menuDisposables, menu);
},

View File

@@ -3,16 +3,16 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ContextMenuHandler, IContextMenuHandlerOptions } from './contextMenuHandler';
import { IContextViewService, IContextMenuService } from './contextView';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { Disposable } from 'vs/base/common/lifecycle';
import { ModifierKeyEmitter } from 'vs/base/browser/dom';
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ContextMenuHandler, IContextMenuHandlerOptions } from './contextMenuHandler';
import { IContextMenuService, IContextViewService } from './contextView';
export class ContextMenuService extends Disposable implements IContextMenuService {
declare readonly _serviceBrand: undefined;

View File

@@ -3,11 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';
import { AnchorAlignment, AnchorAxisAlignment, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const IContextViewService = createDecorator<IContextViewService>('contextViewService');

View File

@@ -3,10 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IContextViewService, IContextViewDelegate } from './contextView';
import { ContextView, ContextViewDOMPosition } from 'vs/base/browser/ui/contextview/contextview';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { IContextViewDelegate, IContextViewService } from './contextView';
export class ContextViewService extends Disposable implements IContextViewService {
declare readonly _serviceBrand: undefined;

View File

@@ -3,8 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const IExtensionHostDebugService = createDecorator<IExtensionHostDebugService>('extensionHostDebugService');

View File

@@ -3,10 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IReloadSessionEvent, ICloseSessionEvent, IAttachSessionEvent, ITerminateSessionEvent, IExtensionHostDebugService, IOpenExtensionWindowResult, INullableProcessEnvironment } from 'vs/platform/debug/common/extensionHostDebug';
import { Event, Emitter } from 'vs/base/common/event';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { IAttachSessionEvent, ICloseSessionEvent, IExtensionHostDebugService, INullableProcessEnvironment, IOpenExtensionWindowResult, IReloadSessionEvent, ITerminateSessionEvent } from 'vs/platform/debug/common/extensionHostDebug';
export class ExtensionHostDebugBroadcastChannel<TContext> implements IServerChannel<TContext> {

View File

@@ -3,11 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { INullableProcessEnvironment, IOpenExtensionWindowResult } from 'vs/platform/debug/common/extensionHostDebug';
import { AddressInfo, createServer } from 'net';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
import { createServer, AddressInfo } from 'net';
import { INullableProcessEnvironment, IOpenExtensionWindowResult } from 'vs/platform/debug/common/extensionHostDebug';
import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc';
import { OPTIONS, parseArgs } from 'vs/platform/environment/node/argv';
import { IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows';
export class ElectronExtensionHostDebugBroadcastChannel<TContext> extends ExtensionHostDebugBroadcastChannel<TContext> {

View File

@@ -3,12 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { UriComponents } from 'vs/base/common/uri';
import { ProcessItem } from 'vs/base/common/processes';
import { IWorkspace } from 'vs/platform/workspace/common/workspace';
import { IStringDictionary } from 'vs/base/common/collections';
import { IMainProcessInfo } from 'vs/platform/launch/common/launch';
import { ProcessItem } from 'vs/base/common/processes';
import { UriComponents } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IMainProcessInfo } from 'vs/platform/launch/common/launch';
import { IWorkspace } from 'vs/platform/workspace/common/workspace';
export const ID = 'diagnosticsService';
export const IDiagnosticsService = createDecorator<IDiagnosticsService>(ID);

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerSharedProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services';
import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnostics';
import { registerSharedProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services';
registerSharedProcessRemoteService(IDiagnosticsService, 'diagnostics', { supportsDelayedInstantiation: true });

View File

@@ -3,21 +3,21 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as osLib from 'os';
import { virtualMachineHint } from 'vs/base/node/id';
import { IDiagnosticsService, IMachineInfo, WorkspaceStats, WorkspaceStatItem, PerformanceInfo, SystemInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError, isRemoteDiagnosticError, IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnostics';
import { join, basename } from 'vs/base/common/path';
import { parse, ParseError, getNodeType } from 'vs/base/common/json';
import { listProcesses } from 'vs/base/node/ps';
import { IProductService } from 'vs/platform/product/common/productService';
import { isWindows, isLinux } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { ProcessItem } from 'vs/base/common/processes';
import { IMainProcessInfo } from 'vs/platform/launch/common/launch';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Iterable } from 'vs/base/common/iterator';
import { getNodeType, parse, ParseError } from 'vs/base/common/json';
import { Schemas } from 'vs/base/common/network';
import { ByteSize } from 'vs/platform/files/common/files';
import { basename, join } from 'vs/base/common/path';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { ProcessItem } from 'vs/base/common/processes';
import { URI } from 'vs/base/common/uri';
import { virtualMachineHint } from 'vs/base/node/id';
import { IDirent, Promises } from 'vs/base/node/pfs';
import { listProcesses } from 'vs/base/node/ps';
import { IDiagnosticsService, IMachineInfo, IRemoteDiagnosticError, IRemoteDiagnosticInfo, isRemoteDiagnosticError, IWorkspaceInformation, PerformanceInfo, SystemInfo, WorkspaceStatItem, WorkspaceStats } from 'vs/platform/diagnostics/common/diagnostics';
import { ByteSize } from 'vs/platform/files/common/files';
import { IMainProcessInfo } from 'vs/platform/launch/common/launch';
import { IProductService } from 'vs/platform/product/common/productService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
export interface VersionInfo {
vscodeVersion: string;

View File

@@ -3,14 +3,14 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Severity from 'vs/base/common/severity';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
import { basename } from 'vs/base/common/resources';
import { localize } from 'vs/nls';
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
import { Codicon } from 'vs/base/common/codicons';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { basename } from 'vs/base/common/resources';
import Severity from 'vs/base/common/severity';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
export interface FileFilter {
extensions: string[];

View File

@@ -3,21 +3,21 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { MessageBoxOptions, MessageBoxReturnValue, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, dialog, FileFilter, BrowserWindow } from 'electron';
import { BrowserWindow, dialog, FileFilter, MessageBoxOptions, MessageBoxReturnValue, OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue } from 'electron';
import { Queue } from 'vs/base/common/async';
import { IStateMainService } from 'vs/platform/state/electron-main/state';
import { isMacintosh } from 'vs/base/common/platform';
import { dirname } from 'vs/base/common/path';
import { normalizeNFC } from 'vs/base/common/normalization';
import { Promises } from 'vs/base/node/pfs';
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs';
import { withNullAsUndefined } from 'vs/base/common/types';
import { localize } from 'vs/nls';
import { WORKSPACE_FILTER } from 'vs/platform/workspaces/common/workspaces';
import { hash } from 'vs/base/common/hash';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { hash } from 'vs/base/common/hash';
import { normalizeNFC } from 'vs/base/common/normalization';
import { dirname } from 'vs/base/common/path';
import { isMacintosh } from 'vs/base/common/platform';
import { withNullAsUndefined } from 'vs/base/common/types';
import { Promises } from 'vs/base/node/pfs';
import { localize } from 'vs/nls';
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IStateMainService } from 'vs/platform/state/electron-main/state';
import { WORKSPACE_FILTER } from 'vs/platform/workspaces/common/workspaces';
export const IDialogMainService = createDecorator<IDialogMainService>('dialogMainService');

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import Severity from 'vs/base/common/severity';
import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions, IShowResult, IInputResult } from 'vs/platform/dialogs/common/dialogs';
import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IInputResult, IShowResult } from 'vs/platform/dialogs/common/dialogs';
export class TestDialogService implements IDialogService {

View File

@@ -3,9 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { CancellationToken } from 'vs/base/common/cancellation';
export const IDownloadService = createDecorator<IDownloadService>('downloadService');

View File

@@ -3,11 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event } from 'vs/base/common/event';
import { IDownloadService } from 'vs/platform/download/common/download';
import { URI } from 'vs/base/common/uri';
import { IURITransformer } from 'vs/base/common/uriIpc';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { IDownloadService } from 'vs/platform/download/common/download';
export class DownloadServiceChannel implements IServerChannel {

View File

@@ -3,12 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDownloadService } from 'vs/platform/download/common/download';
import { URI } from 'vs/base/common/uri';
import { IRequestService, asText } from 'vs/platform/request/common/request';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IFileService } from 'vs/platform/files/common/files';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { IDownloadService } from 'vs/platform/download/common/download';
import { IFileService } from 'vs/platform/files/common/files';
import { asText, IRequestService } from 'vs/platform/request/common/request';
export class DownloadService implements IDownloadService {

View File

@@ -3,9 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getTopLeftOffset, getClientArea } from 'vs/base/browser/dom';
import { getClientArea, getTopLeftOffset } from 'vs/base/browser/dom';
import { coalesce } from 'vs/base/common/arrays';
import { IElement, IWindowDriver } from 'vs/platform/driver/common/driver';
import { language, locale } from 'vs/base/common/platform';
import { IElement, ILocaleInfo, ILocalizedStrings, IWindowDriver } from 'vs/platform/driver/common/driver';
import localizedStrings from 'vs/platform/localizations/common/localizedStrings';
function serializeElement(element: Element, recursive: boolean): IElement {
const attributes = Object.create(null);
@@ -161,6 +163,21 @@ export abstract class BaseWindowDriver implements IWindowDriver {
xterm._core._coreService.triggerDataEvent(text);
}
getLocaleInfo(): Promise<ILocaleInfo> {
return Promise.resolve({
language: language,
locale: locale
});
}
getLocalizedStrings(): Promise<ILocalizedStrings> {
return Promise.resolve({
open: localizedStrings.open,
close: localizedStrings.close,
find: localizedStrings.find
});
}
protected async _getElementXY(selector: string, offset?: { x: number, y: number }): Promise<{ x: number; y: number; }> {
const element = document.querySelector(selector);

View File

@@ -18,13 +18,31 @@ export interface IElement {
left: number;
}
export interface ILocaleInfo {
/**
* The UI language used.
*/
language: string;
/**
* The requested locale
*/
locale?: string;
}
export interface ILocalizedStrings {
open: string;
close: string;
find: string;
}
export interface IDriver {
readonly _serviceBrand: undefined;
getWindowIds(): Promise<number[]>;
capturePage(windowId: number): Promise<string>;
reloadWindow(windowId: number): Promise<void>;
exitApplication(): Promise<void>;
exitApplication(): Promise<boolean>;
dispatchKeybinding(windowId: number, keybinding: string): Promise<void>;
click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise<void>;
doubleClick(windowId: number, selector: string): Promise<void>;
@@ -36,6 +54,8 @@ export interface IDriver {
typeInEditor(windowId: number, selector: string, text: string): Promise<void>;
getTerminalBuffer(windowId: number, selector: string): Promise<string[]>;
writeInTerminal(windowId: number, selector: string, text: string): Promise<void>;
getLocaleInfo(windowId: number): Promise<ILocaleInfo>;
getLocalizedStrings(windowId: number): Promise<ILocalizedStrings>;
}
//*END
@@ -53,6 +73,8 @@ export interface IWindowDriver {
typeInEditor(selector: string, text: string): Promise<void>;
getTerminalBuffer(selector: string): Promise<string[]>;
writeInTerminal(selector: string, text: string): Promise<void>;
getLocaleInfo(): Promise<ILocaleInfo>;
getLocalizedStrings(): Promise<ILocalizedStrings>
}
export interface IDriverOptions {

View File

@@ -5,7 +5,7 @@
import { Event } from 'vs/base/common/event';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { IDriverOptions, IElement, IWindowDriver, IWindowDriverRegistry } from 'vs/platform/driver/common/driver';
import { IDriverOptions, IElement, ILocaleInfo, ILocalizedStrings as ILocalizedStrings, IWindowDriver, IWindowDriverRegistry } from 'vs/platform/driver/common/driver';
export class WindowDriverChannel implements IServerChannel {
@@ -27,6 +27,8 @@ export class WindowDriverChannel implements IServerChannel {
case 'typeInEditor': return this.driver.typeInEditor(arg[0], arg[1]);
case 'getTerminalBuffer': return this.driver.getTerminalBuffer(arg);
case 'writeInTerminal': return this.driver.writeInTerminal(arg[0], arg[1]);
case 'getLocaleInfo': return this.driver.getLocaleInfo();
case 'getLocalizedStrings': return this.driver.getLocalizedStrings();
}
throw new Error(`Call not found: ${command}`);
@@ -78,6 +80,14 @@ export class WindowDriverChannelClient implements IWindowDriver {
writeInTerminal(selector: string, text: string): Promise<void> {
return this.channel.call('writeInTerminal', [selector, text]);
}
getLocaleInfo(): Promise<ILocaleInfo> {
return this.channel.call('getLocaleInfo');
}
getLocalizedStrings(): Promise<ILocalizedStrings> {
return this.channel.call('getLocalizedStrings');
}
}
export class WindowDriverRegistryChannelClient implements IWindowDriverRegistry {

View File

@@ -3,24 +3,23 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DriverChannel, WindowDriverRegistryChannel } from 'vs/platform/driver/node/driver';
import { WindowDriverChannelClient } from 'vs/platform/driver/common/driverIpc';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
import { serve as serveNet } from 'vs/base/parts/ipc/node/ipc.net';
import { combinedDisposable, IDisposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IPCServer, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
import { SimpleKeybinding, KeyCode } from 'vs/base/common/keyCodes';
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
import { OS } from 'vs/base/common/platform';
import { Emitter, Event } from 'vs/base/common/event';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { ScanCodeBinding } from 'vs/base/common/scanCode';
import { KeybindingParser } from 'vs/base/common/keybindingParser';
import { timeout } from 'vs/base/common/async';
import { IDriver, IDriverOptions, IElement, IWindowDriver, IWindowDriverRegistry } from 'vs/platform/driver/common/driver';
import { Emitter, Event } from 'vs/base/common/event';
import { KeybindingParser } from 'vs/base/common/keybindingParser';
import { KeyCode, SimpleKeybinding } from 'vs/base/common/keyCodes';
import { combinedDisposable, IDisposable } from 'vs/base/common/lifecycle';
import { OS } from 'vs/base/common/platform';
import { ScanCodeBinding } from 'vs/base/common/scanCode';
import { IPCServer, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
import { serve as serveNet } from 'vs/base/parts/ipc/node/ipc.net';
import { IDriver, IDriverOptions, IElement, ILocaleInfo, ILocalizedStrings, IWindowDriver, IWindowDriverRegistry } from 'vs/platform/driver/common/driver';
import { WindowDriverChannelClient } from 'vs/platform/driver/common/driverIpc';
import { DriverChannel, WindowDriverRegistryChannel } from 'vs/platform/driver/node/driver';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
function isSilentKeyCode(keyCode: KeyCode) {
return keyCode < KeyCode.KEY_0;
@@ -38,8 +37,7 @@ export class Driver implements IDriver, IWindowDriverRegistry {
private windowServer: IPCServer,
private options: IDriverOptions,
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
@INativeHostMainService private readonly nativeHostMainService: INativeHostMainService
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService
) { }
async registerWindowDriver(windowId: number): Promise<IDriverOptions> {
@@ -82,8 +80,8 @@ export class Driver implements IDriver, IWindowDriverRegistry {
this.lifecycleMainService.reload(window);
}
async exitApplication(): Promise<void> {
return this.nativeHostMainService.quit(undefined);
exitApplication(): Promise<boolean> {
return this.lifecycleMainService.quit();
}
async dispatchKeybinding(windowId: number, keybinding: string): Promise<void> {
@@ -189,6 +187,16 @@ export class Driver implements IDriver, IWindowDriverRegistry {
await windowDriver.writeInTerminal(selector, text);
}
async getLocaleInfo(windowId: number): Promise<ILocaleInfo> {
const windowDriver = await this.getWindowDriver(windowId);
return await windowDriver.getLocaleInfo();
}
async getLocalizedStrings(windowId: number): Promise<ILocalizedStrings> {
const windowDriver = await this.getWindowDriver(windowId);
return await windowDriver.getLocalizedStrings();
}
private async getWindowDriver(windowId: number): Promise<IWindowDriver> {
await this.whenUnfrozen(windowId);

View File

@@ -3,12 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { timeout } from 'vs/base/common/async';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { BaseWindowDriver } from 'vs/platform/driver/browser/baseDriver';
import { WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/common/driverIpc';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services';
import { timeout } from 'vs/base/common/async';
import { BaseWindowDriver } from 'vs/platform/driver/browser/baseDriver';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
class WindowDriver extends BaseWindowDriver {

View File

@@ -3,11 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { Client } from 'vs/base/parts/ipc/common/ipc.net';
import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event } from 'vs/base/common/event';
import { IDriver, IElement, IWindowDriverRegistry } from 'vs/platform/driver/common/driver';
import { IDriver, IElement, ILocaleInfo, ILocalizedStrings, IWindowDriverRegistry } from 'vs/platform/driver/common/driver';
export class DriverChannel implements IServerChannel {
@@ -34,6 +34,8 @@ export class DriverChannel implements IServerChannel {
case 'typeInEditor': return this.driver.typeInEditor(arg[0], arg[1], arg[2]);
case 'getTerminalBuffer': return this.driver.getTerminalBuffer(arg[0], arg[1]);
case 'writeInTerminal': return this.driver.writeInTerminal(arg[0], arg[1], arg[2]);
case 'getLocaleInfo': return this.driver.getLocaleInfo(arg);
case 'getLocalizedStrings': return this.driver.getLocalizedStrings(arg);
}
throw new Error(`Call not found: ${command}`);
@@ -58,7 +60,7 @@ export class DriverChannelClient implements IDriver {
return this.channel.call('reloadWindow', windowId);
}
exitApplication(): Promise<void> {
exitApplication(): Promise<boolean> {
return this.channel.call('exitApplication');
}
@@ -105,6 +107,14 @@ export class DriverChannelClient implements IDriver {
writeInTerminal(windowId: number, selector: string, text: string): Promise<void> {
return this.channel.call('writeInTerminal', [windowId, selector, text]);
}
getLocaleInfo(windowId: number): Promise<ILocaleInfo> {
return this.channel.call('getLocaleInfo', windowId);
}
getLocalizedStrings(windowId: number): Promise<ILocalizedStrings> {
return this.channel.call('getLocalizedStrings', windowId);
}
}
export class WindowDriverRegistryChannel implements IServerChannel {

View File

@@ -3,8 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
export interface IEditorModel {
@@ -114,19 +114,24 @@ export interface ITextResourceEditorInput extends IResourceEditorInput, IBaseTex
/**
* This identifier allows to uniquely identify an editor with a
* resource and type identifier.
* resource, type and editor identifier.
*/
export interface IResourceEditorInputIdentifier {
/**
* The resource URI of the editor.
*/
readonly resource: URI;
/**
* The type of the editor.
*/
readonly typeId: string;
/**
* The identifier of the editor if provided.
*/
readonly editorId: string | undefined;
/**
* The resource URI of the editor.
*/
readonly resource: URI;
}
export enum EditorActivation {
@@ -135,7 +140,7 @@ export enum EditorActivation {
* Activate the editor after it opened. This will automatically restore
* the editor if it is minimized.
*/
ACTIVATE,
ACTIVATE = 1,
/**
* Only restore the editor if it is minimized but do not activate it.
@@ -156,17 +161,22 @@ export enum EditorActivation {
PRESERVE
}
export enum EditorOverride {
export enum EditorResolution {
/**
* Displays a picker and allows the user to decide which editor to use
* Displays a picker and allows the user to decide which editor to use.
*/
PICK = 1,
PICK,
/**
* Disables overrides
* Disables editor resolving.
*/
DISABLED
DISABLED,
/**
* Only exclusive editors are considered.
*/
EXCLUSIVE_ONLY
}
export enum EditorOpenContext {
@@ -263,9 +273,9 @@ export interface IEditorOptions {
* Allows to override the editor that should be used to display the input:
* - `undefined`: let the editor decide for itself
* - `string`: specific override by id
* - `EditorOverride`: specific override handling
* - `EditorResolution`: specific override handling
*/
override?: string | EditorOverride;
override?: string | EditorResolution;
/**
* A optional hint to signal in which context the editor opens.

View File

@@ -3,8 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ICommonEncryptionService } from 'vs/platform/encryption/common/encryptionService';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const IEncryptionMainService = createDecorator<IEncryptionMainService>('encryptionMainService');

View File

@@ -29,6 +29,7 @@ export interface NativeParsedArgs {
'prof-startup-prefix'?: string;
'prof-append-timers'?: string;
'prof-v8-extensions'?: boolean;
'no-cached-data'?: boolean;
verbose?: boolean;
trace?: boolean;
'trace-category-filter'?: string;
@@ -60,6 +61,7 @@ export interface NativeParsedArgs {
'enable-proposed-api'?: string[]; // undefined or array of 1 or more
'open-url'?: boolean;
'skip-release-notes'?: boolean;
'skip-welcome'?: boolean;
'disable-telemetry'?: boolean;
'export-default-configuration'?: string;
'install-source'?: string;
@@ -95,6 +97,7 @@ export interface NativeParsedArgs {
// chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches
'no-proxy-server'?: boolean;
'no-sandbox'?: boolean;
'proxy-server'?: string;
'proxy-bypass-list'?: string;
'proxy-pac-url'?: string;

View File

@@ -3,10 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator, refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { ExtensionKind } from 'vs/platform/extensions/common/extensions';
import { createDecorator, refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation';
export const IEnvironmentService = createDecorator<IEnvironmentService>('environmentService');
export const INativeEnvironmentService = refineServiceDecorator<IEnvironmentService, INativeEnvironmentService>(IEnvironmentService);
@@ -62,6 +62,7 @@ export interface IEnvironmentService {
debugExtensionHost: IExtensionHostDebugParams;
isExtensionDevelopment: boolean;
disableExtensions: boolean | string[];
enableExtensions?: readonly string[];
extensionDevelopmentLocationURI?: URI[];
extensionDevelopmentKind?: ExtensionKind[];
extensionTestsLocationURI?: URI;

View File

@@ -3,17 +3,17 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IProductService } from 'vs/platform/product/common/productService';
import { IDebugParams, IExtensionHostDebugParams, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { dirname, join, normalize, resolve } from 'vs/base/common/path';
import { joinPath } from 'vs/base/common/resources';
import { memoize } from 'vs/base/common/decorators';
import { toLocalISOString } from 'vs/base/common/date';
import { memoize } from 'vs/base/common/decorators';
import { FileAccess } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { ExtensionKind } from 'vs/platform/extensions/common/extensions';
import { dirname, join, normalize, resolve } from 'vs/base/common/path';
import { env } from 'vs/base/common/process';
import { joinPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { IDebugParams, IExtensionHostDebugParams, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { ExtensionKind } from 'vs/platform/extensions/common/extensions';
import { IProductService } from 'vs/platform/product/common/productService';
export interface INativeEnvironmentPaths {

View File

@@ -3,12 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { join } from 'vs/base/common/path';
import { memoize } from 'vs/base/common/decorators';
import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation';
import { join } from 'vs/base/common/path';
import { createStaticIPCHandle } from 'vs/base/parts/ipc/node/ipc.net';
import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
import { createStaticIPCHandle } from 'vs/base/parts/ipc/node/ipc.net';
import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation';
export const IEnvironmentMainService = refineServiceDecorator<IEnvironmentService, IEnvironmentMainService>(IEnvironmentService);
@@ -25,11 +25,13 @@ export interface IEnvironmentMainService extends INativeEnvironmentService {
backupHome: string;
backupWorkspacesPath: string;
// --- V8 code cache path
codeCachePath?: string;
// --- V8 code caching
codeCachePath: string | undefined;
useCodeCache: boolean;
// --- IPC
mainIPCHandle: string;
mainLockfile: string;
// --- config
sandbox: boolean;
@@ -52,6 +54,9 @@ export class EnvironmentMainService extends NativeEnvironmentService implements
@memoize
get mainIPCHandle(): string { return createStaticIPCHandle(this.userDataPath, 'main', this.productService.version); }
@memoize
get mainLockfile(): string { return join(this.userDataPath, 'code.lock'); }
@memoize
get sandbox(): boolean { return !!this.args['__sandbox']; }
@@ -66,4 +71,7 @@ export class EnvironmentMainService extends NativeEnvironmentService implements
@memoize
get codeCachePath(): string | undefined { return process.env['VSCODE_CODE_CACHE_PATH'] || undefined; }
@memoize
get useCodeCache(): boolean { return !!this.codeCachePath; }
}

View File

@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import * as minimist from 'minimist';
import { localize } from 'vs/nls';
import { isWindows } from 'vs/base/common/platform';
import { localize } from 'vs/nls';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
/**
@@ -65,6 +65,7 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
'status': { type: 'boolean', alias: 's', cat: 't', description: localize('status', "Print process usage and diagnostics information.") },
'prof-startup': { type: 'boolean', cat: 't', description: localize('prof-startup', "Run CPU profiler during startup.") },
'prof-append-timers': { type: 'string' },
'no-cached-data': { type: 'boolean' },
'prof-startup-prefix': { type: 'string' },
'prof-v8-extensions': { type: 'boolean' },
'disable-extensions': { type: 'boolean', deprecates: 'disableExtensions', cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") },
@@ -94,6 +95,7 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
'driver': { type: 'string' },
'logExtensionHostCommunication': { type: 'boolean' },
'skip-release-notes': { type: 'boolean' },
'skip-welcome': { type: 'boolean' },
'disable-telemetry': { type: 'boolean' },
'disable-updates': { type: 'boolean' },
'disable-keytar': { type: 'boolean' },
@@ -130,6 +132,12 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
// chromium flags
'no-proxy-server': { type: 'boolean' },
// Minimist incorrectly parses keys that start with `--no`
// https://github.com/substack/minimist/blob/aeb3e27dae0412de5c0494e9563a5f10c82cc7a9/index.js#L118-L121
// If --no-sandbox is passed via cli wrapper it will be treated as --sandbox which is incorrect, we use
// the alias here to make sure --no-sandbox is always respected.
// For https://github.com/microsoft/vscode/issues/128279
'no-sandbox': { type: 'boolean', alias: 'sandbox' },
'proxy-server': { type: 'string' },
'proxy-bypass-list': { type: 'string' },
'proxy-pac-url': { type: 'string' },

View File

@@ -4,11 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { localize } from 'vs/nls';
import { MIN_MAX_MEMORY_SIZE_MB } from 'vs/platform/files/common/files';
import { parseArgs, ErrorReporter, OPTIONS } from 'vs/platform/environment/node/argv';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { localize } from 'vs/nls';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { ErrorReporter, OPTIONS, parseArgs } from 'vs/platform/environment/node/argv';
import { MIN_MAX_MEMORY_SIZE_MB } from 'vs/platform/files/common/files';
function parseAndValidate(cmdLineArgs: string[], reportWarnings: boolean): NativeParsedArgs {
const errorReporter: ErrorReporter = {

View File

@@ -5,8 +5,8 @@
import { homedir, tmpdir } from 'os';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { getUserDataPath } from 'vs/platform/environment/node/userDataPath';
import { AbstractNativeEnvironmentService } from 'vs/platform/environment/common/environmentService';
import { getUserDataPath } from 'vs/platform/environment/node/userDataPath';
import { IProductService } from 'vs/platform/product/common/productService';
export class NativeEnvironmentService extends AbstractNativeEnvironmentService {

View File

@@ -3,15 +3,17 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import { spawn } from 'child_process';
import { generateUuid } from 'vs/base/common/uuid';
import * as path from 'path';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { canceled, isPromiseCanceledError } from 'vs/base/common/errors';
import { IProcessEnvironment, isWindows, OS } from 'vs/base/common/platform';
import { ILogService } from 'vs/platform/log/common/log';
import { generateUuid } from 'vs/base/common/uuid';
import { getSystemShell } from 'vs/base/node/shell';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { getSystemShell } from 'vs/base/node/shell';
import { ILogService } from 'vs/platform/log/common/log';
/**
* We need to get the environment from a user's shell.
@@ -49,8 +51,37 @@ export async function resolveShellEnv(logService: ILogService, args: NativeParse
logService.trace('resolveShellEnv(): running (macOS/Linux)');
}
// Call this only once and cache the promise for
// subsequent calls since this operation can be
// expensive (spawns a process).
if (!unixShellEnvPromise) {
unixShellEnvPromise = doResolveUnixShellEnv(logService);
unixShellEnvPromise = new Promise(async resolve => {
const cts = new CancellationTokenSource();
// Give up resolving shell env after 10 seconds
const timeout = setTimeout(() => {
logService.error(`[resolve shell env] Could not resolve shell environment within 10 seconds. Proceeding without shell environment...`);
cts.dispose(true);
resolve({});
}, 10000);
// Resolve shell env and handle errors
try {
const shellEnv = await doResolveUnixShellEnv(logService, cts.token);
resolve(shellEnv);
} catch (error) {
if (!isPromiseCanceledError(error)) {
logService.error(`[resolve shell env] Unable to resolve shell environment (${error}). Proceeding without shell environment...`);
}
resolve({});
} finally {
clearTimeout(timeout);
cts.dispose();
}
});
}
return unixShellEnvPromise;
@@ -59,7 +90,7 @@ export async function resolveShellEnv(logService: ILogService, args: NativeParse
let unixShellEnvPromise: Promise<typeof process.env> | undefined = undefined;
async function doResolveUnixShellEnv(logService: ILogService): Promise<typeof process.env> {
async function doResolveUnixShellEnv(logService: ILogService, token: CancellationToken): Promise<typeof process.env> {
const promise = new Promise<typeof process.env>(async (resolve, reject) => {
const runAsNode = process.env['ELECTRON_RUN_AS_NODE'];
logService.trace('getUnixShellEnvironment#runAsNode', runAsNode);
@@ -80,6 +111,10 @@ async function doResolveUnixShellEnv(logService: ILogService): Promise<typeof pr
const systemShellUnix = await getSystemShell(OS, env);
logService.trace('getUnixShellEnvironment#shell', systemShellUnix);
if (token.isCancellationRequested) {
return reject(canceled);
}
// handle popular non-POSIX shells
const name = path.basename(systemShellUnix);
let command: string, shellArgs: Array<string>;
@@ -101,6 +136,12 @@ async function doResolveUnixShellEnv(logService: ILogService): Promise<typeof pr
env
});
token.onCancellationRequested(() => {
child.kill();
return reject(canceled);
});
child.on('error', err => {
logService.error('getUnixShellEnvironment#errorChildProcess', toErrorMessage(err));
resolve({});

View File

@@ -5,9 +5,9 @@
/**
* This code is also used by standalone cli's. Avoid adding dependencies to keep the size of the cli small.
*/
import * as paths from 'vs/base/common/path';
import * as fs from 'fs';
import * as os from 'os';
import * as paths from 'vs/base/common/path';
import { resolveTerminalEncoding } from 'vs/base/node/terminalEncoding';
export function hasStdinWithoutTty() {
@@ -36,7 +36,7 @@ export function stdinDataListener(durationinMs: number): Promise<boolean> {
}
export function getStdinFilePath(): string {
return paths.join(os.tmpdir(), `code-stdin-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 3)}.txt`);
return paths.join(os.tmpdir(), `code-stdin-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 3)}`);
}
export async function readFromStdin(targetPath: string, verbose: boolean): Promise<void> {

View File

@@ -8,7 +8,7 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
/**
* Returns the user data path to use with some rules:
* - respect portable mode
* - respect --user-data-dir CLI argument
* - respect VSCODE_APPDATA environment variable
* - respect --user-data-dir CLI argument
*/
export function getUserDataPath(args: NativeParsedArgs): string;

View File

@@ -54,38 +54,42 @@
return path.join(portablePath, 'user-data');
}
// 2. Support explicit --user-data-dir
// 2. Support global VSCODE_APPDATA environment variable
let appDataPath = process.env['VSCODE_APPDATA'];
if (appDataPath) {
return path.join(appDataPath, productName);
}
// With Electron>=13 --user-data-dir switch will be propagated to
// all processes https://github.com/electron/electron/blob/1897b14af36a02e9aa7e4d814159303441548251/shell/browser/electron_browser_client.cc#L546-L553
// Check VSCODE_PORTABLE and VSCODE_APPDATA before this case to get correct values.
// 3. Support explicit --user-data-dir
const cliPath = cliArgs['user-data-dir'];
if (cliPath) {
return cliPath;
}
// 3. Support global VSCODE_APPDATA environment variable
let appDataPath = process.env['VSCODE_APPDATA'];
// 4. Otherwise check per platform
if (!appDataPath) {
switch (process.platform) {
case 'win32':
appDataPath = process.env['APPDATA'];
if (!appDataPath) {
const userProfile = process.env['USERPROFILE'];
if (typeof userProfile !== 'string') {
throw new Error('Windows: Unexpected undefined %USERPROFILE% environment variable');
}
appDataPath = path.join(userProfile, 'AppData', 'Roaming');
switch (process.platform) {
case 'win32':
appDataPath = process.env['APPDATA'];
if (!appDataPath) {
const userProfile = process.env['USERPROFILE'];
if (typeof userProfile !== 'string') {
throw new Error('Windows: Unexpected undefined %USERPROFILE% environment variable');
}
break;
case 'darwin':
appDataPath = path.join(os.homedir(), 'Library', 'Application Support');
break;
case 'linux':
appDataPath = process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
break;
default:
throw new Error('Platform not supported');
}
appDataPath = path.join(userProfile, 'AppData', 'Roaming');
}
break;
case 'darwin':
appDataPath = path.join(os.homedir(), 'Library', 'Application Support');
break;
case 'linux':
appDataPath = process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
break;
default:
throw new Error('Platform not supported');
}
return path.join(appDataPath, 'azuredatastudio'); // {{SQL CARBON EDIT}} hard-code Azure Data Studio

View File

@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
import { parseExtensionHostPort } from 'vs/platform/environment/common/environmentService';
import { OPTIONS, parseArgs } from 'vs/platform/environment/node/argv';
import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
import product from 'vs/platform/product/common/product';

View File

@@ -42,9 +42,9 @@ suite('Native Modules (all platforms)', () => {
assert.ok(typeof nsfWatcher === 'function', testErrorMessage('nsfw'));
});
test('vscode-sqlite3', async () => {
const sqlite3 = await import('vscode-sqlite3');
assert.ok(typeof sqlite3.Database === 'function', testErrorMessage('vscode-sqlite3'));
test('sqlite3', async () => {
const sqlite3 = await import('@vscode/sqlite3');
assert.ok(typeof sqlite3.Database === 'function', testErrorMessage('@vscode/sqlite3'));
});
});

View File

@@ -0,0 +1,654 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Barrier, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { canceled, getErrorMessage } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { isWeb } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import * as nls from 'vs/nls';
import {
DidUninstallExtensionEvent, ExtensionManagementError, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementParticipant, IExtensionManagementService, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallExtensionEvent, InstallExtensionResult, InstallOperation, InstallOptions,
InstallVSIXOptions, INSTALL_ERROR_INCOMPATIBLE, INSTALL_ERROR_MALICIOUS, IReportedExtension, StatisticType, UninstallOptions
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions, ExtensionIdentifierWithVersion, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
import product from 'vs/platform/product/common/product';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
export const INSTALL_ERROR_VALIDATING = 'validating';
export const ERROR_UNKNOWN = 'unknown';
export const INSTALL_ERROR_LOCAL = 'local';
export interface IInstallExtensionTask {
readonly identifier: IExtensionIdentifier;
readonly source: IGalleryExtension | URI;
readonly operation: InstallOperation;
run(): Promise<ILocalExtension>;
waitUntilTaskIsFinished(): Promise<ILocalExtension>;
cancel(): void;
}
export type UninstallExtensionTaskOptions = { readonly remove?: boolean; readonly versionOnly?: boolean };
export interface IUninstallExtensionTask {
readonly extension: ILocalExtension;
run(): Promise<void>;
waitUntilTaskIsFinished(): Promise<void>;
cancel(): void;
}
export abstract class AbstractExtensionManagementService extends Disposable implements IExtensionManagementService {
declare readonly _serviceBrand: undefined;
private reportedExtensions: Promise<IReportedExtension[]> | undefined;
private lastReportTimestamp = 0;
private readonly installingExtensions = new Map<string, IInstallExtensionTask>();
private readonly uninstallingExtensions = new Map<string, IUninstallExtensionTask>();
private readonly _onInstallExtension = this._register(new Emitter<InstallExtensionEvent>());
readonly onInstallExtension: Event<InstallExtensionEvent> = this._onInstallExtension.event;
protected readonly _onDidInstallExtensions = this._register(new Emitter<InstallExtensionResult[]>());
readonly onDidInstallExtensions = this._onDidInstallExtensions.event;
protected readonly _onUninstallExtension = this._register(new Emitter<IExtensionIdentifier>());
readonly onUninstallExtension: Event<IExtensionIdentifier> = this._onUninstallExtension.event;
protected _onDidUninstallExtension = this._register(new Emitter<DidUninstallExtensionEvent>());
onDidUninstallExtension: Event<DidUninstallExtensionEvent> = this._onDidUninstallExtension.event;
private readonly participants: IExtensionManagementParticipant[] = [];
constructor(
@IExtensionGalleryService protected readonly galleryService: IExtensionGalleryService,
@ITelemetryService protected readonly telemetryService: ITelemetryService,
@ILogService protected readonly logService: ILogService,
) {
super();
this._register(toDisposable(() => {
this.installingExtensions.forEach(task => task.cancel());
this.uninstallingExtensions.forEach(promise => promise.cancel());
this.installingExtensions.clear();
this.uninstallingExtensions.clear();
}));
}
async installFromGallery(extension: IGalleryExtension, options: InstallOptions = {}): Promise<ILocalExtension> {
if (!this.galleryService.isEnabled()) {
throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"));
}
try {
extension = await this.checkAndGetCompatibleVersion(extension);
} catch (error) {
this.logService.error(getErrorMessage(error));
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
throw error;
}
if (!await this.canInstall(extension)) {
const error = new ExtensionManagementError(`Not supported`, INSTALL_ERROR_VALIDATING);
this.logService.error(`Canno install extension as it is not supported.`, extension.identifier.id, error.message);
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
throw error;
}
const manifest = await this.galleryService.getManifest(extension, CancellationToken.None);
if (manifest === null) {
const error = new ExtensionManagementError(`Missing manifest for extension ${extension.identifier.id}`, INSTALL_ERROR_VALIDATING);
this.logService.error(`Failed to install extension:`, extension.identifier.id, error.message);
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
throw error;
}
return this.installExtension(manifest, extension, options);
}
async uninstall(extension: ILocalExtension, options: UninstallOptions = {}): Promise<void> {
this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id);
return this.unininstallExtension(extension, options);
}
async reinstallFromGallery(extension: ILocalExtension): Promise<void> {
this.logService.trace('ExtensionManagementService#reinstallFromGallery', extension.identifier.id);
if (!this.galleryService.isEnabled()) {
throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"));
}
const galleryExtension = await this.findGalleryExtension(extension);
if (!galleryExtension) {
throw new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled"));
}
await this.createUninstallExtensionTask(extension, { remove: true, versionOnly: true }).run();
await this.installFromGallery(galleryExtension);
}
getExtensionsReport(): Promise<IReportedExtension[]> {
const now = new Date().getTime();
if (!this.reportedExtensions || now - this.lastReportTimestamp > 1000 * 60 * 5) { // 5 minute cache freshness
this.reportedExtensions = this.updateReportCache();
this.lastReportTimestamp = now;
}
return this.reportedExtensions;
}
registerParticipant(participant: IExtensionManagementParticipant): void {
this.participants.push(participant);
}
protected async installExtension(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): Promise<ILocalExtension> {
// only cache gallery extensions tasks
if (!URI.isUri(extension)) {
let installExtensionTask = this.installingExtensions.get(new ExtensionIdentifierWithVersion(extension.identifier, extension.version).key());
if (installExtensionTask) {
this.logService.info('Extensions is already requested to install', extension.identifier.id);
return installExtensionTask.waitUntilTaskIsFinished();
}
options = { ...options, installOnlyNewlyAddedFromExtensionPack: true /* always true for gallery extensions */ };
}
const allInstallExtensionTasks: { task: IInstallExtensionTask, manifest: IExtensionManifest }[] = [];
const installResults: (InstallExtensionResult & { local: ILocalExtension })[] = [];
const installExtensionTask = this.createInstallExtensionTask(manifest, extension, options);
if (!URI.isUri(extension)) {
this.installingExtensions.set(new ExtensionIdentifierWithVersion(installExtensionTask.identifier, manifest.version).key(), installExtensionTask);
}
this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension });
this.logService.info('Installing extension:', installExtensionTask.identifier.id);
allInstallExtensionTasks.push({ task: installExtensionTask, manifest });
let installExtensionHasDependents: boolean = false;
try {
if (options.donotIncludePackAndDependencies) {
this.logService.info('Installing the extension without checking dependencies and pack', installExtensionTask.identifier.id);
} else {
try {
const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensionsToInstall(installExtensionTask.identifier, manifest, !!options.installOnlyNewlyAddedFromExtensionPack);
for (const { gallery, manifest } of allDepsAndPackExtensionsToInstall) {
installExtensionHasDependents = installExtensionHasDependents || !!manifest.extensionDependencies?.some(id => areSameExtensions({ id }, installExtensionTask.identifier));
if (this.installingExtensions.has(new ExtensionIdentifierWithVersion(gallery.identifier, gallery.version).key())) {
this.logService.info('Extension is already requested to install', gallery.identifier.id);
} else {
const task = this.createInstallExtensionTask(manifest, gallery, { ...options, donotIncludePackAndDependencies: true });
this.installingExtensions.set(new ExtensionIdentifierWithVersion(task.identifier, manifest.version).key(), task);
this._onInstallExtension.fire({ identifier: task.identifier, source: gallery });
this.logService.info('Installing extension:', task.identifier.id);
allInstallExtensionTasks.push({ task, manifest });
}
}
} catch (error) {
this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', installExtensionTask.identifier.id);
this.logService.error(error);
throw error;
}
}
const extensionsToInstallMap = allInstallExtensionTasks.reduce((result, { task, manifest }) => {
result.set(task.identifier.id.toLowerCase(), { task, manifest });
return result;
}, new Map<string, { task: IInstallExtensionTask, manifest: IExtensionManifest }>());
while (extensionsToInstallMap.size) {
let extensionsToInstall;
const extensionsWithoutDepsToInstall = [...extensionsToInstallMap.values()].filter(({ manifest }) => !manifest.extensionDependencies?.some(id => extensionsToInstallMap.has(id.toLowerCase())));
if (extensionsWithoutDepsToInstall.length) {
extensionsToInstall = extensionsToInstallMap.size === 1 ? extensionsWithoutDepsToInstall
/* If the main extension has no dependents remove it and install it at the end */
: extensionsWithoutDepsToInstall.filter(({ task }) => !(task === installExtensionTask && !installExtensionHasDependents));
} else {
this.logService.info('Found extensions with circular dependencies', extensionsWithoutDepsToInstall.map(({ task }) => task.identifier.id));
extensionsToInstall = [...extensionsToInstallMap.values()];
}
// Install extensions in parallel and wait until all extensions are installed / failed
await this.joinAllSettled(extensionsToInstall.map(async ({ task }) => {
const startTime = new Date().getTime();
try {
const local = await task.run();
await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, options, CancellationToken.None)));
if (!URI.isUri(task.source)) {
reportTelemetry(this.telemetryService, task.operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install', getGalleryExtensionTelemetryData(task.source), new Date().getTime() - startTime, undefined);
// In web, report extension install statistics explicitly. In Desktop, statistics are automatically updated while downloading the VSIX.
if (isWeb && task.operation === InstallOperation.Install) {
try {
await this.galleryService.reportStatistic(local.manifest.publisher, local.manifest.name, local.manifest.version, StatisticType.Install);
} catch (error) { /* ignore */ }
}
}
installResults.push({ local, identifier: task.identifier, operation: task.operation, source: task.source });
} catch (error) {
if (!URI.isUri(task.source)) {
reportTelemetry(this.telemetryService, task.operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install', getGalleryExtensionTelemetryData(task.source), new Date().getTime() - startTime, error);
}
this.logService.error('Error while installing the extension:', task.identifier.id);
this.logService.error(error);
throw error;
} finally { extensionsToInstallMap.delete(task.identifier.id.toLowerCase()); }
}));
}
installResults.forEach(({ identifier }) => this.logService.info(`Extension installed successfully:`, identifier.id));
this._onDidInstallExtensions.fire(installResults);
return installResults.filter(({ identifier }) => areSameExtensions(identifier, installExtensionTask.identifier))[0].local;
} catch (error) {
// cancel all tasks
allInstallExtensionTasks.forEach(({ task }) => task.cancel());
// rollback installed extensions
if (installResults.length) {
try {
const result = await Promise.allSettled(installResults.map(({ local }) => this.createUninstallExtensionTask(local, { versionOnly: true }).run()));
for (let index = 0; index < result.length; index++) {
const r = result[index];
const { identifier } = installResults[index];
if (r.status === 'fulfilled') {
this.logService.info('Rollback: Uninstalled extension', identifier.id);
} else {
this.logService.warn('Rollback: Error while uninstalling extension', identifier.id, getErrorMessage(r.reason));
}
}
} catch (error) {
// ignore error
this.logService.warn('Error while rolling back extensions', getErrorMessage(error), installResults.map(({ identifier }) => identifier.id));
}
}
this.logService.error(`Failed to install extension:`, installExtensionTask.identifier.id, getErrorMessage(error));
this._onDidInstallExtensions.fire(allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source })));
if (error instanceof Error) {
error.name = error && (<ExtensionManagementError>error).code ? (<ExtensionManagementError>error).code : ERROR_UNKNOWN;
}
throw error;
} finally {
/* Remove the gallery tasks from the cache */
for (const { task, manifest } of allInstallExtensionTasks) {
if (!URI.isUri(task.source)) {
const key = new ExtensionIdentifierWithVersion(task.identifier, manifest.version).key();
if (!this.installingExtensions.delete(key)) {
this.logService.warn('Installation task is not found in the cache', key);
}
}
}
}
}
private async joinAllSettled<T>(promises: Promise<T>[]): Promise<T[]> {
const results: T[] = [];
const errors: any[] = [];
const promiseResults = await Promise.allSettled(promises);
for (const r of promiseResults) {
if (r.status === 'fulfilled') {
results.push(r.value);
} else {
errors.push(r.reason);
}
}
// If there are errors, throw the error.
if (errors.length) { throw joinErrors(errors); }
return results;
}
private async getAllDepsAndPackExtensionsToInstall(extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest, getOnlyNewlyAddedFromExtensionPack: boolean): Promise<{ gallery: IGalleryExtension, manifest: IExtensionManifest }[]> {
if (!this.galleryService.isEnabled()) {
return [];
}
let installed = await this.getInstalled();
const knownIdentifiers = [extensionIdentifier, ...(installed).map(i => i.identifier)];
const allDependenciesAndPacks: { gallery: IGalleryExtension, manifest: IExtensionManifest }[] = [];
const collectDependenciesAndPackExtensionsToInstall = async (extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest): Promise<void> => {
const dependenciesAndPackExtensions: string[] = manifest.extensionDependencies || [];
if (manifest.extensionPack) {
const existing = getOnlyNewlyAddedFromExtensionPack ? installed.find(e => areSameExtensions(e.identifier, extensionIdentifier)) : undefined;
for (const extension of manifest.extensionPack) {
// add only those extensions which are new in currently installed extension
if (!(existing && existing.manifest.extensionPack && existing.manifest.extensionPack.some(old => areSameExtensions({ id: old }, { id: extension })))) {
if (dependenciesAndPackExtensions.every(e => !areSameExtensions({ id: e }, { id: extension }))) {
dependenciesAndPackExtensions.push(extension);
}
}
}
}
if (dependenciesAndPackExtensions.length) {
// filter out installed and known extensions
const identifiers = [...knownIdentifiers, ...allDependenciesAndPacks.map(r => r.gallery.identifier)];
const names = dependenciesAndPackExtensions.filter(id => identifiers.every(galleryIdentifier => !areSameExtensions(galleryIdentifier, { id })));
if (names.length) {
const galleryResult = await this.galleryService.query({ names, pageSize: dependenciesAndPackExtensions.length }, CancellationToken.None);
for (const galleryExtension of galleryResult.firstPage) {
if (identifiers.find(identifier => areSameExtensions(identifier, galleryExtension.identifier))) {
continue;
}
const compatibleExtension = await this.checkAndGetCompatibleVersion(galleryExtension);
if (!await this.canInstall(compatibleExtension)) {
this.logService.info('Skipping the extension as it cannot be installed', compatibleExtension.identifier.id);
continue;
}
const manifest = await this.galleryService.getManifest(compatibleExtension, CancellationToken.None);
if (manifest === null) {
throw new ExtensionManagementError(`Missing manifest for extension ${compatibleExtension.identifier.id}`, INSTALL_ERROR_VALIDATING);
}
allDependenciesAndPacks.push({ gallery: compatibleExtension, manifest });
await collectDependenciesAndPackExtensionsToInstall(compatibleExtension.identifier, manifest);
}
}
}
};
await collectDependenciesAndPackExtensionsToInstall(extensionIdentifier, manifest);
installed = await this.getInstalled();
return allDependenciesAndPacks.filter(e => !installed.some(i => areSameExtensions(i.identifier, e.gallery.identifier)));
}
private async checkAndGetCompatibleVersion(extension: IGalleryExtension): Promise<IGalleryExtension> {
if (await this.isMalicious(extension)) {
throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), INSTALL_ERROR_MALICIOUS);
}
const compatibleExtension = await this.galleryService.getCompatibleExtension(extension);
if (!compatibleExtension) {
throw new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of VS Code (version {1}).", extension.identifier.id, product.version), INSTALL_ERROR_INCOMPATIBLE);
}
return compatibleExtension;
}
private async isMalicious(extension: IGalleryExtension): Promise<boolean> {
const report = await this.getExtensionsReport();
return getMaliciousExtensionsSet(report).has(extension.identifier.id);
}
private async unininstallExtension(extension: ILocalExtension, options: UninstallOptions): Promise<void> {
const uninstallExtensionTask = this.uninstallingExtensions.get(extension.identifier.id.toLowerCase());
if (uninstallExtensionTask) {
this.logService.info('Extensions is already requested to uninstall', extension.identifier.id);
return uninstallExtensionTask.waitUntilTaskIsFinished();
}
const createUninstallExtensionTask = (extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask => {
const uninstallExtensionTask = this.createUninstallExtensionTask(extension, options);
this.uninstallingExtensions.set(uninstallExtensionTask.extension.identifier.id.toLowerCase(), uninstallExtensionTask);
this.logService.info('Uninstalling extension:', extension.identifier.id);
this._onUninstallExtension.fire(extension.identifier);
return uninstallExtensionTask;
};
const postUninstallExtension = (extension: ILocalExtension, error?: ExtensionManagementError): void => {
if (error) {
this.logService.error('Failed to uninstall extension:', extension.identifier.id, error.message);
} else {
this.logService.info('Successfully uninstalled extension:', extension.identifier.id);
}
reportTelemetry(this.telemetryService, 'extensionGallery:uninstall', getLocalExtensionTelemetryData(extension), undefined, error);
this._onDidUninstallExtension.fire({ identifier: extension.identifier, error: error?.code });
};
const allTasks: IUninstallExtensionTask[] = [];
const processedTasks: IUninstallExtensionTask[] = [];
try {
allTasks.push(createUninstallExtensionTask(extension, {}));
const installed = await this.getInstalled(ExtensionType.User);
if (options.donotIncludePack) {
this.logService.info('Uninstalling the extension without including packed extension', extension.identifier.id);
} else {
const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed);
for (const packedExtension of packedExtensions) {
if (this.uninstallingExtensions.has(packedExtension.identifier.id.toLowerCase())) {
this.logService.info('Extensions is already requested to uninstall', packedExtension.identifier.id);
} else {
allTasks.push(createUninstallExtensionTask(packedExtension, {}));
}
}
}
if (options.donotCheckDependents) {
this.logService.info('Uninstalling the extension without checking dependents', extension.identifier.id);
} else {
this.checkForDependents(allTasks.map(task => task.extension), installed, extension);
}
// Uninstall extensions in parallel and wait until all extensions are uninstalled / failed
await this.joinAllSettled(allTasks.map(async task => {
try {
await task.run();
await this.joinAllSettled(this.participants.map(participant => participant.postUninstall(task.extension, options, CancellationToken.None)));
// only report if extension has a mapped gallery extension. UUID identifies the gallery extension.
if (task.extension.identifier.uuid) {
try {
await this.galleryService.reportStatistic(task.extension.manifest.publisher, task.extension.manifest.name, task.extension.manifest.version, StatisticType.Uninstall);
} catch (error) { /* ignore */ }
}
postUninstallExtension(task.extension);
} catch (e) {
const error = e instanceof ExtensionManagementError ? e : new ExtensionManagementError(getErrorMessage(e), ERROR_UNKNOWN);
postUninstallExtension(task.extension, error);
throw error;
} finally {
processedTasks.push(task);
}
}));
} catch (e) {
const error = e instanceof ExtensionManagementError ? e : new ExtensionManagementError(getErrorMessage(e), ERROR_UNKNOWN);
for (const task of allTasks) {
// cancel the tasks
try { task.cancel(); } catch (error) { /* ignore */ }
if (!processedTasks.includes(task)) {
postUninstallExtension(task.extension, error);
}
}
throw error;
} finally {
// Remove tasks from cache
for (const task of allTasks) {
if (!this.uninstallingExtensions.delete(task.extension.identifier.id.toLowerCase())) {
this.logService.warn('Uninstallation task is not found in the cache', task.extension.identifier.id);
}
}
}
}
private checkForDependents(extensionsToUninstall: ILocalExtension[], installed: ILocalExtension[], extensionToUninstall: ILocalExtension): void {
for (const extension of extensionsToUninstall) {
const dependents = this.getDependents(extension, installed);
if (dependents.length) {
const remainingDependents = dependents.filter(dependent => !extensionsToUninstall.some(e => areSameExtensions(e.identifier, dependent.identifier)));
if (remainingDependents.length) {
throw new Error(this.getDependentsErrorMessage(extension, remainingDependents, extensionToUninstall));
}
}
}
}
private getDependentsErrorMessage(dependingExtension: ILocalExtension, dependents: ILocalExtension[], extensionToUninstall: ILocalExtension): string {
if (extensionToUninstall === dependingExtension) {
if (dependents.length === 1) {
return nls.localize('singleDependentError', "Cannot uninstall '{0}' extension. '{1}' extension depends on this.",
extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name);
}
if (dependents.length === 2) {
return nls.localize('twoDependentsError', "Cannot uninstall '{0}' extension. '{1}' and '{2}' extensions depend on this.",
extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
}
return nls.localize('multipleDependentsError', "Cannot uninstall '{0}' extension. '{1}', '{2}' and other extension depend on this.",
extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
}
if (dependents.length === 1) {
return nls.localize('singleIndirectDependentError', "Cannot uninstall '{0}' extension . It includes uninstalling '{1}' extension and '{2}' extension depends on this.",
extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName
|| dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name);
}
if (dependents.length === 2) {
return nls.localize('twoIndirectDependentsError', "Cannot uninstall '{0}' extension. It includes uninstalling '{1}' extension and '{2}' and '{3}' extensions depend on this.",
extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName
|| dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
}
return nls.localize('multipleIndirectDependentsError', "Cannot uninstall '{0}' extension. It includes uninstalling '{1}' extension and '{2}', '{3}' and other extensions depend on this.",
extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName
|| dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
}
private getAllPackExtensionsToUninstall(extension: ILocalExtension, installed: ILocalExtension[], checked: ILocalExtension[] = []): ILocalExtension[] {
if (checked.indexOf(extension) !== -1) {
return [];
}
checked.push(extension);
const extensionsPack = extension.manifest.extensionPack ? extension.manifest.extensionPack : [];
if (extensionsPack.length) {
const packedExtensions = installed.filter(i => !i.isBuiltin && extensionsPack.some(id => areSameExtensions({ id }, i.identifier)));
const packOfPackedExtensions: ILocalExtension[] = [];
for (const packedExtension of packedExtensions) {
packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked));
}
return [...packedExtensions, ...packOfPackedExtensions];
}
return [];
}
private getDependents(extension: ILocalExtension, installed: ILocalExtension[]): ILocalExtension[] {
return installed.filter(e => e.manifest.extensionDependencies && e.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier)));
}
private async findGalleryExtension(local: ILocalExtension): Promise<IGalleryExtension> {
if (local.identifier.uuid) {
const galleryExtension = await this.findGalleryExtensionById(local.identifier.uuid);
return galleryExtension ? galleryExtension : this.findGalleryExtensionByName(local.identifier.id);
}
return this.findGalleryExtensionByName(local.identifier.id);
}
private async findGalleryExtensionById(uuid: string): Promise<IGalleryExtension> {
const galleryResult = await this.galleryService.query({ ids: [uuid], pageSize: 1 }, CancellationToken.None);
return galleryResult.firstPage[0];
}
private async findGalleryExtensionByName(name: string): Promise<IGalleryExtension> {
const galleryResult = await this.galleryService.query({ names: [name], pageSize: 1 }, CancellationToken.None);
return galleryResult.firstPage[0];
}
private async updateReportCache(): Promise<IReportedExtension[]> {
try {
this.logService.trace('ExtensionManagementService.refreshReportedCache');
const result = await this.galleryService.getExtensionsReport();
this.logService.trace(`ExtensionManagementService.refreshReportedCache - got ${result.length} reported extensions from service`);
return result;
} catch (err) {
this.logService.trace('ExtensionManagementService.refreshReportedCache - failed to get extension report');
return [];
}
}
abstract zip(extension: ILocalExtension): Promise<URI>;
abstract unzip(zipLocation: URI): Promise<IExtensionIdentifier>;
abstract getManifest(vsix: URI): Promise<IExtensionManifest>;
abstract install(vsix: URI, options?: InstallVSIXOptions): Promise<ILocalExtension>;
abstract canInstall(extension: IGalleryExtension): Promise<boolean>;
abstract getInstalled(type?: ExtensionType): Promise<ILocalExtension[]>;
abstract updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension>;
abstract updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise<ILocalExtension>;
protected abstract createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): IInstallExtensionTask;
protected abstract createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask;
}
export function joinErrors(errorOrErrors: (Error | string) | (Array<Error | string>)): Error {
const errors = Array.isArray(errorOrErrors) ? errorOrErrors : [errorOrErrors];
if (errors.length === 1) {
return errors[0] instanceof Error ? <Error>errors[0] : new Error(<string>errors[0]);
}
return errors.reduce<Error>((previousValue: Error, currentValue: Error | string) => {
return new Error(`${previousValue.message}${previousValue.message ? ',' : ''}${currentValue instanceof Error ? currentValue.message : currentValue}`);
}, new Error(''));
}
export function reportTelemetry(telemetryService: ITelemetryService, eventName: string, extensionData: any, duration?: number, error?: Error): void {
const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : undefined;
/* __GDPR__
"extensionGallery:install" : {
"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"recommendationReason": { "retiredFromVersion": "1.23.0", "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
/* __GDPR__
"extensionGallery:uninstall" : {
"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
/* __GDPR__
"extensionGallery:update" : {
"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
telemetryService.publicLogError(eventName, { ...extensionData, success: !error, duration, errorcode });
}
export abstract class AbstractExtensionTask<T> {
private readonly barrier = new Barrier();
private cancellablePromise: CancelablePromise<T> | undefined;
async waitUntilTaskIsFinished(): Promise<T> {
await this.barrier.wait();
return this.cancellablePromise!;
}
async run(): Promise<T> {
if (!this.cancellablePromise) {
this.cancellablePromise = createCancelablePromise(token => this.doRun(token));
}
this.barrier.open();
return this.cancellablePromise;
}
cancel(): void {
if (!this.cancellablePromise) {
this.cancellablePromise = createCancelablePromise(token => {
return new Promise((c, e) => {
const disposable = token.onCancellationRequested(() => {
disposable.dispose();
e(canceled());
});
});
});
this.barrier.open();
}
this.cancellablePromise.cancel();
}
protected abstract doRun(token: CancellationToken): Promise<T>;
}

View File

@@ -3,12 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IExtensionIdentifier, IGlobalExtensionEnablementService, DISABLED_EXTENSIONS_STORAGE_PATH } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { DISABLED_EXTENSIONS_STORAGE_PATH, IExtensionIdentifier, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
export class GlobalExtensionEnablementService extends Disposable implements IGlobalExtensionEnablementService {

View File

@@ -3,27 +3,27 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getErrorMessage, isPromiseCanceledError, canceled } from 'vs/base/common/errors';
import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionIdentifier, IReportedExtension, InstallOperation, ITranslation, IGalleryExtensionVersion, IGalleryExtensionAssets, isIExtensionIdentifier, DefaultIconPath } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionId, getGalleryExtensionTelemetryData, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { getOrDefault } from 'vs/base/common/objects';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IPager } from 'vs/base/common/paging';
import { IRequestService, asJson, asText, isSuccess } from 'vs/platform/request/common/request';
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 { IConfigurationService } from 'vs/platform/configuration/common/configuration'; // {{SQL CARBON EDIT}}
import { CancellationToken } from 'vs/base/common/cancellation';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtensionManifest, ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/platform/extensions/common/extensions'; // {{SQL CARBON EDIT}} add imports
import { IFileService } from 'vs/platform/files/common/files';
import { canceled, getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors';
import { getOrDefault } from 'vs/base/common/objects';
import { IPager } from 'vs/base/common/paging';
import { isWeb } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { IProductService } from 'vs/platform/product/common/productService';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId';
import { IHeaders, IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; // {{SQL CARBON EDIT}} Add import
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { DefaultIconPath, IExtensionGalleryService, IExtensionIdentifier, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IReportedExtension, isIExtensionIdentifier, ITranslation, SortBy, SortOrder, StatisticType, WEB_EXTENSION_TAG } from 'vs/platform/extensionManagement/common/extensionManagement';
import { adoptToGalleryExtensionId, getGalleryExtensionId, getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionsPolicy, ExtensionsPolicyKey, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; // {{SQL CARBON EDIT}} Add ExtensionsPolicy and ExtensionsPolicyKey
import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator';
import { IFileService } from 'vs/platform/files/common/files';
import { optional } from 'vs/platform/instantiation/common/instantiation';
import { joinPath } from 'vs/base/common/resources';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { asJson, asText, IRequestService } from 'vs/platform/request/common/request'; // {{SQL CARBON EDIT}} Remove unused
import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
interface IRawGalleryExtensionFile {
readonly assetType: string;
@@ -57,6 +57,11 @@ interface IRawGalleryExtension {
readonly publisher: { displayName: string, publisherId: string, publisherName: string; };
readonly versions: IRawGalleryExtensionVersion[];
readonly statistics: IRawGalleryExtensionStatistics[];
readonly tags: string[] | undefined;
readonly releaseDate: string;
readonly publishedDate: string;
readonly lastUpdated: string;
readonly categories: string[] | undefined;
readonly flags: string;
}
@@ -152,6 +157,7 @@ const DefaultQueryState: IQueryState = {
assetTypes: []
};
/* {{SQL CARBON EDIT}} Remove unused
type GalleryServiceQueryClassification = {
readonly filterTypes: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
readonly sortBy: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
@@ -164,6 +170,7 @@ type GalleryServiceQueryClassification = {
readonly errorCode?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
readonly count?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
};
*/
type QueryTelemetryData = {
readonly filterTypes: string[];
@@ -171,6 +178,7 @@ type QueryTelemetryData = {
readonly sortOrder: string;
};
/* {{SQL CARBON EDIT}} Remove unused
type GalleryServiceQueryEvent = QueryTelemetryData & {
readonly duration: number;
readonly success: boolean;
@@ -180,6 +188,7 @@ type GalleryServiceQueryEvent = QueryTelemetryData & {
readonly errorCode?: string;
readonly count?: string;
};
*/
class Query {
@@ -346,17 +355,6 @@ function getIsPreview(flags: string): boolean {
return flags.indexOf('preview') !== -1;
}
function getIsWebExtension(version: IRawGalleryExtensionVersion): boolean {
const webExtensionProperty = version.properties ? version.properties.find(p => p.key === PropertyType.WebExtension) : undefined;
return !!webExtensionProperty && webExtensionProperty.value === 'true';
}
function getWebResource(version: IRawGalleryExtensionVersion): URI | undefined {
return version.files.some(f => f.assetType.startsWith('Microsoft.VisualStudio.Code.WebResources'))
? joinPath(URI.parse(version.assetUri), 'Microsoft.VisualStudio.Code.WebResources', 'extension')
: undefined;
}
function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, index: number, query: Query, querySource?: string): IGalleryExtension {
const assets = <IGalleryExtensionAssets>{
manifest: getVersionAsset(version, AssetType.Manifest),
@@ -378,7 +376,6 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
},
name: galleryExtension.extensionName,
version: version.version,
date: version.lastUpdated,
displayName: galleryExtension.displayName,
publisherId: galleryExtension.publisher.publisherId,
publisher: galleryExtension.publisher.publisherName,
@@ -387,9 +384,11 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
installCount: getStatistic(galleryExtension.statistics, 'install'),
rating: getStatistic(galleryExtension.statistics, 'averagerating'),
ratingCount: getStatistic(galleryExtension.statistics, 'ratingcount'),
assetUri: URI.parse(version.assetUri),
webResource: getWebResource(version),
assetTypes: version.files.map(({ assetType }) => assetType),
categories: galleryExtension.categories || [],
tags: galleryExtension.tags || [],
releaseDate: Date.parse(galleryExtension.releaseDate),
lastUpdated: Date.parse(version.lastUpdated), // {{SQL CARBON EDIT}} We don't have the lastUpdated at the top level currently
webExtension: !!galleryExtension.tags?.includes(WEB_EXTENSION_TAG),
assets,
properties: {
dependencies: getExtensions(version, PropertyType.Dependency),
@@ -398,7 +397,6 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
// {{SQL CARBON EDIT}}
azDataEngine: getAzureDataStudioEngine(version),
localizedLanguages: getLocalizedLanguages(version),
webExtension: getIsWebExtension(version)
},
/* __GDPR__FRAGMENT__
"GalleryExtensionTelemetryData2" : {
@@ -469,13 +467,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
}
async getCompatibleExtension(arg1: IExtensionIdentifier | IGalleryExtension, version?: string): Promise<IGalleryExtension | null> {
const extension = await this.getCompatibleExtensionByEngine(arg1, version);
if (extension?.properties.webExtension) {
return extension.webResource ? extension : null;
} else {
return extension;
}
return this.getCompatibleExtensionByEngine(arg1, version);
}
private async getCompatibleExtensionByEngine(arg1: IExtensionIdentifier | IGalleryExtension, version?: string): Promise<IGalleryExtension | null> {
@@ -490,7 +482,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
}
const { id, uuid } = <IExtensionIdentifier>arg1; // {{SQL CARBON EDIT}} @anthonydresser remove extension ? extension.identifier
let query = new Query()
.withFlags(Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeFiles, Flags.IncludeVersionProperties)
.withFlags(Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties)
.withPage(1, 1)
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code');
@@ -534,11 +526,22 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
throw new Error('No extension gallery service configured.');
}
const type = options.names ? 'ids' : (options.text ? 'text' : 'all');
let text = options.text || '';
const pageSize = getOrDefault(options, o => o.pageSize, 50);
type GalleryServiceQueryClassification = {
type: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
text: { classification: 'CustomerContent', purpose: 'FeatureInsight' };
};
type GalleryServiceQueryEvent = {
type: string;
text: string;
};
this.telemetryService.publicLog2<GalleryServiceQueryEvent, GalleryServiceQueryClassification>('galleryService:query', { type, text });
let query = new Query()
.withFlags(Flags.IncludeLatestVersionOnly, Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeFiles, Flags.IncludeVersionProperties)
.withFlags(Flags.IncludeLatestVersionOnly, Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties)
.withPage(1, pageSize)
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code');
@@ -696,7 +699,6 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
extension.extensionId && extension.extensionId.toLocaleLowerCase().indexOf(text) > -1);
}
// {{SQL CARBON EDIT}}
public static compareByField(a: any, b: any, fieldName: string): number {
if (a && !b) {
return 1;
@@ -723,7 +725,6 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
if (!this.isEnabled()) {
throw new Error('No extension gallery service configured.');
}
// Always exclude non validated and unpublished extensions
query = query
.withFlags(query.flags, Flags.ExcludeNonValidated)
@@ -739,56 +740,34 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
'Content-Length': String(data.length)
};
const startTime = new Date().getTime();
let context: IRequestContext | undefined, error: any, total: number = 0;
const context = await this.requestService.request({
// {{SQL CARBON EDIT}}
type: 'GET',
url: this.api('/extensionquery'),
data,
headers
}, token);
try {
context = await this.requestService.request({
// {{SQL CARBON EDIT}}
type: 'GET',
url: this.api('/extensionquery'),
data,
headers
}, token);
// {{SQL CARBON EDIT}}
let extensionPolicy: string = this.configurationService.getValue<string>(ExtensionsPolicyKey);
if (context.res.statusCode && context.res.statusCode >= 400 && context.res.statusCode < 500 || extensionPolicy === ExtensionsPolicy.allowNone) {
return { galleryExtensions: [], total: 0 };
}
const result = await asJson<IRawGalleryQueryResult>(context);
if (result) {
const r = result.results[0];
const galleryExtensions = r.extensions;
// const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0]; {{SQL CARBON EDIT}} comment out for no unused
// const total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0; {{SQL CARBON EDIT}} comment out for no unused
// {{SQL CARBON EDIT}}
let extensionPolicy: string = this.configurationService.getValue<string>(ExtensionsPolicyKey);
if (context.res.statusCode && context.res.statusCode >= 400 && context.res.statusCode < 500 || extensionPolicy === ExtensionsPolicy.allowNone) {
return { galleryExtensions: [], total: 0 };
}
let filteredExtensionsResult = this.createQueryResult(query, galleryExtensions);
const result = await asJson<IRawGalleryQueryResult>(context);
if (result) {
const r = result.results[0];
const galleryExtensions = r.extensions;
// const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0]; {{SQL CARBON EDIT}} comment out for no unused
// const total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0; {{SQL CARBON EDIT}} comment out for no unused
// {{SQL CARBON EDIT}}
let filteredExtensionsResult = this.createQueryResult(query, galleryExtensions);
return { galleryExtensions: filteredExtensionsResult.galleryExtensions, total: filteredExtensionsResult.total };
// {{SQL CARBON EDIT}} - End
}
return { galleryExtensions: [], total };
} catch (e) {
error = e;
throw e;
} finally {
this.telemetryService.publicLog2<GalleryServiceQueryEvent, GalleryServiceQueryClassification>('galleryService:query', {
...query.telemetryData,
requestBodySize: String(data.length),
duration: new Date().getTime() - startTime,
success: !!context && isSuccess(context),
responseBodySize: context?.res.headers['Content-Length'],
statusCode: context ? String(context.res.statusCode) : undefined,
errorCode: error
? isPromiseCanceledError(error) ? 'canceled' : getErrorMessage(error).startsWith('XHR timeout') ? 'timeout' : 'failed'
: undefined,
count: String(total)
});
return { galleryExtensions: filteredExtensionsResult.galleryExtensions, total: filteredExtensionsResult.total };
// {{SQL CARBON EDIT}} - End
}
return { galleryExtensions: [], total: 0 };
}
async reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise<void> {
@@ -796,12 +775,15 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return undefined;
}
const url = isWeb ? this.api(`/itemName/${publisher}.${name}/version/${version}/statType/${type === StatisticType.Install ? '1' : '3'}/vscodewebextension`) : this.api(`/publishers/${publisher}/extensions/${name}/${version}/stats?statType=${type}`);
const Accept = isWeb ? 'api-version=6.1-preview.1' : '*/*;api-version=4.0-preview.1';
const commonHeaders = await this.commonHeadersPromise;
const headers = { ...commonHeaders, Accept: '*/*;api-version=4.0-preview.1' };
const headers = { ...commonHeaders, Accept };
try {
await this.requestService.request({
type: 'POST',
url: this.api(`/publishers/${publisher}/extensions/${name}/${version}/stats?statType=${type}`),
url,
headers
}, CancellationToken.None);
} catch (error) { /* Ignore */ }
@@ -873,7 +855,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
async getAllVersions(extension: IGalleryExtension, compatible: boolean): Promise<IGalleryExtensionVersion[]> {
let query = new Query()
.withFlags(Flags.IncludeVersions, Flags.IncludeFiles, Flags.IncludeVersionProperties)
.withFlags(Flags.IncludeVersions, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties)
.withPage(1, 1)
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code');
@@ -904,7 +886,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
}
private async getAsset(asset: IGalleryExtensionAsset, options: IRequestOptions = {}, token: CancellationToken = CancellationToken.None): Promise<IRequestContext> {
const commonHeaders = await this.commonHeadersPromise;
const commonHeaders = {}; // await this.commonHeadersPromise; {{SQL CARBON EDIT}} Because we query other sources such as github don't insert the custom VS headers - otherwise Electron will make a CORS preflight request which not all endpoints support.
const baseOptions = { type: 'GET' };
const headers = { ...commonHeaders, ...(options.headers || {}) };
options = { ...options, ...baseOptions, headers };

View File

@@ -3,17 +3,18 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { Event } from 'vs/base/common/event';
import { IPager } from 'vs/base/common/paging';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IExtensionManifest, IExtension, ExtensionType } from 'vs/platform/extensions/common/extensions';
import { Event } from 'vs/base/common/event';
import { FileAccess } from 'vs/base/common/network';
import { IPager } from 'vs/base/common/paging';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { ExtensionType, IExtension, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const EXTENSION_IDENTIFIER_PATTERN = '^([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z0-9A-Z][a-z0-9-A-Z]*)$';
export const EXTENSION_IDENTIFIER_REGEX = new RegExp(EXTENSION_IDENTIFIER_PATTERN);
export const WEB_EXTENSION_TAG = '__web_extension';
export interface IGalleryExtensionProperties {
dependencies?: string[];
@@ -22,7 +23,6 @@ export interface IGalleryExtensionProperties {
// {{SQL CARBON EDIT}}
azDataEngine?: string;
localizedLanguages?: string[];
webExtension?: boolean;
}
export interface IGalleryExtensionAsset {
@@ -80,7 +80,6 @@ export interface IGalleryExtension {
name: string;
identifier: IGalleryExtensionIdentifier;
version: string;
date: string;
displayName: string;
publisherId: string;
publisher: string;
@@ -89,13 +88,15 @@ export interface IGalleryExtension {
installCount: number;
rating: number;
ratingCount: number;
assetUri: URI;
assetTypes: string[];
categories: readonly string[];
tags: readonly string[];
releaseDate: number;
lastUpdated: number;
assets: IGalleryExtensionAssets;
properties: IGalleryExtensionProperties;
telemetryData: any;
preview: boolean;
webResource?: URI;
webExtension: boolean;
}
export interface IGalleryMetadata {
@@ -144,6 +145,7 @@ export interface IQueryOptions {
}
export const enum StatisticType {
Install = 'install',
Uninstall = 'uninstall'
}
@@ -183,17 +185,14 @@ export interface IExtensionGalleryService {
export interface InstallExtensionEvent {
identifier: IExtensionIdentifier;
zipPath?: string;
gallery?: IGalleryExtension;
source: URI | IGalleryExtension;
}
export interface DidInstallExtensionEvent {
identifier: IExtensionIdentifier;
operation: InstallOperation;
zipPath?: string;
gallery?: IGalleryExtension;
local?: ILocalExtension;
error?: string;
export interface InstallExtensionResult {
readonly identifier: IExtensionIdentifier;
readonly operation: InstallOperation;
readonly source?: URI | IGalleryExtension;
readonly local?: ILocalExtension;
}
export interface DidUninstallExtensionEvent {
@@ -208,6 +207,7 @@ export const INSTALL_ERROR_INCOMPATIBLE = 'incompatible';
export class ExtensionManagementError extends Error {
constructor(message: string, readonly code: string) {
super(message);
this.name = code;
}
}
@@ -215,12 +215,17 @@ export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean, d
export type InstallVSIXOptions = InstallOptions & { installOnlyNewlyAddedFromExtensionPack?: boolean };
export type UninstallOptions = { donotIncludePack?: boolean, donotCheckDependents?: boolean };
export interface IExtensionManagementParticipant {
postInstall(local: ILocalExtension, source: URI | IGalleryExtension, options: InstallOptions | InstallVSIXOptions, token: CancellationToken): Promise<void>;
postUninstall(local: ILocalExtension, options: UninstallOptions, token: CancellationToken): Promise<void>;
}
export const IExtensionManagementService = createDecorator<IExtensionManagementService>('extensionManagementService');
export interface IExtensionManagementService {
readonly _serviceBrand: undefined;
onInstallExtension: Event<InstallExtensionEvent>;
onDidInstallExtension: Event<DidInstallExtensionEvent>;
onDidInstallExtensions: Event<readonly InstallExtensionResult[]>;
onUninstallExtension: Event<IExtensionIdentifier>;
onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
@@ -237,6 +242,8 @@ export interface IExtensionManagementService {
updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension>;
updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise<ILocalExtension>;
registerParticipant(pariticipant: IExtensionManagementParticipant): void;
}
export const DISABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/disabled';

View File

@@ -3,17 +3,17 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { CancellationToken } from 'vs/base/common/cancellation';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { URI } from 'vs/base/common/uri';
import { getBaseLabel } from 'vs/base/common/labels';
import { Schemas } from 'vs/base/common/network';
import { gt } from 'vs/base/common/semver/semver';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { CLIOutput, IExtensionGalleryService, IExtensionManagementCLIService, IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionType, EXTENSION_CATEGORIES, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
import { getBaseLabel } from 'vs/base/common/labels';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Schemas } from 'vs/base/common/network';
const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id);
const useId = localize('useId', "Make sure you use the full extension ID, including the publisher, e.g.: {0}", 'ms-dotnettools.csharp');
@@ -236,9 +236,9 @@ export class ExtensionManagementCLIService implements IExtensionManagementCLISer
try {
if (installOptions.isBuiltin) {
output.log(localize('installing builtin ', "Installing builtin extension '{0}' v{1}...", id, galleryExtension.version));
output.log(version ? localize('installing builtin with version', "Installing builtin extension '{0}' v{1}...", id, version) : localize('installing builtin ', "Installing builtin extension '{0}'...", id));
} else {
output.log(localize('installing', "Installing extension '{0}' v{1}...", id, galleryExtension.version));
output.log(version ? localize('installing with version', "Installing extension '{0}' v{1}...", id, version) : localize('installing', "Installing extension '{0}'...", id));
}
await this.extensionManagementService.installFromGallery(galleryExtension, installOptions);

View File

@@ -3,14 +3,14 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension, IExtensionTipsService, InstallOptions, UninstallOptions, InstallVSIXOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { Emitter, Event } from 'vs/base/common/event';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IURITransformer, DefaultURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc';
import { cloneAndChange } from 'vs/base/common/objects';
import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
import { Disposable } from 'vs/base/common/lifecycle';
import { cloneAndChange } from 'vs/base/common/objects';
import { URI, UriComponents } from 'vs/base/common/uri';
import { DefaultURITransformer, IURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { DidUninstallExtensionEvent, IExtensionIdentifier, IExtensionManagementService, IExtensionTipsService, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallExtensionEvent, InstallExtensionResult, InstallOptions, InstallVSIXOptions, IReportedExtension, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI {
return URI.revive(transformer ? transformer.transformIncoming(uri) : uri);
@@ -34,13 +34,13 @@ function transformOutgoingExtension(extension: ILocalExtension, transformer: IUR
export class ExtensionManagementChannel implements IServerChannel {
onInstallExtension: Event<InstallExtensionEvent>;
onDidInstallExtension: Event<DidInstallExtensionEvent>;
onDidInstallExtensions: Event<readonly InstallExtensionResult[]>;
onUninstallExtension: Event<IExtensionIdentifier>;
onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
constructor(private service: IExtensionManagementService, private getUriTransformer: (requestContext: any) => IURITransformer | null) {
this.onInstallExtension = Event.buffer(service.onInstallExtension, true);
this.onDidInstallExtension = Event.buffer(service.onDidInstallExtension, true);
this.onDidInstallExtensions = Event.buffer(service.onDidInstallExtensions, true);
this.onUninstallExtension = Event.buffer(service.onUninstallExtension, true);
this.onDidUninstallExtension = Event.buffer(service.onDidUninstallExtension, true);
}
@@ -49,7 +49,7 @@ export class ExtensionManagementChannel implements IServerChannel {
const uriTransformer = this.getUriTransformer(context);
switch (event) {
case 'onInstallExtension': return this.onInstallExtension;
case 'onDidInstallExtension': return Event.map(this.onDidInstallExtension, i => ({ ...i, local: i.local ? transformOutgoingExtension(i.local, uriTransformer) : i.local }));
case 'onDidInstallExtensions': return Event.map(this.onDidInstallExtensions, results => results.map(i => ({ ...i, local: i.local ? transformOutgoingExtension(i.local, uriTransformer) : i.local })));
case 'onUninstallExtension': return this.onUninstallExtension;
case 'onDidUninstallExtension': return this.onDidUninstallExtension;
}
@@ -85,8 +85,8 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt
private readonly _onInstallExtension = this._register(new Emitter<InstallExtensionEvent>());
readonly onInstallExtension = this._onInstallExtension.event;
private readonly _onDidInstallExtension = this._register(new Emitter<DidInstallExtensionEvent>());
readonly onDidInstallExtension = this._onDidInstallExtension.event;
private readonly _onDidInstallExtensions = this._register(new Emitter<readonly InstallExtensionResult[]>());
readonly onDidInstallExtensions = this._onDidInstallExtensions.event;
private readonly _onUninstallExtension = this._register(new Emitter<IExtensionIdentifier>());
readonly onUninstallExtension = this._onUninstallExtension.event;
@@ -98,12 +98,20 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt
private readonly channel: IChannel,
) {
super();
this._register(this.channel.listen<InstallExtensionEvent>('onInstallExtension')(e => this._onInstallExtension.fire(e)));
this._register(this.channel.listen<DidInstallExtensionEvent>('onDidInstallExtension')(e => this._onDidInstallExtension.fire({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local })));
this._register(this.channel.listen<InstallExtensionEvent>('onInstallExtension')(e => this._onInstallExtension.fire({ identifier: e.identifier, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source })));
this._register(this.channel.listen<readonly InstallExtensionResult[]>('onDidInstallExtensions')(results => this._onDidInstallExtensions.fire(results.map(e => ({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source })))));
this._register(this.channel.listen<IExtensionIdentifier>('onUninstallExtension')(e => this._onUninstallExtension.fire(e)));
this._register(this.channel.listen<DidUninstallExtensionEvent>('onDidUninstallExtension')(e => this._onDidUninstallExtension.fire(e)));
}
private isUriComponents(thing: unknown): thing is UriComponents {
if (!thing) {
return false;
}
return typeof (<any>thing).path === 'string' &&
typeof (<any>thing).scheme === 'string';
}
zip(extension: ILocalExtension): Promise<URI> {
return Promise.resolve(this.channel.call('zip', [extension]).then(result => URI.revive(<UriComponents>result)));
}
@@ -154,6 +162,8 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt
getExtensionsReport(): Promise<IReportedExtension[]> {
return Promise.resolve(this.channel.call('getExtensionsReport'));
}
registerParticipant() { throw new Error('Not Supported'); }
}
export class ExtensionTipsChannel implements IServerChannel {

View File

@@ -3,9 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ILocalExtension, IGalleryExtension, IExtensionIdentifier, IReportedExtension, IExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagement';
import { compareIgnoreCase } from 'vs/base/common/strings';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IExtensionIdentifier, IExtensionIdentifierWithVersion, IGalleryExtension, ILocalExtension, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionIdentifier, IExtension } from 'vs/platform/extensions/common/extensions';
export function areSameExtensions(a: IExtensionIdentifier, b: IExtensionIdentifier): boolean {
if (a.uuid && b.uuid) {
@@ -131,3 +131,22 @@ export function getMaliciousExtensionsSet(report: IReportedExtension[]): Set<str
return result;
}
export function getExtensionDependencies(installedExtensions: ReadonlyArray<IExtension>, extension: IExtension): IExtension[] {
const dependencies: IExtension[] = [];
const extensions = extension.manifest.extensionDependencies?.slice(0) ?? [];
while (extensions.length) {
const id = extensions.shift();
if (id && dependencies.every(e => !areSameExtensions(e.identifier, { id }))) {
const ext = installedExtensions.filter(e => areSameExtensions(e.identifier, { id }));
if (ext.length === 1) {
dependencies.push(ext[0]);
extensions.push(...ext[0].manifest.extensionDependencies?.slice(0) ?? []);
}
}
}
return dependencies;
}

View File

@@ -9,11 +9,11 @@ import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
const nlsRegex = /^%([\w\d.-]+)%$/i;
export interface ITranslations {
[key: string]: string;
[key: string]: string | { message: string; comment: string[] };
}
export function localizeManifest(manifest: IExtensionManifest, translations: ITranslations): IExtensionManifest {
const patcher = (value: string) => {
const patcher = (value: string): string | undefined => {
if (typeof value !== 'string') {
return undefined;
}
@@ -24,7 +24,8 @@ export function localizeManifest(manifest: IExtensionManifest, translations: ITr
return undefined;
}
return translations[match[1]] || value;
const translation = translations[match[1]] ?? value;
return typeof translation === 'string' ? translation : (typeof translation.message === 'string' ? translation.message : value);
};
return cloneAndChange(manifest, patcher);

View File

@@ -3,19 +3,19 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { IProductService } from 'vs/platform/product/common/productService';
import { IConfigBasedExtensionTip as IRawConfigBasedExtensionTip } from 'vs/base/common/product';
import { IFileService } from 'vs/platform/files/common/files';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { IExtensionTipsService, IExecutableBasedExtensionTip, IWorkspaceTips, IConfigBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement';
import { forEach } from 'vs/base/common/collections';
import { IRequestService, asJson } from 'vs/platform/request/common/request';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ILogService } from 'vs/platform/log/common/log';
import { joinPath } from 'vs/base/common/resources';
import { getDomainsOfRemotes } from 'vs/platform/extensionManagement/common/configRemotes';
import { forEach } from 'vs/base/common/collections';
import { Disposable } from 'vs/base/common/lifecycle';
import { IConfigBasedExtensionTip as IRawConfigBasedExtensionTip } from 'vs/base/common/product';
import { joinPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { getDomainsOfRemotes } from 'vs/platform/extensionManagement/common/configRemotes';
import { IConfigBasedExtensionTip, IExecutableBasedExtensionTip, IExtensionTipsService, IWorkspaceTips } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IFileService } from 'vs/platform/files/common/files';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { asJson, IRequestService } from 'vs/platform/request/common/request';
export class ExtensionTipsService extends Disposable implements IExtensionTipsService {

View File

@@ -3,26 +3,26 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { basename, join, } from 'vs/base/common/path';
import { IProductService } from 'vs/platform/product/common/productService';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { env } from 'vs/base/common/process';
import { IFileService } from 'vs/platform/files/common/files';
import { isWindows } from 'vs/base/common/platform';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { IExecutableBasedExtensionTip, IExtensionManagementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { forEach, IStringDictionary } from 'vs/base/common/collections';
import { IRequestService } from 'vs/platform/request/common/request';
import { ILogService } from 'vs/platform/log/common/log';
import { ExtensionTipsService as BaseExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionTipsService';
import { disposableTimeout, timeout } from 'vs/base/common/async';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IExtensionRecommendationNotificationService, RecommendationsNotificationResult, RecommendationSource } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
import { localize } from 'vs/nls';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { forEach, IStringDictionary } from 'vs/base/common/collections';
import { Event } from 'vs/base/common/event';
import { join } from 'vs/base/common/path';
import { isWindows } from 'vs/base/common/platform';
import { env } from 'vs/base/common/process';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { IExecutableBasedExtensionTip, IExtensionManagementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionTipsService as BaseExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionTipsService';
import { IExtensionRecommendationNotificationService, RecommendationsNotificationResult, RecommendationSource } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
import { IFileService } from 'vs/platform/files/common/files';
import { ILogService } from 'vs/platform/log/common/log';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { IProductService } from 'vs/platform/product/common/productService';
import { IRequestService } from 'vs/platform/request/common/request';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
type ExeExtensionRecommendationsClassification = {
extensionId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
@@ -130,13 +130,13 @@ export class ExtensionTipsService extends BaseExtensionTipsService {
for (const extensionId of installed) {
const tip = importantExeBasedRecommendations.get(extensionId);
if (tip) {
this.telemetryService.publicLog2<{ exeName: string, extensionId: string }, ExeExtensionRecommendationsClassification>('exeExtensionRecommendations:alreadyInstalled', { extensionId, exeName: basename(tip.windowsPath!) });
this.telemetryService.publicLog2<{ exeName: string, extensionId: string }, ExeExtensionRecommendationsClassification>('exeExtensionRecommendations:alreadyInstalled', { extensionId, exeName: tip.exeName });
}
}
for (const extensionId of recommendations) {
const tip = importantExeBasedRecommendations.get(extensionId);
if (tip) {
this.telemetryService.publicLog2<{ exeName: string, extensionId: string }, ExeExtensionRecommendationsClassification>('exeExtensionRecommendations:notInstalled', { extensionId, exeName: basename(tip.windowsPath!) });
this.telemetryService.publicLog2<{ exeName: string, extensionId: string }, ExeExtensionRecommendationsClassification>('exeExtensionRecommendations:notInstalled', { extensionId, exeName: tip.exeName });
}
}

View File

@@ -3,20 +3,20 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Promises as FSPromises } from 'vs/base/node/pfs';
import { Disposable } from 'vs/base/common/lifecycle';
import { IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files';
import { IExtensionGalleryService, IGalleryExtension, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { URI } from 'vs/base/common/uri';
import { joinPath } from 'vs/base/common/resources';
import { ExtensionIdentifierWithVersion, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ILogService } from 'vs/platform/log/common/log';
import { generateUuid } from 'vs/base/common/uuid';
import * as semver from 'vs/base/common/semver/semver';
import { isWindows } from 'vs/base/common/platform';
import { Promises } from 'vs/base/common/async';
import { getErrorMessage } from 'vs/base/common/errors';
import { Disposable } from 'vs/base/common/lifecycle';
import { isWindows } from 'vs/base/common/platform';
import { joinPath } from 'vs/base/common/resources';
import * as semver from 'vs/base/common/semver/semver';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { Promises as FSPromises } from 'vs/base/node/pfs';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { IExtensionGalleryService, IGalleryExtension, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionIdentifierWithVersion, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files';
import { ILogService } from 'vs/platform/log/common/log';
const ExtensionIdVersionRegex = /^([^.]+\..+)-(\d+\.\d+\.\d+)$/;

View File

@@ -3,17 +3,17 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ILogService } from 'vs/platform/log/common/log';
import { fork, ChildProcess } from 'child_process';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { join } from 'vs/base/common/path';
import { ChildProcess, fork } from 'child_process';
import { Limiter } from 'vs/base/common/async';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { join } from 'vs/base/common/path';
import { Promises } from 'vs/base/node/pfs';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ILogService } from 'vs/platform/log/common/log';
export class ExtensionsLifecycle extends Disposable {

View File

@@ -5,10 +5,10 @@
import { Disposable } from 'vs/base/common/lifecycle';
import { join } from 'vs/base/common/path';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { IExtensionManagementService, DidInstallExtensionEvent, DidUninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement';
import { MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE } from 'vs/platform/extensions/common/extensions';
import * as pfs from 'vs/base/node/pfs';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { DidUninstallExtensionEvent, IExtensionManagementService, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement';
import { MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE } from 'vs/platform/extensions/common/extensions';
export class ExtensionsManifestCache extends Disposable {
@@ -19,12 +19,12 @@ export class ExtensionsManifestCache extends Disposable {
extensionsManagementService: IExtensionManagementService
) {
super();
this._register(extensionsManagementService.onDidInstallExtension(e => this.onDidInstallExtension(e)));
this._register(extensionsManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e)));
this._register(extensionsManagementService.onDidUninstallExtension(e => this.onDidUnInstallExtension(e)));
}
private onDidInstallExtension(e: DidInstallExtensionEvent): void {
if (!e.error) {
private onDidInstallExtensions(results: readonly InstallExtensionResult[]): void {
if (results.some(r => !!r.local)) {
this.invalidate();
}
}

View File

@@ -3,30 +3,30 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as semver from 'vs/base/common/semver/semver';
import { Disposable } from 'vs/base/common/lifecycle';
import * as pfs from 'vs/base/node/pfs';
import * as path from 'vs/base/common/path';
import { ILogService } from 'vs/platform/log/common/log';
import { ILocalExtension, IGalleryMetadata, ExtensionManagementError } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionType, IExtensionManifest, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { areSameExtensions, ExtensionIdentifierWithVersion, groupByExtension, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { flatten } from 'vs/base/common/arrays';
import { Limiter, Promises, Queue } from 'vs/base/common/async';
import { IStringDictionary } from 'vs/base/common/collections';
import { getErrorMessage } from 'vs/base/common/errors';
import { Disposable } from 'vs/base/common/lifecycle';
import { FileAccess } from 'vs/base/common/network';
import * as path from 'vs/base/common/path';
import { isWindows } from 'vs/base/common/platform';
import { basename } from 'vs/base/common/resources';
import * as semver from 'vs/base/common/semver/semver';
import { URI } from 'vs/base/common/uri';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls';
import { generateUuid } from 'vs/base/common/uuid';
import * as pfs from 'vs/base/node/pfs';
import { extract, ExtractError } from 'vs/base/node/zip';
import { localize } from 'vs/nls';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { ExtensionManagementError, IGalleryMetadata, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions, ExtensionIdentifierWithVersion, getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls';
import { ExtensionType, IExtensionIdentifier, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { CancellationToken } from 'vscode';
import { extract, ExtractError } from 'vs/base/node/zip';
import { isWindows } from 'vs/base/common/platform';
import { flatten } from 'vs/base/common/arrays';
import { IStringDictionary } from 'vs/base/common/collections';
import { FileAccess } from 'vs/base/common/network';
import { IFileService } from 'vs/platform/files/common/files';
import { basename } from 'vs/base/common/resources';
import { generateUuid } from 'vs/base/common/uuid';
import { getErrorMessage } from 'vs/base/common/errors';
const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem';
const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser';

View File

@@ -3,15 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { DidInstallExtensionEvent, DidUninstallExtensionEvent, IExtensionManagementService, ILocalExtension, InstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { ExtUri } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { DidUninstallExtensionEvent, IExtensionManagementService, ILocalExtension, InstallExtensionEvent, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { FileChangeType, FileSystemProviderCapabilities, IFileChange, IFileService } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtUri } from 'vs/base/common/resources';
import { ILogService } from 'vs/platform/log/common/log';
export class ExtensionsWatcher extends Disposable {
@@ -35,13 +35,13 @@ export class ExtensionsWatcher extends Disposable {
this.startTimestamp = Date.now();
});
this._register(extensionsManagementService.onInstallExtension(e => this.onInstallExtension(e)));
this._register(extensionsManagementService.onDidInstallExtension(e => this.onDidInstallExtension(e)));
this._register(extensionsManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e)));
this._register(extensionsManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e)));
const extensionsResource = URI.file(environmentService.extensionsPath);
const extUri = new ExtUri(resource => !fileService.hasCapability(resource, FileSystemProviderCapabilities.PathCaseSensitive));
this._register(fileService.watch(extensionsResource));
this._register(Event.filter(fileService.onDidFilesChange, e => e.changes.some(change => this.doesChangeAffects(change, extensionsResource, extUri)))(() => this.onDidChange()));
this._register(Event.filter(fileService.onDidChangeFilesRaw, e => e.changes.some(change => this.doesChangeAffects(change, extensionsResource, extUri)))(() => this.onDidChange()));
}
private doesChangeAffects(change: IFileChange, extensionsResource: URI, extUri: ExtUri): boolean {
@@ -72,10 +72,12 @@ export class ExtensionsWatcher extends Disposable {
this.addInstallingExtension(e.identifier);
}
private onDidInstallExtension(e: DidInstallExtensionEvent): void {
this.removeInstallingExtension(e.identifier);
if (!e.error) {
this.addInstalledExtension(e.identifier);
private onDidInstallExtensions(results: readonly InstallExtensionResult[]): void {
for (const e of results) {
this.removeInstallingExtension(e.identifier);
if (e.local) {
this.addInstalledExtension(e.identifier);
}
}
}

View File

@@ -4,19 +4,19 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/common/extensionGalleryService';
import { isUUID } from 'vs/base/common/uuid';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { joinPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { isUUID } from 'vs/base/common/uuid';
import { mock } from 'vs/base/test/common/mock';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/common/extensionGalleryService';
import { IFileService } from 'vs/platform/files/common/files';
import { FileService } from 'vs/platform/files/common/fileService';
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
import { NullLogService } from 'vs/platform/log/common/log';
import product from 'vs/platform/product/common/product';
import { InMemoryStorageService, IStorageService } from 'vs/platform/storage/common/storage';
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
import { URI } from 'vs/base/common/uri';
import { joinPath } from 'vs/base/common/resources';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { mock } from 'vs/base/test/common/mock';
class EnvironmentServiceMock extends mock<IEnvironmentService>() {
override readonly serviceMachineIdResource: URI;

View File

@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as strings from 'vs/base/common/strings';
import { ILocalization } from 'vs/platform/localizations/common/localizations';
import { URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILocalization } from 'vs/platform/localizations/common/localizations';
export const MANIFEST_CACHE_FOLDER = 'CachedExtensions';
export const USER_MANIFEST_CACHE_FILE = 'user';
@@ -138,6 +138,7 @@ export interface IWalkthrough {
readonly title: string;
readonly description: string;
readonly steps: IWalkthroughStep[];
readonly featuredFor: string[] | undefined;
readonly when?: string;
}
@@ -145,8 +146,12 @@ export interface IStartEntry {
readonly title: string;
readonly description: string;
readonly command: string;
readonly type?: 'sample-folder' | 'sample-notebook' | string;
readonly when?: string;
readonly category: 'file' | 'folder' | 'notebook';
}
export interface INotebookRendererContribution {
readonly id: string;
}
export interface IExtensionContributions {
@@ -171,25 +176,26 @@ export interface IExtensionContributions {
authentication?: IAuthenticationContribution[];
walkthroughs?: IWalkthrough[];
startEntries?: IStartEntry[];
readonly notebookRenderer?: INotebookRendererContribution[];
}
export interface IExtensionCapabilities {
readonly virtualWorkspaces?: ExtensionVirtualWorkpaceSupport;
readonly virtualWorkspaces?: ExtensionVirtualWorkspaceSupport;
readonly untrustedWorkspaces?: ExtensionUntrustedWorkspaceSupport;
}
export const ALL_EXTENSION_KINDS: readonly ExtensionKind[] = ['ui', 'workspace', 'web'];
export type ExtensionKind = 'ui' | 'workspace' | 'web';
export type LimitedWorkpaceSupportType = 'limited';
export type ExtensionUntrustedWorkpaceSupportType = boolean | LimitedWorkpaceSupportType;
export type ExtensionUntrustedWorkspaceSupport = { supported: true; } | { supported: false, description: string } | { supported: LimitedWorkpaceSupportType, description: string, restrictedConfigurations?: string[] };
export type LimitedWorkspaceSupportType = 'limited';
export type ExtensionUntrustedWorkspaceSupportType = boolean | LimitedWorkspaceSupportType;
export type ExtensionUntrustedWorkspaceSupport = { supported: true; } | { supported: false, description: string } | { supported: LimitedWorkspaceSupportType, description: string, restrictedConfigurations?: string[] };
export type ExtensionVirtualWorkpaceSupportType = boolean | LimitedWorkpaceSupportType;
export type ExtensionVirtualWorkpaceSupport = boolean | { supported: true; } | { supported: false | LimitedWorkpaceSupportType, description: string };
export type ExtensionVirtualWorkspaceSupportType = boolean | LimitedWorkspaceSupportType;
export type ExtensionVirtualWorkspaceSupport = boolean | { supported: true; } | { supported: false | LimitedWorkspaceSupportType, description: string };
export function getWorkpaceSupportTypeMessage(supportType: ExtensionUntrustedWorkspaceSupport | ExtensionVirtualWorkpaceSupport | undefined): string | undefined {
export function getWorkspaceSupportTypeMessage(supportType: ExtensionUntrustedWorkspaceSupport | ExtensionVirtualWorkspaceSupport | undefined): string | undefined {
if (typeof supportType === 'object' && supportType !== null) {
if (supportType.supported !== true) {
return supportType.description;
@@ -348,30 +354,8 @@ export function isAuthenticaionProviderExtension(manifest: IExtensionManifest):
return manifest.contributes && manifest.contributes.authentication ? manifest.contributes.authentication.length > 0 : false;
}
export interface IScannedExtension {
readonly identifier: IExtensionIdentifier;
readonly location: URI;
readonly type: ExtensionType;
readonly packageJSON: IExtensionManifest;
readonly packageNLS?: any;
readonly packageNLSUrl?: URI;
readonly readmeUrl?: URI;
readonly changelogUrl?: URI;
readonly isUnderDevelopment: boolean;
}
export interface ITranslatedScannedExtension {
readonly identifier: IExtensionIdentifier;
readonly location: URI;
readonly type: ExtensionType;
readonly packageJSON: IExtensionManifest;
readonly readmeUrl?: URI;
readonly changelogUrl?: URI;
readonly isUnderDevelopment: boolean;
}
export const IBuiltinExtensionsScannerService = createDecorator<IBuiltinExtensionsScannerService>('IBuiltinExtensionsScannerService');
export interface IBuiltinExtensionsScannerService {
readonly _serviceBrand: undefined;
scanBuiltinExtensions(): Promise<IScannedExtension[]>;
scanBuiltinExtensions(): Promise<IExtension[]>;
}

View File

@@ -22,7 +22,7 @@ export interface ITerminalForPlatform {
export interface IExternalTerminalService {
readonly _serviceBrand: undefined;
openTerminal(path: string): Promise<void>;
openTerminal(configuration: IExternalTerminalSettings, cwd: string | undefined): Promise<void>;
runInTerminal(title: string, cwd: string, args: string[], env: ITerminalEnvironment, settings: IExternalTerminalSettings): Promise<number | undefined>;
getDefaultTerminalForPlatforms(): Promise<ITerminalForPlatform>;
}

View File

@@ -5,7 +5,7 @@
import { deepEqual, equal } from 'assert';
import { DEFAULT_TERMINAL_OSX } from 'vs/platform/externalTerminal/common/externalTerminal';
import { WindowsExternalTerminalService, MacExternalTerminalService, LinuxExternalTerminalService } from 'vs/platform/externalTerminal/node/externalTerminalService';
import { LinuxExternalTerminalService, MacExternalTerminalService, WindowsExternalTerminalService } from 'vs/platform/externalTerminal/node/externalTerminalService';
suite('ExternalTerminalService', () => {
let mockOnExit: Function;
@@ -42,7 +42,7 @@ suite('ExternalTerminalService', () => {
};
}
};
let testService = new WindowsExternalTerminalService(mockConfig);
let testService = new WindowsExternalTerminalService();
(<any>testService).spawnTerminal(
mockSpawner,
mockConfig,
@@ -67,7 +67,7 @@ suite('ExternalTerminalService', () => {
}
};
mockConfig.terminal.external.windowsExec = undefined;
let testService = new WindowsExternalTerminalService(mockConfig);
let testService = new WindowsExternalTerminalService();
(<any>testService).spawnTerminal(
mockSpawner,
mockConfig,
@@ -91,7 +91,7 @@ suite('ExternalTerminalService', () => {
};
}
};
let testService = new WindowsExternalTerminalService(mockConfig);
let testService = new WindowsExternalTerminalService();
(<any>testService).spawnTerminal(
mockSpawner,
mockConfig,
@@ -115,7 +115,7 @@ suite('ExternalTerminalService', () => {
return { on: (evt: any) => evt };
}
};
let testService = new WindowsExternalTerminalService(mockConfig);
let testService = new WindowsExternalTerminalService();
(<any>testService).spawnTerminal(
mockSpawner,
mockConfig,
@@ -137,7 +137,7 @@ suite('ExternalTerminalService', () => {
return { on: (evt: any) => evt };
}
};
let testService = new WindowsExternalTerminalService(mockConfig);
let testService = new WindowsExternalTerminalService();
(<any>testService).spawnTerminal(
mockSpawner,
mockConfig,
@@ -160,7 +160,7 @@ suite('ExternalTerminalService', () => {
};
}
};
let testService = new MacExternalTerminalService(mockConfig);
let testService = new MacExternalTerminalService();
(<any>testService).spawnTerminal(
mockSpawner,
mockConfig,
@@ -183,7 +183,7 @@ suite('ExternalTerminalService', () => {
}
};
mockConfig.terminal.external.osxExec = undefined;
let testService = new MacExternalTerminalService(mockConfig);
let testService = new MacExternalTerminalService();
(<any>testService).spawnTerminal(
mockSpawner,
mockConfig,
@@ -206,7 +206,7 @@ suite('ExternalTerminalService', () => {
};
}
};
let testService = new LinuxExternalTerminalService(mockConfig);
let testService = new LinuxExternalTerminalService();
(<any>testService).spawnTerminal(
mockSpawner,
mockConfig,
@@ -230,7 +230,7 @@ suite('ExternalTerminalService', () => {
}
};
mockConfig.terminal.external.linuxExec = undefined;
let testService = new LinuxExternalTerminalService(mockConfig);
let testService = new LinuxExternalTerminalService();
(<any>testService).spawnTerminal(
mockSpawner,
mockConfig,

View File

@@ -3,9 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IExternalTerminalService } from 'vs/platform/externalTerminal/common/externalTerminal';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services';
import { IExternalTerminalService } from 'vs/platform/externalTerminal/common/externalTerminal';
export const IExternalTerminalMainService = createDecorator<IExternalTerminalMainService>('externalTerminal');

View File

@@ -4,17 +4,15 @@
*--------------------------------------------------------------------------------------------*/
import * as cp from 'child_process';
import { FileAccess } from 'vs/base/common/network';
import * as path from 'vs/base/common/path';
import * as env from 'vs/base/common/platform';
import { sanitizeProcessEnvironment } from 'vs/base/common/processes';
import * as pfs from 'vs/base/node/pfs';
import * as processes from 'vs/base/node/processes';
import * as nls from 'vs/nls';
import * as pfs from 'vs/base/node/pfs';
import * as env from 'vs/base/common/platform';
import { IExternalTerminalConfiguration, IExternalTerminalSettings, DEFAULT_TERMINAL_OSX, ITerminalForPlatform, IExternalTerminalMainService } from 'vs/platform/externalTerminal/common/externalTerminal';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { optional } from 'vs/platform/instantiation/common/instantiation';
import { FileAccess } from 'vs/base/common/network';
import { DEFAULT_TERMINAL_OSX, IExternalTerminalMainService, IExternalTerminalSettings, ITerminalForPlatform } from 'vs/platform/externalTerminal/common/externalTerminal';
import { ITerminalEnvironment } from 'vs/platform/terminal/common/terminal';
import { sanitizeProcessEnvironment } from 'vs/base/common/processes';
const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console");
@@ -22,8 +20,11 @@ abstract class ExternalTerminalService {
public _serviceBrand: undefined;
async getDefaultTerminalForPlatforms(): Promise<ITerminalForPlatform> {
const linuxTerminal = await LinuxExternalTerminalService.getDefaultTerminalLinuxReady();
return { windows: WindowsExternalTerminalService.getDefaultTerminalWindows(), linux: linuxTerminal, osx: 'xterm' };
return {
windows: WindowsExternalTerminalService.getDefaultTerminalWindows(),
linux: await LinuxExternalTerminalService.getDefaultTerminalLinuxReady(),
osx: 'xterm'
};
}
}
@@ -31,20 +32,12 @@ export class WindowsExternalTerminalService extends ExternalTerminalService impl
private static readonly CMD = 'cmd.exe';
private static _DEFAULT_TERMINAL_WINDOWS: string;
constructor(
@optional(IConfigurationService) private readonly _configurationService: IConfigurationService
) {
super();
}
public openTerminal(cwd?: string): Promise<void> {
const configuration = this._configurationService.getValue<IExternalTerminalConfiguration>();
public openTerminal(configuration: IExternalTerminalSettings, cwd?: string): Promise<void> {
return this.spawnTerminal(cp, configuration, processes.getWindowsShell(), cwd);
}
public spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalConfiguration, command: string, cwd?: string): Promise<void> {
const terminalConfig = configuration.terminal.external;
const exec = terminalConfig?.windowsExec || WindowsExternalTerminalService.getDefaultTerminalWindows();
public spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalSettings, command: string, cwd?: string): Promise<void> {
const exec = configuration.windowsExec || WindowsExternalTerminalService.getDefaultTerminalWindows();
// Make the drive letter uppercase on Windows (see #9448)
if (cwd && cwd[1] === ':') {
@@ -124,14 +117,7 @@ export class WindowsExternalTerminalService extends ExternalTerminalService impl
export class MacExternalTerminalService extends ExternalTerminalService implements IExternalTerminalMainService {
private static readonly OSASCRIPT = '/usr/bin/osascript'; // osascript is the AppleScript interpreter on OS X
constructor(
@optional(IConfigurationService) private readonly _configurationService: IConfigurationService
) {
super();
}
public openTerminal(cwd?: string): Promise<void> {
const configuration = this._configurationService.getValue<IExternalTerminalConfiguration>();
public openTerminal(configuration: IExternalTerminalSettings, cwd?: string): Promise<void> {
return this.spawnTerminal(cp, configuration, cwd);
}
@@ -161,8 +147,11 @@ export class MacExternalTerminalService extends ExternalTerminalService implemen
}
if (envVars) {
for (let key in envVars) {
const value = envVars[key];
// merge environment variables into a copy of the process.env
const env = Object.assign({}, getSanitizedEnvironment(process), envVars);
for (let key in env) {
const value = env[key];
if (value === null) {
osaArgs.push('-u');
osaArgs.push(key);
@@ -199,16 +188,16 @@ export class MacExternalTerminalService extends ExternalTerminalService implemen
});
}
spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalConfiguration, cwd?: string): Promise<void> {
const terminalConfig = configuration.terminal.external;
const terminalApp = terminalConfig?.osxExec || DEFAULT_TERMINAL_OSX;
spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalSettings, cwd?: string): Promise<void> {
const terminalApp = configuration.osxExec || DEFAULT_TERMINAL_OSX;
return new Promise<void>((c, e) => {
const args = ['-a', terminalApp];
if (cwd) {
args.push(cwd);
}
const child = spawner.spawn('/usr/bin/open', args);
const env = getSanitizedEnvironment(process);
const child = spawner.spawn('/usr/bin/open', args, { cwd, env });
child.on('error', e);
child.on('exit', () => c());
});
@@ -219,14 +208,7 @@ export class LinuxExternalTerminalService extends ExternalTerminalService implem
private static readonly WAIT_MESSAGE = nls.localize('press.any.key', "Press any key to continue...");
constructor(
@optional(IConfigurationService) private readonly _configurationService: IConfigurationService
) {
super();
}
public openTerminal(cwd?: string): Promise<void> {
const configuration = this._configurationService.getValue<IExternalTerminalConfiguration>();
public openTerminal(configuration: IExternalTerminalSettings, cwd?: string): Promise<void> {
return this.spawnTerminal(cp, configuration, cwd);
}
@@ -251,8 +233,9 @@ export class LinuxExternalTerminalService extends ExternalTerminalService implem
const bashCommand = `${quote(args)}; echo; read -p "${LinuxExternalTerminalService.WAIT_MESSAGE}" -n1;`;
termArgs.push(`''${bashCommand}''`); // wrapping argument in two sets of ' because node is so "friendly" that it removes one set...
// merge environment variables into a copy of the process.env
const env = Object.assign({}, process.env, envVars);
const env = Object.assign({}, getSanitizedEnvironment(process), envVars);
// delete environment variables that have a null value
Object.keys(env).filter(v => env[v] === null).forEach(key => delete env[key]);
@@ -314,9 +297,8 @@ export class LinuxExternalTerminalService extends ExternalTerminalService implem
return LinuxExternalTerminalService._DEFAULT_TERMINAL_LINUX_READY;
}
spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalConfiguration, cwd?: string): Promise<void> {
const terminalConfig = configuration.terminal.external;
const execPromise = terminalConfig?.linuxExec ? Promise.resolve(terminalConfig.linuxExec) : LinuxExternalTerminalService.getDefaultTerminalLinuxReady();
spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalSettings, cwd?: string): Promise<void> {
const execPromise = configuration.linuxExec ? Promise.resolve(configuration.linuxExec) : LinuxExternalTerminalService.getDefaultTerminalLinuxReady();
return new Promise<void>((c, e) => {
execPromise.then(exec => {
@@ -330,7 +312,7 @@ export class LinuxExternalTerminalService extends ExternalTerminalService implem
}
function getSanitizedEnvironment(process: NodeJS.Process) {
const env = process.env;
const env = { ...process.env };
sanitizeProcessEnvironment(env);
return env;
}

View File

@@ -3,85 +3,62 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import { IFileSystemProviderWithFileReadWriteCapability, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileDeleteOptions, FileWriteOptions } from 'vs/platform/files/common/files';
import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { extUri } from 'vs/base/common/resources';
import { Schemas } from 'vs/base/common/network';
import { normalize } from 'vs/base/common/path';
import { isLinux } from 'vs/base/common/platform';
import { extUri, extUriIgnorePathCase } from 'vs/base/common/resources';
import { newWriteableStream, ReadableStreamEvents } from 'vs/base/common/stream';
import { generateUuid } from 'vs/base/common/uuid';
import { createFileSystemProviderError, FileDeleteOptions, FileOverwriteOptions, FileReadStreamOptions, FileSystemProviderCapabilities, FileSystemProviderError, FileSystemProviderErrorCode, FileType, FileWriteOptions, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IStat, IWatchOptions } from 'vs/platform/files/common/files';
function split(path: string): [string, string] | undefined {
const match = /^(.*)\/([^/]+)$/.exec(path);
export class HTMLFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithFileReadStreamCapability {
if (!match) {
return undefined;
}
const [, parentPath, name] = match;
return [parentPath, name];
}
function getRootUUID(uri: URI): string | undefined {
const match = /^\/([^/]+)\/[^/]+\/?$/.exec(uri.path);
if (!match) {
return undefined;
}
return match[1];
}
export class HTMLFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability {
private readonly files = new Map<string, FileSystemFileHandle>();
private readonly directories = new Map<string, FileSystemDirectoryHandle>();
readonly capabilities: FileSystemProviderCapabilities =
FileSystemProviderCapabilities.FileReadWrite
| FileSystemProviderCapabilities.PathCaseSensitive;
//#region Events (unsupported)
readonly onDidChangeCapabilities = Event.None;
readonly onDidChangeFile = Event.None;
readonly onDidErrorOccur = Event.None;
private readonly _onDidChangeFile = new Emitter<readonly IFileChange[]>();
readonly onDidChangeFile = this._onDidChangeFile.event;
//#endregion
private readonly _onDidErrorOccur = new Emitter<string>();
readonly onDidErrorOccur = this._onDidErrorOccur.event;
//#region File Capabilities
async readFile(resource: URI): Promise<Uint8Array> {
const handle = await this.getFileHandle(resource);
private extUri = isLinux ? extUri : extUriIgnorePathCase;
if (!handle) {
throw new Error('File not found.');
private _capabilities: FileSystemProviderCapabilities | undefined;
get capabilities(): FileSystemProviderCapabilities {
if (!this._capabilities) {
this._capabilities =
FileSystemProviderCapabilities.FileReadWrite |
FileSystemProviderCapabilities.FileReadStream;
if (isLinux) {
this._capabilities |= FileSystemProviderCapabilities.PathCaseSensitive;
}
}
const file = await handle.getFile();
return new Uint8Array(await file.arrayBuffer());
return this._capabilities;
}
async writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
const handle = await this.getFileHandle(resource);
//#endregion
if (!handle) {
throw new Error('File not found.');
}
const writable = await handle.createWritable();
await writable.write(content);
await writable.close();
}
watch(resource: URI, opts: IWatchOptions): IDisposable {
return Disposable.None;
}
//#region File Metadata Resolving
async stat(resource: URI): Promise<IStat> {
const rootUUID = getRootUUID(resource);
try {
const handle = await this.getHandle(resource);
if (!handle) {
throw this.createFileSystemProviderError(resource, 'No such file or directory, stat', FileSystemProviderErrorCode.FileNotFound);
}
if (rootUUID) {
const fileHandle = this.files.get(rootUUID);
if (fileHandle) {
const file = await fileHandle.getFile();
if (handle.kind === 'file') {
const file = await handle.getFile();
return {
type: FileType.File,
@@ -91,120 +68,335 @@ export class HTMLFileSystemProvider implements IFileSystemProviderWithFileReadWr
};
}
const directoryHandle = this.directories.get(rootUUID);
return {
type: FileType.Directory,
mtime: 0,
ctime: 0,
size: 0
};
} catch (error) {
throw this.toFileSystemProviderError(error);
}
}
if (directoryHandle) {
return {
type: FileType.Directory,
mtime: 0,
ctime: 0,
size: 0
};
async readdir(resource: URI): Promise<[string, FileType][]> {
try {
const handle = await this.getDirectoryHandle(resource);
if (!handle) {
throw this.createFileSystemProviderError(resource, 'No such file or directory, readdir', FileSystemProviderErrorCode.FileNotFound);
}
const result: [string, FileType][] = [];
for await (const [name, child] of handle) {
result.push([name, child.kind === 'file' ? FileType.File : FileType.Directory]);
}
return result;
} catch (error) {
throw this.toFileSystemProviderError(error);
}
}
const parent = await this.getParentDirectoryHandle(resource);
//#endregion
if (!parent) {
throw new Error('Stat error: no parent found');
//#region File Reading/Writing
readFileStream(resource: URI, opts: FileReadStreamOptions, token: CancellationToken): ReadableStreamEvents<Uint8Array> {
const stream = newWriteableStream<Uint8Array>(data => VSBuffer.concat(data.map(data => VSBuffer.wrap(data))).buffer, {
// Set a highWaterMark to prevent the stream
// for file upload to produce large buffers
// in-memory
highWaterMark: 10
});
(async () => {
try {
const handle = await this.getFileHandle(resource);
if (!handle) {
throw this.createFileSystemProviderError(resource, 'No such file or directory, readFile', FileSystemProviderErrorCode.FileNotFound);
}
const file = await handle.getFile();
// Partial file: implemented simply via `readFile`
if (typeof opts.length === 'number' || typeof opts.position === 'number') {
let buffer = new Uint8Array(await file.arrayBuffer());
if (typeof opts?.position === 'number') {
buffer = buffer.slice(opts.position);
}
if (typeof opts?.length === 'number') {
buffer = buffer.slice(0, opts.length);
}
stream.end(buffer);
}
// Entire file
else {
const reader: ReadableStreamDefaultReader<Uint8Array> = file.stream().getReader();
let res = await reader.read();
while (!res.done) {
if (token.isCancellationRequested) {
break;
}
// Write buffer into stream but make sure to wait
// in case the `highWaterMark` is reached
await stream.write(res.value);
if (token.isCancellationRequested) {
break;
}
res = await reader.read();
}
stream.end(undefined);
}
} catch (error) {
stream.error(this.toFileSystemProviderError(error));
stream.end();
}
})();
return stream;
}
async readFile(resource: URI): Promise<Uint8Array> {
try {
const handle = await this.getFileHandle(resource);
if (!handle) {
throw this.createFileSystemProviderError(resource, 'No such file or directory, readFile', FileSystemProviderErrorCode.FileNotFound);
}
const file = await handle.getFile();
return new Uint8Array(await file.arrayBuffer());
} catch (error) {
throw this.toFileSystemProviderError(error);
}
}
const name = extUri.basename(resource);
for await (const [childName, child] of parent) {
if (childName === name) {
if (child.kind === 'file') {
const file = await child.getFile();
async writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
try {
let handle = await this.getFileHandle(resource);
return {
type: FileType.File,
mtime: file.lastModified,
ctime: 0,
size: file.size
};
// Validate target unless { create: true, overwrite: true }
if (!opts.create || !opts.overwrite) {
if (handle) {
if (!opts.overwrite) {
throw this.createFileSystemProviderError(resource, 'File already exists, writeFile', FileSystemProviderErrorCode.FileExists);
}
} else {
return {
type: FileType.Directory,
mtime: 0,
ctime: 0,
size: 0
};
if (!opts.create) {
throw this.createFileSystemProviderError(resource, 'No such file, writeFile', FileSystemProviderErrorCode.FileNotFound);
}
}
}
// Create target as needed
if (!handle) {
const parent = await this.getDirectoryHandle(this.extUri.dirname(resource));
if (!parent) {
throw this.createFileSystemProviderError(resource, 'No such parent directory, writeFile', FileSystemProviderErrorCode.FileNotFound);
}
handle = await parent.getFileHandle(this.extUri.basename(resource), { create: true });
if (!handle) {
throw this.createFileSystemProviderError(resource, 'Unable to create file , writeFile', FileSystemProviderErrorCode.Unknown);
}
}
// Write to target overwriting any existing contents
const writable = await handle.createWritable();
await writable.write(content);
await writable.close();
} catch (error) {
throw this.toFileSystemProviderError(error);
}
}
//#endregion
//#region Move/Copy/Delete/Create Folder
async mkdir(resource: URI): Promise<void> {
try {
const parent = await this.getDirectoryHandle(this.extUri.dirname(resource));
if (!parent) {
throw this.createFileSystemProviderError(resource, 'No such parent directory, mkdir', FileSystemProviderErrorCode.FileNotFound);
}
await parent.getDirectoryHandle(this.extUri.basename(resource), { create: true });
} catch (error) {
throw this.toFileSystemProviderError(error);
}
}
async delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
try {
const parent = await this.getDirectoryHandle(this.extUri.dirname(resource));
if (!parent) {
throw this.createFileSystemProviderError(resource, 'No such parent directory, delete', FileSystemProviderErrorCode.FileNotFound);
}
return parent.removeEntry(this.extUri.basename(resource), { recursive: opts.recursive });
} catch (error) {
throw this.toFileSystemProviderError(error);
}
}
async rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> {
try {
if (this.extUri.isEqual(from, to)) {
return; // no-op if the paths are the same
}
// Implement file rename by write + delete
let fileHandle = await this.getFileHandle(from);
if (fileHandle) {
const file = await fileHandle.getFile();
const contents = new Uint8Array(await file.arrayBuffer());
await this.writeFile(to, contents, { create: true, overwrite: opts.overwrite, unlock: false });
await this.delete(from, { recursive: false, useTrash: false });
}
// File API does not support any real rename otherwise
else {
throw this.createFileSystemProviderError(from, localize('fileSystemRenameError', "Rename is only supported for files."), FileSystemProviderErrorCode.Unavailable);
}
} catch (error) {
throw this.toFileSystemProviderError(error);
}
}
//#endregion
//#region File Watching (unsupported)
watch(resource: URI, opts: IWatchOptions): IDisposable {
return Disposable.None;
}
//#endregion
//#region File/Directoy Handle Registry
private readonly files = new Map<string, FileSystemFileHandle>();
private readonly directories = new Map<string, FileSystemDirectoryHandle>();
registerFileHandle(handle: FileSystemFileHandle): URI {
const handleId = generateUuid();
this.files.set(handleId, handle);
return this.toHandleUri(handle, handleId);
}
registerDirectoryHandle(handle: FileSystemDirectoryHandle): URI {
const handleId = generateUuid();
this.directories.set(handleId, handle);
return this.toHandleUri(handle, handleId);
}
private toHandleUri(handle: FileSystemHandle, handleId: string): URI {
return URI.from({ scheme: Schemas.file, path: `/${handle.name}`, query: handleId });
}
private async getHandle(resource: URI): Promise<FileSystemHandle | undefined> {
// First: try to find a well known handle first
let handle = this.getHandleSync(resource);
// Second: walk up parent directories and resolve handle if possible
if (!handle) {
const parent = await this.getDirectoryHandle(this.extUri.dirname(resource));
if (parent) {
const name = extUri.basename(resource);
try {
handle = await parent.getFileHandle(name);
} catch (error) {
try {
handle = await parent.getDirectoryHandle(name);
} catch (error) {
// Ignore
}
}
}
}
throw new Error('Stat error: entry not found');
return handle;
}
mkdir(resource: URI): Promise<void> {
throw new Error('Method not implemented.');
}
private getHandleSync(resource: URI): FileSystemHandle | undefined {
async readdir(resource: URI): Promise<[string, FileType][]> {
const parent = await this.getDirectoryHandle(resource);
if (!parent) {
throw new Error('Stat error: no parent found');
}
const result: [string, FileType][] = [];
for await (const [name, child] of parent) {
result.push([name, child.kind === 'file' ? FileType.File : FileType.Directory]);
}
return result;
}
delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
throw new Error('Method not implemented: delete');
}
rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> {
throw new Error('Method not implemented: rename');
}
private async getDirectoryHandle(uri: URI): Promise<FileSystemDirectoryHandle | undefined> {
const rootUUID = getRootUUID(uri);
if (rootUUID) {
return this.directories.get(rootUUID);
}
const splitResult = split(uri.path);
if (!splitResult) {
// We store file system handles with the `handle.name`
// and as such require the resource to be on the root
if (this.extUri.dirname(resource).path !== '/') {
return undefined;
}
const parent = await this.getDirectoryHandle(URI.from({ ...uri, path: splitResult[0] }));
return await parent?.getDirectoryHandle(extUri.basename(uri));
}
const handleId = resource.query;
private async getParentDirectoryHandle(uri: URI): Promise<FileSystemDirectoryHandle | undefined> {
return this.getDirectoryHandle(URI.from({ ...uri, path: extUri.dirname(uri).path }));
}
private async getFileHandle(uri: URI): Promise<FileSystemFileHandle | undefined> {
const rootUUID = getRootUUID(uri);
if (rootUUID) {
return this.files.get(rootUUID);
const handle = this.files.get(handleId) || this.directories.get(handleId);
if (!handle) {
throw this.createFileSystemProviderError(resource, 'No file system handle registered', FileSystemProviderErrorCode.Unavailable);
}
const parent = await this.getParentDirectoryHandle(uri);
const name = extUri.basename(uri);
return await parent?.getFileHandle(name);
return handle;
}
registerFileHandle(uuid: string, handle: FileSystemFileHandle): void {
this.files.set(uuid, handle);
private async getFileHandle(resource: URI): Promise<FileSystemFileHandle | undefined> {
const handle = this.getHandleSync(resource);
if (handle instanceof FileSystemFileHandle) {
return handle;
}
const parent = await this.getDirectoryHandle(this.extUri.dirname(resource));
try {
return await parent?.getFileHandle(extUri.basename(resource));
} catch (error) {
return undefined; // guard against possible DOMException
}
}
registerDirectoryHandle(uuid: string, handle: FileSystemDirectoryHandle): void {
this.directories.set(uuid, handle);
private async getDirectoryHandle(resource: URI): Promise<FileSystemDirectoryHandle | undefined> {
const handle = this.getHandleSync(resource);
if (handle instanceof FileSystemDirectoryHandle) {
return handle;
}
const parent = await this.getDirectoryHandle(this.extUri.dirname(resource));
try {
return await parent?.getDirectoryHandle(extUri.basename(resource));
} catch (error) {
return undefined; // guard against possible DOMException
}
}
dispose(): void {
this._onDidChangeFile.dispose();
//#endregion
private toFileSystemProviderError(error: Error): FileSystemProviderError {
if (error instanceof FileSystemProviderError) {
return error; // avoid double conversion
}
let code = FileSystemProviderErrorCode.Unknown;
if (error.name === 'NotAllowedError') {
error = new Error(localize('fileSystemNotAllowedError', "Insufficient permissions. Please retry and allow the operation."));
code = FileSystemProviderErrorCode.Unavailable;
}
return createFileSystemProviderError(error, code);
}
private createFileSystemProviderError(resource: URI, msg: string, code: FileSystemProviderErrorCode): FileSystemProviderError {
return createFileSystemProviderError(new Error(`${msg} (${normalize(resource.path)})`), code);
}
}

View File

@@ -3,14 +3,14 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { IFileSystemProviderWithFileReadWriteCapability, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileDeleteOptions, FileWriteOptions, FileChangeType, createFileSystemProviderError, FileSystemProviderErrorCode } from 'vs/platform/files/common/files';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { VSBuffer } from 'vs/base/common/buffer';
import { Throttler } from 'vs/base/common/async';
import { localize } from 'vs/nls';
import { VSBuffer } from 'vs/base/common/buffer';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { joinPath } from 'vs/base/common/resources';
import { URI, UriComponents } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { createFileSystemProviderError, FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, FileWriteOptions, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, IWatchOptions } from 'vs/platform/files/common/files';
const INDEXEDDB_VSCODE_DB = 'vscode-web-db';
export const INDEXEDDB_USERDATA_OBJECT_STORE = 'vscode-userdata-store';
@@ -33,12 +33,12 @@ export class IndexedDB {
this.indexedDBPromise = this.openIndexedDB(INDEXEDDB_VSCODE_DB, 2, [INDEXEDDB_USERDATA_OBJECT_STORE, INDEXEDDB_LOGS_OBJECT_STORE]);
}
async createFileSystemProvider(scheme: string, store: string): Promise<IIndexedDBFileSystemProvider | null> {
async createFileSystemProvider(scheme: string, store: string, watchCrossWindowChanges: boolean): Promise<IIndexedDBFileSystemProvider | null> {
let fsp: IIndexedDBFileSystemProvider | null = null;
const indexedDB = await this.indexedDBPromise;
if (indexedDB) {
if (indexedDB.objectStoreNames.contains(store)) {
fsp = new IndexedDBFileSystemProvider(scheme, indexedDB, store);
fsp = new IndexedDBFileSystemProvider(scheme, indexedDB, store, watchCrossWindowChanges);
} else {
console.error(`Error while creating indexedDB filesystem provider. Could not find ${store} object store`);
}
@@ -205,6 +205,11 @@ class IndexedDBFileSystemNode {
}
}
type FileChangeDto = {
readonly type: FileChangeType;
readonly resource: UriComponents;
};
class IndexedDBFileSystemProvider extends Disposable implements IIndexedDBFileSystemProvider {
readonly capabilities: FileSystemProviderCapabilities =
@@ -212,18 +217,34 @@ class IndexedDBFileSystemProvider extends Disposable implements IIndexedDBFileSy
| FileSystemProviderCapabilities.PathCaseSensitive;
readonly onDidChangeCapabilities: Event<void> = Event.None;
private readonly changesKey: string;
private readonly _onDidChangeFile = this._register(new Emitter<readonly IFileChange[]>());
readonly onDidChangeFile: Event<readonly IFileChange[]> = this._onDidChangeFile.event;
private readonly versions: Map<string, number> = new Map<string, number>();
private readonly versions = new Map<string, number>();
private cachedFiletree: Promise<IndexedDBFileSystemNode> | undefined;
private writeManyThrottler: Throttler;
constructor(scheme: string, private readonly database: IDBDatabase, private readonly store: string) {
constructor(scheme: string, private readonly database: IDBDatabase, private readonly store: string, private readonly watchCrossWindowChanges: boolean) {
super();
this.writeManyThrottler = new Throttler();
this.changesKey = `vscode.indexedDB.${scheme}.changes`;
if (watchCrossWindowChanges) {
const storageListener = (event: StorageEvent) => this.onDidStorageChange(event);
window.addEventListener('storage', storageListener);
this._register(toDisposable(() => window.removeEventListener('storage', storageListener)));
}
}
private onDidStorageChange(event: StorageEvent): void {
if (event.key === this.changesKey && event.newValue) {
try {
const changesDto: FileChangeDto[] = JSON.parse(event.newValue);
this._onDidChangeFile.fire(changesDto.map(c => ({ type: c.type, resource: URI.revive(c.resource) })));
} catch (error) {/* ignore*/ }
}
}
watch(resource: URI, opts: IWatchOptions): IDisposable {
@@ -317,7 +338,7 @@ class IndexedDBFileSystemProvider extends Disposable implements IIndexedDBFileSy
await this.writeManyThrottler.queue(() => this.writeMany());
(await this.getFiletree()).add(resource.path, { type: 'file', size: content.byteLength });
this.versions.set(resource.toString(), (this.versions.get(resource.toString()) || 0) + 1);
this._onDidChangeFile.fire([{ resource, type: FileChangeType.UPDATED }]);
this.triggerChanges([{ resource, type: FileChangeType.UPDATED }]);
}
async delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
@@ -344,7 +365,7 @@ class IndexedDBFileSystemProvider extends Disposable implements IIndexedDBFileSy
await this.deleteKeys(toDelete);
(await this.getFiletree()).delete(resource.path);
toDelete.forEach(key => this.versions.delete(key));
this._onDidChangeFile.fire(toDelete.map(path => ({ resource: resource.with({ path }), type: FileChangeType.DELETED })));
this.triggerChanges(toDelete.map(path => ({ resource: resource.with({ path }), type: FileChangeType.DELETED })));
}
private async tree(resource: URI): Promise<DirEntry[]> {
@@ -371,6 +392,18 @@ class IndexedDBFileSystemProvider extends Disposable implements IIndexedDBFileSy
return Promise.reject(new Error('Not Supported'));
}
private triggerChanges(changes: IFileChange[]): void {
if (changes.length) {
this._onDidChangeFile.fire(changes);
if (this.watchCrossWindowChanges) {
// remove previous changes so that event is triggered even if new changes are same as old changes
window.localStorage.removeItem(this.changesKey);
window.localStorage.setItem(this.changesKey, JSON.stringify(changes));
}
}
}
private getFiletree(): Promise<IndexedDBFileSystemNode> {
if (!this.cachedFiletree) {
this.cachedFiletree = new Promise((c, e) => {

View File

@@ -3,23 +3,23 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { mark } from 'vs/base/common/performance';
import { Disposable, IDisposable, toDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED, hasFileReadStreamCapability, IFileSystemProviderWithFileReadStreamCapability, ensureFileSystemProviderError, IFileSystemProviderCapabilitiesChangeEvent, IReadFileStreamOptions, FileDeleteOptions, FilePermission, NotModifiedSinceFileOperationError } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { coalesce } from 'vs/base/common/arrays';
import { Promises, ResourceQueue, ThrottledWorker } from 'vs/base/common/async';
import { bufferedStreamToBuffer, bufferToReadable, newWriteableBufferStream, readableToBuffer, streamToBuffer, VSBuffer, VSBufferReadable, VSBufferReadableBufferedStream, VSBufferReadableStream } from 'vs/base/common/buffer';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Emitter } from 'vs/base/common/event';
import { IExtUri, extUri, extUriIgnorePathCase, isAbsolutePath } from 'vs/base/common/resources';
import { TernarySearchTree } from 'vs/base/common/map';
import { isNonEmptyArray, coalesce } from 'vs/base/common/arrays';
import { ILogService } from 'vs/platform/log/common/log';
import { VSBuffer, VSBufferReadable, readableToBuffer, bufferToReadable, streamToBuffer, VSBufferReadableStream, VSBufferReadableBufferedStream, bufferedStreamToBuffer, newWriteableBufferStream } from 'vs/base/common/buffer';
import { isReadableStream, transform, peekReadable, peekStream, isReadableBufferedStream, newWriteableStream, listenStream, consumeStream } from 'vs/base/common/stream';
import { Promises, ResourceQueue } from 'vs/base/common/async';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import { Schemas } from 'vs/base/common/network';
import { readFileIntoStream } from 'vs/platform/files/common/io';
import { Iterable } from 'vs/base/common/iterator';
import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { TernarySearchTree } from 'vs/base/common/map';
import { Schemas } from 'vs/base/common/network';
import { mark } from 'vs/base/common/performance';
import { extUri, extUriIgnorePathCase, IExtUri, isAbsolutePath } from 'vs/base/common/resources';
import { consumeStream, isReadableBufferedStream, isReadableStream, listenStream, newWriteableStream, peekReadable, peekStream, transform } from 'vs/base/common/stream';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { ensureFileSystemProviderError, etag, ETAG_DISABLED, FileChangesEvent, FileDeleteOptions, FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FilePermission, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, hasFileFolderCopyCapability, hasFileReadStreamCapability, hasOpenReadWriteCloseCapability, hasReadWriteCapability, ICreateFileOptions, IFileChange, IFileContent, IFileService, IFileStat, IFileStatWithMetadata, IFileStreamContent, IFileSystemProvider, IFileSystemProviderActivationEvent, IFileSystemProviderCapabilitiesChangeEvent, IFileSystemProviderRegistrationEvent, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IRawFileChangesEvent, IReadFileOptions, IReadFileStreamOptions, IResolveFileOptions, IResolveFileResult, IResolveFileResultWithMetadata, IResolveMetadataFileOptions, IStat, IWatchOptions, IWriteFileOptions, NotModifiedSinceFileOperationError, toFileOperationResult, toFileSystemProviderErrorCode } from 'vs/platform/files/common/files';
import { readFileIntoStream } from 'vs/platform/files/common/io';
import { ILogService } from 'vs/platform/log/common/log';
export class FileService extends Disposable implements IFileService {
@@ -57,7 +57,7 @@ export class FileService extends Disposable implements IFileService {
// Forward events from provider
const providerDisposables = new DisposableStore();
providerDisposables.add(provider.onDidChangeFile(changes => this._onDidFilesChange.fire(new FileChangesEvent(changes, !this.isPathCaseSensitive(provider)))));
providerDisposables.add(provider.onDidChangeFile(changes => this.onDidChangeFile(changes, this.isPathCaseSensitive(provider))));
providerDisposables.add(provider.onDidChangeCapabilities(() => this._onDidChangeFileSystemProviderCapabilities.fire({ provider, scheme })));
if (typeof provider.onDidErrorOccur === 'function') {
providerDisposables.add(provider.onDidErrorOccur(error => this._onError.fire(new Error(error))));
@@ -200,13 +200,15 @@ export class FileService extends Disposable implements IFileService {
if (!trie) {
trie = TernarySearchTree.forUris<true>(() => !isPathCaseSensitive);
trie.set(resource, true);
if (isNonEmptyArray(resolveTo)) {
resolveTo.forEach(uri => trie!.set(uri, true));
if (resolveTo) {
for (const uri of resolveTo) {
trie.set(uri, true);
}
}
}
// check for recursive resolving
if (Boolean(trie.findSuperstr(stat.resource) || trie.get(stat.resource))) {
if (trie.get(stat.resource) || trie.findSuperstr(stat.resource.with({ query: null, fragment: null } /* required for https://github.com/microsoft/vscode/issues/128151 */))) {
return true;
}
@@ -418,7 +420,7 @@ export class FileService extends Disposable implements IFileService {
// but to the same length. This is a compromise we take to avoid having to produce checksums of
// the file content for comparison which would be much slower to compute.
if (
options && typeof options.mtime === 'number' && typeof options.etag === 'string' && options.etag !== ETAG_DISABLED &&
typeof options?.mtime === 'number' && typeof options.etag === 'string' && options.etag !== ETAG_DISABLED &&
typeof stat.mtime === 'number' && typeof stat.size === 'number' &&
options.mtime < stat.mtime && options.etag !== etag({ mtime: options.mtime /* not using stat.mtime for a reason, see above */, size: stat.size })
) {
@@ -496,7 +498,7 @@ export class FileService extends Disposable implements IFileService {
// due to the likelihood of hitting a NOT_MODIFIED_SINCE result.
// otherwise, we let it run in parallel to the file reading for
// optimal startup performance.
if (options && typeof options.etag === 'string' && options.etag !== ETAG_DISABLED) {
if (typeof options?.etag === 'string' && options.etag !== ETAG_DISABLED) {
await statPromise;
}
@@ -572,12 +574,12 @@ export class FileService extends Disposable implements IFileService {
let buffer = await provider.readFile(resource);
// respect position option
if (options && typeof options.position === 'number') {
if (typeof options?.position === 'number') {
buffer = buffer.slice(options.position);
}
// respect length option
if (options && typeof options.length === 'number') {
if (typeof options?.length === 'number') {
buffer = buffer.slice(0, options.length);
}
@@ -604,7 +606,7 @@ export class FileService extends Disposable implements IFileService {
}
// Throw if file not modified since (unless disabled)
if (options && typeof options.etag === 'string' && options.etag !== ETAG_DISABLED && options.etag === stat.etag) {
if (typeof options?.etag === 'string' && options.etag !== ETAG_DISABLED && options.etag === stat.etag) {
throw new NotModifiedSinceFileOperationError(localize('fileNotModifiedError', "File not modified since"), stat, options);
}
@@ -965,29 +967,154 @@ export class FileService extends Disposable implements IFileService {
//#region File Watching
/**
* Providers can send unlimited amount of `IFileChange` events
* and we want to protect against this to reduce CPU pressure.
* The following settings limit the amount of file changes we
* process at once.
* (https://github.com/microsoft/vscode/issues/124723)
*/
private static readonly FILE_EVENTS_THROTTLING = {
maxChangesChunkSize: 500 as const, // number of changes we process per interval
maxChangesBufferSize: 30000 as const, // total number of changes we are willing to buffer in memory
coolDownDelay: 200 as const, // rest for 100ms before processing next events
warningscounter: 0 // keep track how many warnings we showed to reduce log spam
};
private readonly _onDidFilesChange = this._register(new Emitter<FileChangesEvent>());
readonly onDidFilesChange = this._onDidFilesChange.event;
private readonly _onDidChangeFilesRaw = this._register(new Emitter<IRawFileChangesEvent>());
readonly onDidChangeFilesRaw = this._onDidChangeFilesRaw.event;
private readonly activeWatchers = new Map<string, { disposable: IDisposable, count: number; }>();
watch(resource: URI, options: IWatchOptions = { recursive: false, excludes: [] }): IDisposable {
let watchDisposed = false;
let disposeWatch = () => { watchDisposed = true; };
private readonly caseSensitiveFileEventsWorker = this._register(
new ThrottledWorker<IFileChange>(
FileService.FILE_EVENTS_THROTTLING.maxChangesChunkSize,
FileService.FILE_EVENTS_THROTTLING.maxChangesBufferSize,
FileService.FILE_EVENTS_THROTTLING.coolDownDelay,
chunks => this._onDidFilesChange.fire(new FileChangesEvent(chunks, false))
)
);
// Watch and wire in disposable which is async but
// check if we got disposed meanwhile and forward
this.doWatch(resource, options).then(disposable => {
if (watchDisposed) {
dispose(disposable);
} else {
disposeWatch = () => dispose(disposable);
private readonly caseInsensitiveFileEventsWorker = this._register(
new ThrottledWorker<IFileChange>(
FileService.FILE_EVENTS_THROTTLING.maxChangesChunkSize,
FileService.FILE_EVENTS_THROTTLING.maxChangesBufferSize,
FileService.FILE_EVENTS_THROTTLING.coolDownDelay,
chunks => this._onDidFilesChange.fire(new FileChangesEvent(chunks, true))
)
);
private onDidChangeFile(changes: readonly IFileChange[], caseSensitive: boolean): void {
// Event #1: access to raw events goes out instantly
{
this._onDidChangeFilesRaw.fire({ changes });
}
// Event #2: immediately send out events for
// explicitly watched resources by splitting
// changes up into 2 buckets
let explicitlyWatchedFileChanges: IFileChange[] | undefined = undefined;
let implicitlyWatchedFileChanges: IFileChange[] | undefined = undefined;
{
for (const change of changes) {
if (this.watchedResources.has(change.resource)) {
if (!explicitlyWatchedFileChanges) {
explicitlyWatchedFileChanges = [];
}
explicitlyWatchedFileChanges.push(change);
} else {
if (!implicitlyWatchedFileChanges) {
implicitlyWatchedFileChanges = [];
}
implicitlyWatchedFileChanges.push(change);
}
}
}, error => this.logService.error(error));
return toDisposable(() => disposeWatch());
if (explicitlyWatchedFileChanges) {
this._onDidFilesChange.fire(new FileChangesEvent(explicitlyWatchedFileChanges, !caseSensitive));
}
}
// Event #3: implicitly watched resources get
// throttled due to performance reasons
if (implicitlyWatchedFileChanges) {
const worker = caseSensitive ? this.caseSensitiveFileEventsWorker : this.caseInsensitiveFileEventsWorker;
const worked = worker.work(implicitlyWatchedFileChanges);
if (!worked && FileService.FILE_EVENTS_THROTTLING.warningscounter++ < 10) {
this.logService.warn(`[File watcher]: started ignoring events due to too many file change events at once (incoming: ${implicitlyWatchedFileChanges.length}, most recent change: ${implicitlyWatchedFileChanges[0].resource.toString()}). Use 'files.watcherExclude' setting to exclude folders with lots of changing files (e.g. compilation output).`);
}
if (worker.pending > 0) {
this.logService.trace(`[File watcher]: started throttling events due to large amount of file change events at once (pending: ${worker.pending}, most recent change: ${implicitlyWatchedFileChanges[0].resource.toString()}). Use 'files.watcherExclude' setting to exclude folders with lots of changing files (e.g. compilation output).`);
}
}
}
async doWatch(resource: URI, options: IWatchOptions): Promise<IDisposable> {
private readonly watchedResources = TernarySearchTree.forUris<number>(uri => {
const provider = this.getProvider(uri.scheme);
if (provider) {
return !this.isPathCaseSensitive(provider);
}
return false;
});
watch(resource: URI, options: IWatchOptions = { recursive: false, excludes: [] }): IDisposable {
const disposables = new DisposableStore();
// Forward watch request to provider and
// wire in disposables.
{
let watchDisposed = false;
let disposeWatch = () => { watchDisposed = true; };
disposables.add(toDisposable(() => disposeWatch()));
// Watch and wire in disposable which is async but
// check if we got disposed meanwhile and forward
this.doWatch(resource, options).then(disposable => {
if (watchDisposed) {
dispose(disposable);
} else {
disposeWatch = () => dispose(disposable);
}
}, error => this.logService.error(error));
}
// Remember as watched resource and unregister
// properly on disposal.
//
// Note: we only do this for non-recursive watchers
// until we have a better `createWatcher` based API
// (https://github.com/microsoft/vscode/issues/126809)
//
if (!options.recursive) {
// Increment counter for resource
this.watchedResources.set(resource, (this.watchedResources.get(resource) ?? 0) + 1);
// Decrement counter for resource on dispose
// and remove from map when last one is gone
disposables.add(toDisposable(() => {
const watchedResourceCounter = this.watchedResources.get(resource);
if (typeof watchedResourceCounter === 'number') {
if (watchedResourceCounter <= 1) {
this.watchedResources.delete(resource);
} else {
this.watchedResources.set(resource, watchedResourceCounter - 1);
}
}
}));
}
return disposables;
}
private async doWatch(resource: URI, options: IWatchOptions): Promise<IDisposable> {
const provider = await this.withProvider(resource);
const key = this.toWatchKey(provider, resource, options);
@@ -1026,7 +1153,10 @@ export class FileService extends Disposable implements IFileService {
override dispose(): void {
super.dispose();
this.activeWatchers.forEach(watcher => dispose(watcher.disposable));
for (const [, watcher] of this.activeWatchers) {
dispose(watcher.disposable);
}
this.activeWatchers.clear();
}

View File

@@ -3,19 +3,19 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { sep } from 'vs/base/common/path';
import { URI } from 'vs/base/common/uri';
import { IExpression } from 'vs/base/common/glob';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
import { startsWithIgnoreCase } from 'vs/base/common/strings';
import { IDisposable } from 'vs/base/common/lifecycle';
import { isNumber } from 'vs/base/common/types';
import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer';
import { ReadableStreamEvents } from 'vs/base/common/stream';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event';
import { IExpression } from 'vs/base/common/glob';
import { IDisposable } from 'vs/base/common/lifecycle';
import { TernarySearchTree } from 'vs/base/common/map';
import { sep } from 'vs/base/common/path';
import { ReadableStreamEvents } from 'vs/base/common/stream';
import { startsWithIgnoreCase } from 'vs/base/common/strings';
import { isNumber } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
//#region file service & providers
@@ -77,6 +77,15 @@ export interface IFileService {
*/
readonly onDidFilesChange: Event<FileChangesEvent>;
/**
*
* Raw access to all file events emitted from file system providers.
*
* @deprecated use this method only if you know what you are doing. use the other watch related events
* and APIs for more efficient file watching.
*/
readonly onDidChangeFilesRaw: Event<IRawFileChangesEvent>;
/**
* An event that is fired upon successful completion of a certain file operation.
*/
@@ -639,40 +648,38 @@ export interface IFileChange {
readonly resource: URI;
}
export class FileChangesEvent {
export interface IRawFileChangesEvent {
/**
* @deprecated use the `contains()` or `affects` method to efficiently find
* out if the event relates to a given resource. these methods ensure:
* - that there is no expensive lookup needed (by using a `TernarySearchTree`)
* - correctly handles `FileChangeType.DELETED` events
* @deprecated use `FileChangesEvent` instead unless you know what you are doing
*/
readonly changes: readonly IFileChange[];
}
export class FileChangesEvent {
private readonly added: TernarySearchTree<URI, IFileChange> | undefined = undefined;
private readonly updated: TernarySearchTree<URI, IFileChange> | undefined = undefined;
private readonly deleted: TernarySearchTree<URI, IFileChange> | undefined = undefined;
constructor(changes: readonly IFileChange[], private readonly ignorePathCasing: boolean) {
this.changes = changes;
constructor(changes: readonly IFileChange[], ignorePathCasing: boolean) {
for (const change of changes) {
switch (change.type) {
case FileChangeType.ADDED:
if (!this.added) {
this.added = TernarySearchTree.forUris<IFileChange>(() => this.ignorePathCasing);
this.added = TernarySearchTree.forUris<IFileChange>(() => ignorePathCasing);
}
this.added.set(change.resource, change);
break;
case FileChangeType.UPDATED:
if (!this.updated) {
this.updated = TernarySearchTree.forUris<IFileChange>(() => this.ignorePathCasing);
this.updated = TernarySearchTree.forUris<IFileChange>(() => ignorePathCasing);
}
this.updated.set(change.resource, change);
break;
case FileChangeType.DELETED:
if (!this.deleted) {
this.deleted = TernarySearchTree.forUris<IFileChange>(() => this.ignorePathCasing);
this.deleted = TernarySearchTree.forUris<IFileChange>(() => ignorePathCasing);
}
this.deleted.set(change.resource, change);
break;
@@ -741,16 +748,6 @@ export class FileChangesEvent {
return false;
}
/**
* @deprecated use the `contains()` method to efficiently find out if the event
* relates to a given resource. this method ensures:
* - that there is no expensive lookup needed by using a `TernarySearchTree`
* - correctly handles `FileChangeType.DELETED` events
*/
getAdded(): IFileChange[] {
return this.getOfType(FileChangeType.ADDED);
}
/**
* Returns if this event contains added files.
*/
@@ -758,16 +755,6 @@ export class FileChangesEvent {
return !!this.added;
}
/**
* @deprecated use the `contains()` method to efficiently find out if the event
* relates to a given resource. this method ensures:
* - that there is no expensive lookup needed by using a `TernarySearchTree`
* - correctly handles `FileChangeType.DELETED` events
*/
getDeleted(): IFileChange[] {
return this.getOfType(FileChangeType.DELETED);
}
/**
* Returns if this event contains deleted files.
*/
@@ -782,18 +769,22 @@ export class FileChangesEvent {
return !!this.updated;
}
private getOfType(type: FileChangeType): IFileChange[] {
const changes: IFileChange[] = [];
/**
* @deprecated use the `contains` or `affects` method to efficiently find
* out if the event relates to a given resource. these methods ensure:
* - that there is no expensive lookup needed (by using a `TernarySearchTree`)
* - correctly handles `FileChangeType.DELETED` events
*/
get rawAdded(): TernarySearchTree<URI, IFileChange> | undefined { return this.added; }
const eventsForType = type === FileChangeType.ADDED ? this.added : type === FileChangeType.UPDATED ? this.updated : this.deleted;
if (eventsForType) {
for (const [, change] of eventsForType) {
changes.push(change);
}
}
/**
* @deprecated use the `contains` or `affects` method to efficiently find
* out if the event relates to a given resource. these methods ensure:
* - that there is no expensive lookup needed (by using a `TernarySearchTree`)
* - correctly handles `FileChangeType.DELETED` events
*/
get rawDeleted(): TernarySearchTree<URI, IFileChange> | undefined { return this.deleted; }
return changes;
}
}
export function isParent(path: string, candidate: string, ignoreCase?: boolean): boolean {

View File

@@ -3,11 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import * as resources from 'vs/base/common/resources';
import { FileChangeType, FileType, IWatchOptions, IStat, FileSystemProviderErrorCode, FileSystemProviderError, FileWriteOptions, IFileChange, FileDeleteOptions, FileSystemProviderCapabilities, FileOverwriteOptions, IFileSystemProviderWithFileReadWriteCapability } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileSystemProviderError, FileSystemProviderErrorCode, FileType, FileWriteOptions, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, IWatchOptions } from 'vs/platform/files/common/files';
class File implements IStat {

View File

@@ -3,13 +3,13 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IFileSystemProviderWithOpenReadWriteCloseCapability, FileReadStreamOptions, createFileSystemProviderError, FileSystemProviderErrorCode, ensureFileSystemProviderError } from 'vs/platform/files/common/files';
import { canceled } from 'vs/base/common/errors';
import { IErrorTransformer, IDataTransformer, WriteableStream } from 'vs/base/common/stream';
import { IDataTransformer, IErrorTransformer, WriteableStream } from 'vs/base/common/stream';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { createFileSystemProviderError, ensureFileSystemProviderError, FileReadStreamOptions, FileSystemProviderErrorCode, IFileSystemProviderWithOpenReadWriteCloseCapability } from 'vs/platform/files/common/files';
import product from 'vs/platform/product/common/product';
export interface ICreateReadStreamOptions extends FileReadStreamOptions {

View File

@@ -3,17 +3,17 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { canceled } from 'vs/base/common/errors';
import { Emitter } from 'vs/base/common/event';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { newWriteableStream, ReadableStreamEventPayload, ReadableStreamEvents } from 'vs/base/common/stream';
import { URI, UriComponents } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, IFileChange, IStat, IWatchOptions, FileOpenOptions, IFileSystemProviderWithFileReadWriteCapability, FileWriteOptions, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileFolderCopyCapability, FileReadStreamOptions, IFileSystemProviderWithOpenReadWriteCloseCapability } from 'vs/platform/files/common/files';
import { VSBuffer } from 'vs/base/common/buffer';
import { newWriteableStream, ReadableStreamEvents, ReadableStreamEventPayload } from 'vs/base/common/stream';
import { CancellationToken } from 'vs/base/common/cancellation';
import { canceled } from 'vs/base/common/errors';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { FileChangeType, FileDeleteOptions, FileOpenOptions, FileOverwriteOptions, FileReadStreamOptions, FileSystemProviderCapabilities, FileType, FileWriteOptions, IFileChange, IFileSystemProviderWithFileFolderCopyCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IStat, IWatchOptions } from 'vs/platform/files/common/files';
interface IFileChangeDto {
resource: UriComponents;

View File

@@ -3,11 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { isWindows } from 'vs/base/common/platform';
import { basename } from 'vs/base/common/path';
import { DiskFileSystemProvider as NodeDiskFileSystemProvider, IDiskFileSystemProviderOptions } from 'vs/platform/files/node/diskFileSystemProvider';
import { isWindows } from 'vs/base/common/platform';
import { localize } from 'vs/nls';
import { FileDeleteOptions, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { DiskFileSystemProvider as NodeDiskFileSystemProvider, IDiskFileSystemProviderOptions } from 'vs/platform/files/node/diskFileSystemProvider';
import { ILogService } from 'vs/platform/log/common/log';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';

View File

@@ -4,28 +4,28 @@
*--------------------------------------------------------------------------------------------*/
import { Stats } from 'fs';
import { IDisposable, Disposable, toDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
import { FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, FileReadStreamOptions, IFileSystemProviderWithFileFolderCopyCapability, isFileOpenForWriteOptions } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { SymlinkSupport, RimRafMode, IDirent, Promises } from 'vs/base/node/pfs';
import { normalize, basename, dirname } from 'vs/base/common/path';
import { joinPath } from 'vs/base/common/resources';
import { isEqual } from 'vs/base/common/extpath';
import { retry, ThrottledDelayer } from 'vs/base/common/async';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
import { localize } from 'vs/nls';
import { IDiskFileChange, toFileChanges, ILogMessage } from 'vs/platform/files/node/watcher/watcher';
import { FileWatcher as UnixWatcherService } from 'vs/platform/files/node/watcher/unix/watcherService';
import { FileWatcher as WindowsWatcherService } from 'vs/platform/files/node/watcher/win32/watcherService';
import { FileWatcher as NsfwWatcherService } from 'vs/platform/files/node/watcher/nsfw/watcherService';
import { FileWatcher as NodeJSWatcherService } from 'vs/platform/files/node/watcher/nodejs/watcherService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ReadableStreamEvents, newWriteableStream } from 'vs/base/common/stream';
import { readFileIntoStream } from 'vs/platform/files/common/io';
import { insert } from 'vs/base/common/arrays';
import { retry, ThrottledDelayer } from 'vs/base/common/async';
import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { isEqual } from 'vs/base/common/extpath';
import { combinedDisposable, Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { basename, dirname, normalize } from 'vs/base/common/path';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { joinPath } from 'vs/base/common/resources';
import { newWriteableStream, ReadableStreamEvents } from 'vs/base/common/stream';
import { URI } from 'vs/base/common/uri';
import { IDirent, Promises, RimRafMode, SymlinkSupport } from 'vs/base/node/pfs';
import { localize } from 'vs/nls';
import { createFileSystemProviderError, FileDeleteOptions, FileOpenOptions, FileOverwriteOptions, FileReadStreamOptions, FileSystemProviderCapabilities, FileSystemProviderError, FileSystemProviderErrorCode, FileType, FileWriteOptions, IFileChange, IFileSystemProviderWithFileFolderCopyCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, isFileOpenForWriteOptions, IStat, IWatchOptions } from 'vs/platform/files/common/files';
import { readFileIntoStream } from 'vs/platform/files/common/io';
import { FileWatcher as NodeJSWatcherService } from 'vs/platform/files/node/watcher/nodejs/watcherService';
import { FileWatcher as NsfwWatcherService } from 'vs/platform/files/node/watcher/nsfw/watcherService';
import { FileWatcher as UnixWatcherService } from 'vs/platform/files/node/watcher/unix/watcherService';
import { IDiskFileChange, ILogMessage, toFileChanges } from 'vs/platform/files/node/watcher/watcher';
import { FileWatcher as WindowsWatcherService } from 'vs/platform/files/node/watcher/win32/watcherService';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
export interface IWatcherOptions {
pollingInterval?: number;

Some files were not shown because too many files have changed in this diff Show More