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
@@ -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 };
|
||||
}
|
||||
}
|
||||
256
src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts
Normal 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);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
80
src/vs/workbench/contrib/extensions/common/extensionQuery.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
158
src/vs/workbench/contrib/extensions/common/extensions.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
138
src/vs/workbench/contrib/extensions/common/extensionsUtils.ts
Normal 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));
|
||||
}
|
||||
@@ -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)
|
||||
);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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."));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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))));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
|
After Width: | Height: | Size: 1.0 KiB |
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 |
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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);
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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; }
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
|
After Width: | Height: | Size: 7.4 KiB |
@@ -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'));
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 ');
|
||||
});
|
||||
});
|
||||
@@ -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', () => {
|
||||
});
|
||||
});
|
||||
@@ -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! };
|
||||
}
|
||||
});
|
||||
|
||||