Vscode merge (#4582)

* Merge from vscode 37cb23d3dd4f9433d56d4ba5ea3203580719a0bd

* fix issues with merges

* bump node version in azpipe

* replace license headers

* remove duplicate launch task

* fix build errors

* fix build errors

* fix tslint issues

* working through package and linux build issues

* more work

* wip

* fix packaged builds

* working through linux build errors

* wip

* wip

* wip

* fix mac and linux file limits

* iterate linux pipeline

* disable editor typing

* revert series to parallel

* remove optimize vscode from linux

* fix linting issues

* revert testing change

* add work round for new node

* readd packaging for extensions

* fix issue with angular not resolving decorator dependencies
This commit is contained in:
Anthony Dresser
2019-03-19 17:44:35 -07:00
committed by GitHub
parent 833d197412
commit 87765e8673
1879 changed files with 54505 additions and 38058 deletions

View File

@@ -0,0 +1,139 @@
/*---------------------------------------------------------------------------------------------
* 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 { IAutoFocus, Mode, IModel } from 'vs/base/parts/quickopen/common/quickOpen';
import { QuickOpenEntry, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { QuickOpenHandler } from 'vs/workbench/browser/quickopen';
import { IExtensionsViewlet, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { CancellationToken } from 'vs/base/common/cancellation';
class SimpleEntry extends QuickOpenEntry {
constructor(private label: string, private action: Function) {
super();
}
getLabel(): string {
return this.label;
}
getAriaLabel(): string {
return this.label;
}
run(mode: Mode): boolean {
if (mode === Mode.PREVIEW) {
return false;
}
this.action();
return true;
}
}
export class ExtensionsHandler extends QuickOpenHandler {
public static readonly ID = 'workbench.picker.extensions';
constructor(@IViewletService private readonly viewletService: IViewletService) {
super();
}
getResults(text: string, token: CancellationToken): Promise<IModel<any>> {
const label = nls.localize('manage', "Press Enter to manage your extensions.");
const action = () => {
this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
viewlet.search('');
viewlet.focus();
});
};
return Promise.resolve(new QuickOpenModel([new SimpleEntry(label, action)]));
}
getEmptyLabel(input: string): string {
return '';
}
getAutoFocus(searchValue: string): IAutoFocus {
return { autoFocusFirstEntry: true };
}
}
export class GalleryExtensionsHandler extends QuickOpenHandler {
public static readonly ID = 'workbench.picker.gallery';
constructor(
@IViewletService private readonly viewletService: IViewletService,
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
@IExtensionManagementService private readonly extensionsService: IExtensionManagementService,
@INotificationService private readonly notificationService: INotificationService
) {
super();
}
getResults(text: string, token: CancellationToken): Promise<IModel<any>> {
if (/\./.test(text)) {
return this.galleryService.query({ names: [text], pageSize: 1 })
.then(galleryResult => {
const entries: SimpleEntry[] = [];
const galleryExtension = galleryResult.firstPage[0];
if (!galleryExtension) {
const label = nls.localize('notfound', "Extension '{0}' not found in the Marketplace.", text);
entries.push(new SimpleEntry(label, () => null));
} else {
const label = nls.localize('install', "Press Enter to install '{0}' from the Marketplace.", text);
const action = () => {
return this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => viewlet.search(`@id:${text}`))
.then(() => this.extensionsService.installFromGallery(galleryExtension))
.then(undefined, err => this.notificationService.error(err));
};
entries.push(new SimpleEntry(label, action));
}
return new QuickOpenModel(entries);
});
}
const entries: SimpleEntry[] = [];
if (text) {
const label = nls.localize('searchFor', "Press Enter to search for '{0}' in the Marketplace.", text);
const action = () => {
this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
viewlet.search(text);
viewlet.focus();
});
};
entries.push(new SimpleEntry(label, action));
}
return Promise.resolve(new QuickOpenModel(entries));
}
getEmptyLabel(input: string): string {
return nls.localize('noExtensionsToInstall', "Type an extension name");
}
getAutoFocus(searchValue: string): IAutoFocus {
return { autoFocusFirstEntry: true };
}
}

View File

@@ -0,0 +1,256 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import { localize } from 'vs/nls';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IDataSource, ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
import { Action } from 'vs/base/common/actions';
import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions';
import { Event } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { WorkbenchTreeController, WorkbenchTree, IListService } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IThemeService } from 'vs/platform/theme/common/themeService';
export interface IExtensionTemplateData {
icon: HTMLImageElement;
name: HTMLElement;
identifier: HTMLElement;
author: HTMLElement;
extensionDisposables: IDisposable[];
extensionData: IExtensionData;
}
export interface IUnknownExtensionTemplateData {
identifier: HTMLElement;
}
export interface IExtensionData {
extension: IExtension;
hasChildren: boolean;
getChildren: () => Promise<IExtensionData[] | null>;
parent: IExtensionData | null;
}
export class DataSource implements IDataSource {
public getId(tree: ITree, { extension, parent }: IExtensionData): string {
return parent ? this.getId(tree, parent) + '/' + extension.identifier.id : extension.identifier.id;
}
public hasChildren(tree: ITree, { hasChildren }: IExtensionData): boolean {
return hasChildren;
}
public getChildren(tree: ITree, extensionData: IExtensionData): Promise<any> {
return extensionData.getChildren();
}
public getParent(tree: ITree, { parent }: IExtensionData): Promise<any> {
return Promise.resolve(parent);
}
}
export class Renderer implements IRenderer {
private static readonly EXTENSION_TEMPLATE_ID = 'extension-template';
private static readonly UNKNOWN_EXTENSION_TEMPLATE_ID = 'unknown-extension-template';
constructor(@IInstantiationService private readonly instantiationService: IInstantiationService) {
}
public getHeight(tree: ITree, element: IExtensionData): number {
return 62;
}
public getTemplateId(tree: ITree, { extension }: IExtensionData): string {
return extension ? Renderer.EXTENSION_TEMPLATE_ID : Renderer.UNKNOWN_EXTENSION_TEMPLATE_ID;
}
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
if (Renderer.EXTENSION_TEMPLATE_ID === templateId) {
return this.renderExtensionTemplate(tree, container);
}
return this.renderUnknownExtensionTemplate(tree, container);
}
private renderExtensionTemplate(tree: ITree, container: HTMLElement): IExtensionTemplateData {
dom.addClass(container, 'extension');
const icon = dom.append(container, dom.$<HTMLImageElement>('img.icon'));
const details = dom.append(container, dom.$('.details'));
const header = dom.append(details, dom.$('.header'));
const name = dom.append(header, dom.$('span.name'));
const openExtensionAction = this.instantiationService.createInstance(OpenExtensionAction);
const extensionDisposables = [dom.addDisposableListener(name, 'click', (e: MouseEvent) => {
tree.setFocus(openExtensionAction.extensionData);
tree.setSelection([openExtensionAction.extensionData]);
openExtensionAction.run(e.ctrlKey || e.metaKey);
e.stopPropagation();
e.preventDefault();
})];
const identifier = dom.append(header, dom.$('span.identifier'));
const footer = dom.append(details, dom.$('.footer'));
const author = dom.append(footer, dom.$('.author'));
return {
icon,
name,
identifier,
author,
extensionDisposables,
set extensionData(extensionData: IExtensionData) {
openExtensionAction.extensionData = extensionData;
}
};
}
private renderUnknownExtensionTemplate(tree: ITree, container: HTMLElement): IUnknownExtensionTemplateData {
const messageContainer = dom.append(container, dom.$('div.unknown-extension'));
dom.append(messageContainer, dom.$('span.error-marker')).textContent = localize('error', "Error");
dom.append(messageContainer, dom.$('span.message')).textContent = localize('Unknown Extension', "Unknown Extension:");
const identifier = dom.append(messageContainer, dom.$('span.message'));
return { identifier };
}
public renderElement(tree: ITree, element: IExtensionData, templateId: string, templateData: any): void {
if (templateId === Renderer.EXTENSION_TEMPLATE_ID) {
this.renderExtension(tree, element, templateData);
return;
}
this.renderUnknownExtension(tree, element, templateData);
}
private renderExtension(tree: ITree, extensionData: IExtensionData, data: IExtensionTemplateData): void {
const extension = extensionData.extension;
const onError = Event.once(domEvent(data.icon, 'error'));
onError(() => data.icon.src = extension.iconUrlFallback, null, data.extensionDisposables);
data.icon.src = extension.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 = extension.displayName;
data.identifier.textContent = extension.identifier.id;
data.author.textContent = extension.publisherDisplayName;
data.extensionData = extensionData;
}
private renderUnknownExtension(tree: ITree, { extension }: IExtensionData, data: IUnknownExtensionTemplateData): void {
data.identifier.textContent = extension.identifier.id;
}
public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
if (templateId === Renderer.EXTENSION_TEMPLATE_ID) {
templateData.extensionDisposables = dispose((<IExtensionTemplateData>templateData).extensionDisposables);
}
}
}
export class Controller extends WorkbenchTreeController {
constructor(
@IExtensionsWorkbenchService private readonly extensionsWorkdbenchService: IExtensionsWorkbenchService,
@IConfigurationService configurationService: IConfigurationService
) {
super({}, configurationService);
// TODO@Sandeep this should be a command
this.downKeyBindingDispatcher.set(KeyMod.CtrlCmd | KeyCode.Enter, (tree: ITree, event: any) => this.openExtension(tree, true));
}
protected onLeftClick(tree: ITree, element: IExtensionData, event: IMouseEvent): boolean {
let currentFocused = tree.getFocus();
if (super.onLeftClick(tree, element, event)) {
if (element.parent === null) {
if (currentFocused) {
tree.setFocus(currentFocused);
} else {
tree.focusFirst();
}
return true;
}
}
return false;
}
public openExtension(tree: ITree, sideByside: boolean): boolean {
const element: IExtensionData = tree.getFocus();
if (element.extension) {
this.extensionsWorkdbenchService.open(element.extension, sideByside);
return true;
}
return false;
}
}
class OpenExtensionAction extends Action {
private _extensionData: IExtensionData;
constructor(@IExtensionsWorkbenchService private readonly extensionsWorkdbenchService: IExtensionsWorkbenchService) {
super('extensions.action.openExtension', '');
}
public set extensionData(extension: IExtensionData) {
this._extensionData = extension;
}
public get extensionData(): IExtensionData {
return this._extensionData;
}
run(sideByside: boolean): Promise<any> {
return this.extensionsWorkdbenchService.open(this.extensionData.extension, sideByside);
}
}
export class ExtensionsTree extends WorkbenchTree {
constructor(
input: IExtensionData,
container: HTMLElement,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService
) {
const renderer = instantiationService.createInstance(Renderer);
const controller = instantiationService.createInstance(Controller);
super(
container,
{
dataSource: new DataSource(),
renderer,
controller
}, {
indentPixels: 40,
twistiePixels: 20
},
contextKeyService, listService, themeService, instantiationService, configurationService
);
this.setInput(input);
this.disposables.push(this.onDidChangeSelection(event => {
if (event && event.payload && event.payload.origin === 'keyboard') {
controller.openExtension(this, false);
}
}));
}
}

View File

@@ -0,0 +1,80 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { flatten } from 'vs/base/common/arrays';
export class Query {
constructor(public value: string, public sortBy: string, public groupBy: string) {
this.value = value.trim();
}
static suggestions(query: string): string[] {
const commands = ['installed', 'outdated', 'enabled', 'disabled', 'builtin', 'recommended', 'sort', 'category', 'tag', 'ext'];
const subcommands = {
'sort': ['installs', 'rating', 'name'],
'category': ['"programming languages"', 'snippets', 'linters', 'themes', 'debuggers', 'formatters', 'keymaps', '"scm providers"', 'other', '"extension packs"', '"language packs"'],
'tag': [''],
'ext': ['']
};
let queryContains = (substr: string) => query.indexOf(substr) > -1;
let hasSort = subcommands.sort.some(subcommand => queryContains(`@sort:${subcommand}`));
let hasCategory = subcommands.category.some(subcommand => queryContains(`@category:${subcommand}`));
return flatten(
commands.map(command => {
if (hasSort && command === 'sort' || hasCategory && command === 'category') {
return [];
}
if (subcommands[command]) {
return subcommands[command].map(subcommand => `@${command}:${subcommand}${subcommand === '' ? '' : ' '}`);
}
else {
return [`@${command} `];
}
}));
}
static parse(value: string): Query {
let sortBy = '';
value = value.replace(/@sort:(\w+)(-\w*)?/g, (match, by: string, order: string) => {
sortBy = by;
return '';
});
let groupBy = '';
value = value.replace(/@group:(\w+)(-\w*)?/g, (match, by: string, order: string) => {
groupBy = by;
return '';
});
return new Query(value, sortBy, groupBy);
}
toString(): string {
let result = this.value;
if (this.sortBy) {
result = `${result}${result ? ' ' : ''}@sort:${this.sortBy}`;
}
if (this.groupBy) {
result = `${result}${result ? ' ' : ''}@group:${this.groupBy}`;
}
return result;
}
isValid(): boolean {
return !/@outdated/.test(this.value);
}
equals(other: Query): boolean {
return this.value === other.value && this.sortBy === other.sortBy;
}
}

View File

@@ -0,0 +1,158 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IViewlet } from 'vs/workbench/common/viewlet';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
import { IPager } from 'vs/base/common/paging';
import { IQueryOptions, EnablementState, ILocalExtension, IGalleryExtension, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views';
import { Registry } from 'vs/platform/registry/common/platform';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Disposable } from 'vs/base/common/lifecycle';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions';
export const VIEWLET_ID = 'workbench.view.extensions';
export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID);
export const EXTENSIONS_CONFIG = '.azuredatastudio/extensions.json';
export interface IExtensionsViewlet extends IViewlet {
search(text: string): void;
}
export const enum ExtensionState {
Installing,
Installed,
Uninstalling,
Uninstalled
}
export interface IExtension {
type?: ExtensionType;
state: ExtensionState;
name: string;
displayName: string;
identifier: IExtensionIdentifier;
publisher: string;
publisherDisplayName: string;
version: string;
latestVersion: string;
description: string;
url?: string;
// {{SQL CARBON EDIT}}
downloadPage: string;
repository?: string;
iconUrl: string;
iconUrlFallback: string;
licenseUrl?: string;
installCount?: number;
rating?: number;
ratingCount?: number;
outdated: boolean;
enablementState: EnablementState;
dependencies: string[];
extensionPack: string[];
telemetryData: any;
preview: boolean;
getManifest(token: CancellationToken): Promise<IExtensionManifest | null>;
getReadme(token: CancellationToken): Promise<string>;
hasReadme(): boolean;
getChangelog(token: CancellationToken): Promise<string>;
hasChangelog(): boolean;
local?: ILocalExtension;
gallery?: IGalleryExtension;
isMalicious: boolean;
}
export interface IExtensionDependencies {
dependencies: IExtensionDependencies[];
hasDependencies: boolean;
identifier: string;
extension: IExtension;
dependent: IExtensionDependencies | null;
}
export const SERVICE_ID = 'extensionsWorkbenchService';
export const IExtensionsWorkbenchService = createDecorator<IExtensionsWorkbenchService>(SERVICE_ID);
export interface IExtensionsWorkbenchService {
_serviceBrand: any;
onChange: Event<IExtension | undefined>;
local: IExtension[];
queryLocal(): Promise<IExtension[]>;
queryGallery(options?: IQueryOptions): Promise<IPager<IExtension>>;
canInstall(extension: IExtension): boolean;
install(vsix: string): Promise<IExtension>;
install(extension: IExtension, promptToInstallDependencies?: boolean): Promise<IExtension>;
uninstall(extension: IExtension): Promise<void>;
installVersion(extension: IExtension, version: string): Promise<IExtension>;
reinstall(extension: IExtension): Promise<IExtension>;
setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise<void>;
loadDependencies(extension: IExtension, token: CancellationToken): Promise<IExtensionDependencies | null>;
open(extension: IExtension, sideByside?: boolean): Promise<any>;
checkForUpdates(): Promise<void>;
allowedBadgeProviders: string[];
}
export const ConfigurationKey = 'extensions';
export const AutoUpdateConfigurationKey = 'extensions.autoUpdate';
export const AutoCheckUpdatesConfigurationKey = 'extensions.autoCheckUpdates';
export const ShowRecommendationsOnlyOnDemandKey = 'extensions.showRecommendationsOnlyOnDemand';
export const CloseExtensionDetailsOnViewChangeKey = 'extensions.closeExtensionDetailsOnViewChange';
// {{SQL CARBON EDIT}}
export const ExtensionsPolicyKey = 'extensions.extensionsPolicy';
export interface IExtensionsConfiguration {
autoUpdate: boolean;
autoCheckUpdates: boolean;
ignoreRecommendations: boolean;
showRecommendationsOnlyOnDemand: boolean;
closeExtensionDetailsOnViewChange: boolean;
// {{SQL CARBON EDIT}}
extensionsPolicy: string;
}
// {{SQL CARBON EDIT}}
export enum ExtensionsPolicy {
allowAll = 'allowAll',
allowNone = 'allowNone',
allowMicrosoft = 'allowMicrosoft'
}
// {{SQL CARBON EDIT}} - End
export interface IExtensionContainer {
extension: IExtension | null;
update(): void;
}
export class ExtensionContainers extends Disposable {
constructor(
private readonly containers: IExtensionContainer[],
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService
) {
super();
this._register(extensionsWorkbenchService.onChange(this.update, this));
}
set extension(extension: IExtension) {
this.containers.forEach(c => c.extension = extension);
}
private update(extension: IExtension): void {
for (const container of this.containers) {
if (extension && container.extension) {
if (areSameExtensions(container.extension.identifier, extension.identifier)) {
container.extension = extension;
}
} else {
container.update();
}
}
}
}

View File

@@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement';
export const ExtensionsConfigurationSchemaId = 'vscode://schemas/extensions';
export const ExtensionsConfigurationSchema: IJSONSchema = {
id: ExtensionsConfigurationSchemaId,
allowComments: true,
type: 'object',
title: localize('app.extensions.json.title', "Extensions"),
additionalProperties: false,
properties: {
recommendations: {
type: 'array',
description: localize('app.extensions.json.recommendations', "List of extensions which should be recommended for users of this workspace. The identifier of an extension is always '${publisher}.${name}'. For example: 'vscode.csharp'."),
items: {
type: 'string',
pattern: EXTENSION_IDENTIFIER_PATTERN,
errorMessage: localize('app.extension.identifier.errorMessage', "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'.")
},
},
unwantedRecommendations: {
type: 'array',
description: localize('app.extensions.json.unwantedRecommendations', "List of extensions recommended by VS Code that should not be recommended for users of this workspace. The identifier of an extension is always '${publisher}.${name}'. For example: 'vscode.csharp'."),
items: {
type: 'string',
pattern: EXTENSION_IDENTIFIER_PATTERN,
errorMessage: localize('app.extension.identifier.errorMessage', "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'.")
},
},
}
};
export const ExtensionsConfigurationInitialContent: string = [
'{',
'\t// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.',
'\t// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp',
'',
'\t// List of extensions which should be recommended for users of this workspace.',
'\t"recommendations": [',
'\t\t',
'\t],',
'\t// List of extensions recommended by VS Code that should not be recommended for users of this workspace.',
'\t"unwantedRecommendations": [',
'\t\t',
'\t]',
'}'
].join('\n');

View File

@@ -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 { localize } from 'vs/nls';
import { EditorInput } from 'vs/workbench/common/editor';
import { IExtension } from 'vs/workbench/contrib/extensions/common/extensions';
import { URI } from 'vs/base/common/uri';
export class ExtensionsInput extends EditorInput {
static readonly ID = 'workbench.extensions.input2';
get extension(): IExtension { return this._extension; }
constructor(
private _extension: IExtension,
) {
super();
}
getTypeId(): string {
return ExtensionsInput.ID;
}
getName(): string {
return localize('extensionsInputName', "Extension: {0}", this.extension.displayName);
}
matches(other: any): boolean {
if (!(other instanceof ExtensionsInput)) {
return false;
}
const otherExtensionInput = other as ExtensionsInput;
// TODO@joao is this correct?
return this.extension === otherExtensionInput.extension;
}
resolve(): Promise<any> {
return Promise.resolve(null);
}
supportsSplitEditor(): boolean {
return false;
}
getResource(): URI {
return URI.from({
scheme: 'extension',
path: this.extension.identifier.id
});
}
}

View File

@@ -0,0 +1,138 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as arrays from 'vs/base/common/arrays';
import { localize } from 'vs/nls';
import { Event } from 'vs/base/common/event';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IExtensionManagementService, ILocalExtension, IExtensionEnablementService, IExtensionTipsService, IExtensionIdentifier, EnablementState, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { Severity, INotificationService } from 'vs/platform/notification/common/notification';
export interface IExtensionStatus {
identifier: IExtensionIdentifier;
local: ILocalExtension;
globallyEnabled: boolean;
}
export class KeymapExtensions implements IWorkbenchContribution {
private disposables: IDisposable[] = [];
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService,
@IExtensionTipsService private readonly tipsService: IExtensionTipsService,
@ILifecycleService lifecycleService: ILifecycleService,
@INotificationService private readonly notificationService: INotificationService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
) {
this.disposables.push(
lifecycleService.onShutdown(() => this.dispose()),
instantiationService.invokeFunction(onExtensionChanged)((identifiers => {
Promise.all(identifiers.map(identifier => this.checkForOtherKeymaps(identifier)))
.then(undefined, onUnexpectedError);
}))
);
}
private checkForOtherKeymaps(extensionIdentifier: IExtensionIdentifier): Promise<void> {
return this.instantiationService.invokeFunction(getInstalledExtensions).then(extensions => {
const keymaps = extensions.filter(extension => isKeymapExtension(this.tipsService, extension));
const extension = arrays.first(keymaps, extension => areSameExtensions(extension.identifier, extensionIdentifier));
if (extension && extension.globallyEnabled) {
const otherKeymaps = keymaps.filter(extension => !areSameExtensions(extension.identifier, extensionIdentifier) && extension.globallyEnabled);
if (otherKeymaps.length) {
return this.promptForDisablingOtherKeymaps(extension, otherKeymaps);
}
}
return undefined;
});
}
private promptForDisablingOtherKeymaps(newKeymap: IExtensionStatus, oldKeymaps: IExtensionStatus[]): void {
const onPrompt = (confirmed: boolean) => {
const telemetryData: { [key: string]: any; } = {
newKeymap: newKeymap.identifier,
oldKeymaps: oldKeymaps.map(k => k.identifier),
confirmed
};
/* __GDPR__
"disableOtherKeymaps" : {
"newKeymap": { "${inline}": [ "${ExtensionIdentifier}" ] },
"oldKeymaps": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"confirmed" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
}
*/
this.telemetryService.publicLog('disableOtherKeymaps', telemetryData);
if (confirmed) {
this.extensionEnablementService.setEnablement(oldKeymaps.map(keymap => keymap.local), EnablementState.Disabled);
}
};
this.notificationService.prompt(Severity.Info, localize('disableOtherKeymapsConfirmation', "Disable other keymaps ({0}) to avoid conflicts between keybindings?", oldKeymaps.map(k => `'${k.local.manifest.displayName}'`).join(', ')),
[{
label: localize('yes', "Yes"),
run: () => onPrompt(true)
}, {
label: localize('no', "No"),
run: () => onPrompt(false)
}]
);
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
}
export function onExtensionChanged(accessor: ServicesAccessor): Event<IExtensionIdentifier[]> {
const extensionService = accessor.get(IExtensionManagementService);
const extensionEnablementService = accessor.get(IExtensionEnablementService);
const onDidInstallExtension = Event.chain(extensionService.onDidInstallExtension)
.filter(e => e.operation === InstallOperation.Install)
.event;
return Event.debounce<IExtensionIdentifier[], IExtensionIdentifier[]>(Event.any(
Event.chain(Event.any(onDidInstallExtension, extensionService.onDidUninstallExtension))
.map(e => [e.identifier])
.event,
Event.map(extensionEnablementService.onEnablementChanged, extensions => extensions.map(e => e.identifier))
), (result: IExtensionIdentifier[] | undefined, identifiers: IExtensionIdentifier[]) => {
result = result || [];
for (const identifier of identifiers) {
if (result.some(l => !areSameExtensions(l, identifier))) {
result.push(identifier);
}
}
return result;
});
}
export function getInstalledExtensions(accessor: ServicesAccessor): Promise<IExtensionStatus[]> {
const extensionService = accessor.get(IExtensionManagementService);
const extensionEnablementService = accessor.get(IExtensionEnablementService);
return extensionService.getInstalled().then(extensions => {
return extensionEnablementService.getDisabledExtensions()
.then(disabledExtensions => {
return extensions.map(extension => {
return {
identifier: extension.identifier,
local: extension,
globallyEnabled: disabledExtensions.every(disabled => !areSameExtensions(disabled, extension.identifier))
};
});
});
});
}
export function isKeymapExtension(tipsService: IExtensionTipsService, 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));
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,207 @@
/*---------------------------------------------------------------------------------------------
* 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 { Event, Emitter } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionHostProfile, ProfileSession, IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { onUnexpectedError } from 'vs/base/common/errors';
import { append, $, addDisposableListener } from 'vs/base/browser/dom';
import { IStatusbarRegistry, StatusbarItemDescriptor, Extensions, IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar';
import { Registry } from 'vs/platform/registry/common/platform';
import { IExtensionHostProfileService, ProfileSessionState } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { randomPort } from 'vs/base/node/ports';
import product from 'vs/platform/product/node/product';
import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
export class ExtensionHostProfileService extends Disposable implements IExtensionHostProfileService {
_serviceBrand: any;
private readonly _onDidChangeState: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidChangeState: Event<void> = this._onDidChangeState.event;
private readonly _onDidChangeLastProfile: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidChangeLastProfile: Event<void> = this._onDidChangeLastProfile.event;
private readonly _unresponsiveProfiles = new Map<string, IExtensionHostProfile>();
private _profile: IExtensionHostProfile | null;
private _profileSession: ProfileSession | null;
private _state: ProfileSessionState;
public get state() { return this._state; }
public get lastProfile() { return this._profile; }
constructor(
@IExtensionService private readonly _extensionService: IExtensionService,
@IEditorService private readonly _editorService: IEditorService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IWindowsService private readonly _windowsService: IWindowsService,
@IDialogService private readonly _dialogService: IDialogService
) {
super();
this._profile = null;
this._profileSession = null;
this._setState(ProfileSessionState.None);
}
private _setState(state: ProfileSessionState): void {
if (this._state === state) {
return;
}
this._state = state;
if (this._state === ProfileSessionState.Running) {
ProfileExtHostStatusbarItem.instance.show(() => {
this.stopProfiling();
this._editorService.openEditor(this._instantiationService.createInstance(RuntimeExtensionsInput), { revealIfOpened: true });
});
} else if (this._state === ProfileSessionState.Stopping) {
ProfileExtHostStatusbarItem.instance.hide();
}
this._onDidChangeState.fire(undefined);
}
public startProfiling(): Promise<any> | null {
if (this._state !== ProfileSessionState.None) {
return null;
}
if (!this._extensionService.canProfileExtensionHost()) {
return 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?", product.nameLong),
primaryButton: nls.localize('restart3', "Restart"),
secondaryButton: nls.localize('cancel', "Cancel")
}).then(res => {
if (res.confirmed) {
this._windowsService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] });
}
});
}
this._setState(ProfileSessionState.Starting);
return this._extensionService.startExtensionHostProfile().then((value) => {
this._profileSession = value;
this._setState(ProfileSessionState.Running);
}, (err) => {
onUnexpectedError(err);
this._setState(ProfileSessionState.None);
});
}
public stopProfiling(): void {
if (this._state !== ProfileSessionState.Running || !this._profileSession) {
return;
}
this._setState(ProfileSessionState.Stopping);
this._profileSession.stop().then((result) => {
this._setLastProfile(result);
this._setState(ProfileSessionState.None);
}, (err) => {
onUnexpectedError(err);
this._setState(ProfileSessionState.None);
});
this._profileSession = null;
}
private _setLastProfile(profile: IExtensionHostProfile) {
this._profile = profile;
this._onDidChangeLastProfile.fire(undefined);
}
getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile | undefined {
return this._unresponsiveProfiles.get(ExtensionIdentifier.toKey(extensionId));
}
setUnresponsiveProfile(extensionId: ExtensionIdentifier, profile: IExtensionHostProfile): void {
this._unresponsiveProfiles.set(ExtensionIdentifier.toKey(extensionId), profile);
this._setLastProfile(profile);
}
}
export class ProfileExtHostStatusbarItem implements IStatusbarItem {
public static instance: ProfileExtHostStatusbarItem;
private toDispose: IDisposable[];
private statusBarItem: HTMLElement;
private label: HTMLElement;
private timeStarted: number;
private labelUpdater: any;
private clickHandler: (() => void) | null;
constructor() {
ProfileExtHostStatusbarItem.instance = this;
this.toDispose = [];
this.timeStarted = 0;
}
public show(clickHandler: () => void) {
this.clickHandler = clickHandler;
if (this.timeStarted === 0) {
this.timeStarted = new Date().getTime();
this.statusBarItem.hidden = false;
this.labelUpdater = setInterval(() => {
this.updateLabel();
}, 1000);
this.updateLabel();
}
}
public hide() {
this.clickHandler = null;
this.statusBarItem.hidden = true;
this.timeStarted = 0;
clearInterval(this.labelUpdater);
this.labelUpdater = null;
}
public render(container: HTMLElement): IDisposable {
if (!this.statusBarItem && container) {
this.statusBarItem = append(container, $('.profileExtHost-statusbar-item'));
this.toDispose.push(addDisposableListener(this.statusBarItem, 'click', () => {
if (this.clickHandler) {
this.clickHandler();
}
}));
this.statusBarItem.title = nls.localize('selectAndStartDebug', "Click to stop profiling.");
const a = append(this.statusBarItem, $('a'));
append(a, $('.icon'));
this.label = append(a, $('span.label'));
this.updateLabel();
this.statusBarItem.hidden = true;
}
return this;
}
private updateLabel() {
let label = 'Profiling Extension Host';
if (this.timeStarted > 0) {
let secondsRecoreded = (new Date().getTime() - this.timeStarted) / 1000;
label = `Profiling Extension Host (${Math.round(secondsRecoreded)} sec)`;
}
this.label.textContent = label;
}
public dispose(): void {
this.toDispose = dispose(this.toDispose);
}
}
Registry.as<IStatusbarRegistry>(Extensions.Statusbar).registerStatusbarItem(
new StatusbarItemDescriptor(ProfileExtHostStatusbarItem, StatusbarAlignment.RIGHT)
);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,421 @@
/*---------------------------------------------------------------------------------------------
* 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/extensions';
import { localize } from 'vs/nls';
import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
import { Registry } from 'vs/platform/registry/common/platform';
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IExtensionTipsService, ExtensionsLabel, ExtensionsChannelId, PreferencesLabel, IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions';
import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/contrib/output/common/output';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
// {{SQL CARBON EDIT}}
import { VIEWLET_ID, IExtensionsWorkbenchService, ExtensionsPolicy } from '../common/extensions';
import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/node/extensionsWorkbenchService';
import {
OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction,
ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowDisabledExtensionsAction, ShowBuiltInExtensionsAction, UpdateAllAction,
EnableAllAction, EnableAllWorkpsaceAction, DisableAllAction, DisableAllWorkpsaceAction, CheckForUpdatesAction, ShowLanguageExtensionsAction, ShowAzureExtensionsAction, EnableAutoUpdateAction, DisableAutoUpdateAction, ConfigureRecommendedExtensionsCommandsContributor, OpenExtensionsFolderAction, InstallVSIXAction, ReinstallAction, InstallSpecificVersionOfExtensionAction
} from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions';
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet';
import { ExtensionEditor } from 'vs/workbench/contrib/extensions/electron-browser/extensionEditor';
import { StatusUpdater, ExtensionsViewlet, MaliciousExtensionChecker, ExtensionsViewletViewsContribution } from 'vs/workbench/contrib/extensions/electron-browser/extensionsViewlet';
import { IQuickOpenRegistry, Extensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import * as jsonContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { KeymapExtensions } from 'vs/workbench/contrib/extensions/common/extensionsUtils';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { GalleryExtensionsHandler, ExtensionsHandler } from 'vs/workbench/contrib/extensions/browser/extensionsQuickOpen';
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 { 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 { URI, UriComponents } from 'vs/base/common/uri';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ExtensionActivationProgress } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActivationProgress';
import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler';
import { onUnexpectedError } from 'vs/base/common/errors';
import { ExtensionDependencyChecker } from 'vs/workbench/contrib/extensions/electron-browser/extensionsDependencyChecker';
// Singletons
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService);
registerSingleton(IExtensionTipsService, ExtensionTipsService);
registerSingleton(IExtensionHostProfileService, ExtensionHostProfileService, true);
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(StatusUpdater, LifecyclePhase.Restored);
workbenchRegistry.registerWorkbenchContribution(MaliciousExtensionChecker, LifecyclePhase.Eventually);
workbenchRegistry.registerWorkbenchContribution(ConfigureRecommendedExtensionsCommandsContributor, LifecyclePhase.Eventually);
workbenchRegistry.registerWorkbenchContribution(KeymapExtensions, LifecyclePhase.Restored);
workbenchRegistry.registerWorkbenchContribution(ExtensionsViewletViewsContribution, LifecyclePhase.Starting);
workbenchRegistry.registerWorkbenchContribution(ExtensionActivationProgress, LifecyclePhase.Eventually);
workbenchRegistry.registerWorkbenchContribution(ExtensionsAutoProfiler, LifecyclePhase.Eventually);
workbenchRegistry.registerWorkbenchContribution(ExtensionDependencyChecker, LifecyclePhase.Eventually);
Registry.as<IOutputChannelRegistry>(OutputExtensions.OutputChannels)
.registerChannel({ id: ExtensionsChannelId, label: ExtensionsLabel, log: false });
// Quickopen
Registry.as<IQuickOpenRegistry>(Extensions.Quickopen).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
ExtensionsHandler,
ExtensionsHandler.ID,
'ext ',
undefined,
localize('extensionsCommands', "Manage Extensions"),
true
)
);
Registry.as<IQuickOpenRegistry>(Extensions.Quickopen).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
GalleryExtensionsHandler,
GalleryExtensionsHandler.ID,
'ext install ',
undefined,
localize('galleryExtensionsCommands', "Install Gallery Extensions"),
true
)
);
// Editor
const editorDescriptor = new EditorDescriptor(
ExtensionEditor,
ExtensionEditor.ID,
localize('extension', "Extension")
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(editorDescriptor, [new SyncDescriptor(ExtensionsInput)]);
// Running Extensions Editor
const runtimeExtensionsEditorDescriptor = new EditorDescriptor(
RuntimeExtensionsEditor,
RuntimeExtensionsEditor.ID,
localize('runtimeExtension', "Running Extensions")
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(runtimeExtensionsEditorDescriptor, [new SyncDescriptor(RuntimeExtensionsInput)]);
class RuntimeExtensionsInputFactory implements IEditorInputFactory {
serialize(editorInput: EditorInput): string {
return '';
}
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput {
return new RuntimeExtensionsInput();
}
}
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(RuntimeExtensionsInput.ID, RuntimeExtensionsInputFactory);
// Viewlet
const viewletDescriptor = new ViewletDescriptor(
ExtensionsViewlet,
VIEWLET_ID,
localize('extensions', "Extensions"),
'extensions',
// {{SQL CARBON EDIT}}
14
);
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets)
.registerViewlet(viewletDescriptor);
// Global actions
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionExtensions.WorkbenchActions);
const openViewletActionDescriptor = new SyncActionDescriptor(OpenExtensionsViewletAction, OpenExtensionsViewletAction.ID, OpenExtensionsViewletAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_X });
actionRegistry.registerWorkbenchAction(openViewletActionDescriptor, 'View: Show Extensions', localize('view', "View"));
const installActionDescriptor = new SyncActionDescriptor(InstallExtensionsAction, InstallExtensionsAction.ID, InstallExtensionsAction.LABEL);
actionRegistry.registerWorkbenchAction(installActionDescriptor, 'Extensions: Install Extensions', ExtensionsLabel);
const listOutdatedActionDescriptor = new SyncActionDescriptor(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, ShowOutdatedExtensionsAction.LABEL);
actionRegistry.registerWorkbenchAction(listOutdatedActionDescriptor, 'Extensions: Show Outdated Extensions', ExtensionsLabel);
const recommendationsActionDescriptor = new SyncActionDescriptor(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL);
actionRegistry.registerWorkbenchAction(recommendationsActionDescriptor, 'Extensions: Show Recommended Extensions', ExtensionsLabel);
const keymapRecommendationsActionDescriptor = new SyncActionDescriptor(ShowRecommendedKeymapExtensionsAction, ShowRecommendedKeymapExtensionsAction.ID, ShowRecommendedKeymapExtensionsAction.SHORT_LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_M) });
actionRegistry.registerWorkbenchAction(keymapRecommendationsActionDescriptor, 'Preferences: Keymaps', PreferencesLabel);
const languageExtensionsActionDescriptor = new SyncActionDescriptor(ShowLanguageExtensionsAction, ShowLanguageExtensionsAction.ID, ShowLanguageExtensionsAction.SHORT_LABEL);
actionRegistry.registerWorkbenchAction(languageExtensionsActionDescriptor, 'Preferences: Language Extensions', PreferencesLabel);
const azureExtensionsActionDescriptor = new SyncActionDescriptor(ShowAzureExtensionsAction, ShowAzureExtensionsAction.ID, ShowAzureExtensionsAction.SHORT_LABEL);
actionRegistry.registerWorkbenchAction(azureExtensionsActionDescriptor, 'Preferences: Azure Extensions', PreferencesLabel);
const popularActionDescriptor = new SyncActionDescriptor(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL);
actionRegistry.registerWorkbenchAction(popularActionDescriptor, 'Extensions: Show Popular Extensions', ExtensionsLabel);
const enabledActionDescriptor = new SyncActionDescriptor(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, ShowEnabledExtensionsAction.LABEL);
actionRegistry.registerWorkbenchAction(enabledActionDescriptor, 'Extensions: Show Enabled Extensions', ExtensionsLabel);
const installedActionDescriptor = new SyncActionDescriptor(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL);
actionRegistry.registerWorkbenchAction(installedActionDescriptor, 'Extensions: Show Installed Extensions', ExtensionsLabel);
const disabledActionDescriptor = new SyncActionDescriptor(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, ShowDisabledExtensionsAction.LABEL);
actionRegistry.registerWorkbenchAction(disabledActionDescriptor, 'Extensions: Show Disabled Extensions', ExtensionsLabel);
const builtinActionDescriptor = new SyncActionDescriptor(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, ShowBuiltInExtensionsAction.LABEL);
actionRegistry.registerWorkbenchAction(builtinActionDescriptor, 'Extensions: Show Built-in Extensions', ExtensionsLabel);
const updateAllActionDescriptor = new SyncActionDescriptor(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL);
actionRegistry.registerWorkbenchAction(updateAllActionDescriptor, 'Extensions: Update All Extensions', ExtensionsLabel);
const openExtensionsFolderActionDescriptor = new SyncActionDescriptor(OpenExtensionsFolderAction, OpenExtensionsFolderAction.ID, OpenExtensionsFolderAction.LABEL);
actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel);
const installVSIXActionDescriptor = new SyncActionDescriptor(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL);
actionRegistry.registerWorkbenchAction(installVSIXActionDescriptor, 'Extensions: Install from VSIX...', ExtensionsLabel);
const disableAllAction = new SyncActionDescriptor(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL);
actionRegistry.registerWorkbenchAction(disableAllAction, 'Extensions: Disable All Installed Extensions', ExtensionsLabel);
const disableAllWorkspaceAction = new SyncActionDescriptor(DisableAllWorkpsaceAction, DisableAllWorkpsaceAction.ID, DisableAllWorkpsaceAction.LABEL);
actionRegistry.registerWorkbenchAction(disableAllWorkspaceAction, 'Extensions: Disable All Installed Extensions for this Workspace', ExtensionsLabel);
const enableAllAction = new SyncActionDescriptor(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL);
actionRegistry.registerWorkbenchAction(enableAllAction, 'Extensions: Enable All Installed Extensions', ExtensionsLabel);
const enableAllWorkspaceAction = new SyncActionDescriptor(EnableAllWorkpsaceAction, EnableAllWorkpsaceAction.ID, EnableAllWorkpsaceAction.LABEL);
actionRegistry.registerWorkbenchAction(enableAllWorkspaceAction, 'Extensions: Enable All Installed Extensions for this Workspace', ExtensionsLabel);
const checkForUpdatesAction = new SyncActionDescriptor(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL);
actionRegistry.registerWorkbenchAction(checkForUpdatesAction, `Extensions: Check for Updates`, ExtensionsLabel);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL), `Extensions: Enable Auto Updating Extensions`, ExtensionsLabel);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL), `Extensions: Disable Auto Updating Extensions`, ExtensionsLabel);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(InstallSpecificVersionOfExtensionAction, InstallSpecificVersionOfExtensionAction.ID, InstallSpecificVersionOfExtensionAction.LABEL), 'Install Specific Version of Extension...', ExtensionsLabel);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowRuntimeExtensionsAction, ShowRuntimeExtensionsAction.ID, ShowRuntimeExtensionsAction.LABEL), 'Show Running Extensions', localize('developer', "Developer"));
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ReinstallAction, ReinstallAction.ID, ReinstallAction.LABEL), 'Reinstall Extension...', localize('developer', "Developer"));
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
.registerConfiguration({
id: 'extensions',
order: 30,
title: localize('extensionsConfigurationTitle', "Extensions"),
type: 'object',
properties: {
'extensions.autoUpdate': {
type: 'boolean',
description: localize('extensionsAutoUpdate', "When enabled, automatically installs updates for extensions. The updates are fetched from a Microsoft online service."),
default: true,
scope: ConfigurationScope.APPLICATION,
tags: ['usesOnlineServices']
},
'extensions.autoCheckUpdates': {
type: 'boolean',
description: localize('extensionsCheckUpdates', "When enabled, automatically checks extensions for updates. If an extension has an update, it is marked as outdated in the Extensions view. The updates are fetched from a Microsoft online service."),
default: true,
scope: ConfigurationScope.APPLICATION,
tags: ['usesOnlineServices']
},
'extensions.ignoreRecommendations': {
type: 'boolean',
description: localize('extensionsIgnoreRecommendations', "When enabled, the notifications for extension recommendations will not be shown."),
default: false
},
'extensions.showRecommendationsOnlyOnDemand': {
type: 'boolean',
description: localize('extensionsShowRecommendationsOnlyOnDemand', "When enabled, recommendations will not be fetched or shown unless specifically requested by the user. Some recommendations are fetched from a Microsoft online service."),
default: false,
tags: ['usesOnlineServices']
},
'extensions.closeExtensionDetailsOnViewChange': {
type: 'boolean',
description: localize('extensionsCloseExtensionDetailsOnViewChange', "When enabled, editors with extension details will be automatically closed upon navigating away from the Extensions View."),
default: false
},
// {{SQL CARBON EDIT}}
'extensions.extensionsPolicy': {
type: 'string',
description: localize('extensionsPolicy', "Sets the security policy for downloading extensions."),
scope: ConfigurationScope.APPLICATION,
default: ExtensionsPolicy.allowAll
}
}
});
const jsonRegistry = <jsonContributionRegistry.IJSONContributionRegistry>Registry.as(jsonContributionRegistry.Extensions.JSONContribution);
jsonRegistry.registerSchema(ExtensionsConfigurationSchemaId, ExtensionsConfigurationSchema);
// Register Commands
CommandsRegistry.registerCommand('_extensions.manage', (accessor: ServicesAccessor, extensionId: string) => {
const extensionService = accessor.get(IExtensionsWorkbenchService);
const extension = extensionService.local.filter(e => areSameExtensions(e.identifier, { id: extensionId }));
if (extension.length === 1) {
extensionService.open(extension[0]);
}
});
CommandsRegistry.registerCommand('extension.open', (accessor: ServicesAccessor, extensionId: string) => {
const extensionService = accessor.get(IExtensionsWorkbenchService);
return extensionService.queryGallery({ names: [extensionId], pageSize: 1 }).then(pager => {
if (pager.total !== 1) {
return;
}
extensionService.open(pager.firstPage[0]);
});
});
CommandsRegistry.registerCommand(DebugExtensionHostAction.ID, (accessor: ServicesAccessor) => {
const instantiationService = accessor.get(IInstantiationService);
instantiationService.createInstance(DebugExtensionHostAction).run();
});
CommandsRegistry.registerCommand(StartExtensionHostProfileAction.ID, (accessor: ServicesAccessor) => {
const instantiationService = accessor.get(IInstantiationService);
instantiationService.createInstance(StartExtensionHostProfileAction, StartExtensionHostProfileAction.ID, StartExtensionHostProfileAction.LABEL).run();
});
CommandsRegistry.registerCommand(StopExtensionHostProfileAction.ID, (accessor: ServicesAccessor) => {
const instantiationService = accessor.get(IInstantiationService);
instantiationService.createInstance(StopExtensionHostProfileAction, StopExtensionHostProfileAction.ID, StopExtensionHostProfileAction.LABEL).run();
});
CommandsRegistry.registerCommand(SaveExtensionHostProfileAction.ID, (accessor: ServicesAccessor) => {
const instantiationService = accessor.get(IInstantiationService);
instantiationService.createInstance(SaveExtensionHostProfileAction, SaveExtensionHostProfileAction.ID, SaveExtensionHostProfileAction.LABEL).run();
});
// File menu registration
// {{SQL CARBON EDIT}} - Disable unused menu item
// MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
// group: '2_keybindings',
// command: {
// id: ShowRecommendedKeymapExtensionsAction.ID,
// title: localize({ key: 'miOpenKeymapExtensions', comment: ['&& denotes a mnemonic'] }, "&&Keymaps")
// },
// order: 2
// });
// {{SQL CARBON EDIT}} - End
MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
group: '1_settings',
command: {
id: VIEWLET_ID,
title: localize({ key: 'miPreferencesExtensions', comment: ['&& denotes a mnemonic'] }, "&&Extensions")
},
order: 2
});
// View menu
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
group: '3_views',
command: {
id: VIEWLET_ID,
title: localize({ key: 'miViewExtensions', comment: ['&& denotes a mnemonic'] }, "E&&xtensions")
},
// {{SQL CARBON EDIT}} - Change the order
order: 7
});
// Running extensions
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: DebugExtensionHostAction.ID,
title: DebugExtensionHostAction.LABEL,
iconLocation: {
dark: URI.parse(require.toUrl(`vs/workbench/contrib/extensions/electron-browser/media/start-inverse.svg`)),
light: URI.parse(require.toUrl(`vs/workbench/contrib/extensions/electron-browser/media/start.svg`)),
}
},
group: 'navigation',
when: ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID)
});
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: StartExtensionHostProfileAction.ID,
title: StartExtensionHostProfileAction.LABEL,
iconLocation: {
dark: URI.parse(require.toUrl(`vs/workbench/contrib/extensions/electron-browser/media/profile-start-inverse.svg`)),
light: URI.parse(require.toUrl(`vs/workbench/contrib/extensions/electron-browser/media/profile-start.svg`)),
}
},
group: 'navigation',
when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID), CONTEXT_PROFILE_SESSION_STATE.notEqualsTo('running'))
});
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: StopExtensionHostProfileAction.ID,
title: StopExtensionHostProfileAction.LABEL,
iconLocation: {
dark: URI.parse(require.toUrl(`vs/workbench/contrib/extensions/electron-browser/media/profile-stop-inverse.svg`)),
light: URI.parse(require.toUrl(`vs/workbench/contrib/extensions/electron-browser/media/profile-stop.svg`)),
}
},
group: 'navigation',
when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID), CONTEXT_PROFILE_SESSION_STATE.isEqualTo('running'))
});
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: SaveExtensionHostProfileAction.ID,
title: SaveExtensionHostProfileAction.LABEL,
iconLocation: {
dark: URI.parse(require.toUrl(`vs/workbench/contrib/extensions/electron-browser/media/save-inverse.svg`)),
light: URI.parse(require.toUrl(`vs/workbench/contrib/extensions/electron-browser/media/save.svg`)),
},
precondition: CONTEXT_EXTENSION_HOST_PROFILE_RECORDED
},
group: 'navigation',
when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID))
});
CommandsRegistry.registerCommand({
id: 'workbench.extensions.installExtension',
description: {
description: localize('workbench.extensions.installExtension.description', "Install the given extension"),
args: [
{
name: localize('workbench.extensions.installExtension.arg.name', "Extension id or VSIX resource uri"),
schema: {
'type': ['object', 'string']
}
}
]
},
handler: async (accessor, arg: string | UriComponents) => {
const extensionManagementService = accessor.get(IExtensionManagementService);
const extensionGalleryService = accessor.get(IExtensionGalleryService);
try {
if (typeof arg === 'string') {
const extension = await extensionGalleryService.getCompatibleExtension({ id: arg });
if (extension) {
await extensionManagementService.installFromGallery(extension);
} else {
throw new Error(localize('notFound', "Extension '{0}' not found.", arg));
}
} else {
const vsix = URI.revive(arg);
await extensionManagementService.install(vsix);
}
} catch (e) {
onUnexpectedError(e);
}
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress';
import { localize } from 'vs/nls';
import { IDisposable } from 'vs/base/common/lifecycle';
import { timeout } from 'vs/base/common/async';
import { ILogService } from 'vs/platform/log/common/log';
export class ExtensionActivationProgress implements IWorkbenchContribution {
private readonly _listener: IDisposable;
constructor(
@IExtensionService extensionService: IExtensionService,
@IProgressService2 progressService: IProgressService2,
@ILogService logService: ILogService,
) {
const options = {
location: ProgressLocation.Window,
title: localize('activation', "Activating Extensions...")
};
this._listener = extensionService.onWillActivateByEvent(e => {
logService.trace('onWillActivateByEvent: ', e.event);
progressService.withProgress(options, _ => Promise.race([e.activation, timeout(5000)]));
});
}
dispose(): void {
this._listener.dispose();
}
}

View File

@@ -0,0 +1,208 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IExtensionService, IResponsiveStateChangeEvent, ICpuProfilerTarget, IExtensionHostProfile, ProfileSession } from 'vs/workbench/services/extensions/common/extensions';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
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 { tmpdir } from 'os';
import { join } from 'vs/base/common/path';
import { writeFile } from 'vs/base/node/pfs';
import { IExtensionHostProfileService, ReportExtensionIssueAction } 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 { generateUuid } from 'vs/base/common/uuid';
import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchContribution {
private readonly _session = new Map<ICpuProfilerTarget, CancellationTokenSource>();
private readonly _blame = new Set<string>();
constructor(
@IExtensionService private readonly _extensionService: IExtensionService,
@IExtensionHostProfileService private readonly _extensionProfileService: IExtensionHostProfileService,
@IExtensionsWorkbenchService private readonly _anotherExtensionService: IExtensionsWorkbenchService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@ILogService private readonly _logService: ILogService,
@INotificationService private readonly _notificationService: INotificationService,
@IEditorService private readonly _editorService: IEditorService,
) {
super();
this._register(_extensionService.onDidChangeResponsiveChange(this._onDidChangeResponsiveChange, this));
}
private async _onDidChangeResponsiveChange(event: IResponsiveStateChangeEvent): Promise<void> {
const { target } = event;
if (!target.canProfileExtensionHost()) {
return;
}
if (event.isResponsive && this._session.has(target)) {
// stop profiling when responsive again
this._session.get(target)!.cancel();
} else if (!event.isResponsive && !this._session.has(target)) {
// start profiling if not yet profiling
const token = new CancellationTokenSource();
this._session.set(target, token);
let session: ProfileSession;
try {
session = await target.startExtensionHostProfile();
} catch (err) {
this._session.delete(target);
// fail silent as this is often
// caused by another party being
// connected already
return;
}
// wait 5 seconds or until responsive again
await new Promise(resolve => {
token.token.onCancellationRequested(resolve);
setTimeout(resolve, 5e3);
});
try {
// stop profiling and analyse results
this._processCpuProfile(await session.stop());
} catch (err) {
onUnexpectedError(err);
} finally {
this._session.delete(target);
}
}
}
private async _processCpuProfile(profile: IExtensionHostProfile) {
interface NamedSlice {
id: string;
total: number;
percentage: number;
}
let data: NamedSlice[] = [];
for (let i = 0; i < profile.ids.length; i++) {
let id = profile.ids[i];
let total = profile.deltas[i];
data.push({ id, total, percentage: 0 });
}
// merge data by identifier
let anchor = 0;
data.sort((a, b) => a.id.localeCompare(b.id));
for (let i = 1; i < data.length; i++) {
if (data[anchor].id === data[i].id) {
data[anchor].total += data[i].total;
} else {
anchor += 1;
data[anchor] = data[i];
}
}
data = data.slice(0, anchor + 1);
const duration = profile.endTime - profile.startTime;
const percentage = duration / 100;
let top: NamedSlice | undefined;
for (const slice of data) {
slice.percentage = Math.round(slice.total / percentage);
if (!top || top.percentage < slice.percentage) {
top = slice;
}
}
if (!top) {
return;
}
const extension = await this._extensionService.getExtension(top.id);
if (!extension) {
// not an extension => idle, gc, self?
return;
}
// add to running extensions view
this._extensionProfileService.setUnresponsiveProfile(extension.identifier, profile);
// print message to log
const path = join(tmpdir(), `exthost-${Math.random().toString(16).slice(2, 8)}.cpuprofile`);
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);
// send telemetry
const id = generateUuid();
/* __GDPR__
"exthostunresponsive" : {
"id" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"data": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
}
*/
this._telemetryService.publicLog('exthostunresponsive', {
id,
duration,
data,
});
// prompt: when really slow/greedy
if (!(top.percentage >= 99 && top.total >= 5e6)) {
return;
}
// prompt: only when you can file an issue
const reportAction = new ReportExtensionIssueAction({
marketplaceInfo: this._anotherExtensionService.local.filter(value => ExtensionIdentifier.equals(value.identifier.id, extension.identifier))[0],
description: extension,
unresponsiveProfile: profile,
status: undefined,
});
if (!reportAction.enabled) {
return;
}
// only blame once per extension, don't blame too often
if (this._blame.has(ExtensionIdentifier.toKey(extension.identifier)) || this._blame.size >= 3) {
return;
}
this._blame.add(ExtensionIdentifier.toKey(extension.identifier));
// user-facing message when very bad...
this._notificationService.prompt(
Severity.Warning,
localize(
'unresponsive-exthost',
"The extension '{0}' took a very long time to complete its last operation and it has prevented other extensions from running.",
extension.displayName || extension.name
),
[{
label: localize('show', 'Show Extensions'),
run: () => this._editorService.openEditor(new RuntimeExtensionsInput())
},
{
label: localize('report', "Report Issue"),
run: () => {
/* __GDPR__
"exthostunresponsive/report" : {
"id" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
}
*/
this._telemetryService.publicLog('exthostunresponsive/report', { id });
return reportAction.run();
}
}],
{ silent: true }
);
}
}

