Merge from vscode 2b0b9136329c181a9e381463a1f7dc3a2d105a34 (#4880)

This commit is contained in:
Karl Burtram
2019-04-05 10:09:18 -07:00
committed by GitHub
parent 9bd7e30d18
commit cb5bcf2248
433 changed files with 8915 additions and 8361 deletions

View File

@@ -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));
}));
}
}

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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 }
);
}

View File

@@ -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) }
);
}
}

View File

@@ -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;

View File

@@ -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)}`;
}
}

View File

@@ -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;
}

View File

@@ -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';