mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-30 08:40:29 -04:00
Merge from vscode 2b0b9136329c181a9e381463a1f7dc3a2d105a34 (#4880)
This commit is contained in:
@@ -5,19 +5,20 @@
|
||||
|
||||
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 { IListService, WorkbenchAsyncDataTree } 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';
|
||||
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IAsyncDataSource, ITreeNode } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
|
||||
export interface IExtensionTemplateData {
|
||||
icon: HTMLImageElement;
|
||||
@@ -39,49 +40,40 @@ export interface IExtensionData {
|
||||
parent: IExtensionData | null;
|
||||
}
|
||||
|
||||
export class DataSource implements IDataSource {
|
||||
export class AsyncDataSource implements IAsyncDataSource<IExtensionData, any> {
|
||||
|
||||
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 {
|
||||
public hasChildren({ hasChildren }: IExtensionData): boolean {
|
||||
return hasChildren;
|
||||
}
|
||||
|
||||
public getChildren(tree: ITree, extensionData: IExtensionData): Promise<any> {
|
||||
public getChildren(extensionData: IExtensionData): Promise<any> {
|
||||
return extensionData.getChildren();
|
||||
}
|
||||
|
||||
public getParent(tree: ITree, { parent }: IExtensionData): Promise<any> {
|
||||
return Promise.resolve(parent);
|
||||
}
|
||||
|
||||
export class VirualDelegate implements IListVirtualDelegate<IExtensionData> {
|
||||
|
||||
public getHeight(element: IExtensionData): number {
|
||||
return 62;
|
||||
}
|
||||
public getTemplateId({ extension }: IExtensionData): string {
|
||||
return extension ? ExtensionRenderer.TEMPLATE_ID : UnknownExtensionRenderer.TEMPLATE_ID;
|
||||
}
|
||||
}
|
||||
|
||||
export class Renderer implements IRenderer {
|
||||
export class ExtensionRenderer implements IListRenderer<ITreeNode<IExtensionData>, IExtensionTemplateData> {
|
||||
|
||||
private static readonly EXTENSION_TEMPLATE_ID = 'extension-template';
|
||||
private static readonly UNKNOWN_EXTENSION_TEMPLATE_ID = 'unknown-extension-template';
|
||||
static readonly TEMPLATE_ID = 'extension-template';
|
||||
|
||||
constructor(@IInstantiationService private readonly instantiationService: IInstantiationService) {
|
||||
}
|
||||
|
||||
public getHeight(tree: ITree, element: IExtensionData): number {
|
||||
return 62;
|
||||
public get templateId(): string {
|
||||
return ExtensionRenderer.TEMPLATE_ID;
|
||||
}
|
||||
|
||||
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 {
|
||||
public renderTemplate(container: HTMLElement): IExtensionTemplateData {
|
||||
dom.addClass(container, 'extension');
|
||||
|
||||
const icon = dom.append(container, dom.$<HTMLImageElement>('img.icon'));
|
||||
@@ -91,8 +83,6 @@ export class Renderer implements IRenderer {
|
||||
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();
|
||||
@@ -113,25 +103,8 @@ export class Renderer implements IRenderer {
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
public renderElement(node: ITreeNode<IExtensionData>, index: number, data: IExtensionTemplateData): void {
|
||||
const extension = node.element.extension;
|
||||
const onError = Event.once(domEvent(data.icon, 'error'));
|
||||
onError(() => data.icon.src = extension.iconUrlFallback, null, data.extensionDisposables);
|
||||
data.icon.src = extension.iconUrl;
|
||||
@@ -146,54 +119,36 @@ export class Renderer implements IRenderer {
|
||||
data.name.textContent = extension.displayName;
|
||||
data.identifier.textContent = extension.identifier.id;
|
||||
data.author.textContent = extension.publisherDisplayName;
|
||||
data.extensionData = extensionData;
|
||||
data.extensionData = node.element;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
public disposeTemplate(templateData: IExtensionTemplateData): void {
|
||||
templateData.extensionDisposables = dispose((<IExtensionTemplateData>templateData).extensionDisposables);
|
||||
}
|
||||
}
|
||||
|
||||
export class Controller extends WorkbenchTreeController {
|
||||
export class UnknownExtensionRenderer implements IListRenderer<ITreeNode<IExtensionData>, IUnknownExtensionTemplateData> {
|
||||
|
||||
constructor(
|
||||
@IExtensionsWorkbenchService private readonly extensionsWorkdbenchService: IExtensionsWorkbenchService,
|
||||
@IConfigurationService configurationService: IConfigurationService
|
||||
) {
|
||||
super({}, configurationService);
|
||||
static readonly TEMPLATE_ID = 'unknown-extension-template';
|
||||
|
||||
// TODO@Sandeep this should be a command
|
||||
this.downKeyBindingDispatcher.set(KeyMod.CtrlCmd | KeyCode.Enter, (tree: ITree, event: any) => this.openExtension(tree, true));
|
||||
public get templateId(): string {
|
||||
return UnknownExtensionRenderer.TEMPLATE_ID;
|
||||
}
|
||||
|
||||
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 renderTemplate(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 openExtension(tree: ITree, sideByside: boolean): boolean {
|
||||
const element: IExtensionData = tree.getFocus();
|
||||
if (element.extension) {
|
||||
this.extensionsWorkdbenchService.open(element.extension, sideByside);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
public renderElement(node: ITreeNode<IExtensionData>, index: number, data: IUnknownExtensionTemplateData): void {
|
||||
data.identifier.textContent = node.element.extension.identifier.id;
|
||||
}
|
||||
|
||||
public disposeTemplate(data: IUnknownExtensionTemplateData): void {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +173,7 @@ class OpenExtensionAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtensionsTree extends WorkbenchTree {
|
||||
export class ExtensionsTree extends WorkbenchAsyncDataTree<IExtensionData, any> {
|
||||
|
||||
constructor(
|
||||
input: IExtensionData,
|
||||
@@ -227,30 +182,37 @@ export class ExtensionsTree extends WorkbenchTree {
|
||||
@IListService listService: IListService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IConfigurationService configurationService: IConfigurationService
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IAccessibilityService accessibilityService: IAccessibilityService,
|
||||
@IExtensionsWorkbenchService extensionsWorkdbenchService: IExtensionsWorkbenchService
|
||||
) {
|
||||
const renderer = instantiationService.createInstance(Renderer);
|
||||
const controller = instantiationService.createInstance(Controller);
|
||||
const delegate = new VirualDelegate();
|
||||
const dataSource = new AsyncDataSource();
|
||||
const renderers = [instantiationService.createInstance(ExtensionRenderer), instantiationService.createInstance(UnknownExtensionRenderer)];
|
||||
const identityProvider = {
|
||||
getId({ extension, parent }: IExtensionData): string {
|
||||
return parent ? this.getId(parent) + '/' + extension.identifier.id : extension.identifier.id;
|
||||
}
|
||||
};
|
||||
|
||||
super(
|
||||
container,
|
||||
delegate,
|
||||
renderers,
|
||||
dataSource,
|
||||
{
|
||||
dataSource: new DataSource(),
|
||||
renderer,
|
||||
controller
|
||||
}, {
|
||||
indentPixels: 40,
|
||||
twistiePixels: 20
|
||||
indent: 40,
|
||||
identityProvider,
|
||||
multipleSelectionSupport: false
|
||||
},
|
||||
contextKeyService, listService, themeService, instantiationService, configurationService
|
||||
contextKeyService, listService, themeService, configurationService, keybindingService, accessibilityService
|
||||
);
|
||||
|
||||
this.setInput(input);
|
||||
|
||||
this.disposables.push(this.onDidChangeSelection(event => {
|
||||
if (event && event.payload && event.payload.origin === 'keyboard') {
|
||||
controller.openExtension(this, false);
|
||||
}
|
||||
extensionsWorkdbenchService.open(event.elements[0], event.browserEvent instanceof MouseEvent && (event.browserEvent.ctrlKey || event.browserEvent.metaKey || event.browserEvent.altKey));
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ export class ExtensionsInput extends EditorInput {
|
||||
return localize('extensionsInputName', "Extension: {0}", this.extension.displayName);
|
||||
}
|
||||
|
||||
matches(other: any): boolean {
|
||||
matches(other: unknown): boolean {
|
||||
if (!(other instanceof ExtensionsInput)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/we
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
|
||||
@@ -136,7 +135,7 @@ class NavBar {
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.actionbar = dispose(this.actionbar);
|
||||
dispose(this.actionbar);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -671,7 +670,7 @@ export class ExtensionEditor extends BaseEditor {
|
||||
});
|
||||
}
|
||||
|
||||
private renderDependencies(container: HTMLElement, extensionDependencies: IExtensionDependencies): Tree {
|
||||
private renderDependencies(container: HTMLElement, extensionDependencies: IExtensionDependencies): ExtensionsTree {
|
||||
class ExtensionData implements IExtensionData {
|
||||
|
||||
private readonly extensionDependencies: IExtensionDependencies;
|
||||
@@ -720,7 +719,7 @@ export class ExtensionEditor extends BaseEditor {
|
||||
return Promise.resolve({ focus() { extensionsPackTree.domFocus(); } });
|
||||
}
|
||||
|
||||
private renderExtensionPack(container: HTMLElement, extension: IExtension): Tree {
|
||||
private renderExtensionPack(container: HTMLElement, extension: IExtension): ExtensionsTree {
|
||||
const extensionsWorkbenchService = this.extensionsWorkbenchService;
|
||||
class ExtensionData implements IExtensionData {
|
||||
|
||||
|
||||
@@ -10,17 +10,17 @@ 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 * as os 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 { IExtensionHostProfileService } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { createSlowExtensionAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions';
|
||||
|
||||
export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
@@ -30,11 +30,11 @@ export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchCont
|
||||
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,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
this._register(_extensionService.onDidChangeResponsiveChange(this._onDidChangeResponsiveChange, this));
|
||||
@@ -132,16 +132,12 @@ export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchCont
|
||||
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`);
|
||||
const path = join(os.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" : {
|
||||
@@ -151,24 +147,22 @@ export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchCont
|
||||
}
|
||||
*/
|
||||
this._telemetryService.publicLog('exthostunresponsive', {
|
||||
id,
|
||||
duration,
|
||||
data,
|
||||
});
|
||||
|
||||
// add to running extensions view
|
||||
this._extensionProfileService.setUnresponsiveProfile(extension.identifier, profile);
|
||||
|
||||
// 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) {
|
||||
const action = await this._instantiationService.invokeFunction(createSlowExtensionAction, extension, profile);
|
||||
|
||||
if (!action) {
|
||||
// cannot report issues against this extension...
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -190,18 +184,8 @@ export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchCont
|
||||
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();
|
||||
}
|
||||
}],
|
||||
action
|
||||
],
|
||||
{ silent: true }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as os from 'os';
|
||||
import pkg from 'vs/platform/product/node/package';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IRequestService } from 'vs/platform/request/node/request';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { asText } from 'vs/base/node/request';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
|
||||
abstract class RepoInfo {
|
||||
readonly base: string;
|
||||
readonly owner: string;
|
||||
readonly repo: string;
|
||||
|
||||
static fromExtension(desc: IExtensionDescription): RepoInfo | undefined {
|
||||
|
||||
let result: RepoInfo | undefined;
|
||||
|
||||
// scheme:auth/OWNER/REPO/issues/
|
||||
if (desc.bugs && typeof desc.bugs.url === 'string') {
|
||||
const base = URI.parse(desc.bugs.url);
|
||||
const match = /\/([^/]+)\/([^/]+)\/issues\/?$/.exec(desc.bugs.url);
|
||||
if (match) {
|
||||
result = {
|
||||
base: base.with({ path: null, fragment: null, query: null }).toString(true),
|
||||
owner: match[1],
|
||||
repo: match[2]
|
||||
};
|
||||
}
|
||||
}
|
||||
// scheme:auth/OWNER/REPO.git
|
||||
if (!result && desc.repository && typeof desc.repository.url === 'string') {
|
||||
const base = URI.parse(desc.repository.url);
|
||||
const match = /\/([^/]+)\/([^/]+)(\.git)?$/.exec(desc.repository.url);
|
||||
if (match) {
|
||||
result = {
|
||||
base: base.with({ path: null, fragment: null, query: null }).toString(true),
|
||||
owner: match[1],
|
||||
repo: match[2]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// for now only GH is supported
|
||||
if (result && result.base.indexOf('github') === -1) {
|
||||
result = undefined;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export class SlowExtensionAction extends Action {
|
||||
|
||||
constructor(
|
||||
readonly extension: IExtensionDescription,
|
||||
readonly profile: IExtensionHostProfile,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
) {
|
||||
super('report.slow', localize('cmd.reportOrShow', "Performance Issue"), 'extension-action report-issue');
|
||||
this.enabled = Boolean(RepoInfo.fromExtension(extension));
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
const action = await this._instantiationService.invokeFunction(createSlowExtensionAction, this.extension, this.profile);
|
||||
if (action) {
|
||||
await action.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function createSlowExtensionAction(
|
||||
accessor: ServicesAccessor,
|
||||
extension: IExtensionDescription,
|
||||
profile: IExtensionHostProfile
|
||||
): Promise<Action | undefined> {
|
||||
|
||||
const info = RepoInfo.fromExtension(extension);
|
||||
if (!info) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const requestService = accessor.get(IRequestService);
|
||||
const instaService = accessor.get(IInstantiationService);
|
||||
const url = `https://api.github.com/search/issues?q=is:issue+state:open+in:title+repo:${info.owner}/${info.repo}+%22Extension+causes+high+cpu+load%22`;
|
||||
const res = await requestService.request({ url }, CancellationToken.None);
|
||||
const rawText = await asText(res);
|
||||
if (!rawText) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const data = <{ total_count: number; }>JSON.parse(rawText);
|
||||
if (!data || typeof data.total_count !== 'number') {
|
||||
return undefined;
|
||||
} else if (data.total_count === 0) {
|
||||
return instaService.createInstance(ReportExtensionSlowAction, extension, info, profile);
|
||||
} else {
|
||||
return instaService.createInstance(ShowExtensionSlowAction, extension, info, profile);
|
||||
}
|
||||
}
|
||||
|
||||
class ReportExtensionSlowAction extends Action {
|
||||
|
||||
constructor(
|
||||
readonly extension: IExtensionDescription,
|
||||
readonly repoInfo: RepoInfo,
|
||||
readonly profile: IExtensionHostProfile,
|
||||
@IDialogService private readonly _dialogService: IDialogService,
|
||||
) {
|
||||
super('report.slow', localize('cmd.report', "Report Issue"));
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
|
||||
// rewrite pii (paths) and store on disk
|
||||
const profiler = await import('v8-inspect-profiler');
|
||||
const data = profiler.rewriteAbsolutePaths({ profile: <any>this.profile.data }, 'pii_removed');
|
||||
const path = join(os.homedir(), `${this.extension.identifier.value}-unresponsive.cpuprofile.txt`);
|
||||
await profiler.writeProfile(data, path).then(undefined, onUnexpectedError);
|
||||
|
||||
// build issue
|
||||
const title = encodeURIComponent('Extension causes high cpu load');
|
||||
const osVersion = `${os.type()} ${os.arch()} ${os.release()}`;
|
||||
const message = `:warning: Make sure to **attach** this file from your *home*-directory:\n:warning:\`${path}\`\n\nFind more details here: https://github.com/Microsoft/vscode/wiki/Explain:-extension-causes-high-cpu-load`;
|
||||
const body = encodeURIComponent(`- Issue Type: \`Performance\`
|
||||
- Extension Name: \`${this.extension.name}\`
|
||||
- Extension Version: \`${this.extension.version}\`
|
||||
- OS Version: \`${osVersion}\`
|
||||
- VSCode version: \`${pkg.version}\`\n\n${message}`);
|
||||
|
||||
const url = `${this.repoInfo.base}/${this.repoInfo.owner}/${this.repoInfo.repo}/issues/new/?body=${body}&title=${title}`;
|
||||
window.open(url);
|
||||
|
||||
this._dialogService.show(
|
||||
Severity.Info,
|
||||
localize('attach.title', "Did you attach the CPU-Profile?"),
|
||||
[localize('ok', 'OK')],
|
||||
{ detail: localize('attach.msg', "This is a reminder to make sure that you have not forgotten to attach '{0}' to the issue you have just created.", path) }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ShowExtensionSlowAction extends Action {
|
||||
|
||||
constructor(
|
||||
readonly extension: IExtensionDescription,
|
||||
readonly repoInfo: RepoInfo,
|
||||
readonly profile: IExtensionHostProfile,
|
||||
@IDialogService private readonly _dialogService: IDialogService,
|
||||
) {
|
||||
super('show.slow', localize('cmd.show', "Show Issues"));
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
|
||||
// rewrite pii (paths) and store on disk
|
||||
const profiler = await import('v8-inspect-profiler');
|
||||
const data = profiler.rewriteAbsolutePaths({ profile: <any>this.profile.data }, 'pii_removed');
|
||||
const path = join(os.homedir(), `${this.extension.identifier.value}-unresponsive.cpuprofile.txt`);
|
||||
await profiler.writeProfile(data, path).then(undefined, onUnexpectedError);
|
||||
|
||||
// show issues
|
||||
const url = `${this.repoInfo.base}/${this.repoInfo.owner}/${this.repoInfo.repo}/issues?utf8=✓&q=is%3Aissue+state%3Aopen+%22Extension+causes+high+cpu+load%22`;
|
||||
window.open(url);
|
||||
|
||||
this._dialogService.show(
|
||||
Severity.Info,
|
||||
localize('attach.title', "Did you attach the CPU-Profile?"),
|
||||
[localize('ok', 'OK')],
|
||||
{ detail: localize('attach.msg2', "This is a reminder to make sure that you have not forgotten to attach '{0}' to an existing performance issue.", path) }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -300,11 +300,11 @@
|
||||
border-color: rgb(238, 238, 238);
|
||||
}
|
||||
|
||||
.extension-editor .subcontent .monaco-tree-row .content .unknown-extension {
|
||||
.extension-editor .subcontent .monaco-list-row .content .unknown-extension {
|
||||
line-height: 62px;
|
||||
}
|
||||
|
||||
.extension-editor .subcontent .monaco-tree-row .content .unknown-extension > .error-marker {
|
||||
.extension-editor .subcontent .monaco-list-row .content .unknown-extension > .error-marker {
|
||||
background-color: #BE1100;
|
||||
padding: 2px 4px;
|
||||
font-weight: bold;
|
||||
@@ -312,46 +312,46 @@
|
||||
color: #CCC;
|
||||
}
|
||||
|
||||
.extension-editor .subcontent .monaco-tree-row .unknown-extension > .message {
|
||||
.extension-editor .subcontent .monaco-list-row .unknown-extension > .message {
|
||||
padding-left: 10px;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.extension-editor .subcontent .monaco-tree-row .extension {
|
||||
.extension-editor .subcontent .monaco-list-row .extension {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.extension-editor .subcontent .monaco-tree-row .extension > .details {
|
||||
.extension-editor .subcontent .monaco-list-row .extension > .details {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.extension-editor .subcontent .monaco-tree-row .extension > .details > .header {
|
||||
.extension-editor .subcontent .monaco-list-row .extension > .details > .header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 19px;
|
||||
line-height: 19px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.extension-editor .subcontent .monaco-tree-row .extension > .icon {
|
||||
.extension-editor .subcontent .monaco-list-row .extension > .icon {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.extension-editor .subcontent .monaco-tree-row .extension > .details > .header > .name {
|
||||
.extension-editor .subcontent .monaco-list-row .extension > .details > .header > .name {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.extension-editor .subcontent .monaco-tree-row .extension > .details > .header > .name:hover {
|
||||
.extension-editor .subcontent .monaco-list-row .extension > .details > .header > .name:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.extension-editor .subcontent .monaco-tree-row .extension > .details > .header > .identifier {
|
||||
.extension-editor .subcontent .monaco-list-row .extension > .details > .header > .identifier {
|
||||
font-size: 90%;
|
||||
opacity: 0.6;
|
||||
margin-left: 10px;
|
||||
@@ -360,14 +360,14 @@
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.extension-editor .subcontent .monaco-tree-row .extension > .details > .footer {
|
||||
.extension-editor .subcontent .monaco-list-row .extension > .details > .footer {
|
||||
display: flex;
|
||||
height: 19px;
|
||||
line-height: 19px;
|
||||
overflow: hidden;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.extension-editor .subcontent .monaco-tree-row .extension > .details > .footer > .author {
|
||||
.extension-editor .subcontent .monaco-list-row .extension > .details > .footer > .author {
|
||||
font-size: 90%;
|
||||
font-weight: 600;
|
||||
opacity: 0.6;
|
||||
|
||||
@@ -40,10 +40,9 @@ import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/cont
|
||||
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';
|
||||
import { SlowExtensionAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions';
|
||||
|
||||
export const IExtensionHostProfileService = createDecorator<IExtensionHostProfileService>('extensionHostProfileService');
|
||||
export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey<string>('profileSessionState', 'none');
|
||||
@@ -308,7 +307,10 @@ export class RuntimeExtensionsEditor extends BaseEditor {
|
||||
data.activationTime.textContent = activationTimes.startup ? `Startup Activation: ${syncTime}ms` : `Activation: ${syncTime}ms`;
|
||||
|
||||
data.actionbar.clear();
|
||||
if (element.unresponsiveProfile || isNonEmptyArray(element.status.runtimeErrors)) {
|
||||
if (element.unresponsiveProfile) {
|
||||
data.actionbar.push(this._instantiationService.createInstance(SlowExtensionAction, element.description, element.unresponsiveProfile), { icon: true, label: true });
|
||||
}
|
||||
if (isNonEmptyArray(element.status.runtimeErrors)) {
|
||||
data.actionbar.push(new ReportExtensionIssueAction(element), { icon: true, label: true });
|
||||
}
|
||||
|
||||
@@ -471,7 +473,6 @@ export class ReportExtensionIssueAction extends Action {
|
||||
private static _label = nls.localize('reportExtensionIssue', "Report Issue");
|
||||
|
||||
private readonly _url: string;
|
||||
private readonly _task?: () => Promise<any>;
|
||||
|
||||
constructor(extension: {
|
||||
description: IExtensionDescription;
|
||||
@@ -484,15 +485,10 @@ export class ReportExtensionIssueAction extends Action {
|
||||
&& extension.marketplaceInfo.type === ExtensionType.User
|
||||
&& !!extension.description.repository && !!extension.description.repository.url;
|
||||
|
||||
const { url, task } = ReportExtensionIssueAction._generateNewIssueUrl(extension);
|
||||
this._url = url;
|
||||
this._task = task;
|
||||
this._url = ReportExtensionIssueAction._generateNewIssueUrl(extension);
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
if (this._task) {
|
||||
await this._task();
|
||||
}
|
||||
window.open(this._url);
|
||||
}
|
||||
|
||||
@@ -501,9 +497,9 @@ export class ReportExtensionIssueAction extends Action {
|
||||
marketplaceInfo: IExtension;
|
||||
status?: IExtensionsStatus;
|
||||
unresponsiveProfile?: IExtensionHostProfile
|
||||
}): { url: string, task?: () => Promise<any> } {
|
||||
}): string {
|
||||
|
||||
|
||||
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/`;
|
||||
@@ -511,28 +507,10 @@ export class ReportExtensionIssueAction extends Action {
|
||||
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```');
|
||||
}
|
||||
let reason = 'Bug';
|
||||
let title = 'Extension issue';
|
||||
let 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 ? '?' : '&';
|
||||
@@ -544,10 +522,7 @@ export class ReportExtensionIssueAction extends Action {
|
||||
- VSCode version: \`${pkg.version}\`\n\n${message}`
|
||||
);
|
||||
|
||||
return {
|
||||
url: `${baseUrl}${queryStringPrefix}body=${body}&title=${encodeURIComponent(title)}`,
|
||||
task
|
||||
};
|
||||
return `${baseUrl}${queryStringPrefix}body=${body}&title=${encodeURIComponent(title)}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ export class RuntimeExtensionsInput extends EditorInput {
|
||||
return nls.localize('extensionsInputName', "Running Extensions");
|
||||
}
|
||||
|
||||
matches(other: any): boolean {
|
||||
matches(other: unknown): boolean {
|
||||
if (!(other instanceof RuntimeExtensionsInput)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ 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 { mkdirp, rimraf, RimRafMode } from 'vs/base/node/pfs';
|
||||
import {
|
||||
IExtensionGalleryService, IGalleryExtensionAssets, IGalleryExtension, IExtensionManagementService,
|
||||
IExtensionEnablementService, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier
|
||||
@@ -25,9 +26,7 @@ import { TestNotificationService } from 'vs/platform/notification/test/common/te
|
||||
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 { LegacyFileService } from 'vs/workbench/services/files/node/fileService';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
import { IPager } from 'vs/base/common/paging';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
|
||||
Reference in New Issue
Block a user