View File

@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { localize } from 'vs/nls';
import { values } from 'vs/base/common/map';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { Action } from 'vs/base/common/actions';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { Disposable } from 'vs/base/common/lifecycle';
export class ExtensionDependencyChecker extends Disposable implements IWorkbenchContribution {
constructor(
@IExtensionService private readonly extensionService: IExtensionService,
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@INotificationService private readonly notificationService: INotificationService,
@IWindowService private readonly windowService: IWindowService
) {
super();
CommandsRegistry.registerCommand('workbench.extensions.installMissingDepenencies', () => this.installMissingDependencies());
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: 'workbench.extensions.installMissingDepenencies',
category: localize('extensions', "Extensions"),
title: localize('auto install missing deps', "Install Missing Dependencies")
}
});
}
private async getUninstalledMissingDependencies(): Promise<string[]> {
const allMissingDependencies = await this.getAllMissingDependencies();
const localExtensions = await this.extensionsWorkbenchService.queryLocal();
return allMissingDependencies.filter(id => localExtensions.every(l => !areSameExtensions(l.identifier, { id })));
}
private async getAllMissingDependencies(): Promise<string[]> {
const runningExtensions = await this.extensionService.getExtensions();
const runningExtensionsIds: Set<string> = runningExtensions.reduce((result, r) => { result.add(r.identifier.value.toLowerCase()); return result; }, new Set<string>());
const missingDependencies: Set<string> = new Set<string>();
for (const extension of runningExtensions) {
if (extension.extensionDependencies) {
extension.extensionDependencies.forEach(dep => {
if (!runningExtensionsIds.has(dep.toLowerCase())) {
missingDependencies.add(dep);
}
});
}
}
return values(missingDependencies);
}
private async installMissingDependencies(): Promise<void> {
const missingDependencies = await this.getUninstalledMissingDependencies();
if (missingDependencies.length) {
const extensions = (await this.extensionsWorkbenchService.queryGallery({ names: missingDependencies, pageSize: missingDependencies.length })).firstPage;
if (extensions.length) {
await Promise.all(extensions.map(extension => this.extensionsWorkbenchService.install(extension)));
this.notificationService.notify({
severity: Severity.Info,
message: localize('finished installing missing deps', "Finished installing missing dependencies. Please reload the window now."),
actions: {
primary: [new Action('realod', localize('reload', "Realod Window"), '', true,
() => this.windowService.reloadWindow())]
}
});
}
} else {
this.notificationService.info(localize('no missing deps', "There are no missing dependencies to install."));
}
}
}

