mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-15 10:58:31 -05:00
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:
@@ -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');
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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({});
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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'));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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+)$/;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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[]>;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user