mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-29 16:20:29 -04:00
Merge from vscode 2c306f762bf9c3db82dc06c7afaa56ef46d72f79 (#14050)
* Merge from vscode 2c306f762bf9c3db82dc06c7afaa56ef46d72f79 * Fix breaks * Extension management fixes * Fix breaks in windows bundling * Fix/skip failing tests * Update distro * Add clear to nuget.config * Add hygiene task * Bump distro * Fix hygiene issue * Add build to hygiene exclusion * Update distro * Update hygiene * Hygiene exclusions * Update tsconfig * Bump distro for server breaks * Update build config * Update darwin path * Add done calls to notebook tests * Skip failing tests * Disable smoke tests
This commit is contained in:
@@ -0,0 +1,474 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/runtimeExtensionsEditor';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Action, IAction, Separator } from 'vs/base/common/actions';
|
||||
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IExtensionService, IExtensionsStatus, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import { WorkbenchList } from 'vs/platform/list/browser/listService';
|
||||
import { append, $, reset, Dimension, clearNode } from 'vs/base/browser/dom';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { renderCodicons } from 'vs/base/browser/codicons';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { editorBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput';
|
||||
|
||||
interface IExtensionProfileInformation {
|
||||
/**
|
||||
* segment when the extension was running.
|
||||
* 2*i = segment start time
|
||||
* 2*i+1 = segment end time
|
||||
*/
|
||||
segments: number[];
|
||||
/**
|
||||
* total time when the extension was running.
|
||||
* (sum of all segment lengths).
|
||||
*/
|
||||
totalTime: number;
|
||||
}
|
||||
|
||||
export interface IRuntimeExtension {
|
||||
originalIndex: number;
|
||||
description: IExtensionDescription;
|
||||
marketplaceInfo: IExtension;
|
||||
status: IExtensionsStatus;
|
||||
profileInfo?: IExtensionProfileInformation;
|
||||
unresponsiveProfile?: IExtensionHostProfile;
|
||||
}
|
||||
|
||||
export abstract class AbstractRuntimeExtensionsEditor extends EditorPane {
|
||||
|
||||
public static readonly ID: string = 'workbench.editor.runtimeExtensions';
|
||||
|
||||
private _list: WorkbenchList<IRuntimeExtension> | null;
|
||||
private _elements: IRuntimeExtension[] | null;
|
||||
private _updateSoon: RunOnceScheduler;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IExtensionsWorkbenchService private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IExtensionService private readonly _extensionService: IExtensionService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
|
||||
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ILabelService private readonly _labelService: ILabelService,
|
||||
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
|
||||
) {
|
||||
super(AbstractRuntimeExtensionsEditor.ID, telemetryService, themeService, storageService);
|
||||
|
||||
this._list = null;
|
||||
this._elements = null;
|
||||
this._updateSoon = this._register(new RunOnceScheduler(() => this._updateExtensions(), 200));
|
||||
|
||||
this._register(this._extensionService.onDidChangeExtensionsStatus(() => this._updateSoon.schedule()));
|
||||
this._updateExtensions();
|
||||
}
|
||||
|
||||
protected async _updateExtensions(): Promise<void> {
|
||||
this._elements = await this._resolveExtensions();
|
||||
if (this._list) {
|
||||
this._list.splice(0, this._list.length, this._elements);
|
||||
}
|
||||
}
|
||||
|
||||
private async _resolveExtensions(): Promise<IRuntimeExtension[]> {
|
||||
// We only deal with extensions with source code!
|
||||
const extensionsDescriptions = (await this._extensionService.getExtensions()).filter((extension) => {
|
||||
return Boolean(extension.main) || Boolean(extension.browser);
|
||||
});
|
||||
let marketplaceMap: { [id: string]: IExtension; } = Object.create(null);
|
||||
const marketPlaceExtensions = await this._extensionsWorkbenchService.queryLocal();
|
||||
for (let extension of marketPlaceExtensions) {
|
||||
marketplaceMap[ExtensionIdentifier.toKey(extension.identifier.id)] = extension;
|
||||
}
|
||||
|
||||
let statusMap = this._extensionService.getExtensionsStatus();
|
||||
|
||||
// group profile segments by extension
|
||||
let segments: { [id: string]: number[]; } = Object.create(null);
|
||||
|
||||
const profileInfo = this._getProfileInfo();
|
||||
if (profileInfo) {
|
||||
let currentStartTime = profileInfo.startTime;
|
||||
for (let i = 0, len = profileInfo.deltas.length; i < len; i++) {
|
||||
const id = profileInfo.ids[i];
|
||||
const delta = profileInfo.deltas[i];
|
||||
|
||||
let extensionSegments = segments[ExtensionIdentifier.toKey(id)];
|
||||
if (!extensionSegments) {
|
||||
extensionSegments = [];
|
||||
segments[ExtensionIdentifier.toKey(id)] = extensionSegments;
|
||||
}
|
||||
|
||||
extensionSegments.push(currentStartTime);
|
||||
currentStartTime = currentStartTime + delta;
|
||||
extensionSegments.push(currentStartTime);
|
||||
}
|
||||
}
|
||||
|
||||
let result: IRuntimeExtension[] = [];
|
||||
for (let i = 0, len = extensionsDescriptions.length; i < len; i++) {
|
||||
const extensionDescription = extensionsDescriptions[i];
|
||||
|
||||
let profileInfo: IExtensionProfileInformation | null = null;
|
||||
if (profileInfo) {
|
||||
let extensionSegments = segments[ExtensionIdentifier.toKey(extensionDescription.identifier)] || [];
|
||||
let extensionTotalTime = 0;
|
||||
for (let j = 0, lenJ = extensionSegments.length / 2; j < lenJ; j++) {
|
||||
const startTime = extensionSegments[2 * j];
|
||||
const endTime = extensionSegments[2 * j + 1];
|
||||
extensionTotalTime += (endTime - startTime);
|
||||
}
|
||||
profileInfo = {
|
||||
segments: extensionSegments,
|
||||
totalTime: extensionTotalTime
|
||||
};
|
||||
}
|
||||
|
||||
result[i] = {
|
||||
originalIndex: i,
|
||||
description: extensionDescription,
|
||||
marketplaceInfo: marketplaceMap[ExtensionIdentifier.toKey(extensionDescription.identifier)],
|
||||
status: statusMap[extensionDescription.identifier.value],
|
||||
profileInfo: profileInfo || undefined,
|
||||
unresponsiveProfile: this._getUnresponsiveProfile(extensionDescription.identifier)
|
||||
};
|
||||
}
|
||||
|
||||
result = result.filter(element => element.status.activationTimes);
|
||||
|
||||
// bubble up extensions that have caused slowness
|
||||
|
||||
const isUnresponsive = (extension: IRuntimeExtension): boolean =>
|
||||
extension.unresponsiveProfile === profileInfo;
|
||||
|
||||
const profileTime = (extension: IRuntimeExtension): number =>
|
||||
extension.profileInfo?.totalTime ?? 0;
|
||||
|
||||
const activationTime = (extension: IRuntimeExtension): number =>
|
||||
(extension.status.activationTimes?.codeLoadingTime ?? 0) +
|
||||
(extension.status.activationTimes?.activateCallTime ?? 0);
|
||||
|
||||
result = result.sort((a, b) => {
|
||||
if (isUnresponsive(a) || isUnresponsive(b)) {
|
||||
return +isUnresponsive(b) - +isUnresponsive(a);
|
||||
} else if (profileTime(a) || profileTime(b)) {
|
||||
return profileTime(b) - profileTime(a);
|
||||
} else if (activationTime(a) || activationTime(b)) {
|
||||
return activationTime(b) - activationTime(a);
|
||||
}
|
||||
return a.originalIndex - b.originalIndex;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected createEditor(parent: HTMLElement): void {
|
||||
parent.classList.add('runtime-extensions-editor');
|
||||
|
||||
const TEMPLATE_ID = 'runtimeExtensionElementTemplate';
|
||||
|
||||
const delegate = new class implements IListVirtualDelegate<IRuntimeExtension>{
|
||||
getHeight(element: IRuntimeExtension): number {
|
||||
return 62;
|
||||
}
|
||||
getTemplateId(element: IRuntimeExtension): string {
|
||||
return TEMPLATE_ID;
|
||||
}
|
||||
};
|
||||
|
||||
interface IRuntimeExtensionTemplateData {
|
||||
root: HTMLElement;
|
||||
element: HTMLElement;
|
||||
icon: HTMLImageElement;
|
||||
name: HTMLElement;
|
||||
version: HTMLElement;
|
||||
msgContainer: HTMLElement;
|
||||
actionbar: ActionBar;
|
||||
activationTime: HTMLElement;
|
||||
profileTime: HTMLElement;
|
||||
disposables: IDisposable[];
|
||||
elementDisposables: IDisposable[];
|
||||
}
|
||||
|
||||
const renderer: IListRenderer<IRuntimeExtension, IRuntimeExtensionTemplateData> = {
|
||||
templateId: TEMPLATE_ID,
|
||||
renderTemplate: (root: HTMLElement): IRuntimeExtensionTemplateData => {
|
||||
const element = append(root, $('.extension'));
|
||||
const iconContainer = append(element, $('.icon-container'));
|
||||
const icon = append(iconContainer, $<HTMLImageElement>('img.icon'));
|
||||
|
||||
const desc = append(element, $('div.desc'));
|
||||
const headerContainer = append(desc, $('.header-container'));
|
||||
const header = append(headerContainer, $('.header'));
|
||||
const name = append(header, $('div.name'));
|
||||
const version = append(header, $('span.version'));
|
||||
|
||||
const msgContainer = append(desc, $('div.msg'));
|
||||
|
||||
const actionbar = new ActionBar(desc, { animated: false });
|
||||
actionbar.onDidRun(({ error }) => error && this._notificationService.error(error));
|
||||
|
||||
|
||||
const timeContainer = append(element, $('.time'));
|
||||
const activationTime = append(timeContainer, $('div.activation-time'));
|
||||
const profileTime = append(timeContainer, $('div.profile-time'));
|
||||
|
||||
const disposables = [actionbar];
|
||||
|
||||
return {
|
||||
root,
|
||||
element,
|
||||
icon,
|
||||
name,
|
||||
version,
|
||||
actionbar,
|
||||
activationTime,
|
||||
profileTime,
|
||||
msgContainer,
|
||||
disposables,
|
||||
elementDisposables: [],
|
||||
};
|
||||
},
|
||||
|
||||
renderElement: (element: IRuntimeExtension, index: number, data: IRuntimeExtensionTemplateData): void => {
|
||||
|
||||
data.elementDisposables = dispose(data.elementDisposables);
|
||||
|
||||
data.root.classList.toggle('odd', index % 2 === 1);
|
||||
|
||||
const onError = Event.once(domEvent(data.icon, 'error'));
|
||||
onError(() => data.icon.src = element.marketplaceInfo.iconUrlFallback, null, data.elementDisposables);
|
||||
data.icon.src = element.marketplaceInfo.iconUrl;
|
||||
|
||||
if (!data.icon.complete) {
|
||||
data.icon.style.visibility = 'hidden';
|
||||
data.icon.onload = () => data.icon.style.visibility = 'inherit';
|
||||
} else {
|
||||
data.icon.style.visibility = 'inherit';
|
||||
}
|
||||
data.name.textContent = element.marketplaceInfo.displayName;
|
||||
data.version.textContent = element.description.version;
|
||||
|
||||
const activationTimes = element.status.activationTimes!;
|
||||
let syncTime = activationTimes.codeLoadingTime + activationTimes.activateCallTime;
|
||||
data.activationTime.textContent = activationTimes.activationReason.startup ? `Startup Activation: ${syncTime}ms` : `Activation: ${syncTime}ms`;
|
||||
|
||||
data.actionbar.clear();
|
||||
const slowExtensionAction = this._createSlowExtensionAction(element);
|
||||
if (slowExtensionAction) {
|
||||
data.actionbar.push(slowExtensionAction, { icon: true, label: true });
|
||||
}
|
||||
if (isNonEmptyArray(element.status.runtimeErrors)) {
|
||||
const reportExtensionIssueAction = this._createReportExtensionIssueAction(element);
|
||||
if (reportExtensionIssueAction) {
|
||||
data.actionbar.push(reportExtensionIssueAction, { icon: true, label: true });
|
||||
}
|
||||
}
|
||||
|
||||
let title: string;
|
||||
const activationId = activationTimes.activationReason.extensionId.value;
|
||||
const activationEvent = activationTimes.activationReason.activationEvent;
|
||||
if (activationEvent === '*') {
|
||||
title = nls.localize('starActivation', "Activated by {0} on start-up", activationId);
|
||||
} else if (/^workspaceContains:/.test(activationEvent)) {
|
||||
let fileNameOrGlob = activationEvent.substr('workspaceContains:'.length);
|
||||
if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) {
|
||||
title = nls.localize({
|
||||
key: 'workspaceContainsGlobActivation',
|
||||
comment: [
|
||||
'{0} will be a glob pattern'
|
||||
]
|
||||
}, "Activated by {1} because a file matching {1} exists in your workspace", fileNameOrGlob, activationId);
|
||||
} else {
|
||||
title = nls.localize({
|
||||
key: 'workspaceContainsFileActivation',
|
||||
comment: [
|
||||
'{0} will be a file name'
|
||||
]
|
||||
}, "Activated by {1} because file {0} exists in your workspace", fileNameOrGlob, activationId);
|
||||
}
|
||||
} else if (/^workspaceContainsTimeout:/.test(activationEvent)) {
|
||||
const glob = activationEvent.substr('workspaceContainsTimeout:'.length);
|
||||
title = nls.localize({
|
||||
key: 'workspaceContainsTimeout',
|
||||
comment: [
|
||||
'{0} will be a glob pattern'
|
||||
]
|
||||
}, "Activated by {1} because searching for {0} took too long", glob, activationId);
|
||||
} else if (activationEvent === 'onStartupFinished') {
|
||||
title = nls.localize({
|
||||
key: 'startupFinishedActivation',
|
||||
comment: [
|
||||
'This refers to an extension. {0} will be an activation event.'
|
||||
]
|
||||
}, "Activated by {0} after start-up finished", activationId);
|
||||
} else if (/^onLanguage:/.test(activationEvent)) {
|
||||
let language = activationEvent.substr('onLanguage:'.length);
|
||||
title = nls.localize('languageActivation', "Activated by {1} because you opened a {0} file", language, activationId);
|
||||
} else {
|
||||
title = nls.localize({
|
||||
key: 'workspaceGenericActivation',
|
||||
comment: [
|
||||
'The {0} placeholder will be an activation event, like e.g. \'language:typescript\', \'debug\', etc.'
|
||||
]
|
||||
}, "Activated by {1} on {0}", activationEvent, activationId);
|
||||
}
|
||||
data.activationTime.title = title;
|
||||
|
||||
clearNode(data.msgContainer);
|
||||
|
||||
if (this._getUnresponsiveProfile(element.description.identifier)) {
|
||||
const el = $('span', undefined, ...renderCodicons(` $(alert) Unresponsive`));
|
||||
el.title = nls.localize('unresponsive.title', "Extension has caused the extension host to freeze.");
|
||||
data.msgContainer.appendChild(el);
|
||||
}
|
||||
|
||||
if (isNonEmptyArray(element.status.runtimeErrors)) {
|
||||
const el = $('span', undefined, ...renderCodicons(`$(bug) ${nls.localize('errors', "{0} uncaught errors", element.status.runtimeErrors.length)}`));
|
||||
data.msgContainer.appendChild(el);
|
||||
}
|
||||
|
||||
if (element.status.messages && element.status.messages.length > 0) {
|
||||
const el = $('span', undefined, ...renderCodicons(`$(alert) ${element.status.messages[0].message}`));
|
||||
data.msgContainer.appendChild(el);
|
||||
}
|
||||
|
||||
if (element.description.extensionLocation.scheme !== Schemas.file) {
|
||||
const el = $('span', undefined, ...renderCodicons(`$(remote) ${element.description.extensionLocation.authority}`));
|
||||
data.msgContainer.appendChild(el);
|
||||
|
||||
const hostLabel = this._labelService.getHostLabel(Schemas.vscodeRemote, this._environmentService.remoteAuthority);
|
||||
if (hostLabel) {
|
||||
reset(el, ...renderCodicons(`$(remote) ${hostLabel}`));
|
||||
}
|
||||
}
|
||||
|
||||
if (element.profileInfo) {
|
||||
data.profileTime.textContent = `Profile: ${(element.profileInfo.totalTime / 1000).toFixed(2)}ms`;
|
||||
} else {
|
||||
data.profileTime.textContent = '';
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
disposeTemplate: (data: IRuntimeExtensionTemplateData): void => {
|
||||
data.disposables = dispose(data.disposables);
|
||||
}
|
||||
};
|
||||
|
||||
this._list = <WorkbenchList<IRuntimeExtension>>this._instantiationService.createInstance(WorkbenchList,
|
||||
'RuntimeExtensions',
|
||||
parent, delegate, [renderer], {
|
||||
multipleSelectionSupport: false,
|
||||
setRowLineHeight: false,
|
||||
horizontalScrolling: false,
|
||||
overrideStyles: {
|
||||
listBackground: editorBackground
|
||||
},
|
||||
accessibilityProvider: new class implements IListAccessibilityProvider<IRuntimeExtension> {
|
||||
getWidgetAriaLabel(): string {
|
||||
return nls.localize('runtimeExtensions', "Runtime Extensions");
|
||||
}
|
||||
getAriaLabel(element: IRuntimeExtension): string | null {
|
||||
return element.description.name;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._list.splice(0, this._list.length, this._elements || undefined);
|
||||
|
||||
this._list.onContextMenu((e) => {
|
||||
if (!e.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actions: IAction[] = [];
|
||||
|
||||
const reportExtensionIssueAction = this._createReportExtensionIssueAction(e.element);
|
||||
if (reportExtensionIssueAction) {
|
||||
actions.push(reportExtensionIssueAction);
|
||||
actions.push(new Separator());
|
||||
}
|
||||
|
||||
actions.push(new Action('runtimeExtensionsEditor.action.disableWorkspace', nls.localize('disable workspace', "Disable (Workspace)"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.DisabledWorkspace)));
|
||||
actions.push(new Action('runtimeExtensionsEditor.action.disable', nls.localize('disable', "Disable"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.DisabledGlobally)));
|
||||
actions.push(new Separator());
|
||||
|
||||
const profileAction = this._createProfileAction();
|
||||
if (profileAction) {
|
||||
actions.push(profileAction);
|
||||
}
|
||||
const saveExtensionHostProfileAction = this.saveExtensionHostProfileAction;
|
||||
if (saveExtensionHostProfileAction) {
|
||||
actions.push(saveExtensionHostProfileAction);
|
||||
}
|
||||
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => e.anchor,
|
||||
getActions: () => actions
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get saveExtensionHostProfileAction(): IAction | null {
|
||||
return this._createSaveExtensionHostProfileAction();
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
if (this._list) {
|
||||
this._list.layout(dimension.height);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract _getProfileInfo(): IExtensionHostProfile | null;
|
||||
protected abstract _getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile | undefined;
|
||||
protected abstract _createSlowExtensionAction(element: IRuntimeExtension): Action | null;
|
||||
protected abstract _createReportExtensionIssueAction(element: IRuntimeExtension): Action | null;
|
||||
protected abstract _createSaveExtensionHostProfileAction(): Action | null;
|
||||
protected abstract _createProfileAction(): Action | null;
|
||||
}
|
||||
|
||||
export class ShowRuntimeExtensionsAction extends Action {
|
||||
static readonly ID = 'workbench.action.showRuntimeExtensions';
|
||||
static readonly LABEL = nls.localize('showRuntimeExtensions', "Show Running Extensions");
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IEditorService private readonly _editorService: IEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public async run(e?: any): Promise<any> {
|
||||
await this._editorService.openEditor(RuntimeExtensionsInput.instance, { revealIfOpened: true, pinned: true });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IExtensionService, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { AbstractRuntimeExtensionsEditor, IRuntimeExtension } from 'vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor';
|
||||
|
||||
export class RuntimeExtensionsEditor extends AbstractRuntimeExtensionsEditor {
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ILabelService labelService: ILabelService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
) {
|
||||
super(telemetryService, themeService, contextKeyService, extensionsWorkbenchService, extensionService, notificationService, contextMenuService, instantiationService, storageService, labelService, environmentService);
|
||||
}
|
||||
|
||||
protected _getProfileInfo(): IExtensionHostProfile | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected _getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected _createSlowExtensionAction(element: IRuntimeExtension): Action | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected _createReportExtensionIssueAction(element: IRuntimeExtension): Action | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected _createSaveExtensionHostProfileAction(): Action | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected _createProfileAction(): Action | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IExtensionTipsService, IConfigBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionRecommendations, ExtensionRecommendation, PromptedExtensionRecommendations } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
|
||||
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
import { IWorkspaceContextService, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
@@ -27,11 +27,11 @@ export class ConfigBasedRecommendations extends ExtensionRecommendations {
|
||||
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return [...this.importantRecommendations, ...this.otherRecommendations]; }
|
||||
|
||||
constructor(
|
||||
promptedExtensionRecommendations: PromptedExtensionRecommendations,
|
||||
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||
) {
|
||||
super(promptedExtensionRecommendations);
|
||||
// {{SQL CARBON EDIT}}
|
||||
super(undefined);
|
||||
}
|
||||
|
||||
protected async doActivate(): Promise<void> {
|
||||
@@ -73,7 +73,6 @@ export class ConfigBasedRecommendations extends ExtensionRecommendations {
|
||||
private toExtensionRecommendation(tip: IConfigBasedExtensionTip): ExtensionRecommendation {
|
||||
return {
|
||||
extensionId: tip.extensionId,
|
||||
source: 'config',
|
||||
reason: {
|
||||
reasonId: ExtensionRecommendationReason.WorkspaceConfig,
|
||||
reasonText: localize('exeBasedRecommendation', "This extension is recommended because of the current workspace configuration")
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspaceTags';
|
||||
import { isNumber } from 'vs/base/common/types';
|
||||
import { ExtensionRecommendations, ExtensionRecommendation, PromptedExtensionRecommendations } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
|
||||
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
|
||||
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
type DynamicWorkspaceRecommendationsClassification = {
|
||||
@@ -30,7 +30,6 @@ export class DynamicWorkspaceRecommendations extends ExtensionRecommendations {
|
||||
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
|
||||
|
||||
constructor(
|
||||
promptedExtensionRecommendations: PromptedExtensionRecommendations,
|
||||
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
|
||||
@IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@@ -38,7 +37,7 @@ export class DynamicWorkspaceRecommendations extends ExtensionRecommendations {
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
) {
|
||||
super(promptedExtensionRecommendations);
|
||||
super();
|
||||
}
|
||||
|
||||
protected async doActivate(): Promise<void> {
|
||||
@@ -82,7 +81,7 @@ export class DynamicWorkspaceRecommendations extends ExtensionRecommendations {
|
||||
const workspaceTip = workspacesTips.filter(workspaceTip => isNonEmptyArray(workspaceTip.remoteSet) && workspaceTip.remoteSet.indexOf(hashedRemote) > -1)[0];
|
||||
if (workspaceTip) {
|
||||
this._recommendations = workspaceTip.recommendations.map(id => this.toExtensionRecommendation(id, folder));
|
||||
this.storageService.store(dynamicWorkspaceRecommendationsStorageKey, JSON.stringify(<IStoredDynamicWorkspaceRecommendations>{ recommendations: workspaceTip.recommendations, timestamp: Date.now() }), StorageScope.WORKSPACE);
|
||||
this.storageService.store(dynamicWorkspaceRecommendationsStorageKey, JSON.stringify(<IStoredDynamicWorkspaceRecommendations>{ recommendations: workspaceTip.recommendations, timestamp: Date.now() }), StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
this.telemetryService.publicLog2<{ count: number, cache: number }, DynamicWorkspaceRecommendationsClassification>('dynamicWorkspaceRecommendations', { count: this._recommendations.length, cache: 0 });
|
||||
return;
|
||||
}
|
||||
@@ -107,7 +106,6 @@ export class DynamicWorkspaceRecommendations extends ExtensionRecommendations {
|
||||
private toExtensionRecommendation(extensionId: string, folder: IWorkspaceFolder): ExtensionRecommendation {
|
||||
return {
|
||||
extensionId: extensionId.toLowerCase(),
|
||||
source: 'dynamic',
|
||||
reason: {
|
||||
reasonId: ExtensionRecommendationReason.DynamicWorkspace,
|
||||
reasonText: localize('dynamicWorkspaceRecommendation', "This extension may interest you because it's popular among users of the {0} repository.", folder.name)
|
||||
|
||||
@@ -3,20 +3,11 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IExtensionTipsService, IExecutableBasedExtensionTip, IExtensionManagementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ExtensionRecommendations, ExtensionRecommendation, PromptedExtensionRecommendations } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { IExtensionTipsService, IExecutableBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
|
||||
import { localize } from 'vs/nls';
|
||||
import { optional } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService';
|
||||
|
||||
type ExeExtensionRecommendationsClassification = {
|
||||
extensionId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
|
||||
exeName: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
|
||||
export class ExeBasedRecommendations extends ExtensionRecommendations {
|
||||
|
||||
@@ -28,23 +19,10 @@ export class ExeBasedRecommendations extends ExtensionRecommendations {
|
||||
|
||||
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return [...this.importantRecommendations, ...this.otherRecommendations]; }
|
||||
|
||||
private readonly tasExperimentService: ITASExperimentService | undefined;
|
||||
|
||||
constructor(
|
||||
promptedExtensionRecommendations: PromptedExtensionRecommendations,
|
||||
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
|
||||
@optional(ITASExperimentService) tasExperimentService: ITASExperimentService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
) {
|
||||
super(promptedExtensionRecommendations);
|
||||
this.tasExperimentService = tasExperimentService;
|
||||
|
||||
/*
|
||||
3s has come out to be the good number to fetch and prompt important exe based recommendations
|
||||
Also fetch important exe based recommendations for reporting telemetry
|
||||
*/
|
||||
timeout(3000).then(() => this.fetchAndPromptImportantExeBasedRecommendations());
|
||||
super();
|
||||
}
|
||||
|
||||
getRecommendations(exe: string): { important: ExtensionRecommendation[], others: ExtensionRecommendation[] } {
|
||||
@@ -79,79 +57,9 @@ export class ExeBasedRecommendations extends ExtensionRecommendations {
|
||||
return importantExeBasedRecommendations;
|
||||
}
|
||||
|
||||
private async fetchAndPromptImportantExeBasedRecommendations(): Promise<void> {
|
||||
const importantExeBasedRecommendations = await this.fetchImportantExeBasedRecommendations();
|
||||
|
||||
const local = await this.extensionManagementService.getInstalled();
|
||||
const { installed, uninstalled } = this.groupByInstalled([...importantExeBasedRecommendations.keys()], local);
|
||||
|
||||
/* Log installed and uninstalled exe based recommendations */
|
||||
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!) });
|
||||
}
|
||||
}
|
||||
for (const extensionId of uninstalled) {
|
||||
const tip = importantExeBasedRecommendations.get(extensionId);
|
||||
if (tip) {
|
||||
this.telemetryService.publicLog2<{ exeName: string, extensionId: string }, ExeExtensionRecommendationsClassification>('exeExtensionRecommendations:notInstalled', { extensionId, exeName: basename(tip.windowsPath!) });
|
||||
}
|
||||
}
|
||||
|
||||
this.promptImportantExeBasedRecommendations(uninstalled, importantExeBasedRecommendations);
|
||||
}
|
||||
|
||||
private async promptImportantExeBasedRecommendations(recommendations: string[], importantExeBasedRecommendations: Map<string, IExecutableBasedExtensionTip>): Promise<void> {
|
||||
if (this.promptedExtensionRecommendations.hasToIgnoreRecommendationNotifications()) {
|
||||
return;
|
||||
}
|
||||
recommendations = this.promptedExtensionRecommendations.filterIgnoredOrNotAllowed(recommendations);
|
||||
if (recommendations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const recommendationsByExe = new Map<string, IExecutableBasedExtensionTip[]>();
|
||||
for (const extensionId of recommendations) {
|
||||
const tip = importantExeBasedRecommendations.get(extensionId);
|
||||
if (tip) {
|
||||
let tips = recommendationsByExe.get(tip.exeFriendlyName);
|
||||
if (!tips) {
|
||||
tips = [];
|
||||
recommendationsByExe.set(tip.exeFriendlyName, tips);
|
||||
}
|
||||
tips.push(tip);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [, tips] of recommendationsByExe) {
|
||||
const extensionIds = tips.map(({ extensionId }) => extensionId.toLowerCase());
|
||||
if (this.tasExperimentService && extensionIds.indexOf('ms-vscode-remote.remote-wsl') !== -1) {
|
||||
await this.tasExperimentService.getTreatment<boolean>('wslpopupaa');
|
||||
}
|
||||
|
||||
const message = localize('exeRecommended', "You have {0} installed on your system. Do you want to install the recommended extensions for it?", tips[0].exeFriendlyName);
|
||||
this.promptedExtensionRecommendations.promptImportantExtensionsInstallNotification(extensionIds, message, `@exe:"${tips[0].exeName}"`);
|
||||
}
|
||||
}
|
||||
|
||||
private groupByInstalled(recommendationsToSuggest: string[], local: ILocalExtension[]): { installed: string[], uninstalled: string[] } {
|
||||
const installed: string[] = [], uninstalled: string[] = [];
|
||||
const installedExtensionsIds = local.reduce((result, i) => { result.add(i.identifier.id.toLowerCase()); return result; }, new Set<string>());
|
||||
recommendationsToSuggest.forEach(id => {
|
||||
if (installedExtensionsIds.has(id.toLowerCase())) {
|
||||
installed.push(id);
|
||||
} else {
|
||||
uninstalled.push(id);
|
||||
}
|
||||
});
|
||||
return { installed, uninstalled };
|
||||
}
|
||||
|
||||
private toExtensionRecommendation(tip: IExecutableBasedExtensionTip): ExtensionRecommendation {
|
||||
return {
|
||||
extensionId: tip.extensionId.toLowerCase(),
|
||||
source: 'executable',
|
||||
reason: {
|
||||
reasonId: ExtensionRecommendationReason.Executable,
|
||||
reasonText: localize('exeBasedRecommendation', "This extension is recommended because you have {0} installed.", tip.exeFriendlyName || basename(tip.windowsPath!))
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { ExtensionRecommendations, ExtensionRecommendation, PromptedExtensionRecommendations } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
|
||||
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
|
||||
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/workbench/contrib/experiments/common/experimentService';
|
||||
|
||||
export class ExperimentalRecommendations extends ExtensionRecommendations {
|
||||
@@ -14,10 +14,9 @@ export class ExperimentalRecommendations extends ExtensionRecommendations {
|
||||
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
|
||||
|
||||
constructor(
|
||||
promptedExtensionRecommendations: PromptedExtensionRecommendations,
|
||||
@IExperimentService private readonly experimentService: IExperimentService,
|
||||
) {
|
||||
super(promptedExtensionRecommendations);
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,7 +28,6 @@ export class ExperimentalRecommendations extends ExtensionRecommendations {
|
||||
if (state === ExperimentState.Run && isNonEmptyArray(action?.properties?.recommendations) && action?.properties?.recommendationReason) {
|
||||
action.properties.recommendations.forEach((extensionId: string) => this._recommendations.push({
|
||||
extensionId: extensionId.toLowerCase(),
|
||||
source: 'experimental',
|
||||
reason: {
|
||||
reasonId: ExtensionRecommendationReason.Experimental,
|
||||
reasonText: action.properties.recommendationReason
|
||||
|
||||
@@ -14,20 +14,25 @@ import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { dispose, toDisposable, Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { append, $, finalHandler, join, hide, show, addDisposableListener, EventType } from 'vs/base/browser/dom';
|
||||
import { append, $, finalHandler, join, hide, show, addDisposableListener, EventType, setParentFlowTo } from 'vs/base/browser/dom';
|
||||
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionManifest, IKeyBinding, IView, IViewContainer, ExtensionType } from 'vs/platform/extensions/common/extensions';
|
||||
import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
import { IExtensionManifest, IKeyBinding, IView, IViewContainer } from 'vs/platform/extensions/common/extensions';
|
||||
import { ResolvedKeybinding, KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
|
||||
import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, IExtension, ExtensionContainers } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { /*RatingsWidget, InstallCountWidget, */RemoteBadgeWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets';
|
||||
import { EditorOptions, IEditorOpenContext } from 'vs/workbench/common/editor';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { CombinedInstallAction, UpdateAction, ExtensionEditorDropDownAction, ReloadAction, MaliciousStatusLabelAction, IgnoreExtensionRecommendationAction, UndoIgnoreExtensionRecommendationAction, EnableDropDownAction, DisableDropDownAction, StatusLabelAction, SetFileIconThemeAction, SetColorThemeAction, RemoteInstallAction, ExtensionToolTipAction, SystemDisabledWarningAction, LocalInstallAction, SyncIgnoredIconAction, SetProductIconThemeAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import {
|
||||
UpdateAction, ReloadAction, MaliciousStatusLabelAction, EnableDropDownAction, DisableDropDownAction, StatusLabelAction, SetFileIconThemeAction, SetColorThemeAction,
|
||||
RemoteInstallAction, ExtensionToolTipAction, SystemDisabledWarningAction, LocalInstallAction, ToggleSyncExtensionAction, SetProductIconThemeAction,
|
||||
ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, UninstallAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction,
|
||||
InstallAnotherVersionAction, ExtensionEditorManageExtensionAction
|
||||
} from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener';
|
||||
@@ -38,7 +43,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { ExtensionsTree, ExtensionData, ExtensionsGridView, getExtensions } from 'vs/workbench/contrib/extensions/browser/extensionsViewer';
|
||||
import { ShowCurrentReleaseNotesActionId } from 'vs/workbench/contrib/update/common/update';
|
||||
import { KeybindingParser } from 'vs/base/common/keybindingParser';
|
||||
@@ -60,6 +65,7 @@ import { TokenizationRegistry } from 'vs/editor/common/modes';
|
||||
import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization';
|
||||
import { editorBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { registerAction2, Action2 } from 'vs/platform/actions/common/actions';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
|
||||
function removeEmbeddedSVGs(documentContent: string): string {
|
||||
const newDocument = new DOMParser().parseFromString(documentContent, 'text/html');
|
||||
@@ -190,11 +196,13 @@ export class ExtensionEditor extends EditorPane {
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IOpenerService private readonly openerService: IOpenerService,
|
||||
@IExtensionRecommendationsService private readonly extensionRecommendationsService: IExtensionRecommendationsService,
|
||||
@IExtensionIgnoredRecommendationsService private readonly extensionIgnoredRecommendationsService: IExtensionIgnoredRecommendationsService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService,
|
||||
@IWebviewService private readonly webviewService: IWebviewService,
|
||||
@IModeService private readonly modeService: IModeService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
||||
) {
|
||||
super(ExtensionEditor.ID, telemetryService, themeService, storageService);
|
||||
this.extensionReadme = null;
|
||||
@@ -250,9 +258,12 @@ export class ExtensionEditor extends EditorPane {
|
||||
const extensionActionBar = this._register(new ActionBar(extensionActions, {
|
||||
animated: false,
|
||||
actionViewItemProvider: (action: IAction) => {
|
||||
if (action instanceof ExtensionEditorDropDownAction) {
|
||||
if (action instanceof ExtensionDropDownAction) {
|
||||
return action.createActionViewItem();
|
||||
}
|
||||
if (action instanceof ActionWithDropDownAction) {
|
||||
return new ExtensionActionWithDropdownActionViewItem(action, { icon: true, label: true, menuActionsOrProvider: { getActions: () => action.menuActions }, menuActionClassNames: (action.class || '').split(' ') }, this.contextMenuService);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}));
|
||||
@@ -275,6 +286,7 @@ export class ExtensionEditor extends EditorPane {
|
||||
const navbar = new NavBar(body);
|
||||
|
||||
const content = append(body, $('.content'));
|
||||
content.id = generateUuid(); // An id is needed for the webview parent flow to
|
||||
|
||||
this.template = {
|
||||
builtin,
|
||||
@@ -322,8 +334,6 @@ export class ExtensionEditor extends EditorPane {
|
||||
}
|
||||
|
||||
private async updateTemplate(input: ExtensionsInput, template: IExtensionEditorTemplate, preserveFocus: boolean): Promise<void> {
|
||||
const runningExtensions = await this.extensionService.getExtensions();
|
||||
|
||||
this.activeElement = null;
|
||||
this.editorLoadComplete = false;
|
||||
const extension = input.extension;
|
||||
@@ -342,7 +352,7 @@ export class ExtensionEditor extends EditorPane {
|
||||
template.name.textContent = extension.displayName;
|
||||
template.identifier.textContent = extension.identifier.id;
|
||||
template.preview.style.display = extension.preview ? 'inherit' : 'none';
|
||||
template.builtin.style.display = extension.type === ExtensionType.System ? 'inherit' : 'none';
|
||||
template.builtin.style.display = extension.isBuiltin ? 'inherit' : 'none';
|
||||
|
||||
template.publisher.textContent = extension.publisherDisplayName;
|
||||
template.version.textContent = `v${extension.version}`;
|
||||
@@ -420,11 +430,10 @@ export class ExtensionEditor extends EditorPane {
|
||||
// this.instantiationService.createInstance(RatingsWidget, template.rating, false) {{SQL CARBON EDIT}} Remove the widgets
|
||||
];
|
||||
const reloadAction = this.instantiationService.createInstance(ReloadAction);
|
||||
const combinedInstallAction = this.instantiationService.createInstance(CombinedInstallAction);
|
||||
const combinedInstallAction = this.instantiationService.createInstance(InstallDropdownAction);
|
||||
const systemDisabledWarningAction = this.instantiationService.createInstance(SystemDisabledWarningAction);
|
||||
const actions = [
|
||||
reloadAction,
|
||||
this.instantiationService.createInstance(SyncIgnoredIconAction),
|
||||
this.instantiationService.createInstance(StatusLabelAction),
|
||||
this.instantiationService.createInstance(UpdateAction),
|
||||
this.instantiationService.createInstance(SetColorThemeAction, await this.workbenchThemeService.getColorThemes()),
|
||||
@@ -432,11 +441,18 @@ export class ExtensionEditor extends EditorPane {
|
||||
this.instantiationService.createInstance(SetProductIconThemeAction, await this.workbenchThemeService.getProductIconThemes()),
|
||||
|
||||
this.instantiationService.createInstance(EnableDropDownAction),
|
||||
this.instantiationService.createInstance(DisableDropDownAction, runningExtensions),
|
||||
this.instantiationService.createInstance(DisableDropDownAction),
|
||||
this.instantiationService.createInstance(RemoteInstallAction, false),
|
||||
this.instantiationService.createInstance(LocalInstallAction),
|
||||
combinedInstallAction,
|
||||
this.instantiationService.createInstance(InstallingLabelAction),
|
||||
this.instantiationService.createInstance(ActionWithDropDownAction, 'extensions.uninstall', UninstallAction.UninstallLabel, [
|
||||
this.instantiationService.createInstance(UninstallAction),
|
||||
this.instantiationService.createInstance(InstallAnotherVersionAction),
|
||||
]),
|
||||
this.instantiationService.createInstance(ToggleSyncExtensionAction),
|
||||
systemDisabledWarningAction,
|
||||
this.instantiationService.createInstance(ExtensionEditorManageExtensionAction),
|
||||
this.instantiationService.createInstance(ExtensionToolTipAction, systemDisabledWarningAction, reloadAction),
|
||||
this.instantiationService.createInstance(MaliciousStatusLabelAction, true),
|
||||
];
|
||||
@@ -449,7 +465,7 @@ export class ExtensionEditor extends EditorPane {
|
||||
this.transientDisposables.add(disposable);
|
||||
}
|
||||
|
||||
this.setSubText(extension, reloadAction, template);
|
||||
this.setSubText(extension, template);
|
||||
template.content.innerText = ''; // Clear content before setting navbar actions.
|
||||
|
||||
template.navbar.clear();
|
||||
@@ -480,65 +496,24 @@ export class ExtensionEditor extends EditorPane {
|
||||
this.editorLoadComplete = true;
|
||||
}
|
||||
|
||||
private setSubText(extension: IExtension, reloadAction: ReloadAction, template: IExtensionEditorTemplate): void {
|
||||
private setSubText(extension: IExtension, template: IExtensionEditorTemplate): void {
|
||||
hide(template.subtextContainer);
|
||||
|
||||
const ignoreAction = this.instantiationService.createInstance(IgnoreExtensionRecommendationAction, extension);
|
||||
const undoIgnoreAction = this.instantiationService.createInstance(UndoIgnoreExtensionRecommendationAction, extension);
|
||||
ignoreAction.enabled = false;
|
||||
undoIgnoreAction.enabled = false;
|
||||
|
||||
template.ignoreActionbar.clear();
|
||||
template.ignoreActionbar.push([ignoreAction, undoIgnoreAction], { icon: true, label: true });
|
||||
this.transientDisposables.add(ignoreAction);
|
||||
this.transientDisposables.add(undoIgnoreAction);
|
||||
|
||||
const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason();
|
||||
if (extRecommendations[extension.identifier.id.toLowerCase()]) {
|
||||
ignoreAction.enabled = true;
|
||||
template.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText;
|
||||
show(template.subtextContainer);
|
||||
} else if (this.extensionRecommendationsService.getIgnoredRecommendations().indexOf(extension.identifier.id.toLowerCase()) !== -1) {
|
||||
undoIgnoreAction.enabled = true;
|
||||
template.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension.");
|
||||
show(template.subtextContainer);
|
||||
}
|
||||
else {
|
||||
template.subtext.textContent = '';
|
||||
}
|
||||
|
||||
this.extensionRecommendationsService.onRecommendationChange(change => {
|
||||
if (change.extensionId.toLowerCase() === extension.identifier.id.toLowerCase()) {
|
||||
if (change.isRecommended) {
|
||||
undoIgnoreAction.enabled = false;
|
||||
const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason();
|
||||
if (extRecommendations[extension.identifier.id.toLowerCase()]) {
|
||||
ignoreAction.enabled = true;
|
||||
template.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText;
|
||||
}
|
||||
} else {
|
||||
undoIgnoreAction.enabled = true;
|
||||
ignoreAction.enabled = false;
|
||||
template.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.transientDisposables.add(reloadAction.onDidChange(e => {
|
||||
if (e.tooltip) {
|
||||
template.subtext.textContent = reloadAction.tooltip;
|
||||
const updateRecommendationFn = () => {
|
||||
const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason();
|
||||
if (extRecommendations[extension.identifier.id.toLowerCase()]) {
|
||||
template.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText;
|
||||
show(template.subtextContainer);
|
||||
ignoreAction.enabled = false;
|
||||
undoIgnoreAction.enabled = false;
|
||||
}
|
||||
if (e.enabled === true) {
|
||||
} else if (this.extensionIgnoredRecommendationsService.globalIgnoredRecommendations.indexOf(extension.identifier.id.toLowerCase()) !== -1) {
|
||||
template.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension.");
|
||||
show(template.subtextContainer);
|
||||
}
|
||||
if (e.enabled === false) {
|
||||
} else {
|
||||
template.subtext.textContent = '';
|
||||
hide(template.subtextContainer);
|
||||
}
|
||||
this.layout();
|
||||
}));
|
||||
};
|
||||
updateRecommendationFn();
|
||||
this.transientDisposables.add(this.extensionRecommendationsService.onDidChangeRecommendations(() => updateRecommendationFn()));
|
||||
}
|
||||
|
||||
clearInput(): void {
|
||||
@@ -584,8 +559,13 @@ export class ExtensionEditor extends EditorPane {
|
||||
template.content.innerText = '';
|
||||
this.activeElement = null;
|
||||
if (id) {
|
||||
this.open(id, extension, template)
|
||||
const cts = new CancellationTokenSource();
|
||||
this.contentDisposables.add(toDisposable(() => cts.dispose(true)));
|
||||
this.open(id, extension, template, cts.token)
|
||||
.then(activeElement => {
|
||||
if (cts.token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
this.activeElement = activeElement;
|
||||
if (focus) {
|
||||
this.focus();
|
||||
@@ -594,26 +574,31 @@ export class ExtensionEditor extends EditorPane {
|
||||
}
|
||||
}
|
||||
|
||||
private open(id: string, extension: IExtension, template: IExtensionEditorTemplate): Promise<IActiveElement | null> {
|
||||
private open(id: string, extension: IExtension, template: IExtensionEditorTemplate, token: CancellationToken): Promise<IActiveElement | null> {
|
||||
switch (id) {
|
||||
case NavbarSection.Readme: return this.openReadme(template);
|
||||
case NavbarSection.Contributions: return this.openContributions(template);
|
||||
case NavbarSection.Changelog: return this.openChangelog(template);
|
||||
case NavbarSection.Dependencies: return this.openDependencies(extension, template);
|
||||
case NavbarSection.Readme: return this.openReadme(template, token);
|
||||
case NavbarSection.Contributions: return this.openContributions(template, token);
|
||||
case NavbarSection.Changelog: return this.openChangelog(template, token);
|
||||
case NavbarSection.Dependencies: return this.openDependencies(extension, template, token);
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
private async openMarkdown(cacheResult: CacheResult<string>, noContentCopy: string, template: IExtensionEditorTemplate): Promise<IActiveElement> {
|
||||
private async openMarkdown(cacheResult: CacheResult<string>, noContentCopy: string, template: IExtensionEditorTemplate, token: CancellationToken): Promise<IActiveElement | null> {
|
||||
try {
|
||||
const body = await this.renderMarkdown(cacheResult, template);
|
||||
if (token.isCancellationRequested) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const webview = this.contentDisposables.add(this.webviewService.createWebviewOverlay('extensionEditor', {
|
||||
enableFindWidget: true,
|
||||
}, {}, undefined));
|
||||
|
||||
webview.claim(this);
|
||||
webview.claim(this, this.scopedContextKeyService);
|
||||
setParentFlowTo(webview.container, template.content);
|
||||
webview.layoutWebviewOverElement(template.content);
|
||||
|
||||
webview.html = body;
|
||||
|
||||
this.contentDisposables.add(webview.onDidFocus(() => this.fireOnDidFocus()));
|
||||
@@ -854,15 +839,19 @@ export class ExtensionEditor extends EditorPane {
|
||||
</html>`;
|
||||
}
|
||||
|
||||
private async openReadme(template: IExtensionEditorTemplate): Promise<IActiveElement> {
|
||||
private async openReadme(template: IExtensionEditorTemplate, token: CancellationToken): Promise<IActiveElement | null> {
|
||||
const manifest = await this.extensionManifest!.get().promise;
|
||||
if (manifest && manifest.extensionPack && manifest.extensionPack.length) {
|
||||
return this.openExtensionPackReadme(manifest, template);
|
||||
return this.openExtensionPackReadme(manifest, template, token);
|
||||
}
|
||||
return this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available."), template);
|
||||
return this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available."), template, token);
|
||||
}
|
||||
|
||||
private async openExtensionPackReadme(manifest: IExtensionManifest, template: IExtensionEditorTemplate): Promise<IActiveElement> {
|
||||
private async openExtensionPackReadme(manifest: IExtensionManifest, template: IExtensionEditorTemplate, token: CancellationToken): Promise<IActiveElement | null> {
|
||||
if (token.isCancellationRequested) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const extensionPackReadme = append(template.content, $('div', { class: 'extension-pack-readme' }));
|
||||
extensionPackReadme.style.margin = '0 auto';
|
||||
extensionPackReadme.style.maxWidth = '882px';
|
||||
@@ -886,21 +875,25 @@ export class ExtensionEditor extends EditorPane {
|
||||
const readmeContent = append(extensionPackReadme, $('div.readme-content'));
|
||||
|
||||
await Promise.all([
|
||||
this.renderExtensionPack(manifest, extensionPackContent),
|
||||
this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available."), { ...template, ...{ content: readmeContent } }),
|
||||
this.renderExtensionPack(manifest, extensionPackContent, token),
|
||||
this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available."), { ...template, ...{ content: readmeContent } }, token),
|
||||
]);
|
||||
|
||||
return { focus: () => extensionPackContent.focus() };
|
||||
}
|
||||
|
||||
private openChangelog(template: IExtensionEditorTemplate): Promise<IActiveElement> {
|
||||
return this.openMarkdown(this.extensionChangelog!.get(), localize('noChangelog', "No Changelog available."), template);
|
||||
private openChangelog(template: IExtensionEditorTemplate, token: CancellationToken): Promise<IActiveElement | null> {
|
||||
return this.openMarkdown(this.extensionChangelog!.get(), localize('noChangelog', "No Changelog available."), template, token);
|
||||
}
|
||||
|
||||
private openContributions(template: IExtensionEditorTemplate): Promise<IActiveElement> {
|
||||
private openContributions(template: IExtensionEditorTemplate, token: CancellationToken): Promise<IActiveElement | null> {
|
||||
const content = $('div', { class: 'subcontent', tabindex: '0' });
|
||||
return this.loadContents(() => this.extensionManifest!.get(), template)
|
||||
.then(manifest => {
|
||||
if (token.isCancellationRequested) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!manifest) {
|
||||
return content;
|
||||
}
|
||||
@@ -927,6 +920,7 @@ export class ExtensionEditor extends EditorPane {
|
||||
renderDashboardContributions(content, manifest, layout), // {{SQL CARBON EDIT}}
|
||||
this.renderCustomEditors(content, manifest, layout),
|
||||
this.renderAuthentication(content, manifest, layout),
|
||||
this.renderActivationEvents(content, manifest, layout),
|
||||
];
|
||||
|
||||
scrollableContent.scanDomNode();
|
||||
@@ -941,13 +935,21 @@ export class ExtensionEditor extends EditorPane {
|
||||
}
|
||||
return content;
|
||||
}, () => {
|
||||
if (token.isCancellationRequested) {
|
||||
return null;
|
||||
}
|
||||
|
||||
append(content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions");
|
||||
append(template.content, content);
|
||||
return content;
|
||||
});
|
||||
}
|
||||
|
||||
private openDependencies(extension: IExtension, template: IExtensionEditorTemplate): Promise<IActiveElement> {
|
||||
private openDependencies(extension: IExtension, template: IExtensionEditorTemplate, token: CancellationToken): Promise<IActiveElement | null> {
|
||||
if (token.isCancellationRequested) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
if (arrays.isFalsyOrEmpty(extension.dependencies)) {
|
||||
append(template.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies");
|
||||
return Promise.resolve(template.content);
|
||||
@@ -976,7 +978,11 @@ export class ExtensionEditor extends EditorPane {
|
||||
return Promise.resolve({ focus() { dependenciesTree.domFocus(); } });
|
||||
}
|
||||
|
||||
private async renderExtensionPack(manifest: IExtensionManifest, parent: HTMLElement): Promise<void> {
|
||||
private async renderExtensionPack(manifest: IExtensionManifest, parent: HTMLElement, token: CancellationToken): Promise<void> {
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
const content = $('div', { class: 'subcontent' });
|
||||
const scrollableContent = new DomScrollableElement(content, { useShadows: false });
|
||||
append(parent, scrollableContent.getDomNode());
|
||||
@@ -1441,6 +1447,21 @@ export class ExtensionEditor extends EditorPane {
|
||||
return true;
|
||||
}
|
||||
|
||||
private renderActivationEvents(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
|
||||
const activationEvents = manifest.activationEvents || [];
|
||||
if (!activationEvents.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const details = $('details', { open: true, ontoggle: onDetailsToggle },
|
||||
$('summary', { tabindex: '0' }, localize('activation events', "Activation Events ({0})", activationEvents.length)),
|
||||
$('ul', undefined, ...activationEvents.map(activationEvent => $('li', undefined, $('code', undefined, activationEvent))))
|
||||
);
|
||||
|
||||
append(container, details);
|
||||
return true;
|
||||
}
|
||||
|
||||
private resolveKeybinding(rawKeyBinding: IKeyBinding): ResolvedKeybinding | null {
|
||||
let key: string | undefined;
|
||||
|
||||
|
||||
@@ -0,0 +1,420 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { CancelablePromise, createCancelablePromise, raceCancellablePromises, raceCancellation, timeout } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IExtensionRecommendationNotificationService, RecommendationsNotificationResult, RecommendationSource } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
|
||||
import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { INotificationHandle, INotificationService, IPromptChoice, IPromptChoiceWithMenu, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IUserDataAutoSyncEnablementService, IUserDataSyncResourceEnablementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { SearchExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService';
|
||||
import { EnablementState, IWorkbenchExtensioManagementService, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionIgnoredRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
|
||||
type ExtensionRecommendationsNotificationClassification = {
|
||||
userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
extensionId?: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
|
||||
type ExtensionWorkspaceRecommendationsNotificationClassification = {
|
||||
userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
|
||||
const ignoreImportantExtensionRecommendationStorageKey = 'extensionsAssistant/importantRecommendationsIgnore';
|
||||
const donotShowWorkspaceRecommendationsStorageKey = 'extensionsAssistant/workspaceRecommendationsIgnore';
|
||||
const choiceNever = localize('neverShowAgain', "Don't Show Again");
|
||||
|
||||
type RecommendationsNotificationActions = {
|
||||
onDidInstallRecommendedExtensions(extensions: IExtension[]): void;
|
||||
onDidShowRecommendedExtensions(extensions: IExtension[]): void;
|
||||
onDidCancelRecommendedExtensions(extensions: IExtension[]): void;
|
||||
onDidNeverShowRecommendedExtensionsAgain(extensions: IExtension[]): void;
|
||||
};
|
||||
|
||||
class RecommendationsNotification {
|
||||
|
||||
private _onDidClose = new Emitter<void>();
|
||||
readonly onDidClose = this._onDidClose.event;
|
||||
|
||||
private _onDidChangeVisibility = new Emitter<boolean>();
|
||||
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
|
||||
|
||||
private notificationHandle: INotificationHandle | undefined;
|
||||
private cancelled: boolean = false;
|
||||
|
||||
constructor(
|
||||
private readonly severity: Severity,
|
||||
private readonly message: string,
|
||||
private readonly choices: IPromptChoice[],
|
||||
private readonly notificationService: INotificationService
|
||||
) { }
|
||||
|
||||
show(): void {
|
||||
if (!this.notificationHandle) {
|
||||
this.updateNotificationHandle(this.notificationService.prompt(this.severity, this.message, this.choices, { sticky: true, onCancel: () => this.cancelled = true }));
|
||||
}
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
if (this.notificationHandle) {
|
||||
this.onDidCloseDisposable.clear();
|
||||
this.notificationHandle.close();
|
||||
this.cancelled = false;
|
||||
this.updateNotificationHandle(this.notificationService.prompt(this.severity, this.message, this.choices, { silent: true, sticky: false, onCancel: () => this.cancelled = true }));
|
||||
}
|
||||
}
|
||||
|
||||
isCancelled(): boolean {
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
private onDidCloseDisposable = new MutableDisposable();
|
||||
private onDidChangeVisibilityDisposable = new MutableDisposable();
|
||||
private updateNotificationHandle(notificationHandle: INotificationHandle) {
|
||||
this.onDidCloseDisposable.clear();
|
||||
this.onDidChangeVisibilityDisposable.clear();
|
||||
this.notificationHandle = notificationHandle;
|
||||
|
||||
this.onDidCloseDisposable.value = this.notificationHandle.onDidClose(() => {
|
||||
this.onDidCloseDisposable.dispose();
|
||||
this.onDidChangeVisibilityDisposable.dispose();
|
||||
|
||||
this._onDidClose.fire();
|
||||
|
||||
this._onDidClose.dispose();
|
||||
this._onDidChangeVisibility.dispose();
|
||||
});
|
||||
this.onDidChangeVisibilityDisposable.value = this.notificationHandle.onDidChangeVisibility((e) => this._onDidChangeVisibility.fire(e));
|
||||
}
|
||||
}
|
||||
|
||||
type PendingRecommendationsNotification = { recommendationsNotification: RecommendationsNotification, source: RecommendationSource, token: CancellationToken };
|
||||
type VisibleRecommendationsNotification = { recommendationsNotification: RecommendationsNotification, source: RecommendationSource, from: number };
|
||||
|
||||
export class ExtensionRecommendationNotificationService implements IExtensionRecommendationNotificationService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly tasExperimentService: ITASExperimentService | undefined;
|
||||
|
||||
// Ignored Important Recommendations
|
||||
get ignoredRecommendations(): string[] {
|
||||
return distinct([...(<string[]>JSON.parse(this.storageService.get(ignoreImportantExtensionRecommendationStorageKey, StorageScope.GLOBAL, '[]')))].map(i => i.toLowerCase()));
|
||||
}
|
||||
|
||||
private recommendedExtensions: string[] = [];
|
||||
private recommendationSources: RecommendationSource[] = [];
|
||||
|
||||
private hideVisibleNotificationPromise: CancelablePromise<void> | undefined;
|
||||
private visibleNotification: VisibleRecommendationsNotification | undefined;
|
||||
private pendingNotificaitons: PendingRecommendationsNotification[] = [];
|
||||
|
||||
constructor(
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IWorkbenchExtensioManagementService private readonly extensionManagementService: IWorkbenchExtensioManagementService,
|
||||
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
|
||||
@IExtensionIgnoredRecommendationsService private readonly extensionIgnoredRecommendationsService: IExtensionIgnoredRecommendationsService,
|
||||
@IUserDataAutoSyncEnablementService private readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService,
|
||||
@IUserDataSyncResourceEnablementService private readonly userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService,
|
||||
@optional(ITASExperimentService) tasExperimentService: ITASExperimentService,
|
||||
) {
|
||||
this.tasExperimentService = tasExperimentService;
|
||||
}
|
||||
|
||||
hasToIgnoreRecommendationNotifications(): boolean {
|
||||
const config = this.configurationService.getValue<{ ignoreRecommendations: boolean, showRecommendationsOnlyOnDemand?: boolean }>('extensions');
|
||||
return config.ignoreRecommendations || !!config.showRecommendationsOnlyOnDemand;
|
||||
}
|
||||
|
||||
async promptImportantExtensionsInstallNotification(extensionIds: string[], message: string, searchValue: string, source: RecommendationSource): Promise<RecommendationsNotificationResult> {
|
||||
const ignoredRecommendations = [...this.extensionIgnoredRecommendationsService.ignoredRecommendations, ...this.ignoredRecommendations];
|
||||
extensionIds = extensionIds.filter(id => !ignoredRecommendations.includes(id));
|
||||
if (!extensionIds.length) {
|
||||
return RecommendationsNotificationResult.Ignored;
|
||||
}
|
||||
|
||||
return this.promptRecommendationsNotification(extensionIds, message, searchValue, source, {
|
||||
onDidInstallRecommendedExtensions: (extensions: IExtension[]) => extensions.forEach(extension => this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'install', extensionId: extension.identifier.id })),
|
||||
onDidShowRecommendedExtensions: (extensions: IExtension[]) => extensions.forEach(extension => this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'show', extensionId: extension.identifier.id })),
|
||||
onDidCancelRecommendedExtensions: (extensions: IExtension[]) => extensions.forEach(extension => this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'cancelled', extensionId: extension.identifier.id })),
|
||||
onDidNeverShowRecommendedExtensionsAgain: (extensions: IExtension[]) => {
|
||||
for (const extension of extensions) {
|
||||
this.addToImportantRecommendationsIgnore(extension.identifier.id);
|
||||
this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId: extension.identifier.id });
|
||||
}
|
||||
this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
localize('ignoreExtensionRecommendations', "Do you want to ignore all extension recommendations?"),
|
||||
[{
|
||||
label: localize('ignoreAll', "Yes, Ignore All"),
|
||||
run: () => this.setIgnoreRecommendationsConfig(true)
|
||||
}, {
|
||||
label: localize('no', "No"),
|
||||
run: () => this.setIgnoreRecommendationsConfig(false)
|
||||
}]
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async promptWorkspaceRecommendations(recommendations: string[]): Promise<void> {
|
||||
if (this.storageService.getBoolean(donotShowWorkspaceRecommendationsStorageKey, StorageScope.WORKSPACE, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let installed = await this.extensionManagementService.getInstalled();
|
||||
installed = installed.filter(l => this.extensionEnablementService.getEnablementState(l) !== EnablementState.DisabledByExtensionKind); // Filter extensions disabled by kind
|
||||
recommendations = recommendations.filter(extensionId => installed.every(local => !areSameExtensions({ id: extensionId }, local.identifier)));
|
||||
if (!recommendations.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await this.promptRecommendationsNotification(recommendations, localize('workspaceRecommended', "Do you want to install the recommended extensions for this repository?"), '@recommended ', RecommendationSource.WORKSPACE, {
|
||||
onDidInstallRecommendedExtensions: () => this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'install' }),
|
||||
onDidShowRecommendedExtensions: () => this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'show' }),
|
||||
onDidCancelRecommendedExtensions: () => this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'cancelled' }),
|
||||
onDidNeverShowRecommendedExtensionsAgain: () => this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'neverShowAgain' }),
|
||||
});
|
||||
|
||||
if (result === RecommendationsNotificationResult.Accepted) {
|
||||
this.storageService.store(donotShowWorkspaceRecommendationsStorageKey, true, StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async promptRecommendationsNotification(extensionIds: string[], message: string, searchValue: string, source: RecommendationSource, recommendationsNotificationActions: RecommendationsNotificationActions): Promise<RecommendationsNotificationResult> {
|
||||
|
||||
if (this.hasToIgnoreRecommendationNotifications()) {
|
||||
return RecommendationsNotificationResult.Ignored;
|
||||
}
|
||||
|
||||
// Ignore exe recommendation if the window
|
||||
// => has shown an exe based recommendation already
|
||||
// => or has shown any two recommendations already
|
||||
if (source === RecommendationSource.EXE && (this.recommendationSources.includes(RecommendationSource.EXE) || this.recommendationSources.length >= 2)) {
|
||||
return RecommendationsNotificationResult.TooMany;
|
||||
}
|
||||
|
||||
// Ignore exe recommendation if recommendations are already shown
|
||||
if (source === RecommendationSource.EXE && extensionIds.every(id => this.recommendedExtensions.includes(id))) {
|
||||
return RecommendationsNotificationResult.Ignored;
|
||||
}
|
||||
|
||||
const extensions = await this.getInstallableExtensions(extensionIds);
|
||||
if (!extensions.length) {
|
||||
return RecommendationsNotificationResult.Ignored;
|
||||
}
|
||||
|
||||
if (this.tasExperimentService && extensionIds.indexOf('ms-vscode-remote.remote-wsl') !== -1) {
|
||||
await this.tasExperimentService.getTreatment<boolean>('wslpopupaa');
|
||||
}
|
||||
|
||||
this.recommendedExtensions = distinct([...this.recommendedExtensions, ...extensionIds]);
|
||||
|
||||
return raceCancellablePromises([
|
||||
this.showRecommendationsNotification(extensions, message, searchValue, source, recommendationsNotificationActions),
|
||||
this.waitUntilRecommendationsAreInstalled(extensions)
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
private showRecommendationsNotification(extensions: IExtension[], message: string, searchValue: string, source: RecommendationSource,
|
||||
{ onDidInstallRecommendedExtensions, onDidShowRecommendedExtensions, onDidCancelRecommendedExtensions, onDidNeverShowRecommendedExtensionsAgain }: RecommendationsNotificationActions): CancelablePromise<RecommendationsNotificationResult> {
|
||||
return createCancelablePromise<RecommendationsNotificationResult>(async token => {
|
||||
let accepted = false;
|
||||
const choices: (IPromptChoice | IPromptChoiceWithMenu)[] = [];
|
||||
const installExtensions = async (isMachineScoped?: boolean) => {
|
||||
this.runAction(this.instantiationService.createInstance(SearchExtensionsAction, searchValue));
|
||||
onDidInstallRecommendedExtensions(extensions);
|
||||
await Promise.all([
|
||||
Promise.all(extensions.map(extension => this.extensionsWorkbenchService.open(extension, { pinned: true }))),
|
||||
this.extensionManagementService.installExtensions(extensions.map(e => e.gallery!), { isMachineScoped })
|
||||
]);
|
||||
};
|
||||
choices.push({
|
||||
label: localize('install', "Install"),
|
||||
run: () => installExtensions(),
|
||||
menu: this.userDataAutoSyncEnablementService.isEnabled() && this.userDataSyncResourceEnablementService.isResourceEnabled(SyncResource.Extensions) ? [{
|
||||
label: localize('install and do no sync', "Install (Do not sync)"),
|
||||
run: () => installExtensions(true)
|
||||
}] : undefined,
|
||||
});
|
||||
choices.push(...[{
|
||||
label: localize('show recommendations', "Show Recommendations"),
|
||||
run: async () => {
|
||||
onDidShowRecommendedExtensions(extensions);
|
||||
for (const extension of extensions) {
|
||||
this.extensionsWorkbenchService.open(extension, { pinned: true });
|
||||
}
|
||||
this.runAction(this.instantiationService.createInstance(SearchExtensionsAction, searchValue));
|
||||
}
|
||||
}, {
|
||||
label: choiceNever,
|
||||
isSecondary: true,
|
||||
run: () => {
|
||||
onDidNeverShowRecommendedExtensionsAgain(extensions);
|
||||
}
|
||||
}]);
|
||||
try {
|
||||
accepted = await this.doShowRecommendationsNotification(Severity.Info, message, choices, source, token);
|
||||
} catch (error) {
|
||||
if (!isPromiseCanceledError(error)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (accepted) {
|
||||
return RecommendationsNotificationResult.Accepted;
|
||||
} else {
|
||||
onDidCancelRecommendedExtensions(extensions);
|
||||
return RecommendationsNotificationResult.Cancelled;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private waitUntilRecommendationsAreInstalled(extensions: IExtension[]): CancelablePromise<RecommendationsNotificationResult.Accepted> {
|
||||
const installedExtensions: string[] = [];
|
||||
const disposables = new DisposableStore();
|
||||
return createCancelablePromise(async token => {
|
||||
disposables.add(token.onCancellationRequested(e => disposables.dispose()));
|
||||
return new Promise<RecommendationsNotificationResult.Accepted>((c, e) => {
|
||||
disposables.add(this.extensionManagementService.onInstallExtension(e => {
|
||||
installedExtensions.push(e.identifier.id.toLowerCase());
|
||||
if (extensions.every(e => installedExtensions.includes(e.identifier.id.toLowerCase()))) {
|
||||
c(RecommendationsNotificationResult.Accepted);
|
||||
}
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show recommendations in Queue
|
||||
* At any time only one recommendation is shown
|
||||
* If a new recommendation comes in
|
||||
* => If no recommendation is visible, show it immediately
|
||||
* => Otherwise, add to the pending queue
|
||||
* => If it is not exe based and has higher or same priority as current, hide the current notification after showing it for 3s.
|
||||
* => Otherwise wait until the current notification is hidden.
|
||||
*/
|
||||
private async doShowRecommendationsNotification(severity: Severity, message: string, choices: IPromptChoice[], source: RecommendationSource, token: CancellationToken): Promise<boolean> {
|
||||
const disposables = new DisposableStore();
|
||||
try {
|
||||
this.recommendationSources.push(source);
|
||||
const recommendationsNotification = new RecommendationsNotification(severity, message, choices, this.notificationService);
|
||||
Event.once(Event.filter(recommendationsNotification.onDidChangeVisibility, e => !e))(() => this.showNextNotification());
|
||||
if (this.visibleNotification) {
|
||||
const index = this.pendingNotificaitons.length;
|
||||
token.onCancellationRequested(() => this.pendingNotificaitons.splice(index, 1), disposables);
|
||||
this.pendingNotificaitons.push({ recommendationsNotification, source, token });
|
||||
if (source !== RecommendationSource.EXE && source <= this.visibleNotification!.source) {
|
||||
this.hideVisibleNotification(3000);
|
||||
}
|
||||
} else {
|
||||
this.visibleNotification = { recommendationsNotification, source, from: Date.now() };
|
||||
recommendationsNotification.show();
|
||||
}
|
||||
await raceCancellation(Event.toPromise(recommendationsNotification.onDidClose), token);
|
||||
return !recommendationsNotification.isCancelled();
|
||||
} finally {
|
||||
disposables.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private showNextNotification(): void {
|
||||
const index = this.getNextPendingNotificationIndex();
|
||||
const [nextNotificaiton] = index > -1 ? this.pendingNotificaitons.splice(index, 1) : [];
|
||||
|
||||
// Show the next notification after a delay of 500ms (after the current notification is dismissed)
|
||||
timeout(nextNotificaiton ? 500 : 0)
|
||||
.then(() => {
|
||||
this.unsetVisibileNotification();
|
||||
if (nextNotificaiton) {
|
||||
this.visibleNotification = { recommendationsNotification: nextNotificaiton.recommendationsNotification, source: nextNotificaiton.source, from: Date.now() };
|
||||
nextNotificaiton.recommendationsNotification.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the recent high priroity pending notification
|
||||
*/
|
||||
private getNextPendingNotificationIndex(): number {
|
||||
let index = this.pendingNotificaitons.length - 1;
|
||||
if (this.pendingNotificaitons.length) {
|
||||
for (let i = 0; i < this.pendingNotificaitons.length; i++) {
|
||||
if (this.pendingNotificaitons[i].source <= this.pendingNotificaitons[index].source) {
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
private hideVisibleNotification(timeInMillis: number): void {
|
||||
if (this.visibleNotification && !this.hideVisibleNotificationPromise) {
|
||||
const visibleNotification = this.visibleNotification;
|
||||
this.hideVisibleNotificationPromise = timeout(Math.max(timeInMillis - (Date.now() - visibleNotification.from), 0));
|
||||
this.hideVisibleNotificationPromise.then(() => visibleNotification!.recommendationsNotification.hide());
|
||||
}
|
||||
}
|
||||
|
||||
private unsetVisibileNotification(): void {
|
||||
this.hideVisibleNotificationPromise?.cancel();
|
||||
this.hideVisibleNotificationPromise = undefined;
|
||||
this.visibleNotification = undefined;
|
||||
}
|
||||
|
||||
private async getInstallableExtensions(extensionIds: string[]): Promise<IExtension[]> {
|
||||
const extensions: IExtension[] = [];
|
||||
if (extensionIds.length) {
|
||||
const pager = await this.extensionsWorkbenchService.queryGallery({ names: extensionIds, pageSize: extensionIds.length, source: 'install-recommendations' }, CancellationToken.None);
|
||||
for (const extension of pager.firstPage) {
|
||||
if (extension.gallery && (await this.extensionManagementService.canInstall(extension.gallery))) {
|
||||
extensions.push(extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
|
||||
private async runAction(action: IAction): Promise<void> {
|
||||
try {
|
||||
await action.run();
|
||||
} finally {
|
||||
action.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private addToImportantRecommendationsIgnore(id: string) {
|
||||
const importantRecommendationsIgnoreList = [...this.ignoredRecommendations];
|
||||
if (!importantRecommendationsIgnoreList.includes(id.toLowerCase())) {
|
||||
importantRecommendationsIgnoreList.push(id.toLowerCase());
|
||||
this.storageService.store(ignoreImportantExtensionRecommendationStorageKey, JSON.stringify(importantRecommendationsIgnoreList), StorageScope.GLOBAL, StorageTarget.USER);
|
||||
}
|
||||
}
|
||||
|
||||
private setIgnoreRecommendationsConfig(configVal: boolean) {
|
||||
this.configurationService.updateValue('extensions.ignoreRecommendations', configVal);
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,15 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IExtensionRecommendationReason, EnablementState, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { localize } from 'vs/nls';
|
||||
import { SearchExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { EnablementState, ExtensionRecommendationSource, IExtensionRecommendationReson, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionsConfiguration, ConfigurationKey, IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
@@ -31,11 +30,9 @@ type ExtensionWorkspaceRecommendationsNotificationClassification = {
|
||||
const ignoreWorkspaceRecommendationsStorageKey = 'extensionsAssistant/workspaceRecommendationsIgnore';
|
||||
const ignoreImportantExtensionRecommendation = 'extensionsAssistant/importantRecommendationsIgnore';
|
||||
const choiceNever = localize('neverShowAgain', "Don't Show Again");
|
||||
|
||||
export type ExtensionRecommendation = {
|
||||
readonly extensionId: string,
|
||||
readonly source: ExtensionRecommendationSource;
|
||||
readonly reason: IExtensionRecommendationReson;
|
||||
readonly reason: IExtensionRecommendationReason;
|
||||
};
|
||||
|
||||
export abstract class ExtensionRecommendations extends Disposable {
|
||||
@@ -44,7 +41,8 @@ export abstract class ExtensionRecommendations extends Disposable {
|
||||
protected abstract doActivate(): Promise<void>;
|
||||
|
||||
constructor(
|
||||
protected readonly promptedExtensionRecommendations: PromptedExtensionRecommendations,
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected readonly promptedExtensionRecommendations?: PromptedExtensionRecommendations,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -72,10 +70,8 @@ export class PromptedExtensionRecommendations extends Disposable {
|
||||
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
|
||||
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
|
||||
) {
|
||||
super();
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: ignoreImportantExtensionRecommendation, version: 1 });
|
||||
}
|
||||
|
||||
async promptImportantExtensionsInstallNotification(extensionIds: string[], message: string, searchValue: string): Promise<void> {
|
||||
@@ -182,7 +178,7 @@ export class PromptedExtensionRecommendations extends Disposable {
|
||||
isSecondary: true,
|
||||
run: () => {
|
||||
this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'neverShowAgain' });
|
||||
this.storageService.store(ignoreWorkspaceRecommendationsStorageKey, true, StorageScope.WORKSPACE);
|
||||
this.storageService.store(ignoreWorkspaceRecommendationsStorageKey, true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
}
|
||||
}],
|
||||
{
|
||||
@@ -240,7 +236,7 @@ export class PromptedExtensionRecommendations extends Disposable {
|
||||
private addToImportantRecommendationsIgnore(id: string) {
|
||||
const importantRecommendationsIgnoreList = <string[]>JSON.parse(this.storageService.get(ignoreImportantExtensionRecommendation, StorageScope.GLOBAL, '[]'));
|
||||
importantRecommendationsIgnoreList.push(id.toLowerCase());
|
||||
this.storageService.store(ignoreImportantExtensionRecommendation, JSON.stringify(importantRecommendationsIgnoreList), StorageScope.GLOBAL);
|
||||
this.storageService.store(ignoreImportantExtensionRecommendation, JSON.stringify(importantRecommendationsIgnoreList), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
private setIgnoreRecommendationsConfig(configVal: boolean) {
|
||||
|
||||
@@ -5,42 +5,35 @@
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IExtensionManagementService, IExtensionGalleryService, InstallOperation, DidInstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionRecommendationsService, ExtensionRecommendationReason, RecommendationChangeNotification, IExtensionRecommendation, ExtensionRecommendationSource } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
|
||||
import { IExtensionRecommendationsService, ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ShowRecommendationsOnlyOnDemandKey } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { distinct, shuffle } from 'vs/base/common/arrays';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { LifecyclePhase, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { LifecyclePhase, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { DynamicWorkspaceRecommendations } from 'vs/workbench/contrib/extensions/browser/dynamicWorkspaceRecommendations';
|
||||
import { ExeBasedRecommendations } from 'vs/workbench/contrib/extensions/browser/exeBasedRecommendations';
|
||||
import { ExperimentalRecommendations } from 'vs/workbench/contrib/extensions/browser/experimentalRecommendations';
|
||||
import { WorkspaceRecommendations } from 'vs/workbench/contrib/extensions/browser/workspaceRecommendations';
|
||||
import { FileBasedRecommendations } from 'vs/workbench/contrib/extensions/browser/fileBasedRecommendations';
|
||||
import { KeymapRecommendations } from 'vs/workbench/contrib/extensions/browser/keymapRecommendations';
|
||||
import { ExtensionRecommendation, PromptedExtensionRecommendations } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
|
||||
import { ConfigBasedRecommendations } from 'vs/workbench/contrib/extensions/browser/configBasedRecommendations';
|
||||
import { StaticRecommendations } from 'sql/workbench/contrib/extensions/browser/staticRecommendations';
|
||||
import { ScenarioRecommendations } from 'sql/workbench/contrib/extensions/browser/scenarioRecommendations';
|
||||
import { ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/platform/extensions/common/extensions';
|
||||
import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
|
||||
import { IExtensionRecommendation } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; // {{ SQL CARBON EDIT }}
|
||||
|
||||
type IgnoreRecommendationClassification = {
|
||||
recommendationReason: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
extensionId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
|
||||
const ignoredRecommendationsStorageKey = 'extensionsAssistant/ignored_recommendations';
|
||||
|
||||
export class ExtensionRecommendationsService extends Disposable implements IExtensionRecommendationsService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly promptedExtensionRecommendations: PromptedExtensionRecommendations;
|
||||
|
||||
// Recommendations
|
||||
private readonly fileBasedRecommendations: FileBasedRecommendations;
|
||||
private readonly workspaceRecommendations: WorkspaceRecommendations;
|
||||
@@ -52,41 +45,33 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||
private readonly staticRecommendations: StaticRecommendations; // {{SQL CARBON EDIT}} add ours
|
||||
private readonly scenarioRecommendations: ScenarioRecommendations; // {{SQL CARBON EDIT}} add ours
|
||||
|
||||
// Ignored Recommendations
|
||||
private globallyIgnoredRecommendations: string[] = [];
|
||||
|
||||
public readonly activationPromise: Promise<void>;
|
||||
private sessionSeed: number;
|
||||
|
||||
private readonly _onRecommendationChange = this._register(new Emitter<RecommendationChangeNotification>());
|
||||
onRecommendationChange: Event<RecommendationChangeNotification> = this._onRecommendationChange.event;
|
||||
private _onDidChangeRecommendations = this._register(new Emitter<void>());
|
||||
readonly onDidChangeRecommendations = this._onDidChangeRecommendations.event;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@ILifecycleService private readonly lifecycleService: ILifecycleService,
|
||||
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
|
||||
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@IExtensionIgnoredRecommendationsService private readonly extensionRecommendationsManagementService: IExtensionIgnoredRecommendationsService,
|
||||
@IExtensionRecommendationNotificationService private readonly extensionRecommendationNotificationService: IExtensionRecommendationNotificationService,
|
||||
) {
|
||||
super();
|
||||
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: ignoredRecommendationsStorageKey, version: 1 });
|
||||
|
||||
const isExtensionAllowedToBeRecommended = (extensionId: string) => this.isExtensionAllowedToBeRecommended(extensionId);
|
||||
this.promptedExtensionRecommendations = instantiationService.createInstance(PromptedExtensionRecommendations, isExtensionAllowedToBeRecommended);
|
||||
this.workspaceRecommendations = instantiationService.createInstance(WorkspaceRecommendations, this.promptedExtensionRecommendations);
|
||||
this.fileBasedRecommendations = instantiationService.createInstance(FileBasedRecommendations, this.promptedExtensionRecommendations);
|
||||
this.experimentalRecommendations = instantiationService.createInstance(ExperimentalRecommendations, this.promptedExtensionRecommendations);
|
||||
this.configBasedRecommendations = instantiationService.createInstance(ConfigBasedRecommendations, this.promptedExtensionRecommendations);
|
||||
this.exeBasedRecommendations = instantiationService.createInstance(ExeBasedRecommendations, this.promptedExtensionRecommendations);
|
||||
this.dynamicWorkspaceRecommendations = instantiationService.createInstance(DynamicWorkspaceRecommendations, this.promptedExtensionRecommendations);
|
||||
this.keymapRecommendations = instantiationService.createInstance(KeymapRecommendations, this.promptedExtensionRecommendations);
|
||||
this.staticRecommendations = instantiationService.createInstance(StaticRecommendations, this.promptedExtensionRecommendations); // {{SQL CARBON EDIT}} add ours
|
||||
this.scenarioRecommendations = instantiationService.createInstance(ScenarioRecommendations, this.promptedExtensionRecommendations); // {{SQL CARBON EDIT}} add ours
|
||||
this.workspaceRecommendations = instantiationService.createInstance(WorkspaceRecommendations);
|
||||
this.fileBasedRecommendations = instantiationService.createInstance(FileBasedRecommendations);
|
||||
this.experimentalRecommendations = instantiationService.createInstance(ExperimentalRecommendations);
|
||||
this.configBasedRecommendations = instantiationService.createInstance(ConfigBasedRecommendations);
|
||||
this.exeBasedRecommendations = instantiationService.createInstance(ExeBasedRecommendations);
|
||||
this.dynamicWorkspaceRecommendations = instantiationService.createInstance(DynamicWorkspaceRecommendations);
|
||||
this.keymapRecommendations = instantiationService.createInstance(KeymapRecommendations);
|
||||
this.staticRecommendations = instantiationService.createInstance(StaticRecommendations); // {{SQL CARBON EDIT}} add ours
|
||||
this.scenarioRecommendations = instantiationService.createInstance(ScenarioRecommendations); // {{SQL CARBON EDIT}} add ours
|
||||
|
||||
if (!this.isEnabled()) {
|
||||
this.sessionSeed = 0;
|
||||
@@ -95,13 +80,11 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||
}
|
||||
|
||||
this.sessionSeed = +new Date();
|
||||
this.globallyIgnoredRecommendations = this.getCachedIgnoredRecommendations();
|
||||
|
||||
// Activation
|
||||
this.activationPromise = this.activate();
|
||||
|
||||
this._register(this.extensionManagementService.onDidInstallExtension(e => this.onDidInstallExtension(e)));
|
||||
this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e)));
|
||||
}
|
||||
|
||||
private async activate(): Promise<void> {
|
||||
@@ -115,20 +98,23 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||
this.keymapRecommendations.activate(),
|
||||
this.staticRecommendations.activate(), // {{SQL CARBON EDIT}} add ours
|
||||
this.scenarioRecommendations.activate(), // {{SQL CARBON EDIT}} add ours
|
||||
this.lifecycleService.when(LifecyclePhase.Eventually)
|
||||
.then(async () => {
|
||||
if (!this.configurationService.getValue<boolean>(ShowRecommendationsOnlyOnDemandKey)) {
|
||||
await this.activateProactiveRecommendations();
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
this._register(Event.any(this.workspaceRecommendations.onDidChangeRecommendations, this.configBasedRecommendations.onDidChangeRecommendations, this.extensionRecommendationsManagementService.onDidChangeIgnoredRecommendations)(() => this._onDidChangeRecommendations.fire()));
|
||||
this._register(this.extensionRecommendationsManagementService.onDidChangeGlobalIgnoredRecommendation(({ extensionId, isRecommended }) => {
|
||||
if (!isRecommended) {
|
||||
const reason = this.getAllRecommendationsWithReason()[extensionId];
|
||||
if (reason && reason.reasonId) {
|
||||
this.telemetryService.publicLog2<{ extensionId: string, recommendationReason: ExtensionRecommendationReason }, IgnoreRecommendationClassification>('extensionsRecommendations:ignoreRecommendation', { extensionId, recommendationReason: reason.reasonId });
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
await this.promptWorkspaceRecommendations();
|
||||
this._register(Event.any(this.workspaceRecommendations.onDidChangeRecommendations, this.configBasedRecommendations.onDidChangeRecommendations)(() => this.promptWorkspaceRecommendations()));
|
||||
}
|
||||
|
||||
private isEnabled(): boolean {
|
||||
return this.galleryService.isEnabled() && !this.environmentService.extensionDevelopmentLocationURI && this.configurationService.getValue<string>(ExtensionsPolicyKey) !== ExtensionsPolicy.allowNone;
|
||||
return this.galleryService.isEnabled() && !this.environmentService.extensionDevelopmentLocationURI;
|
||||
}
|
||||
|
||||
private async activateProactiveRecommendations(): Promise<void> {
|
||||
@@ -161,7 +147,7 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||
return output;
|
||||
}
|
||||
|
||||
async getConfigBasedRecommendations(): Promise<{ important: IExtensionRecommendation[], others: IExtensionRecommendation[] }> {
|
||||
async getConfigBasedRecommendations(): Promise<{ important: string[], others: string[] }> {
|
||||
await this.configBasedRecommendations.activate();
|
||||
return {
|
||||
important: this.toExtensionRecommendations(this.configBasedRecommendations.importantRecommendations),
|
||||
@@ -169,7 +155,7 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||
};
|
||||
}
|
||||
|
||||
async getOtherRecommendations(): Promise<IExtensionRecommendation[]> {
|
||||
async getOtherRecommendations(): Promise<string[]> {
|
||||
await this.activateProactiveRecommendations();
|
||||
|
||||
const recommendations = [
|
||||
@@ -185,13 +171,10 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||
|
||||
shuffle(extensionIds, this.sessionSeed);
|
||||
|
||||
return extensionIds.map(extensionId => {
|
||||
const sources: ExtensionRecommendationSource[] = distinct(recommendations.filter(r => r.extensionId === extensionId).map(r => r.source));
|
||||
return (<IExtensionRecommendation>{ extensionId, sources });
|
||||
});
|
||||
return extensionIds;
|
||||
}
|
||||
|
||||
async getImportantRecommendations(): Promise<IExtensionRecommendation[]> {
|
||||
async getImportantRecommendations(): Promise<string[]> {
|
||||
await this.activateProactiveRecommendations();
|
||||
|
||||
const recommendations = [
|
||||
@@ -205,17 +188,14 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||
|
||||
shuffle(extensionIds, this.sessionSeed);
|
||||
|
||||
return extensionIds.map(extensionId => {
|
||||
const sources: ExtensionRecommendationSource[] = distinct(recommendations.filter(r => r.extensionId === extensionId).map(r => r.source));
|
||||
return (<IExtensionRecommendation>{ extensionId, sources });
|
||||
});
|
||||
return extensionIds;
|
||||
}
|
||||
|
||||
getKeymapRecommendations(): IExtensionRecommendation[] {
|
||||
getKeymapRecommendations(): string[] {
|
||||
return this.toExtensionRecommendations(this.keymapRecommendations.recommendations);
|
||||
}
|
||||
|
||||
async getWorkspaceRecommendations(): Promise<IExtensionRecommendation[]> {
|
||||
async getWorkspaceRecommendations(): Promise<string[]> {
|
||||
if (!this.isEnabled()) {
|
||||
return [];
|
||||
}
|
||||
@@ -223,40 +203,17 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||
return this.toExtensionRecommendations(this.workspaceRecommendations.recommendations);
|
||||
}
|
||||
|
||||
async getExeBasedRecommendations(exe?: string): Promise<{ important: IExtensionRecommendation[], others: IExtensionRecommendation[] }> {
|
||||
async getExeBasedRecommendations(exe?: string): Promise<{ important: string[], others: string[] }> {
|
||||
await this.exeBasedRecommendations.activate();
|
||||
const { important, others } = exe ? this.exeBasedRecommendations.getRecommendations(exe)
|
||||
: { important: this.exeBasedRecommendations.importantRecommendations, others: this.exeBasedRecommendations.otherRecommendations };
|
||||
return { important: this.toExtensionRecommendations(important), others: this.toExtensionRecommendations(others) };
|
||||
}
|
||||
|
||||
getFileBasedRecommendations(): IExtensionRecommendation[] {
|
||||
getFileBasedRecommendations(): string[] {
|
||||
return this.toExtensionRecommendations(this.fileBasedRecommendations.recommendations);
|
||||
}
|
||||
|
||||
getIgnoredRecommendations(): ReadonlyArray<string> {
|
||||
return this.globallyIgnoredRecommendations;
|
||||
}
|
||||
|
||||
toggleIgnoredRecommendation(extensionId: string, shouldIgnore: boolean) {
|
||||
extensionId = extensionId.toLowerCase();
|
||||
const ignored = this.globallyIgnoredRecommendations.indexOf(extensionId) !== -1;
|
||||
if (ignored === shouldIgnore) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldIgnore) {
|
||||
const reason = this.getAllRecommendationsWithReason()[extensionId];
|
||||
if (reason && reason.reasonId) {
|
||||
this.telemetryService.publicLog2<{ extensionId: string, recommendationReason: ExtensionRecommendationReason }, IgnoreRecommendationClassification>('extensionsRecommendations:ignoreRecommendation', { extensionId, recommendationReason: reason.reasonId });
|
||||
}
|
||||
}
|
||||
|
||||
this.globallyIgnoredRecommendations = shouldIgnore ? [...this.globallyIgnoredRecommendations, extensionId] : this.globallyIgnoredRecommendations.filter(id => id !== extensionId);
|
||||
this.storeCachedIgnoredRecommendations(this.globallyIgnoredRecommendations);
|
||||
this._onRecommendationChange.fire({ extensionId, isRecommended: !shouldIgnore });
|
||||
}
|
||||
|
||||
private onDidInstallExtension(e: DidInstallExtensionEvent): void {
|
||||
if (e.gallery && e.operation === InstallOperation.Install) {
|
||||
const extRecommendations = this.getAllRecommendationsWithReason() || {};
|
||||
@@ -275,22 +232,15 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||
}
|
||||
}
|
||||
|
||||
private toExtensionRecommendations(recommendations: ReadonlyArray<ExtensionRecommendation>): IExtensionRecommendation[] {
|
||||
private toExtensionRecommendations(recommendations: ReadonlyArray<ExtensionRecommendation>): string[] {
|
||||
const extensionIds = distinct(recommendations.map(e => e.extensionId))
|
||||
.filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId));
|
||||
|
||||
return extensionIds.map(extensionId => {
|
||||
const sources: ExtensionRecommendationSource[] = distinct(recommendations.filter(r => r.extensionId === extensionId).map(r => r.source));
|
||||
return (<IExtensionRecommendation>{ extensionId, sources });
|
||||
});
|
||||
return extensionIds;
|
||||
}
|
||||
|
||||
private isExtensionAllowedToBeRecommended(id: string): boolean {
|
||||
const allIgnoredRecommendations = [
|
||||
...this.globallyIgnoredRecommendations,
|
||||
...this.workspaceRecommendations.ignoredRecommendations
|
||||
];
|
||||
return allIgnoredRecommendations.indexOf(id.toLowerCase()) === -1;
|
||||
private isExtensionAllowedToBeRecommended(extensionId: string): boolean {
|
||||
return !this.extensionRecommendationsManagementService.ignoredRecommendations.includes(extensionId.toLowerCase());
|
||||
}
|
||||
|
||||
private async promptWorkspaceRecommendations(): Promise<void> {
|
||||
@@ -299,42 +249,11 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||
.filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId));
|
||||
|
||||
if (allowedRecommendations.length) {
|
||||
await this.promptedExtensionRecommendations.promptWorkspaceRecommendations(allowedRecommendations);
|
||||
await this.extensionRecommendationNotificationService.promptWorkspaceRecommendations(allowedRecommendations);
|
||||
}
|
||||
}
|
||||
|
||||
private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void {
|
||||
if (e.key === ignoredRecommendationsStorageKey && e.scope === StorageScope.GLOBAL
|
||||
&& this.ignoredRecommendationsValue !== this.getStoredIgnoredRecommendationsValue() /* This checks if current window changed the value or not */) {
|
||||
this._ignoredRecommendationsValue = undefined;
|
||||
this.globallyIgnoredRecommendations = this.getCachedIgnoredRecommendations();
|
||||
}
|
||||
}
|
||||
|
||||
private getCachedIgnoredRecommendations(): string[] {
|
||||
const ignoredRecommendations: string[] = JSON.parse(this.ignoredRecommendationsValue);
|
||||
return ignoredRecommendations.map(e => e.toLowerCase());
|
||||
}
|
||||
|
||||
private storeCachedIgnoredRecommendations(ignoredRecommendations: string[]): void {
|
||||
this.ignoredRecommendationsValue = JSON.stringify(ignoredRecommendations);
|
||||
}
|
||||
|
||||
private _ignoredRecommendationsValue: string | undefined;
|
||||
private get ignoredRecommendationsValue(): string {
|
||||
if (!this._ignoredRecommendationsValue) {
|
||||
this._ignoredRecommendationsValue = this.getStoredIgnoredRecommendationsValue();
|
||||
}
|
||||
|
||||
return this._ignoredRecommendationsValue;
|
||||
}
|
||||
|
||||
private set ignoredRecommendationsValue(ignoredRecommendationsValue: string) {
|
||||
if (this.ignoredRecommendationsValue !== ignoredRecommendationsValue) {
|
||||
this._ignoredRecommendationsValue = ignoredRecommendationsValue;
|
||||
this.setStoredIgnoredRecommendationsValue(ignoredRecommendationsValue);
|
||||
}
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
promptRecommendedExtensionsByScenario(scenarioType: string): void {
|
||||
@@ -346,12 +265,4 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||
}
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
|
||||
private getStoredIgnoredRecommendationsValue(): string {
|
||||
return this.storageService.get(ignoredRecommendationsStorageKey, StorageScope.GLOBAL, '[]');
|
||||
}
|
||||
|
||||
private setStoredIgnoredRecommendationsValue(value: string): void {
|
||||
this.storageService.store(ignoredRecommendationsStorageKey, value, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,9 +3,15 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
|
||||
import { RuntimeExtensionsEditor } from 'vs/workbench/contrib/extensions/browser/browserRuntimeExtensionsEditor';
|
||||
import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput';
|
||||
|
||||
// TODO@sandbox TODO@ben move back into common/extensions.contribution.ts when 'semver-umd' can be loaded
|
||||
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService);
|
||||
// Running Extensions
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
EditorDescriptor.create(RuntimeExtensionsEditor, RuntimeExtensionsEditor.ID, localize('runtimeExtension', "Running Extensions")),
|
||||
[new SyncDescriptor(RuntimeExtensionsInput)]
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { localize } from 'vs/nls';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
|
||||
export const extensionsViewIcon = registerIcon('extensions-view-icon', Codicon.extensions, localize('extensionsViewIcon', 'View icon of the extensions view.'));
|
||||
|
||||
export const manageExtensionIcon = registerIcon('extensions-manage', Codicon.gear, localize('manageExtensionIcon', 'Icon for the \'Manage\' action in the extensions view.'));
|
||||
|
||||
export const clearSearchResultsIcon = registerIcon('extensions-clear-search-results', Codicon.clearAll, localize('clearSearchResultsIcon', 'Icon for the \'Clear Search Result\' action in the extensions view.'));
|
||||
export const refreshIcon = registerIcon('extensions-refresh', Codicon.refresh, localize('refreshIcon', 'Icon for the \'Refresh\' action in the extensions view.'));
|
||||
export const filterIcon = registerIcon('extensions-filter', Codicon.filter, localize('filterIcon', 'Icon for the \'Filter\' action in the extensions view.'));
|
||||
|
||||
export const installLocalInRemoteIcon = registerIcon('extensions-install-local-in-remote', Codicon.cloudDownload, localize('installLocalInRemoteIcon', 'Icon for the \'Install Local Extension in Remote\' action in the extensions view.'));
|
||||
export const installWorkspaceRecommendedIcon = registerIcon('extensions-install-workspace-recommended', Codicon.cloudDownload, localize('installWorkspaceRecommendedIcon', 'Icon for the \'Install Workspace Recommended Extensions\' action in the extensions view.'));
|
||||
export const configureRecommendedIcon = registerIcon('extensions-configure-recommended', Codicon.pencil, localize('configureRecommendedIcon', 'Icon for the \'Configure Recommended Extensions\' action in the extensions view.'));
|
||||
|
||||
export const syncEnabledIcon = registerIcon('extensions-sync-enabled', Codicon.sync, localize('syncEnabledIcon', 'Icon to indicate that an extension is synced.'));
|
||||
export const syncIgnoredIcon = registerIcon('extensions-sync-ignored', Codicon.syncIgnored, localize('syncIgnoredIcon', 'Icon to indicate that an extension is ignored when syncing.'));
|
||||
export const remoteIcon = registerIcon('extensions-remote', Codicon.remote, localize('remoteIcon', 'Icon to indicate that an extension is remote in the extensions view and editor.'));
|
||||
export const installCountIcon = registerIcon('extensions-install-count', Codicon.cloudDownload, localize('installCountIcon', 'Icon shown along with the install count in the extensions view and editor.'));
|
||||
export const ratingIcon = registerIcon('extensions-rating', Codicon.star, localize('ratingIcon', 'Icon shown along with the rating in the extensions view and editor.'));
|
||||
|
||||
export const starFullIcon = registerIcon('extensions-star-full', Codicon.starFull, localize('starFullIcon', 'Full star icon used for the rating in the extensions editor.'));
|
||||
export const starHalfIcon = registerIcon('extensions-star-half', Codicon.starHalf, localize('starHalfIcon', 'Half star icon used for the rating in the extensions editor.'));
|
||||
export const starEmptyIcon = registerIcon('extensions-star-empty', Codicon.starEmpty, localize('starEmptyIcon', 'Empty star icon used for the rating in the extensions editor.'));
|
||||
|
||||
export const warningIcon = registerIcon('extensions-warning-message', Codicon.warning, localize('warningIcon', 'Icon shown with a warning message in the extensions editor.'));
|
||||
export const infoIcon = registerIcon('extensions-info-message', Codicon.info, localize('infoIcon', 'Icon shown with an info message in the extensions editor.'));
|
||||
@@ -14,9 +14,9 @@ import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { IExtension, ExtensionContainers, ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { InstallAction, UpdateAction, ManageExtensionAction, ReloadAction, MaliciousStatusLabelAction, ExtensionActionViewItem, StatusLabelAction, RemoteInstallAction, SystemDisabledWarningAction, ExtensionToolTipAction, LocalInstallAction, SyncIgnoredIconAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { UpdateAction, ManageExtensionAction, ReloadAction, MaliciousStatusLabelAction, ExtensionActionViewItem, StatusLabelAction, RemoteInstallAction, SystemDisabledWarningAction, ExtensionToolTipAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { Label, RatingsWidget, /*InstallCountWidget,*/ RecommendationWidget, RemoteBadgeWidget, TooltipWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets';
|
||||
import { Label, RatingsWidget, /*InstallCountWidget,*/ RecommendationWidget, RemoteBadgeWidget, TooltipWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget, SyncIgnoredWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets';
|
||||
import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
@@ -24,6 +24,9 @@ import { isLanguagePackExtension } from 'vs/platform/extensions/common/extension
|
||||
import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { foreground, listActiveSelectionForeground, listActiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionBackground, listFocusForeground, listFocusBackground, listHoverForeground, listHoverBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
|
||||
export const EXTENSION_LIST_ELEMENT_HEIGHT = 62;
|
||||
|
||||
export interface IExtensionsViewState {
|
||||
onFocus: Event<IExtension>;
|
||||
@@ -47,7 +50,7 @@ export interface ITemplateData {
|
||||
}
|
||||
|
||||
export class Delegate implements IListVirtualDelegate<IExtension> {
|
||||
getHeight() { return 62; }
|
||||
getHeight() { return EXTENSION_LIST_ELEMENT_HEIGHT; }
|
||||
getTemplateId() { return 'extension'; }
|
||||
}
|
||||
|
||||
@@ -62,6 +65,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
|
||||
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
||||
) { }
|
||||
|
||||
get templateId() { return 'extension'; }
|
||||
@@ -80,6 +84,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
|
||||
const version = append(header, $('span.version'));
|
||||
// const installCount = append(header, $('span.install-count')); {{SQL CARBON EDIT}} no unused
|
||||
const ratings = append(header, $('span.ratings'));
|
||||
const syncIgnore = append(header, $('span.sync-ignored'));
|
||||
const headerRemoteBadgeWidget = this.instantiationService.createInstance(RemoteBadgeWidget, header, false);
|
||||
const description = append(details, $('.description.ellipsis'));
|
||||
const footer = append(details, $('.footer'));
|
||||
@@ -87,8 +92,11 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
|
||||
const actionbar = new ActionBar(footer, {
|
||||
animated: false,
|
||||
actionViewItemProvider: (action: IAction) => {
|
||||
if (action.id === ManageExtensionAction.ID) {
|
||||
return (<ManageExtensionAction>action).createActionViewItem();
|
||||
if (action instanceof ActionWithDropDownAction) {
|
||||
return new ExtensionActionWithDropdownActionViewItem(action, { icon: true, label: true, menuActionsOrProvider: { getActions: () => action.menuActions }, menuActionClassNames: (action.class || '').split(' ') }, this.contextMenuService);
|
||||
}
|
||||
if (action instanceof ExtensionDropDownAction) {
|
||||
return action.createActionViewItem();
|
||||
}
|
||||
return new ExtensionActionViewItem(null, action, actionOptions);
|
||||
}
|
||||
@@ -99,10 +107,10 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
|
||||
const reloadAction = this.instantiationService.createInstance(ReloadAction);
|
||||
const actions = [
|
||||
this.instantiationService.createInstance(StatusLabelAction),
|
||||
this.instantiationService.createInstance(SyncIgnoredIconAction),
|
||||
this.instantiationService.createInstance(UpdateAction),
|
||||
reloadAction,
|
||||
this.instantiationService.createInstance(InstallAction),
|
||||
this.instantiationService.createInstance(InstallDropdownAction),
|
||||
this.instantiationService.createInstance(InstallingLabelAction),
|
||||
this.instantiationService.createInstance(RemoteInstallAction, false),
|
||||
this.instantiationService.createInstance(LocalInstallAction),
|
||||
this.instantiationService.createInstance(MaliciousStatusLabelAction, false),
|
||||
@@ -118,6 +126,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
|
||||
headerRemoteBadgeWidget,
|
||||
tooltipWidget,
|
||||
this.instantiationService.createInstance(Label, version, (e: IExtension) => e.version),
|
||||
this.instantiationService.createInstance(SyncIgnoredWidget, syncIgnore),
|
||||
// {{SQL CARBON EDIT}}
|
||||
// this.instantiationService.createInstance(InstallCountWidget, installCount, true),
|
||||
this.instantiationService.createInstance(RatingsWidget, ratings, true)
|
||||
@@ -201,19 +210,31 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
|
||||
|
||||
this.extensionViewState.onFocus(e => {
|
||||
if (areSameExtensions(extension.identifier, e.identifier)) {
|
||||
data.actionbar.viewItems.forEach(item => (<ExtensionActionViewItem>item).setFocus(true));
|
||||
data.actionbar.viewItems.forEach(item => {
|
||||
if (item instanceof ExtensionActionViewItem || item instanceof ExtensionActionWithDropdownActionViewItem) {
|
||||
item.setFocus(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, this, data.extensionDisposables);
|
||||
|
||||
this.extensionViewState.onBlur(e => {
|
||||
if (areSameExtensions(extension.identifier, e.identifier)) {
|
||||
data.actionbar.viewItems.forEach(item => (<ExtensionActionViewItem>item).setFocus(false));
|
||||
data.actionbar.viewItems.forEach(item => {
|
||||
if (item instanceof ExtensionActionViewItem || item instanceof ExtensionActionWithDropdownActionViewItem) {
|
||||
item.setFocus(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, this, data.extensionDisposables);
|
||||
}
|
||||
|
||||
disposeElement(extension: IExtension, index: number, data: ITemplateData): void {
|
||||
data.extensionDisposables = dispose(data.extensionDisposables);
|
||||
}
|
||||
|
||||
disposeTemplate(data: ITemplateData): void {
|
||||
data.extensionDisposables = dispose(data.extensionDisposables);
|
||||
data.disposables = dispose(data.disposables);
|
||||
}
|
||||
}
|
||||
@@ -228,38 +249,40 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
|
||||
}
|
||||
|
||||
const listActiveSelectionForegroundColor = theme.getColor(listActiveSelectionForeground);
|
||||
const listActiveSelectionBackgroundColor = theme.getColor(listActiveSelectionBackground);
|
||||
if (listActiveSelectionForegroundColor && listActiveSelectionBackgroundColor) {
|
||||
const authorForeground = listActiveSelectionForegroundColor.transparent(.9).makeOpaque(listActiveSelectionBackgroundColor);
|
||||
if (listActiveSelectionForegroundColor) {
|
||||
const backgroundColor = theme.getColor(listActiveSelectionBackground) || WORKBENCH_BACKGROUND(theme);
|
||||
const authorForeground = listActiveSelectionForegroundColor.transparent(.9).makeOpaque(backgroundColor);
|
||||
collector.addRule(`.extensions-list .monaco-list:focus .monaco-list-row:not(.disabled).focused.selected .author { color: ${authorForeground}; }`);
|
||||
collector.addRule(`.extensions-list .monaco-list:focus .monaco-list-row:not(.disabled).selected .author { color: ${authorForeground}; }`);
|
||||
const disabledExtensionForeground = listActiveSelectionForegroundColor.transparent(.5).makeOpaque(listActiveSelectionBackgroundColor);
|
||||
const disabledExtensionForeground = listActiveSelectionForegroundColor.transparent(.5).makeOpaque(backgroundColor);
|
||||
collector.addRule(`.extensions-list .monaco-list:focus .monaco-list-row.disabled.focused.selected { color: ${disabledExtensionForeground}; }`);
|
||||
collector.addRule(`.extensions-list .monaco-list:focus .monaco-list-row.disabled.selected { color: ${disabledExtensionForeground}; }`);
|
||||
}
|
||||
|
||||
const listInactiveSelectionForegroundColor = theme.getColor(listInactiveSelectionForeground);
|
||||
const listInactiveSelectionBackgroundColor = theme.getColor(listInactiveSelectionBackground);
|
||||
if (listInactiveSelectionForegroundColor && listInactiveSelectionBackgroundColor) {
|
||||
const authorForeground = listInactiveSelectionForegroundColor.transparent(.9).makeOpaque(listInactiveSelectionBackgroundColor);
|
||||
if (listInactiveSelectionForegroundColor) {
|
||||
const backgroundColor = theme.getColor(listInactiveSelectionBackground) || WORKBENCH_BACKGROUND(theme);
|
||||
const authorForeground = listInactiveSelectionForegroundColor.transparent(.9).makeOpaque(backgroundColor);
|
||||
collector.addRule(`.extensions-list .monaco-list .monaco-list-row:not(.disabled).selected .author { color: ${authorForeground}; }`);
|
||||
const disabledExtensionForeground = listInactiveSelectionForegroundColor.transparent(.5).makeOpaque(listInactiveSelectionBackgroundColor);
|
||||
const disabledExtensionForeground = listInactiveSelectionForegroundColor.transparent(.5).makeOpaque(backgroundColor);
|
||||
collector.addRule(`.extensions-list .monaco-list .monaco-list-row.disabled.selected { color: ${disabledExtensionForeground}; }`);
|
||||
}
|
||||
|
||||
const listFocusForegroundColor = theme.getColor(listFocusForeground);
|
||||
const listFocusBackgroundColor = theme.getColor(listFocusBackground);
|
||||
if (listFocusForegroundColor && listFocusBackgroundColor) {
|
||||
const authorForeground = listFocusForegroundColor.transparent(.9).makeOpaque(listFocusBackgroundColor);
|
||||
if (listFocusForegroundColor) {
|
||||
const backgroundColor = theme.getColor(listFocusBackground) || WORKBENCH_BACKGROUND(theme);
|
||||
const authorForeground = listFocusForegroundColor.transparent(.9).makeOpaque(backgroundColor);
|
||||
collector.addRule(`.extensions-list .monaco-list:focus .monaco-list-row:not(.disabled).focused .author { color: ${authorForeground}; }`);
|
||||
const disabledExtensionForeground = listFocusForegroundColor.transparent(.5).makeOpaque(listFocusBackgroundColor);
|
||||
const disabledExtensionForeground = listFocusForegroundColor.transparent(.5).makeOpaque(backgroundColor);
|
||||
collector.addRule(`.extensions-list .monaco-list:focus .monaco-list-row.disabled.focused { color: ${disabledExtensionForeground}; }`);
|
||||
}
|
||||
|
||||
const listHoverForegroundColor = theme.getColor(listHoverForeground);
|
||||
const listHoverBackgroundColor = theme.getColor(listHoverBackground);
|
||||
if (listHoverForegroundColor && listHoverBackgroundColor) {
|
||||
const authorForeground = listHoverForegroundColor.transparent(.9).makeOpaque(listHoverBackgroundColor);
|
||||
if (listHoverForegroundColor) {
|
||||
const backgroundColor = theme.getColor(listHoverBackground) || WORKBENCH_BACKGROUND(theme);
|
||||
const authorForeground = listHoverForegroundColor.transparent(.9).makeOpaque(backgroundColor);
|
||||
collector.addRule(`.extensions-list .monaco-list .monaco-list-row:hover:not(.disabled):not(.selected):.not(.focused) .author { color: ${authorForeground}; }`);
|
||||
const disabledExtensionForeground = listHoverForegroundColor.transparent(.5).makeOpaque(listHoverBackgroundColor);
|
||||
const disabledExtensionForeground = listHoverForegroundColor.transparent(.5).makeOpaque(backgroundColor);
|
||||
collector.addRule(`.extensions-list .monaco-list .monaco-list-row.disabled:hover:not(.selected):.not(.focused) { color: ${disabledExtensionForeground}; }`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import { timeout, Delayer } from 'vs/base/common/async';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Event as EventOf, Emitter } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IAction, Action, Separator, SubmenuAction } from 'vs/base/common/actions';
|
||||
import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
@@ -17,27 +17,27 @@ import { append, $, Dimension, hide, show } from 'vs/base/browser/dom';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, AutoUpdateConfigurationKey, CloseExtensionDetailsOnViewChangeKey } from '../common/extensions';
|
||||
import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, AutoUpdateConfigurationKey, CloseExtensionDetailsOnViewChangeKey, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID } from '../common/extensions';
|
||||
import {
|
||||
ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction,
|
||||
EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction, SearchCategoryAction,
|
||||
/*RecentlyPublishedExtensionsAction, */ShowInstalledExtensionsAction, ShowOutdatedExtensionsAction, ShowDisabledExtensionsAction,
|
||||
ShowEnabledExtensionsAction, PredefinedExtensionFilterAction
|
||||
ShowEnabledExtensionsAction, PredefinedExtensionFilterAction, RefreshExtensionsAction
|
||||
} from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { OpenExtensionAuthoringDocsAction } from 'sql/workbench/contrib/extensions/browser/extensionsActions'; // {{ SQL CARBON EDIT }}
|
||||
import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
|
||||
import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInFeatureExtensionsView, BuiltInThemesExtensionsView, BuiltInProgrammingLanguageExtensionsView, ServerExtensionsView, DefaultRecommendedExtensionsView, OutdatedExtensionsView, InstalledExtensionsView, SearchBuiltInExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews';
|
||||
import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInFeatureExtensionsView, BuiltInThemesExtensionsView, BuiltInProgrammingLanguageExtensionsView, ServerInstalledExtensionsView, DefaultRecommendedExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews';
|
||||
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
@@ -60,8 +60,12 @@ import { DragAndDropObserver } from 'vs/workbench/browser/dnd';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { filterIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons';
|
||||
|
||||
const NonEmptyWorkspaceContext = new RawContextKey<boolean>('nonEmptyWorkspace', false);
|
||||
const DefaultViewsContext = new RawContextKey<boolean>('defaultExtensionViews', true);
|
||||
const SearchMarketplaceExtensionsContext = new RawContextKey<boolean>('searchMarketplaceExtensions', false);
|
||||
const SearchIntalledExtensionsContext = new RawContextKey<boolean>('searchInstalledExtensions', false);
|
||||
@@ -69,6 +73,7 @@ const SearchOutdatedExtensionsContext = new RawContextKey<boolean>('searchOutdat
|
||||
const SearchEnabledExtensionsContext = new RawContextKey<boolean>('searchEnabledExtensions', false);
|
||||
const SearchDisabledExtensionsContext = new RawContextKey<boolean>('searchDisabledExtensions', false);
|
||||
const HasInstalledExtensionsContext = new RawContextKey<boolean>('hasInstalledExtensions', true);
|
||||
const HasInstalledWebExtensionsContext = new RawContextKey<boolean>('hasInstalledWebExtensions', false);
|
||||
const BuiltInExtensionsContext = new RawContextKey<boolean>('builtInExtensions', false);
|
||||
const SearchBuiltInExtensionsContext = new RawContextKey<boolean>('searchBuiltInExtensions', false);
|
||||
const RecommendedExtensionsContext = new RawContextKey<boolean>('recommendedExtensions', false);
|
||||
@@ -80,7 +85,8 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
|
||||
constructor(
|
||||
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService
|
||||
) {
|
||||
this.container = viewDescriptorService.getViewContainerById(VIEWLET_ID)!;
|
||||
this.registerViews();
|
||||
@@ -135,27 +141,71 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
|
||||
servers.push(this.extensionManagementServerService.remoteExtensionManagementServer);
|
||||
}
|
||||
const getViewName = (viewTitle: string, server: IExtensionManagementServer): string => {
|
||||
if (servers.length) {
|
||||
const serverLabel = server === this.extensionManagementServerService.webExtensionManagementServer && !this.extensionManagementServerService.localExtensionManagementServer ? localize('local', "Local") : server.label;
|
||||
return servers.length > 1 ? `${serverLabel} - ${viewTitle}` : viewTitle;
|
||||
if (servers.length > 1) {
|
||||
// In Web, use view title as is for remote server, when web extension server is enabled and no web extensions are installed
|
||||
if (isWeb && server === this.extensionManagementServerService.remoteExtensionManagementServer &&
|
||||
this.extensionManagementServerService.webExtensionManagementServer && !this.contextKeyService.getContextKeyValue<boolean>('hasInstalledWebExtensions')) {
|
||||
return viewTitle;
|
||||
}
|
||||
return `${server.label} - ${viewTitle}`;
|
||||
}
|
||||
return viewTitle;
|
||||
};
|
||||
let installedWebExtensionsContextChangeEvent = Event.None;
|
||||
if (this.extensionManagementServerService.webExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) {
|
||||
const interestingContextKeys = new Set();
|
||||
interestingContextKeys.add('hasInstalledWebExtensions');
|
||||
installedWebExtensionsContextChangeEvent = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(interestingContextKeys));
|
||||
}
|
||||
const serverLabelChangeEvent = Event.any(this.labelService.onDidChangeFormatters, installedWebExtensionsContextChangeEvent);
|
||||
for (const server of servers) {
|
||||
const getInstalledViewName = (): string => getViewName(localize('installed', "Installed"), server);
|
||||
const onDidChangeServerLabel: EventOf<void> = EventOf.map(this.labelService.onDidChangeFormatters, () => undefined);
|
||||
const onDidChangeTitle = Event.map<void, string>(serverLabelChangeEvent, () => getInstalledViewName());
|
||||
const id = servers.length > 1 ? `workbench.views.extensions.${server.id}.installed` : `workbench.views.extensions.installed`;
|
||||
const isWebServer = server === this.extensionManagementServerService.webExtensionManagementServer;
|
||||
if (!isWebServer) {
|
||||
/* Empty installed extensions view */
|
||||
viewDescriptors.push({
|
||||
id: `${id}.empty`,
|
||||
get name() { return getInstalledViewName(); },
|
||||
weight: 100,
|
||||
order: 1,
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.not('hasInstalledExtensions')),
|
||||
/* Empty installed extensions view shall have fixed height */
|
||||
ctorDescriptor: new SyncDescriptor(ServerInstalledExtensionsView, [{ server, fixedHeight: true, onDidChangeTitle }]),
|
||||
/* Empty installed extensions views shall not be allowed to hidden */
|
||||
canToggleVisibility: false
|
||||
});
|
||||
}
|
||||
/* Installed extensions view */
|
||||
viewDescriptors.push({
|
||||
id: servers.length > 1 ? `workbench.views.extensions.${server.id}.installed` : `workbench.views.extensions.installed`,
|
||||
id,
|
||||
get name() { return getInstalledViewName(); },
|
||||
ctorDescriptor: new SyncDescriptor(ServerExtensionsView, [server, EventOf.map<void, string>(onDidChangeServerLabel, () => getInstalledViewName())]),
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions')),
|
||||
weight: 100,
|
||||
order: 2,
|
||||
/* Installed extensions views shall not be hidden when there are more than one server */
|
||||
order: 1,
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), isWebServer ? ContextKeyExpr.has('hasInstalledWebExtensions') : ContextKeyExpr.has('hasInstalledExtensions')),
|
||||
ctorDescriptor: new SyncDescriptor(ServerInstalledExtensionsView, [{ server, onDidChangeTitle }]),
|
||||
/* Installed extensions views shall not be allowed to hidden when there are more than one server */
|
||||
canToggleVisibility: servers.length === 1
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Default popular extensions view
|
||||
* Separate view for popular extensions required as we need to show popular and recommended sections
|
||||
* in the default view when there is no search text, and user has no installed extensions.
|
||||
*/
|
||||
// {{SQL CARBON EDIT}} -- remove "Popular" view
|
||||
// viewDescriptors.push({
|
||||
// id: 'workbench.views.extensions.popular',
|
||||
// name: localize('popularExtensions', "Popular"),
|
||||
// ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),
|
||||
// when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.not('hasInstalledExtensions')),
|
||||
// weight: 60,
|
||||
// order: 2,
|
||||
// canToggleVisibility: false
|
||||
// });
|
||||
|
||||
/*
|
||||
* Default recommended extensions view
|
||||
* When user has installed extensions, this is shown along with the views for enabled & disabled extensions
|
||||
@@ -163,8 +213,8 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
|
||||
*/
|
||||
viewDescriptors.push({
|
||||
id: 'extensions.recommendedList',
|
||||
name: localize('recommendedExtensions', "Recommended"),
|
||||
ctorDescriptor: new SyncDescriptor(DefaultRecommendedExtensionsView),
|
||||
name: localize('recommendedExtensions', "Marketplace"), // {{SQL CARBON EDIT}} - change name to marketplace
|
||||
ctorDescriptor: new SyncDescriptor(DefaultRecommendedExtensionsView, [{}]),
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.not('config.extensions.showRecommendationsOnlyOnDemand')),
|
||||
weight: 40,
|
||||
order: 3,
|
||||
@@ -180,7 +230,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
|
||||
viewDescriptors.push({
|
||||
id: 'workbench.views.extensions.enabled',
|
||||
name: localize('enabledExtensions', "Enabled"),
|
||||
ctorDescriptor: new SyncDescriptor(EnabledExtensionsView),
|
||||
ctorDescriptor: new SyncDescriptor(EnabledExtensionsView, [{}]),
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions')),
|
||||
hideByDefault: true,
|
||||
weight: 40,
|
||||
@@ -195,7 +245,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
|
||||
viewDescriptors.push({
|
||||
id: 'workbench.views.extensions.disabled',
|
||||
name: localize('disabledExtensions', "Disabled"),
|
||||
ctorDescriptor: new SyncDescriptor(DisabledExtensionsView),
|
||||
ctorDescriptor: new SyncDescriptor(DisabledExtensionsView, [{}]),
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions')),
|
||||
hideByDefault: true,
|
||||
weight: 10,
|
||||
@@ -217,7 +267,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
|
||||
viewDescriptors.push({
|
||||
id: 'workbench.views.extensions.marketplace',
|
||||
name: localize('marketPlace', "Marketplace"),
|
||||
ctorDescriptor: new SyncDescriptor(ExtensionsListView),
|
||||
ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.has('searchMarketplaceExtensions')),
|
||||
});
|
||||
|
||||
@@ -227,7 +277,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
|
||||
viewDescriptors.push({
|
||||
id: 'workbench.views.extensions.searchInstalled',
|
||||
name: localize('installed', "Installed"),
|
||||
ctorDescriptor: new SyncDescriptor(InstalledExtensionsView),
|
||||
ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.has('searchInstalledExtensions')),
|
||||
});
|
||||
|
||||
@@ -237,7 +287,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
|
||||
viewDescriptors.push({
|
||||
id: 'workbench.views.extensions.searchEnabled',
|
||||
name: localize('enabled', "Enabled"),
|
||||
ctorDescriptor: new SyncDescriptor(EnabledExtensionsView),
|
||||
ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.has('searchEnabledExtensions')),
|
||||
});
|
||||
|
||||
@@ -247,7 +297,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
|
||||
viewDescriptors.push({
|
||||
id: 'workbench.views.extensions.searchDisabled',
|
||||
name: localize('disabled', "Disabled"),
|
||||
ctorDescriptor: new SyncDescriptor(DisabledExtensionsView),
|
||||
ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.has('searchDisabledExtensions')),
|
||||
});
|
||||
|
||||
@@ -257,7 +307,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
|
||||
viewDescriptors.push({
|
||||
id: 'workbench.views.extensions.searchOutdated',
|
||||
name: localize('outdated', "Outdated"),
|
||||
ctorDescriptor: new SyncDescriptor(OutdatedExtensionsView),
|
||||
ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.has('searchOutdatedExtensions')),
|
||||
});
|
||||
|
||||
@@ -267,7 +317,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
|
||||
viewDescriptors.push({
|
||||
id: 'workbench.views.extensions.searchBuiltin',
|
||||
name: localize('builtin', "Builtin"),
|
||||
ctorDescriptor: new SyncDescriptor(SearchBuiltInExtensionsView),
|
||||
ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.has('searchBuiltInExtensions')),
|
||||
});
|
||||
|
||||
@@ -280,15 +330,15 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
|
||||
viewDescriptors.push({
|
||||
id: 'workbench.views.extensions.workspaceRecommendations',
|
||||
name: localize('workspaceRecommendedExtensions', "Workspace Recommendations"),
|
||||
ctorDescriptor: new SyncDescriptor(WorkspaceRecommendedExtensionsView),
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), ContextKeyExpr.has('nonEmptyWorkspace')),
|
||||
ctorDescriptor: new SyncDescriptor(WorkspaceRecommendedExtensionsView, [{}]),
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), WorkbenchStateContext.notEqualsTo('empty')),
|
||||
order: 1
|
||||
});
|
||||
|
||||
viewDescriptors.push({
|
||||
id: 'workbench.views.extensions.otherRecommendations',
|
||||
name: localize('otherRecommendedExtensions', "Other Recommendations"),
|
||||
ctorDescriptor: new SyncDescriptor(RecommendedExtensionsView),
|
||||
ctorDescriptor: new SyncDescriptor(RecommendedExtensionsView, [{}]),
|
||||
when: ContextKeyExpr.has('recommendedExtensions'),
|
||||
order: 2
|
||||
});
|
||||
@@ -302,21 +352,21 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
|
||||
viewDescriptors.push({
|
||||
id: 'workbench.views.extensions.builtinFeatureExtensions',
|
||||
name: localize('builtinFeatureExtensions', "Features"),
|
||||
ctorDescriptor: new SyncDescriptor(BuiltInFeatureExtensionsView),
|
||||
ctorDescriptor: new SyncDescriptor(BuiltInFeatureExtensionsView, [{}]),
|
||||
when: ContextKeyExpr.has('builtInExtensions'),
|
||||
});
|
||||
|
||||
viewDescriptors.push({
|
||||
id: 'workbench.views.extensions.builtinThemeExtensions',
|
||||
name: localize('builtInThemesExtensions', "Themes"),
|
||||
ctorDescriptor: new SyncDescriptor(BuiltInThemesExtensionsView),
|
||||
ctorDescriptor: new SyncDescriptor(BuiltInThemesExtensionsView, [{}]),
|
||||
when: ContextKeyExpr.has('builtInExtensions'),
|
||||
});
|
||||
|
||||
viewDescriptors.push({
|
||||
id: 'workbench.views.extensions.builtinProgrammingLanguageExtensions',
|
||||
name: localize('builtinProgrammingLanguageExtensions', "Programming Languages"),
|
||||
ctorDescriptor: new SyncDescriptor(BuiltInProgrammingLanguageExtensionsView),
|
||||
ctorDescriptor: new SyncDescriptor(BuiltInProgrammingLanguageExtensionsView, [{}]),
|
||||
when: ContextKeyExpr.has('builtInExtensions'),
|
||||
});
|
||||
|
||||
@@ -328,8 +378,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
|
||||
export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IExtensionsViewPaneContainer {
|
||||
|
||||
private readonly _onSearchChange: Emitter<string> = this._register(new Emitter<string>());
|
||||
private readonly onSearchChange: EventOf<string> = this._onSearchChange.event;
|
||||
private nonEmptyWorkspaceContextKey: IContextKey<boolean>;
|
||||
private readonly onSearchChange: Event<string> = this._onSearchChange.event;
|
||||
private defaultViewsContextKey: IContextKey<boolean>;
|
||||
private searchMarketplaceExtensionsContextKey: IContextKey<boolean>;
|
||||
private searchInstalledExtensionsContextKey: IContextKey<boolean>;
|
||||
@@ -337,6 +386,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
private searchEnabledExtensionsContextKey: IContextKey<boolean>;
|
||||
private searchDisabledExtensionsContextKey: IContextKey<boolean>;
|
||||
private hasInstalledExtensionsContextKey: IContextKey<boolean>;
|
||||
private hasInstalledWebExtensionsContextKey: IContextKey<boolean>;
|
||||
private builtInExtensionsContextKey: IContextKey<boolean>;
|
||||
private searchBuiltInExtensionsContextKey: IContextKey<boolean>;
|
||||
private recommendedExtensionsContextKey: IContextKey<boolean>;
|
||||
@@ -346,6 +396,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
private searchBox: SuggestEnabledInput | undefined;
|
||||
private readonly searchViewletState: MementoObject;
|
||||
private readonly sortActions: ChangeSortAction[];
|
||||
private secondaryActions: IAction[] | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@@ -353,7 +404,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
@IProgressService private readonly progressService: IProgressService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
|
||||
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IViewletService private readonly viewletService: IViewletService,
|
||||
@@ -366,11 +418,11 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService,
|
||||
@ICommandService private readonly commandService: ICommandService
|
||||
) {
|
||||
super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService);
|
||||
|
||||
this.searchDelayer = new Delayer(500);
|
||||
this.nonEmptyWorkspaceContextKey = NonEmptyWorkspaceContext.bindTo(contextKeyService);
|
||||
this.defaultViewsContextKey = DefaultViewsContext.bindTo(contextKeyService);
|
||||
this.searchMarketplaceExtensionsContextKey = SearchMarketplaceExtensionsContext.bindTo(contextKeyService);
|
||||
this.searchInstalledExtensionsContextKey = SearchIntalledExtensionsContext.bindTo(contextKeyService);
|
||||
@@ -378,22 +430,30 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
this.searchEnabledExtensionsContextKey = SearchEnabledExtensionsContext.bindTo(contextKeyService);
|
||||
this.searchDisabledExtensionsContextKey = SearchDisabledExtensionsContext.bindTo(contextKeyService);
|
||||
this.hasInstalledExtensionsContextKey = HasInstalledExtensionsContext.bindTo(contextKeyService);
|
||||
this.hasInstalledWebExtensionsContextKey = HasInstalledWebExtensionsContext.bindTo(contextKeyService);
|
||||
this.builtInExtensionsContextKey = BuiltInExtensionsContext.bindTo(contextKeyService);
|
||||
this.searchBuiltInExtensionsContextKey = SearchBuiltInExtensionsContext.bindTo(contextKeyService);
|
||||
this.recommendedExtensionsContextKey = RecommendedExtensionsContext.bindTo(contextKeyService);
|
||||
this._register(this.viewletService.onDidViewletOpen(this.onViewletOpen, this));
|
||||
this.searchViewletState = this.getMemento(StorageScope.WORKSPACE);
|
||||
|
||||
this.extensionManagementService.getInstalled(ExtensionType.User).then(result => {
|
||||
this.hasInstalledExtensionsContextKey.set(result.length > 0);
|
||||
});
|
||||
this.searchViewletState = this.getMemento(StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(AutoUpdateConfigurationKey)) {
|
||||
this.secondaryActions = undefined;
|
||||
this.updateTitleArea();
|
||||
}
|
||||
}, this));
|
||||
|
||||
if (extensionManagementServerService.webExtensionManagementServer) {
|
||||
this._register(extensionsWorkbenchService.onChange(() => {
|
||||
// show installed web extensions view only when it is not visible
|
||||
// Do not hide the view automatically when it is visible
|
||||
if (!this.hasInstalledWebExtensionsContextKey.get()) {
|
||||
this.updateInstalledWebExtensionsContext();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
this.sortActions = [
|
||||
// this._register(this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.install', localize('sort by installs', "Install Count"), this.onSearchChange, 'installs')), // {{SQL CARBON EDIT}}
|
||||
// this._register(this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.rating', localize('sort by rating', "Rating"), this.onSearchChange, 'rating')), // {{SQL CARBON EDIT}}
|
||||
@@ -426,6 +486,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
provideResults: (query: string) => Query.suggestions(query)
|
||||
}, placeholder, 'extensions:searchinput', { placeholderText: placeholder, value: searchValue }));
|
||||
|
||||
this.updateInstalledExtensionsContexts();
|
||||
if (this.searchBox.getValue()) {
|
||||
this.triggerSearch();
|
||||
}
|
||||
@@ -478,7 +539,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
|
||||
try {
|
||||
// Attempt to install the extension(s)
|
||||
await this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL).run(vsixPaths);
|
||||
await this.commandService.executeCommand(INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, vsixPaths);
|
||||
}
|
||||
catch (err) {
|
||||
this.notificationService.error(err);
|
||||
@@ -502,7 +563,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
this.root.classList.toggle('narrow', dimension.width <= 300);
|
||||
}
|
||||
if (this.searchBox) {
|
||||
this.searchBox.layout({ height: 20, width: dimension.width - 34 });
|
||||
this.searchBox.layout(new Dimension(dimension.width - 34, 20));
|
||||
}
|
||||
super.layout(new Dimension(dimension.width, dimension.height - 41));
|
||||
}
|
||||
@@ -511,20 +572,19 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
return 400;
|
||||
}
|
||||
|
||||
@memoize
|
||||
getActions(): IAction[] {
|
||||
const filterActions: IAction[] = [];
|
||||
|
||||
// Local extensions filters
|
||||
filterActions.push(...[
|
||||
this.instantiationService.createInstance(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, localize('builtin filter', "Built-in")),
|
||||
this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, localize('installed filter', "Installed")),
|
||||
this.instantiationService.createInstance(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, localize('enabled filter', "Enabled")),
|
||||
this.instantiationService.createInstance(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, localize('disabled filter', "Disabled")),
|
||||
this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, localize('outdated filter', "Outdated")),
|
||||
]);
|
||||
let filterActions: IAction[] = [
|
||||
this._register(this.instantiationService.createInstance(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, localize('builtin filter', "Built-in"))),
|
||||
this._register(this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, localize('installed filter', "Installed"))),
|
||||
this._register(this.instantiationService.createInstance(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, localize('enabled filter', "Enabled"))),
|
||||
this._register(this.instantiationService.createInstance(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, localize('disabled filter', "Disabled"))),
|
||||
this._register(this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, localize('outdated filter', "Outdated"))),
|
||||
];
|
||||
|
||||
if (this.extensionGalleryService.isEnabled()) {
|
||||
const galleryFilterActions = [
|
||||
filterActions = [
|
||||
// this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.featured', localize('featured filter', "Featured"), '@featured'), // {{SQL CARBON EDIT}}
|
||||
// this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.popular', localize('most popular filter', "Most Popular"), '@popular'), // {{SQL CARBON EDIT}}
|
||||
this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.recommended', localize('most popular recommended', "Recommended"), '@recommended'),
|
||||
@@ -532,51 +592,62 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
new Separator(),
|
||||
new SubmenuAction('workbench.extensions.action.filterExtensionsByCategory', localize('filter by category', "Category"), EXTENSION_CATEGORIES.map(category => this.instantiationService.createInstance(SearchCategoryAction, `extensions.actions.searchByCategory.${category}`, category, category))),
|
||||
new Separator(),
|
||||
...filterActions,
|
||||
this._register(new Separator()),
|
||||
this._register(new SubmenuAction('workbench.extensions.action.sortBy', localize('sorty by', "Sort By"), this.sortActions)),
|
||||
];
|
||||
filterActions.splice(0, 0, ...galleryFilterActions);
|
||||
filterActions.push(...[
|
||||
new Separator(),
|
||||
new SubmenuAction('workbench.extensions.action.sortBy', localize('sorty by', "Sort By"), this.sortActions),
|
||||
]);
|
||||
}
|
||||
|
||||
return [
|
||||
new SubmenuAction('workbench.extensions.action.filterExtensions', localize('filterExtensions', "Filter Extensions..."), filterActions, 'codicon-filter'),
|
||||
this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange, this.searchBox ? this.searchBox.getValue() : ''),
|
||||
this._register(new SubmenuAction('workbench.extensions.action.filterExtensions', localize('filterExtensions', "Filter Extensions..."), filterActions, ThemeIcon.asClassName(filterIcon))),
|
||||
this._register(this.instantiationService.createInstance(RefreshExtensionsAction, RefreshExtensionsAction.ID, RefreshExtensionsAction.LABEL)),
|
||||
this._register(this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange, () => this.searchBox!.getValue() || '')),
|
||||
];
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
const actions: IAction[] = [];
|
||||
if (!this.secondaryActions) {
|
||||
this.secondaryActions = [];
|
||||
this.secondaryActions.push(this.instantiationService.createInstance(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL));
|
||||
if (this.configurationService.getValue(AutoUpdateConfigurationKey)) {
|
||||
this.secondaryActions.push(this.instantiationService.createInstance(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL));
|
||||
} else {
|
||||
this.secondaryActions.push(this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL, false), this.instantiationService.createInstance(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL));
|
||||
}
|
||||
|
||||
this.secondaryActions.push(new Separator());
|
||||
this.secondaryActions.push(this.instantiationService.createInstance(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL, false));
|
||||
this.secondaryActions.push(this.instantiationService.createInstance(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL, false));
|
||||
|
||||
this.secondaryActions.push(new Separator());
|
||||
this.secondaryActions.push(this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL));
|
||||
this.secondaryActions.push(this.instantiationService.createInstance(OpenExtensionAuthoringDocsAction, OpenExtensionAuthoringDocsAction.ID, OpenExtensionAuthoringDocsAction.LABEL)); // {{SQL CARBON EDIT}}
|
||||
|
||||
actions.push(this.instantiationService.createInstance(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL));
|
||||
if (this.configurationService.getValue(AutoUpdateConfigurationKey)) {
|
||||
actions.push(this.instantiationService.createInstance(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL));
|
||||
} else {
|
||||
actions.push(this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL), this.instantiationService.createInstance(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL));
|
||||
}
|
||||
|
||||
actions.push(new Separator());
|
||||
actions.push(this.instantiationService.createInstance(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL));
|
||||
actions.push(this.instantiationService.createInstance(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL));
|
||||
|
||||
actions.push(new Separator());
|
||||
actions.push(this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL));
|
||||
actions.push(this.instantiationService.createInstance(OpenExtensionAuthoringDocsAction, OpenExtensionAuthoringDocsAction.ID, OpenExtensionAuthoringDocsAction.LABEL)); // {{SQL CARBON EDIT}}
|
||||
|
||||
return actions;
|
||||
return this.secondaryActions;
|
||||
}
|
||||
|
||||
search(value: string, refresh: boolean = false): void {
|
||||
if (this.searchBox) {
|
||||
if (this.searchBox.getValue() !== value) {
|
||||
this.searchBox.setValue(value);
|
||||
} else if (refresh) {
|
||||
this.doSearch();
|
||||
}
|
||||
search(value: string): void {
|
||||
if (this.searchBox && this.searchBox.getValue() !== value) {
|
||||
this.searchBox.setValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
async refresh(): Promise<void> {
|
||||
await this.updateInstalledExtensionsContexts();
|
||||
this.doSearch(true);
|
||||
}
|
||||
|
||||
private async updateInstalledExtensionsContexts(): Promise<void> {
|
||||
const result = await this.extensionsWorkbenchService.queryLocal();
|
||||
this.hasInstalledExtensionsContextKey.set(result.some(r => !r.isBuiltin));
|
||||
this.updateInstalledWebExtensionsContext();
|
||||
}
|
||||
|
||||
private updateInstalledWebExtensionsContext(): void {
|
||||
this.hasInstalledWebExtensionsContextKey.set(!!this.extensionManagementServerService.webExtensionManagementServer && this.extensionsWorkbenchService.installed.some(r => r.server === this.extensionManagementServerService.webExtensionManagementServer));
|
||||
}
|
||||
|
||||
private triggerSearch(): void {
|
||||
this.searchDelayer.trigger(() => this.doSearch(), this.searchBox && this.searchBox.getValue() ? 500 : 0).then(undefined, err => this.onError(err));
|
||||
}
|
||||
@@ -603,7 +674,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
super.saveState();
|
||||
}
|
||||
|
||||
private doSearch(): Promise<void> {
|
||||
private doSearch(refresh?: boolean): Promise<void> {
|
||||
const value = this.normalizedQuery();
|
||||
const isRecommendedExtensionsQuery = ExtensionsListView.isRecommendedExtensionsQuery(value);
|
||||
this.searchInstalledExtensionsContextKey.set(ExtensionsListView.isInstalledExtensionsQuery(value));
|
||||
@@ -614,11 +685,11 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
|
||||
this.builtInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value));
|
||||
this.recommendedExtensionsContextKey.set(isRecommendedExtensionsQuery);
|
||||
this.searchMarketplaceExtensionsContextKey.set(!!value && !ExtensionsListView.isLocalExtensionsQuery(value) && !isRecommendedExtensionsQuery);
|
||||
this.nonEmptyWorkspaceContextKey.set(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY);
|
||||
this.defaultViewsContextKey.set(!value);
|
||||
this.updateInstalledWebExtensionsContext();
|
||||
|
||||
return this.progress(Promise.all(this.panes.map(view =>
|
||||
(<ExtensionsListView>view).show(this.normalizedQuery())
|
||||
(<ExtensionsListView>view).show(this.normalizedQuery(), refresh)
|
||||
.then(model => this.alertSearchResult(model.length, view.id))
|
||||
))).then(() => undefined);
|
||||
}
|
||||
@@ -768,7 +839,7 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution {
|
||||
.filter(e => maliciousSet.has(e.identifier.id));
|
||||
|
||||
if (maliciousExtensions.length) {
|
||||
return Promise.all(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e, true).then(() => {
|
||||
return Promise.all(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e).then(() => {
|
||||
this.notificationService.prompt(
|
||||
Severity.Warning,
|
||||
localize('malicious warning', "We have uninstalled '{0}' which was reported to be problematic.", e.identifier.id),
|
||||
|
||||
@@ -4,22 +4,23 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { isPromiseCanceledError, getErrorMessage } from 'vs/base/common/errors';
|
||||
import { PagedModel, IPagedModel, IPager, DelayedPagedModel } from 'vs/base/common/paging';
|
||||
import { SortBy, SortOrder, IQueryOptions, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionManagementServer, IExtensionManagementServerService, IExtensionRecommendationsService, IExtensionRecommendation, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { SortBy, SortOrder, IQueryOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionManagementServer, IExtensionManagementServerService, EnablementState, IWorkbenchExtensioManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { append, $ } from 'vs/base/browser/dom';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Delegate, Renderer, IExtensionsViewState } from 'vs/workbench/contrib/extensions/browser/extensionsList';
|
||||
import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { Delegate, Renderer, IExtensionsViewState, EXTENSION_LIST_ELEMENT_HEIGHT } from 'vs/workbench/contrib/extensions/browser/extensionsList';
|
||||
import { ExtensionState, IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery';
|
||||
import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { attachBadgeStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
@@ -37,17 +38,18 @@ import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list';
|
||||
import { createErrorWithActions } from 'vs/base/common/errorsWithActions';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IAction, Action, Separator } from 'vs/base/common/actions';
|
||||
import { ExtensionType, ExtensionIdentifier, IExtensionDescription, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionIdentifier, ExtensionType, IExtensionDescription, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; // {{ SQL CARBON EDIT }}
|
||||
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { configureRecommendedIcon, installLocalInRemoteIcon, installWorkspaceRecommendedIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons';
|
||||
|
||||
// Extensions that are automatically classified as Programming Language extensions, but should be Feature extensions
|
||||
const FORCE_FEATURE_EXTENSIONS = ['vscode.git', 'vscode.search-result'];
|
||||
@@ -73,15 +75,22 @@ class ExtensionsViewState extends Disposable implements IExtensionsViewState {
|
||||
}
|
||||
}
|
||||
|
||||
export interface ExtensionsListViewOptions extends IViewletViewOptions {
|
||||
export interface ExtensionsListViewOptions {
|
||||
server?: IExtensionManagementServer;
|
||||
fixedHeight?: boolean;
|
||||
onDidChangeTitle?: Event<string>;
|
||||
}
|
||||
|
||||
class ExtensionListViewWarning extends Error { }
|
||||
|
||||
interface IQueryResult {
|
||||
readonly model: IPagedModel<IExtension>;
|
||||
readonly onDidChangeModel?: Event<IPagedModel<IExtension>>;
|
||||
readonly disposables: DisposableStore;
|
||||
}
|
||||
|
||||
export class ExtensionsListView extends ViewPane {
|
||||
|
||||
protected readonly server: IExtensionManagementServer | undefined;
|
||||
private bodyTemplate: {
|
||||
messageContainer: HTMLElement;
|
||||
messageSeverityIcon: HTMLElement;
|
||||
@@ -91,9 +100,11 @@ export class ExtensionsListView extends ViewPane {
|
||||
private badge: CountBadge | undefined;
|
||||
private list: WorkbenchPagedList<IExtension> | null = null;
|
||||
private queryRequest: { query: string, request: CancelablePromise<IPagedModel<IExtension>> } | null = null;
|
||||
private queryResult: IQueryResult | undefined;
|
||||
|
||||
constructor(
|
||||
options: ExtensionsListViewOptions,
|
||||
protected readonly options: ExtensionsListViewOptions,
|
||||
viewletViewOptions: IViewletViewOptions,
|
||||
@INotificationService protected notificationService: INotificationService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@@ -107,16 +118,22 @@ export class ExtensionsListView extends ViewPane {
|
||||
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
|
||||
@IExperimentService private readonly experimentService: IExperimentService,
|
||||
@IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService,
|
||||
@IExtensionManagementService protected readonly extensionManagementService: IExtensionManagementService,
|
||||
@IWorkbenchExtensioManagementService protected readonly extensionManagementService: IWorkbenchExtensioManagementService,
|
||||
@IProductService protected readonly productService: IProductService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
) {
|
||||
super({ ...(options as IViewPaneOptions), showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
|
||||
this.server = options.server;
|
||||
super({
|
||||
...(viewletViewOptions as IViewPaneOptions),
|
||||
showActionsAlways: true,
|
||||
maximumBodySize: options.fixedHeight ? storageService.getNumber(viewletViewOptions.id, StorageScope.GLOBAL, 0) : undefined
|
||||
}, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
|
||||
if (this.options.onDidChangeTitle) {
|
||||
this._register(this.options.onDidChangeTitle(title => this.updateTitle(title)));
|
||||
}
|
||||
}
|
||||
|
||||
protected renderHeader(container: HTMLElement): void {
|
||||
@@ -181,15 +198,20 @@ export class ExtensionsListView extends ViewPane {
|
||||
}
|
||||
}
|
||||
|
||||
async show(query: string): Promise<IPagedModel<IExtension>> {
|
||||
async show(query: string, refresh?: boolean): Promise<IPagedModel<IExtension>> {
|
||||
if (this.queryRequest) {
|
||||
if (this.queryRequest.query === query) {
|
||||
if (!refresh && this.queryRequest.query === query) {
|
||||
return this.queryRequest.request;
|
||||
}
|
||||
this.queryRequest.request.cancel();
|
||||
this.queryRequest = null;
|
||||
}
|
||||
|
||||
if (this.queryResult) {
|
||||
this.queryResult.disposables.dispose();
|
||||
this.queryResult = undefined;
|
||||
}
|
||||
|
||||
const parsedQuery = Query.parse(query);
|
||||
|
||||
let options: IQueryOptions = {
|
||||
@@ -203,23 +225,25 @@ export class ExtensionsListView extends ViewPane {
|
||||
case 'publishedDate': options.sortBy = SortBy.PublishedDate; break;
|
||||
}
|
||||
|
||||
const successCallback = (model: IPagedModel<IExtension>) => {
|
||||
this.queryRequest = null;
|
||||
this.setModel(model);
|
||||
return model;
|
||||
};
|
||||
|
||||
|
||||
const errorCallback = (e: any) => {
|
||||
const model = new PagedModel([]);
|
||||
if (!isPromiseCanceledError(e)) {
|
||||
this.queryRequest = null;
|
||||
this.setModel(model, e);
|
||||
const request = createCancelablePromise(async token => {
|
||||
try {
|
||||
this.queryResult = await this.query(parsedQuery, options, token);
|
||||
const model = this.queryResult.model;
|
||||
this.setModel(model);
|
||||
if (this.queryResult.onDidChangeModel) {
|
||||
this.queryResult.disposables.add(this.queryResult.onDidChangeModel(model => this.updateModel(model)));
|
||||
}
|
||||
return model;
|
||||
} catch (e) {
|
||||
const model = new PagedModel([]);
|
||||
if (!isPromiseCanceledError(e)) {
|
||||
this.setModel(model, e);
|
||||
}
|
||||
return this.list ? this.list.model : model;
|
||||
}
|
||||
return this.list ? this.list.model : model;
|
||||
};
|
||||
});
|
||||
|
||||
const request = createCancelablePromise(token => this.query(parsedQuery, options, token).then(successCallback).catch(errorCallback));
|
||||
request.finally(() => this.queryRequest = null);
|
||||
this.queryRequest = { query, request };
|
||||
return request;
|
||||
}
|
||||
@@ -250,7 +274,7 @@ export class ExtensionsListView extends ViewPane {
|
||||
getActions: () => actions.slice(0, actions.length - 1)
|
||||
});
|
||||
} else if (e.element) {
|
||||
const groups = getContextMenuActions(this.menuService, this.contextKeyService.createScoped(), this.instantiationService, e.element);
|
||||
const groups = getContextMenuActions(e.element, false, this.instantiationService);
|
||||
groups.forEach(group => group.forEach(extensionAction => {
|
||||
if (extensionAction instanceof ExtensionAction) {
|
||||
extensionAction.extension = e.element!;
|
||||
@@ -268,7 +292,7 @@ export class ExtensionsListView extends ViewPane {
|
||||
}
|
||||
}
|
||||
|
||||
private async query(query: Query, options: IQueryOptions, token: CancellationToken): Promise<IPagedModel<IExtension>> {
|
||||
private async query(query: Query, options: IQueryOptions, token: CancellationToken): Promise<IQueryResult> {
|
||||
const idRegex = /@id:(([a-z0-9A-Z][a-z0-9\-A-Z]*)\.([a-z0-9A-Z][a-z0-9\-A-Z]*))/g;
|
||||
const ids: string[] = [];
|
||||
let idMatch;
|
||||
@@ -277,21 +301,26 @@ export class ExtensionsListView extends ViewPane {
|
||||
ids.push(name);
|
||||
}
|
||||
if (ids.length) {
|
||||
return this.queryByIds(ids, options, token);
|
||||
const model = await this.queryByIds(ids, options, token);
|
||||
return { model, disposables: new DisposableStore() };
|
||||
}
|
||||
if (ExtensionsListView.isLocalExtensionsQuery(query.value) || /@builtin/.test(query.value)) {
|
||||
|
||||
if (ExtensionsListView.isLocalExtensionsQuery(query.value)) {
|
||||
return this.queryLocal(query, options);
|
||||
}
|
||||
return this.queryGallery(query, options, token)
|
||||
.then(null, e => {
|
||||
console.warn('Error querying extensions gallery', getErrorMessage(e));
|
||||
return Promise.reject(new ExtensionListViewWarning(localize('galleryError', "We cannot connect to the Extensions Marketplace at this time, please try again later.")));
|
||||
});
|
||||
|
||||
try {
|
||||
const model = await this.queryGallery(query, options, token);
|
||||
return { model, disposables: new DisposableStore() };
|
||||
} catch (e) {
|
||||
console.warn('Error querying extensions gallery', getErrorMessage(e));
|
||||
return Promise.reject(new ExtensionListViewWarning(localize('galleryError', "We cannot connect to the Extensions Marketplace at this time, please try again later.")));
|
||||
}
|
||||
}
|
||||
|
||||
private async queryByIds(ids: string[], options: IQueryOptions, token: CancellationToken): Promise<IPagedModel<IExtension>> {
|
||||
const idsSet: Set<string> = ids.reduce((result, id) => { result.add(id.toLowerCase()); return result; }, new Set<string>());
|
||||
const result = (await this.extensionsWorkbenchService.queryLocal(this.server))
|
||||
const result = (await this.extensionsWorkbenchService.queryLocal(this.options.server))
|
||||
.filter(e => idsSet.has(e.identifier.id.toLowerCase()));
|
||||
|
||||
if (result.length) {
|
||||
@@ -302,58 +331,120 @@ export class ExtensionsListView extends ViewPane {
|
||||
.then(pager => this.getPagedModel(pager));
|
||||
}
|
||||
|
||||
private async queryLocal(query: Query, options: IQueryOptions): Promise<IPagedModel<IExtension>> {
|
||||
let value = query.value;
|
||||
if (/@builtin/i.test(value)) {
|
||||
const showThemesOnly = /@builtin:themes/i.test(value);
|
||||
if (showThemesOnly) {
|
||||
value = value.replace(/@builtin:themes/g, '');
|
||||
}
|
||||
const showBasicsOnly = /@builtin:basics/i.test(value);
|
||||
if (showBasicsOnly) {
|
||||
value = value.replace(/@builtin:basics/g, '');
|
||||
}
|
||||
const showFeaturesOnly = /@builtin:features/i.test(value);
|
||||
if (showFeaturesOnly) {
|
||||
value = value.replace(/@builtin:features/g, '');
|
||||
}
|
||||
private async queryLocal(query: Query, options: IQueryOptions): Promise<IQueryResult> {
|
||||
const local = await this.extensionsWorkbenchService.queryLocal(this.options.server);
|
||||
const runningExtensions = await this.extensionService.getExtensions();
|
||||
let { extensions, canIncludeInstalledExtensions } = this.filterLocal(local, runningExtensions, query, options);
|
||||
const disposables = new DisposableStore();
|
||||
const onDidChangeModel = disposables.add(new Emitter<IPagedModel<IExtension>>());
|
||||
|
||||
value = value.replace(/@builtin/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase();
|
||||
let result = await this.extensionsWorkbenchService.queryLocal(this.server);
|
||||
|
||||
result = result
|
||||
.filter(e => e.type === ExtensionType.System && (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1));
|
||||
|
||||
const isThemeExtension = (e: IExtension): boolean => {
|
||||
return (Array.isArray(e.local?.manifest?.contributes?.themes) && e.local!.manifest!.contributes!.themes.length > 0)
|
||||
|| (Array.isArray(e.local?.manifest?.contributes?.iconThemes) && e.local!.manifest!.contributes!.iconThemes.length > 0);
|
||||
};
|
||||
if (showThemesOnly) {
|
||||
const themesExtensions = result.filter(isThemeExtension);
|
||||
return this.getPagedModel(this.sortExtensions(themesExtensions, options));
|
||||
}
|
||||
|
||||
const isLangaugeBasicExtension = (e: IExtension): boolean => {
|
||||
return FORCE_FEATURE_EXTENSIONS.indexOf(e.identifier.id) === -1
|
||||
&& (Array.isArray(e.local?.manifest?.contributes?.grammars) && e.local!.manifest!.contributes!.grammars.length > 0);
|
||||
};
|
||||
if (showBasicsOnly) {
|
||||
const basics = result.filter(isLangaugeBasicExtension);
|
||||
return this.getPagedModel(this.sortExtensions(basics, options));
|
||||
}
|
||||
if (showFeaturesOnly) {
|
||||
const others = result.filter(e => {
|
||||
return e.local
|
||||
&& e.local.manifest
|
||||
&& !isThemeExtension(e)
|
||||
&& !isLangaugeBasicExtension(e);
|
||||
});
|
||||
return this.getPagedModel(this.sortExtensions(others, options));
|
||||
}
|
||||
|
||||
return this.getPagedModel(this.sortExtensions(result, options));
|
||||
if (canIncludeInstalledExtensions) {
|
||||
let isDisposed: boolean = false;
|
||||
disposables.add(toDisposable(() => isDisposed = true));
|
||||
disposables.add(Event.debounce(Event.any(
|
||||
Event.filter(this.extensionsWorkbenchService.onChange, e => e?.state === ExtensionState.Installed),
|
||||
this.extensionService.onDidChangeExtensions
|
||||
), () => undefined)(async () => {
|
||||
const local = this.options.server ? this.extensionsWorkbenchService.installed.filter(e => e.server === this.options.server) : this.extensionsWorkbenchService.local;
|
||||
const runningExtensions = await this.extensionService.getExtensions();
|
||||
const { extensions: newExtensions } = this.filterLocal(local, runningExtensions, query, options);
|
||||
if (!isDisposed) {
|
||||
const mergedExtensions = this.mergeAddedExtensions(extensions, newExtensions);
|
||||
if (mergedExtensions) {
|
||||
extensions = mergedExtensions;
|
||||
onDidChangeModel.fire(new PagedModel(extensions));
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
return {
|
||||
model: new PagedModel(extensions),
|
||||
onDidChangeModel: onDidChangeModel.event,
|
||||
disposables
|
||||
};
|
||||
}
|
||||
|
||||
private filterLocal(local: IExtension[], runningExtensions: IExtensionDescription[], query: Query, options: IQueryOptions): { extensions: IExtension[], canIncludeInstalledExtensions: boolean } {
|
||||
let value = query.value;
|
||||
let extensions: IExtension[] = [];
|
||||
let canIncludeInstalledExtensions = true;
|
||||
|
||||
if (/@builtin/i.test(value)) {
|
||||
extensions = this.filterBuiltinExtensions(local, query, options);
|
||||
canIncludeInstalledExtensions = false;
|
||||
}
|
||||
|
||||
else if (/@installed/i.test(value)) {
|
||||
extensions = this.filterInstalledExtensions(local, runningExtensions, query, options);
|
||||
}
|
||||
|
||||
else if (/@outdated/i.test(value)) {
|
||||
extensions = this.filterOutdatedExtensions(local, query, options);
|
||||
}
|
||||
|
||||
else if (/@disabled/i.test(value)) {
|
||||
extensions = this.filterDisabledExtensions(local, runningExtensions, query, options);
|
||||
}
|
||||
|
||||
else if (/@enabled/i.test(value)) {
|
||||
extensions = this.filterEnabledExtensions(local, runningExtensions, query, options);
|
||||
}
|
||||
|
||||
return { extensions, canIncludeInstalledExtensions };
|
||||
}
|
||||
|
||||
private filterBuiltinExtensions(local: IExtension[], query: Query, options: IQueryOptions): IExtension[] {
|
||||
let value = query.value;
|
||||
const showThemesOnly = /@builtin:themes/i.test(value);
|
||||
if (showThemesOnly) {
|
||||
value = value.replace(/@builtin:themes/g, '');
|
||||
}
|
||||
const showBasicsOnly = /@builtin:basics/i.test(value);
|
||||
if (showBasicsOnly) {
|
||||
value = value.replace(/@builtin:basics/g, '');
|
||||
}
|
||||
const showFeaturesOnly = /@builtin:features/i.test(value);
|
||||
if (showFeaturesOnly) {
|
||||
value = value.replace(/@builtin:features/g, '');
|
||||
}
|
||||
|
||||
value = value.replace(/@builtin/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase();
|
||||
|
||||
let result = local
|
||||
.filter(e => e.isBuiltin && (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1));
|
||||
|
||||
const isThemeExtension = (e: IExtension): boolean => {
|
||||
return (Array.isArray(e.local?.manifest?.contributes?.themes) && e.local!.manifest!.contributes!.themes.length > 0)
|
||||
|| (Array.isArray(e.local?.manifest?.contributes?.iconThemes) && e.local!.manifest!.contributes!.iconThemes.length > 0);
|
||||
};
|
||||
if (showThemesOnly) {
|
||||
const themesExtensions = result.filter(isThemeExtension);
|
||||
return this.sortExtensions(themesExtensions, options);
|
||||
}
|
||||
|
||||
const isLangaugeBasicExtension = (e: IExtension): boolean => {
|
||||
return FORCE_FEATURE_EXTENSIONS.indexOf(e.identifier.id) === -1
|
||||
&& (Array.isArray(e.local?.manifest?.contributes?.grammars) && e.local!.manifest!.contributes!.grammars.length > 0);
|
||||
};
|
||||
if (showBasicsOnly) {
|
||||
const basics = result.filter(isLangaugeBasicExtension);
|
||||
return this.sortExtensions(basics, options);
|
||||
}
|
||||
if (showFeaturesOnly) {
|
||||
const others = result.filter(e => {
|
||||
return e.local
|
||||
&& e.local.manifest
|
||||
&& !isThemeExtension(e)
|
||||
&& !isLangaugeBasicExtension(e);
|
||||
});
|
||||
return this.sortExtensions(others, options);
|
||||
}
|
||||
|
||||
return this.sortExtensions(result, options);
|
||||
}
|
||||
|
||||
private parseCategories(value: string): { value: string, categories: string[] } {
|
||||
const categories: string[] = [];
|
||||
value = value.replace(/\bcategory:("([^"]*)"|([^"]\S*))(\s+|\b|$)/g, (_, quotedCategory, category) => {
|
||||
const entry = (category || quotedCategory || '').toLowerCase();
|
||||
@@ -362,96 +453,118 @@ export class ExtensionsListView extends ViewPane {
|
||||
}
|
||||
return '';
|
||||
});
|
||||
return { value, categories };
|
||||
}
|
||||
|
||||
if (/@installed/i.test(value)) {
|
||||
// Show installed extensions
|
||||
value = value.replace(/@installed/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase();
|
||||
private filterInstalledExtensions(local: IExtension[], runningExtensions: IExtensionDescription[], query: Query, options: IQueryOptions): IExtension[] {
|
||||
let { value, categories } = this.parseCategories(query.value);
|
||||
|
||||
let result = await this.extensionsWorkbenchService.queryLocal(this.server);
|
||||
value = value.replace(/@installed/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase();
|
||||
|
||||
result = result
|
||||
.filter(e => e.type === ExtensionType.User
|
||||
&& (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1)
|
||||
&& (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category))));
|
||||
let result = local
|
||||
.filter(e => !e.isBuiltin
|
||||
&& (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1)
|
||||
&& (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category))));
|
||||
|
||||
if (options.sortBy !== undefined) {
|
||||
result = this.sortExtensions(result, options);
|
||||
} else {
|
||||
const runningExtensions = await this.extensionService.getExtensions();
|
||||
const runningExtensionsById = runningExtensions.reduce((result, e) => { result.set(ExtensionIdentifier.toKey(e.identifier.value), e); return result; }, new Map<string, IExtensionDescription>());
|
||||
result = result.sort((e1, e2) => {
|
||||
const running1 = runningExtensionsById.get(ExtensionIdentifier.toKey(e1.identifier.id));
|
||||
const isE1Running = running1 && this.extensionManagementServerService.getExtensionManagementServer(toExtension(running1)) === e1.server;
|
||||
const running2 = runningExtensionsById.get(ExtensionIdentifier.toKey(e2.identifier.id));
|
||||
const isE2Running = running2 && this.extensionManagementServerService.getExtensionManagementServer(toExtension(running2)) === e2.server;
|
||||
if ((isE1Running && isE2Running)) {
|
||||
return e1.displayName.localeCompare(e2.displayName);
|
||||
if (options.sortBy !== undefined) {
|
||||
result = this.sortExtensions(result, options);
|
||||
} else {
|
||||
const runningExtensionsById = runningExtensions.reduce((result, e) => { result.set(ExtensionIdentifier.toKey(e.identifier.value), e); return result; }, new Map<string, IExtensionDescription>());
|
||||
result = result.sort((e1, e2) => {
|
||||
const running1 = runningExtensionsById.get(ExtensionIdentifier.toKey(e1.identifier.id));
|
||||
const isE1Running = running1 && this.extensionManagementServerService.getExtensionManagementServer(toExtension(running1)) === e1.server;
|
||||
const running2 = runningExtensionsById.get(ExtensionIdentifier.toKey(e2.identifier.id));
|
||||
const isE2Running = running2 && this.extensionManagementServerService.getExtensionManagementServer(toExtension(running2)) === e2.server;
|
||||
if ((isE1Running && isE2Running)) {
|
||||
return e1.displayName.localeCompare(e2.displayName);
|
||||
}
|
||||
const isE1LanguagePackExtension = e1.local && isLanguagePackExtension(e1.local.manifest);
|
||||
const isE2LanguagePackExtension = e2.local && isLanguagePackExtension(e2.local.manifest);
|
||||
if (!isE1Running && !isE2Running) {
|
||||
if (isE1LanguagePackExtension) {
|
||||
return -1;
|
||||
}
|
||||
const isE1LanguagePackExtension = e1.local && isLanguagePackExtension(e1.local.manifest);
|
||||
const isE2LanguagePackExtension = e2.local && isLanguagePackExtension(e2.local.manifest);
|
||||
if (!isE1Running && !isE2Running) {
|
||||
if (isE1LanguagePackExtension) {
|
||||
return -1;
|
||||
}
|
||||
if (isE2LanguagePackExtension) {
|
||||
return 1;
|
||||
}
|
||||
return e1.displayName.localeCompare(e2.displayName);
|
||||
if (isE2LanguagePackExtension) {
|
||||
return 1;
|
||||
}
|
||||
if ((isE1Running && isE2LanguagePackExtension) || (isE2Running && isE1LanguagePackExtension)) {
|
||||
return e1.displayName.localeCompare(e2.displayName);
|
||||
}
|
||||
return isE1Running ? -1 : 1;
|
||||
});
|
||||
return e1.displayName.localeCompare(e2.displayName);
|
||||
}
|
||||
if ((isE1Running && isE2LanguagePackExtension) || (isE2Running && isE1LanguagePackExtension)) {
|
||||
return e1.displayName.localeCompare(e2.displayName);
|
||||
}
|
||||
return isE1Running ? -1 : 1;
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private filterOutdatedExtensions(local: IExtension[], query: Query, options: IQueryOptions): IExtension[] {
|
||||
let { value, categories } = this.parseCategories(query.value);
|
||||
|
||||
value = value.replace(/@outdated/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase();
|
||||
|
||||
const result = local
|
||||
.sort((e1, e2) => e1.displayName.localeCompare(e2.displayName))
|
||||
.filter(extension => extension.outdated
|
||||
&& (extension.name.toLowerCase().indexOf(value) > -1 || extension.displayName.toLowerCase().indexOf(value) > -1)
|
||||
&& (!categories.length || categories.some(category => !!extension.local && extension.local.manifest.categories!.some(c => c.toLowerCase() === category))));
|
||||
|
||||
return this.sortExtensions(result, options);
|
||||
}
|
||||
|
||||
private filterDisabledExtensions(local: IExtension[], runningExtensions: IExtensionDescription[], query: Query, options: IQueryOptions): IExtension[] {
|
||||
let { value, categories } = this.parseCategories(query.value);
|
||||
|
||||
value = value.replace(/@disabled/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase();
|
||||
|
||||
const result = local
|
||||
.sort((e1, e2) => e1.displayName.localeCompare(e2.displayName))
|
||||
.filter(e => runningExtensions.every(r => !areSameExtensions({ id: r.identifier.value, uuid: r.uuid }, e.identifier))
|
||||
&& (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1)
|
||||
&& (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category))));
|
||||
|
||||
return this.sortExtensions(result, options);
|
||||
}
|
||||
|
||||
private filterEnabledExtensions(local: IExtension[], runningExtensions: IExtensionDescription[], query: Query, options: IQueryOptions): IExtension[] {
|
||||
let { value, categories } = this.parseCategories(query.value);
|
||||
|
||||
value = value ? value.replace(/@enabled/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase() : '';
|
||||
|
||||
local = local.filter(e => !e.isBuiltin);
|
||||
const result = local
|
||||
.sort((e1, e2) => e1.displayName.localeCompare(e2.displayName))
|
||||
.filter(e => runningExtensions.some(r => areSameExtensions({ id: r.identifier.value, uuid: r.uuid }, e.identifier))
|
||||
&& (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1)
|
||||
&& (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category))));
|
||||
|
||||
return this.sortExtensions(result, options);
|
||||
}
|
||||
|
||||
private mergeAddedExtensions(extensions: IExtension[], newExtensions: IExtension[]): IExtension[] | undefined {
|
||||
const oldExtensions = [...extensions];
|
||||
const findPreviousExtensionIndex = (from: number): number => {
|
||||
let index = -1;
|
||||
const previousExtensionInNew = newExtensions[from];
|
||||
if (previousExtensionInNew) {
|
||||
index = oldExtensions.findIndex(e => areSameExtensions(e.identifier, previousExtensionInNew.identifier));
|
||||
if (index === -1) {
|
||||
return findPreviousExtensionIndex(from - 1);
|
||||
}
|
||||
}
|
||||
return index;
|
||||
};
|
||||
|
||||
let hasChanged: boolean = false;
|
||||
for (let index = 0; index < newExtensions.length; index++) {
|
||||
const extension = newExtensions[index];
|
||||
if (extensions.every(r => !areSameExtensions(r.identifier, extension.identifier))) {
|
||||
hasChanged = true;
|
||||
extensions.splice(findPreviousExtensionIndex(index - 1) + 1, 0, extension);
|
||||
}
|
||||
return this.getPagedModel(result);
|
||||
}
|
||||
|
||||
|
||||
if (/@outdated/i.test(value)) {
|
||||
value = value.replace(/@outdated/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase();
|
||||
|
||||
const local = await this.extensionsWorkbenchService.queryLocal(this.server);
|
||||
const result = local
|
||||
.sort((e1, e2) => e1.displayName.localeCompare(e2.displayName))
|
||||
.filter(extension => extension.outdated
|
||||
&& (extension.name.toLowerCase().indexOf(value) > -1 || extension.displayName.toLowerCase().indexOf(value) > -1)
|
||||
&& (!categories.length || categories.some(category => !!extension.local && extension.local.manifest.categories!.some(c => c.toLowerCase() === category))));
|
||||
|
||||
return this.getPagedModel(this.sortExtensions(result, options));
|
||||
}
|
||||
|
||||
if (/@disabled/i.test(value)) {
|
||||
value = value.replace(/@disabled/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase();
|
||||
|
||||
const local = await this.extensionsWorkbenchService.queryLocal(this.server);
|
||||
const runningExtensions = await this.extensionService.getExtensions();
|
||||
|
||||
const result = local
|
||||
.sort((e1, e2) => e1.displayName.localeCompare(e2.displayName))
|
||||
.filter(e => runningExtensions.every(r => !areSameExtensions({ id: r.identifier.value, uuid: r.uuid }, e.identifier))
|
||||
&& (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1)
|
||||
&& (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category))));
|
||||
|
||||
return this.getPagedModel(this.sortExtensions(result, options));
|
||||
}
|
||||
|
||||
if (/@enabled/i.test(value)) {
|
||||
value = value ? value.replace(/@enabled/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase() : '';
|
||||
|
||||
const local = (await this.extensionsWorkbenchService.queryLocal(this.server)).filter(e => e.type === ExtensionType.User);
|
||||
const runningExtensions = await this.extensionService.getExtensions();
|
||||
|
||||
const result = local
|
||||
.sort((e1, e2) => e1.displayName.localeCompare(e2.displayName))
|
||||
.filter(e => runningExtensions.some(r => areSameExtensions({ id: r.identifier.value, uuid: r.uuid }, e.identifier))
|
||||
&& (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1)
|
||||
&& (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category))));
|
||||
|
||||
return this.getPagedModel(this.sortExtensions(result, options));
|
||||
}
|
||||
|
||||
return new PagedModel([]);
|
||||
return hasChanged ? extensions : undefined;
|
||||
}
|
||||
|
||||
private async queryGallery(query: Query, options: IQueryOptions, token: CancellationToken): Promise<IPagedModel<IExtension>> {
|
||||
@@ -601,8 +714,8 @@ export class ExtensionsListView extends ViewPane {
|
||||
|
||||
// sort the marketplace extensions
|
||||
pager.firstPage.sort((a, b) => {
|
||||
let isRecommendedA: boolean = recommmended.findIndex(ext => ext.extensionId === `${a.publisher}.${a.name}`) > -1;
|
||||
let isRecommendedB: boolean = recommmended.findIndex(ext => ext.extensionId === `${b.publisher}.${b.name}`) > -1;
|
||||
let isRecommendedA: boolean = recommmended.findIndex(extensionId => extensionId === `${a.publisher}.${a.name}`) > -1;
|
||||
let isRecommendedB: boolean = recommmended.findIndex(extensionId => extensionId === `${b.publisher}.${b.name}`) > -1;
|
||||
|
||||
// sort recommeded extensions before other extensions
|
||||
if (isRecommendedA !== isRecommendedB) {
|
||||
@@ -674,11 +787,10 @@ export class ExtensionsListView extends ViewPane {
|
||||
return new PagedModel([]);
|
||||
}
|
||||
|
||||
protected async getInstallableRecommendations(recommendations: IExtensionRecommendation[], options: IQueryOptions, token: CancellationToken): Promise<IExtension[]> {
|
||||
protected async getInstallableRecommendations(recommendations: string[], options: IQueryOptions, token: CancellationToken): Promise<IExtension[]> {
|
||||
const extensions: IExtension[] = [];
|
||||
if (recommendations.length) {
|
||||
const names = recommendations.map(({ extensionId }) => extensionId);
|
||||
const pager = await this.extensionsWorkbenchService.queryGallery({ ...options, names, pageSize: names.length }, token);
|
||||
const pager = await this.extensionsWorkbenchService.queryGallery({ ...options, names: recommendations, pageSize: recommendations.length }, token);
|
||||
for (const extension of pager.firstPage) {
|
||||
if (extension.gallery && (await this.extensionManagementService.canInstall(extension.gallery))) {
|
||||
extensions.push(extension);
|
||||
@@ -688,11 +800,11 @@ export class ExtensionsListView extends ViewPane {
|
||||
return extensions;
|
||||
}
|
||||
|
||||
protected async getWorkspaceRecommendations(): Promise<IExtensionRecommendation[]> {
|
||||
protected async getWorkspaceRecommendations(): Promise<string[]> {
|
||||
const recommendations = await this.extensionRecommendationsService.getWorkspaceRecommendations();
|
||||
const { important } = await this.extensionRecommendationsService.getConfigBasedRecommendations();
|
||||
for (const configBasedRecommendation of important) {
|
||||
if (!recommendations.find(r => r.extensionId === configBasedRecommendation.extensionId)) {
|
||||
if (!recommendations.find(extensionId => extensionId === configBasedRecommendation)) {
|
||||
recommendations.push(configBasedRecommendation);
|
||||
}
|
||||
}
|
||||
@@ -705,7 +817,7 @@ export class ExtensionsListView extends ViewPane {
|
||||
const installableRecommendations = (await this.getInstallableRecommendations(recommendations, { ...options, source: 'recommendations-workspace' }, token))
|
||||
.filter(extension => extension.identifier.id.toLowerCase().indexOf(value) > -1);
|
||||
this.telemetryService.publicLog2<{ count: number }, WorkspaceRecommendationsClassification>('extensionWorkspaceRecommendations:open', { count: installableRecommendations.length });
|
||||
const result: IExtension[] = coalesce(recommendations.map(({ extensionId: id }) => installableRecommendations.find(i => areSameExtensions(i.identifier, { id }))));
|
||||
const result: IExtension[] = coalesce(recommendations.map(id => installableRecommendations.find(i => areSameExtensions(i.identifier, { id }))));
|
||||
return new PagedModel(result);
|
||||
}
|
||||
|
||||
@@ -727,11 +839,10 @@ export class ExtensionsListView extends ViewPane {
|
||||
private async getOtherRecommendationsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise<IPagedModel<IExtension>> {
|
||||
const value = query.value.replace(/@recommended/g, '').trim().toLowerCase();
|
||||
|
||||
const local = (await this.extensionsWorkbenchService.queryLocal(this.server))
|
||||
.filter(e => e.type === ExtensionType.User)
|
||||
const local = (await this.extensionsWorkbenchService.queryLocal(this.options.server))
|
||||
.map(e => e.identifier.id.toLowerCase());
|
||||
const workspaceRecommendations = (await this.getWorkspaceRecommendations())
|
||||
.map(r => r.extensionId.toLowerCase());
|
||||
.map(extensionId => extensionId.toLowerCase());
|
||||
|
||||
const otherRecommendations = distinct(
|
||||
flatten(await Promise.all([
|
||||
@@ -739,21 +850,19 @@ export class ExtensionsListView extends ViewPane {
|
||||
this.extensionRecommendationsService.getImportantRecommendations(),
|
||||
this.extensionRecommendationsService.getFileBasedRecommendations(),
|
||||
this.extensionRecommendationsService.getOtherRecommendations()
|
||||
])).filter(({ extensionId }) => !local.includes(extensionId.toLowerCase()) && !workspaceRecommendations.includes(extensionId.toLowerCase())
|
||||
), r => r.extensionId.toLowerCase());
|
||||
])).filter(extensionId => !local.includes(extensionId.toLowerCase()) && !workspaceRecommendations.includes(extensionId.toLowerCase())
|
||||
), extensionId => extensionId.toLowerCase());
|
||||
|
||||
const installableRecommendations = (await this.getInstallableRecommendations(otherRecommendations, { ...options, source: 'recommendations-other', sortBy: undefined }, token))
|
||||
.filter(extension => extension.identifier.id.toLowerCase().indexOf(value) > -1);
|
||||
|
||||
const result: IExtension[] = coalesce(otherRecommendations.map(({ extensionId: id }) => installableRecommendations.find(i => areSameExtensions(i.identifier, { id }))));
|
||||
const result: IExtension[] = coalesce(otherRecommendations.map(id => installableRecommendations.find(i => areSameExtensions(i.identifier, { id }))));
|
||||
return new PagedModel(result);
|
||||
}
|
||||
|
||||
// Get All types of recommendations, trimmed to show a max of 8 at any given time
|
||||
private async getAllRecommendationsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise<IPagedModel<IExtension>> {
|
||||
const local = (await this.extensionsWorkbenchService.queryLocal(this.server))
|
||||
.filter(e => e.type === ExtensionType.User)
|
||||
.map(e => e.identifier.id.toLowerCase());
|
||||
const local = (await this.extensionsWorkbenchService.queryLocal(this.options.server)).map(e => e.identifier.id.toLowerCase());
|
||||
|
||||
const allRecommendations = distinct(
|
||||
flatten(await Promise.all([
|
||||
@@ -762,11 +871,11 @@ export class ExtensionsListView extends ViewPane {
|
||||
this.extensionRecommendationsService.getImportantRecommendations(),
|
||||
this.extensionRecommendationsService.getFileBasedRecommendations(),
|
||||
this.extensionRecommendationsService.getOtherRecommendations()
|
||||
])).filter(({ extensionId }) => !local.includes(extensionId.toLowerCase())
|
||||
), r => r.extensionId.toLowerCase());
|
||||
])).filter(extensionId => !local.includes(extensionId.toLowerCase())
|
||||
), extensionId => extensionId.toLowerCase());
|
||||
|
||||
const installableRecommendations = await this.getInstallableRecommendations(allRecommendations, { ...options, source: 'recommendations-all', sortBy: undefined }, token);
|
||||
const result: IExtension[] = coalesce(allRecommendations.map(({ extensionId: id }) => installableRecommendations.find(i => areSameExtensions(i.identifier, { id }))));
|
||||
const result: IExtension[] = coalesce(allRecommendations.map(id => installableRecommendations.find(i => areSameExtensions(i.identifier, { id }))));
|
||||
return new PagedModel(result.slice(0, 8));
|
||||
}
|
||||
|
||||
@@ -782,31 +891,51 @@ export class ExtensionsListView extends ViewPane {
|
||||
if (this.list) {
|
||||
this.list.model = new DelayedPagedModel(model);
|
||||
this.list.scrollTop = 0;
|
||||
const count = this.count();
|
||||
this.updateBody(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.bodyTemplate && this.badge) {
|
||||
private updateBody(error?: any): void {
|
||||
const count = this.count();
|
||||
if (this.bodyTemplate && this.badge) {
|
||||
|
||||
this.bodyTemplate.extensionsList.classList.toggle('hidden', count === 0);
|
||||
this.bodyTemplate.messageContainer.classList.toggle('hidden', count > 0);
|
||||
this.badge.setCount(count);
|
||||
this.bodyTemplate.extensionsList.classList.toggle('hidden', count === 0);
|
||||
this.bodyTemplate.messageContainer.classList.toggle('hidden', count > 0);
|
||||
this.badge.setCount(count);
|
||||
|
||||
if (count === 0 && this.isBodyVisible()) {
|
||||
if (error) {
|
||||
if (error instanceof ExtensionListViewWarning) {
|
||||
this.bodyTemplate.messageSeverityIcon.className = `codicon ${SeverityIcon.className(Severity.Warning)}`;
|
||||
this.bodyTemplate.messageBox.textContent = getErrorMessage(error);
|
||||
} else {
|
||||
this.bodyTemplate.messageSeverityIcon.className = `codicon ${SeverityIcon.className(Severity.Error)}`;
|
||||
this.bodyTemplate.messageBox.textContent = localize('error', "Error while loading extensions. {0}", getErrorMessage(error));
|
||||
}
|
||||
if (count === 0 && this.isBodyVisible()) {
|
||||
if (error) {
|
||||
if (error instanceof ExtensionListViewWarning) {
|
||||
this.bodyTemplate.messageSeverityIcon.className = SeverityIcon.className(Severity.Warning);
|
||||
this.bodyTemplate.messageBox.textContent = getErrorMessage(error);
|
||||
} else {
|
||||
this.bodyTemplate.messageSeverityIcon.className = '';
|
||||
this.bodyTemplate.messageBox.textContent = localize('no extensions found', "No extensions found.");
|
||||
this.bodyTemplate.messageSeverityIcon.className = SeverityIcon.className(Severity.Error);
|
||||
this.bodyTemplate.messageBox.textContent = localize('error', "Error while loading extensions. {0}", getErrorMessage(error));
|
||||
}
|
||||
alert(this.bodyTemplate.messageBox.textContent);
|
||||
} else {
|
||||
this.bodyTemplate.messageSeverityIcon.className = '';
|
||||
this.bodyTemplate.messageBox.textContent = localize('no extensions found', "No extensions found.");
|
||||
}
|
||||
alert(this.bodyTemplate.messageBox.textContent);
|
||||
}
|
||||
}
|
||||
this.updateSize();
|
||||
}
|
||||
|
||||
protected updateSize() {
|
||||
if (this.options.fixedHeight) {
|
||||
const length = this.list?.model.length || 0;
|
||||
this.minimumBodySize = Math.min(length, 3) * EXTENSION_LIST_ELEMENT_HEIGHT;
|
||||
this.maximumBodySize = length * EXTENSION_LIST_ELEMENT_HEIGHT;
|
||||
this.storageService.store(this.id, this.maximumBodySize, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
}
|
||||
|
||||
private updateModel(model: IPagedModel<IExtension>) {
|
||||
if (this.list) {
|
||||
this.list.model = new DelayedPagedModel(model);
|
||||
this.updateBody();
|
||||
}
|
||||
}
|
||||
|
||||
private openExtension(extension: IExtension, options: { sideByside?: boolean, preserveFocus?: boolean, pinned?: boolean }): void {
|
||||
@@ -854,6 +983,10 @@ export class ExtensionsListView extends ViewPane {
|
||||
this.queryRequest.request.cancel();
|
||||
this.queryRequest = null;
|
||||
}
|
||||
if (this.queryResult) {
|
||||
this.queryResult.disposables.dispose();
|
||||
this.queryResult = undefined;
|
||||
}
|
||||
this.list = null;
|
||||
}
|
||||
|
||||
@@ -863,7 +996,8 @@ export class ExtensionsListView extends ViewPane {
|
||||
|| this.isEnabledExtensionsQuery(query)
|
||||
|| this.isDisabledExtensionsQuery(query)
|
||||
|| this.isBuiltInExtensionsQuery(query)
|
||||
|| this.isSearchBuiltInExtensionsQuery(query);
|
||||
|| this.isSearchBuiltInExtensionsQuery(query)
|
||||
|| this.isBuiltInGroupExtensionsQuery(query);
|
||||
}
|
||||
|
||||
static isSearchBuiltInExtensionsQuery(query: string): boolean {
|
||||
@@ -871,7 +1005,11 @@ export class ExtensionsListView extends ViewPane {
|
||||
}
|
||||
|
||||
static isBuiltInExtensionsQuery(query: string): boolean {
|
||||
return /@builtin$/i.test(query.trim());
|
||||
return /^\s*@builtin$/i.test(query.trim());
|
||||
}
|
||||
|
||||
static isBuiltInGroupExtensionsQuery(query: string): boolean {
|
||||
return /^\s*@builtin:.+$/i.test(query.trim());
|
||||
}
|
||||
|
||||
static isInstalledExtensionsQuery(query: string): boolean {
|
||||
@@ -928,39 +1066,7 @@ export class ExtensionsListView extends ViewPane {
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerExtensionsView extends ExtensionsListView {
|
||||
|
||||
constructor(
|
||||
server: IExtensionManagementServer,
|
||||
onDidChangeTitle: Event<string>,
|
||||
options: ExtensionsListViewOptions,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IExtensionRecommendationsService tipsService: IExtensionRecommendationsService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IExperimentService experimentService: IExperimentService,
|
||||
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService,
|
||||
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
|
||||
@IProductService productService: IProductService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IMenuService menuService: IMenuService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IPreferencesService preferencesService: IPreferencesService,
|
||||
) {
|
||||
options.server = server;
|
||||
super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, tipsService,
|
||||
telemetryService, configurationService, contextService, experimentService, extensionManagementServerService, extensionManagementService, productService,
|
||||
contextKeyService, viewDescriptorService, menuService, openerService, preferencesService);
|
||||
this._register(onDidChangeTitle(title => this.updateTitle(title)));
|
||||
}
|
||||
export class ServerInstalledExtensionsView extends ExtensionsListView {
|
||||
|
||||
async show(query: string): Promise<IPagedModel<IExtension>> {
|
||||
query = query ? query : '@installed';
|
||||
@@ -971,9 +1077,9 @@ export class ServerExtensionsView extends ExtensionsListView {
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
if (this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManagementServerService.localExtensionManagementServer === this.server) {
|
||||
if (this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManagementServerService.localExtensionManagementServer === this.options.server) {
|
||||
const installLocalExtensionsInRemoteAction = this._register(this.instantiationService.createInstance(InstallLocalExtensionsInRemoteAction));
|
||||
installLocalExtensionsInRemoteAction.class = 'codicon codicon-cloud-download';
|
||||
installLocalExtensionsInRemoteAction.class = ThemeIcon.asClassName(installLocalInRemoteIcon);
|
||||
return [installLocalExtensionsInRemoteAction];
|
||||
}
|
||||
return [];
|
||||
@@ -996,28 +1102,6 @@ export class DisabledExtensionsView extends ExtensionsListView {
|
||||
}
|
||||
}
|
||||
|
||||
export class OutdatedExtensionsView extends ExtensionsListView {
|
||||
|
||||
async show(query: string): Promise<IPagedModel<IExtension>> {
|
||||
query = query || '@outdated';
|
||||
return ExtensionsListView.isOutdatedExtensionsQuery(query) ? super.show(query) : this.showEmptyModel();
|
||||
}
|
||||
}
|
||||
|
||||
export class InstalledExtensionsView extends ExtensionsListView {
|
||||
|
||||
async show(query: string): Promise<IPagedModel<IExtension>> {
|
||||
query = query || '@installed';
|
||||
return ExtensionsListView.isInstalledExtensionsQuery(query) ? super.show(query) : this.showEmptyModel();
|
||||
}
|
||||
}
|
||||
|
||||
export class SearchBuiltInExtensionsView extends ExtensionsListView {
|
||||
async show(query: string): Promise<IPagedModel<IExtension>> {
|
||||
return ExtensionsListView.isSearchBuiltInExtensionsQuery(query) ? super.show(query) : this.showEmptyModel();
|
||||
}
|
||||
}
|
||||
|
||||
export class BuiltInFeatureExtensionsView extends ExtensionsListView {
|
||||
async show(query: string): Promise<IPagedModel<IExtension>> {
|
||||
return (query && query.trim() !== '@builtin') ? this.showEmptyModel() : super.show('@builtin:features');
|
||||
@@ -1043,7 +1127,7 @@ export class DefaultRecommendedExtensionsView extends ExtensionsListView {
|
||||
renderBody(container: HTMLElement): void {
|
||||
super.renderBody(container);
|
||||
|
||||
this._register(this.extensionRecommendationsService.onRecommendationChange(() => {
|
||||
this._register(this.extensionRecommendationsService.onDidChangeRecommendations(() => {
|
||||
this.show('');
|
||||
}));
|
||||
}
|
||||
@@ -1053,7 +1137,7 @@ export class DefaultRecommendedExtensionsView extends ExtensionsListView {
|
||||
return this.showEmptyModel();
|
||||
}
|
||||
const model = await super.show(this.recommendedExtensionsQuery);
|
||||
if (!this.extensionsWorkbenchService.local.some(e => e.type === ExtensionType.User)) {
|
||||
if (!this.extensionsWorkbenchService.local.some(e => !e.isBuiltin)) {
|
||||
// This is part of popular extensions view. Collapse if no installed extensions.
|
||||
this.setExpanded(model.length > 0);
|
||||
}
|
||||
@@ -1068,7 +1152,7 @@ export class RecommendedExtensionsView extends ExtensionsListView {
|
||||
renderBody(container: HTMLElement): void {
|
||||
super.renderBody(container);
|
||||
|
||||
this._register(this.extensionRecommendationsService.onRecommendationChange(() => {
|
||||
this._register(this.extensionRecommendationsService.onDidChangeRecommendations(() => {
|
||||
this.show('');
|
||||
}));
|
||||
}
|
||||
@@ -1086,17 +1170,17 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView {
|
||||
renderBody(container: HTMLElement): void {
|
||||
super.renderBody(container);
|
||||
|
||||
this._register(this.extensionRecommendationsService.onRecommendationChange(() => this.show(this.recommendedExtensionsQuery)));
|
||||
this._register(this.extensionRecommendationsService.onDidChangeRecommendations(() => this.show(this.recommendedExtensionsQuery)));
|
||||
this._register(this.contextService.onDidChangeWorkbenchState(() => this.show(this.recommendedExtensionsQuery)));
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
if (!this.installAllAction) {
|
||||
this.installAllAction = this._register(new Action('workbench.extensions.action.installWorkspaceRecommendedExtensions', localize('installWorkspaceRecommendedExtensions', "Install Workspace Recommended Extensions"), 'codicon codicon-cloud-download', false, () => this.installWorkspaceRecommendations()));
|
||||
this.installAllAction = this._register(new Action('workbench.extensions.action.installWorkspaceRecommendedExtensions', localize('installWorkspaceRecommendedExtensions', "Install Workspace Recommended Extensions"), ThemeIcon.asClassName(installWorkspaceRecommendedIcon), false, () => this.installWorkspaceRecommendations()));
|
||||
}
|
||||
|
||||
const configureWorkspaceFolderAction = this._register(this.instantiationService.createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL));
|
||||
configureWorkspaceFolderAction.class = 'codicon codicon-pencil';
|
||||
configureWorkspaceFolderAction.class = ThemeIcon.asClassName(configureRecommendedIcon);
|
||||
return [this.installAllAction, configureWorkspaceFolderAction];
|
||||
}
|
||||
|
||||
@@ -1119,13 +1203,13 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView {
|
||||
const installed = (await this.extensionsWorkbenchService.queryLocal())
|
||||
.filter(l => l.enablementState !== EnablementState.DisabledByExtensionKind); // Filter extensions disabled by kind
|
||||
const recommendations = (await this.getWorkspaceRecommendations())
|
||||
.filter(({ extensionId }) => installed.every(local => !areSameExtensions({ id: extensionId }, local.identifier)));
|
||||
.filter(extensionId => installed.every(local => !areSameExtensions({ id: extensionId }, local.identifier)));
|
||||
return this.getInstallableRecommendations(recommendations, { source: 'install-all-workspace-recommendations' }, CancellationToken.None);
|
||||
}
|
||||
|
||||
private async installWorkspaceRecommendations(): Promise<void> {
|
||||
const installableRecommendations = await this.getInstallableWorkspaceRecommendations();
|
||||
await Promise.all(installableRecommendations.map(extension => this.extensionManagementService.installFromGallery(extension.gallery!)));
|
||||
await this.extensionManagementService.installExtensions(installableRecommendations.map(i => i.gallery!));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,18 +5,22 @@
|
||||
|
||||
import 'vs/css!./media/extensionsWidgets';
|
||||
import { Disposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IExtension, IExtensionsWorkbenchService, IExtensionContainer } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IExtension, IExtensionsWorkbenchService, IExtensionContainer, ExtensionState } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { append, $ } from 'vs/base/browser/dom';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IExtensionRecommendationsService, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { extensionButtonProminentBackground, extensionButtonProminentForeground, ExtensionToolTipAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService';
|
||||
import { IThemeService, IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { EXTENSION_BADGE_REMOTE_BACKGROUND, EXTENSION_BADGE_REMOTE_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { installCountIcon, ratingIcon, remoteIcon, starEmptyIcon, starFullIcon, starHalfIcon, syncIgnoredIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons';
|
||||
|
||||
export abstract class ExtensionWidget extends Disposable implements IExtensionContainer {
|
||||
private _extension: IExtension | null = null;
|
||||
@@ -82,7 +86,7 @@ export class InstallCountWidget extends ExtensionWidget {
|
||||
installLabel = installCount.toLocaleString(platform.locale);
|
||||
}
|
||||
|
||||
append(this.container, $('span.codicon.codicon-cloud-download'));
|
||||
append(this.container, $('span' + ThemeIcon.asCSSSelector(installCountIcon)));
|
||||
const count = append(this.container, $('span.count'));
|
||||
count.textContent = installLabel;
|
||||
}
|
||||
@@ -122,18 +126,18 @@ export class RatingsWidget extends ExtensionWidget {
|
||||
const rating = Math.round(this.extension.rating * 2) / 2;
|
||||
|
||||
if (this.small) {
|
||||
append(this.container, $('span.codicon.codicon-star-full'));
|
||||
append(this.container, $('span' + ThemeIcon.asCSSSelector(starFullIcon)));
|
||||
|
||||
const count = append(this.container, $('span.count'));
|
||||
count.textContent = String(rating);
|
||||
} else {
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
if (rating >= i) {
|
||||
append(this.container, $('span.codicon.codicon-star-full'));
|
||||
append(this.container, $('span' + ThemeIcon.asCSSSelector(starFullIcon)));
|
||||
} else if (rating >= i - 0.5) {
|
||||
append(this.container, $('span.codicon.codicon-star-half'));
|
||||
append(this.container, $('span' + ThemeIcon.asCSSSelector(starHalfIcon)));
|
||||
} else {
|
||||
append(this.container, $('span.codicon.codicon-star-empty'));
|
||||
append(this.container, $('span' + ThemeIcon.asCSSSelector(starEmptyIcon)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,7 +202,7 @@ export class RecommendationWidget extends ExtensionWidget {
|
||||
super();
|
||||
this.render();
|
||||
this._register(toDisposable(() => this.clear()));
|
||||
this._register(this.extensionRecommendationsService.onRecommendationChange(() => this.render()));
|
||||
this._register(this.extensionRecommendationsService.onDidChangeRecommendations(() => this.render()));
|
||||
}
|
||||
|
||||
private clear(): void {
|
||||
@@ -219,7 +223,7 @@ export class RecommendationWidget extends ExtensionWidget {
|
||||
if (extRecommendations[this.extension.identifier.id.toLowerCase()]) {
|
||||
this.element = append(this.parent, $('div.extension-bookmark'));
|
||||
const recommendation = append(this.element, $('.recommendation'));
|
||||
append(recommendation, $('span.codicon.codicon-star'));
|
||||
append(recommendation, $('span' + ThemeIcon.asCSSSelector(ratingIcon)));
|
||||
const applyBookmarkStyle = (theme: IColorTheme) => {
|
||||
const bgColor = theme.getColor(extensionButtonProminentBackground);
|
||||
const fgColor = theme.getColor(extensionButtonProminentForeground);
|
||||
@@ -285,7 +289,7 @@ class RemoteBadge extends Disposable {
|
||||
}
|
||||
|
||||
private render(): void {
|
||||
append(this.element, $('span.codicon.codicon-remote'));
|
||||
append(this.element, $('span' + ThemeIcon.asCSSSelector(remoteIcon)));
|
||||
|
||||
const applyBadgeStyle = () => {
|
||||
if (!this.element) {
|
||||
@@ -339,3 +343,28 @@ export class ExtensionPackCountWidget extends ExtensionWidget {
|
||||
countBadge.setCount(this.extension.extensionPack.length);
|
||||
}
|
||||
}
|
||||
|
||||
export class SyncIgnoredWidget extends ExtensionWidget {
|
||||
|
||||
private element: HTMLElement;
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IUserDataAutoSyncEnablementService private readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService,
|
||||
) {
|
||||
super();
|
||||
this.element = append(container, $('span.extension-sync-ignored' + ThemeIcon.asCSSSelector(syncIgnoredIcon)));
|
||||
this.element.title = localize('syncingore.label', "This extension is ignored during sync.");
|
||||
this.element.classList.add(...ThemeIcon.asClassNameArray(syncIgnoredIcon));
|
||||
this.element.classList.add('hide');
|
||||
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectedKeys.includes('settingsSync.ignoredExtensions'))(() => this.render()));
|
||||
this._register(userDataAutoSyncEnablementService.onDidChangeEnablement(() => this.update()));
|
||||
this.render();
|
||||
}
|
||||
|
||||
render(): void {
|
||||
this.element.classList.toggle('hide', !(this.extension && this.extension.state === ExtensionState.Installed && this.userDataAutoSyncEnablementService.isEnabled() && this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,23 +4,23 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as semver from 'semver-umd';
|
||||
import * as semver from 'vs/base/common/semver/semver';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { index, distinct } from 'vs/base/common/arrays';
|
||||
import { ThrottledDelayer } from 'vs/base/common/async';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { canceled, isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
// {{SQL CARBON EDIT}}
|
||||
import {
|
||||
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions,
|
||||
InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, IExtensionIdentifier, InstallOperation, DefaultIconPath
|
||||
InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, IExtensionIdentifier, InstallOperation, DefaultIconPath, InstallOptions
|
||||
} from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensioManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet, groupByExtension, ExtensionIdentifierWithVersion, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
@@ -29,18 +29,18 @@ import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/commo
|
||||
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension, isLanguagePackExtension, ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/platform/extensions/common/extensions'; // {{SQL CARBON EDIT}}
|
||||
import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension, ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/platform/extensions/common/extensions'; // {{SQL CARBON EDIT}}
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { asDomUri } from 'vs/base/browser/dom';
|
||||
import { getIgnoredExtensions } from 'vs/platform/userDataSync/common/extensionsMerge';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions';
|
||||
import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
|
||||
import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; // {{SQL CARBON EDIT}}
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener'; // {{SQL CARBON EDIT}}
|
||||
@@ -69,6 +69,10 @@ class Extension implements IExtension {
|
||||
return this.local ? this.local.type : ExtensionType.User;
|
||||
}
|
||||
|
||||
get isBuiltin(): boolean {
|
||||
return this.local ? this.local.isBuiltin : false;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this.gallery ? this.gallery.name : this.local!.manifest.name;
|
||||
}
|
||||
@@ -151,7 +155,7 @@ class Extension implements IExtension {
|
||||
|
||||
private get localIconUrl(): string | null {
|
||||
if (this.local && this.local.manifest.icon) {
|
||||
return asDomUri(resources.joinPath(this.local.location, this.local.manifest.icon)).toString(true);
|
||||
return FileAccess.asBrowserUri(resources.joinPath(this.local.location, this.local.manifest.icon)).toString(true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -168,10 +172,10 @@ class Extension implements IExtension {
|
||||
if (this.type === ExtensionType.System && this.local) {
|
||||
if (this.local.manifest && this.local.manifest.contributes) {
|
||||
if (Array.isArray(this.local.manifest.contributes.themes) && this.local.manifest.contributes.themes.length) {
|
||||
return require.toUrl('./media/theme-icon.png');
|
||||
return FileAccess.asBrowserUri('./media/theme-icon.png', require).toString(true);
|
||||
}
|
||||
if (Array.isArray(this.local.manifest.contributes.grammars) && this.local.manifest.contributes.grammars.length) {
|
||||
return require.toUrl('./media/language-icon.svg');
|
||||
return FileAccess.asBrowserUri('./media/language-icon.svg', require).toString(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,12 +226,12 @@ class Extension implements IExtension {
|
||||
return this.gallery ? this.gallery.preview : false;
|
||||
}
|
||||
|
||||
private isGalleryOutdated(): boolean {
|
||||
return this.local && this.gallery ? semver.gt(this.local.manifest.version, this.gallery.version) : false;
|
||||
}
|
||||
|
||||
getManifest(token: CancellationToken): Promise<IExtensionManifest | null> {
|
||||
if (this.gallery && !this.isGalleryOutdated()) {
|
||||
if (this.local && !this.outdated) {
|
||||
return Promise.resolve(this.local.manifest);
|
||||
}
|
||||
|
||||
if (this.gallery) {
|
||||
if (this.gallery.assets.manifest) {
|
||||
return this.galleryService.getManifest(this.gallery, token);
|
||||
}
|
||||
@@ -235,19 +239,15 @@ class Extension implements IExtension {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
if (this.local) {
|
||||
return Promise.resolve(this.local.manifest);
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
hasReadme(): boolean {
|
||||
if (this.gallery && !this.isGalleryOutdated() && this.gallery.assets.readme) {
|
||||
if (this.local && this.local.readmeUrl) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.local && this.local.readmeUrl) {
|
||||
if (this.gallery && this.gallery.assets.readme) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -255,17 +255,17 @@ class Extension implements IExtension {
|
||||
}
|
||||
|
||||
getReadme(token: CancellationToken): Promise<string> {
|
||||
if (this.gallery && !this.isGalleryOutdated()) {
|
||||
if (this.local && this.local.readmeUrl && !this.outdated) {
|
||||
return this.fileService.readFile(this.local.readmeUrl).then(content => content.value.toString());
|
||||
}
|
||||
|
||||
if (this.gallery) {
|
||||
if (this.gallery.assets.readme) {
|
||||
return this.galleryService.getReadme(this.gallery, token);
|
||||
}
|
||||
this.telemetryService.publicLog('extensions:NotFoundReadMe', this.telemetryData);
|
||||
}
|
||||
|
||||
if (this.local && this.local.readmeUrl) {
|
||||
return this.fileService.readFile(this.local.readmeUrl).then(content => content.value.toString());
|
||||
}
|
||||
|
||||
if (this.type === ExtensionType.System) {
|
||||
// {{SQL CARBON EDIT}}
|
||||
return Promise.resolve(`# ${this.displayName || this.name}
|
||||
@@ -279,11 +279,11 @@ ${this.description}
|
||||
}
|
||||
|
||||
hasChangelog(): boolean {
|
||||
if (this.gallery && this.gallery.assets.changelog && !this.isGalleryOutdated()) {
|
||||
if (this.local && this.local.changelogUrl) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.local && this.local.changelogUrl) {
|
||||
if (this.gallery && this.gallery.assets.changelog) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -291,7 +291,12 @@ ${this.description}
|
||||
}
|
||||
|
||||
getChangelog(token: CancellationToken): Promise<string> {
|
||||
if (this.gallery && this.gallery.assets.changelog && !this.isGalleryOutdated()) {
|
||||
|
||||
if (this.local && this.local.changelogUrl && !this.outdated) {
|
||||
return this.fileService.readFile(this.local.changelogUrl).then(content => content.value.toString());
|
||||
}
|
||||
|
||||
if (this.gallery && this.gallery.assets.changelog) {
|
||||
return this.galleryService.getChangelog(this.gallery, token);
|
||||
}
|
||||
|
||||
@@ -311,23 +316,23 @@ ${this.description}
|
||||
|
||||
get dependencies(): string[] {
|
||||
const { local, gallery } = this;
|
||||
if (gallery && !this.isGalleryOutdated()) {
|
||||
return gallery.properties.dependencies || [];
|
||||
}
|
||||
if (local && local.manifest.extensionDependencies) {
|
||||
if (local && local.manifest.extensionDependencies && !this.outdated) {
|
||||
return local.manifest.extensionDependencies;
|
||||
}
|
||||
if (gallery) {
|
||||
return gallery.properties.dependencies || [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
get extensionPack(): string[] {
|
||||
const { local, gallery } = this;
|
||||
if (gallery && !this.isGalleryOutdated()) {
|
||||
return gallery.properties.extensionPack || [];
|
||||
}
|
||||
if (local && local.manifest.extensionPack) {
|
||||
if (local && local.manifest.extensionPack && !this.outdated) {
|
||||
return local.manifest.extensionPack;
|
||||
}
|
||||
if (gallery) {
|
||||
return gallery.properties.extensionPack || [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -526,7 +531,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IExtensionManagementService private readonly extensionService: IExtensionManagementService,
|
||||
@IWorkbenchExtensioManagementService private readonly extensionManagementService: IWorkbenchExtensioManagementService,
|
||||
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@@ -538,8 +543,11 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IModeService private readonly modeService: IModeService,
|
||||
@IIgnoredExtensionsManagementService private readonly extensionsSyncManagementService: IIgnoredExtensionsManagementService,
|
||||
@IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IOpenerService private readonly openerService: IOpenerService // {{SQL CARBON EDIT}}
|
||||
@IOpenerService private readonly openerService: IOpenerService, // {{SQL CARBON EDIT}}
|
||||
@IExtensionManagementService private readonly extensionService: IExtensionManagementService
|
||||
) {
|
||||
super();
|
||||
if (extensionManagementServerService.localExtensionManagementServer) {
|
||||
@@ -646,7 +654,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||
const options: IQueryOptions = CancellationToken.isCancellationToken(arg1) ? {} : arg1;
|
||||
const token: CancellationToken = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2;
|
||||
options.text = options.text ? this.resolveQueryText(options.text) : options.text;
|
||||
return this.extensionService.getExtensionsReport()
|
||||
return this.extensionManagementService.getExtensionsReport()
|
||||
.then(report => {
|
||||
const maliciousSet = getMaliciousExtensionsSet(report);
|
||||
|
||||
@@ -921,7 +929,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||
return false;
|
||||
}
|
||||
|
||||
install(extension: URI | IExtension): Promise<IExtension> {
|
||||
install(extension: URI | IExtension, installOptions?: InstallOptions): Promise<IExtension> {
|
||||
let extensionPolicy = this.configurationService.getValue<string>(ExtensionsPolicyKey); // {{SQL CARBON EDIT}} add line
|
||||
if (extension instanceof URI) {
|
||||
return this.installWithProgress(() => this.installFromVSIX(extension));
|
||||
@@ -986,7 +994,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||
location: ProgressLocation.Extensions,
|
||||
title: nls.localize('uninstallingExtension', 'Uninstalling extension....'),
|
||||
source: `${toUninstall.identifier.id}`
|
||||
}, () => this.extensionService.uninstall(toUninstall).then(() => undefined));
|
||||
}, () => this.extensionManagementService.uninstall(toUninstall).then(() => undefined));
|
||||
}
|
||||
|
||||
installVersion(extension: IExtension, version: string): Promise<IExtension> {
|
||||
@@ -1025,38 +1033,44 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||
return this.progressService.withProgress({
|
||||
location: ProgressLocation.Extensions,
|
||||
source: `${toReinstall.identifier.id}`
|
||||
}, () => this.extensionService.reinstallFromGallery(toReinstall).then(() => this.local.filter(local => areSameExtensions(local.identifier, extension.identifier))[0]));
|
||||
}, () => this.extensionManagementService.reinstallFromGallery(toReinstall).then(() => this.local.filter(local => areSameExtensions(local.identifier, extension.identifier))[0]));
|
||||
}
|
||||
|
||||
isExtensionIgnoredToSync(extension: IExtension): boolean {
|
||||
const localExtensions = (!isWeb && this.localExtensions ? this.localExtensions.local : this.local)
|
||||
.filter(l => !!l.local)
|
||||
.map(l => l.local!);
|
||||
|
||||
const ignoredExtensions = getIgnoredExtensions(localExtensions, this.configurationService);
|
||||
return ignoredExtensions.includes(extension.identifier.id.toLowerCase());
|
||||
return extension.local ? !this.isInstalledExtensionSynced(extension.local)
|
||||
: this.extensionsSyncManagementService.hasToNeverSyncExtension(extension.identifier.id);
|
||||
}
|
||||
|
||||
toggleExtensionIgnoredToSync(extension: IExtension): Promise<void> {
|
||||
async toggleExtensionIgnoredToSync(extension: IExtension): Promise<void> {
|
||||
const isIgnored = this.isExtensionIgnoredToSync(extension);
|
||||
const isDefaultIgnored = extension.local?.isMachineScoped;
|
||||
const id = extension.identifier.id.toLowerCase();
|
||||
|
||||
// first remove the extension completely from ignored extensions
|
||||
let currentValue = [...this.configurationService.getValue<string[]>('settingsSync.ignoredExtensions')].map(id => id.toLowerCase());
|
||||
currentValue = currentValue.filter(v => v !== id && v !== `-${id}`);
|
||||
|
||||
// If ignored, then add only if it is ignored by default
|
||||
if (isIgnored && isDefaultIgnored) {
|
||||
currentValue.push(`-${id}`);
|
||||
if (extension.local && isIgnored) {
|
||||
(<Extension>extension).local = await this.updateSynchronizingInstalledExtension(extension.local, true);
|
||||
this._onChange.fire(extension);
|
||||
} else {
|
||||
this.extensionsSyncManagementService.updateIgnoredExtensions(extension.identifier.id, !isIgnored);
|
||||
}
|
||||
await this.userDataAutoSyncService.triggerSync(['IgnoredExtensionsUpdated'], false, false);
|
||||
}
|
||||
|
||||
// If asked not to sync, then add only if it is not ignored by default
|
||||
if (!isIgnored && !isDefaultIgnored) {
|
||||
currentValue.push(id);
|
||||
private isInstalledExtensionSynced(extension: ILocalExtension): boolean {
|
||||
if (extension.isMachineScoped) {
|
||||
return false;
|
||||
}
|
||||
if (this.extensionsSyncManagementService.hasToAlwaysSyncExtension(extension.identifier.id)) {
|
||||
return true;
|
||||
}
|
||||
return !this.extensionsSyncManagementService.hasToNeverSyncExtension(extension.identifier.id);
|
||||
}
|
||||
|
||||
return this.configurationService.updateValue('settingsSync.ignoredExtensions', currentValue.length ? currentValue : undefined, ConfigurationTarget.USER);
|
||||
async updateSynchronizingInstalledExtension(extension: ILocalExtension, sync: boolean): Promise<ILocalExtension> {
|
||||
const isMachineScoped = !sync;
|
||||
if (extension.isMachineScoped !== isMachineScoped) {
|
||||
extension = await this.extensionManagementService.updateExtensionScope(extension, isMachineScoped);
|
||||
}
|
||||
if (sync) {
|
||||
this.extensionsSyncManagementService.updateIgnoredExtensions(extension.identifier.id, false);
|
||||
}
|
||||
return extension;
|
||||
}
|
||||
|
||||
private installWithProgress<T>(installTask: () => Promise<T>, extensionName?: string): Promise<T> {
|
||||
@@ -1068,9 +1082,9 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||
}
|
||||
|
||||
private async installFromVSIX(vsix: URI): Promise<IExtension> {
|
||||
const manifest = await this.extensionService.getManifest(vsix);
|
||||
const manifest = await this.extensionManagementService.getManifest(vsix);
|
||||
const existingExtension = this.local.find(local => areSameExtensions(local.identifier, { id: getGalleryExtensionId(manifest.publisher, manifest.name) }));
|
||||
const { identifier } = await this.extensionService.install(vsix);
|
||||
const { identifier } = await this.extensionManagementService.install(vsix);
|
||||
|
||||
if (existingExtension && existingExtension.latestVersion !== manifest.version) {
|
||||
this.ignoreAutoUpdate(new ExtensionIdentifierWithVersion(identifier, manifest.version));
|
||||
@@ -1079,12 +1093,15 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||
return this.local.filter(local => areSameExtensions(local.identifier, identifier))[0];
|
||||
}
|
||||
|
||||
private async installFromGallery(extension: IExtension, gallery: IGalleryExtension): Promise<IExtension> {
|
||||
private async installFromGallery(extension: IExtension, gallery: IGalleryExtension, installOptions?: InstallOptions): Promise<IExtension> {
|
||||
this.installing.push(extension);
|
||||
this._onChange.fire(extension);
|
||||
try {
|
||||
const extensionService = extension.server && extension.local && !isLanguagePackExtension(extension.local.manifest) ? extension.server.extensionManagementService : this.extensionService;
|
||||
await extensionService.installFromGallery(gallery);
|
||||
if (extension.state === ExtensionState.Installed && extension.local) {
|
||||
await this.extensionManagementService.updateFromGallery(gallery, extension.local);
|
||||
} else {
|
||||
await this.extensionManagementService.installFromGallery(gallery, installOptions);
|
||||
}
|
||||
const ids: string[] | undefined = extension.identifier.uuid ? [extension.identifier.uuid] : undefined;
|
||||
const names: string[] | undefined = extension.identifier.uuid ? undefined : [extension.identifier.id];
|
||||
this.queryGallery({ names, ids, pageSize: 1 }, CancellationToken.None);
|
||||
@@ -1116,7 +1133,23 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||
for (const extension of extensions) {
|
||||
let dependents = this.getDependentsAfterDisablement(extension, allExtensions, this.local);
|
||||
if (dependents.length) {
|
||||
return Promise.reject(new Error(this.getDependentsErrorMessage(extension, allExtensions, dependents)));
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.notificationService.prompt(Severity.Error, this.getDependentsErrorMessage(extension, allExtensions, dependents), [
|
||||
{
|
||||
label: nls.localize('disable all', 'Disable All'),
|
||||
run: async () => {
|
||||
try {
|
||||
await this.checkAndSetEnablement(dependents, [extension], enablementState);
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
], {
|
||||
onCancel: () => reject(canceled())
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1137,7 +1170,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||
return false;
|
||||
}
|
||||
const enable = enablementState === EnablementState.EnabledGlobally || enablementState === EnablementState.EnabledWorkspace;
|
||||
return (enable || i.type === ExtensionType.User) // Include all Extensions for enablement and only user extensions for disablement
|
||||
return (enable || !i.isBuiltin) // Include all Extensions for enablement and only non builtin extensions for disablement
|
||||
&& (options.dependencies || options.pack)
|
||||
&& extensions.some(extension =>
|
||||
(options.dependencies && extension.dependencies.some(id => areSameExtensions({ id }, i.identifier)))
|
||||
@@ -1182,13 +1215,13 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||
|
||||
private getErrorMessageForDisablingAnExtensionWithDependents(extension: IExtension, dependents: IExtension[]): string {
|
||||
if (dependents.length === 1) {
|
||||
return nls.localize('singleDependentError', "Cannot disable extension '{0}'. Extension '{1}' depends on this.", extension.displayName, dependents[0].displayName);
|
||||
return nls.localize('singleDependentError', "Cannot disable '{0}' extension alone. '{1}' extension depends on this. Do you want to disable all these extensions?", extension.displayName, dependents[0].displayName);
|
||||
}
|
||||
if (dependents.length === 2) {
|
||||
return nls.localize('twoDependentsError', "Cannot disable extension '{0}'. Extensions '{1}' and '{2}' depend on this.",
|
||||
return nls.localize('twoDependentsError', "Cannot disable '{0}' extension alone. '{1}' and '{2}' extensions depend on this. Do you want to disable all these extensions?",
|
||||
extension.displayName, dependents[0].displayName, dependents[1].displayName);
|
||||
}
|
||||
return nls.localize('multipleDependentsError', "Cannot disable extension '{0}'. Extensions '{1}', '{2}' and others depend on this.",
|
||||
return nls.localize('multipleDependentsError', "Cannot disable '{0}' extension alone. '{1}', '{2}' and other extensions depend on this. Do you want to disable all these extensions?",
|
||||
extension.displayName, dependents[0].displayName, dependents[1].displayName);
|
||||
}
|
||||
|
||||
@@ -1296,7 +1329,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||
|
||||
private set ignoredAutoUpdateExtensions(extensionIds: string[]) {
|
||||
this._ignoredAutoUpdateExtensions = distinct(extensionIds.map(id => id.toLowerCase()));
|
||||
this.storageService.store('extensions.ignoredAutoUpdateExtension', JSON.stringify(this._ignoredAutoUpdateExtensions), StorageScope.GLOBAL);
|
||||
this.storageService.store('extensions.ignoredAutoUpdateExtension', JSON.stringify(this._ignoredAutoUpdateExtensions), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
private ignoreAutoUpdate(identifierWithVersion: ExtensionIdentifierWithVersion): void {
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ExtensionRecommendations, ExtensionRecommendation, PromptedExtensionRecommendations } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
|
||||
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { ExtensionRecommendationSource, ExtensionRecommendationReason, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
import { IExtensionsViewPaneContainer, IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { localize } from 'vs/nls';
|
||||
import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { StorageScope, IStorageService, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { ImportantExtensionTip, IProductService } from 'vs/platform/product/common/productService';
|
||||
import { forEach, IStringDictionary } from 'vs/base/common/collections';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
@@ -24,12 +25,17 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { setImmediate } from 'vs/base/common/platform';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IExtensionRecommendationNotificationService, RecommendationsNotificationResult, RecommendationSource } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
|
||||
type FileExtensionSuggestionClassification = {
|
||||
userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
fileExtension: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
|
||||
const promptedRecommendationsStorageKey = 'fileBasedRecommendations/promptedRecommendations';
|
||||
const promptedFileExtensionsStorageKey = 'fileBasedRecommendations/promptedFileExtensions';
|
||||
const recommendationsStorageKey = 'extensionsAssistant/recommendations';
|
||||
const searchMarketplace = localize('searchMarketplace', "Search Marketplace");
|
||||
const milliSecondsInADay = 1000 * 60 * 60 * 24;
|
||||
@@ -41,7 +47,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
|
||||
|
||||
private readonly fileBasedRecommendationsByPattern = new Map<string, string[]>();
|
||||
private readonly fileBasedRecommendationsByLanguage = new Map<string, string[]>();
|
||||
private readonly fileBasedRecommendations = new Map<string, { recommendedTime: number, sources: ExtensionRecommendationSource[] }>();
|
||||
private readonly fileBasedRecommendations = new Map<string, { recommendedTime: number }>();
|
||||
private readonly processedFileExtensions: string[] = [];
|
||||
private readonly processedLanguages: string[] = [];
|
||||
|
||||
@@ -60,16 +66,13 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
|
||||
return this.fileBasedRecommendations.get(a)!.recommendedTime > this.fileBasedRecommendations.get(b)!.recommendedTime ? -1 : 1;
|
||||
})
|
||||
.forEach(extensionId => {
|
||||
for (const source of this.fileBasedRecommendations.get(extensionId)!.sources) {
|
||||
recommendations.push({
|
||||
extensionId,
|
||||
source,
|
||||
reason: {
|
||||
reasonId: ExtensionRecommendationReason.File,
|
||||
reasonText: localize('fileBasedRecommendation', "This extension is recommended based on the files you recently opened.")
|
||||
}
|
||||
});
|
||||
}
|
||||
recommendations.push({
|
||||
extensionId,
|
||||
reason: {
|
||||
reasonId: ExtensionRecommendationReason.File,
|
||||
reasonText: localize('fileBasedRecommendation', "This extension is recommended based on the files you recently opened.")
|
||||
}
|
||||
});
|
||||
});
|
||||
return recommendations;
|
||||
}
|
||||
@@ -83,7 +86,6 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
|
||||
}
|
||||
|
||||
constructor(
|
||||
promptedExtensionRecommendations: PromptedExtensionRecommendations,
|
||||
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IViewletService private readonly viewletService: IViewletService,
|
||||
@@ -93,8 +95,10 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IExtensionRecommendationNotificationService private readonly extensionRecommendationNotificationService: IExtensionRecommendationNotificationService,
|
||||
@IExtensionIgnoredRecommendationsService private readonly extensionIgnoredRecommendationsService: IExtensionIgnoredRecommendationsService,
|
||||
) {
|
||||
super(promptedExtensionRecommendations);
|
||||
super();
|
||||
|
||||
if (productService.extensionTips) {
|
||||
forEach(productService.extensionTips, ({ key, value }) => this.extensionTips.set(key.toLowerCase(), value));
|
||||
@@ -138,7 +142,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
|
||||
forEach(cachedRecommendations, ({ key, value }) => {
|
||||
const diff = (now - value) / milliSecondsInADay;
|
||||
if (diff <= 7 && allRecommendations.indexOf(key) > -1) {
|
||||
this.fileBasedRecommendations.set(key.toLowerCase(), { recommendedTime: value, sources: ['cached'] });
|
||||
this.fileBasedRecommendations.set(key.toLowerCase(), { recommendedTime: value });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -147,8 +151,16 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
|
||||
}
|
||||
|
||||
private onModelAdded(model: ITextModel): void {
|
||||
const uri = model.uri;
|
||||
const supportedSchemes = [Schemas.untitled, Schemas.file, Schemas.vscodeRemote];
|
||||
if (!uri || !supportedSchemes.includes(uri.scheme)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.promptRecommendationsForModel(model);
|
||||
this._register(model.onDidChangeLanguage(() => this.promptRecommendationsForModel(model)));
|
||||
const disposables = new DisposableStore();
|
||||
disposables.add(model.onDidChangeLanguage(() => this.promptRecommendationsForModel(model)));
|
||||
disposables.add(model.onWillDispose(() => disposables.dispose()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,13 +169,8 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
|
||||
*/
|
||||
private promptRecommendationsForModel(model: ITextModel): void {
|
||||
const uri = model.uri;
|
||||
const supportedSchemes = [Schemas.untitled, Schemas.file, Schemas.vscodeRemote];
|
||||
if (!uri || !supportedSchemes.includes(uri.scheme)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const language = model.getLanguageIdentifier().language;
|
||||
const fileExtension = extname(uri);
|
||||
const fileExtension = extname(uri).toLowerCase();
|
||||
if (this.processedLanguages.includes(language) && this.processedFileExtensions.includes(fileExtension)) {
|
||||
return;
|
||||
}
|
||||
@@ -201,21 +208,18 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
|
||||
for (const recommendation of fileBasedRecommendations) {
|
||||
const filedBasedRecommendation = this.fileBasedRecommendations.get(recommendation) || { recommendedTime: Date.now(), sources: [] };
|
||||
filedBasedRecommendation.recommendedTime = Date.now();
|
||||
if (!filedBasedRecommendation.sources.some(s => s instanceof URI && s.toString() === uri.toString())) {
|
||||
filedBasedRecommendation.sources.push(uri);
|
||||
}
|
||||
this.fileBasedRecommendations.set(recommendation, filedBasedRecommendation);
|
||||
}
|
||||
|
||||
this.storeCachedRecommendations();
|
||||
|
||||
if (this.promptedExtensionRecommendations.hasToIgnoreRecommendationNotifications()) {
|
||||
if (this.extensionRecommendationNotificationService.hasToIgnoreRecommendationNotifications()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const installed = await this.extensionsWorkbenchService.queryLocal();
|
||||
if (importantRecommendations.length &&
|
||||
await this.promptRecommendedExtensionForFileType(languageName || basename(uri), importantRecommendations, installed)) {
|
||||
await this.promptRecommendedExtensionForFileType(languageName || basename(uri), language, importantRecommendations, installed)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -232,9 +236,9 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
|
||||
this.promptRecommendedExtensionForFileExtension(fileExtension, installed);
|
||||
}
|
||||
|
||||
private async promptRecommendedExtensionForFileType(name: string, recommendations: string[], installed: IExtension[]): Promise<boolean> {
|
||||
private async promptRecommendedExtensionForFileType(name: string, language: string, recommendations: string[], installed: IExtension[]): Promise<boolean> {
|
||||
|
||||
recommendations = this.promptedExtensionRecommendations.filterIgnoredOrNotAllowed(recommendations);
|
||||
recommendations = this.filterIgnoredOrNotAllowed(recommendations);
|
||||
if (recommendations.length === 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -250,70 +254,101 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.promptedExtensionRecommendations.promptImportantExtensionsInstallNotification([extensionId], localize('reallyRecommended', "Do you want to install the recommended extensions for {0}?", name), `@id:${extensionId}`);
|
||||
const promptedRecommendations = this.getPromptedRecommendations();
|
||||
if (promptedRecommendations[language] && promptedRecommendations[language].includes(extensionId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.extensionRecommendationNotificationService.promptImportantExtensionsInstallNotification([extensionId], localize('reallyRecommended', "Do you want to install the recommended extensions for {0}?", name), `@id:${extensionId}`, RecommendationSource.FILE)
|
||||
.then(result => {
|
||||
if (result === RecommendationsNotificationResult.Accepted) {
|
||||
this.addToPromptedRecommendations(language, [extensionId]);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private getPromptedRecommendations(): IStringDictionary<string[]> {
|
||||
return JSON.parse(this.storageService.get(promptedRecommendationsStorageKey, StorageScope.GLOBAL, '{}'));
|
||||
}
|
||||
|
||||
private addToPromptedRecommendations(exeName: string, extensions: string[]) {
|
||||
const promptedRecommendations = this.getPromptedRecommendations();
|
||||
promptedRecommendations[exeName] = extensions;
|
||||
this.storageService.store(promptedRecommendationsStorageKey, JSON.stringify(promptedRecommendations), StorageScope.GLOBAL, StorageTarget.USER);
|
||||
}
|
||||
|
||||
private getPromptedFileExtensions(): string[] {
|
||||
return JSON.parse(this.storageService.get(promptedFileExtensionsStorageKey, StorageScope.GLOBAL, '[]'));
|
||||
}
|
||||
|
||||
private addToPromptedFileExtensions(fileExtension: string) {
|
||||
const promptedFileExtensions = this.getPromptedFileExtensions();
|
||||
promptedFileExtensions.push(fileExtension);
|
||||
this.storageService.store(promptedFileExtensionsStorageKey, JSON.stringify(distinct(promptedFileExtensions)), StorageScope.GLOBAL, StorageTarget.USER);
|
||||
}
|
||||
|
||||
private async promptRecommendedExtensionForFileExtension(fileExtension: string, installed: IExtension[]): Promise<void> {
|
||||
// {{SQL CARBON EDIT}} - Turn off file extension-based extensions until we have a scenario that requires this. The queryGallery isn't working correctly in ADS now.
|
||||
let enableRecommendation: boolean = false;
|
||||
if (!enableRecommendation) {
|
||||
return;
|
||||
}
|
||||
const fileExtensionSuggestionIgnoreList = <string[]>JSON.parse(this.storageService.get('extensionsAssistant/fileExtensionsSuggestionIgnore', StorageScope.GLOBAL, '[]'));
|
||||
if (fileExtensionSuggestionIgnoreList.indexOf(fileExtension) > -1) {
|
||||
return;
|
||||
} else {
|
||||
const fileExtensionSuggestionIgnoreList = <string[]>JSON.parse(this.storageService.get('extensionsAssistant/fileExtensionsSuggestionIgnore', StorageScope.GLOBAL, '[]'));
|
||||
if (fileExtensionSuggestionIgnoreList.indexOf(fileExtension) > -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const text = `ext:${fileExtension}`;
|
||||
const pager = await this.extensionsWorkbenchService.queryGallery({ text, pageSize: 100 }, CancellationToken.None);
|
||||
if (pager.firstPage.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const installedExtensionsIds = installed.reduce((result, i) => { result.add(i.identifier.id.toLowerCase()); return result; }, new Set<string>());
|
||||
if (pager.firstPage.some(e => installedExtensionsIds.has(e.identifier.id.toLowerCase()))) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
localize('showLanguageExtensions', "The Marketplace has extensions that can help with '.{0}' files", fileExtension),
|
||||
[{
|
||||
label: searchMarketplace,
|
||||
run: () => {
|
||||
this.telemetryService.publicLog2<{ userReaction: string, fileExtension: string }, FileExtensionSuggestionClassification>('fileExtensionSuggestion:popup', { userReaction: 'ok', fileExtension });
|
||||
this.viewletService.openViewlet('workbench.view.extensions', true)
|
||||
.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
|
||||
.then(viewlet => {
|
||||
viewlet.search(`ext:${fileExtension}`);
|
||||
viewlet.focus();
|
||||
});
|
||||
}
|
||||
}, {
|
||||
label: localize('dontShowAgainExtension', "Don't Show Again for '.{0}' files", fileExtension),
|
||||
run: () => {
|
||||
fileExtensionSuggestionIgnoreList.push(fileExtension);
|
||||
this.storageService.store(
|
||||
'extensionsAssistant/fileExtensionsSuggestionIgnore',
|
||||
JSON.stringify(fileExtensionSuggestionIgnoreList),
|
||||
StorageScope.GLOBAL
|
||||
);
|
||||
this.telemetryService.publicLog2<{ userReaction: string, fileExtension: string }, FileExtensionSuggestionClassification>('fileExtensionSuggestion:popup', { userReaction: 'neverShowAgain', fileExtension });
|
||||
}
|
||||
}],
|
||||
{
|
||||
sticky: true,
|
||||
onCancel: () => {
|
||||
this.telemetryService.publicLog2<{ userReaction: string, fileExtension: string }, FileExtensionSuggestionClassification>('fileExtensionSuggestion:popup', { userReaction: 'cancelled', fileExtension });
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const promptedFileExtensions = this.getPromptedFileExtensions();
|
||||
if (promptedFileExtensions.includes(fileExtension)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const text = `ext:${fileExtension}`;
|
||||
const pager = await this.extensionsWorkbenchService.queryGallery({ text, pageSize: 100 }, CancellationToken.None);
|
||||
if (pager.firstPage.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const installedExtensionsIds = installed.reduce((result, i) => { result.add(i.identifier.id.toLowerCase()); return result; }, new Set<string>());
|
||||
if (pager.firstPage.some(e => installedExtensionsIds.has(e.identifier.id.toLowerCase()))) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
localize('showLanguageExtensions', "The Marketplace has extensions that can help with '.{0}' files", fileExtension),
|
||||
[{
|
||||
label: searchMarketplace,
|
||||
run: () => {
|
||||
this.addToPromptedFileExtensions(fileExtension);
|
||||
this.telemetryService.publicLog2<{ userReaction: string, fileExtension: string }, FileExtensionSuggestionClassification>('fileExtensionSuggestion:popup', { userReaction: 'ok', fileExtension });
|
||||
this.viewletService.openViewlet('workbench.view.extensions', true)
|
||||
.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
|
||||
.then(viewlet => {
|
||||
viewlet.search(`ext:${fileExtension}`);
|
||||
viewlet.focus();
|
||||
});
|
||||
}
|
||||
}, {
|
||||
label: localize('dontShowAgainExtension', "Don't Show Again for '.{0}' files", fileExtension),
|
||||
run: () => {
|
||||
fileExtensionSuggestionIgnoreList.push(fileExtension);
|
||||
this.storageService.store(
|
||||
'extensionsAssistant/fileExtensionsSuggestionIgnore',
|
||||
JSON.stringify(fileExtensionSuggestionIgnoreList),
|
||||
StorageScope.GLOBAL,
|
||||
StorageTarget.USER);
|
||||
this.telemetryService.publicLog2<{ userReaction: string, fileExtension: string }, FileExtensionSuggestionClassification>('fileExtensionSuggestion:popup', { userReaction: 'neverShowAgain', fileExtension });
|
||||
}
|
||||
}],
|
||||
{
|
||||
sticky: true,
|
||||
onCancel: () => {
|
||||
this.telemetryService.publicLog2<{ userReaction: string, fileExtension: string }, FileExtensionSuggestionClassification>('fileExtensionSuggestion:popup', { userReaction: 'cancelled', fileExtension });
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private filterIgnoredOrNotAllowed(recommendationsToSuggest: string[]): string[] {
|
||||
const ignoredRecommendations = [...this.extensionIgnoredRecommendationsService.ignoredRecommendations, ...this.extensionRecommendationNotificationService.ignoredRecommendations];
|
||||
return recommendationsToSuggest.filter(id => !ignoredRecommendations.includes(id));
|
||||
}
|
||||
|
||||
private filterInstalled(recommendationsToSuggest: string[], installed: IExtension[]): string[] {
|
||||
@@ -343,6 +378,6 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
|
||||
private storeCachedRecommendations(): void {
|
||||
const storedRecommendations: IStringDictionary<number> = {};
|
||||
this.fileBasedRecommendations.forEach((value, key) => storedRecommendations[key] = value.recommendedTime);
|
||||
this.storageService.store(recommendationsStorageKey, JSON.stringify(storedRecommendations), StorageScope.GLOBAL);
|
||||
this.storageService.store(recommendationsStorageKey, JSON.stringify(storedRecommendations), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtensionRecommendations, ExtensionRecommendation, PromptedExtensionRecommendations } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
|
||||
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionRecommendationReason, IExtensionRecommendationReson } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
|
||||
export class KeymapRecommendations extends ExtensionRecommendations {
|
||||
|
||||
@@ -13,18 +13,16 @@ export class KeymapRecommendations extends ExtensionRecommendations {
|
||||
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
|
||||
|
||||
constructor(
|
||||
promptedExtensionRecommendations: PromptedExtensionRecommendations,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
) {
|
||||
super(promptedExtensionRecommendations);
|
||||
super();
|
||||
}
|
||||
|
||||
protected async doActivate(): Promise<void> {
|
||||
if (this.productService.keymapExtensionTips) {
|
||||
this._recommendations = this.productService.keymapExtensionTips.map(extensionId => (<ExtensionRecommendation>{
|
||||
extensionId: extensionId.toLowerCase(),
|
||||
source: 'application',
|
||||
reason: {
|
||||
reason: <IExtensionRecommendationReson>{ // {{SQL CARBON EDIT}}
|
||||
reasonId: ExtensionRecommendationReason.Application,
|
||||
reasonText: ''
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
|
||||
.extension-list-item > .details > .header-container > .header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
@@ -139,6 +139,15 @@
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.extension-list-item > .details > .header-container > .header .sync-ignored {
|
||||
display: flex;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.extension-list-item > .details > .header-container > .header .sync-ignored > .codicon {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.extension-list-item > .details > .description {
|
||||
padding-right: 11px;
|
||||
}
|
||||
|
||||
@@ -10,26 +10,29 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.monaco-action-bar .action-item .action-label.extension-action.label {
|
||||
.monaco-action-bar .action-item > .action-label.extension-action.label,
|
||||
.monaco-action-bar .action-dropdown-item > .action-label.extension-action.label {
|
||||
padding: 0 5px;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.monaco-action-bar .action-item .action-label.extension-action.label {
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.monaco-action-bar .action-item .action-label.extension-action.text,
|
||||
.monaco-action-bar .action-item .action-label.extension-action.label {
|
||||
.monaco-action-bar .action-item .action-label.extension-action.label,
|
||||
.monaco-action-bar .action-dropdown-item .action-label.extension-action.label {
|
||||
line-height: 14px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.monaco-action-bar .action-item .action-label.extension-action.icon {
|
||||
padding: 0 2px;
|
||||
height: 18px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.monaco-action-bar .action-item .action-label.extension-action.multiserver.install:after,
|
||||
.monaco-action-bar .action-item .action-label.extension-action.multiserver.update:after,
|
||||
.monaco-action-bar .action-item .action-label.extension-action.extension-editor-dropdown-action.dropdown:after {
|
||||
.monaco-action-bar .action-item .action-label.extension-action.multiserver.update:after {
|
||||
content: '▼';
|
||||
padding-left: 2px;
|
||||
font-size: 80%;
|
||||
@@ -42,7 +45,9 @@
|
||||
.monaco-action-bar .action-item.disabled .action-label.extension-action.uninstall:not(.uninstalling),
|
||||
.monaco-action-bar .action-item.disabled .action-label.extension-action.update,
|
||||
.monaco-action-bar .action-item.disabled .action-label.extension-action.theme,
|
||||
.monaco-action-bar .action-item.disabled .action-label.extension-action.extension-editor-dropdown-action,
|
||||
.monaco-action-bar .action-item.disabled .action-label.extension-action.extension-sync,
|
||||
.monaco-action-bar .action-item.action-dropdown-item.disabled,
|
||||
.monaco-action-bar .action-item.action-dropdown-item .action-label.extension-action.hide,
|
||||
.monaco-action-bar .action-item.disabled .action-label.extension-action.reload,
|
||||
.monaco-action-bar .action-item.disabled .action-label.disable-status.hide,
|
||||
.monaco-action-bar .action-item.disabled .action-label.system-disable.hide,
|
||||
|
||||
@@ -76,7 +76,6 @@
|
||||
.extension-editor > .header > .details > .title > .identifier {
|
||||
margin-left: 10px;
|
||||
font-size: 14px;
|
||||
opacity: 0.6;
|
||||
background: rgba(173, 173, 173, 0.31);
|
||||
padding: 0px 4px;
|
||||
border-radius: 4px;
|
||||
@@ -153,16 +152,30 @@
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .extension-action {
|
||||
.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item:not(.action-dropdown-item) > .extension-action,
|
||||
.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item.action-dropdown-item .extension-action.action-label.dropdown,
|
||||
.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item.action-dropdown-item > .extension-action.action-label.empty-dropdown {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .extension-action.label {
|
||||
.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .extension-action.label,
|
||||
.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item.action-dropdown-item .extension-action.label {
|
||||
font-weight: 600;
|
||||
padding: 1px 6px;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .extension-action.icon {
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .extension-action.label {
|
||||
padding: 1px 6px;
|
||||
}
|
||||
|
||||
.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item.action-dropdown-item .extension-action.dropdown.label {
|
||||
padding: 1px 0px;
|
||||
}
|
||||
|
||||
.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .action-label.system-disable {
|
||||
margin-right: 0;
|
||||
}
|
||||
@@ -202,8 +215,7 @@
|
||||
.extension-editor > .header > .details > .subtext-container > .monaco-action-bar .action-label {
|
||||
margin-top: 4px;
|
||||
margin-left: 4px;
|
||||
padding-top: 0;
|
||||
padding-bottom: 2px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .actions-container {
|
||||
@@ -387,7 +399,7 @@
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
.extension-editor > .body > .content table code:not(:empty) {
|
||||
.extension-editor > .body > .content code:not(:empty) {
|
||||
font-family: var(--monaco-monospace-font);
|
||||
font-size: 90%;
|
||||
background-color: rgba(128, 128, 128, 0.17);
|
||||
|
||||
@@ -52,6 +52,12 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* {{SQL CARBON EDIT}} - hide drop-down to avoid rendering bug
|
||||
.extension-editor .details .monaco-action-bar .monaco-dropdown,
|
||||
.extensions-viewlet > .extensions .monaco-action-bar .monaco-dropdown {
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
.extensions-viewlet > .extensions .extensions-list.hidden,
|
||||
.extensions-viewlet > .extensions .message-container.hidden {
|
||||
display: none;
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.extension-sync-ignored.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.extension-ratings {
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -11,7 +15,7 @@
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.extension-ratings > .codicon[class*='codicon-star']:not(:first-child) {
|
||||
.extension-ratings > .codicon[class*='codicon-extensions-rating']:not(:first-child) {
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
@@ -24,12 +28,17 @@
|
||||
}
|
||||
|
||||
/* TODO @misolori make this a color token */
|
||||
.extension-ratings .codicon-star-full,
|
||||
.extension-ratings .codicon-star-half {
|
||||
.extension-ratings .codicon-extensions-star-full,
|
||||
.extension-ratings .codicon-extensions-star-half {
|
||||
color: #FF8E00 !important;
|
||||
}
|
||||
|
||||
.extension-ratings .codicon-star-empty {
|
||||
.extension-install-count .codicon,
|
||||
.extension-action.codicon-extensions-manage {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.extension-ratings .codicon-extensions-star-empty {
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,11 +57,6 @@
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.monaco-workbench.vs .runtime-extensions-editor .extension > .icon-container > .icon,
|
||||
.monaco-workbench.vs-dark .runtime-extensions-editor .extension > .icon-container > .icon {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.runtime-extensions-editor .extension > .desc > .header-container {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
@@ -5,13 +5,14 @@
|
||||
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { MenuRegistry, MenuId, Action2, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { InstallLocalExtensionsInRemoteAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { InstallLocalExtensionsInRemoteAction, InstallRemoteExtensionsInLocalAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
|
||||
export class RemoteExtensionsInstaller extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
@@ -38,6 +39,20 @@ export class RemoteExtensionsInstaller extends Disposable implements IWorkbenchC
|
||||
appendMenuItem();
|
||||
this._register(labelService.onDidChangeFormatters(e => appendMenuItem()));
|
||||
this._register(toDisposable(() => disposable.dispose()));
|
||||
|
||||
this._register(registerAction2(class InstallRemoteExtensionsInLocalAction2 extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.extensions.actions.installLocalExtensionsInRemote',
|
||||
title: { value: localize('install remote in local', "Install Remote Extensions Locally..."), original: 'Install Remote Extensions Locally...' },
|
||||
category: localize({ key: 'remote', comment: ['Remote as in remote machine'] }, "Remote"),
|
||||
f1: true
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor): Promise<void> {
|
||||
return accessor.get(IInstantiationService).createInstance(InstallRemoteExtensionsInLocalAction, 'workbench.extensions.actions.installLocalExtensionsInRemote').run();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,18 +4,15 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EXTENSION_IDENTIFIER_PATTERN, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder, IWorkspace, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { distinct, flatten, coalesce } from 'vs/base/common/arrays';
|
||||
import { ExtensionRecommendations, ExtensionRecommendation, PromptedExtensionRecommendations } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
|
||||
import { distinct, flatten } from 'vs/base/common/arrays';
|
||||
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IExtensionsConfigContent, ExtensionRecommendationSource, ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { parse } from 'vs/base/common/json';
|
||||
import { EXTENSIONS_CONFIG } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IExtensionsConfigContent, IWorkpsaceExtensionsConfigService } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig';
|
||||
|
||||
export class WorkspaceRecommendations extends ExtensionRecommendations {
|
||||
|
||||
@@ -29,19 +26,17 @@ export class WorkspaceRecommendations extends ExtensionRecommendations {
|
||||
get ignoredRecommendations(): ReadonlyArray<string> { return this._ignoredRecommendations; }
|
||||
|
||||
constructor(
|
||||
promptedExtensionRecommendations: PromptedExtensionRecommendations,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IWorkpsaceExtensionsConfigService private readonly workpsaceExtensionsConfigService: IWorkpsaceExtensionsConfigService,
|
||||
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
) {
|
||||
super(promptedExtensionRecommendations);
|
||||
super();
|
||||
}
|
||||
|
||||
protected async doActivate(): Promise<void> {
|
||||
await this.fetch();
|
||||
this._register(this.contextService.onDidChangeWorkspaceFolders(e => this.onWorkspaceFoldersChanged(e)));
|
||||
this._register(this.workpsaceExtensionsConfigService.onDidChangeExtensionsConfigs(() => this.onDidChangeExtensionsConfigs()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,71 +44,40 @@ export class WorkspaceRecommendations extends ExtensionRecommendations {
|
||||
*/
|
||||
private async fetch(): Promise<void> {
|
||||
|
||||
const extensionsConfigBySource = await this.fetchExtensionsConfigBySource();
|
||||
const extensionsConfigs = await this.workpsaceExtensionsConfigService.getExtensionsConfigs();
|
||||
|
||||
const { invalidRecommendations, message } = await this.validateExtensions(extensionsConfigBySource.map(({ contents }) => contents));
|
||||
const { invalidRecommendations, message } = await this.validateExtensions(extensionsConfigs);
|
||||
if (invalidRecommendations.length) {
|
||||
this.notificationService.warn(`The ${invalidRecommendations.length} extension(s) below, in workspace recommendations have issues:\n${message}`);
|
||||
}
|
||||
|
||||
this._recommendations = [];
|
||||
this._ignoredRecommendations = [];
|
||||
|
||||
for (const extensionsConfig of extensionsConfigBySource) {
|
||||
for (const unwantedRecommendation of extensionsConfig.contents.unwantedRecommendations) {
|
||||
if (invalidRecommendations.indexOf(unwantedRecommendation) === -1) {
|
||||
this._ignoredRecommendations.push(unwantedRecommendation);
|
||||
for (const extensionsConfig of extensionsConfigs) {
|
||||
if (extensionsConfig.unwantedRecommendations) {
|
||||
for (const unwantedRecommendation of extensionsConfig.unwantedRecommendations) {
|
||||
if (invalidRecommendations.indexOf(unwantedRecommendation) === -1) {
|
||||
this._ignoredRecommendations.push(unwantedRecommendation);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const extensionId of extensionsConfig.contents.recommendations) {
|
||||
if (invalidRecommendations.indexOf(extensionId) === -1) {
|
||||
this._recommendations.push({
|
||||
extensionId,
|
||||
source: extensionsConfig.source,
|
||||
reason: {
|
||||
reasonId: ExtensionRecommendationReason.Workspace,
|
||||
reasonText: localize('workspaceRecommendation', "This extension is recommended by users of the current workspace.")
|
||||
}
|
||||
});
|
||||
if (extensionsConfig.recommendations) {
|
||||
for (const extensionId of extensionsConfig.recommendations) {
|
||||
if (invalidRecommendations.indexOf(extensionId) === -1) {
|
||||
this._recommendations.push({
|
||||
extensionId,
|
||||
reason: {
|
||||
reasonId: ExtensionRecommendationReason.Workspace,
|
||||
reasonText: localize('workspaceRecommendation', "This extension is recommended by users of the current workspace.")
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchExtensionsConfigBySource(): Promise<{ contents: IExtensionsConfigContent, source: ExtensionRecommendationSource }[]> {
|
||||
const workspace = this.contextService.getWorkspace();
|
||||
const result = await Promise.all([
|
||||
this.resolveWorkspaceExtensionConfig(workspace),
|
||||
...workspace.folders.map(workspaceFolder => this.resolveWorkspaceFolderExtensionConfig(workspaceFolder))
|
||||
]);
|
||||
return coalesce(result);
|
||||
}
|
||||
|
||||
private async resolveWorkspaceExtensionConfig(workspace: IWorkspace): Promise<{ contents: IExtensionsConfigContent, source: ExtensionRecommendationSource } | null> {
|
||||
try {
|
||||
if (workspace.configuration) {
|
||||
const content = await this.fileService.readFile(workspace.configuration);
|
||||
const extensionsConfigContent = <IExtensionsConfigContent | undefined>parse(content.value.toString())['extensions'];
|
||||
const contents = this.parseExtensionConfig(extensionsConfigContent);
|
||||
if (contents) {
|
||||
return { contents, source: workspace };
|
||||
}
|
||||
}
|
||||
} catch (e) { /* Ignore */ }
|
||||
return null;
|
||||
}
|
||||
|
||||
private async resolveWorkspaceFolderExtensionConfig(workspaceFolder: IWorkspaceFolder): Promise<{ contents: IExtensionsConfigContent, source: ExtensionRecommendationSource } | null> {
|
||||
try {
|
||||
const content = await this.fileService.readFile(workspaceFolder.toResource(EXTENSIONS_CONFIG));
|
||||
const extensionsConfigContent = <IExtensionsConfigContent>parse(content.value.toString());
|
||||
const contents = this.parseExtensionConfig(extensionsConfigContent);
|
||||
if (contents) {
|
||||
return { contents, source: workspaceFolder };
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
return null;
|
||||
}
|
||||
|
||||
private async validateExtensions(contents: IExtensionsConfigContent[]): Promise<{ validRecommendations: string[], invalidRecommendations: string[], message: string }> {
|
||||
|
||||
const validExtensions: string[] = [];
|
||||
@@ -154,25 +118,9 @@ export class WorkspaceRecommendations extends ExtensionRecommendations {
|
||||
return { validRecommendations: validExtensions, invalidRecommendations: invalidExtensions, message };
|
||||
}
|
||||
|
||||
private async onWorkspaceFoldersChanged(event: IWorkspaceFoldersChangeEvent): Promise<void> {
|
||||
if (event.added.length) {
|
||||
const oldWorkspaceRecommended = this._recommendations;
|
||||
await this.fetch();
|
||||
// Suggest only if at least one of the newly added recommendations was not suggested before
|
||||
if (this._recommendations.some(current => oldWorkspaceRecommended.every(old => current.extensionId !== old.extensionId))) {
|
||||
this._onDidChangeRecommendations.fire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private parseExtensionConfig(extensionsConfigContent: IExtensionsConfigContent | undefined): IExtensionsConfigContent | null {
|
||||
if (extensionsConfigContent) {
|
||||
return {
|
||||
recommendations: distinct((extensionsConfigContent.recommendations || []).map(e => e.toLowerCase())),
|
||||
unwantedRecommendations: distinct((extensionsConfigContent.unwantedRecommendations || []).map(e => e.toLowerCase()))
|
||||
};
|
||||
}
|
||||
return null;
|
||||
private async onDidChangeExtensionsConfigs(): Promise<void> {
|
||||
await this.fetch();
|
||||
this._onDidChangeRecommendations.fire();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IPager } from 'vs/base/common/paging';
|
||||
import { IQueryOptions, ILocalExtension, IGalleryExtension, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IQueryOptions, ILocalExtension, IGalleryExtension, IExtensionIdentifier, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { EnablementState, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
@@ -20,7 +20,8 @@ export const VIEWLET_ID = 'workbench.view.extensions';
|
||||
export const EXTENSIONS_CONFIG = '.azuredatastudio/extensions.json';
|
||||
|
||||
export interface IExtensionsViewPaneContainer extends IViewPaneContainer {
|
||||
search(text: string, refresh?: boolean): void;
|
||||
search(text: string): void;
|
||||
refresh(): Promise<void>;
|
||||
}
|
||||
|
||||
export const enum ExtensionState {
|
||||
@@ -32,6 +33,7 @@ export const enum ExtensionState {
|
||||
|
||||
export interface IExtension {
|
||||
readonly type: ExtensionType;
|
||||
readonly isBuiltin: boolean;
|
||||
readonly state: ExtensionState;
|
||||
readonly name: string;
|
||||
readonly displayName: string;
|
||||
@@ -82,7 +84,7 @@ export interface IExtensionsWorkbenchService {
|
||||
queryGallery(options: IQueryOptions, token: CancellationToken): Promise<IPager<IExtension>>;
|
||||
canInstall(extension: IExtension): boolean;
|
||||
install(vsix: URI): Promise<IExtension>;
|
||||
install(extension: IExtension, promptToInstallDependencies?: boolean): Promise<IExtension>;
|
||||
install(extension: IExtension, installOptins?: InstallOptions): Promise<IExtension>;
|
||||
uninstall(extension: IExtension): Promise<void>;
|
||||
installVersion(extension: IExtension, version: string): Promise<IExtension>;
|
||||
reinstall(extension: IExtension): Promise<IExtension>;
|
||||
@@ -135,10 +137,12 @@ export class ExtensionContainers extends Disposable {
|
||||
for (const container of this.containers) {
|
||||
if (extension && container.extension) {
|
||||
if (areSameExtensions(container.extension.identifier, extension.identifier)) {
|
||||
if (!container.extension.server || !extension.server || container.extension.server === extension.server) {
|
||||
if (container.extension.server && extension.server && container.extension.server !== extension.server) {
|
||||
if (container.updateWhenCounterExtensionChanges) {
|
||||
container.update();
|
||||
}
|
||||
} else {
|
||||
container.extension = extension;
|
||||
} else if (container.updateWhenCounterExtensionChanges) {
|
||||
container.update();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -149,3 +153,4 @@ export class ExtensionContainers extends Disposable {
|
||||
}
|
||||
|
||||
export const TOGGLE_IGNORE_EXTENSION_ACTION_ID = 'workbench.extensions.action.toggleIgnoreExtension';
|
||||
export const INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID = 'workbench.extensions.command.installFromVSIX';
|
||||
|
||||
@@ -9,6 +9,7 @@ import { localize } from 'vs/nls';
|
||||
import { EditorInput } from 'vs/workbench/common/editor';
|
||||
import { IExtension } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { join } from 'vs/base/common/path';
|
||||
|
||||
export class ExtensionsInput extends EditorInput {
|
||||
|
||||
@@ -17,7 +18,7 @@ export class ExtensionsInput extends EditorInput {
|
||||
get resource() {
|
||||
return URI.from({
|
||||
scheme: Schemas.extension,
|
||||
path: this.extension.identifier.id
|
||||
path: join(this.extension.identifier.id, 'extension')
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,9 @@ import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IExtensionManagementService, ILocalExtension, IExtensionIdentifier, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionRecommendationsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
@@ -123,5 +124,5 @@ export async function getInstalledExtensions(accessor: ServicesAccessor): Promis
|
||||
|
||||
export function isKeymapExtension(tipsService: IExtensionRecommendationsService, extension: IExtensionStatus): boolean {
|
||||
const cats = extension.local.manifest.categories;
|
||||
return cats && cats.indexOf('Keymaps') !== -1 || tipsService.getKeymapRecommendations().some(({ extensionId }) => areSameExtensions({ id: extensionId }, extension.local.identifier));
|
||||
return cats && cats.indexOf('Keymaps') !== -1 || tipsService.getKeymapRecommendations().some(extensionId => areSameExtensions({ id: extensionId }, extension.local.identifier));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
import { IDebugService } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { randomPort } from 'vs/base/node/ports';
|
||||
|
||||
export class DebugExtensionHostAction extends Action {
|
||||
static readonly ID = 'workbench.extensions.action.debugExtensionHost';
|
||||
static readonly LABEL = nls.localize('debugExtensionHost', "Start Debugging Extension Host");
|
||||
static readonly CSS_CLASS = 'debug-extension-host';
|
||||
|
||||
constructor(
|
||||
@IDebugService private readonly _debugService: IDebugService,
|
||||
@INativeHostService private readonly _nativeHostService: INativeHostService,
|
||||
@IDialogService private readonly _dialogService: IDialogService,
|
||||
@IExtensionService private readonly _extensionService: IExtensionService,
|
||||
@IProductService private readonly productService: IProductService
|
||||
) {
|
||||
super(DebugExtensionHostAction.ID, DebugExtensionHostAction.LABEL, DebugExtensionHostAction.CSS_CLASS);
|
||||
}
|
||||
|
||||
async run(): Promise<any> {
|
||||
|
||||
const inspectPort = await this._extensionService.getInspectPort(false);
|
||||
if (!inspectPort) {
|
||||
const res = await this._dialogService.confirm({
|
||||
type: 'info',
|
||||
message: nls.localize('restart1', "Profile Extensions"),
|
||||
detail: nls.localize('restart2', "In order to profile extensions a restart is required. Do you want to restart '{0}' now?", this.productService.nameLong),
|
||||
primaryButton: nls.localize('restart3', "&&Restart"),
|
||||
secondaryButton: nls.localize('cancel', "&&Cancel")
|
||||
});
|
||||
if (res.confirmed) {
|
||||
await this._nativeHostService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] });
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return this._debugService.startDebugging(undefined, {
|
||||
type: 'node',
|
||||
name: nls.localize('debugExtensionHost.launch.name', "Attach Extension Host"),
|
||||
request: 'attach',
|
||||
port: inspectPort
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,11 @@ import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar';
|
||||
import { IExtensionHostProfileService, ProfileSessionState } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
|
||||
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { randomPort } from 'vs/base/node/ports';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput';
|
||||
import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
@@ -46,7 +46,7 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio
|
||||
@IExtensionService private readonly _extensionService: IExtensionService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IElectronService private readonly _electronService: IElectronService,
|
||||
@INativeHostService private readonly _nativeHostService: INativeHostService,
|
||||
@IDialogService private readonly _dialogService: IDialogService,
|
||||
@IStatusbarService private readonly _statusbarService: IStatusbarService,
|
||||
@IProductService private readonly _productService: IProductService
|
||||
@@ -56,9 +56,9 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio
|
||||
this._profileSession = null;
|
||||
this._setState(ProfileSessionState.None);
|
||||
|
||||
CommandsRegistry.registerCommand('workbench.action.extensionHostProfilder.stop', () => {
|
||||
CommandsRegistry.registerCommand('workbench.action.extensionHostProfiler.stop', () => {
|
||||
this.stopProfiling();
|
||||
this._editorService.openEditor(RuntimeExtensionsInput.instance, { revealIfOpened: true });
|
||||
this._editorService.openEditor(RuntimeExtensionsInput.instance, { revealIfOpened: true, pinned: true });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -82,16 +82,17 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio
|
||||
|
||||
if (visible) {
|
||||
const indicator: IStatusbarEntry = {
|
||||
text: '$(sync~spin) ' + nls.localize('profilingExtensionHost', "Profiling Extension Host"),
|
||||
text: nls.localize('profilingExtensionHost', "Profiling Extension Host"),
|
||||
showProgress: true,
|
||||
ariaLabel: nls.localize('profilingExtensionHost', "Profiling Extension Host"),
|
||||
tooltip: nls.localize('selectAndStartDebug', "Click to stop profiling."),
|
||||
command: 'workbench.action.extensionHostProfilder.stop'
|
||||
command: 'workbench.action.extensionHostProfiler.stop'
|
||||
};
|
||||
|
||||
const timeStarted = Date.now();
|
||||
const handle = setInterval(() => {
|
||||
if (this.profilingStatusBarIndicator) {
|
||||
this.profilingStatusBarIndicator.update({ ...indicator, text: '$(sync~spin) ' + nls.localize('profilingExtensionHostTime', "Profiling Extension Host ({0} sec)", Math.round((new Date().getTime() - timeStarted) / 1000)), });
|
||||
this.profilingStatusBarIndicator.update({ ...indicator, text: nls.localize('profilingExtensionHostTime', "Profiling Extension Host ({0} sec)", Math.round((new Date().getTime() - timeStarted) / 1000)), });
|
||||
}
|
||||
}, 1000);
|
||||
this.profilingStatusBarIndicatorLabelUpdater.value = toDisposable(() => clearInterval(handle));
|
||||
@@ -120,11 +121,11 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio
|
||||
type: 'info',
|
||||
message: nls.localize('restart1', "Profile Extensions"),
|
||||
detail: nls.localize('restart2', "In order to profile extensions a restart is required. Do you want to restart '{0}' now?", this._productService.nameLong),
|
||||
primaryButton: nls.localize('restart3', "Restart"),
|
||||
secondaryButton: nls.localize('cancel', "Cancel")
|
||||
primaryButton: nls.localize('restart3', "&&Restart"),
|
||||
secondaryButton: nls.localize('cancel', "&&Cancel")
|
||||
}).then(res => {
|
||||
if (res.confirmed) {
|
||||
this._electronService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] });
|
||||
this._nativeHostService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,38 +13,34 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { RuntimeExtensionsEditor, ShowRuntimeExtensionsAction, IExtensionHostProfileService, DebugExtensionHostAction, StartExtensionHostProfileAction, StopExtensionHostProfileAction, CONTEXT_PROFILE_SESSION_STATE, SaveExtensionHostProfileAction, CONTEXT_EXTENSION_HOST_PROFILE_RECORDED } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { RuntimeExtensionsEditor, IExtensionHostProfileService, StartExtensionHostProfileAction, StopExtensionHostProfileAction, CONTEXT_PROFILE_SESSION_STATE, CONTEXT_EXTENSION_HOST_PROFILE_RECORDED, SaveExtensionHostProfileAction } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor';
|
||||
import { DebugExtensionHostAction } from 'vs/workbench/contrib/extensions/electron-browser/debugExtensionHostAction';
|
||||
import { EditorInput, IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, ActiveEditorContext } from 'vs/workbench/common/editor';
|
||||
import { ExtensionHostProfileService } from 'vs/workbench/contrib/extensions/electron-browser/extensionProfileService';
|
||||
import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput';
|
||||
import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
|
||||
import { OpenExtensionsFolderAction } from 'vs/workbench/contrib/extensions/electron-sandbox/extensionsActions';
|
||||
import { ExtensionsLabel } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService';
|
||||
import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
|
||||
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
|
||||
import { ExtensionRecommendationNotificationServiceChannel } from 'vs/platform/extensionRecommendations/electron-sandbox/extensionRecommendationsIpc';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
|
||||
// Singletons
|
||||
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); // TODO@sandbox TODO@ben move back into common/extensions.contribution.ts when 'semver-umd' can be loaded
|
||||
registerSingleton(IExtensionHostProfileService, ExtensionHostProfileService, true);
|
||||
|
||||
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||
workbenchRegistry.registerWorkbenchContribution(ExtensionsAutoProfiler, LifecyclePhase.Eventually);
|
||||
|
||||
// Running Extensions Editor
|
||||
|
||||
const runtimeExtensionsEditorDescriptor = EditorDescriptor.create(
|
||||
RuntimeExtensionsEditor,
|
||||
RuntimeExtensionsEditor.ID,
|
||||
localize('runtimeExtension', "Running Extensions")
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
EditorDescriptor.create(RuntimeExtensionsEditor, RuntimeExtensionsEditor.ID, localize('runtimeExtension', "Running Extensions")),
|
||||
[new SyncDescriptor(RuntimeExtensionsInput)]
|
||||
);
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
|
||||
.registerEditor(runtimeExtensionsEditorDescriptor, [new SyncDescriptor(RuntimeExtensionsInput)]);
|
||||
|
||||
class RuntimeExtensionsInputFactory implements IEditorInputFactory {
|
||||
canSerialize(editorInput: EditorInput): boolean {
|
||||
return true;
|
||||
@@ -63,17 +59,16 @@ Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactor
|
||||
// Global actions
|
||||
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionExtensions.WorkbenchActions);
|
||||
|
||||
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ShowRuntimeExtensionsAction), 'Show Running Extensions', localize({ key: 'developer', comment: ['A developer on Code itself or someone diagnosing issues in Code'] }, "Developer"));
|
||||
|
||||
class ExtensionsContributions implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@IWorkbenchEnvironmentService workbenchEnvironmentService: INativeWorkbenchEnvironmentService
|
||||
@INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService,
|
||||
@IExtensionRecommendationNotificationService extensionRecommendationNotificationService: IExtensionRecommendationNotificationService,
|
||||
@ISharedProcessService sharedProcessService: ISharedProcessService,
|
||||
) {
|
||||
if (workbenchEnvironmentService.extensionsPath) {
|
||||
const openExtensionsFolderActionDescriptor = SyncActionDescriptor.from(OpenExtensionsFolderAction);
|
||||
actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel);
|
||||
}
|
||||
sharedProcessService.registerChannel('IExtensionRecommendationNotificationService', new ExtensionRecommendationNotificationServiceChannel(extensionRecommendationNotificationService));
|
||||
const openExtensionsFolderActionDescriptor = SyncActionDescriptor.from(OpenExtensionsFolderAction);
|
||||
actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,9 +102,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
command: {
|
||||
id: DebugExtensionHostAction.ID,
|
||||
title: DebugExtensionHostAction.LABEL,
|
||||
icon: {
|
||||
id: 'codicon/debug-start'
|
||||
}
|
||||
icon: Codicon.debugStart
|
||||
},
|
||||
group: 'navigation',
|
||||
when: ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID)
|
||||
@@ -119,9 +112,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
command: {
|
||||
id: StartExtensionHostProfileAction.ID,
|
||||
title: StartExtensionHostProfileAction.LABEL,
|
||||
icon: {
|
||||
id: 'codicon/circle-filled'
|
||||
}
|
||||
icon: Codicon.circleFilled
|
||||
},
|
||||
group: 'navigation',
|
||||
when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID), CONTEXT_PROFILE_SESSION_STATE.notEqualsTo('running'))
|
||||
@@ -131,9 +122,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
command: {
|
||||
id: StopExtensionHostProfileAction.ID,
|
||||
title: StopExtensionHostProfileAction.LABEL,
|
||||
icon: {
|
||||
id: 'codicon/debug-stop'
|
||||
}
|
||||
icon: Codicon.debugStop
|
||||
},
|
||||
group: 'navigation',
|
||||
when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID), CONTEXT_PROFILE_SESSION_STATE.isEqualTo('running'))
|
||||
@@ -143,9 +132,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
command: {
|
||||
id: SaveExtensionHostProfileAction.ID,
|
||||
title: SaveExtensionHostProfileAction.LABEL,
|
||||
icon: {
|
||||
id: 'codicon/save-all'
|
||||
},
|
||||
icon: Codicon.saveAll,
|
||||
precondition: CONTEXT_EXTENSION_HOST_PROFILE_RECORDED
|
||||
},
|
||||
group: 'navigation',
|
||||
|
||||
@@ -10,18 +10,18 @@ import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import * as os from 'os';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { writeFile } from 'vs/base/node/pfs';
|
||||
import { IExtensionHostProfileService } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput';
|
||||
import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { createSlowExtensionAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions';
|
||||
import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler';
|
||||
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
|
||||
|
||||
export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
@@ -36,6 +36,7 @@ export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchCont
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@INativeWorkbenchEnvironmentService private readonly _environmentServie: INativeWorkbenchEnvironmentService
|
||||
) {
|
||||
super();
|
||||
this._register(_extensionService.onDidChangeResponsiveChange(this._onDidChangeResponsiveChange, this));
|
||||
@@ -138,7 +139,7 @@ export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchCont
|
||||
|
||||
|
||||
// print message to log
|
||||
const path = join(os.tmpdir(), `exthost-${Math.random().toString(16).slice(2, 8)}.cpuprofile`);
|
||||
const path = joinPath(this._environmentServie.tmpDir, `exthost-${Math.random().toString(16).slice(2, 8)}.cpuprofile`).fsPath;
|
||||
await writeFile(path, JSON.stringify(profile.data));
|
||||
this._logService.warn(`UNRESPONSIVE extension host, '${top.id}' took ${top!.percentage}% of ${duration / 1e3}ms, saved PROFILE here: '${path}'`, data);
|
||||
|
||||
@@ -186,7 +187,7 @@ export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchCont
|
||||
),
|
||||
[{
|
||||
label: localize('show', 'Show Extensions'),
|
||||
run: () => this._editorService.openEditor(RuntimeExtensionsInput.instance)
|
||||
run: () => this._editorService.openEditor(RuntimeExtensionsInput.instance, { pinned: true })
|
||||
},
|
||||
action
|
||||
],
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as os from 'os';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
@@ -13,11 +12,13 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati
|
||||
import { localize } from 'vs/nls';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IRequestService, asText } from 'vs/platform/request/common/request';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
|
||||
|
||||
abstract class RepoInfo {
|
||||
abstract get base(): string;
|
||||
@@ -119,7 +120,9 @@ class ReportExtensionSlowAction extends Action {
|
||||
readonly profile: IExtensionHostProfile,
|
||||
@IDialogService private readonly _dialogService: IDialogService,
|
||||
@IOpenerService private readonly _openerService: IOpenerService,
|
||||
@IProductService private readonly _productService: IProductService
|
||||
@IProductService private readonly _productService: IProductService,
|
||||
@INativeHostService private readonly _nativeHostService: INativeHostService,
|
||||
@INativeWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService
|
||||
) {
|
||||
super('report.slow', localize('cmd.report', "Report Issue"));
|
||||
}
|
||||
@@ -129,12 +132,13 @@ class ReportExtensionSlowAction extends Action {
|
||||
// rewrite pii (paths) and store on disk
|
||||
const profiler = await import('v8-inspect-profiler');
|
||||
const data = profiler.rewriteAbsolutePaths({ profile: <any>this.profile.data }, 'pii_removed');
|
||||
const path = join(os.homedir(), `${this.extension.identifier.value}-unresponsive.cpuprofile.txt`);
|
||||
const path = joinPath(this._environmentService.tmpDir, `${this.extension.identifier.value}-unresponsive.cpuprofile.txt`).fsPath;
|
||||
await profiler.writeProfile(data, path).then(undefined, onUnexpectedError);
|
||||
|
||||
// build issue
|
||||
const os = await this._nativeHostService.getOSProperties();
|
||||
const title = encodeURIComponent('Extension causes high cpu load');
|
||||
const osVersion = `${os.type()} ${os.arch()} ${os.release()}`;
|
||||
const osVersion = `${os.type} ${os.arch} ${os.release}`;
|
||||
const message = `:warning: Make sure to **attach** this file from your *home*-directory:\n:warning:\`${path}\`\n\nFind more details here: https://github.com/microsoft/vscode/wiki/Explain-extension-causes-high-cpu-load`;
|
||||
const body = encodeURIComponent(`- Issue Type: \`Performance\`
|
||||
- Extension Name: \`${this.extension.name}\`
|
||||
@@ -161,7 +165,8 @@ class ShowExtensionSlowAction extends Action {
|
||||
readonly repoInfo: RepoInfo,
|
||||
readonly profile: IExtensionHostProfile,
|
||||
@IDialogService private readonly _dialogService: IDialogService,
|
||||
@IOpenerService private readonly _openerService: IOpenerService
|
||||
@IOpenerService private readonly _openerService: IOpenerService,
|
||||
@INativeWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService
|
||||
) {
|
||||
super('show.slow', localize('cmd.show', "Show Issues"));
|
||||
}
|
||||
@@ -171,7 +176,7 @@ class ShowExtensionSlowAction extends Action {
|
||||
// rewrite pii (paths) and store on disk
|
||||
const profiler = await import('v8-inspect-profiler');
|
||||
const data = profiler.rewriteAbsolutePaths({ profile: <any>this.profile.data }, 'pii_removed');
|
||||
const path = join(os.homedir(), `${this.extension.identifier.value}-unresponsive.cpuprofile.txt`);
|
||||
const path = joinPath(this._environmentService.tmpDir, `${this.extension.identifier.value}-unresponsive.cpuprofile.txt`).fsPath;
|
||||
await profiler.writeProfile(data, path).then(undefined, onUnexpectedError);
|
||||
|
||||
// show issues
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IExtension } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IExtensionsStatus, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
import { ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export class ReportExtensionIssueAction extends Action {
|
||||
|
||||
private static readonly _id = 'workbench.extensions.action.reportExtensionIssue';
|
||||
private static readonly _label = nls.localize('reportExtensionIssue', "Report Issue");
|
||||
|
||||
private _url: string | undefined;
|
||||
|
||||
constructor(
|
||||
private extension: {
|
||||
description: IExtensionDescription;
|
||||
marketplaceInfo: IExtension;
|
||||
status?: IExtensionsStatus;
|
||||
unresponsiveProfile?: IExtensionHostProfile
|
||||
},
|
||||
@IOpenerService private readonly openerService: IOpenerService,
|
||||
@IClipboardService private readonly clipboardService: IClipboardService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@INativeHostService private readonly nativeHostService: INativeHostService
|
||||
) {
|
||||
super(ReportExtensionIssueAction._id, ReportExtensionIssueAction._label, 'extension-action report-issue');
|
||||
this.enabled = !!extension.description.repository && !!extension.description.repository.url;
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
if (!this._url) {
|
||||
this._url = await this._generateNewIssueUrl(this.extension);
|
||||
}
|
||||
this.openerService.open(URI.parse(this._url));
|
||||
}
|
||||
|
||||
private async _generateNewIssueUrl(extension: {
|
||||
description: IExtensionDescription;
|
||||
marketplaceInfo: IExtension;
|
||||
status?: IExtensionsStatus;
|
||||
unresponsiveProfile?: IExtensionHostProfile
|
||||
}): Promise<string> {
|
||||
let baseUrl = extension.marketplaceInfo && extension.marketplaceInfo.type === ExtensionType.User && extension.description.repository ? extension.description.repository.url : undefined;
|
||||
if (!!baseUrl) {
|
||||
baseUrl = `${baseUrl.indexOf('.git') !== -1 ? baseUrl.substr(0, baseUrl.length - 4) : baseUrl}/issues/new/`;
|
||||
} else {
|
||||
baseUrl = this.productService.reportIssueUrl!;
|
||||
}
|
||||
|
||||
let reason = 'Bug';
|
||||
let title = 'Extension issue';
|
||||
let message = ':warning: We have written the needed data into your clipboard. Please paste! :warning:';
|
||||
this.clipboardService.writeText('```json \n' + JSON.stringify(extension.status, null, '\t') + '\n```');
|
||||
|
||||
const os = await this.nativeHostService.getOSProperties();
|
||||
const osVersion = `${os.type} ${os.arch} ${os.release}`;
|
||||
const queryStringPrefix = baseUrl.indexOf('?') === -1 ? '?' : '&';
|
||||
const body = encodeURIComponent(
|
||||
`- Issue Type: \`${reason}\`
|
||||
- Extension Name: \`${extension.description.name}\`
|
||||
- Extension Version: \`${extension.description.version}\`
|
||||
- OS Version: \`${osVersion}\`
|
||||
- VSCode version: \`${this.productService.version}\`\n\n${message}`
|
||||
);
|
||||
|
||||
return `${baseUrl}${queryStringPrefix}body=${body}&title=${encodeURIComponent(title)}`;
|
||||
}
|
||||
}
|
||||
@@ -3,50 +3,28 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/runtimeExtensionsEditor';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as os from 'os';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { Action, IAction, Separator } from 'vs/base/common/actions';
|
||||
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IExtensionService, IExtensionsStatus, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import { WorkbenchList } from 'vs/platform/list/browser/listService';
|
||||
import { append, $, reset, Dimension, clearNode } from 'vs/base/browser/dom';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionService, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
|
||||
import { writeFile } from 'vs/base/node/pfs';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput';
|
||||
import { IDebugService } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { randomPort } from 'vs/base/node/ports';
|
||||
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { renderCodicons } from 'vs/base/browser/codicons';
|
||||
import { ExtensionIdentifier, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { SlowExtensionAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { ReportExtensionIssueAction } from 'vs/workbench/contrib/extensions/electron-browser/reportExtensionIssueAction';
|
||||
import { AbstractRuntimeExtensionsEditor, IRuntimeExtension } from 'vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { editorBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
|
||||
export const IExtensionHostProfileService = createDecorator<IExtensionHostProfileService>('extensionHostProfileService');
|
||||
export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey<string>('profileSessionState', 'none');
|
||||
@@ -75,63 +53,31 @@ export interface IExtensionHostProfileService {
|
||||
setUnresponsiveProfile(extensionId: ExtensionIdentifier, profile: IExtensionHostProfile): void;
|
||||
}
|
||||
|
||||
interface IExtensionProfileInformation {
|
||||
/**
|
||||
* segment when the extension was running.
|
||||
* 2*i = segment start time
|
||||
* 2*i+1 = segment end time
|
||||
*/
|
||||
segments: number[];
|
||||
/**
|
||||
* total time when the extension was running.
|
||||
* (sum of all segment lengths).
|
||||
*/
|
||||
totalTime: number;
|
||||
}
|
||||
export class RuntimeExtensionsEditor extends AbstractRuntimeExtensionsEditor {
|
||||
|
||||
interface IRuntimeExtension {
|
||||
originalIndex: number;
|
||||
description: IExtensionDescription;
|
||||
marketplaceInfo: IExtension;
|
||||
status: IExtensionsStatus;
|
||||
profileInfo?: IExtensionProfileInformation;
|
||||
unresponsiveProfile?: IExtensionHostProfile;
|
||||
}
|
||||
|
||||
export class RuntimeExtensionsEditor extends EditorPane {
|
||||
|
||||
public static readonly ID: string = 'workbench.editor.runtimeExtensions';
|
||||
|
||||
private _list: WorkbenchList<IRuntimeExtension> | null;
|
||||
private _profileInfo: IExtensionHostProfile | null;
|
||||
|
||||
private _elements: IRuntimeExtension[] | null;
|
||||
private _extensionsDescriptions: IExtensionDescription[];
|
||||
private _updateSoon: RunOnceScheduler;
|
||||
private _profileSessionState: IContextKey<string>;
|
||||
private _extensionsHostRecorded: IContextKey<boolean>;
|
||||
private _profileSessionState: IContextKey<string>;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IExtensionsWorkbenchService private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IExtensionService private readonly _extensionService: IExtensionService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService,
|
||||
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ILabelService private readonly _labelService: ILabelService,
|
||||
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
|
||||
@IOpenerService private readonly _openerService: IOpenerService,
|
||||
@IClipboardService private readonly _clipboardService: IClipboardService,
|
||||
@IProductService private readonly _productService: IProductService
|
||||
@ILabelService labelService: ILabelService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService,
|
||||
) {
|
||||
super(RuntimeExtensionsEditor.ID, telemetryService, themeService, storageService);
|
||||
|
||||
this._list = null;
|
||||
super(telemetryService, themeService, contextKeyService, extensionsWorkbenchService, extensionService, notificationService, contextMenuService, instantiationService, storageService, labelService, environmentService);
|
||||
this._profileInfo = this._extensionHostProfileService.lastProfile;
|
||||
this._extensionsHostRecorded = CONTEXT_EXTENSION_HOST_PROFILE_RECORDED.bindTo(contextKeyService);
|
||||
this._profileSessionState = CONTEXT_PROFILE_SESSION_STATE.bindTo(contextKeyService);
|
||||
|
||||
this._register(this._extensionHostProfileService.onDidChangeLastProfile(() => {
|
||||
this._profileInfo = this._extensionHostProfileService.lastProfile;
|
||||
this._extensionsHostRecorded.set(!!this._profileInfo);
|
||||
@@ -141,483 +87,39 @@ export class RuntimeExtensionsEditor extends EditorPane {
|
||||
const state = this._extensionHostProfileService.state;
|
||||
this._profileSessionState.set(ProfileSessionState[state].toLowerCase());
|
||||
}));
|
||||
|
||||
this._elements = null;
|
||||
|
||||
this._extensionsDescriptions = [];
|
||||
this._updateExtensions();
|
||||
|
||||
this._profileSessionState = CONTEXT_PROFILE_SESSION_STATE.bindTo(contextKeyService);
|
||||
this._extensionsHostRecorded = CONTEXT_EXTENSION_HOST_PROFILE_RECORDED.bindTo(contextKeyService);
|
||||
|
||||
this._updateSoon = this._register(new RunOnceScheduler(() => this._updateExtensions(), 200));
|
||||
|
||||
this._extensionService.getExtensions().then((extensions) => {
|
||||
// We only deal with extensions with source code!
|
||||
this._extensionsDescriptions = extensions.filter((extension) => {
|
||||
return Boolean(extension.main) || Boolean(extension.browser);
|
||||
});
|
||||
this._updateExtensions();
|
||||
});
|
||||
this._register(this._extensionService.onDidChangeExtensionsStatus(() => this._updateSoon.schedule()));
|
||||
}
|
||||
|
||||
private async _updateExtensions(): Promise<void> {
|
||||
this._elements = await this._resolveExtensions();
|
||||
if (this._list) {
|
||||
this._list.splice(0, this._list.length, this._elements);
|
||||
}
|
||||
protected _getProfileInfo(): IExtensionHostProfile | null {
|
||||
return this._profileInfo;
|
||||
}
|
||||
|
||||
private async _resolveExtensions(): Promise<IRuntimeExtension[]> {
|
||||
let marketplaceMap: { [id: string]: IExtension; } = Object.create(null);
|
||||
const marketPlaceExtensions = await this._extensionsWorkbenchService.queryLocal();
|
||||
for (let extension of marketPlaceExtensions) {
|
||||
marketplaceMap[ExtensionIdentifier.toKey(extension.identifier.id)] = extension;
|
||||
}
|
||||
|
||||
let statusMap = this._extensionService.getExtensionsStatus();
|
||||
|
||||
// group profile segments by extension
|
||||
let segments: { [id: string]: number[]; } = Object.create(null);
|
||||
|
||||
if (this._profileInfo) {
|
||||
let currentStartTime = this._profileInfo.startTime;
|
||||
for (let i = 0, len = this._profileInfo.deltas.length; i < len; i++) {
|
||||
const id = this._profileInfo.ids[i];
|
||||
const delta = this._profileInfo.deltas[i];
|
||||
|
||||
let extensionSegments = segments[ExtensionIdentifier.toKey(id)];
|
||||
if (!extensionSegments) {
|
||||
extensionSegments = [];
|
||||
segments[ExtensionIdentifier.toKey(id)] = extensionSegments;
|
||||
}
|
||||
|
||||
extensionSegments.push(currentStartTime);
|
||||
currentStartTime = currentStartTime + delta;
|
||||
extensionSegments.push(currentStartTime);
|
||||
}
|
||||
}
|
||||
|
||||
let result: IRuntimeExtension[] = [];
|
||||
for (let i = 0, len = this._extensionsDescriptions.length; i < len; i++) {
|
||||
const extensionDescription = this._extensionsDescriptions[i];
|
||||
|
||||
let profileInfo: IExtensionProfileInformation | null = null;
|
||||
if (this._profileInfo) {
|
||||
let extensionSegments = segments[ExtensionIdentifier.toKey(extensionDescription.identifier)] || [];
|
||||
let extensionTotalTime = 0;
|
||||
for (let j = 0, lenJ = extensionSegments.length / 2; j < lenJ; j++) {
|
||||
const startTime = extensionSegments[2 * j];
|
||||
const endTime = extensionSegments[2 * j + 1];
|
||||
extensionTotalTime += (endTime - startTime);
|
||||
}
|
||||
profileInfo = {
|
||||
segments: extensionSegments,
|
||||
totalTime: extensionTotalTime
|
||||
};
|
||||
}
|
||||
|
||||
result[i] = {
|
||||
originalIndex: i,
|
||||
description: extensionDescription,
|
||||
marketplaceInfo: marketplaceMap[ExtensionIdentifier.toKey(extensionDescription.identifier)],
|
||||
status: statusMap[extensionDescription.identifier.value],
|
||||
profileInfo: profileInfo || undefined,
|
||||
unresponsiveProfile: this._extensionHostProfileService.getUnresponsiveProfile(extensionDescription.identifier)
|
||||
};
|
||||
}
|
||||
|
||||
result = result.filter(element => element.status.activationTimes);
|
||||
|
||||
// bubble up extensions that have caused slowness
|
||||
|
||||
const isUnresponsive = (extension: IRuntimeExtension): boolean =>
|
||||
extension.unresponsiveProfile === this._profileInfo;
|
||||
|
||||
const profileTime = (extension: IRuntimeExtension): number =>
|
||||
extension.profileInfo?.totalTime ?? 0;
|
||||
|
||||
const activationTime = (extension: IRuntimeExtension): number =>
|
||||
(extension.status.activationTimes?.codeLoadingTime ?? 0) +
|
||||
(extension.status.activationTimes?.activateCallTime ?? 0);
|
||||
|
||||
result = result.sort((a, b) => {
|
||||
if (isUnresponsive(a) || isUnresponsive(b)) {
|
||||
return +isUnresponsive(b) - +isUnresponsive(a);
|
||||
} else if (profileTime(a) || profileTime(b)) {
|
||||
return profileTime(b) - profileTime(a);
|
||||
} else if (activationTime(a) || activationTime(b)) {
|
||||
return activationTime(b) - activationTime(a);
|
||||
}
|
||||
return a.originalIndex - b.originalIndex;
|
||||
});
|
||||
|
||||
return result;
|
||||
protected _getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile | undefined {
|
||||
return this._extensionHostProfileService.getUnresponsiveProfile(extensionId);
|
||||
}
|
||||
|
||||
protected createEditor(parent: HTMLElement): void {
|
||||
parent.classList.add('runtime-extensions-editor');
|
||||
|
||||
const TEMPLATE_ID = 'runtimeExtensionElementTemplate';
|
||||
|
||||
const delegate = new class implements IListVirtualDelegate<IRuntimeExtension>{
|
||||
getHeight(element: IRuntimeExtension): number {
|
||||
return 62;
|
||||
}
|
||||
getTemplateId(element: IRuntimeExtension): string {
|
||||
return TEMPLATE_ID;
|
||||
}
|
||||
};
|
||||
|
||||
interface IRuntimeExtensionTemplateData {
|
||||
root: HTMLElement;
|
||||
element: HTMLElement;
|
||||
icon: HTMLImageElement;
|
||||
name: HTMLElement;
|
||||
version: HTMLElement;
|
||||
msgContainer: HTMLElement;
|
||||
actionbar: ActionBar;
|
||||
activationTime: HTMLElement;
|
||||
profileTime: HTMLElement;
|
||||
disposables: IDisposable[];
|
||||
elementDisposables: IDisposable[];
|
||||
protected _createSlowExtensionAction(element: IRuntimeExtension): Action | null {
|
||||
if (element.unresponsiveProfile) {
|
||||
return this._instantiationService.createInstance(SlowExtensionAction, element.description, element.unresponsiveProfile);
|
||||
}
|
||||
|
||||
const renderer: IListRenderer<IRuntimeExtension, IRuntimeExtensionTemplateData> = {
|
||||
templateId: TEMPLATE_ID,
|
||||
renderTemplate: (root: HTMLElement): IRuntimeExtensionTemplateData => {
|
||||
const element = append(root, $('.extension'));
|
||||
const iconContainer = append(element, $('.icon-container'));
|
||||
const icon = append(iconContainer, $<HTMLImageElement>('img.icon'));
|
||||
|
||||
const desc = append(element, $('div.desc'));
|
||||
const headerContainer = append(desc, $('.header-container'));
|
||||
const header = append(headerContainer, $('.header'));
|
||||
const name = append(header, $('div.name'));
|
||||
const version = append(header, $('span.version'));
|
||||
|
||||
const msgContainer = append(desc, $('div.msg'));
|
||||
|
||||
const actionbar = new ActionBar(desc, { animated: false });
|
||||
actionbar.onDidRun(({ error }) => error && this._notificationService.error(error));
|
||||
|
||||
|
||||
const timeContainer = append(element, $('.time'));
|
||||
const activationTime = append(timeContainer, $('div.activation-time'));
|
||||
const profileTime = append(timeContainer, $('div.profile-time'));
|
||||
|
||||
const disposables = [actionbar];
|
||||
|
||||
return {
|
||||
root,
|
||||
element,
|
||||
icon,
|
||||
name,
|
||||
version,
|
||||
actionbar,
|
||||
activationTime,
|
||||
profileTime,
|
||||
msgContainer,
|
||||
disposables,
|
||||
elementDisposables: [],
|
||||
};
|
||||
},
|
||||
|
||||
renderElement: (element: IRuntimeExtension, index: number, data: IRuntimeExtensionTemplateData): void => {
|
||||
|
||||
data.elementDisposables = dispose(data.elementDisposables);
|
||||
|
||||
data.root.classList.toggle('odd', index % 2 === 1);
|
||||
|
||||
const onError = Event.once(domEvent(data.icon, 'error'));
|
||||
onError(() => data.icon.src = element.marketplaceInfo.iconUrlFallback, null, data.elementDisposables);
|
||||
data.icon.src = element.marketplaceInfo.iconUrl;
|
||||
|
||||
if (!data.icon.complete) {
|
||||
data.icon.style.visibility = 'hidden';
|
||||
data.icon.onload = () => data.icon.style.visibility = 'inherit';
|
||||
} else {
|
||||
data.icon.style.visibility = 'inherit';
|
||||
}
|
||||
data.name.textContent = element.marketplaceInfo.displayName;
|
||||
data.version.textContent = element.description.version;
|
||||
|
||||
const activationTimes = element.status.activationTimes!;
|
||||
let syncTime = activationTimes.codeLoadingTime + activationTimes.activateCallTime;
|
||||
data.activationTime.textContent = activationTimes.activationReason.startup ? `Startup Activation: ${syncTime}ms` : `Activation: ${syncTime}ms`;
|
||||
|
||||
data.actionbar.clear();
|
||||
if (element.unresponsiveProfile) {
|
||||
data.actionbar.push(this._instantiationService.createInstance(SlowExtensionAction, element.description, element.unresponsiveProfile), { icon: true, label: true });
|
||||
}
|
||||
if (isNonEmptyArray(element.status.runtimeErrors)) {
|
||||
data.actionbar.push(new ReportExtensionIssueAction(element, this._openerService, this._clipboardService, this._productService), { icon: true, label: true });
|
||||
}
|
||||
|
||||
let title: string;
|
||||
const activationId = activationTimes.activationReason.extensionId.value;
|
||||
const activationEvent = activationTimes.activationReason.activationEvent;
|
||||
if (activationEvent === '*') {
|
||||
title = nls.localize('starActivation', "Activated by {0} on start-up", activationId);
|
||||
} else if (/^workspaceContains:/.test(activationEvent)) {
|
||||
let fileNameOrGlob = activationEvent.substr('workspaceContains:'.length);
|
||||
if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) {
|
||||
title = nls.localize({
|
||||
key: 'workspaceContainsGlobActivation',
|
||||
comment: [
|
||||
'{0} will be a glob pattern'
|
||||
]
|
||||
}, "Activated by {1} because a file matching {1} exists in your workspace", fileNameOrGlob, activationId);
|
||||
} else {
|
||||
title = nls.localize({
|
||||
key: 'workspaceContainsFileActivation',
|
||||
comment: [
|
||||
'{0} will be a file name'
|
||||
]
|
||||
}, "Activated by {1} because file {0} exists in your workspace", fileNameOrGlob, activationId);
|
||||
}
|
||||
} else if (/^workspaceContainsTimeout:/.test(activationEvent)) {
|
||||
const glob = activationEvent.substr('workspaceContainsTimeout:'.length);
|
||||
title = nls.localize({
|
||||
key: 'workspaceContainsTimeout',
|
||||
comment: [
|
||||
'{0} will be a glob pattern'
|
||||
]
|
||||
}, "Activated by {1} because searching for {0} took too long", glob, activationId);
|
||||
} else if (activationEvent === 'onStartupFinished') {
|
||||
title = nls.localize({
|
||||
key: 'startupFinishedActivation',
|
||||
comment: [
|
||||
'This refers to an extension. {0} will be an activation event.'
|
||||
]
|
||||
}, "Activated by {0} after start-up finished", activationId);
|
||||
} else if (/^onLanguage:/.test(activationEvent)) {
|
||||
let language = activationEvent.substr('onLanguage:'.length);
|
||||
title = nls.localize('languageActivation', "Activated by {1} because you opened a {0} file", language, activationId);
|
||||
} else {
|
||||
title = nls.localize({
|
||||
key: 'workspaceGenericActivation',
|
||||
comment: [
|
||||
'The {0} placeholder will be an activation event, like e.g. \'language:typescript\', \'debug\', etc.'
|
||||
]
|
||||
}, "Activated by {1} on {0}", activationEvent, activationId);
|
||||
}
|
||||
data.activationTime.title = title;
|
||||
|
||||
clearNode(data.msgContainer);
|
||||
|
||||
if (this._extensionHostProfileService.getUnresponsiveProfile(element.description.identifier)) {
|
||||
const el = $('span', undefined, ...renderCodicons(` $(alert) Unresponsive`));
|
||||
el.title = nls.localize('unresponsive.title', "Extension has caused the extension host to freeze.");
|
||||
data.msgContainer.appendChild(el);
|
||||
}
|
||||
|
||||
if (isNonEmptyArray(element.status.runtimeErrors)) {
|
||||
const el = $('span', undefined, ...renderCodicons(`$(bug) ${nls.localize('errors', "{0} uncaught errors", element.status.runtimeErrors.length)}`));
|
||||
data.msgContainer.appendChild(el);
|
||||
}
|
||||
|
||||
if (element.status.messages && element.status.messages.length > 0) {
|
||||
const el = $('span', undefined, ...renderCodicons(`$(alert) ${element.status.messages[0].message}`));
|
||||
data.msgContainer.appendChild(el);
|
||||
}
|
||||
|
||||
if (element.description.extensionLocation.scheme !== 'file') {
|
||||
const el = $('span', undefined, ...renderCodicons(`$(remote) ${element.description.extensionLocation.authority}`));
|
||||
data.msgContainer.appendChild(el);
|
||||
|
||||
const hostLabel = this._labelService.getHostLabel(Schemas.vscodeRemote, this._environmentService.configuration.remoteAuthority);
|
||||
if (hostLabel) {
|
||||
reset(el, ...renderCodicons(`$(remote) ${hostLabel}`));
|
||||
}
|
||||
}
|
||||
|
||||
if (this._profileInfo && element.profileInfo) {
|
||||
data.profileTime.textContent = `Profile: ${(element.profileInfo.totalTime / 1000).toFixed(2)}ms`;
|
||||
} else {
|
||||
data.profileTime.textContent = '';
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
disposeTemplate: (data: IRuntimeExtensionTemplateData): void => {
|
||||
data.disposables = dispose(data.disposables);
|
||||
}
|
||||
};
|
||||
|
||||
this._list = <WorkbenchList<IRuntimeExtension>>this._instantiationService.createInstance(WorkbenchList,
|
||||
'RuntimeExtensions',
|
||||
parent, delegate, [renderer], {
|
||||
multipleSelectionSupport: false,
|
||||
setRowLineHeight: false,
|
||||
horizontalScrolling: false,
|
||||
overrideStyles: {
|
||||
listBackground: editorBackground
|
||||
},
|
||||
accessibilityProvider: new RuntimeExtensionsEditorAccessibilityProvider()
|
||||
});
|
||||
|
||||
this._list.splice(0, this._list.length, this._elements || undefined);
|
||||
|
||||
this._list.onContextMenu((e) => {
|
||||
if (!e.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actions: IAction[] = [];
|
||||
|
||||
actions.push(new ReportExtensionIssueAction(e.element, this._openerService, this._clipboardService, this._productService));
|
||||
actions.push(new Separator());
|
||||
|
||||
actions.push(new Action('runtimeExtensionsEditor.action.disableWorkspace', nls.localize('disable workspace', "Disable (Workspace)"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.DisabledWorkspace)));
|
||||
actions.push(new Action('runtimeExtensionsEditor.action.disable', nls.localize('disable', "Disable"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.DisabledGlobally)));
|
||||
actions.push(new Separator());
|
||||
|
||||
const state = this._extensionHostProfileService.state;
|
||||
if (state === ProfileSessionState.Running) {
|
||||
actions.push(this._instantiationService.createInstance(StopExtensionHostProfileAction, StopExtensionHostProfileAction.ID, StopExtensionHostProfileAction.LABEL));
|
||||
} else {
|
||||
actions.push(this._instantiationService.createInstance(StartExtensionHostProfileAction, StartExtensionHostProfileAction.ID, StartExtensionHostProfileAction.LABEL));
|
||||
}
|
||||
actions.push(this.saveExtensionHostProfileAction);
|
||||
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => e.anchor,
|
||||
getActions: () => actions
|
||||
});
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get saveExtensionHostProfileAction(): IAction {
|
||||
protected _createReportExtensionIssueAction(element: IRuntimeExtension): Action | null {
|
||||
return this._instantiationService.createInstance(ReportExtensionIssueAction, element);
|
||||
}
|
||||
|
||||
protected _createSaveExtensionHostProfileAction(): Action | null {
|
||||
return this._instantiationService.createInstance(SaveExtensionHostProfileAction, SaveExtensionHostProfileAction.ID, SaveExtensionHostProfileAction.LABEL);
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
if (this._list) {
|
||||
this._list.layout(dimension.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ShowRuntimeExtensionsAction extends Action {
|
||||
static readonly ID = 'workbench.action.showRuntimeExtensions';
|
||||
static readonly LABEL = nls.localize('showRuntimeExtensions', "Show Running Extensions");
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IEditorService private readonly _editorService: IEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public async run(e?: any): Promise<any> {
|
||||
await this._editorService.openEditor(RuntimeExtensionsInput.instance, { revealIfOpened: true });
|
||||
}
|
||||
}
|
||||
|
||||
export class ReportExtensionIssueAction extends Action {
|
||||
|
||||
private static readonly _id = 'workbench.extensions.action.reportExtensionIssue';
|
||||
private static readonly _label = nls.localize('reportExtensionIssue', "Report Issue");
|
||||
|
||||
private readonly _url: string;
|
||||
|
||||
constructor(
|
||||
extension: {
|
||||
description: IExtensionDescription;
|
||||
marketplaceInfo: IExtension;
|
||||
status?: IExtensionsStatus;
|
||||
unresponsiveProfile?: IExtensionHostProfile
|
||||
},
|
||||
@IOpenerService private readonly openerService: IOpenerService,
|
||||
@IClipboardService private readonly clipboardService: IClipboardService,
|
||||
@IProductService private readonly productService: IProductService
|
||||
) {
|
||||
super(ReportExtensionIssueAction._id, ReportExtensionIssueAction._label, 'extension-action report-issue');
|
||||
this.enabled = extension.marketplaceInfo
|
||||
&& extension.marketplaceInfo.type === ExtensionType.User
|
||||
&& !!extension.description.repository && !!extension.description.repository.url;
|
||||
|
||||
this._url = this._generateNewIssueUrl(extension);
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
this.openerService.open(URI.parse(this._url));
|
||||
}
|
||||
|
||||
private _generateNewIssueUrl(extension: {
|
||||
description: IExtensionDescription;
|
||||
marketplaceInfo: IExtension;
|
||||
status?: IExtensionsStatus;
|
||||
unresponsiveProfile?: IExtensionHostProfile
|
||||
}): string {
|
||||
let baseUrl = extension.marketplaceInfo && extension.marketplaceInfo.type === ExtensionType.User && extension.description.repository ? extension.description.repository.url : undefined;
|
||||
if (!!baseUrl) {
|
||||
baseUrl = `${baseUrl.indexOf('.git') !== -1 ? baseUrl.substr(0, baseUrl.length - 4) : baseUrl}/issues/new/`;
|
||||
} else {
|
||||
baseUrl = this.productService.reportIssueUrl!;
|
||||
}
|
||||
|
||||
let reason = 'Bug';
|
||||
let title = 'Extension issue';
|
||||
let message = ':warning: We have written the needed data into your clipboard. Please paste! :warning:';
|
||||
this.clipboardService.writeText('```json \n' + JSON.stringify(extension.status, null, '\t') + '\n```');
|
||||
|
||||
const osVersion = `${os.type()} ${os.arch()} ${os.release()}`;
|
||||
const queryStringPrefix = baseUrl.indexOf('?') === -1 ? '?' : '&';
|
||||
const body = encodeURIComponent(
|
||||
`- Issue Type: \`${reason}\`
|
||||
- Extension Name: \`${extension.description.name}\`
|
||||
- Extension Version: \`${extension.description.version}\`
|
||||
- OS Version: \`${osVersion}\`
|
||||
- VSCode version: \`${this.productService.version}\`\n\n${message}`
|
||||
protected _createProfileAction(): Action | null {
|
||||
const state = this._extensionHostProfileService.state;
|
||||
const profileAction = (
|
||||
state === ProfileSessionState.Running
|
||||
? this._instantiationService.createInstance(StopExtensionHostProfileAction, StopExtensionHostProfileAction.ID, StopExtensionHostProfileAction.LABEL)
|
||||
: this._instantiationService.createInstance(StartExtensionHostProfileAction, StartExtensionHostProfileAction.ID, StartExtensionHostProfileAction.LABEL)
|
||||
);
|
||||
|
||||
return `${baseUrl}${queryStringPrefix}body=${body}&title=${encodeURIComponent(title)}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class DebugExtensionHostAction extends Action {
|
||||
static readonly ID = 'workbench.extensions.action.debugExtensionHost';
|
||||
static readonly LABEL = nls.localize('debugExtensionHost', "Start Debugging Extension Host");
|
||||
static readonly CSS_CLASS = 'debug-extension-host';
|
||||
|
||||
constructor(
|
||||
@IDebugService private readonly _debugService: IDebugService,
|
||||
@IElectronService private readonly _electronService: IElectronService,
|
||||
@IDialogService private readonly _dialogService: IDialogService,
|
||||
@IExtensionService private readonly _extensionService: IExtensionService,
|
||||
@IProductService private readonly productService: IProductService
|
||||
) {
|
||||
super(DebugExtensionHostAction.ID, DebugExtensionHostAction.LABEL, DebugExtensionHostAction.CSS_CLASS);
|
||||
}
|
||||
|
||||
async run(): Promise<any> {
|
||||
|
||||
const inspectPort = await this._extensionService.getInspectPort(false);
|
||||
if (!inspectPort) {
|
||||
const res = await this._dialogService.confirm({
|
||||
type: 'info',
|
||||
message: nls.localize('restart1', "Profile Extensions"),
|
||||
detail: nls.localize('restart2', "In order to profile extensions a restart is required. Do you want to restart '{0}' now?", this.productService.nameLong),
|
||||
primaryButton: nls.localize('restart3', "Restart"),
|
||||
secondaryButton: nls.localize('cancel', "Cancel")
|
||||
});
|
||||
if (res.confirmed) {
|
||||
await this._electronService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] });
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return this._debugService.startDebugging(undefined, {
|
||||
type: 'node',
|
||||
name: nls.localize('debugExtensionHost.launch.name', "Attach Extension Host"),
|
||||
request: 'attach',
|
||||
port: inspectPort
|
||||
});
|
||||
return profileAction;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,9 +164,10 @@ export class SaveExtensionHostProfileAction extends Action {
|
||||
|
||||
constructor(
|
||||
id: string = SaveExtensionHostProfileAction.ID, label: string = SaveExtensionHostProfileAction.LABEL,
|
||||
@IElectronService private readonly _electronService: IElectronService,
|
||||
@INativeHostService private readonly _nativeHostService: INativeHostService,
|
||||
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
|
||||
@IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService,
|
||||
@IFileService private readonly _fileService: IFileService
|
||||
) {
|
||||
super(id, label, undefined, false);
|
||||
this._extensionHostProfileService.onDidChangeLastProfile(() => {
|
||||
@@ -677,7 +180,7 @@ export class SaveExtensionHostProfileAction extends Action {
|
||||
}
|
||||
|
||||
private async _asyncRun(): Promise<any> {
|
||||
let picked = await this._electronService.showSaveDialog({
|
||||
let picked = await this._nativeHostService.showSaveDialog({
|
||||
title: 'Save Extension Host Profile',
|
||||
buttonLabel: 'Save',
|
||||
defaultPath: `CPU-${new Date().toISOString().replace(/[\-:]/g, '')}.cpuprofile`,
|
||||
@@ -702,23 +205,12 @@ export class SaveExtensionHostProfileAction extends Action {
|
||||
// absolute filenames because we don't want to reveal anything
|
||||
// about users. We also append the `.txt` suffix to make it
|
||||
// easier to attach these files to GH issues
|
||||
|
||||
let tmp = profiler.rewriteAbsolutePaths({ profile: dataToWrite as any }, 'piiRemoved');
|
||||
dataToWrite = tmp.profile;
|
||||
|
||||
savePath = savePath + '.txt';
|
||||
}
|
||||
|
||||
return writeFile(savePath, JSON.stringify(profileInfo ? profileInfo.data : {}, null, '\t'));
|
||||
}
|
||||
}
|
||||
|
||||
class RuntimeExtensionsEditorAccessibilityProvider implements IListAccessibilityProvider<IRuntimeExtension> {
|
||||
getWidgetAriaLabel(): string {
|
||||
return nls.localize('runtimeExtensions', "Runtime Extensions");
|
||||
}
|
||||
|
||||
getAriaLabel(element: IRuntimeExtension): string | null {
|
||||
return element.description.name;
|
||||
return this._fileService.writeFile(URI.file(savePath), VSBuffer.fromString(JSON.stringify(profileInfo ? profileInfo.data : {}, null, '\t')));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,8 @@ import { localize } from 'vs/nls';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
|
||||
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
|
||||
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
export class OpenExtensionsFolderAction extends Action {
|
||||
@@ -20,28 +19,26 @@ export class OpenExtensionsFolderAction extends Action {
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IElectronService private readonly electronService: IElectronService,
|
||||
@INativeHostService private readonly nativeHostService: INativeHostService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService
|
||||
@INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService
|
||||
) {
|
||||
super(id, label, undefined, true);
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
if (this.environmentService.extensionsPath) {
|
||||
const extensionsHome = URI.file(this.environmentService.extensionsPath);
|
||||
const file = await this.fileService.resolve(extensionsHome);
|
||||
const extensionsHome = URI.file(this.environmentService.extensionsPath);
|
||||
const file = await this.fileService.resolve(extensionsHome);
|
||||
|
||||
let itemToShow: URI;
|
||||
if (file.children && file.children.length > 0) {
|
||||
itemToShow = file.children[0].resource;
|
||||
} else {
|
||||
itemToShow = extensionsHome;
|
||||
}
|
||||
let itemToShow: URI;
|
||||
if (file.children && file.children.length > 0) {
|
||||
itemToShow = file.children[0].resource;
|
||||
} else {
|
||||
itemToShow = extensionsHome;
|
||||
}
|
||||
|
||||
if (itemToShow.scheme === Schemas.file) {
|
||||
return this.electronService.showItemInFolder(itemToShow.fsPath);
|
||||
}
|
||||
if (itemToShow.scheme === Schemas.file) {
|
||||
return this.nativeHostService.showItemInFolder(itemToShow.fsPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
|
||||
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
@@ -37,12 +37,12 @@ import { TestExtensionEnablementService } from 'vs/workbench/services/extensionM
|
||||
import { IURLService } from 'vs/platform/url/common/url';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { INotificationService, Severity, IPromptChoice, IPromptOptions } from 'vs/platform/notification/common/notification';
|
||||
import { NativeURLService } from 'vs/platform/url/common/urlService';
|
||||
import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
|
||||
import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
|
||||
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
@@ -51,13 +51,17 @@ import { Schemas } from 'vs/base/common/network';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService';
|
||||
import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-sandbox/extensionTipsService';
|
||||
import { ExtensionRecommendationsService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationsService';
|
||||
import { NoOpWorkspaceTagsService } from 'vs/workbench/contrib/tags/browser/workspaceTagsService';
|
||||
import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspaceTags';
|
||||
import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IWorkpsaceExtensionsConfigService, WorkspaceExtensionsConfigService } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig';
|
||||
import { IExtensionIgnoredRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
import { ExtensionIgnoredRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionIgnoredRecommendationsService';
|
||||
import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
|
||||
import { ExtensionRecommendationNotificationService } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService';
|
||||
|
||||
const mockExtensionGallery: IGalleryExtension[] = [
|
||||
aGalleryExtension('MockExtension1', {
|
||||
@@ -183,6 +187,7 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
|
||||
uninstallEvent: Emitter<IExtensionIdentifier>,
|
||||
didUninstallEvent: Emitter<DidUninstallExtensionEvent>;
|
||||
let prompted: boolean;
|
||||
let promptedEmitter = new Emitter<void>();
|
||||
let onModelAddedEvent: Emitter<ITextModel>;
|
||||
let experimentService: TestExperimentService;
|
||||
|
||||
@@ -216,7 +221,6 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
|
||||
instantiationService.stub(IWorkspaceTagsService, new NoOpWorkspaceTagsService());
|
||||
instantiationService.stub(IStorageService, new TestStorageService());
|
||||
instantiationService.stub(ILogService, new NullLogService());
|
||||
instantiationService.stub(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryService());
|
||||
instantiationService.stub(IProductService, <Partial<IProductService>>{
|
||||
extensionTips: {
|
||||
'ms-dotnettools.csharp': '{**/*.cs,**/project.json,**/global.json,**/*.csproj,**/*.sln,**/appsettings.json}',
|
||||
@@ -260,13 +264,14 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
|
||||
class TestNotificationService2 extends TestNotificationService {
|
||||
public prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions) {
|
||||
prompted = true;
|
||||
return null!;
|
||||
promptedEmitter.fire();
|
||||
return super.prompt(severity, message, choices, options);
|
||||
}
|
||||
}
|
||||
|
||||
instantiationService.stub(INotificationService, new TestNotificationService2());
|
||||
|
||||
testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: false, showRecommendationsOnlyOnDemand: false });
|
||||
testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: false });
|
||||
instantiationService.stub(IModelService, <IModelService>{
|
||||
getModels(): any { return []; },
|
||||
onModelAdded: onModelAddedEvent.event
|
||||
@@ -299,11 +304,14 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
|
||||
}, null, '\t'));
|
||||
|
||||
const myWorkspace = testWorkspace(URI.from({ scheme: 'file', path: folderDir }));
|
||||
workspaceService = new TestContextService(myWorkspace);
|
||||
instantiationService.stub(IWorkspaceContextService, workspaceService);
|
||||
const fileService = new FileService(new NullLogService());
|
||||
fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService()));
|
||||
instantiationService.stub(IFileService, fileService);
|
||||
workspaceService = new TestContextService(myWorkspace);
|
||||
instantiationService.stub(IWorkspaceContextService, workspaceService);
|
||||
instantiationService.stub(IWorkpsaceExtensionsConfigService, instantiationService.createInstance(WorkspaceExtensionsConfigService));
|
||||
instantiationService.stub(IExtensionIgnoredRecommendationsService, instantiationService.createInstance(ExtensionIgnoredRecommendationsService));
|
||||
instantiationService.stub(IExtensionRecommendationNotificationService, instantiationService.createInstance(ExtensionRecommendationNotificationService));
|
||||
}
|
||||
|
||||
function testNoPromptForValidRecommendations(recommendations: string[]) {
|
||||
@@ -348,15 +356,14 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
|
||||
test('ExtensionRecommendationsService: Prompt for valid workspace recommendations', async () => {
|
||||
await setUpFolderWorkspace('myFolder', mockTestData.recommendedExtensions);
|
||||
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
|
||||
await testObject.activationPromise;
|
||||
|
||||
await Event.toPromise(promptedEmitter.event);
|
||||
const recommendations = Object.keys(testObject.getAllRecommendationsWithReason());
|
||||
assert.equal(recommendations.length, mockTestData.validRecommendedExtensions.length);
|
||||
mockTestData.validRecommendedExtensions.forEach(x => {
|
||||
assert.equal(recommendations.indexOf(x.toLowerCase()) > -1, true);
|
||||
});
|
||||
|
||||
assert.ok(prompted);
|
||||
});
|
||||
|
||||
test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if they are already installed', () => {
|
||||
@@ -374,6 +381,16 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
|
||||
return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions);
|
||||
});
|
||||
|
||||
test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if ignoreRecommendations is set', () => {
|
||||
testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: true });
|
||||
return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => {
|
||||
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
|
||||
return testObject.activationPromise.then(() => {
|
||||
assert.ok(!prompted);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if showRecommendationsOnlyOnDemand is set', () => {
|
||||
testConfigurationService.setUserConfiguration(ConfigurationKey, { showRecommendationsOnlyOnDemand: true });
|
||||
return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => {
|
||||
@@ -385,14 +402,14 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
|
||||
});
|
||||
|
||||
test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if ignoreRecommendations is set for current workspace', () => {
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE);
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions);
|
||||
});
|
||||
|
||||
test('ExtensionRecommendationsService: No Recommendations of globally ignored recommendations', () => {
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE);
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', '["ms-dotnettools.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]', StorageScope.GLOBAL);
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', '["ms-dotnettools.csharp", "mockpublisher2.mockextension2"]', StorageScope.GLOBAL);
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', '["ms-dotnettools.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]', StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', '["ms-dotnettools.csharp", "mockpublisher2.mockextension2"]', StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
|
||||
return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => {
|
||||
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
|
||||
@@ -409,8 +426,8 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
|
||||
test('ExtensionRecommendationsService: No Recommendations of workspace ignored recommendations', () => {
|
||||
const ignoredRecommendations = ['ms-dotnettools.csharp', 'mockpublisher2.mockextension2']; // ignore a stored recommendation and a workspace recommendation.
|
||||
const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]';
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE);
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL);
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
|
||||
return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, ignoredRecommendations).then(() => {
|
||||
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
|
||||
@@ -424,73 +441,73 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
|
||||
});
|
||||
});
|
||||
|
||||
test('ExtensionRecommendationsService: Able to retrieve collection of all ignored recommendations', () => {
|
||||
test.skip('ExtensionRecommendationsService: Able to retrieve collection of all ignored recommendations', async () => {
|
||||
|
||||
const storageService = instantiationService.get(IStorageService);
|
||||
const workspaceIgnoredRecommendations = ['ms-dotnettools.csharp']; // ignore a stored recommendation and a workspace recommendation.
|
||||
const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]';
|
||||
const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation.
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE);
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL);
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.GLOBAL);
|
||||
storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
storageService.store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
storageService.store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
|
||||
return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, workspaceIgnoredRecommendations).then(() => {
|
||||
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
|
||||
return testObject.activationPromise.then(() => {
|
||||
const recommendations = testObject.getAllRecommendationsWithReason();
|
||||
assert.ok(recommendations['ms-python.python']);
|
||||
await setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, workspaceIgnoredRecommendations);
|
||||
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
|
||||
await testObject.activationPromise;
|
||||
|
||||
assert.ok(!recommendations['mockpublisher2.mockextension2']);
|
||||
assert.ok(!recommendations['ms-dotnettools.csharp']);
|
||||
});
|
||||
});
|
||||
const recommendations = testObject.getAllRecommendationsWithReason();
|
||||
assert.ok(recommendations['ms-python.python'], 'ms-python.python extension shall exist');
|
||||
assert.ok(!recommendations['mockpublisher2.mockextension2'], 'mockpublisher2.mockextension2 extension shall not exist');
|
||||
assert.ok(!recommendations['ms-dotnettools.csharp'], 'ms-dotnettools.csharp extension shall not exist');
|
||||
});
|
||||
|
||||
test('ExtensionRecommendationsService: Able to dynamically ignore/unignore global recommendations', () => {
|
||||
test('ExtensionRecommendationsService: Able to dynamically ignore/unignore global recommendations', async () => {
|
||||
const storageService = instantiationService.get(IStorageService);
|
||||
|
||||
const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]';
|
||||
const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation.
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE);
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL);
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.GLOBAL);
|
||||
storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
storageService.store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
storageService.store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
|
||||
return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => {
|
||||
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
|
||||
return testObject.activationPromise.then(() => {
|
||||
const recommendations = testObject.getAllRecommendationsWithReason();
|
||||
assert.ok(recommendations['ms-python.python']);
|
||||
assert.ok(recommendations['mockpublisher1.mockextension1']);
|
||||
await setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions);
|
||||
const extensionIgnoredRecommendationsService = instantiationService.get(IExtensionIgnoredRecommendationsService);
|
||||
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
|
||||
await testObject.activationPromise;
|
||||
|
||||
assert.ok(!recommendations['mockpublisher2.mockextension2']);
|
||||
let recommendations = testObject.getAllRecommendationsWithReason();
|
||||
assert.ok(recommendations['ms-python.python']);
|
||||
assert.ok(recommendations['mockpublisher1.mockextension1']);
|
||||
assert.ok(!recommendations['mockpublisher2.mockextension2']);
|
||||
|
||||
return testObject.toggleIgnoredRecommendation('mockpublisher1.mockextension1', true);
|
||||
}).then(() => {
|
||||
const recommendations = testObject.getAllRecommendationsWithReason();
|
||||
assert.ok(recommendations['ms-python.python']);
|
||||
extensionIgnoredRecommendationsService.toggleGlobalIgnoredRecommendation('mockpublisher1.mockextension1', true);
|
||||
|
||||
assert.ok(!recommendations['mockpublisher1.mockextension1']);
|
||||
assert.ok(!recommendations['mockpublisher2.mockextension2']);
|
||||
recommendations = testObject.getAllRecommendationsWithReason();
|
||||
assert.ok(recommendations['ms-python.python']);
|
||||
assert.ok(!recommendations['mockpublisher1.mockextension1']);
|
||||
assert.ok(!recommendations['mockpublisher2.mockextension2']);
|
||||
|
||||
return testObject.toggleIgnoredRecommendation('mockpublisher1.mockextension1', false);
|
||||
}).then(() => {
|
||||
const recommendations = testObject.getAllRecommendationsWithReason();
|
||||
assert.ok(recommendations['ms-python.python']);
|
||||
extensionIgnoredRecommendationsService.toggleGlobalIgnoredRecommendation('mockpublisher1.mockextension1', false);
|
||||
|
||||
assert.ok(recommendations['mockpublisher1.mockextension1']);
|
||||
assert.ok(!recommendations['mockpublisher2.mockextension2']);
|
||||
});
|
||||
});
|
||||
recommendations = testObject.getAllRecommendationsWithReason();
|
||||
assert.ok(recommendations['ms-python.python']);
|
||||
assert.ok(recommendations['mockpublisher1.mockextension1']);
|
||||
assert.ok(!recommendations['mockpublisher2.mockextension2']);
|
||||
});
|
||||
|
||||
test('test global extensions are modified and recommendation change event is fired when an extension is ignored', async () => {
|
||||
const storageService = instantiationService.get(IStorageService);
|
||||
const changeHandlerTarget = sinon.spy();
|
||||
const ignoredExtensionId = 'Some.Extension';
|
||||
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE);
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', '["ms-vscode.vscode"]', StorageScope.GLOBAL);
|
||||
storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
storageService.store('extensionsAssistant/ignored_recommendations', '["ms-vscode.vscode"]', StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
|
||||
await setUpFolderWorkspace('myFolder', []);
|
||||
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
|
||||
testObject.onRecommendationChange(changeHandlerTarget);
|
||||
testObject.toggleIgnoredRecommendation(ignoredExtensionId, true);
|
||||
const extensionIgnoredRecommendationsService = instantiationService.get(IExtensionIgnoredRecommendationsService);
|
||||
extensionIgnoredRecommendationsService.onDidChangeGlobalIgnoredRecommendation(changeHandlerTarget);
|
||||
extensionIgnoredRecommendationsService.toggleGlobalIgnoredRecommendation(ignoredExtensionId, true);
|
||||
await testObject.activationPromise;
|
||||
|
||||
assert.ok(changeHandlerTarget.calledOnce);
|
||||
@@ -499,16 +516,16 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
|
||||
|
||||
test('ExtensionRecommendationsService: Get file based recommendations from storage (old format)', () => {
|
||||
const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]';
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL);
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
|
||||
return setUpFolderWorkspace('myFolder', []).then(() => {
|
||||
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
|
||||
return testObject.activationPromise.then(() => {
|
||||
const recommendations = testObject.getFileBasedRecommendations();
|
||||
assert.equal(recommendations.length, 2);
|
||||
assert.ok(recommendations.some(({ extensionId }) => extensionId === 'ms-dotnettools.csharp')); // stored recommendation that exists in product.extensionTips
|
||||
assert.ok(recommendations.some(({ extensionId }) => extensionId === 'ms-python.python')); // stored recommendation that exists in product.extensionImportantTips
|
||||
assert.ok(recommendations.every(({ extensionId }) => extensionId !== 'ms-vscode.vscode-typescript-tslint-plugin')); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips
|
||||
assert.ok(recommendations.some(extensionId => extensionId === 'ms-dotnettools.csharp')); // stored recommendation that exists in product.extensionTips
|
||||
assert.ok(recommendations.some(extensionId => extensionId === 'ms-python.python')); // stored recommendation that exists in product.extensionImportantTips
|
||||
assert.ok(recommendations.every(extensionId => extensionId !== 'ms-vscode.vscode-typescript-tslint-plugin')); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -518,17 +535,17 @@ suite.skip('ExtensionRecommendationsService Test', () => { // {{SQL CARBON EDIT}
|
||||
const now = Date.now();
|
||||
const tenDaysOld = 10 * milliSecondsInADay;
|
||||
const storedRecommendations = `{"ms-dotnettools.csharp": ${now}, "ms-python.python": ${now}, "ms-vscode.vscode-typescript-tslint-plugin": ${now}, "lukehoban.Go": ${tenDaysOld}}`;
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL);
|
||||
instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
|
||||
return setUpFolderWorkspace('myFolder', []).then(() => {
|
||||
testObject = instantiationService.createInstance(ExtensionRecommendationsService);
|
||||
return testObject.activationPromise.then(() => {
|
||||
const recommendations = testObject.getFileBasedRecommendations();
|
||||
assert.equal(recommendations.length, 2);
|
||||
assert.ok(recommendations.some(({ extensionId }) => extensionId === 'ms-dotnettools.csharp')); // stored recommendation that exists in product.extensionTips
|
||||
assert.ok(recommendations.some(({ extensionId }) => extensionId === 'ms-python.python')); // stored recommendation that exists in product.extensionImportantTips
|
||||
assert.ok(recommendations.every(({ extensionId }) => extensionId !== 'ms-vscode.vscode-typescript-tslint-plugin')); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips
|
||||
assert.ok(recommendations.every(({ extensionId }) => extensionId !== 'lukehoban.Go')); //stored recommendation that is older than a week
|
||||
assert.ok(recommendations.some(extensionId => extensionId === 'ms-dotnettools.csharp')); // stored recommendation that exists in product.extensionTips
|
||||
assert.ok(recommendations.some(extensionId => extensionId === 'ms-python.python')); // stored recommendation that exists in product.extensionImportantTips
|
||||
assert.ok(recommendations.every(extensionId => extensionId !== 'ms-vscode.vscode-typescript-tslint-plugin')); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips
|
||||
assert.ok(recommendations.every(extensionId => extensionId !== 'lukehoban.Go')); //stored recommendation that is older than a week
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,7 +12,8 @@ import {
|
||||
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension,
|
||||
DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, InstallOperation, IExtensionTipsService, IGalleryMetadata
|
||||
} from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IExtensionRecommendationsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test';
|
||||
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
|
||||
@@ -22,7 +23,7 @@ import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IPager } from 'vs/base/common/paging';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IExtensionService, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
|
||||
import { TestSharedProcessService } from 'vs/workbench/test/electron-browser/workbenchTestServices';
|
||||
@@ -37,19 +38,21 @@ import { ExtensionIdentifier, IExtensionContributions, ExtensionType, IExtension
|
||||
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ILabelService, IFormatterChangeEvent } from 'vs/platform/label/common/label';
|
||||
import { ExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { ProgressService } from 'vs/workbench/services/progress/browser/progressService';
|
||||
import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test';
|
||||
import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
|
||||
import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-sandbox/extensionTipsService';
|
||||
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { UserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataAutoSyncService';
|
||||
import { IUserDataAutoSyncEnablementService, IUserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSyncResourceEnablementService';
|
||||
|
||||
let instantiationService: TestInstantiationService;
|
||||
let installEvent: Emitter<InstallExtensionEvent>,
|
||||
@@ -73,7 +76,6 @@ async function setupTest() {
|
||||
instantiationService.stub(IWorkspaceContextService, new TestContextService());
|
||||
instantiationService.stub(IConfigurationService, new TestConfigurationService());
|
||||
instantiationService.stub(IProgressService, ProgressService);
|
||||
instantiationService.stub(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryService());
|
||||
instantiationService.stub(IProductService, {});
|
||||
|
||||
instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService);
|
||||
@@ -96,14 +98,18 @@ async function setupTest() {
|
||||
|
||||
instantiationService.stub(IRemoteAgentService, RemoteAgentService);
|
||||
|
||||
instantiationService.stub(IExtensionManagementServerService, new class extends ExtensionManagementServerService {
|
||||
#localExtensionManagementServer: IExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', id: 'vscode-local' };
|
||||
constructor() {
|
||||
super(instantiationService.get(ISharedProcessService), instantiationService.get(IRemoteAgentService), instantiationService.get(ILabelService), instantiationService.get(IExtensionGalleryService), instantiationService.get(IProductService), instantiationService.get(IConfigurationService), instantiationService.get(ILogService));
|
||||
const localExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', id: 'vscode-local' };
|
||||
instantiationService.stub(IExtensionManagementServerService, <Partial<IExtensionManagementServerService>>{
|
||||
get localExtensionManagementServer(): IExtensionManagementServer {
|
||||
return localExtensionManagementServer;
|
||||
},
|
||||
getExtensionManagementServer(extension: IExtension): IExtensionManagementServer | null {
|
||||
if (extension.location.scheme === Schemas.file) {
|
||||
return localExtensionManagementServer;
|
||||
}
|
||||
throw new Error(`Invalid Extension ${extension.location}`);
|
||||
}
|
||||
get localExtensionManagementServer(): IExtensionManagementServer { return this.#localExtensionManagementServer; }
|
||||
set localExtensionManagementServer(server: IExtensionManagementServer) { }
|
||||
}());
|
||||
});
|
||||
|
||||
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
|
||||
instantiationService.stub(ILabelService, { onDidChangeFormatters: new Emitter<IFormatterChangeEvent>().event });
|
||||
@@ -118,6 +124,9 @@ async function setupTest() {
|
||||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{ getExtensions: () => Promise.resolve([]), onDidChangeExtensions: new Emitter<void>().event, canAddExtension: (extension: IExtensionDescription) => false, canRemoveExtension: (extension: IExtensionDescription) => false });
|
||||
(<TestExtensionEnablementService>instantiationService.get(IWorkbenchExtensionEnablementService)).reset();
|
||||
|
||||
instantiationService.stub(IUserDataAutoSyncEnablementService, instantiationService.createInstance(UserDataAutoSyncEnablementService));
|
||||
instantiationService.stub(IUserDataSyncResourceEnablementService, instantiationService.createInstance(UserDataSyncResourceEnablementService));
|
||||
|
||||
instantiationService.set(IExtensionsWorkbenchService, disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)));
|
||||
}
|
||||
|
||||
@@ -152,9 +161,9 @@ suite('ExtensionsActions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('Test Install action when state is installing', () => {
|
||||
test('Test InstallingLabelAction when state is installing', () => {
|
||||
const workbenchService = instantiationService.get(IExtensionsWorkbenchService);
|
||||
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction);
|
||||
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallingLabelAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const gallery = aGalleryExtension('a');
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery));
|
||||
@@ -302,111 +311,6 @@ suite('ExtensionsActions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('Test CombinedInstallAction when there is no extension', () => {
|
||||
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
|
||||
assert.ok(!testObject.enabled);
|
||||
assert.equal('extension-action label prominent install no-extension', testObject.class);
|
||||
});
|
||||
|
||||
test('Test CombinedInstallAction when extension is system extension', () => {
|
||||
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const local = aLocalExtension('a', {}, { type: ExtensionType.System });
|
||||
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
|
||||
|
||||
return instantiationService.get(IExtensionsWorkbenchService).queryLocal()
|
||||
.then(extensions => {
|
||||
testObject.extension = extensions[0];
|
||||
assert.ok(!testObject.enabled);
|
||||
assert.equal('extension-action label prominent install no-extension', testObject.class);
|
||||
});
|
||||
});
|
||||
|
||||
test('Test CombinedInstallAction when installAction is enabled', () => {
|
||||
const workbenchService = instantiationService.get(IExtensionsWorkbenchService);
|
||||
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const gallery = aGalleryExtension('a');
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery));
|
||||
|
||||
return workbenchService.queryGallery(CancellationToken.None)
|
||||
.then((paged) => {
|
||||
testObject.extension = paged.firstPage[0];
|
||||
assert.ok(testObject.enabled);
|
||||
assert.equal('Install', testObject.label);
|
||||
assert.equal('extension-action label prominent install', testObject.class);
|
||||
});
|
||||
});
|
||||
|
||||
test('Test CombinedInstallAction when unInstallAction is enabled', () => {
|
||||
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const local = aLocalExtension('a');
|
||||
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
|
||||
|
||||
return instantiationService.get(IExtensionsWorkbenchService).queryLocal()
|
||||
.then(extensions => {
|
||||
testObject.extension = extensions[0];
|
||||
assert.ok(testObject.enabled);
|
||||
assert.equal('Uninstall', testObject.label);
|
||||
assert.equal('extension-action label uninstall', testObject.class);
|
||||
});
|
||||
});
|
||||
|
||||
test('Test CombinedInstallAction when state is installing', () => {
|
||||
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const workbenchService = instantiationService.get(IExtensionsWorkbenchService);
|
||||
const gallery = aGalleryExtension('a');
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery));
|
||||
return workbenchService.queryGallery(CancellationToken.None)
|
||||
.then((paged) => {
|
||||
testObject.extension = paged.firstPage[0];
|
||||
installEvent.fire({ identifier: gallery.identifier, gallery });
|
||||
|
||||
assert.ok(!testObject.enabled);
|
||||
assert.equal('Installing', testObject.label);
|
||||
assert.equal('extension-action label install installing', testObject.class);
|
||||
});
|
||||
});
|
||||
|
||||
test('Test CombinedInstallAction when state is installing during update', () => {
|
||||
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const local = aLocalExtension('a');
|
||||
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
|
||||
|
||||
return instantiationService.get(IExtensionsWorkbenchService).queryLocal()
|
||||
.then(extensions => {
|
||||
const gallery = aGalleryExtension('a');
|
||||
const extension = extensions[0];
|
||||
extension.gallery = gallery;
|
||||
testObject.extension = extension;
|
||||
installEvent.fire({ identifier: gallery.identifier, gallery });
|
||||
assert.ok(!testObject.enabled);
|
||||
assert.equal('Installing', testObject.label);
|
||||
assert.equal('extension-action label install installing', testObject.class);
|
||||
});
|
||||
});
|
||||
|
||||
test('Test CombinedInstallAction when state is uninstalling', () => {
|
||||
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const local = aLocalExtension('a');
|
||||
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
|
||||
|
||||
return instantiationService.get(IExtensionsWorkbenchService).queryLocal()
|
||||
.then(extensions => {
|
||||
testObject.extension = extensions[0];
|
||||
uninstallEvent.fire(local.identifier);
|
||||
assert.ok(!testObject.enabled);
|
||||
assert.equal('Uninstalling', testObject.label);
|
||||
assert.equal('extension-action label uninstall uninstalling', testObject.class);
|
||||
});
|
||||
});
|
||||
|
||||
test('Test UpdateAction when there is no extension', () => {
|
||||
const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
@@ -515,7 +419,7 @@ suite('ExtensionsActions', () => {
|
||||
.then(extensions => {
|
||||
testObject.extension = extensions[0];
|
||||
assert.ok(testObject.enabled);
|
||||
assert.equal('extension-action icon manage codicon-gear', testObject.class);
|
||||
assert.equal('extension-action icon manage codicon codicon-extensions-manage', testObject.class);
|
||||
assert.equal('', testObject.tooltip);
|
||||
});
|
||||
});
|
||||
@@ -530,7 +434,7 @@ suite('ExtensionsActions', () => {
|
||||
.then(page => {
|
||||
testObject.extension = page.firstPage[0];
|
||||
assert.ok(!testObject.enabled);
|
||||
assert.equal('extension-action icon manage codicon-gear hide', testObject.class);
|
||||
assert.equal('extension-action icon manage codicon codicon-extensions-manage hide', testObject.class);
|
||||
assert.equal('', testObject.tooltip);
|
||||
});
|
||||
});
|
||||
@@ -547,7 +451,7 @@ suite('ExtensionsActions', () => {
|
||||
|
||||
installEvent.fire({ identifier: gallery.identifier, gallery });
|
||||
assert.ok(!testObject.enabled);
|
||||
assert.equal('extension-action icon manage codicon-gear hide', testObject.class);
|
||||
assert.equal('extension-action icon manage codicon codicon-extensions-manage hide', testObject.class);
|
||||
assert.equal('', testObject.tooltip);
|
||||
});
|
||||
});
|
||||
@@ -565,7 +469,7 @@ suite('ExtensionsActions', () => {
|
||||
didInstallEvent.fire({ identifier: gallery.identifier, gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) });
|
||||
|
||||
assert.ok(testObject.enabled);
|
||||
assert.equal('extension-action icon manage codicon-gear', testObject.class);
|
||||
assert.equal('extension-action icon manage codicon codicon-extensions-manage', testObject.class);
|
||||
assert.equal('', testObject.tooltip);
|
||||
});
|
||||
});
|
||||
@@ -580,7 +484,7 @@ suite('ExtensionsActions', () => {
|
||||
.then(extensions => {
|
||||
testObject.extension = extensions[0];
|
||||
assert.ok(testObject.enabled);
|
||||
assert.equal('extension-action icon manage codicon-gear', testObject.class);
|
||||
assert.equal('extension-action icon manage codicon codicon-extensions-manage', testObject.class);
|
||||
assert.equal('', testObject.tooltip);
|
||||
});
|
||||
});
|
||||
@@ -597,7 +501,7 @@ suite('ExtensionsActions', () => {
|
||||
uninstallEvent.fire(local.identifier);
|
||||
|
||||
assert.ok(!testObject.enabled);
|
||||
assert.equal('extension-action icon manage codicon-gear', testObject.class);
|
||||
assert.equal('extension-action icon manage codicon codicon-extensions-manage', testObject.class);
|
||||
assert.equal('Uninstalling', testObject.tooltip);
|
||||
});
|
||||
});
|
||||
@@ -914,25 +818,19 @@ suite('ExtensionsActions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('Test DisableDropDownAction when there is no extension', () => {
|
||||
const testObject: ExtensionsActions.DisableDropDownAction = instantiationService.createInstance(ExtensionsActions.DisableDropDownAction, []);
|
||||
|
||||
assert.ok(!testObject.enabled);
|
||||
});
|
||||
|
||||
test('Test DisableDropDownAction when extension is installed and enabled', () => {
|
||||
test('Test DisableGloballyAction when extension is installed and enabled', () => {
|
||||
const local = aLocalExtension('a');
|
||||
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
|
||||
|
||||
return instantiationService.get(IExtensionsWorkbenchService).queryLocal()
|
||||
.then(extensions => {
|
||||
const testObject: ExtensionsActions.DisableDropDownAction = instantiationService.createInstance(ExtensionsActions.DisableDropDownAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]);
|
||||
const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]);
|
||||
testObject.extension = extensions[0];
|
||||
assert.ok(testObject.enabled);
|
||||
});
|
||||
});
|
||||
|
||||
test('Test DisableDropDownAction when extension is installed and disabled globally', () => {
|
||||
test('Test DisableGloballyAction when extension is installed and disabled globally', () => {
|
||||
const local = aLocalExtension('a');
|
||||
return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally)
|
||||
.then(() => {
|
||||
@@ -940,47 +838,32 @@ suite('ExtensionsActions', () => {
|
||||
|
||||
return instantiationService.get(IExtensionsWorkbenchService).queryLocal()
|
||||
.then(extensions => {
|
||||
const testObject: ExtensionsActions.DisableDropDownAction = instantiationService.createInstance(ExtensionsActions.DisableDropDownAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]);
|
||||
const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]);
|
||||
testObject.extension = extensions[0];
|
||||
assert.ok(!testObject.enabled);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Test DisableDropDownAction when extension is installed and disabled for workspace', () => {
|
||||
const local = aLocalExtension('a');
|
||||
return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledWorkspace)
|
||||
.then(() => {
|
||||
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
|
||||
|
||||
return instantiationService.get(IExtensionsWorkbenchService).queryLocal()
|
||||
.then(extensions => {
|
||||
const testObject: ExtensionsActions.DisableDropDownAction = instantiationService.createInstance(ExtensionsActions.DisableDropDownAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]);
|
||||
testObject.extension = extensions[0];
|
||||
assert.ok(!testObject.enabled);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Test DisableDropDownAction when extension is uninstalled', () => {
|
||||
test('Test DisableGloballyAction when extension is uninstalled', () => {
|
||||
const gallery = aGalleryExtension('a');
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery));
|
||||
|
||||
return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None)
|
||||
.then(page => {
|
||||
const testObject: ExtensionsActions.DisableDropDownAction = instantiationService.createInstance(ExtensionsActions.DisableDropDownAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]);
|
||||
const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]);
|
||||
testObject.extension = page.firstPage[0];
|
||||
assert.ok(!testObject.enabled);
|
||||
});
|
||||
});
|
||||
|
||||
test('Test DisableDropDownAction when extension is installing', () => {
|
||||
test('Test DisableGloballyAction when extension is installing', () => {
|
||||
const gallery = aGalleryExtension('a');
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery));
|
||||
|
||||
return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None)
|
||||
.then(page => {
|
||||
const testObject: ExtensionsActions.DisableDropDownAction = instantiationService.createInstance(ExtensionsActions.DisableDropDownAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]);
|
||||
const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]);
|
||||
testObject.extension = page.firstPage[0];
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
installEvent.fire({ identifier: gallery.identifier, gallery });
|
||||
@@ -988,13 +871,13 @@ suite('ExtensionsActions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('Test DisableDropDownAction when extension is uninstalling', () => {
|
||||
test('Test DisableGloballyAction when extension is uninstalling', () => {
|
||||
const local = aLocalExtension('a');
|
||||
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
|
||||
|
||||
return instantiationService.get(IExtensionsWorkbenchService).queryLocal()
|
||||
.then(extensions => {
|
||||
const testObject: ExtensionsActions.DisableDropDownAction = instantiationService.createInstance(ExtensionsActions.DisableDropDownAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]);
|
||||
const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction, [{ identifier: new ExtensionIdentifier('pub.a'), extensionLocation: URI.file('pub.a') }]);
|
||||
testObject.extension = extensions[0];
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
uninstallEvent.fire(local.identifier);
|
||||
@@ -1003,20 +886,20 @@ suite('ExtensionsActions', () => {
|
||||
});
|
||||
|
||||
test('Test UpdateAllAction when no installed extensions', () => {
|
||||
const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label');
|
||||
const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label', true);
|
||||
|
||||
assert.ok(!testObject.enabled);
|
||||
});
|
||||
|
||||
test('Test UpdateAllAction when installed extensions are not outdated', () => {
|
||||
const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label');
|
||||
const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label', true);
|
||||
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a'), aLocalExtension('b')]);
|
||||
return instantiationService.get(IExtensionsWorkbenchService).queryLocal()
|
||||
.then(extensions => assert.ok(!testObject.enabled));
|
||||
});
|
||||
|
||||
test('Test UpdateAllAction when some installed extensions are outdated', () => {
|
||||
const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label');
|
||||
const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label', true);
|
||||
const local = [aLocalExtension('a', { version: '1.0.1' }), aLocalExtension('b', { version: '1.0.1' }), aLocalExtension('c', { version: '1.0.1' })];
|
||||
const workbenchService = instantiationService.get(IExtensionsWorkbenchService);
|
||||
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', local);
|
||||
@@ -1036,7 +919,7 @@ suite('ExtensionsActions', () => {
|
||||
});
|
||||
|
||||
test('Test UpdateAllAction when some installed extensions are outdated and some outdated are being installed', () => {
|
||||
const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label');
|
||||
const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label', true);
|
||||
const local = [aLocalExtension('a', { version: '1.0.1' }), aLocalExtension('b', { version: '1.0.1' }), aLocalExtension('c', { version: '1.0.1' })];
|
||||
const gallery = [aGalleryExtension('a', { identifier: local[0].identifier, version: '1.0.2' }), aGalleryExtension('b', { identifier: local[1].identifier, version: '1.0.2' }), aGalleryExtension('c', local[2].manifest)];
|
||||
const workbenchService = instantiationService.get(IExtensionsWorkbenchService);
|
||||
@@ -1058,7 +941,7 @@ suite('ExtensionsActions', () => {
|
||||
});
|
||||
|
||||
test('Test UpdateAllAction when some installed extensions are outdated and all outdated are being installed', () => {
|
||||
const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label');
|
||||
const testObject: ExtensionsActions.UpdateAllAction = instantiationService.createInstance(ExtensionsActions.UpdateAllAction, 'id', 'label', true);
|
||||
const local = [aLocalExtension('a', { version: '1.0.1' }), aLocalExtension('b', { version: '1.0.1' }), aLocalExtension('c', { version: '1.0.1' })];
|
||||
const gallery = [aGalleryExtension('a', { identifier: local[0].identifier, version: '1.0.2' }), aGalleryExtension('b', { identifier: local[1].identifier, version: '1.0.2' }), aGalleryExtension('c', local[2].manifest)];
|
||||
const workbenchService = instantiationService.get(IExtensionsWorkbenchService);
|
||||
@@ -1120,7 +1003,7 @@ suite('ReloadAction', () => {
|
||||
|
||||
test('Test ReloadAction when extension is newly installed', async () => {
|
||||
const onDidChangeExtensionsEmitter: Emitter<void> = new Emitter<void>();
|
||||
const runningExtensions = [ExtensionsActions.toExtensionDescription(aLocalExtension('b'))];
|
||||
const runningExtensions = [toExtensionDescription(aLocalExtension('b'))];
|
||||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
getExtensions: () => Promise.resolve(runningExtensions),
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
@@ -1143,7 +1026,7 @@ suite('ReloadAction', () => {
|
||||
|
||||
test('Test ReloadAction when extension is newly installed and reload is not required', async () => {
|
||||
const onDidChangeExtensionsEmitter: Emitter<void> = new Emitter<void>();
|
||||
const runningExtensions = [ExtensionsActions.toExtensionDescription(aLocalExtension('b'))];
|
||||
const runningExtensions = [toExtensionDescription(aLocalExtension('b'))];
|
||||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
getExtensions: () => Promise.resolve(runningExtensions),
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
@@ -1164,7 +1047,7 @@ suite('ReloadAction', () => {
|
||||
});
|
||||
|
||||
test('Test ReloadAction when extension is installed and uninstalled', async () => {
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [ExtensionsActions.toExtensionDescription(aLocalExtension('b'))]);
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [toExtensionDescription(aLocalExtension('b'))]);
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const gallery = aGalleryExtension('a');
|
||||
@@ -1182,7 +1065,7 @@ suite('ReloadAction', () => {
|
||||
});
|
||||
|
||||
test('Test ReloadAction when extension is uninstalled', async () => {
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [ExtensionsActions.toExtensionDescription(aLocalExtension('a', { version: '1.0.0' }))]);
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [toExtensionDescription(aLocalExtension('a', { version: '1.0.0' }))]);
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const local = aLocalExtension('a');
|
||||
@@ -1199,7 +1082,7 @@ suite('ReloadAction', () => {
|
||||
test('Test ReloadAction when extension is uninstalled and can be removed', async () => {
|
||||
const local = aLocalExtension('a');
|
||||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
getExtensions: () => Promise.resolve([ExtensionsActions.toExtensionDescription(local)]),
|
||||
getExtensions: () => Promise.resolve([toExtensionDescription(local)]),
|
||||
onDidChangeExtensions: new Emitter<void>().event,
|
||||
canRemoveExtension: (extension) => true,
|
||||
canAddExtension: (extension) => true
|
||||
@@ -1216,7 +1099,7 @@ suite('ReloadAction', () => {
|
||||
});
|
||||
|
||||
test('Test ReloadAction when extension is uninstalled and installed', async () => {
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [ExtensionsActions.toExtensionDescription(aLocalExtension('a', { version: '1.0.0' }))]);
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [toExtensionDescription(aLocalExtension('a', { version: '1.0.0' }))]);
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const local = aLocalExtension('a');
|
||||
@@ -1236,7 +1119,7 @@ suite('ReloadAction', () => {
|
||||
});
|
||||
|
||||
test('Test ReloadAction when extension is updated while running', async () => {
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [ExtensionsActions.toExtensionDescription(aLocalExtension('a', { version: '1.0.1' }))]);
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [toExtensionDescription(aLocalExtension('a', { version: '1.0.1' }))]);
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const local = aLocalExtension('a', { version: '1.0.1' });
|
||||
@@ -1258,7 +1141,7 @@ suite('ReloadAction', () => {
|
||||
});
|
||||
|
||||
test('Test ReloadAction when extension is updated when not running', async () => {
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [ExtensionsActions.toExtensionDescription(aLocalExtension('b'))]);
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [toExtensionDescription(aLocalExtension('b'))]);
|
||||
const local = aLocalExtension('a', { version: '1.0.1' });
|
||||
await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally);
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
@@ -1276,7 +1159,7 @@ suite('ReloadAction', () => {
|
||||
});
|
||||
|
||||
test('Test ReloadAction when extension is disabled when running', async () => {
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [ExtensionsActions.toExtensionDescription(aLocalExtension('a'))]);
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [toExtensionDescription(aLocalExtension('a'))]);
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const local = aLocalExtension('a');
|
||||
@@ -1292,7 +1175,7 @@ suite('ReloadAction', () => {
|
||||
});
|
||||
|
||||
test('Test ReloadAction when extension enablement is toggled when running', async () => {
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [ExtensionsActions.toExtensionDescription(aLocalExtension('a', { version: '1.0.0' }))]);
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [toExtensionDescription(aLocalExtension('a', { version: '1.0.0' }))]);
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const local = aLocalExtension('a');
|
||||
@@ -1306,7 +1189,7 @@ suite('ReloadAction', () => {
|
||||
});
|
||||
|
||||
test('Test ReloadAction when extension is enabled when not running', async () => {
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [ExtensionsActions.toExtensionDescription(aLocalExtension('b'))]);
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [toExtensionDescription(aLocalExtension('b'))]);
|
||||
const local = aLocalExtension('a');
|
||||
await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally);
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
@@ -1322,7 +1205,7 @@ suite('ReloadAction', () => {
|
||||
});
|
||||
|
||||
test('Test ReloadAction when extension enablement is toggled when not running', async () => {
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [ExtensionsActions.toExtensionDescription(aLocalExtension('b'))]);
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [toExtensionDescription(aLocalExtension('b'))]);
|
||||
const local = aLocalExtension('a');
|
||||
await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally);
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
@@ -1337,7 +1220,7 @@ suite('ReloadAction', () => {
|
||||
});
|
||||
|
||||
test('Test ReloadAction when extension is updated when not running and enabled', async () => {
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [ExtensionsActions.toExtensionDescription(aLocalExtension('b'))]);
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [toExtensionDescription(aLocalExtension('b'))]);
|
||||
const local = aLocalExtension('a', { version: '1.0.1' });
|
||||
await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally);
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
@@ -1357,7 +1240,7 @@ suite('ReloadAction', () => {
|
||||
});
|
||||
|
||||
test('Test ReloadAction when a localization extension is newly installed', async () => {
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [ExtensionsActions.toExtensionDescription(aLocalExtension('b'))]);
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [toExtensionDescription(aLocalExtension('b'))]);
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const gallery = aGalleryExtension('a');
|
||||
@@ -1373,7 +1256,7 @@ suite('ReloadAction', () => {
|
||||
});
|
||||
|
||||
test('Test ReloadAction when a localization extension is updated while running', async () => {
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [ExtensionsActions.toExtensionDescription(aLocalExtension('a', { version: '1.0.1' }))]);
|
||||
instantiationService.stubPromise(IExtensionService, 'getExtensions', [toExtensionDescription(aLocalExtension('a', { version: '1.0.1' }))]);
|
||||
const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const local = aLocalExtension('a', { version: '1.0.1', contributes: <IExtensionContributions>{ localizations: [{ languageId: 'de', translations: [] }] } });
|
||||
@@ -1400,7 +1283,7 @@ suite('ReloadAction', () => {
|
||||
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
|
||||
|
||||
const onDidChangeExtensionsEmitter: Emitter<void> = new Emitter<void>();
|
||||
const runningExtensions = [ExtensionsActions.toExtensionDescription(remoteExtension)];
|
||||
const runningExtensions = [toExtensionDescription(remoteExtension)];
|
||||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
getExtensions: () => Promise.resolve(runningExtensions),
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
@@ -1434,7 +1317,7 @@ suite('ReloadAction', () => {
|
||||
instantiationService.set(IExtensionsWorkbenchService, workbenchService);
|
||||
|
||||
const onDidChangeExtensionsEmitter: Emitter<void> = new Emitter<void>();
|
||||
const runningExtensions = [ExtensionsActions.toExtensionDescription(remoteExtension)];
|
||||
const runningExtensions = [toExtensionDescription(remoteExtension)];
|
||||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
getExtensions: () => Promise.resolve(runningExtensions),
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
@@ -1544,7 +1427,7 @@ suite('ReloadAction', () => {
|
||||
|
||||
const onDidChangeExtensionsEmitter: Emitter<void> = new Emitter<void>();
|
||||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
getExtensions: () => Promise.resolve([ExtensionsActions.toExtensionDescription(localExtension)]),
|
||||
getExtensions: () => Promise.resolve([toExtensionDescription(localExtension)]),
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
canAddExtension: (extension) => false
|
||||
});
|
||||
@@ -1575,7 +1458,7 @@ suite('ReloadAction', () => {
|
||||
|
||||
const onDidChangeExtensionsEmitter: Emitter<void> = new Emitter<void>();
|
||||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
getExtensions: () => Promise.resolve([ExtensionsActions.toExtensionDescription(localExtension)]),
|
||||
getExtensions: () => Promise.resolve([toExtensionDescription(localExtension)]),
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
canAddExtension: (extension) => false
|
||||
});
|
||||
@@ -1606,7 +1489,7 @@ suite('ReloadAction', () => {
|
||||
|
||||
const onDidChangeExtensionsEmitter: Emitter<void> = new Emitter<void>();
|
||||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
getExtensions: () => Promise.resolve([ExtensionsActions.toExtensionDescription(remoteExtension)]),
|
||||
getExtensions: () => Promise.resolve([toExtensionDescription(remoteExtension)]),
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
canAddExtension: (extension) => false
|
||||
});
|
||||
@@ -1637,7 +1520,7 @@ suite('ReloadAction', () => {
|
||||
|
||||
const onDidChangeExtensionsEmitter: Emitter<void> = new Emitter<void>();
|
||||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
getExtensions: () => Promise.resolve([ExtensionsActions.toExtensionDescription(localExtension)]),
|
||||
getExtensions: () => Promise.resolve([toExtensionDescription(localExtension)]),
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
canAddExtension: (extension) => false
|
||||
});
|
||||
@@ -1668,7 +1551,7 @@ suite('ReloadAction', () => {
|
||||
|
||||
const onDidChangeExtensionsEmitter: Emitter<void> = new Emitter<void>();
|
||||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
getExtensions: () => Promise.resolve([ExtensionsActions.toExtensionDescription(remoteExtension)]),
|
||||
getExtensions: () => Promise.resolve([toExtensionDescription(remoteExtension)]),
|
||||
onDidChangeExtensions: onDidChangeExtensionsEmitter.event,
|
||||
canAddExtension: (extension) => false
|
||||
});
|
||||
@@ -1904,6 +1787,7 @@ suite('RemoteInstallAction', () => {
|
||||
const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) });
|
||||
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]));
|
||||
instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService);
|
||||
instantiationService.stub(INativeWorkbenchEnvironmentService, { disableExtensions: true } as INativeWorkbenchEnvironmentService);
|
||||
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
|
||||
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
|
||||
const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService);
|
||||
@@ -2283,6 +2167,7 @@ suite('LocalInstallAction', () => {
|
||||
// multi server setup
|
||||
const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
|
||||
instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService);
|
||||
instantiationService.stub(INativeWorkbenchEnvironmentService, { disableExtensions: true } as INativeWorkbenchEnvironmentService);
|
||||
const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension]));
|
||||
instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService);
|
||||
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
|
||||
@@ -2499,6 +2384,7 @@ function aLocalExtension(name: string = 'someext', manifest: any = {}, propertie
|
||||
identifier: { id: getGalleryExtensionId(manifest.publisher, manifest.name) },
|
||||
...properties
|
||||
};
|
||||
properties.isBuiltin = properties.type === ExtensionType.System;
|
||||
return <ILocalExtension>Object.create({ manifest, ...properties });
|
||||
}
|
||||
|
||||
|
||||
@@ -13,16 +13,17 @@ import {
|
||||
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions,
|
||||
DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, SortBy
|
||||
} from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IExtensionRecommendationsService, ExtensionRecommendationReason, IExtensionRecommendation } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionRecommendationsService, ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test';
|
||||
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
|
||||
import { IURLService } from 'vs/platform/url/common/url';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IPager } from 'vs/base/common/paging';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IExtensionService, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { TestMenuService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { TestSharedProcessService } from 'vs/workbench/test/electron-browser/workbenchTestServices';
|
||||
@@ -35,16 +36,14 @@ import { SinonStub } from 'sinon';
|
||||
import { IExperimentService, ExperimentState, ExperimentActionType, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl';
|
||||
import { ExtensionIdentifier, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionType, IExtension, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
|
||||
import { ExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
|
||||
import { IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
|
||||
import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
suite('ExtensionsListView Tests', () => {
|
||||
|
||||
@@ -101,14 +100,18 @@ suite('ExtensionsListView Tests', () => {
|
||||
instantiationService.stub(IContextKeyService, new MockContextKeyService());
|
||||
instantiationService.stub(IMenuService, new TestMenuService());
|
||||
|
||||
instantiationService.stub(IExtensionManagementServerService, new class extends ExtensionManagementServerService {
|
||||
#localExtensionManagementServer: IExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', id: 'vscode-local' };
|
||||
constructor() {
|
||||
super(instantiationService.get(ISharedProcessService), instantiationService.get(IRemoteAgentService), instantiationService.get(ILabelService), instantiationService.get(IExtensionGalleryService), instantiationService.get(IProductService), instantiationService.get(IConfigurationService), instantiationService.get(ILogService));
|
||||
const localExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', id: 'vscode-local' };
|
||||
instantiationService.stub(IExtensionManagementServerService, <Partial<IExtensionManagementServerService>>{
|
||||
get localExtensionManagementServer(): IExtensionManagementServer {
|
||||
return localExtensionManagementServer;
|
||||
},
|
||||
getExtensionManagementServer(extension: IExtension): IExtensionManagementServer | null {
|
||||
if (extension.location.scheme === Schemas.file) {
|
||||
return localExtensionManagementServer;
|
||||
}
|
||||
throw new Error(`Invalid Extension ${extension.location}`);
|
||||
}
|
||||
get localExtensionManagementServer(): IExtensionManagementServer { return this.#localExtensionManagementServer; }
|
||||
set localExtensionManagementServer(server: IExtensionManagementServer) { }
|
||||
}());
|
||||
});
|
||||
|
||||
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
|
||||
|
||||
@@ -122,28 +125,28 @@ suite('ExtensionsListView Tests', () => {
|
||||
instantiationService.stub(IExtensionRecommendationsService, <Partial<IExtensionRecommendationsService>>{
|
||||
getWorkspaceRecommendations() {
|
||||
return Promise.resolve([
|
||||
{ extensionId: workspaceRecommendationA.identifier.id },
|
||||
{ extensionId: workspaceRecommendationB.identifier.id }]);
|
||||
workspaceRecommendationA.identifier.id,
|
||||
workspaceRecommendationB.identifier.id]);
|
||||
},
|
||||
getConfigBasedRecommendations() {
|
||||
return Promise.resolve({
|
||||
important: [{ extensionId: configBasedRecommendationA.identifier.id }],
|
||||
others: [{ extensionId: configBasedRecommendationB.identifier.id }],
|
||||
important: [configBasedRecommendationA.identifier.id],
|
||||
others: [configBasedRecommendationB.identifier.id],
|
||||
});
|
||||
},
|
||||
getImportantRecommendations(): Promise<IExtensionRecommendation[]> {
|
||||
getImportantRecommendations(): Promise<string[]> {
|
||||
return Promise.resolve([]);
|
||||
},
|
||||
getFileBasedRecommendations() {
|
||||
return [
|
||||
{ extensionId: fileBasedRecommendationA.identifier.id },
|
||||
{ extensionId: fileBasedRecommendationB.identifier.id }
|
||||
fileBasedRecommendationA.identifier.id,
|
||||
fileBasedRecommendationB.identifier.id
|
||||
];
|
||||
},
|
||||
getOtherRecommendations() {
|
||||
return Promise.resolve([
|
||||
{ extensionId: configBasedRecommendationB.identifier.id },
|
||||
{ extensionId: otherRecommendationA.identifier.id }
|
||||
configBasedRecommendationB.identifier.id,
|
||||
otherRecommendationA.identifier.id
|
||||
]);
|
||||
},
|
||||
getAllRecommendationsWithReason() {
|
||||
@@ -165,7 +168,8 @@ suite('ExtensionsListView Tests', () => {
|
||||
}
|
||||
});
|
||||
|
||||
instantiationService.stub(IExtensionService, {
|
||||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
onDidChangeExtensions: Event.None,
|
||||
getExtensions: (): Promise<IExtensionDescription[]> => {
|
||||
return Promise.resolve([
|
||||
toExtensionDescription(localEnabledTheme),
|
||||
@@ -180,7 +184,7 @@ suite('ExtensionsListView Tests', () => {
|
||||
await (<TestExtensionEnablementService>instantiationService.get(IWorkbenchExtensionEnablementService)).setEnablement([localDisabledLanguage], EnablementState.DisabledGlobally);
|
||||
|
||||
instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService));
|
||||
testableView = instantiationService.createInstance(ExtensionsListView, {});
|
||||
testableView = instantiationService.createInstance(ExtensionsListView, {}, {});
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
@@ -482,7 +486,7 @@ suite('ExtensionsListView Tests', () => {
|
||||
}]);
|
||||
|
||||
testableView.dispose();
|
||||
testableView = instantiationService.createInstance(ExtensionsListView, {});
|
||||
testableView = instantiationService.createInstance(ExtensionsListView, {}, {});
|
||||
|
||||
return testableView.show('search-me').then(result => {
|
||||
const options: IQueryOptions = queryTarget.args[0][0];
|
||||
@@ -509,7 +513,7 @@ suite('ExtensionsListView Tests', () => {
|
||||
const queryTarget = <SinonStub>instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...realResults));
|
||||
|
||||
testableView.dispose();
|
||||
testableView = instantiationService.createInstance(ExtensionsListView, {});
|
||||
testableView = instantiationService.createInstance(ExtensionsListView, {}, {});
|
||||
|
||||
return testableView.show('search-me @sort:installs').then(result => {
|
||||
const options: IQueryOptions = queryTarget.args[0][0];
|
||||
@@ -532,6 +536,7 @@ suite('ExtensionsListView Tests', () => {
|
||||
metadata: { id: getGalleryExtensionId(manifest.publisher, manifest.name), publisherId: manifest.publisher, publisherDisplayName: 'somename' },
|
||||
...properties
|
||||
};
|
||||
properties.isBuiltin = properties.type === ExtensionType.System;
|
||||
return <ILocalExtension>Object.create({ manifest, ...properties });
|
||||
}
|
||||
|
||||
@@ -547,14 +552,5 @@ suite('ExtensionsListView Tests', () => {
|
||||
return { firstPage: objects, total: objects.length, pageSize: objects.length, getPage: () => null! };
|
||||
}
|
||||
|
||||
function toExtensionDescription(local: ILocalExtension): IExtensionDescription {
|
||||
return {
|
||||
identifier: new ExtensionIdentifier(local.identifier.id),
|
||||
isBuiltin: local.type === ExtensionType.System,
|
||||
isUnderDevelopment: false,
|
||||
extensionLocation: local.location,
|
||||
...local.manifest
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ import {
|
||||
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension,
|
||||
DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IGalleryExtensionAssets, IExtensionIdentifier, InstallOperation, IExtensionTipsService, IGalleryMetadata
|
||||
} from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionRecommendationsService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test';
|
||||
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
|
||||
@@ -38,13 +39,12 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA
|
||||
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl';
|
||||
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
|
||||
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
|
||||
import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
|
||||
import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test';
|
||||
import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService';
|
||||
import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-sandbox/extensionTipsService';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
suite('ExtensionsWorkbenchServiceTest', () => {
|
||||
@@ -67,7 +67,6 @@ suite('ExtensionsWorkbenchServiceTest', () => {
|
||||
instantiationService.stub(ITelemetryService, NullTelemetryService);
|
||||
instantiationService.stub(ILogService, NullLogService);
|
||||
instantiationService.stub(IProgressService, ProgressService);
|
||||
instantiationService.stub(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryService());
|
||||
instantiationService.stub(IProductService, {});
|
||||
|
||||
instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService);
|
||||
@@ -223,8 +222,8 @@ suite('ExtensionsWorkbenchServiceTest', () => {
|
||||
assert.equal('1.1.0', actual.version);
|
||||
assert.equal('1.1.0', actual.latestVersion);
|
||||
assert.equal('localDescription1', actual.description);
|
||||
assert.equal('file:///localPath1/localIcon1', actual.iconUrl);
|
||||
assert.equal('file:///localPath1/localIcon1', actual.iconUrlFallback);
|
||||
assert.ok(actual.iconUrl === 'file:///localPath1/localIcon1' || actual.iconUrl === 'vscode-file://vscode-app/localPath1/localIcon1');
|
||||
assert.ok(actual.iconUrlFallback === 'file:///localPath1/localIcon1' || actual.iconUrlFallback === 'vscode-file://vscode-app/localPath1/localIcon1');
|
||||
assert.equal(null, actual.licenseUrl);
|
||||
assert.equal(ExtensionState.Installed, actual.state);
|
||||
assert.equal(null, actual.installCount);
|
||||
@@ -686,6 +685,11 @@ suite('ExtensionsWorkbenchServiceTest', () => {
|
||||
const extensionB = aLocalExtension('b');
|
||||
const extensionC = aLocalExtension('c');
|
||||
|
||||
instantiationService.stub(INotificationService, <Partial<INotificationService>>{
|
||||
prompt(severity, message, choices, options) {
|
||||
options!.onCancel!();
|
||||
}
|
||||
});
|
||||
return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionA], EnablementState.EnabledGlobally)
|
||||
.then(() => instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionB], EnablementState.EnabledGlobally))
|
||||
.then(() => instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionC], EnablementState.EnabledGlobally))
|
||||
@@ -696,6 +700,28 @@ suite('ExtensionsWorkbenchServiceTest', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('test disable extension disables all dependents when chosen to disable all', async () => {
|
||||
const extensionA = aLocalExtension('a', { extensionDependencies: ['pub.b'] });
|
||||
const extensionB = aLocalExtension('b');
|
||||
const extensionC = aLocalExtension('c');
|
||||
|
||||
instantiationService.stub(INotificationService, <Partial<INotificationService>>{
|
||||
prompt(severity, message, choices, options) {
|
||||
choices[0].run();
|
||||
}
|
||||
});
|
||||
return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionA], EnablementState.EnabledGlobally)
|
||||
.then(() => instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionB], EnablementState.EnabledGlobally))
|
||||
.then(() => instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionC], EnablementState.EnabledGlobally))
|
||||
.then(async () => {
|
||||
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extensionA, extensionB, extensionC]);
|
||||
testObject = await aWorkbenchService();
|
||||
await testObject.setEnablement(testObject.local[1], EnablementState.DisabledGlobally);
|
||||
assert.equal(testObject.local[0].enablementState, EnablementState.DisabledGlobally);
|
||||
assert.equal(testObject.local[1].enablementState, EnablementState.DisabledGlobally);
|
||||
});
|
||||
});
|
||||
|
||||
test('test disable extension when extension is part of a pack', async () => {
|
||||
const extensionA = aLocalExtension('a', { extensionPack: ['pub.b'] });
|
||||
const extensionB = aLocalExtension('b');
|
||||
@@ -801,6 +827,11 @@ suite('ExtensionsWorkbenchServiceTest', () => {
|
||||
const extensionB = aLocalExtension('b', { extensionDependencies: ['pub.a'] });
|
||||
const extensionC = aLocalExtension('c');
|
||||
|
||||
instantiationService.stub(INotificationService, <Partial<INotificationService>>{
|
||||
prompt(severity, message, choices, options) {
|
||||
options!.onCancel!();
|
||||
}
|
||||
});
|
||||
return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionA], EnablementState.EnabledGlobally)
|
||||
.then(() => instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionB], EnablementState.EnabledGlobally))
|
||||
.then(() => instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionC], EnablementState.EnabledGlobally))
|
||||
@@ -836,6 +867,11 @@ suite('ExtensionsWorkbenchServiceTest', () => {
|
||||
const extensionB = aLocalExtension('b', { extensionDependencies: ['pub.c'] });
|
||||
const extensionC = aLocalExtension('c', { extensionDependencies: ['pub.a'] });
|
||||
|
||||
instantiationService.stub(INotificationService, <Partial<INotificationService>>{
|
||||
prompt(severity, message, choices, options) {
|
||||
options!.onCancel!();
|
||||
}
|
||||
});
|
||||
return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionA], EnablementState.EnabledGlobally)
|
||||
.then(() => instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionB], EnablementState.EnabledGlobally))
|
||||
.then(() => instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([extensionC], EnablementState.EnabledGlobally))
|
||||
|
||||
Reference in New Issue
Block a user