View File

@@ -0,0 +1,203 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { append, $, addClass, removeClass, toggleClass } from 'vs/base/browser/dom';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Action } from 'vs/base/common/actions';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
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, IExtensionsWorkbenchService, ExtensionContainers } from 'vs/workbench/contrib/extensions/common/extensions';
import { InstallAction, UpdateAction, ManageExtensionAction, ReloadAction, MaliciousStatusLabelAction, ExtensionActionItem, StatusLabelAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { Label, RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget } from 'vs/workbench/contrib/extensions/electron-browser/extensionsWidgets';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionManagementServerService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { INotificationService } from 'vs/platform/notification/common/notification';
export interface IExtensionsViewState {
onFocus: Event<IExtension>;
onBlur: Event<IExtension>;
}
export interface ITemplateData {
root: HTMLElement;
element: HTMLElement;
icon: HTMLImageElement;
name: HTMLElement;
// {{SQL CARBON EDIT}}
//installCount: HTMLElement;
//ratings: HTMLElement;
author: HTMLElement;
description: HTMLElement;
extension: IExtension | null;
disposables: IDisposable[];
extensionDisposables: IDisposable[];
actionbar: ActionBar;
}
export class Delegate implements IListVirtualDelegate<IExtension> {
getHeight() { return 62; }
getTemplateId() { return 'extension'; }
}
const actionOptions = { icon: true, label: true, tabOnlyOnFocus: true };
export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
constructor(
private extensionViewState: IExtensionsViewState,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@INotificationService private readonly notificationService: INotificationService,
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionService private readonly extensionService: IExtensionService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService
) { }
get templateId() { return 'extension'; }
renderTemplate(root: HTMLElement): ITemplateData {
const recommendationWidget = this.instantiationService.createInstance(RecommendationWidget, root);
const element = append(root, $('.extension'));
const iconContainer = append(element, $('.icon-container'));
const icon = append(iconContainer, $<HTMLImageElement>('img.icon'));
const badgeWidget = this.instantiationService.createInstance(RemoteBadgeWidget, iconContainer);
const details = append(element, $('.details'));
const headerContainer = append(details, $('.header-container'));
const header = append(headerContainer, $('.header'));
const name = append(header, $('span.name'));
const version = append(header, $('span.version'));
const installCount = append(header, $('span.install-count'));
const ratings = append(header, $('span.ratings'));
const description = append(details, $('.description.ellipsis'));
const footer = append(details, $('.footer'));
const author = append(footer, $('.author.ellipsis'));
const actionbar = new ActionBar(footer, {
animated: false,
actionItemProvider: (action: Action) => {
if (action.id === ManageExtensionAction.ID) {
return (<ManageExtensionAction>action).createActionItem();
}
return new ExtensionActionItem(null, action, actionOptions);
}
});
actionbar.onDidRun(({ error }) => error && this.notificationService.error(error));
const widgets = [
recommendationWidget,
badgeWidget,
this.instantiationService.createInstance(Label, version, (e: IExtension) => e.version),
this.instantiationService.createInstance(InstallCountWidget, installCount, true),
this.instantiationService.createInstance(RatingsWidget, ratings, true)
];
const actions = [
this.instantiationService.createInstance(StatusLabelAction),
this.instantiationService.createInstance(UpdateAction),
this.instantiationService.createInstance(ReloadAction),
this.instantiationService.createInstance(InstallAction),
this.instantiationService.createInstance(MaliciousStatusLabelAction, false),
this.instantiationService.createInstance(ManageExtensionAction)
];
const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets]);
actionbar.push(actions, actionOptions);
const disposables = [...actions, ...widgets, actionbar, extensionContainers];
return {
// {{SQL CARBON EDIT}}
root, element, icon, name, /*installCount, ratings,*/ author, description, disposables, actionbar,
extensionDisposables: [],
set extension(extension: IExtension) {
extensionContainers.extension = extension;
}
};
}
renderPlaceholder(index: number, data: ITemplateData): void {
addClass(data.element, 'loading');
data.root.removeAttribute('aria-label');
data.extensionDisposables = dispose(data.extensionDisposables);
data.icon.src = '';
data.name.textContent = '';
data.author.textContent = '';
data.description.textContent = '';
// {{SQL CARBON EDIT}}
//data.installCount.style.display = 'none';
//data.ratings.style.display = 'none';
data.extension = null;
}
renderElement(extension: IExtension, index: number, data: ITemplateData): void {
removeClass(data.element, 'loading');
data.extensionDisposables = dispose(data.extensionDisposables);
const updateEnablement = async () => {
const runningExtensions = await this.extensionService.getExtensions();
const installed = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, extension.identifier))[0];
if (installed && installed.local) {
const installedExtensionServer = this.extensionManagementServerService.getExtensionManagementServer(installed.local.location);
const isSameExtensionRunning = runningExtensions.some(e => {
if (!areSameExtensions({ id: e.identifier.value }, extension.identifier)) {
return false;
}
const runningExtensionServer = this.extensionManagementServerService.getExtensionManagementServer(e.extensionLocation);
if (!installedExtensionServer || !runningExtensionServer) {
return false;
}
return installedExtensionServer.authority === runningExtensionServer.authority;
});
toggleClass(data.root, 'disabled', !isSameExtensionRunning);
} else {
removeClass(data.root, 'disabled');
}
};
updateEnablement();
this.extensionService.onDidChangeExtensions(() => updateEnablement(), this, data.extensionDisposables);
const onError = Event.once(domEvent(data.icon, 'error'));
onError(() => data.icon.src = extension.iconUrlFallback, null, data.extensionDisposables);
data.icon.src = extension.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 = extension.displayName;
data.author.textContent = extension.publisherDisplayName;
data.description.textContent = extension.description;
// {{SQL CARBON EDIT}}
//data.installCount.style.display = '';
//data.ratings.style.display = '';
data.extension = extension;
if (extension.gallery && extension.gallery.properties && extension.gallery.properties.localizedLanguages && extension.gallery.properties.localizedLanguages.length) {
data.description.textContent = extension.gallery.properties.localizedLanguages.map(name => name[0].toLocaleUpperCase() + name.slice(1)).join(', ');
}
this.extensionViewState.onFocus(e => {
if (areSameExtensions(extension.identifier, e.identifier)) {
data.actionbar.items.forEach(item => (<ExtensionActionItem>item).setFocus(true));
}
}, this, data.extensionDisposables);
this.extensionViewState.onBlur(e => {
if (areSameExtensions(extension.identifier, e.identifier)) {
data.actionbar.items.forEach(item => (<ExtensionActionItem>item).setFocus(false));
}
}, this, data.extensionDisposables);
}
disposeTemplate(data: ITemplateData): void {
data.disposables = dispose(data.disposables);
}
}

View File

@@ -0,0 +1,644 @@
/*---------------------------------------------------------------------------------------------
* 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/extensionsViewlet';
import { localize } from 'vs/nls';
import { ThrottledDelayer, timeout } from 'vs/base/common/async';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Event as EventOf, Emitter } from 'vs/base/common/event';
import { IAction } from 'vs/base/common/actions';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { IViewlet } from 'vs/workbench/common/viewlet';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { append, $, addClass, toggleClass, Dimension } 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, IExtensionsViewlet, VIEWLET_ID, ExtensionState, AutoUpdateConfigurationKey, ShowRecommendationsOnlyOnDemandKey, CloseExtensionDetailsOnViewChangeKey, VIEW_CONTAINER } from '../common/extensions';
import {
ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowDisabledExtensionsAction,
ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction,
EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction
} from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions';
import { IExtensionManagementService, IExtensionManagementServerService, IExtensionManagementServer, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInExtensionsView, BuiltInThemesExtensionsView, BuiltInBasicsExtensionsView, GroupByServerExtensionsView, DefaultRecommendedExtensionsView } from './extensionsViews';
import { OpenGlobalSettingsAction } from 'vs/workbench/contrib/preferences/browser/preferencesActions';
import { IProgressService2, 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, ProgressBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IViewsRegistry, IViewDescriptor, Extensions } from 'vs/workbench/common/views';
import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IWorkspaceContextService, WorkbenchState } 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';
import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views';
import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery';
import { SuggestEnabledInput, attachSuggestEnabledInputBoxStyler } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput';
import { alert } from 'vs/base/browser/ui/aria/aria';
import { createErrorWithActions } from 'vs/base/common/errorsWithActions';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { Registry } from 'vs/platform/registry/common/platform';
interface SearchInputEvent extends Event {
target: HTMLInputElement;
immediate?: boolean;
}
const NonEmptyWorkspaceContext = new RawContextKey<boolean>('nonEmptyWorkspace', false);
const SearchExtensionsContext = new RawContextKey<boolean>('searchExtensions', false);
const HasInstalledExtensionsContext = new RawContextKey<boolean>('hasInstalledExtensions', true);
const SearchBuiltInExtensionsContext = new RawContextKey<boolean>('searchBuiltInExtensions', false);
const RecommendedExtensionsContext = new RawContextKey<boolean>('recommendedExtensions', false);
const DefaultRecommendedExtensionsContext = new RawContextKey<boolean>('defaultRecommendedExtensions', false);
const GroupByServersContext = new RawContextKey<boolean>('groupByServersContext', false);
const viewIdNameMappings: { [id: string]: string } = {
'extensions.listView': localize('marketPlace', "Marketplace"),
'extensions.enabledExtensionList': localize('enabledExtensions', "Enabled"),
'extensions.disabledExtensionList': localize('disabledExtensions', "Disabled"),
'extensions.popularExtensionsList': localize('popularExtensions', "Popular"),
// {{SQL CARBON EDIT}}
'extensions.recommendedList': localize('recommendedExtensions', "Marketplace"),
'extensions.otherrecommendedList': localize('otherRecommendedExtensions', "Other Recommendations"),
'extensions.workspaceRecommendedList': localize('workspaceRecommendedExtensions', "Workspace Recommendations"),
'extensions.builtInExtensionsList': localize('builtInExtensions', "Features"),
'extensions.builtInThemesExtensionsList': localize('builtInThemesExtensions', "Themes"),
'extensions.builtInBasicsExtensionsList': localize('builtInBasicsExtensions', "Programming Languages"),
};
export class ExtensionsViewletViewsContribution implements IWorkbenchContribution {
constructor(
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService
) {
this.registerViews();
}
private registerViews(): void {
let viewDescriptors: IViewDescriptor[] = [];
viewDescriptors.push(this.createMarketPlaceExtensionsListViewDescriptor());
viewDescriptors.push(this.createEnabledExtensionsListViewDescriptor());
viewDescriptors.push(this.createDisabledExtensionsListViewDescriptor());
viewDescriptors.push(this.createPopularExtensionsListViewDescriptor());
viewDescriptors.push(this.createBuiltInExtensionsListViewDescriptor());
viewDescriptors.push(this.createBuiltInBasicsExtensionsListViewDescriptor());
viewDescriptors.push(this.createBuiltInThemesExtensionsListViewDescriptor());
viewDescriptors.push(this.createDefaultRecommendedExtensionsListViewDescriptor());
viewDescriptors.push(this.createOtherRecommendedExtensionsListViewDescriptor());
viewDescriptors.push(this.createWorkspaceRecommendedExtensionsListViewDescriptor());
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
viewDescriptors.push(...this.createExtensionsViewDescriptorsForServer(this.extensionManagementServerService.localExtensionManagementServer));
viewDescriptors.push(...this.createExtensionsViewDescriptorsForServer(this.extensionManagementServerService.remoteExtensionManagementServer));
}
Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).registerViews(viewDescriptors, VIEW_CONTAINER);
}
// View used for any kind of searching
private createMarketPlaceExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.listView';
return {
id,
name: viewIdNameMappings[id],
ctorDescriptor: { ctor: ExtensionsListView },
when: ContextKeyExpr.and(ContextKeyExpr.has('searchExtensions'), ContextKeyExpr.not('searchInstalledExtensions'), ContextKeyExpr.not('searchBuiltInExtensions'), ContextKeyExpr.not('recommendedExtensions'), ContextKeyExpr.not('groupByServersContext')),
weight: 100
};
}
// Separate view for enabled extensions required as we need to show enabled, disabled and recommended sections
// in the default view when there is no search text, but user has installed extensions.
private createEnabledExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.enabledExtensionList';
return {
id,
name: viewIdNameMappings[id],
ctorDescriptor: { ctor: EnabledExtensionsView },
when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions'), ContextKeyExpr.has('hasInstalledExtensions')),
weight: 40,
canToggleVisibility: true,
order: 1
};
}
// Separate view for disabled extensions required as we need to show enabled, disabled and recommended sections
// in the default view when there is no search text, but user has installed extensions.
private createDisabledExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.disabledExtensionList';
return {
id,
name: viewIdNameMappings[id],
ctorDescriptor: { ctor: DisabledExtensionsView },
when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions'), ContextKeyExpr.has('hasInstalledExtensions')),
weight: 10,
canToggleVisibility: true,
order: 3,
collapsed: true
};
}
// 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.
private createPopularExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.popularExtensionsList';
return {
id,
name: viewIdNameMappings[id],
ctorDescriptor: { ctor: ExtensionsListView },
when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions'), ContextKeyExpr.not('hasInstalledExtensions')),
weight: 60,
order: 1
};
}
private createExtensionsViewDescriptorsForServer(server: IExtensionManagementServer): IViewDescriptor[] {
return [{
id: `server.extensionsList.${server.authority}`,
name: server.label,
ctorDescriptor: { ctor: GroupByServerExtensionsView },
when: ContextKeyExpr.has('groupByServersContext'),
weight: 100
}];
}
// Separate view for recommended extensions required as we need to show it along with other views when there is no search text.
// When user has installed extensions, this is shown along with the views for enabled & disabled extensions
// When user has no installed extensions, this is shown along with the view for popular extensions
private createDefaultRecommendedExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.recommendedList';
return {
id,
name: viewIdNameMappings[id],
ctorDescriptor: { ctor: DefaultRecommendedExtensionsView },
when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions'), ContextKeyExpr.has('defaultRecommendedExtensions')),
weight: 40,
order: 2,
canToggleVisibility: true
};
}
// Separate view for recommedations that are not workspace recommendations.
// Shown along with view for workspace recommendations, when using the command that shows recommendations
private createOtherRecommendedExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.otherrecommendedList';
return {
id,
name: viewIdNameMappings[id],
ctorDescriptor: { ctor: RecommendedExtensionsView },
when: ContextKeyExpr.has('recommendedExtensions'),
weight: 50,
canToggleVisibility: true,
order: 2
};
}
// Separate view for workspace recommendations.
// Shown along with view for other recommendations, when using the command that shows recommendations
private createWorkspaceRecommendedExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.workspaceRecommendedList';
return {
id,
name: viewIdNameMappings[id],
ctorDescriptor: { ctor: WorkspaceRecommendedExtensionsView },
when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), ContextKeyExpr.has('nonEmptyWorkspace')),
weight: 50,
canToggleVisibility: true,
order: 1
};
}
private createBuiltInExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.builtInExtensionsList';
return {
id,
name: viewIdNameMappings[id],
ctorDescriptor: { ctor: BuiltInExtensionsView },
when: ContextKeyExpr.has('searchBuiltInExtensions'),
weight: 100,
canToggleVisibility: true
};
}
private createBuiltInThemesExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.builtInThemesExtensionsList';
return {
id,
name: viewIdNameMappings[id],
ctorDescriptor: { ctor: BuiltInThemesExtensionsView },
when: ContextKeyExpr.has('searchBuiltInExtensions'),
weight: 100,
canToggleVisibility: true
};
}
private createBuiltInBasicsExtensionsListViewDescriptor(): IViewDescriptor {
const id = 'extensions.builtInBasicsExtensionsList';
return {
id,
name: viewIdNameMappings[id],
ctorDescriptor: { ctor: BuiltInBasicsExtensionsView },
when: ContextKeyExpr.has('searchBuiltInExtensions'),
weight: 100,
canToggleVisibility: true
};
}
}
export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensionsViewlet {
private onSearchChange: EventOf<string>;
private nonEmptyWorkspaceContextKey: IContextKey<boolean>;
private searchExtensionsContextKey: IContextKey<boolean>;
private hasInstalledExtensionsContextKey: IContextKey<boolean>;
private searchBuiltInExtensionsContextKey: IContextKey<boolean>;
private groupByServersContextKey: IContextKey<boolean>;
private recommendedExtensionsContextKey: IContextKey<boolean>;
private defaultRecommendedExtensionsContextKey: IContextKey<boolean>;
private searchDelayer: ThrottledDelayer<any>;
private root: HTMLElement;
private searchBox: SuggestEnabledInput;
private extensionsBox: HTMLElement;
private primaryActions: IAction[];
private secondaryActions: IAction[] | null;
private disposables: IDisposable[] = [];
private searchViewletState: object;
constructor(
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@ITelemetryService telemetryService: ITelemetryService,
@IProgressService2 private readonly progressService: IProgressService2,
@IInstantiationService instantiationService: IInstantiationService,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@INotificationService private readonly notificationService: INotificationService,
@IViewletService private readonly viewletService: IViewletService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
@IStorageService storageService: IStorageService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IContextKeyService contextKeyService: IContextKeyService,
@IContextMenuService contextMenuService: IContextMenuService,
@IExtensionService extensionService: IExtensionService
) {
super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
this.searchDelayer = new ThrottledDelayer(500);
this.nonEmptyWorkspaceContextKey = NonEmptyWorkspaceContext.bindTo(contextKeyService);
this.searchExtensionsContextKey = SearchExtensionsContext.bindTo(contextKeyService);
this.hasInstalledExtensionsContextKey = HasInstalledExtensionsContext.bindTo(contextKeyService);
this.searchBuiltInExtensionsContextKey = SearchBuiltInExtensionsContext.bindTo(contextKeyService);
this.recommendedExtensionsContextKey = RecommendedExtensionsContext.bindTo(contextKeyService);
this.groupByServersContextKey = GroupByServersContext.bindTo(contextKeyService);
this.defaultRecommendedExtensionsContextKey = DefaultRecommendedExtensionsContext.bindTo(contextKeyService);
this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue<boolean>(ShowRecommendationsOnlyOnDemandKey));
this.disposables.push(this.viewletService.onDidViewletOpen(this.onViewletOpen, this, this.disposables));
this.searchViewletState = this.getMemento(StorageScope.WORKSPACE);
this.extensionManagementService.getInstalled(ExtensionType.User).then(result => {
this.hasInstalledExtensionsContextKey.set(result.length > 0);
});
this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(AutoUpdateConfigurationKey)) {
this.secondaryActions = null;
this.updateTitleArea();
}
if (e.affectedKeys.indexOf(ShowRecommendationsOnlyOnDemandKey) > -1) {
this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue<boolean>(ShowRecommendationsOnlyOnDemandKey));
}
}, this, this.disposables);
}
create(parent: HTMLElement): void {
addClass(parent, 'extensions-viewlet');
this.root = parent;
const header = append(this.root, $('.header'));
const placeholder = localize('searchExtensions', "Search Extensions in Marketplace");
const searchValue = this.searchViewletState['query.value'] ? this.searchViewletState['query.value'] : '';
this.searchBox = this.instantiationService.createInstance(SuggestEnabledInput, `${VIEWLET_ID}.searchbox`, header, {
triggerCharacters: ['@'],
sortKey: item => {
if (item.indexOf(':') === -1) { return 'a'; }
else if (/ext:/.test(item) || /tag:/.test(item)) { return 'b'; }
else if (/sort:/.test(item)) { return 'c'; }
else { return 'd'; }
},
provideResults: (query) => Query.suggestions(query)
}, placeholder, 'extensions:searchinput', { placeholderText: placeholder, value: searchValue });
if (this.searchBox.getValue()) {
this.triggerSearch();
}
this.disposables.push(attachSuggestEnabledInputBoxStyler(this.searchBox, this.themeService));
this.disposables.push(this.searchBox);
const _searchChange = new Emitter<string>();
this.onSearchChange = _searchChange.event;
this.searchBox.onInputDidChange(() => {
this.triggerSearch();
_searchChange.fire(this.searchBox.getValue());
}, this, this.disposables);
this.searchBox.onShouldFocusResults(() => this.focusListView(), this, this.disposables);
this._register(this.onDidChangeVisibility(visible => {
if (visible) {
this.searchBox.focus();
}
}));
this.extensionsBox = append(this.root, $('.extensions'));
super.create(this.extensionsBox);
}
focus(): void {
this.searchBox.focus();
}
layout(dimension: Dimension): void {
toggleClass(this.root, 'narrow', dimension.width <= 300);
this.searchBox.layout({ height: 20, width: dimension.width - 34 });
super.layout(new Dimension(dimension.width, dimension.height - 38));
}
getOptimalWidth(): number {
return 400;
}
getActions(): IAction[] {
if (!this.primaryActions) {
this.primaryActions = [
this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange)
];
}
return this.primaryActions;
}
getSecondaryActions(): IAction[] {
if (!this.secondaryActions) {
this.secondaryActions = [
this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, ShowOutdatedExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, ShowEnabledExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, ShowDisabledExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, ShowBuiltInExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL),
new Separator(),
// {{SQL CARBON EDIT}}
//this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.install', localize('sort by installs', "Sort By: Install Count"), this.onSearchChange, 'installs'),
//this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.rating', localize('sort by rating', "Sort By: Rating"), this.onSearchChange, 'rating'),
this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.name', localize('sort by name', "Sort By: Name"), this.onSearchChange, 'name'),
new Separator(),
this.instantiationService.createInstance(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL),
...(this.configurationService.getValue(AutoUpdateConfigurationKey) ? [this.instantiationService.createInstance(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL)] : [this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL), this.instantiationService.createInstance(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL)]),
this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL),
new Separator(),
this.instantiationService.createInstance(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL),
this.instantiationService.createInstance(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL)
];
}
return this.secondaryActions;
}
search(value: string): void {
const event = new Event('input', { bubbles: true }) as SearchInputEvent;
event.immediate = true;
this.searchBox.setValue(value);
}
private triggerSearch(immediate = false): void {
this.searchDelayer.trigger(() => this.doSearch(), immediate || !this.searchBox.getValue() ? 0 : 500).then(undefined, err => this.onError(err));
}
private normalizedQuery(): string {
return this.searchBox.getValue().replace(/@category/g, 'category').replace(/@tag:/g, 'tag:').replace(/@ext:/g, 'ext:');
}
protected saveState(): void {
const value = this.searchBox.getValue();
if (ExtensionsListView.isInstalledExtensionsQuery(value)) {
this.searchViewletState['query.value'] = value;
} else {
this.searchViewletState['query.value'] = '';
}
super.saveState();
}
private doSearch(): Promise<any> {
const value = this.normalizedQuery();
this.searchExtensionsContextKey.set(!!value);
this.searchBuiltInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value));
this.groupByServersContextKey.set(ExtensionsListView.isGroupByServersExtensionsQuery(value));
this.recommendedExtensionsContextKey.set(ExtensionsListView.isRecommendedExtensionsQuery(value));
this.nonEmptyWorkspaceContextKey.set(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY);
if (value) {
return this.progress(Promise.all(this.panels.map(view =>
(<ExtensionsListView>view).show(this.normalizedQuery())
.then(model => this.alertSearchResult(model.length, view.id))
)));
}
return Promise.resolve(null);
}
protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] {
const addedViews = super.onDidAddViews(added);
this.progress(Promise.all(addedViews.map(addedView =>
(<ExtensionsListView>addedView).show(this.normalizedQuery())
.then(model => this.alertSearchResult(model.length, addedView.id))
)));
return addedViews;
}
private alertSearchResult(count: number, viewId: string) {
switch (count) {
case 0:
break;
case 1:
if (viewIdNameMappings[viewId]) {
alert(localize('extensionFoundInSection', "1 extension found in the {0} section.", viewIdNameMappings[viewId]));
} else {
alert(localize('extensionFound', "1 extension found."));
}
break;
default:
if (viewIdNameMappings[viewId]) {
alert(localize('extensionsFoundInSection', "{0} extensions found in the {1} section.", count, viewIdNameMappings[viewId]));
} else {
alert(localize('extensionsFound', "{0} extensions found.", count));
}
break;
}
}
private count(): number {
return this.panels.reduce((count, view) => (<ExtensionsListView>view).count() + count, 0);
}
private focusListView(): void {
if (this.count() > 0) {
this.panels[0].focus();
}
}
private onViewletOpen(viewlet: IViewlet): void {
if (!viewlet || viewlet.getId() === VIEWLET_ID) {
return;
}
if (this.configurationService.getValue<boolean>(CloseExtensionDetailsOnViewChangeKey)) {
const promises = this.editorGroupService.groups.map(group => {
const editors = group.editors.filter(input => input instanceof ExtensionsInput);
const promises = editors.map(editor => group.closeEditor(editor));
return Promise.all(promises);
});
Promise.all(promises);
}
}
private progress<T>(promise: Promise<T>): Promise<T> {
return this.progressService.withProgress({ location: ProgressLocation.Extensions }, () => promise);
}
private onError(err: any): void {
if (isPromiseCanceledError(err)) {
return;
}
const message = err && err.message || '';
if (/ECONNREFUSED/.test(message)) {
const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), {
actions: [
this.instantiationService.createInstance(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL)
]
});
this.notificationService.error(error);
return;
}
this.notificationService.error(err);
}
dispose(): void {
this.disposables = dispose(this.disposables);
super.dispose();
}
}
export class StatusUpdater implements IWorkbenchContribution {
private disposables: IDisposable[];
private badgeHandle: IDisposable;
constructor(
@IActivityService private readonly activityService: IActivityService,
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService
) {
extensionsWorkbenchService.onChange(this.onServiceChange, this, this.disposables);
}
private onServiceChange(): void {
dispose(this.badgeHandle);
if (this.extensionsWorkbenchService.local.some(e => e.state === ExtensionState.Installing)) {
this.badgeHandle = this.activityService.showActivity(VIEWLET_ID, new ProgressBadge(() => localize('extensions', "Extensions")), 'extensions-badge progress-badge');
return;
}
const outdated = this.extensionsWorkbenchService.local.reduce((r, e) => r + (e.outdated && e.enablementState !== EnablementState.Disabled && e.enablementState !== EnablementState.WorkspaceDisabled ? 1 : 0), 0);
if (outdated > 0) {
const badge = new NumberBadge(outdated, n => localize('outdatedExtensions', '{0} Outdated Extensions', n));
this.badgeHandle = this.activityService.showActivity(VIEWLET_ID, badge, 'extensions-badge count-badge');
}
}
dispose(): void {
this.disposables = dispose(this.disposables);
dispose(this.badgeHandle);
}
}
export class MaliciousExtensionChecker implements IWorkbenchContribution {
private disposables: IDisposable[];
constructor(
@IExtensionManagementService private readonly extensionsManagementService: IExtensionManagementService,
@IWindowService private readonly windowService: IWindowService,
@ILogService private readonly logService: ILogService,
@INotificationService private readonly notificationService: INotificationService,
@IEnvironmentService private readonly environmentService: IEnvironmentService
) {
if (!this.environmentService.disableExtensions) {
this.loopCheckForMaliciousExtensions();
}
}
private loopCheckForMaliciousExtensions(): void {
this.checkForMaliciousExtensions()
.then(() => timeout(1000 * 60 * 5)) // every five minutes
.then(() => this.loopCheckForMaliciousExtensions());
}
private checkForMaliciousExtensions(): Promise<any> {
return this.extensionsManagementService.getExtensionsReport().then(report => {
const maliciousSet = getMaliciousExtensionsSet(report);
return this.extensionsManagementService.getInstalled(ExtensionType.User).then(installed => {
const maliciousExtensions = installed
.filter(e => maliciousSet.has(e.identifier.id));
if (maliciousExtensions.length) {
return Promise.all(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e, true).then(() => {
this.notificationService.prompt(
Severity.Warning,
localize('malicious warning', "We have uninstalled '{0}' which was reported to be problematic.", e.identifier.id),
[{
label: localize('reloadNow', "Reload Now"),
run: () => this.windowService.reloadWindow()
}],
{ sticky: true }
);
})));
} else {
return Promise.resolve(null);
}
});
}, err => this.logService.error(err));
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
}

View File

@@ -0,0 +1,933 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { dispose, Disposable } from 'vs/base/common/lifecycle';
import { assign } from 'vs/base/common/objects';
import { Event, Emitter } from 'vs/base/common/event';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { PagedModel, IPagedModel, IPager, DelayedPagedModel } from 'vs/base/common/paging';
import { SortBy, SortOrder, IQueryOptions, IExtensionTipsService, IExtensionRecommendation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { append, $, toggleClass } from 'vs/base/browser/dom';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Delegate, Renderer, IExtensionsViewState } from 'vs/workbench/contrib/extensions/electron-browser/extensionsList';
import { IExtension, IExtensionsWorkbenchService } from '../common/extensions';
import { Query } from '../common/extensionQuery';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachBadgeStyler } from 'vs/platform/theme/common/styler';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { OpenGlobalSettingsAction } from 'vs/workbench/contrib/preferences/browser/preferencesActions';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions';
import { WorkbenchPagedList } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { distinct } from 'vs/base/common/arrays';
import { IExperimentService, IExperiment, ExperimentActionType } from 'vs/workbench/contrib/experiments/node/experimentService';
import { alert } from 'vs/base/browser/ui/aria/aria';
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 } from 'vs/base/common/actions';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import product from 'vs/platform/product/node/product';
class ExtensionsViewState extends Disposable implements IExtensionsViewState {
private readonly _onFocus: Emitter<IExtension> = this._register(new Emitter<IExtension>());
readonly onFocus: Event<IExtension> = this._onFocus.event;
private readonly _onBlur: Emitter<IExtension> = this._register(new Emitter<IExtension>());
readonly onBlur: Event<IExtension> = this._onBlur.event;
private currentlyFocusedItems: IExtension[] = [];
onFocusChange(extensions: IExtension[]): void {
this.currentlyFocusedItems.forEach(extension => this._onBlur.fire(extension));
this.currentlyFocusedItems = extensions;
this.currentlyFocusedItems.forEach(extension => this._onFocus.fire(extension));
}
}
export class ExtensionsListView extends ViewletPanel {
private messageBox: HTMLElement;
private extensionsList: HTMLElement;
private badge: CountBadge;
protected badgeContainer: HTMLElement;
private list: WorkbenchPagedList<IExtension> | null;
constructor(
private options: IViewletViewOptions,
@INotificationService protected notificationService: INotificationService,
@IKeybindingService keybindingService: IKeybindingService,
@IContextMenuService contextMenuService: IContextMenuService,
@IInstantiationService protected instantiationService: IInstantiationService,
@IThemeService private readonly themeService: IThemeService,
@IExtensionService private readonly extensionService: IExtensionService,
@IExtensionsWorkbenchService protected extensionsWorkbenchService: IExtensionsWorkbenchService,
@IEditorService private readonly editorService: IEditorService,
@IExtensionTipsService protected tipsService: IExtensionTipsService,
@IModeService private readonly modeService: IModeService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IConfigurationService configurationService: IConfigurationService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
@IExperimentService private readonly experimentService: IExperimentService,
@IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService
) {
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService);
}
protected renderHeader(container: HTMLElement): void {
this.renderHeaderTitle(container);
}
renderHeaderTitle(container: HTMLElement): void {
super.renderHeaderTitle(container, this.options.title);
this.badgeContainer = append(container, $('.count-badge-wrapper'));
this.badge = new CountBadge(this.badgeContainer);
this.disposables.push(attachBadgeStyler(this.badge, this.themeService));
}
renderBody(container: HTMLElement): void {
this.extensionsList = append(container, $('.extensions-list'));
this.messageBox = append(container, $('.message'));
const delegate = new Delegate();
const extensionsViewState = new ExtensionsViewState();
const renderer = this.instantiationService.createInstance(Renderer, extensionsViewState);
this.list = this.instantiationService.createInstance(WorkbenchPagedList, this.extensionsList, delegate, [renderer], {
ariaLabel: localize('extensions', "Extensions"),
multipleSelectionSupport: false,
setRowLineHeight: false,
horizontalScrolling: false
}) as WorkbenchPagedList<IExtension>;
this.list.onContextMenu(e => this.onContextMenu(e), this, this.disposables);
this.list.onFocusChange(e => extensionsViewState.onFocusChange(e.elements), this, this.disposables);
this.disposables.push(this.list);
this.disposables.push(extensionsViewState);
Event.chain(this.list.onOpen)
.map(e => e.elements[0])
.filter(e => !!e)
.on(this.openExtension, this, this.disposables);
Event.chain(this.list.onPin)
.map(e => e.elements[0])
.filter(e => !!e)
.on(this.pin, this, this.disposables);
}
protected layoutBody(height: number, width: number): void {
this.extensionsList.style.height = height + 'px';
if (this.list) {
this.list.layout(height, width);
}
}
async show(query: string): Promise<IPagedModel<IExtension>> {
const parsedQuery = Query.parse(query);
let options: IQueryOptions = {
sortOrder: SortOrder.Default
};
switch (parsedQuery.sortBy) {
case 'installs': options = assign(options, { sortBy: SortBy.InstallCount }); break;
case 'rating': options = assign(options, { sortBy: SortBy.WeightedRating }); break;
case 'name': options = assign(options, { sortBy: SortBy.Title }); break;
}
const successCallback = model => {
this.setModel(model);
return model;
};
const errorCallback = e => {
console.warn('Error querying extensions gallery', e);
const model = new PagedModel([]);
this.setModel(model, true);
return model;
};
if (ExtensionsListView.isInstalledExtensionsQuery(query) || /@builtin/.test(query)) {
return await this.queryLocal(parsedQuery, options).then(successCallback).catch(errorCallback);
}
return await this.queryGallery(parsedQuery, options).then(successCallback).catch(errorCallback);
}
count(): number {
return this.list ? this.list.length : 0;
}
protected showEmptyModel(): Promise<IPagedModel<IExtension>> {
const emptyModel = new PagedModel([]);
this.setModel(emptyModel);
return Promise.resolve(emptyModel);
}
private async onContextMenu(e: IListContextMenuEvent<IExtension>): Promise<void> {
if (e.element) {
const runningExtensions = await this.extensionService.getExtensions();
const colorThemes = await this.workbenchThemeService.getColorThemes();
const fileIconThemes = await this.workbenchThemeService.getFileIconThemes();
const manageExtensionAction = this.instantiationService.createInstance(ManageExtensionAction);
manageExtensionAction.extension = e.element;
const groups = manageExtensionAction.getActionGroups(runningExtensions, colorThemes, fileIconThemes);
let actions: IAction[] = [];
for (const menuActions of groups) {
actions = [...actions, ...menuActions, new Separator()];
}
if (manageExtensionAction.enabled) {
this.contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
getActions: () => actions.slice(0, actions.length - 1)
});
}
}
}
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, '');
}
value = value.replace(/@builtin/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase();
let result = await this.extensionsWorkbenchService.queryLocal();
result = result
.filter(e => e.type === ExtensionType.System && (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1));
if (showThemesOnly) {
const themesExtensions = result.filter(e => {
return e.local
&& e.local.manifest
&& e.local.manifest.contributes
&& Array.isArray(e.local.manifest.contributes.themes)
&& e.local.manifest.contributes.themes.length;
});
return this.getPagedModel(this.sortExtensions(themesExtensions, options));
}
if (showBasicsOnly) {
const basics = result.filter(e => {
return e.local && e.local.manifest
&& e.local.manifest.contributes
&& Array.isArray(e.local.manifest.contributes.grammars)
&& e.local.manifest.contributes.grammars.length
&& e.local.identifier.id !== 'vscode.git';
});
return this.getPagedModel(this.sortExtensions(basics, options));
}
if (showFeaturesOnly) {
const others = result.filter(e => {
return e.local
&& e.local.manifest
&& e.local.manifest.contributes
&& (!Array.isArray(e.local.manifest.contributes.grammars) || e.local.identifier.id === 'vscode.git')
&& !Array.isArray(e.local.manifest.contributes.themes);
});
return this.getPagedModel(this.sortExtensions(others, options));
}
return this.getPagedModel(this.sortExtensions(result, options));
}
const categories: string[] = [];
value = value.replace(/\bcategory:("([^"]*)"|([^"]\S*))(\s+|\b|$)/g, (_, quotedCategory, category) => {
const entry = (category || quotedCategory || '').toLowerCase();
if (categories.indexOf(entry) === -1) {
categories.push(entry);
}
return '';
});
if (/@installed/i.test(value)) {
// Show installed extensions
value = value.replace(/@installed/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase();
let result = await this.extensionsWorkbenchService.queryLocal();
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))));
return this.getPagedModel(this.sortExtensions(result, options));
}
if (/@outdated/i.test(value)) {
value = value.replace(/@outdated/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase();
const local = await this.extensionsWorkbenchService.queryLocal();
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();
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 }, 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()).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 }, 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([]);
}
private async queryGallery(query: Query, options: IQueryOptions): Promise<IPagedModel<IExtension>> {
const hasUserDefinedSortOrder = options.sortBy !== undefined;
if (!hasUserDefinedSortOrder && !query.value.trim()) {
options.sortBy = SortBy.InstallCount;
}
let value = query.value;
const idRegex = /@id:(([a-z0-9A-Z][a-z0-9\-A-Z]*)\.([a-z0-9A-Z][a-z0-9\-A-Z]*))/g;
let idMatch;
const names: string[] = [];
while ((idMatch = idRegex.exec(value)) !== null) {
const name = idMatch[1];
names.push(name);
}
if (names.length) {
return this.extensionsWorkbenchService.queryGallery({ names, source: 'queryById' })
.then(pager => this.getPagedModel(pager));
}
if (ExtensionsListView.isWorkspaceRecommendedExtensionsQuery(query.value)) {
return this.getWorkspaceRecommendationsModel(query, options);
} else if (ExtensionsListView.isKeymapsRecommendedExtensionsQuery(query.value)) {
return this.getKeymapRecommendationsModel(query, options);
} else if (/@recommended:all/i.test(query.value) || ExtensionsListView.isSearchRecommendedExtensionsQuery(query.value)) {
return this.getAllRecommendationsModel(query, options);
} else if (ExtensionsListView.isRecommendedExtensionsQuery(query.value)) {
return this.getRecommendationsModel(query, options);
// {{SQL CARBON EDIT}}
} else if (ExtensionsListView.isAllMarketplaceExtensionsQuery(query.value)) {
return this.getAllMarketplaceModel(query, options);
}
if (/\bcurated:([^\s]+)\b/.test(query.value)) {
return this.getCuratedModel(query, options);
}
let text = query.value;
const extensionRegex = /\bext:([^\s]+)\b/g;
if (extensionRegex.test(query.value)) {
text = query.value.replace(extensionRegex, (m, ext) => {
// Get curated keywords
const lookup = product.extensionKeywords || {};
const keywords = lookup[ext] || [];
// Get mode name
const modeId = this.modeService.getModeIdByFilepathOrFirstLine(`.${ext}`);
const languageName = modeId && this.modeService.getLanguageName(modeId);
const languageTag = languageName ? ` tag:"${languageName}"` : '';
// Construct a rich query
return `tag:"__ext_${ext}" tag:"__ext_.${ext}" ${keywords.map(tag => `tag:"${tag}"`).join(' ')}${languageTag} tag:"${ext}"`;
});
if (text !== query.value) {
options = assign(options, { text: text.substr(0, 350), source: 'file-extension-tags' });
return this.extensionsWorkbenchService.queryGallery(options).then(pager => this.getPagedModel(pager));
}
}
let preferredResults: string[] = [];
if (text) {
options = assign(options, { text: text.substr(0, 350), source: 'searchText' });
if (!hasUserDefinedSortOrder) {
const searchExperiments = await this.getSearchExperiments();
for (const experiment of searchExperiments) {
if (experiment.action && text.toLowerCase() === experiment.action.properties['searchText'] && Array.isArray(experiment.action.properties['preferredResults'])) {
preferredResults = experiment.action.properties['preferredResults'];
options.source += `-experiment-${experiment.id}`;
break;
}
}
}
} else {
options.source = 'viewlet';
}
const pager = await this.extensionsWorkbenchService.queryGallery(options);
let positionToUpdate = 0;
for (const preferredResult of preferredResults) {
for (let j = positionToUpdate; j < pager.firstPage.length; j++) {
if (areSameExtensions(pager.firstPage[j].identifier, { id: preferredResult })) {
if (positionToUpdate !== j) {
const preferredExtension = pager.firstPage.splice(j, 1)[0];
pager.firstPage.splice(positionToUpdate, 0, preferredExtension);
positionToUpdate++;
}
break;
}
}
}
return this.getPagedModel(pager);
}
private _searchExperiments: Promise<IExperiment[]>;
private getSearchExperiments(): Promise<IExperiment[]> {
if (!this._searchExperiments) {
this._searchExperiments = this.experimentService.getExperimentsByType(ExperimentActionType.ExtensionSearchResults);
}
return this._searchExperiments;
}
private sortExtensions(extensions: IExtension[], options: IQueryOptions): IExtension[] {
switch (options.sortBy) {
case SortBy.InstallCount:
extensions = extensions.sort((e1, e2) => typeof e2.installCount === 'number' && typeof e1.installCount === 'number' ? e2.installCount - e1.installCount : NaN);
break;
case SortBy.AverageRating:
case SortBy.WeightedRating:
extensions = extensions.sort((e1, e2) => typeof e2.rating === 'number' && typeof e1.rating === 'number' ? e2.rating - e1.rating : NaN);
break;
default:
extensions = extensions.sort((e1, e2) => e1.displayName.localeCompare(e2.displayName));
break;
}
if (options.sortOrder === SortOrder.Descending) {
extensions = extensions.reverse();
}
return extensions;
}
// Get All types of recommendations, trimmed to show a max of 8 at any given time
private getAllRecommendationsModel(query: Query, options: IQueryOptions): Promise<IPagedModel<IExtension>> {
const value = query.value.replace(/@recommended:all/g, '').replace(/@recommended/g, '').trim().toLowerCase();
return this.extensionsWorkbenchService.queryLocal()
.then(result => result.filter(e => e.type === ExtensionType.User))
.then(local => {
const fileBasedRecommendations = this.tipsService.getFileBasedRecommendations();
const othersPromise = this.tipsService.getOtherRecommendations();
const workspacePromise = this.tipsService.getWorkspaceRecommendations();
return Promise.all([othersPromise, workspacePromise])
.then(([others, workspaceRecommendations]) => {
const names = this.getTrimmedRecommendations(local, value, fileBasedRecommendations, others, workspaceRecommendations);
const recommendationsWithReason = this.tipsService.getAllRecommendationsWithReason();
/* __GDPR__
"extensionAllRecommendations:open" : {
"count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"recommendations": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('extensionAllRecommendations:open', {
count: names.length,
recommendations: names.map(id => {
return {
id,
recommendationReason: recommendationsWithReason[id.toLowerCase()].reasonId
};
})
});
if (!names.length) {
return Promise.resolve(new PagedModel([]));
}
options.source = 'recommendations-all';
return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length }))
.then(pager => {
this.sortFirstPage(pager, names);
return this.getPagedModel(pager || []);
});
});
});
}
private async getCuratedModel(query: Query, options: IQueryOptions): Promise<IPagedModel<IExtension>> {
const value = query.value.replace(/curated:/g, '').trim();
const names = await this.experimentService.getCuratedExtensionsList(value);
if (Array.isArray(names) && names.length) {
options.source = `curated:${value}`;
const pager = await this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length }));
this.sortFirstPage(pager, names);
return this.getPagedModel(pager || []);
}
return new PagedModel([]);
}
// Get All types of recommendations other than Workspace recommendations, trimmed to show a max of 8 at any given time
private getRecommendationsModel(query: Query, options: IQueryOptions): Promise<IPagedModel<IExtension>> {
const value = query.value.replace(/@recommended/g, '').trim().toLowerCase();
return this.extensionsWorkbenchService.queryLocal()
.then(result => result.filter(e => e.type === ExtensionType.User))
.then(local => {
let fileBasedRecommendations = this.tipsService.getFileBasedRecommendations();
const othersPromise = this.tipsService.getOtherRecommendations();
const workspacePromise = this.tipsService.getWorkspaceRecommendations();
return Promise.all([othersPromise, workspacePromise])
.then(([others, workspaceRecommendations]) => {
fileBasedRecommendations = fileBasedRecommendations.filter(x => workspaceRecommendations.every(({ extensionId }) => x.extensionId !== extensionId));
others = others.filter(x => x => workspaceRecommendations.every(({ extensionId }) => x.extensionId !== extensionId));
const names = this.getTrimmedRecommendations(local, value, fileBasedRecommendations, others, []);
const recommendationsWithReason = this.tipsService.getAllRecommendationsWithReason();
/* __GDPR__
"extensionRecommendations:open" : {
"count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"recommendations": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('extensionRecommendations:open', {
count: names.length,
recommendations: names.map(id => {
return {
id,
recommendationReason: recommendationsWithReason[id.toLowerCase()].reasonId
};
})
});
if (!names.length) {
return Promise.resolve(new PagedModel([]));
}
options.source = 'recommendations';
return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length }))
.then(pager => {
this.sortFirstPage(pager, names);
return this.getPagedModel(pager || []);
});
});
});
}
// {{SQL CARBON EDIT}}
private getAllMarketplaceModel(query: Query, options: IQueryOptions): Promise<IPagedModel<IExtension>> {
const value = query.value.trim().toLowerCase();
return this.extensionsWorkbenchService.queryLocal()
.then(result => result.filter(e => e.type === ExtensionType.User))
.then(local => {
return this.tipsService.getOtherRecommendations().then((recommmended) => {
const installedExtensions = local.map(x => `${x.publisher}.${x.name}`);
options = assign(options, { text: value, source: 'searchText' });
return this.extensionsWorkbenchService.queryGallery(options).then((pager) => {
// filter out installed extensions
pager.firstPage = pager.firstPage.filter((p) => {
return installedExtensions.indexOf(`${p.publisher}.${p.name}`) === -1;
});
// 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;
// sort recommeded extensions before other extensions
if (isRecommendedA !== isRecommendedB) {
return (isRecommendedA && !isRecommendedB) ? -1 : 1;
}
// otherwise sort by name
return a.displayName.toLowerCase() < b.displayName.toLowerCase() ? -1 : 1;
});
pager.total = pager.firstPage.length;
pager.pageSize = pager.firstPage.length;
return this.getPagedModel(pager);
});
});
});
}
// Given all recommendations, trims and returns recommendations in the relevant order after filtering out installed extensions
private getTrimmedRecommendations(installedExtensions: IExtension[], value: string, fileBasedRecommendations: IExtensionRecommendation[], otherRecommendations: IExtensionRecommendation[], workpsaceRecommendations: IExtensionRecommendation[]): string[] {
const totalCount = 8;
workpsaceRecommendations = workpsaceRecommendations
.filter(recommendation => {
return !this.isRecommendationInstalled(recommendation, installedExtensions)
&& recommendation.extensionId.toLowerCase().indexOf(value) > -1;
});
fileBasedRecommendations = fileBasedRecommendations.filter(recommendation => {
return !this.isRecommendationInstalled(recommendation, installedExtensions)
&& workpsaceRecommendations.every(workspaceRecommendation => workspaceRecommendation.extensionId !== recommendation.extensionId)
&& recommendation.extensionId.toLowerCase().indexOf(value) > -1;
});
otherRecommendations = otherRecommendations.filter(recommendation => {
return !this.isRecommendationInstalled(recommendation, installedExtensions)
&& fileBasedRecommendations.every(fileBasedRecommendation => fileBasedRecommendation.extensionId !== recommendation.extensionId)
&& workpsaceRecommendations.every(workspaceRecommendation => workspaceRecommendation.extensionId !== recommendation.extensionId)
&& recommendation.extensionId.toLowerCase().indexOf(value) > -1;
});
const otherCount = Math.min(2, otherRecommendations.length);
const fileBasedCount = Math.min(fileBasedRecommendations.length, totalCount - workpsaceRecommendations.length - otherCount);
const recommendations = workpsaceRecommendations;
recommendations.push(...fileBasedRecommendations.splice(0, fileBasedCount));
recommendations.push(...otherRecommendations.splice(0, otherCount));
return distinct(recommendations.map(({ extensionId }) => extensionId));
}
private isRecommendationInstalled(recommendation: IExtensionRecommendation, installed: IExtension[]): boolean {
return installed.some(i => areSameExtensions(i.identifier, { id: recommendation.extensionId }));
}
private getWorkspaceRecommendationsModel(query: Query, options: IQueryOptions): Promise<IPagedModel<IExtension>> {
const value = query.value.replace(/@recommended:workspace/g, '').trim().toLowerCase();
return this.tipsService.getWorkspaceRecommendations()
.then(recommendations => {
const names = recommendations.map(({ extensionId }) => extensionId).filter(name => name.toLowerCase().indexOf(value) > -1);
/* __GDPR__
"extensionWorkspaceRecommendations:open" : {
"count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
}
*/
this.telemetryService.publicLog('extensionWorkspaceRecommendations:open', { count: names.length });
if (!names.length) {
return Promise.resolve(new PagedModel([]));
}
options.source = 'recommendations-workspace';
return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length }))
.then(pager => this.getPagedModel(pager || []));
});
}
private getKeymapRecommendationsModel(query: Query, options: IQueryOptions): Promise<IPagedModel<IExtension>> {
const value = query.value.replace(/@recommended:keymaps/g, '').trim().toLowerCase();
const names: string[] = this.tipsService.getKeymapRecommendations().map(({ extensionId }) => extensionId)
.filter(extensionId => extensionId.toLowerCase().indexOf(value) > -1);
if (!names.length) {
return Promise.resolve(new PagedModel([]));
}
options.source = 'recommendations-keymaps';
return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length }))
.then(result => this.getPagedModel(result));
}
// Sorts the firstPage of the pager in the same order as given array of extension ids
private sortFirstPage(pager: IPager<IExtension>, ids: string[]) {
ids = ids.map(x => x.toLowerCase());
pager.firstPage.sort((a, b) => {
return ids.indexOf(a.identifier.id.toLowerCase()) < ids.indexOf(b.identifier.id.toLowerCase()) ? -1 : 1;
});
}
private setModel(model: IPagedModel<IExtension>, isGalleryError?: boolean) {
if (this.list) {
this.list.model = new DelayedPagedModel(model);
this.list.scrollTop = 0;
const count = this.count();
toggleClass(this.extensionsList, 'hidden', count === 0);
toggleClass(this.messageBox, 'hidden', count > 0);
this.badge.setCount(count);
if (count === 0 && this.isBodyVisible()) {
this.messageBox.textContent = isGalleryError ? localize('galleryError', "We cannot connect to the Extensions Marketplace at this time, please try again later.") : localize('no extensions found', "No extensions found.");
if (isGalleryError) {
alert(this.messageBox.textContent);
}
} else {
this.messageBox.textContent = '';
}
}
}
private openExtension(extension: IExtension): void {
this.extensionsWorkbenchService.open(extension).then(undefined, err => this.onError(err));
}
private pin(): void {
const activeControl = this.editorService.activeControl;
if (activeControl) {
activeControl.group.pinEditor(activeControl.input);
activeControl.focus();
}
}
private onError(err: any): void {
if (isPromiseCanceledError(err)) {
return;
}
const message = err && err.message || '';
if (/ECONNREFUSED/.test(message)) {
const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), {
actions: [
this.instantiationService.createInstance(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL)
]
});
this.notificationService.error(error);
return;
}
this.notificationService.error(err);
}
private getPagedModel(arg: IPager<IExtension> | IExtension[]): IPagedModel<IExtension> {
if (Array.isArray(arg)) {
return new PagedModel(arg);
}
const pager = {
total: arg.total,
pageSize: arg.pageSize,
firstPage: arg.firstPage,
getPage: (pageIndex: number, cancellationToken: CancellationToken) => arg.getPage(pageIndex, cancellationToken)
};
return new PagedModel(pager);
}
dispose(): void {
super.dispose();
this.disposables = dispose(this.disposables);
this.list = null;
}
static isBuiltInExtensionsQuery(query: string): boolean {
return /^\s*@builtin\s*$/i.test(query);
}
static isInstalledExtensionsQuery(query: string): boolean {
return /@installed|@outdated|@enabled|@disabled/i.test(query);
}
static isGroupByServersExtensionsQuery(query: string): boolean {
return !!Query.parse(query).groupBy;
}
static isRecommendedExtensionsQuery(query: string): boolean {
return /^@recommended$/i.test(query.trim());
}
static isSearchRecommendedExtensionsQuery(query: string): boolean {
return /@recommended/i.test(query) && !ExtensionsListView.isRecommendedExtensionsQuery(query);
}
static isWorkspaceRecommendedExtensionsQuery(query: string): boolean {
return /@recommended:workspace/i.test(query);
}
static isKeymapsRecommendedExtensionsQuery(query: string): boolean {
return /@recommended:keymaps/i.test(query);
}
focus(): void {
super.focus();
if (!this.list) {
return;
}
if (!(this.list.getFocus().length || this.list.getSelection().length)) {
this.list.focusNext();
}
this.list.domFocus();
}
// {{SQL CARBON EDIT}}
static isAllMarketplaceExtensionsQuery(query: string): boolean {
return /@allmarketplace/i.test(query);
}
}
export class GroupByServerExtensionsView extends ExtensionsListView {
async show(query: string): Promise<IPagedModel<IExtension>> {
query = query.replace(/@group:server/g, '').trim();
query = query ? query : '@installed';
if (!ExtensionsListView.isInstalledExtensionsQuery(query) && !ExtensionsListView.isBuiltInExtensionsQuery(query)) {
query = query += ' @installed';
}
return super.show(query.trim());
}
}
export class EnabledExtensionsView extends ExtensionsListView {
private readonly enabledExtensionsQuery = '@enabled';
async show(query: string): Promise<IPagedModel<IExtension>> {
return (query && query.trim() !== this.enabledExtensionsQuery) ? this.showEmptyModel() : super.show(this.enabledExtensionsQuery);
}
}
export class DisabledExtensionsView extends ExtensionsListView {
private readonly disabledExtensionsQuery = '@disabled';
async show(query: string): Promise<IPagedModel<IExtension>> {
return (query && query.trim() !== this.disabledExtensionsQuery) ? this.showEmptyModel() : super.show(this.disabledExtensionsQuery);
}
}
export class BuiltInExtensionsView extends ExtensionsListView {
async show(query: string): Promise<IPagedModel<IExtension>> {
return (query && query.trim() !== '@builtin') ? this.showEmptyModel() : super.show('@builtin:features');
}
}
export class BuiltInThemesExtensionsView extends ExtensionsListView {
async show(query: string): Promise<IPagedModel<IExtension>> {
return (query && query.trim() !== '@builtin') ? this.showEmptyModel() : super.show('@builtin:themes');
}
}
export class BuiltInBasicsExtensionsView extends ExtensionsListView {
async show(query: string): Promise<IPagedModel<IExtension>> {
return (query && query.trim() !== '@builtin') ? this.showEmptyModel() : super.show('@builtin:basics');
}
}
export class DefaultRecommendedExtensionsView extends ExtensionsListView {
// {{SQL CARBON EDIT}}
private readonly recommendedExtensionsQuery = '@allmarketplace';
renderBody(container: HTMLElement): void {
super.renderBody(container);
this.disposables.push(this.tipsService.onRecommendationChange(() => {
this.show('');
}));
}
async show(query: string): Promise<IPagedModel<IExtension>> {
if (query && query.trim() !== this.recommendedExtensionsQuery) {
return this.showEmptyModel();
}
const model = await super.show(this.recommendedExtensionsQuery);
if (!this.extensionsWorkbenchService.local.some(e => e.type === ExtensionType.User)) {
// This is part of popular extensions view. Collapse if no installed extensions.
this.setExpanded(model.length > 0);
}
return model;
}
}
export class RecommendedExtensionsView extends ExtensionsListView {
private readonly recommendedExtensionsQuery = '@recommended';
renderBody(container: HTMLElement): void {
super.renderBody(container);
this.disposables.push(this.tipsService.onRecommendationChange(() => {
this.show('');
}));
}
async show(query: string): Promise<IPagedModel<IExtension>> {
// {{SQL CARBON EDIT}}
return super.show('@allmarketplace');
}
}
export class WorkspaceRecommendedExtensionsView extends ExtensionsListView {
private readonly recommendedExtensionsQuery = '@recommended:workspace';
private installAllAction: InstallWorkspaceRecommendedExtensionsAction;
renderBody(container: HTMLElement): void {
super.renderBody(container);
this.disposables.push(this.tipsService.onRecommendationChange(() => this.update()));
this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.setRecommendationsToInstall()));
this.disposables.push(this.contextService.onDidChangeWorkbenchState(() => this.update()));
}
renderHeader(container: HTMLElement): void {
super.renderHeader(container);
const listActionBar = $('.list-actionbar-container');
container.insertBefore(listActionBar, this.badgeContainer);
const actionbar = new ActionBar(listActionBar, {
animated: false
});
actionbar.onDidRun(({ error }) => error && this.notificationService.error(error));
this.installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, InstallWorkspaceRecommendedExtensionsAction.LABEL, []);
const configureWorkspaceFolderAction = this.instantiationService.createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL);
this.installAllAction.class = 'octicon octicon-cloud-download';
configureWorkspaceFolderAction.class = 'octicon octicon-pencil';
actionbar.push([this.installAllAction], { icon: true, label: false });
actionbar.push([configureWorkspaceFolderAction], { icon: true, label: false });
this.disposables.push(...[this.installAllAction, configureWorkspaceFolderAction, actionbar]);
}
async show(query: string): Promise<IPagedModel<IExtension>> {
let shouldShowEmptyView = query && query.trim() !== '@recommended' && query.trim() !== '@recommended:workspace';
let model = await (shouldShowEmptyView ? this.showEmptyModel() : super.show(this.recommendedExtensionsQuery));
this.setExpanded(model.length > 0);
return model;
}
private update(): void {
this.show(this.recommendedExtensionsQuery);
this.setRecommendationsToInstall();
}
private setRecommendationsToInstall(): Promise<void> {
return this.getRecommendationsToInstall()
.then(recommendations => { this.installAllAction.recommendations = recommendations; });
}
private getRecommendationsToInstall(): Promise<IExtensionRecommendation[]> {
return this.tipsService.getWorkspaceRecommendations()
.then(recommendations => recommendations.filter(({ extensionId }) => !this.extensionsWorkbenchService.local.some(i => areSameExtensions({ id: extensionId }, i.identifier))));
}
}

View File

@@ -0,0 +1,260 @@
/*---------------------------------------------------------------------------------------------
* 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/extensionsWidgets';
import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { IExtension, IExtensionsWorkbenchService, IExtensionContainer } from '../common/extensions';
import { append, $, addClass } from 'vs/base/browser/dom';
import * as platform from 'vs/base/common/platform';
import { localize } from 'vs/nls';
import { IExtensionManagementServerService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ILabelService } from 'vs/platform/label/common/label';
import { extensionButtonProminentBackground, extensionButtonProminentForeground } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND } from 'vs/workbench/common/theme';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IWindowService } from 'vs/platform/windows/common/windows';
export abstract class ExtensionWidget extends Disposable implements IExtensionContainer {
private _extension: IExtension;
get extension(): IExtension { return this._extension; }
set extension(extension: IExtension) { this._extension = extension; this.update(); }
update(): void { this.render(); }
abstract render(): void;
}
export class Label extends ExtensionWidget {
constructor(
private element: HTMLElement,
private fn: (extension: IExtension) => string,
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService
) {
super();
this.render();
}
render(): void {
this.element.textContent = this.extension ? this.fn(this.extension) : '';
}
}
export class InstallCountWidget extends ExtensionWidget {
constructor(
private container: HTMLElement,
private small: boolean,
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService
) {
super();
addClass(container, 'extension-install-count');
this.render();
}
render(): void {
this.container.innerHTML = '';
if (!this.extension) {
return;
}
const installCount = this.extension.installCount;
if (installCount === undefined) {
return;
}
let installLabel: string;
if (this.small) {
if (installCount > 1000000) {
installLabel = `${Math.floor(installCount / 100000) / 10}M`;
} else if (installCount > 1000) {
installLabel = `${Math.floor(installCount / 1000)}K`;
} else {
installLabel = String(installCount);
}
}
else {
installLabel = installCount.toLocaleString(platform.locale);
}
append(this.container, $('span.octicon.octicon-cloud-download'));
const count = append(this.container, $('span.count'));
count.textContent = installLabel;
}
}
export class RatingsWidget extends ExtensionWidget {
constructor(
private container: HTMLElement,
private small: boolean
) {
super();
addClass(container, 'extension-ratings');
if (this.small) {
addClass(container, 'small');
}
this.render();
}
render(): void {
this.container.innerHTML = '';
if (!this.extension) {
return;
}
if (this.extension.rating === undefined) {
return;
}
if (this.small && !this.extension.ratingCount) {
return;
}
const rating = Math.round(this.extension.rating * 2) / 2;
if (this.small) {
append(this.container, $('span.full.star'));
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.full.star'));
} else if (rating >= i - 0.5) {
append(this.container, $('span.half.star'));
} else {
append(this.container, $('span.empty.star'));
}
}
}
this.container.title = this.extension.ratingCount === 1 ? localize('ratedBySingleUser', "Rated by 1 user")
: typeof this.extension.ratingCount === 'number' && this.extension.ratingCount > 1 ? localize('ratedByUsers', "Rated by {0} users", this.extension.ratingCount) : localize('noRating', "No rating");
}
}
export class RecommendationWidget extends ExtensionWidget {
private element?: HTMLElement;
private disposables: IDisposable[] = [];
constructor(
private parent: HTMLElement,
@IThemeService private readonly themeService: IThemeService,
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService
) {
super();
this.render();
this._register(toDisposable(() => this.clear()));
}
private clear(): void {
this.parent.title = '';
this.parent.setAttribute('aria-label', this.extension ? localize('viewExtensionDetailsAria', "{0}. Press enter for extension details.", this.extension.displayName) : '');
if (this.element) {
this.parent.removeChild(this.element);
}
this.element = undefined;
this.disposables = dispose(this.disposables);
}
render(): void {
this.clear();
if (!this.extension) {
return;
}
const updateRecommendationMarker = () => {
this.clear();
const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
if (extRecommendations[this.extension.identifier.id.toLowerCase()]) {
this.element = append(this.parent, $('div.bookmark'));
const recommendation = append(this.element, $('.recommendation'));
append(recommendation, $('span.octicon.octicon-star'));
const applyBookmarkStyle = (theme) => {
const bgColor = theme.getColor(extensionButtonProminentBackground);
const fgColor = theme.getColor(extensionButtonProminentForeground);
recommendation.style.borderTopColor = bgColor ? bgColor.toString() : 'transparent';
recommendation.style.color = fgColor ? fgColor.toString() : 'white';
};
applyBookmarkStyle(this.themeService.getTheme());
this.themeService.onThemeChange(applyBookmarkStyle, this, this.disposables);
this.parent.title = extRecommendations[this.extension.identifier.id.toLowerCase()].reasonText;
this.parent.setAttribute('aria-label', localize('viewRecommendedExtensionDetailsAria', "{0}. {1} Press enter for extension details.", this.extension.displayName, extRecommendations[this.extension.identifier.id.toLowerCase()].reasonText));
}
};
updateRecommendationMarker();
this.extensionTipsService.onRecommendationChange(() => updateRecommendationMarker(), this, this.disposables);
}
}
export class RemoteBadgeWidget extends ExtensionWidget {
private element: HTMLElement | null;
private disposables: IDisposable[] = [];
constructor(
private parent: HTMLElement,
@ILabelService private readonly labelService: ILabelService,
@IThemeService private readonly themeService: IThemeService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IWindowService private readonly windowService: IWindowService
) {
super();
this.render();
this._register(toDisposable(() => this.clear()));
}
private clear(): void {
if (this.element) {
this.parent.removeChild(this.element);
}
this.element = null;
this.disposables = dispose(this.disposables);
}
render(): void {
this.clear();
if (!this.extension || !this.extension.local) {
return;
}
const server = this.extensionManagementServerService.getExtensionManagementServer(this.extension.local.location);
if (server === this.extensionManagementServerService.remoteExtensionManagementServer) {
this.element = append(this.parent, $('div.extension-remote-badge'));
append(this.element, $('span.octicon.octicon-file-symlink-directory'));
const applyBadgeStyle = () => {
if (!this.element) {
return;
}
const bgColor = this.themeService.getTheme().getColor(STATUS_BAR_HOST_NAME_BACKGROUND);
const fgColor = this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY ? this.themeService.getTheme().getColor(STATUS_BAR_NO_FOLDER_FOREGROUND) : this.themeService.getTheme().getColor(STATUS_BAR_FOREGROUND);
this.element.style.backgroundColor = bgColor ? bgColor.toString() : '';
this.element.style.color = fgColor ? fgColor.toString() : '';
};
applyBadgeStyle();
this.themeService.onThemeChange(applyBadgeStyle, this, this.disposables);
this.workspaceContextService.onDidChangeWorkbenchState(applyBadgeStyle, this, this.disposables);
const updateTitle = () => {
if (this.element) {
this.element.title = localize('remote extension title', "Extension in {0}", this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.windowService.getConfiguration().remoteAuthority));
}
};
this.labelService.onDidChangeFormatters(() => updateTitle(), this, this.disposables);
updateTitle();
}
}
}

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 10.3 10" style="enable-background:new 0 0 10.3 10;" xml:space="preserve">
<style type="text/css">
.st0{display:none;}
.st1{display:inline;opacity:0.2;fill:#ED1C24;}
.st2{display:inline;opacity:0.43;fill:#39B54A;}
.st3{fill:#353535;}
.st4{fill:#FFFFFF;}
.st5{display:inline;opacity:0.8;fill:#353535;}
.st6{display:inline;}
.st7{display:inline;opacity:0.8;fill:#321F5A;}
.st8{opacity:0.15;fill:#353535;}
.st9{display:inline;opacity:0.34;}
.st10{fill:#070909;}
.st11{fill:#333333;}
.st12{fill:#7551A0;}
.st13{fill:#D2D2D2;}
.st14{fill:#E6E7E8;}
.st15{fill:#3076BC;}
.st16{display:none;fill:#3076BC;}
.st17{display:inline;fill:#321F5A;}
.st18{fill:#D63F26;}
.st19{display:none;fill:none;stroke:#BCBEC0;stroke-miterlimit:10;}
.st20{opacity:0.52;}
.st21{fill:none;stroke:#BE1E2D;stroke-miterlimit:10;}
.st22{fill:#BE1E2D;}
.st23{display:none;opacity:0.2;}
.st24{display:inline;opacity:0.2;}
.st25{fill:#656565;}
.st26{fill:#D1D3D4;}
.st27{fill:#EEEEEE;}
.st28{display:none;fill-rule:evenodd;clip-rule:evenodd;fill:#F4F4F4;}
.st29{fill:#505150;}
.st30{display:none;fill:#BFD8E8;}
.st31{fill:#224096;}
.st32{fill:#17244E;}
.st33{display:inline;fill:#353535;}
.st34{display:none;fill:#F1F2F2;}
.st35{fill:#009444;}
.st36{fill:none;stroke:#505150;stroke-miterlimit:10;}
.st37{fill:#B9D532;}
.st38{fill:none;stroke:#D9D9D8;stroke-width:0.72;stroke-linejoin:round;stroke-miterlimit:10;}
.st39{fill:none;stroke:#D2D2D2;stroke-miterlimit:10;}
.st40{display:inline;fill:#333333;}
.st41{display:inline;fill:#7551A0;}
.st42{display:inline;fill:#D2D2D2;}
.st43{fill-rule:evenodd;clip-rule:evenodd;fill:#EEEEEE;}
.st44{fill:#321F5A;}
.st45{fill:none;stroke:#BCBEC0;stroke-miterlimit:10;}
.st46{opacity:0.53;fill:#FFFFFF;}
.st47{fill:none;stroke:#ED1C24;stroke-miterlimit:10;}
.st48{fill:#ED1C24;}
.st49{display:inline;opacity:0.1;fill:#E71E27;}
.st50{display:inline;opacity:0.3;}
.st51{fill:#E71E27;}
.st52{display:inline;opacity:0.2;fill:#E71E27;}
.st53{display:inline;fill:none;stroke:#ED1C24;stroke-miterlimit:10;}
.st54{fill:none;stroke:#2E3192;stroke-miterlimit:10;}
.st55{fill:#2E3192;}
.st56{fill:none;stroke:#FFF200;stroke-miterlimit:10;}
.st57{fill:#FFF200;}
</style>
<g id="grids">
</g>
<g id="Layer_2" class="st0">
</g>
<g id="home">
</g>
<g id="VSOdetails">
<g id="overview2">
</g>
</g>
<g id="reviews">
</g>
<g id="QA_2">
</g>
<g id="CARDS-RL">
<g>
<g>
<path class="st13" d="M8,9.7c-0.1,0-0.2,0-0.2-0.1L5.1,7.8L2.5,9.6c-0.1,0.1-0.3,0.1-0.5,0C1.9,9.5,1.9,9.4,1.9,9.2l1-3.1
L0.3,4.2C0.2,4.1,0.1,4,0.2,3.8c0.1-0.2,0.2-0.3,0.4-0.3h3.2l1-3.1C4.8,0.3,5,0.2,5.1,0.2c0.2,0,0.3,0.1,0.4,0.3l1,3.1h3.2
c0.2,0,0.3,0.1,0.4,0.3c0.1,0.2,0,0.3-0.1,0.5L7.4,6.1l1,3.1c0.1,0.2,0,0.3-0.1,0.5C8.2,9.7,8.1,9.7,8,9.7z"/>
</g>
</g>
</g>
<g id="sticky">
</g>
<g id="REDLINES" class="st0">
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 10.3 10" style="enable-background:new 0 0 10.3 10;" xml:space="preserve">
<style type="text/css">
.st0{display:none;}
.st1{display:inline;opacity:0.2;fill:#ED1C24;}
.st2{display:inline;opacity:0.43;fill:#39B54A;}
.st3{fill:#353535;}
.st4{fill:#FFFFFF;}
.st5{display:inline;opacity:0.8;fill:#353535;}
.st6{display:inline;}
.st7{display:inline;opacity:0.8;fill:#321F5A;}
.st8{opacity:0.15;fill:#353535;}
.st9{display:inline;opacity:0.34;}
.st10{fill:#070909;}
.st11{fill:#333333;}
.st12{fill:#7551A0;}
.st13{fill:#D2D2D2;}
.st14{fill:#E6E7E8;}
.st15{fill:#3076BC;}
.st16{display:none;fill:#3076BC;}
.st17{display:inline;fill:#321F5A;}
.st18{fill:#FF8E00;}
.st19{display:none;fill:none;stroke:#BCBEC0;stroke-miterlimit:10;}
.st20{opacity:0.52;}
.st21{fill:none;stroke:#BE1E2D;stroke-miterlimit:10;}
.st22{fill:#BE1E2D;}
.st23{display:none;opacity:0.2;}
.st24{display:inline;opacity:0.2;}
.st25{fill:#656565;}
.st26{fill:#D1D3D4;}
.st27{fill:#EEEEEE;}
.st28{display:none;fill-rule:evenodd;clip-rule:evenodd;fill:#F4F4F4;}
.st29{fill:#505150;}
.st30{display:none;fill:#BFD8E8;}
.st31{fill:#224096;}
.st32{fill:#17244E;}
.st33{display:inline;fill:#353535;}
.st34{display:none;fill:#F1F2F2;}
.st35{fill:#009444;}
.st36{fill:none;stroke:#505150;stroke-miterlimit:10;}
.st37{fill:#B9D532;}
.st38{fill:none;stroke:#D9D9D8;stroke-width:0.72;stroke-linejoin:round;stroke-miterlimit:10;}
.st39{fill:none;stroke:#D2D2D2;stroke-miterlimit:10;}
.st40{display:inline;fill:#333333;}
.st41{display:inline;fill:#7551A0;}
.st42{display:inline;fill:#D2D2D2;}
.st43{fill-rule:evenodd;clip-rule:evenodd;fill:#EEEEEE;}
.st44{fill:#321F5A;}
.st45{fill:none;stroke:#BCBEC0;stroke-miterlimit:10;}
.st46{opacity:0.53;fill:#FFFFFF;}
.st47{fill:none;stroke:#ED1C24;stroke-miterlimit:10;}
.st48{fill:#ED1C24;}
.st49{display:inline;opacity:0.1;fill:#E71E27;}
.st50{display:inline;opacity:0.3;}
.st51{fill:#E71E27;}
.st52{display:inline;opacity:0.2;fill:#E71E27;}
.st53{display:inline;fill:none;stroke:#ED1C24;stroke-miterlimit:10;}
.st54{fill:none;stroke:#2E3192;stroke-miterlimit:10;}
.st55{fill:#2E3192;}
.st56{fill:none;stroke:#FFF200;stroke-miterlimit:10;}
.st57{fill:#FFF200;}
</style>
<g id="grids">
</g>
<g id="Layer_2" class="st0">
</g>
<g id="home">
</g>
<g id="VSOdetails">
<g id="overview2">
</g>
</g>
<g id="reviews">
</g>
<g id="QA_2">
</g>
<g id="CARDS-RL">
<g>
<g>
<path class="st18" d="M8,9.8c-0.1,0-0.2,0-0.2-0.1L5.2,7.8L2.6,9.7c-0.1,0.1-0.3,0.1-0.5,0C1.9,9.6,1.9,9.4,1.9,9.2l1-3.1
L0.3,4.3C0.2,4.2,0.1,4,0.2,3.8c0.1-0.2,0.2-0.3,0.4-0.3h3.2l1-3.1C4.8,0.3,5,0.2,5.2,0.2c0.2,0,0.3,0.1,0.4,0.3l1,3.1h3.2
c0.2,0,0.3,0.1,0.4,0.3c0.1,0.2,0,0.3-0.1,0.5L7.4,6.2l1,3.1c0.1,0.2,0,0.3-0.1,0.5C8.2,9.7,8.1,9.8,8,9.8z"/>
</g>
</g>
</g>
<g id="sticky">
</g>
<g id="REDLINES" class="st0">
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 10.3 10" style="enable-background:new 0 0 10.3 10;" xml:space="preserve">
<style type="text/css">
.st0{display:none;}
.st1{display:inline;opacity:0.2;fill:#ED1C24;}
.st2{display:inline;opacity:0.43;fill:#39B54A;}
.st3{fill:#353535;}
.st4{fill:#FFFFFF;}
.st5{display:inline;opacity:0.8;fill:#353535;}
.st6{display:inline;}
.st7{display:inline;opacity:0.8;fill:#321F5A;}
.st8{opacity:0.15;fill:#353535;}
.st9{display:inline;opacity:0.34;}
.st10{fill:#070909;}
.st11{fill:#333333;}
.st12{fill:#7551A0;}
.st13{fill:#D2D2D2;}
.st14{fill:#E6E7E8;}
.st15{fill:#3076BC;}
.st16{display:none;fill:#3076BC;}
.st17{display:inline;fill:#321F5A;}
.st18{fill:#FF8E00;}
.st19{display:none;fill:none;stroke:#BCBEC0;stroke-miterlimit:10;}
.st20{opacity:0.52;}
.st21{fill:none;stroke:#BE1E2D;stroke-miterlimit:10;}
.st22{fill:#BE1E2D;}
.st23{display:none;opacity:0.2;}
.st24{display:inline;opacity:0.2;}
.st25{fill:#656565;}
.st26{fill:#D1D3D4;}
.st27{fill:#EEEEEE;}
.st28{display:none;fill-rule:evenodd;clip-rule:evenodd;fill:#F4F4F4;}
.st29{fill:#505150;}
.st30{display:none;fill:#BFD8E8;}
.st31{fill:#224096;}
.st32{fill:#17244E;}
.st33{display:inline;fill:#353535;}
.st34{display:none;fill:#F1F2F2;}
.st35{fill:#009444;}
.st36{fill:none;stroke:#505150;stroke-miterlimit:10;}
.st37{fill:#B9D532;}
.st38{fill:none;stroke:#D9D9D8;stroke-width:0.72;stroke-linejoin:round;stroke-miterlimit:10;}
.st39{fill:none;stroke:#D2D2D2;stroke-miterlimit:10;}
.st40{display:inline;fill:#333333;}
.st41{display:inline;fill:#7551A0;}
.st42{display:inline;fill:#D2D2D2;}
.st43{fill-rule:evenodd;clip-rule:evenodd;fill:#EEEEEE;}
.st44{fill:#321F5A;}
.st45{fill:none;stroke:#BCBEC0;stroke-miterlimit:10;}
.st46{opacity:0.53;fill:#FFFFFF;}
.st47{fill:none;stroke:#ED1C24;stroke-miterlimit:10;}
.st48{fill:#ED1C24;}
.st49{display:inline;opacity:0.1;fill:#E71E27;}
.st50{display:inline;opacity:0.3;}
.st51{fill:#E71E27;}
.st52{display:inline;opacity:0.2;fill:#E71E27;}
.st53{display:inline;fill:none;stroke:#ED1C24;stroke-miterlimit:10;}
.st54{fill:none;stroke:#2E3192;stroke-miterlimit:10;}
.st55{fill:#2E3192;}
.st56{fill:none;stroke:#FFF200;stroke-miterlimit:10;}
.st57{fill:#FFF200;}
</style>
<g id="grids">
</g>
<g id="Layer_2" class="st0">
</g>
<g id="home">
</g>
<g id="VSOdetails">
<g id="overview2">
</g>
</g>
<g id="reviews">
</g>
<g id="QA_2">
</g>
<g id="CARDS-RL">
<g>
<path class="st13" d="M10.1,3.8c-0.1-0.2-0.2-0.3-0.4-0.3H6.5l-1-3.1C5.5,0.3,5.3,0.2,5.2,0.2v7.6l2.6,1.9C7.8,9.7,7.9,9.7,8,9.7
s0.2,0,0.2-0.1c0.1-0.1,0.2-0.3,0.1-0.5l-1-3.1L10,4.2C10.1,4.1,10.2,4,10.1,3.8z"/>
<path class="st18" d="M5.2,0.2L5.2,0.2C5,0.2,4.8,0.3,4.8,0.5l-1,3.1H0.6c-0.2,0-0.3,0.1-0.4,0.3C0.1,4,0.2,4.1,0.3,4.2l2.6,1.9
l-1,3.1c-0.1,0.2,0,0.3,0.1,0.5c0.1,0.1,0.3,0.1,0.5,0l2.6-1.9l0,0V0.2z"/>
</g>
</g>
<g id="sticky">
</g>
<g id="REDLINES" class="st0">
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.065 13H15v2H2.056v-2h5.009zm3.661-12H7.385L8.44 2.061 7.505 3H15V1h-4.274zM3.237 9H2.056v2H15V9H3.237zm4.208-4l.995 1-.995 1H15V5H7.445z" fill="#C5C5C5"/><path d="M5.072 4.03L7.032 6 5.978 7.061l-1.96-1.97-1.961 1.97L1 6l1.96-1.97L1 2.061 2.056 1l1.96 1.97L5.977 1l1.057 1.061L5.072 4.03z" fill="#F48771"/></svg>

After

Width:  |  Height:  |  Size: 419 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.065 13H15v2H2.056v-2h5.009zm3.661-12H7.385L8.44 2.061 7.505 3H15V1h-4.274zM3.237 9H2.056v2H15V9H3.237zm4.208-4l.995 1-.995 1H15V5H7.445z" fill="#424242"/><path d="M5.072 4.03L7.032 6 5.978 7.061l-1.96-1.97-1.961 1.97L1 6l1.96-1.97L1 2.061 2.056 1l1.96 1.97L5.977 1l1.057 1.061L5.072 4.03z" fill="#A1260D"/></svg>

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,96 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-action-bar .action-item .action-label.extension-action {
padding: 0 5px;
outline-offset: 2px;
line-height: initial;
}
.monaco-action-bar .action-item .action-label.clear-extensions {
background: url('clear.svg') center center no-repeat;
}
.vs-dark .monaco-action-bar .action-item .action-label.clear-extensions,
.hc-black .monaco-action-bar .action-item .action-label.clear-extensions {
background: url('clear-inverse.svg') center center no-repeat;
}
.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 {
content: '▼';
padding-left: 2px;
font-size: 80%;
}
.monaco-action-bar .action-item.disabled .action-label.extension-action.ignore,
.monaco-action-bar .action-item.disabled .action-label.extension-action.undo-ignore,
.monaco-action-bar .action-item.disabled .action-label.extension-action.install:not(.installing),
.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.reload,
.monaco-action-bar .action-item.disabled .action-label.disable-status.hide,
.monaco-action-bar .action-item.disabled .action-label.extension-status-label.hide,
.monaco-action-bar .action-item.disabled .action-label.malicious-status.not-malicious {
display: none;
}
.monaco-action-bar .action-item.disabled .action-label.extension-status-label:before {
content: '✓';
padding-right: 4px;
}
.monaco-action-bar .action-item .action-label.disable-status,
.monaco-action-bar .action-item .action-label.malicious-status,
.monaco-action-bar .action-item.disabled .action-label.extension-status-label {
opacity: 0.9;
line-height: initial;
padding: 0 5px;
}
.monaco-action-bar .action-item .action-label.disable-status,
.monaco-action-bar .action-item .action-label.malicious-status {
border-radius: 4px;
color: inherit;
background-color: transparent;
font-style: italic;
}
.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.extension-status-label,
.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.disable-status,
.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.malicious-status {
font-weight: normal;
}
.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.extension-status-label:hover,
.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.disable-status:hover,
.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.malicious-status:hover {
opacity: 0.9;
}
.extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.extension-action.manage.hide {
display: none;
}
.extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.extension-action.manage {
height: 18px;
width: 10px;
border: none;
background: url('manage.svg') center center no-repeat;
outline-offset: 0px;
margin-top: 0.15em
}
.hc-black .extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.extension-action.manage,
.vs-dark .extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.extension-action.manage {
background: url('manage-inverse.svg') center center no-repeat;
}
.extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .actions-container {
justify-content: flex-start;
}

View File

@@ -0,0 +1,374 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.extension-editor {
height: 100%;
overflow: hidden;
}
.extension-editor .clickable {
cursor: pointer;
}
.extension-editor > .header {
display: flex;
padding-top: 20px;
padding-bottom: 14px;
padding-left: 20px;
padding-right: 20px;
overflow: hidden;
font-size: 14px;
}
.extension-editor > .header > .icon-container {
position: relative;
}
.extension-editor > .header > .icon-container .icon {
height: 128px;
width: 128px;
object-fit: contain;
}
.extension-editor > .header > .icon-container .extension-remote-badge {
position: absolute;
right: 0px;
top: 94px;
}
.extension-editor > .header > .details {
padding-left: 20px;
overflow: hidden;
user-select: text;
}
.extension-editor > .header > .details > .title {
display: flex;
align-items: center;
}
.extension-editor > .header > .details > .title > .name {
flex: 0;
font-size: 26px;
line-height: 30px;
font-weight: 600;
white-space: nowrap;
}
.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;
user-select: text;
white-space: nowrap;
}
.extension-editor > .header > .details > .title > .builtin {
font-size: 10px;
font-style: italic;
margin-left: 10px;
}
.vs .extension-editor > .header > .details > .title > .preview {
color: white;
}
.extension-editor > .header > .details > .title > .preview {
background: rgb(214, 63, 38);
font-size: 10px;
font-style: italic;
margin-left: 10px;
padding: 0px 4px;
border-radius: 4px;
user-select: none;
}
.extension-editor > .header > .details > .subtitle {
padding-top: 6px;
white-space: nowrap;
height: 20px;
line-height: 20px;
}
.extension-editor > .header > .details > .subtitle > .publisher {
font-size: 18px;
}
.extension-editor > .header > .details > .subtitle > .install > .count {
margin-left: 6px;
}
.extension-editor > .header > .details > .subtitle > span:not(:first-child):not(:empty),
.extension-editor > .header > .details > .subtitle > a:not(:first-child):not(:empty) {
border-left: 1px solid rgba(128, 128, 128, 0.7);
margin-left: 14px;
padding-left: 14px;
}
.extension-editor > .header > .details > .description {
margin-top: 10px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.extension-editor > .header > .details > .actions {
margin-top: 10px;
}
.extension-editor > .header > .details > .actions > .monaco-action-bar {
text-align: initial;
}
.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container {
justify-content: flex-start;
}
.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .action-label {
font-weight: 600;
margin: 4px 8px 4px 0px;
padding: 1px 6px;
}
.extension-editor > .header > .details > .subtext-container {
display: block;
float: left;
margin-top: 0;
font-size: 13px;
font-style: italic;
}
.extension-editor > .header > .details > .subtext-container > .monaco-action-bar {
float: left;
margin-top: 2px;
font-style: normal;
}
.extension-editor > .header > .details > .subtext-container > .subtext {
float:left;
margin-top: 5px;
margin-right: 4px;
}
.extension-editor > .header > .details > .subtext-container > .monaco-action-bar .action-label {
margin-top: 4px;
margin-left: 4px;
padding-top: 0;
padding-bottom: 2px;
}
.extension-editor > .body {
height: calc(100% - 168px);
overflow: hidden;
}
.extension-editor > .body > .navbar {
height: 36px;
font-weight: bold;
font-size: 14px;
line-height: 36px;
padding-left: 20px;
border-bottom: 1px solid rgba(136, 136, 136, 0.45);
box-sizing: border-box;
}
.extension-editor > .body > .navbar > .monaco-action-bar > .actions-container {
justify-content: initial;
}
.extension-editor > .body > .navbar > .monaco-action-bar > .actions-container > .action-item > .action-label {
margin-right: 16px;
}
.extension-editor > .body > .navbar > .monaco-action-bar > .actions-container > .action-item > .action-label {
font-size: inherit;
opacity: 0.7;
}
.extension-editor > .body > .navbar > .monaco-action-bar > .actions-container > .action-item.disabled > .action-label {
opacity: 1;
text-decoration: underline;
}
.extension-editor > .body > .navbar > .monaco-action-bar > .actions-container > .action-item > .action-label:hover {
text-decoration: underline;
}
.extension-editor > .body > .content {
height: calc(100% - 36px);
position: relative;
overflow: hidden;
user-select: text;
}
.extension-editor > .body > .content.loading {
background: url('loading.svg') center center no-repeat;
}
.extension-editor > .body > .content > .monaco-scrollable-element {
height: 100%;
}
.extension-editor > .body > .content > .nocontent {
margin-left: 20px;
}
.extension-editor > .body > .content > .monaco-scrollable-element > .subcontent {
height: 100%;
padding: 20px;
overflow-y: scroll;
box-sizing: border-box;
}
.extension-editor > .body > .content table {
width: 100%;
border-spacing: 0;
border-collapse: separate;
}
.extension-editor > .body > .content details:not(:first-child) {
margin-top: 20px;
}
.extension-editor > .body > .content details > summary {
cursor: pointer;
margin-bottom: 10px;
font-weight: bold;
font-size: 120%;
border-bottom: 1px solid rgba(128, 128, 128, 0.22);
padding-bottom: 6px;
}
.extension-editor > .body > .content details > summary:focus {
outline: none;
}
.extension-editor > .body > .content details > summary::-webkit-details-marker {
color: rgba(128, 128, 128, 0.5);
}
.extension-editor > .body > .content table tr:nth-child(odd) {
background-color: rgba(130, 130, 130, 0.04);
}
.extension-editor > .body > .content table tr:not(:first-child):hover {
background-color: rgba(128, 128, 128, 0.15);
}
.extension-editor > .body > .content table th,
.extension-editor > .body > .content table td {
padding: 2px 16px 2px 4px;
}
.extension-editor > .body > .content table th:last-child,
.extension-editor > .body > .content table td:last-child {
padding: 2px 4px;
}
.extension-editor > .body > .content table th {
text-align: left;
word-break: keep-all;
}
.extension-editor > .body > .content table code:not(:empty) {
font-family: var(--monaco-monospace-font);
font-size: 90%;
background-color: rgba(128, 128, 128, 0.17);
border-radius: 4px;
padding: 1px 4px;
}
.extension-editor > .body > .content table .colorBox {
box-sizing: border-box;
width: 0.8em;
height: 0.8em;
display: inline-block;
border-width: 0.1em;
border-style: solid;
border-color: rgb(0, 0, 0);
margin: 0em 0.2em;
vertical-align: middle;
}
.vs-dark .extension-editor > .body > .content table .colorBox,
.hc-black .extension-editor > .body > .content table .colorBox {
border-color: rgb(238, 238, 238);
}
.extension-editor .subcontent .monaco-tree-row .content .unknown-extension {
line-height: 62px;
}
.extension-editor .subcontent .monaco-tree-row .content .unknown-extension > .error-marker {
background-color: #BE1100;
padding: 2px 4px;
font-weight: bold;
font-size: 11px;
color: #CCC;
}
.extension-editor .subcontent .monaco-tree-row .unknown-extension > .message {
padding-left: 10px;
font-weight: bold;
font-size: 14px;
}
.extension-editor .subcontent .monaco-tree-row .extension {
display: flex;
align-items: center;
}
.extension-editor .subcontent .monaco-tree-row .extension > .details {
flex: 1;
overflow: hidden;
padding-left: 10px;
}
.extension-editor .subcontent .monaco-tree-row .extension > .details > .header {
display: flex;
align-items: center;
height: 19px;
overflow: hidden;
}
.extension-editor .subcontent .monaco-tree-row .extension > .icon {
height: 40px;
width: 40px;
object-fit: contain;
}
.extension-editor .subcontent .monaco-tree-row .extension > .details > .header > .name {
font-weight: bold;
font-size: 16px;
}
.extension-editor .subcontent .monaco-tree-row .extension > .details > .header > .name:hover {
text-decoration: underline;
}
.extension-editor .subcontent .monaco-tree-row .extension > .details > .header > .identifier {
font-size: 90%;
opacity: 0.6;
margin-left: 10px;
background: rgba(173, 173, 173, 0.31);
padding: 0px 4px;
border-radius: 4px;
}
.extension-editor .subcontent .monaco-tree-row .extension > .details > .footer {
display: flex;
height: 19px;
overflow: hidden;
padding-top: 5px;
}
.extension-editor .subcontent .monaco-tree-row .extension > .details > .footer > .author {
font-size: 90%;
font-weight: 600;
opacity: 0.6;
}

View File

@@ -0,0 +1 @@
<svg fill="none" height="28" viewBox="0 0 28 28" width="28" xmlns="http://www.w3.org/2000/svg"><g fill="#fff"><path clip-rule="evenodd" d="m3 3h10v4h-6v6 2 6h6v4h-10zm18 12v6h-6v4h10v-10zm4-2v-10h-10v4h3v-1h4v4h-1v3z" fill-rule="evenodd"/><path d="m9 9h10v10h-10z"/></g></svg>

After

Width:  |  Height:  |  Size: 276 B

View File

@@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .activitybar > .content .monaco-action-bar .action-label.extensions {
-webkit-mask: url('extensions-dark.svg') no-repeat 50% 50%;
}
.extensions .split-view-view .panel-header .count-badge-wrapper {
position: absolute;
right: 12px;
}

View File

@@ -0,0 +1,234 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.extensions-viewlet {
height: 100%;
}
.extensions-viewlet > .header {
height: 38px;
box-sizing: border-box;
padding: 5px 9px 5px 16px;
}
.extensions-viewlet > .header > .search-box {
width: 100%;
height: 28px;
line-height: 18px;
box-sizing: border-box;
padding: 4px;
border: 1px solid transparent;
-webkit-appearance: textfield;
}
.extensions-viewlet > .extensions {
height: calc(100% - 38px);
}
.extensions-viewlet > .extensions .list-actionbar-container .monaco-action-bar .action-item > .octicon {
font-size: 12px;
line-height: 1;
margin-right: 10px;
}
.extensions-viewlet > .extensions .list-actionbar-container .monaco-action-bar .action-item.disabled {
display: none;
}
.extensions-viewlet > .extensions .extensions-list.hidden,
.extensions-viewlet > .extensions .message.hidden {
display: none;
visibility: hidden;
}
.extensions-viewlet > .extensions .panel-header {
padding-right: 28px;
}
.extensions-viewlet > .extensions .panel-header > .title {
flex: 1;
}
.extensions-viewlet > .extensions .message {
padding: 5px 9px 5px 16px;
cursor: default;
}
.extensions-viewlet > .extensions .monaco-list-row > .bookmark {
display: inline-block;
height: 20px;
width: 20px;
}
.extensions-viewlet > .extensions .monaco-list-row > .bookmark > .recommendation {
border-right: 20px solid transparent;
border-top: 20px solid;
box-sizing: border-box;
}
.extensions-viewlet > .extensions .monaco-list-row > .bookmark > .recommendation > .octicon {
position: absolute;
top: 1px;
left: 1px;
font-size: 90%;
}
.extensions-viewlet > .extensions .extension {
box-sizing: border-box;
width: 100%;
height: 100%;
padding: 0 0 0 16px;
overflow: hidden;
display: flex;
position: absolute;
top: 0;
}
.extensions-viewlet > .extensions .extension.loading {
background: url('loading.svg') center center no-repeat;
}
.extensions-viewlet > .extensions .monaco-list-row > .extension > .icon-container {
position: relative;
}
.extensions-viewlet > .extensions .extension > .icon-container > .icon {
width: 42px;
height: 42px;
padding: 10px 14px 10px 0;
flex-shrink: 0;
object-fit: contain;
}
.extensions-viewlet > .extensions .monaco-list-row > .extension > .icon-container > .extension-remote-badge {
position: absolute;
right: 5px;
bottom: 5px;
}
.extensions-viewlet.narrow > .extensions .extension > .icon-container,
.extensions-viewlet > .extensions .extension.loading > .icon-container {
display: none;
}
.extensions-viewlet > .extensions .extension > .details {
flex: 1;
padding: 4px 0;
overflow: hidden;
}
.extensions-viewlet > .extensions .extension > .details > .header-container {
height: 19px;
display: flex;
overflow: hidden;
padding-right: 11px;
}
.extensions-viewlet > .extensions .extension > .details > .header-container > .header {
display: flex;
align-items: baseline;
flex-wrap: nowrap;
overflow: hidden;
flex: 1;
min-width: 0;
}
.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .name {
font-weight: bold;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .version {
opacity: 0.85;
font-size: 80%;
padding-left: 6px;
flex: 1;
min-width: fit-content;
}
.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count:not(:empty) {
font-size: 80%;
margin: 0 6px;
}
.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count > .octicon {
font-size: 100%;
margin-right: 2px;
}
.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .ratings {
text-align: right;
}
.extensions-viewlet.narrow > .extensions .extension > .details > .header-container > .header > .ratings,
.extensions-viewlet.narrow > .extensions .extension > .details > .header-container > .header > .install-count {
display: none;
}
.extensions-viewlet > .extensions .extension > .details > .description {
padding-right: 11px;
}
.extensions-viewlet > .extensions .extension > .details > .footer {
display: flex;
justify-content: flex-end;
padding-right: 7px;
height: 24px;
overflow: hidden;
}
.extensions-viewlet > .extensions .extension > .details > .footer > .author {
flex: 1;
font-size: 90%;
padding-right: 6px;
opacity: 0.9;
font-weight: 600;
}
.extensions-viewlet > .extensions .selected .extension > .details > .footer > .author,
.extensions-viewlet > .extensions .selected.focused .extension > .details > .footer > .author {
opacity: 1;
}
.extensions-viewlet > .extensions .extension > .details > .footer > .monaco-action-bar > .actions-container {
flex-wrap: wrap-reverse;
}
.extensions-viewlet > .extensions .extension > .details > .footer > .monaco-action-bar .action-label {
margin-top: 0.3em;
margin-left: 0.3em;
line-height: 14px;
}
.extensions-viewlet > .extensions .extension > .details > .footer > .monaco-action-bar .action-label:not(:empty) {
opacity: 0.9;
}
.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .bookmark,
.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .bookmark,
.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .icon-container > .icon,
.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .icon-container > .icon,
.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .details > .header-container,
.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .details > .header-container,
.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .details > .description,
.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .details > .description,
.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .details > .footer > .author,
.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .details > .footer > .author {
opacity: 0.5;
}
.extensions-viewlet > .extensions .extension .ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.extensions-badge.progress-badge > .badge-content {
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSIxNCIgdmlld0JveD0iMiAyIDE0IDE0IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDIgMiAxNCAxNCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTkgMTZjLTMuODYgMC03LTMuMTQtNy03czMuMTQtNyA3LTdjMy44NTkgMCA3IDMuMTQxIDcgN3MtMy4xNDEgNy03IDd6bTAtMTIuNmMtMy4wODggMC01LjYgMi41MTMtNS42IDUuNnMyLjUxMiA1LjYgNS42IDUuNiA1LjYtMi41MTIgNS42LTUuNi0yLjUxMi01LjYtNS42LTUuNnptMy44NiA3LjFsLTMuMTYtMS44OTZ2LTMuODA0aC0xLjR2NC41OTZsMy44NCAyLjMwNS43Mi0xLjIwMXoiLz48L3N2Zz4=");
background-position: center center;
background-repeat: no-repeat;
}

View File

@@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.extension-ratings {
display: inline-block;
}
.extension-ratings.small {
font-size: 80%;
}
.extension-ratings > .star {
display: inline-block;
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-position: center center;
}
.extension-ratings > .star:not(:first-child) {
margin-left: 3px;
}
.extension-ratings.small > .star {
width: 10px;
height: 10px;
}
.extension-ratings > .full {
background-image: url('FullStarLight.svg');
}
.extension-ratings > .half {
background-image: url('HalfStarLight.svg');
}
.extension-ratings > .empty {
background-image: url('EmptyStar.svg');
}
.extension-ratings > .count {
margin-left: 6px;
}
.extension-ratings.small > .count {
margin-left: 2px;
}
.extension-remote-badge {
width: 22px;
height: 22px;
line-height: 22px;
border-radius: 20px;
text-align: center;
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>Market_LanguageGeneric</title><path d="M14.315,11H1.685a6.912,6.912,0,0,1,0-6h12.63a6.912,6.912,0,0,1,0,6Z" fill="#fff"/><path d="M8,0a8,8,0,1,0,8,8A8.009,8.009,0,0,0,8,0ZM8,1a6.993,6.993,0,0,1,5.736,3H2.264A6.991,6.991,0,0,1,8,1ZM8,15a6.991,6.991,0,0,1-5.736-3H13.736A6.993,6.993,0,0,1,8,15Zm6.315-4H1.685a6.912,6.912,0,0,1,0-6h12.63a6.912,6.912,0,0,1,0,6Z" fill="#2b2b2b"/><path d="M8,1a6.993,6.993,0,0,1,5.736,3H2.264A6.991,6.991,0,0,1,8,1ZM8,15a6.991,6.991,0,0,1-5.736-3H13.736A6.993,6.993,0,0,1,8,15Z" fill="#ff8c00"/><path d="M8,0a8,8,0,1,0,8,8A8.009,8.009,0,0,0,8,0ZM8,1a6.993,6.993,0,0,1,5.736,3H2.264A6.991,6.991,0,0,1,8,1ZM8,15a6.991,6.991,0,0,1-5.736-3H13.736A6.993,6.993,0,0,1,8,15Zm6.315-4H1.685a6.912,6.912,0,0,1,0-6h12.63a6.912,6.912,0,0,1,0,6Z" fill="#2b2b2b"/><path d="M8,1a6.993,6.993,0,0,1,5.736,3H2.264A6.991,6.991,0,0,1,8,1ZM8,15a6.991,6.991,0,0,1-5.736-3H13.736A6.993,6.993,0,0,1,8,15Z" fill="#767676"/><path d="M5.783,6.783,4.565,8,5.783,9.217l-.566.566L3.435,8,5.217,6.217Zm5-.566-.566.566L11.435,8,10.217,9.217l.566.566L12.565,8Zm-4.14,3.6.714.358,2-4-.714-.358Z" fill="#2b2b2b"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="575" height="6px">
<style>
circle {
animation: ball 2.5s cubic-bezier(0.000, 1.000, 1.000, 0.000) infinite;
fill: #bbb;
}
#balls {
animation: balls 2.5s linear infinite;
}
#circle2 { animation-delay: 0.1s; }
#circle3 { animation-delay: 0.2s; }
#circle4 { animation-delay: 0.3s; }
#circle5 { animation-delay: 0.4s; }
@keyframes ball {
from { transform: none; }
20% { transform: none; }
80% { transform: translateX(864px); }
to { transform: translateX(864px); }
}
@keyframes balls {
from { transform: translateX(-40px); }
to { transform: translateX(30px); }
}
</style>
<g id="balls">
<circle class="circle" id="circle1" cx="-115" cy="3" r="3"/>
<circle class="circle" id="circle2" cx="-130" cy="3" r="3" />
<circle class="circle" id="circle3" cx="-145" cy="3" r="3" />
<circle class="circle" id="circle4" cx="-160" cy="3" r="3" />
<circle class="circle" id="circle5" cx="-175" cy="3" r="3" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#C5C5C5"><path d="M12.714 9.603c-.07.207-.15.407-.246.601l1.017 2.139c-.335.424-.718.807-1.142 1.143l-2.14-1.018c-.193.097-.394.176-.601.247l-.795 2.235c-.265.03-.534.05-.807.05-.272 0-.541-.02-.806-.05l-.795-2.235c-.207-.071-.408-.15-.602-.247l-2.14 1.017c-.424-.336-.807-.719-1.143-1.143l1.017-2.139c-.094-.193-.175-.393-.245-.6l-2.236-.796c-.03-.265-.05-.534-.05-.807s.02-.542.05-.807l2.236-.795c.07-.207.15-.407.246-.601l-1.016-2.139c.336-.423.719-.807 1.143-1.142l2.14 1.017c.193-.096.394-.176.602-.247l.793-2.236c.265-.03.534-.05.806-.05.273 0 .542.02.808.05l.795 2.236c.207.07.407.15.601.246l2.14-1.017c.424.335.807.719 1.142 1.142l-1.017 2.139c.096.194.176.394.246.601l2.236.795c.029.266.049.535.049.808s-.02.542-.05.807l-2.236.796zm-4.714-4.603c-1.657 0-3 1.343-3 3s1.343 3 3 3 3-1.343 3-3-1.343-3-3-3z"/><circle cx="8" cy="8" r="1.5"/></g></svg>

After

Width:  |  Height:  |  Size: 927 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#424242"><path d="M12.714 9.603c-.07.207-.15.407-.246.601l1.017 2.139c-.335.424-.718.807-1.142 1.143l-2.14-1.018c-.193.097-.394.176-.601.247l-.795 2.235c-.265.03-.534.05-.807.05-.272 0-.541-.02-.806-.05l-.795-2.235c-.207-.071-.408-.15-.602-.247l-2.14 1.017c-.424-.336-.807-.719-1.143-1.143l1.017-2.139c-.094-.193-.175-.393-.245-.6l-2.236-.796c-.03-.265-.05-.534-.05-.807s.02-.542.05-.807l2.236-.795c.07-.207.15-.407.246-.601l-1.016-2.139c.336-.423.719-.807 1.143-1.142l2.14 1.017c.193-.096.394-.176.602-.247l.793-2.236c.265-.03.534-.05.806-.05.273 0 .542.02.808.05l.795 2.236c.207.07.407.15.601.246l2.14-1.017c.424.335.807.719 1.142 1.142l-1.017 2.139c.096.194.176.394.246.601l2.236.795c.029.266.049.535.049.808s-.02.542-.05.807l-2.236.796zm-4.714-4.603c-1.657 0-3 1.343-3 3s1.343 3 3 3 3-1.343 3-3-1.343-3-3-3z"/><circle cx="8" cy="8" r="1.5"/></g></svg>

After

Width:  |  Height:  |  Size: 927 B

View File

@@ -0,0 +1,175 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
body {
padding: 10px 20px;
line-height: 22px;
}
img {
max-width: 100%;
max-height: 100%;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a:focus,
input:focus,
select:focus,
textarea:focus {
outline: 1px solid -webkit-focus-ring-color;
outline-offset: -1px;
}
hr {
border: 0;
height: 2px;
border-bottom: 2px solid;
}
h1 {
padding-bottom: 0.3em;
line-height: 1.2;
border-bottom-width: 1px;
border-bottom-style: solid;
}
h1, h2, h3 {
font-weight: normal;
}
table {
border-collapse: collapse;
}
table > thead > tr > th {
text-align: left;
border-bottom: 1px solid;
}
table > thead > tr > th,
table > thead > tr > td,
table > tbody > tr > th,
table > tbody > tr > td {
padding: 5px 10px;
}
table > tbody > tr + tr > td {
border-top: 1px solid;
}
blockquote {
margin: 0 7px 0 5px;
padding: 0 16px 0 10px;
border-left-width: 5px;
border-left-style: solid;
}
code {
font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
font-size: 14px;
line-height: 19px;
}
.mac code {
font-size: 12px;
line-height: 18px;
}
code > div {
padding: 16px;
border-radius: 3px;
overflow: auto;
}
#scroll-to-top {
position: fixed;
width: 40px;
height: 40px;
right: 25px;
bottom: 25px;
background-color:#444444;
border-radius: 50%;
cursor: pointer;
box-shadow: 1px 1px 1px rgba(0,0,0,.25);
outline: none;
display: flex;
justify-content: center;
align-items: center;
}
#scroll-to-top:hover {
background-color:#007acc;
box-shadow: 2px 2px 2px rgba(0,0,0,.25);
}
body.vscode-light #scroll-to-top {
background-color: #949494;
}
body.vscode-high-contrast #scroll-to-top:hover {
background-color: #007acc;
}
body.vscode-high-contrast #scroll-to-top {
background-color: black;
border: 2px solid #6fc3df;
box-shadow: none;
}
body.vscode-high-contrast #scroll-to-top:hover {
background-color: #007acc;
}
#scroll-to-top span.icon::before {
content: "";
display: block;
/* Chevron up icon */
background:url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjIuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxNiAxNiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTYgMTY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbDojRkZGRkZGO30KCS5zdDF7ZmlsbDpub25lO30KPC9zdHlsZT4KPHRpdGxlPnVwY2hldnJvbjwvdGl0bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik04LDUuMWwtNy4zLDcuM0wwLDExLjZsOC04bDgsOGwtMC43LDAuN0w4LDUuMXoiLz4KPHJlY3QgY2xhc3M9InN0MSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ii8+Cjwvc3ZnPgo=');
width: 16px;
height: 16px;
}
/** Theming */
.vscode-light code > div {
background-color: rgba(220, 220, 220, 0.4);
}
.vscode-dark code > div {
background-color: rgba(10, 10, 10, 0.4);
}
.vscode-high-contrast code > div {
background-color: rgb(0, 0, 0);
}
.vscode-high-contrast h1 {
border-color: rgb(0, 0, 0);
}
.vscode-light table > thead > tr > th {
border-color: rgba(0, 0, 0, 0.69);
}
.vscode-dark table > thead > tr > th {
border-color: rgba(255, 255, 255, 0.69);
}
.vscode-light h1,
.vscode-light hr,
.vscode-light table > tbody > tr + tr > td {
border-color: rgba(0, 0, 0, 0.18);
}
.vscode-dark h1,
.vscode-dark hr,
.vscode-dark table > tbody > tr + tr > td {
border-color: rgba(255, 255, 255, 0.18);
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><circle fill="#C5C5C5" cx="8" cy="8" r="6"/></svg>

After

Width:  |  Height:  |  Size: 167 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><circle fill="#424242" cx="8" cy="8" r="6"/></svg>

After

Width:  |  Height:  |  Size: 167 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><circle fill="#c10" cx="8" cy="8" r="6"/></svg>

After

Width:  |  Height:  |  Size: 164 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><circle fill="#E51400" cx="8" cy="8" r="6"/></svg>

After

Width:  |  Height:  |  Size: 167 B

View File

@@ -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.
*--------------------------------------------------------------------------------------------*/
.runtime-extensions-editor .monaco-list .monaco-list-rows > .monaco-list-row.odd:not(:hover):not(.focused) {
background-color: rgba(130, 130, 130, 0.08);
}
.runtime-extensions-editor .extension {
display: flex;
padding-left: 20px;
padding-right: 20px;
}
.runtime-extensions-editor .extension .desc {
flex: 1;
padding: 4px 0;
}
.runtime-extensions-editor .extension .desc .name {
font-weight: bold;
}
.runtime-extensions-editor .extension .time {
padding: 4px;
text-align: right;
}
.runtime-extensions-editor .extension .desc>.msg>span:not(:last-child)::after {
content: '\2022';
padding: 0 4px;
opacity: .8;
}
.runtime-extensions-editor .monaco-action-bar .actions-container {
justify-content: left;
}
.monaco-workbench .part.statusbar .profileExtHost-statusbar-item .icon {
background: url('profile-stop.svg') no-repeat;
display: inline-block;
padding-right: 2px;
padding-bottom: 2px;
width: 16px;
height: 16px;
vertical-align: middle;
animation:fade 1000ms infinite;
}
@keyframes fade {
from { opacity: 1.0; }
50% { opacity: 0.5; }
to { opacity: 1.0; }
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><path fill="#1E1E1E" d="M16 2.6L13.4 0H6v6H0v10h10v-6h6z"/><g fill="#C5C5C5"><path d="M5 7h1v2H5zM11 1h1v2h-1zM13 1v3H9V1H7v5h.4L10 8.6V9h5V3zM7 10H3V7H1v8h8V9L7 7z"/></g></svg>

After

Width:  |  Height:  |  Size: 294 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><path fill="#F6F6F6" d="M16 2.6L13.4 0H6v6H0v10h10v-6h6z"/><g fill="#424242"><path d="M5 7h1v2H5zM11 1h1v2h-1zM13 1v3H9V1H7v5h.4L10 8.6V9h5V3zM7 10H3V7H1v8h8V9L7 7z"/></g></svg>

After

Width:  |  Height:  |  Size: 294 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-green{fill:#89d185;}</style></defs><title>continue</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14.334,8,3.667,16H3V0h.667Z"/></g><g id="iconBg"><path class="icon-vs-action-green" d="M4,1.5v13L12.667,8,4,1.5Z"/></g></svg>

After

Width:  |  Height:  |  Size: 505 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-green{fill:#388a34;}</style></defs><title>continue</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14.334,8,3.667,16H3V0h.667Z"/></g><g id="iconBg"><path class="icon-vs-action-green" d="M4,1.5v13L12.667,8,4,1.5Z"/></g></svg>

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -0,0 +1,681 @@
/*---------------------------------------------------------------------------------------------
* 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 * as os from 'os';
import product from 'vs/platform/product/node/product';
import pkg from 'vs/platform/product/node/package';
import { Action, IAction } from 'vs/base/common/actions';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
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 { 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, $, addClass, toggleClass, Dimension, clearNode } from 'vs/base/browser/dom';
import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { RunOnceScheduler } from 'vs/base/common/async';
import { clipboard } from 'electron';
import { EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { writeFile } from 'vs/base/node/pfs';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
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 { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
import { join } from 'vs/base/common/path';
import { onUnexpectedError } from 'vs/base/common/errors';
import { ExtensionIdentifier, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
export const IExtensionHostProfileService = createDecorator<IExtensionHostProfileService>('extensionHostProfileService');
export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey<string>('profileSessionState', 'none');
export const CONTEXT_EXTENSION_HOST_PROFILE_RECORDED = new RawContextKey<boolean>('extensionHostProfileRecorded', false);
export enum ProfileSessionState {
None = 0,
Starting = 1,
Running = 2,
Stopping = 3
}
export interface IExtensionHostProfileService {
_serviceBrand: any;
readonly onDidChangeState: Event<void>;
readonly onDidChangeLastProfile: Event<void>;
readonly state: ProfileSessionState;
readonly lastProfile: IExtensionHostProfile | null;
startProfiling(): void;
stopProfiling(): void;
getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile | undefined;
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;
}
interface IRuntimeExtension {
originalIndex: number;
description: IExtensionDescription;
marketplaceInfo: IExtension;
status: IExtensionsStatus;
profileInfo?: IExtensionProfileInformation;
unresponsiveProfile?: IExtensionHostProfile;
}
export class RuntimeExtensionsEditor extends BaseEditor {
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>;
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,
@IStorageService storageService: IStorageService,
@ILabelService private readonly _labelService: ILabelService,
@IWindowService private readonly _windowService: IWindowService
) {
super(RuntimeExtensionsEditor.ID, telemetryService, themeService, storageService);
this._list = null;
this._profileInfo = this._extensionHostProfileService.lastProfile;
this._register(this._extensionHostProfileService.onDidChangeLastProfile(() => {
this._profileInfo = this._extensionHostProfileService.lastProfile;
this._extensionsHostRecorded.set(!!this._profileInfo);
this._updateExtensions();
}));
this._register(this._extensionHostProfileService.onDidChangeState(() => {
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 !!extension.main;
});
this._updateExtensions();
});
this._register(this._extensionService.onDidChangeExtensionsStatus(() => this._updateSoon.schedule()));
}
private _updateExtensions(): void {
this._elements = this._resolveExtensions();
if (this._list) {
this._list.splice(0, this._list.length, this._elements);
}
}
private _resolveExtensions(): IRuntimeExtension[] {
let marketplaceMap: { [id: string]: IExtension; } = Object.create(null);
for (let extension of this._extensionsWorkbenchService.local) {
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
result = result.sort((a, b) => {
if (a.unresponsiveProfile === this._profileInfo && !b.unresponsiveProfile) {
return -1;
} else if (!a.unresponsiveProfile && b.unresponsiveProfile === this._profileInfo) {
return 1;
}
return a.originalIndex - b.originalIndex;
});
return result;
}
protected createEditor(parent: HTMLElement): void {
addClass(parent, '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;
name: 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 desc = append(element, $('div.desc'));
const name = append(desc, $('div.name'));
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,
name,
actionbar,
activationTime,
profileTime,
msgContainer,
disposables,
elementDisposables: []
};
},
renderElement: (element: IRuntimeExtension, index: number, data: IRuntimeExtensionTemplateData): void => {
data.elementDisposables = dispose(data.elementDisposables);
toggleClass(data.root, 'odd', index % 2 === 1);
data.name.textContent = element.marketplaceInfo ? element.marketplaceInfo.displayName : element.description.displayName || '';
const activationTimes = element.status.activationTimes!;
let syncTime = activationTimes.codeLoadingTime + activationTimes.activateCallTime;
data.activationTime.textContent = activationTimes.startup ? `Startup Activation: ${syncTime}ms` : `Activation: ${syncTime}ms`;
data.actionbar.clear();
if (element.unresponsiveProfile || isNonEmptyArray(element.status.runtimeErrors)) {
data.actionbar.push(new ReportExtensionIssueAction(element), { icon: true, label: true });
}
let title: string;
if (activationTimes.activationEvent === '*') {
title = nls.localize('starActivation', "Activated on start-up");
} else if (/^workspaceContains:/.test(activationTimes.activationEvent)) {
let fileNameOrGlob = activationTimes.activationEvent.substr('workspaceContains:'.length);
if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) {
title = nls.localize({
key: 'workspaceContainsGlobActivation',
comment: [
'{0} will be a glob pattern'
]
}, "Activated because a file matching {0} exists in your workspace", fileNameOrGlob);
} else {
title = nls.localize({
key: 'workspaceContainsFileActivation',
comment: [
'{0} will be a file name'
]
}, "Activated because file {0} exists in your workspace", fileNameOrGlob);
}
} else if (/^workspaceContainsTimeout:/.test(activationTimes.activationEvent)) {
const glob = activationTimes.activationEvent.substr('workspaceContainsTimeout:'.length);
title = nls.localize({
key: 'workspaceContainsTimeout',
comment: [
'{0} will be a glob pattern'
]
}, "Activated because searching for {0} took too long", glob);
} else if (/^onLanguage:/.test(activationTimes.activationEvent)) {
let language = activationTimes.activationEvent.substr('onLanguage:'.length);
title = nls.localize('languageActivation', "Activated because you opened a {0} file", language);
} else {
title = nls.localize({
key: 'workspaceGenericActivation',
comment: [
'The {0} placeholder will be an activation event, like e.g. \'language:typescript\', \'debug\', etc.'
]
}, "Activated on {0}", activationTimes.activationEvent);
}
data.activationTime.title = title;
clearNode(data.msgContainer);
if (this._extensionHostProfileService.getUnresponsiveProfile(element.description.identifier)) {
const el = $('span');
el.innerHTML = renderOcticons(` $(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');
el.innerHTML = renderOcticons(`$(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');
el.innerHTML = renderOcticons(`$(alert) ${element.status.messages[0].message}`);
data.msgContainer.appendChild(el);
}
if (element.description.extensionLocation.scheme !== 'file') {
const el = $('span');
el.innerHTML = renderOcticons(`$(rss) ${element.description.extensionLocation.authority}`);
data.msgContainer.appendChild(el);
const hostLabel = this._labelService.getHostLabel(REMOTE_HOST_SCHEME, this._windowService.getConfiguration().remoteAuthority);
if (hostLabel) {
el.innerHTML = renderOcticons(`$(rss) ${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 = this._instantiationService.createInstance(WorkbenchList, parent, delegate, [renderer], {
multipleSelectionSupport: false,
setRowLineHeight: false,
horizontalScrolling: false
}) as WorkbenchList<IRuntimeExtension>;
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));
actions.push(new Separator());
if (e.element.marketplaceInfo) {
actions.push(new Action('runtimeExtensionsEditor.action.disableWorkspace', nls.localize('disable workspace', "Disable (Workspace)"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.WorkspaceDisabled)));
actions.push(new Action('runtimeExtensionsEditor.action.disable', nls.localize('disable', "Disable"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.Disabled)));
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
});
});
}
@memoize
private get saveExtensionHostProfileAction(): IAction {
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 LABEL = nls.localize('showRuntimeExtensions', "Show Running Extensions");
constructor(
id: string, label: string,
@IEditorService private readonly _editorService: IEditorService,
@IInstantiationService private readonly _instantiationService: IInstantiationService
) {
super(id, label);
}
public async run(e?: any): Promise<any> {
await this._editorService.openEditor(this._instantiationService.createInstance(RuntimeExtensionsInput), { revealIfOpened: true });
}
}
export class ReportExtensionIssueAction extends Action {
private static readonly _id = 'workbench.extensions.action.reportExtensionIssue';
private static _label = nls.localize('reportExtensionIssue', "Report Issue");
private readonly _url: string;
private readonly _task?: () => Promise<any>;
constructor(extension: {
description: IExtensionDescription;
marketplaceInfo: IExtension;
status?: IExtensionsStatus;
unresponsiveProfile?: IExtensionHostProfile
}) {
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;
const { url, task } = ReportExtensionIssueAction._generateNewIssueUrl(extension);
this._url = url;
this._task = task;
}
async run(): Promise<void> {
if (this._task) {
await this._task();
}
window.open(this._url);
}
private static _generateNewIssueUrl(extension: {
description: IExtensionDescription;
marketplaceInfo: IExtension;
status?: IExtensionsStatus;
unresponsiveProfile?: IExtensionHostProfile
}): { url: string, task?: () => Promise<any> } {
let task: (() => Promise<any>) | undefined;
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 = product.reportIssueUrl;
}
let title: string;
let message: string;
let reason: string;
if (extension.unresponsiveProfile) {
// unresponsive extension host caused
reason = 'Performance';
title = 'Extension causes high cpu load';
let path = join(os.homedir(), `${extension.description.identifier.value}-unresponsive.cpuprofile.txt`);
task = async () => {
const profiler = await import('v8-inspect-profiler');
const data = profiler.rewriteAbsolutePaths({ profile: <any>extension.unresponsiveProfile!.data }, 'pii_removed');
profiler.writeProfile(data, path).then(undefined, onUnexpectedError);
};
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`;
} else {
// generic
reason = 'Bug';
title = 'Extension issue';
message = ':warning: We have written the needed data into your clipboard. Please paste! :warning:';
clipboard.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: \`${pkg.version}\`\n\n${message}`
);
return {
url: `${baseUrl}${queryStringPrefix}body=${body}&title=${encodeURIComponent(title)}`,
task
};
}
}
export class DebugExtensionHostAction extends Action {
static readonly ID = 'workbench.extensions.action.debugExtensionHost';
static LABEL = nls.localize('debugExtensionHost', "Start Debugging Extension Host");
static CSS_CLASS = 'debug-extension-host';
constructor(
@IDebugService private readonly _debugService: IDebugService,
@IWindowsService private readonly _windowsService: IWindowsService,
@IDialogService private readonly _dialogService: IDialogService,
@IExtensionService private readonly _extensionService: IExtensionService,
) {
super(DebugExtensionHostAction.ID, DebugExtensionHostAction.LABEL, DebugExtensionHostAction.CSS_CLASS);
}
async run(): Promise<any> {
const inspectPort = this._extensionService.getInspectPort();
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?", product.nameLong),
primaryButton: nls.localize('restart3', "Restart"),
secondaryButton: nls.localize('cancel', "Cancel")
});
if (res.confirmed) {
this._windowsService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] });
}
}
return this._debugService.startDebugging(undefined, {
type: 'node',
name: nls.localize('debugExtensionHost.launch.name', "Attach Extension Host"),
request: 'attach',
port: inspectPort
});
}
}
export class StartExtensionHostProfileAction extends Action {
static readonly ID = 'workbench.extensions.action.extensionHostProfile';
static LABEL = nls.localize('extensionHostProfileStart', "Start Extension Host Profile");
constructor(
id: string = StartExtensionHostProfileAction.ID, label: string = StartExtensionHostProfileAction.LABEL,
@IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService,
) {
super(id, label);
}
run(): Promise<any> {
this._extensionHostProfileService.startProfiling();
return Promise.resolve();
}
}
export class StopExtensionHostProfileAction extends Action {
static readonly ID = 'workbench.extensions.action.stopExtensionHostProfile';
static LABEL = nls.localize('stopExtensionHostProfileStart', "Stop Extension Host Profile");
constructor(
id: string = StartExtensionHostProfileAction.ID, label: string = StartExtensionHostProfileAction.LABEL,
@IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService,
) {
super(id, label);
}
run(): Promise<any> {
this._extensionHostProfileService.stopProfiling();
return Promise.resolve();
}
}
export class SaveExtensionHostProfileAction extends Action {
static LABEL = nls.localize('saveExtensionHostProfile', "Save Extension Host Profile");
static readonly ID = 'workbench.extensions.action.saveExtensionHostProfile';
constructor(
id: string = SaveExtensionHostProfileAction.ID, label: string = SaveExtensionHostProfileAction.LABEL,
@IWindowService private readonly _windowService: IWindowService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService,
) {
super(id, label, undefined, false);
this._extensionHostProfileService.onDidChangeLastProfile(() => {
this.enabled = (this._extensionHostProfileService.lastProfile !== null);
});
}
run(): Promise<any> {
return Promise.resolve(this._asyncRun());
}
private async _asyncRun(): Promise<any> {
let picked = await this._windowService.showSaveDialog({
title: 'Save Extension Host Profile',
buttonLabel: 'Save',
defaultPath: `CPU-${new Date().toISOString().replace(/[\-:]/g, '')}.cpuprofile`,
filters: [{
name: 'CPU Profiles',
extensions: ['cpuprofile', 'txt']
}]
});
if (!picked) {
return;
}
const profileInfo = this._extensionHostProfileService.lastProfile;
let dataToWrite: object = profileInfo ? profileInfo.data : {};
if (this._environmentService.isBuilt) {
const profiler = await import('v8-inspect-profiler');
// when running from a not-development-build we remove
// 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;
picked = picked + '.txt';
}
return writeFile(picked, JSON.stringify(profileInfo ? profileInfo.data : {}, null, '\t'));
}
}

View File

@@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* 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 { URI } from 'vs/base/common/uri';
import { EditorInput } from 'vs/workbench/common/editor';
export class RuntimeExtensionsInput extends EditorInput {
static readonly ID = 'workbench.runtimeExtensions.input';
constructor() {
super();
}
getTypeId(): string {
return RuntimeExtensionsInput.ID;
}
getName(): string {
return nls.localize('extensionsInputName', "Running Extensions");
}
matches(other: any): boolean {
if (!(other instanceof RuntimeExtensionsInput)) {
return false;
}
return true;
}
resolve(): Promise<any> {
return Promise.resolve(null);
}
supportsSplitEditor(): boolean {
return false;
}
getResource(): URI {
return URI.from({
scheme: 'runtime-extensions',
path: 'default'
});
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,149 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery';
suite('Extension query', () => {
test('parse', () => {
let query = Query.parse('');
assert.equal(query.value, '');
assert.equal(query.sortBy, '');
query = Query.parse('hello');
assert.equal(query.value, 'hello');
assert.equal(query.sortBy, '');
query = Query.parse(' hello world ');
assert.equal(query.value, 'hello world');
assert.equal(query.sortBy, '');
query = Query.parse('@sort');
assert.equal(query.value, '@sort');
assert.equal(query.sortBy, '');
query = Query.parse('@sort:');
assert.equal(query.value, '@sort:');
assert.equal(query.sortBy, '');
query = Query.parse(' @sort: ');
assert.equal(query.value, '@sort:');
assert.equal(query.sortBy, '');
query = Query.parse('@sort:installs');
assert.equal(query.value, '');
assert.equal(query.sortBy, 'installs');
query = Query.parse(' @sort:installs ');
assert.equal(query.value, '');
assert.equal(query.sortBy, 'installs');
query = Query.parse('@sort:installs-');
assert.equal(query.value, '');
assert.equal(query.sortBy, 'installs');
query = Query.parse('@sort:installs-foo');
assert.equal(query.value, '');
assert.equal(query.sortBy, 'installs');
query = Query.parse('@sort:installs');
assert.equal(query.value, '');
assert.equal(query.sortBy, 'installs');
query = Query.parse('@sort:installs');
assert.equal(query.value, '');
assert.equal(query.sortBy, 'installs');
query = Query.parse('vs @sort:installs');
assert.equal(query.value, 'vs');
assert.equal(query.sortBy, 'installs');
query = Query.parse('vs @sort:installs code');
assert.equal(query.value, 'vs code');
assert.equal(query.sortBy, 'installs');
query = Query.parse('@sort:installs @sort:ratings');
assert.equal(query.value, '');
assert.equal(query.sortBy, 'ratings');
});
test('toString', () => {
let query = new Query('hello', '', '');
assert.equal(query.toString(), 'hello');
query = new Query('hello world', '', '');
assert.equal(query.toString(), 'hello world');
query = new Query(' hello ', '', '');
assert.equal(query.toString(), 'hello');
query = new Query('', 'installs', '');
assert.equal(query.toString(), '@sort:installs');
query = new Query('', 'installs', '');
assert.equal(query.toString(), '@sort:installs');
query = new Query('', 'installs', '');
assert.equal(query.toString(), '@sort:installs');
query = new Query('hello', 'installs', '');
assert.equal(query.toString(), 'hello @sort:installs');
query = new Query(' hello ', 'installs', '');
assert.equal(query.toString(), 'hello @sort:installs');
});
test('isValid', () => {
let query = new Query('hello', '', '');
assert(query.isValid());
query = new Query('hello world', '', '');
assert(query.isValid());
query = new Query(' hello ', '', '');
assert(query.isValid());
query = new Query('', 'installs', '');
assert(query.isValid());
query = new Query('', 'installs', '');
assert(query.isValid());
query = new Query('', 'installs', '');
assert(query.isValid());
query = new Query('', 'installs', '');
assert(query.isValid());
query = new Query('hello', 'installs', '');
assert(query.isValid());
query = new Query(' hello ', 'installs', '');
assert(query.isValid());
});
test('equals', () => {
let query1 = new Query('hello', '', '');
let query2 = new Query('hello', '', '');
assert(query1.equals(query2));
query2 = new Query('hello world', '', '');
assert(!query1.equals(query2));
query2 = new Query('hello', 'installs', '');
assert(!query1.equals(query2));
query2 = new Query('hello', 'installs', '');
assert(!query1.equals(query2));
});
test('autocomplete', () => {
Query.suggestions('@sort:in').some(x => x === '@sort:installs ');
Query.suggestions('@sort:installs').every(x => x !== '@sort:rating ');
Query.suggestions('@category:blah').some(x => x === '@category:"extension packs" ');
Query.suggestions('@category:"extension packs"').every(x => x !== '@category:formatters ');
});
});

View File

@@ -0,0 +1,170 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as sinon from 'sinon';
import * as assert from 'assert';
import * as path from 'vs/base/common/path';
import * as fs from 'fs';
import * as os from 'os';
import * as uuid from 'vs/base/common/uuid';
import {
IExtensionGalleryService, IGalleryExtensionAssets, IGalleryExtension, IExtensionManagementService,
IExtensionEnablementService, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { Emitter } 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';
import { TestTextResourceConfigurationService, TestContextService, TestLifecycleService, TestEnvironmentService, TestStorageService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { URI } from 'vs/base/common/uri';
import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
import { IFileService } from 'vs/platform/files/common/files';
import { FileService } from 'vs/workbench/services/files/node/fileService';
import * as extfs from 'vs/base/node/extfs';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { IPager } from 'vs/base/common/paging';
import { assign } from 'vs/base/common/objects';
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensions';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test';
import { IURLService } from 'vs/platform/url/common/url';
import product from 'vs/platform/product/node/product';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { INotificationService, Severity, IPromptChoice, IPromptOptions } from 'vs/platform/notification/common/notification';
import { URLService } from 'vs/platform/url/common/urlService';
import { IExperimentService } from 'vs/workbench/contrib/experiments/node/experimentService';
import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
const mockExtensionGallery: IGalleryExtension[] = [
aGalleryExtension('MockExtension1', {
displayName: 'Mock Extension 1',
version: '1.5',
publisherId: 'mockPublisher1Id',
publisher: 'mockPublisher1',
publisherDisplayName: 'Mock Publisher 1',
description: 'Mock Description',
installCount: 1000,
rating: 4,
ratingCount: 100
}, {
dependencies: ['pub.1'],
}, {
manifest: { uri: 'uri:manifest', fallbackUri: 'fallback:manifest' },
readme: { uri: 'uri:readme', fallbackUri: 'fallback:readme' },
changelog: { uri: 'uri:changelog', fallbackUri: 'fallback:changlog' },
download: { uri: 'uri:download', fallbackUri: 'fallback:download' },
icon: { uri: 'uri:icon', fallbackUri: 'fallback:icon' },
license: { uri: 'uri:license', fallbackUri: 'fallback:license' },
repository: { uri: 'uri:repository', fallbackUri: 'fallback:repository' },
coreTranslations: {}
}),
aGalleryExtension('MockExtension2', {
displayName: 'Mock Extension 2',
version: '1.5',
publisherId: 'mockPublisher2Id',
publisher: 'mockPublisher2',
publisherDisplayName: 'Mock Publisher 2',
description: 'Mock Description',
installCount: 1000,
rating: 4,
ratingCount: 100
}, {
dependencies: ['pub.1', 'pub.2'],
}, {
manifest: { uri: 'uri:manifest', fallbackUri: 'fallback:manifest' },
readme: { uri: 'uri:readme', fallbackUri: 'fallback:readme' },
changelog: { uri: 'uri:changelog', fallbackUri: 'fallback:changlog' },
download: { uri: 'uri:download', fallbackUri: 'fallback:download' },
icon: { uri: 'uri:icon', fallbackUri: 'fallback:icon' },
license: { uri: 'uri:license', fallbackUri: 'fallback:license' },
repository: { uri: 'uri:repository', fallbackUri: 'fallback:repository' },
coreTranslations: {}
})
];
const mockExtensionLocal = [
{
type: ExtensionType.User,
identifier: mockExtensionGallery[0].identifier,
manifest: {
name: mockExtensionGallery[0].name,
publisher: mockExtensionGallery[0].publisher,
version: mockExtensionGallery[0].version
},
metadata: null,
path: 'somepath',
readmeUrl: 'some readmeUrl',
changelogUrl: 'some changelogUrl'
},
{
type: ExtensionType.User,
identifier: mockExtensionGallery[1].identifier,
manifest: {
name: mockExtensionGallery[1].name,
publisher: mockExtensionGallery[1].publisher,
version: mockExtensionGallery[1].version
},
metadata: null,
path: 'somepath',
readmeUrl: 'some readmeUrl',
changelogUrl: 'some changelogUrl'
}
];
const mockTestData = {
recommendedExtensions: [
'mockPublisher1.mockExtension1',
'MOCKPUBLISHER2.mockextension2',
'badlyformattedextension',
'MOCKPUBLISHER2.mockextension2',
'unknown.extension'
],
validRecommendedExtensions: [
'mockPublisher1.mockExtension1',
'MOCKPUBLISHER2.mockextension2'
]
};
function aPage<T>(...objects: T[]): IPager<T> {
return { firstPage: objects, total: objects.length, pageSize: objects.length, getPage: () => null! };
}
const noAssets: IGalleryExtensionAssets = {
changelog: null,
download: null!,
icon: null!,
license: null,
manifest: null,
readme: null,
repository: null,
coreTranslations: null!
};
function aGalleryExtension(name: string, properties: any = {}, galleryExtensionProperties: any = {}, assets: IGalleryExtensionAssets = noAssets): IGalleryExtension {
const galleryExtension = <IGalleryExtension>Object.create({});
assign(galleryExtension, { name, publisher: 'pub', version: '1.0.0', properties: {}, assets: {} }, properties);
assign(galleryExtension.properties, { dependencies: [] }, galleryExtensionProperties);
assign(galleryExtension.assets, assets);
galleryExtension.identifier = { id: getGalleryExtensionId(galleryExtension.publisher, galleryExtension.name), uuid: uuid.generateUuid() };
return <IGalleryExtension>galleryExtension;
}
// {{SQL CARBON EDIT}} disable broken tests
suite('ExtensionsTipsService Test', () => {
test('ExtensionTipsService: No Prompt for valid workspace recommendations when galleryService is absent', () => {
});
});

View File

@@ -0,0 +1,527 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { assign } from 'vs/base/common/objects';
import { generateUuid } from 'vs/base/common/uuid';
import { ExtensionsListView } from 'vs/workbench/contrib/extensions/electron-browser/extensionsViews';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/node/extensionsWorkbenchService';
import {
IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, IExtensionTipsService, ILocalExtension, IGalleryExtension, IQueryOptions,
DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, IExtensionManagementServerService, IExtensionManagementServer, EnablementState, ExtensionRecommendationReason, SortBy
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService';
import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { IURLService } from 'vs/platform/url/common/url';
import { Emitter } 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 { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { TestContextService, TestWindowService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { URLService } from 'vs/platform/url/common/urlService';
import { URI } from 'vs/base/common/uri';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { SinonStub } from 'sinon';
import { IExperimentService, ExperimentService, ExperimentState, ExperimentActionType } from 'vs/workbench/contrib/experiments/node/experimentService';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl';
import { ExtensionManagementServerService } from 'vs/workbench/services/extensions/electron-browser/extensionManagementServerService';
import { ExtensionIdentifier, ExtensionType } from 'vs/platform/extensions/common/extensions';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
suite('ExtensionsListView Tests', () => {
let instantiationService: TestInstantiationService;
let testableView: ExtensionsListView;
let installEvent: Emitter<InstallExtensionEvent>,
didInstallEvent: Emitter<DidInstallExtensionEvent>,
uninstallEvent: Emitter<IExtensionIdentifier>,
didUninstallEvent: Emitter<DidUninstallExtensionEvent>;
const localEnabledTheme = aLocalExtension('first-enabled-extension', { categories: ['Themes', 'random'] });
const localEnabledLanguage = aLocalExtension('second-enabled-extension', { categories: ['Programming languages'] });
const localDisabledTheme = aLocalExtension('first-disabled-extension', { categories: ['themes'] });
const localDisabledLanguage = aLocalExtension('second-disabled-extension', { categories: ['programming languages'] });
const localRandom = aLocalExtension('random-enabled-extension', { categories: ['random'] });
const builtInTheme = aLocalExtension('my-theme', { contributes: { themes: ['my-theme'] } }, { type: ExtensionType.System });
const builtInBasic = aLocalExtension('my-lang', { contributes: { grammars: [{ language: 'my-language' }] } }, { type: ExtensionType.System });
const workspaceRecommendationA = aGalleryExtension('workspace-recommendation-A');
const workspaceRecommendationB = aGalleryExtension('workspace-recommendation-B');
const fileBasedRecommendationA = aGalleryExtension('filebased-recommendation-A');
const fileBasedRecommendationB = aGalleryExtension('filebased-recommendation-B');
const otherRecommendationA = aGalleryExtension('other-recommendation-A');
suiteSetup(() => {
installEvent = new Emitter<InstallExtensionEvent>();
didInstallEvent = new Emitter<DidInstallExtensionEvent>();
uninstallEvent = new Emitter<IExtensionIdentifier>();
didUninstallEvent = new Emitter<DidUninstallExtensionEvent>();
instantiationService = new TestInstantiationService();
instantiationService.stub(ITelemetryService, NullTelemetryService);
instantiationService.stub(ILogService, NullLogService);
instantiationService.stub(IWindowService, TestWindowService);
instantiationService.stub(IWorkspaceContextService, new TestContextService());
instantiationService.stub(IConfigurationService, new TestConfigurationService());
instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService);
instantiationService.stub(ISharedProcessService, TestSharedProcessService);
instantiationService.stub(IExperimentService, ExperimentService);
instantiationService.stub(IExtensionManagementService, ExtensionManagementService);
instantiationService.stub(IExtensionManagementService, 'onInstallExtension', installEvent.event);
instantiationService.stub(IExtensionManagementService, 'onDidInstallExtension', didInstallEvent.event);
instantiationService.stub(IExtensionManagementService, 'onUninstallExtension', uninstallEvent.event);
instantiationService.stub(IExtensionManagementService, 'onDidUninstallExtension', didUninstallEvent.event);
instantiationService.stub(IRemoteAgentService, RemoteAgentService);
instantiationService.stub(IExtensionManagementServerService, instantiationService.createInstance(ExtensionManagementServerService, <IExtensionManagementServer>{ authority: 'vscode-local', extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local' }));
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
instantiationService.stub(IExtensionTipsService, ExtensionTipsService);
instantiationService.stub(IURLService, URLService);
instantiationService.stubPromise(IExtensionTipsService, 'getWorkspaceRecommendations', [
{ extensionId: workspaceRecommendationA.identifier.id },
{ extensionId: workspaceRecommendationB.identifier.id }]);
instantiationService.stub(IExtensionTipsService, 'getFileBasedRecommendations', [
{ extensionId: fileBasedRecommendationA.identifier.id },
{ extensionId: fileBasedRecommendationB.identifier.id }]);
instantiationService.stubPromise(IExtensionTipsService, 'getOtherRecommendations', [
{ extensionId: otherRecommendationA.identifier.id }
]);
const reasons = {};
reasons[workspaceRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.Workspace };
reasons[workspaceRecommendationB.identifier.id] = { reasonId: ExtensionRecommendationReason.Workspace };
reasons[fileBasedRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.File };
reasons[fileBasedRecommendationB.identifier.id] = { reasonId: ExtensionRecommendationReason.File };
reasons[otherRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.Executable };
instantiationService.stub(IExtensionTipsService, 'getAllRecommendationsWithReason', reasons);
});
setup(async () => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localEnabledTheme, localEnabledLanguage, localRandom, localDisabledTheme, localDisabledLanguage, builtInTheme, builtInBasic]);
instantiationService.stubPromise(IExtensionManagementService, 'getExtensionsReport', []);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage());
instantiationService.stubPromise(IExperimentService, 'getExperimentsByType', []);
instantiationService.stub(IExtensionService, {
getExtensions: () => {
return Promise.resolve([
{ identifier: new ExtensionIdentifier(localEnabledTheme.identifier.id) },
{ identifier: new ExtensionIdentifier(localEnabledLanguage.identifier.id) },
{ identifier: new ExtensionIdentifier(localRandom.identifier.id) },
{ identifier: new ExtensionIdentifier(builtInTheme.identifier.id) },
{ identifier: new ExtensionIdentifier(builtInBasic.identifier.id) }
]);
}
});
await (<TestExtensionEnablementService>instantiationService.get(IExtensionEnablementService)).setEnablement([localDisabledTheme], EnablementState.Disabled);
await (<TestExtensionEnablementService>instantiationService.get(IExtensionEnablementService)).setEnablement([localDisabledLanguage], EnablementState.Disabled);
instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService));
testableView = instantiationService.createInstance(ExtensionsListView, {});
});
teardown(() => {
(<ExtensionsWorkbenchService>instantiationService.get(IExtensionsWorkbenchService)).dispose();
testableView.dispose();
});
test('Test query types', () => {
assert.equal(ExtensionsListView.isBuiltInExtensionsQuery('@builtin'), true);
assert.equal(ExtensionsListView.isInstalledExtensionsQuery('@installed'), true);
assert.equal(ExtensionsListView.isInstalledExtensionsQuery('@enabled'), true);
assert.equal(ExtensionsListView.isInstalledExtensionsQuery('@disabled'), true);
assert.equal(ExtensionsListView.isInstalledExtensionsQuery('@outdated'), true);
assert.equal(ExtensionsListView.isInstalledExtensionsQuery('@installed searchText'), true);
assert.equal(ExtensionsListView.isInstalledExtensionsQuery('@enabled searchText'), true);
assert.equal(ExtensionsListView.isInstalledExtensionsQuery('@disabled searchText'), true);
assert.equal(ExtensionsListView.isInstalledExtensionsQuery('@outdated searchText'), true);
});
test('Test empty query equates to sort by install count', () => {
const target = <SinonStub>instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage());
return testableView.show('').then(() => {
assert.ok(target.calledOnce);
const options: IQueryOptions = target.args[0][0];
assert.equal(options.sortBy, SortBy.InstallCount);
});
});
test('Test non empty query without sort doesnt use sortBy', () => {
const target = <SinonStub>instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage());
return testableView.show('some extension').then(() => {
assert.ok(target.calledOnce);
const options: IQueryOptions = target.args[0][0];
assert.equal(options.sortBy, undefined);
});
});
test('Test query with sort uses sortBy', () => {
const target = <SinonStub>instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage());
return testableView.show('some extension @sort:rating').then(() => {
assert.ok(target.calledOnce);
const options: IQueryOptions = target.args[0][0];
assert.equal(options.sortBy, SortBy.WeightedRating);
});
});
test('Test installed query results', () => {
const allInstalledCheck = testableView.show('@installed').then(result => {
assert.equal(result.length, 5, 'Unexpected number of results for @installed query');
const actual = [result.get(0).name, result.get(1).name, result.get(2).name, result.get(3).name, result.get(4).name].sort();
const expected = [localDisabledTheme.manifest.name, localEnabledTheme.manifest.name, localRandom.manifest.name, localDisabledLanguage.manifest.name, localEnabledLanguage.manifest.name];
for (let i = 0; i < result.length; i++) {
assert.equal(actual[i], expected[i], 'Unexpected extension for @installed query.');
}
});
const installedCheck = testableView.show('@installed first').then(result => {
assert.equal(result.length, 2, 'Unexpected number of results for @installed query');
assert.equal(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with search text.');
assert.equal(result.get(1).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with search text.');
});
const allDisabledCheck = testableView.show('@disabled').then(result => {
assert.equal(result.length, 2, 'Unexpected number of results for @disabled query');
assert.equal(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @disabled query.');
assert.equal(result.get(1).name, localDisabledLanguage.manifest.name, 'Unexpected extension for @disabled query.');
});
const allEnabledCheck = testableView.show('@enabled').then(result => {
assert.equal(result.length, 3, 'Unexpected number of results for @enabled query');
assert.equal(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @enabled query.');
assert.equal(result.get(1).name, localRandom.manifest.name, 'Unexpected extension for @enabled query.');
assert.equal(result.get(2).name, localEnabledLanguage.manifest.name, 'Unexpected extension for @enabled query.');
});
const allBuiltinThemesCheck = testableView.show('@builtin:themes').then(result => {
assert.equal(result.length, 1, 'Unexpected number of results for @builtin:themes query');
assert.equal(result.get(0).name, builtInTheme.manifest.name, 'Unexpected extension for @builtin:themes query.');
});
const allBuiltinBasicsCheck = testableView.show('@builtin:basics').then(result => {
assert.equal(result.length, 1, 'Unexpected number of results for @builtin:basics query');
assert.equal(result.get(0).name, builtInBasic.manifest.name, 'Unexpected extension for @builtin:basics query.');
});
const allBuiltinCheck = testableView.show('@builtin').then(result => {
assert.equal(result.length, 2, 'Unexpected number of results for @builtin query');
assert.equal(result.get(0).name, builtInBasic.manifest.name, 'Unexpected extension for @builtin query.');
assert.equal(result.get(1).name, builtInTheme.manifest.name, 'Unexpected extension for @builtin query.');
});
const builtinCheck = testableView.show('@builtin my-theme').then(result => {
assert.equal(result.length, 1, 'Unexpected number of results for @builtin query');
assert.equal(result.get(0).name, builtInTheme.manifest.name, 'Unexpected extension for @builtin query.');
});
return Promise.all([
allInstalledCheck,
installedCheck,
allDisabledCheck,
allEnabledCheck,
allBuiltinThemesCheck,
allBuiltinBasicsCheck,
allBuiltinCheck,
builtinCheck]);
});
test('Test installed query with category', () => {
const installedCategoryWithoutQuotesCheck = testableView.show('@installed category:themes').then(result => {
assert.equal(result.length, 2, 'Unexpected number of results for @installed query with category');
assert.equal(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with category.');
assert.equal(result.get(1).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with category.');
});
const installedCategoryWithQuotesCheck = testableView.show('@installed category:"themes"').then(result => {
assert.equal(result.length, 2, 'Unexpected number of results for @installed query with quoted category');
assert.equal(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with quoted category.');
assert.equal(result.get(1).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with quoted category.');
});
const installedCategoryWithSpaceCheck = testableView.show('@installed category:"programming languages"').then(result => {
assert.equal(result.length, 2, 'Unexpected number of results for @installed query with quoted category including space');
assert.equal(result.get(0).name, localDisabledLanguage.manifest.name, 'Unexpected extension for @installed query with quoted category inlcuding space.');
assert.equal(result.get(1).name, localEnabledLanguage.manifest.name, 'Unexpected extension for @installed query with quoted category including space.');
});
const installedMultipleCategoryCheck = testableView.show('@installed category:themes category:random').then(result => {
assert.equal(result.length, 3, 'Unexpected number of results for @installed query with multiple category');
assert.equal(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @installed query with multiple category.');
assert.equal(result.get(1).name, localEnabledTheme.manifest.name, 'Unexpected extension for @installed query with multiple category.');
assert.equal(result.get(2).name, localRandom.manifest.name, 'Unexpected extension for @installed query with multiple category.');
});
const enabledCategoryWithoutQuotesCheck = testableView.show('@enabled category:themes').then(result => {
assert.equal(result.length, 1, 'Unexpected number of results for @enabled query with category');
assert.equal(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @enabled query with category.');
});
const enabledCategoryWithQuotesCheck = testableView.show('@enabled category:"themes"').then(result => {
assert.equal(result.length, 1, 'Unexpected number of results for @enabled query with quoted category');
assert.equal(result.get(0).name, localEnabledTheme.manifest.name, 'Unexpected extension for @enabled query with quoted category.');
});
const enabledCategoryWithSpaceCheck = testableView.show('@enabled category:"programming languages"').then(result => {
assert.equal(result.length, 1, 'Unexpected number of results for @enabled query with quoted category inlcuding space');
assert.equal(result.get(0).name, localEnabledLanguage.manifest.name, 'Unexpected extension for @enabled query with quoted category including space.');
});
const disabledCategoryWithoutQuotesCheck = testableView.show('@disabled category:themes').then(result => {
assert.equal(result.length, 1, 'Unexpected number of results for @disabled query with category');
assert.equal(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @disabled query with category.');
});
const disabledCategoryWithQuotesCheck = testableView.show('@disabled category:"themes"').then(result => {
assert.equal(result.length, 1, 'Unexpected number of results for @disabled query with quoted category');
assert.equal(result.get(0).name, localDisabledTheme.manifest.name, 'Unexpected extension for @disabled query with quoted category.');
});
const disabledCategoryWithSpaceCheck = testableView.show('@disabled category:"programming languages"').then(result => {
assert.equal(result.length, 1, 'Unexpected number of results for @disabled query with quoted category inlcuding space');
assert.equal(result.get(0).name, localDisabledLanguage.manifest.name, 'Unexpected extension for @disabled query with quoted category including space.');
});
return Promise.resolve([
installedCategoryWithoutQuotesCheck,
installedCategoryWithQuotesCheck,
installedCategoryWithSpaceCheck,
installedMultipleCategoryCheck,
enabledCategoryWithoutQuotesCheck,
enabledCategoryWithQuotesCheck,
enabledCategoryWithSpaceCheck,
disabledCategoryWithoutQuotesCheck,
disabledCategoryWithQuotesCheck,
disabledCategoryWithSpaceCheck
]);
});
test('Test @recommended:workspace query', () => {
const workspaceRecommendedExtensions = [
workspaceRecommendationA,
workspaceRecommendationB
];
const target = <SinonStub>instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...workspaceRecommendedExtensions));
return testableView.show('@recommended:workspace').then(result => {
assert.ok(target.calledOnce);
const options: IQueryOptions = target.args[0][0];
assert.equal(options.names!.length, workspaceRecommendedExtensions.length);
assert.equal(result.length, workspaceRecommendedExtensions.length);
for (let i = 0; i < workspaceRecommendedExtensions.length; i++) {
assert.equal(options.names![i], workspaceRecommendedExtensions[i].identifier.id);
assert.equal(result.get(i).identifier.id, workspaceRecommendedExtensions[i].identifier.id);
}
});
});
test('Test @recommended query', () => {
const allRecommendedExtensions = [
fileBasedRecommendationA,
fileBasedRecommendationB,
otherRecommendationA
];
const target = <SinonStub>instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...allRecommendedExtensions));
return testableView.show('@recommended').then(result => {
const options: IQueryOptions = target.args[0][0];
assert.ok(target.calledOnce);
assert.equal(options.names!.length, allRecommendedExtensions.length);
assert.equal(result.length, allRecommendedExtensions.length);
for (let i = 0; i < allRecommendedExtensions.length; i++) {
assert.equal(options.names![i], allRecommendedExtensions[i].identifier.id);
assert.equal(result.get(i).identifier.id, allRecommendedExtensions[i].identifier.id);
}
});
});
test('Test @recommended:all query', () => {
const allRecommendedExtensions = [
workspaceRecommendationA,
workspaceRecommendationB,
fileBasedRecommendationA,
fileBasedRecommendationB,
otherRecommendationA
];
const target = <SinonStub>instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...allRecommendedExtensions));
return testableView.show('@recommended:all').then(result => {
const options: IQueryOptions = target.args[0][0];
assert.ok(target.calledOnce);
assert.equal(options.names!.length, allRecommendedExtensions.length);
assert.equal(result.length, allRecommendedExtensions.length);
for (let i = 0; i < allRecommendedExtensions.length; i++) {
assert.equal(options.names![i], allRecommendedExtensions[i].identifier.id);
assert.equal(result.get(i).identifier.id, allRecommendedExtensions[i].identifier.id);
}
});
});
test('Test curated list experiment', () => {
const curatedList = [
workspaceRecommendationA,
fileBasedRecommendationA
];
const experimentTarget = <SinonStub>instantiationService.stubPromise(IExperimentService, 'getCuratedExtensionsList', curatedList.map(e => e.identifier.id));
const queryTarget = <SinonStub>instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...curatedList));
return testableView.show('curated:mykey').then(result => {
const curatedKey: string = experimentTarget.args[0][0];
const options: IQueryOptions = queryTarget.args[0][0];
assert.ok(experimentTarget.calledOnce);
assert.ok(queryTarget.calledOnce);
assert.equal(options.names!.length, curatedList.length);
assert.equal(result.length, curatedList.length);
for (let i = 0; i < curatedList.length; i++) {
assert.equal(options.names![i], curatedList[i].identifier.id);
assert.equal(result.get(i).identifier.id, curatedList[i].identifier.id);
}
assert.equal(curatedKey, 'mykey');
});
});
test('Test search', () => {
const searchText = 'search-me';
const results = [
fileBasedRecommendationA,
workspaceRecommendationA,
otherRecommendationA,
workspaceRecommendationB
];
const queryTarget = <SinonStub>instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...results));
return testableView.show('search-me').then(result => {
const options: IQueryOptions = queryTarget.args[0][0];
assert.ok(queryTarget.calledOnce);
assert.equal(options.text, searchText);
assert.equal(result.length, results.length);
for (let i = 0; i < results.length; i++) {
assert.equal(result.get(i).identifier.id, results[i].identifier.id);
}
});
});
test('Test preferred search experiment', () => {
const searchText = 'search-me';
const actual = [
fileBasedRecommendationA,
workspaceRecommendationA,
otherRecommendationA,
workspaceRecommendationB
];
const expected = [
workspaceRecommendationA,
workspaceRecommendationB,
fileBasedRecommendationA,
otherRecommendationA
];
const queryTarget = <SinonStub>instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...actual));
const experimentTarget = <SinonStub>instantiationService.stubPromise(IExperimentService, 'getExperimentsByType', [{
id: 'someId',
enabled: true,
state: ExperimentState.Run,
action: {
type: ExperimentActionType.ExtensionSearchResults,
properties: {
searchText: 'search-me',
preferredResults: [
workspaceRecommendationA.identifier.id,
'something-that-wasnt-in-first-page',
workspaceRecommendationB.identifier.id
]
}
}
}]);
testableView.dispose();
testableView = instantiationService.createInstance(ExtensionsListView, {});
return testableView.show('search-me').then(result => {
const options: IQueryOptions = queryTarget.args[0][0];
assert.ok(experimentTarget.calledOnce);
assert.ok(queryTarget.calledOnce);
assert.equal(options.text, searchText);
assert.equal(result.length, expected.length);
for (let i = 0; i < expected.length; i++) {
assert.equal(result.get(i).identifier.id, expected[i].identifier.id);
}
});
});
test('Skip preferred search experiment when user defines sort order', () => {
const searchText = 'search-me';
const realResults = [
fileBasedRecommendationA,
workspaceRecommendationA,
otherRecommendationA,
workspaceRecommendationB
];
const queryTarget = <SinonStub>instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...realResults));
testableView.dispose();
testableView = instantiationService.createInstance(ExtensionsListView, {});
return testableView.show('search-me @sort:installs').then(result => {
const options: IQueryOptions = queryTarget.args[0][0];
assert.ok(queryTarget.calledOnce);
assert.equal(options.text, searchText);
assert.equal(result.length, realResults.length);
for (let i = 0; i < realResults.length; i++) {
assert.equal(result.get(i).identifier.id, realResults[i].identifier.id);
}
});
});
function aLocalExtension(name: string = 'someext', manifest: any = {}, properties: any = {}): ILocalExtension {
manifest = assign({ name, publisher: 'pub', version: '1.0.0' }, manifest);
properties = assign({
type: ExtensionType.User,
location: URI.file(`pub.${name}`),
identifier: { id: getGalleryExtensionId(manifest.publisher, manifest.name), uuid: undefined },
metadata: { id: getGalleryExtensionId(manifest.publisher, manifest.name), publisherId: manifest.publisher, publisherDisplayName: 'somename' }
}, properties);
return <ILocalExtension>Object.create({ manifest, ...properties });
}
function aGalleryExtension(name: string, properties: any = {}, galleryExtensionProperties: any = {}, assets: any = {}): IGalleryExtension {
const galleryExtension = <IGalleryExtension>Object.create({});
assign(galleryExtension, { name, publisher: 'pub', version: '1.0.0', properties: {}, assets: {} }, properties);
assign(galleryExtension.properties, { dependencies: [] }, galleryExtensionProperties);
assign(galleryExtension.assets, assets);
galleryExtension.identifier = { id: getGalleryExtensionId(galleryExtension.publisher, galleryExtension.name), uuid: generateUuid() };
return <IGalleryExtension>galleryExtension;
}
function aPage<T>(...objects: T[]): IPager<T> {
return { firstPage: objects, total: objects.length, pageSize: objects.length, getPage: () => null! };
}
});