提交 9736b536 编写于 作者: J João Moreno

Merge pull request #3971 from joaomoreno/extensions

Extension status bar improvements & polish
......@@ -13,6 +13,7 @@ import strings = require('vs/base/common/strings');
import {IAction} from 'vs/base/common/actions';
import {IXHRResponse} from 'vs/base/common/http';
import Severity from 'vs/base/common/severity';
import { TPromise } from 'vs/base/common/winjs.base';
export interface ErrorListenerCallback {
(error: any): void;
......@@ -88,6 +89,10 @@ export function onUnexpectedError(e: any): void {
}
}
export function onUnexpectedPromiseError<T>(promise: TPromise<T>): TPromise<T> {
return promise.then<T>(null, onUnexpectedError);
}
export interface IConnectionErrorData {
status: number;
statusText?: string;
......
......@@ -5,6 +5,7 @@
'use strict';
import nls = require('vs/nls');
import { TPromise } from 'vs/base/common/winjs.base';
import Event from 'vs/base/common/event';
import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
......@@ -52,7 +53,7 @@ export interface IGalleryService {
export interface IExtensionsService {
serviceId: ServiceIdentifier<any>;
onInstallExtension: Event<IExtensionManifest>;
onDidInstallExtension: Event<IExtension>;
onDidInstallExtension: Event<{ extension: IExtension; error?: Error; }>;
onUninstallExtension: Event<IExtension>;
onDidUninstallExtension: Event<IExtension>;
......@@ -67,4 +68,6 @@ export var IExtensionTipsService = createDecorator<IExtensionTipsService>('exten
export interface IExtensionTipsService {
serviceId: ServiceIdentifier<any>;
getRecommendations(): TPromise<IExtension[]>;
}
\ No newline at end of file
}
export var commandCategory = nls.localize('extensionsCategory', "Extensions");
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IExtension, IExtensionsService, IGalleryService } from 'vs/workbench/parts/extensions/common/extensions';
import { TPromise } from 'vs/base/common/winjs.base';
import * as semver from 'semver';
'use strict';
export function extensionEquals(one: IExtension, other: IExtension): boolean {
return one.publisher === other.publisher && one.name === other.name;
}
export function getOutdatedExtensions(accessor: ServicesAccessor): TPromise<IExtension[]> {
const extensionsService = accessor.get(IExtensionsService);
const galleryService = accessor.get(IGalleryService);
if (!galleryService.isEnabled()) {
return TPromise.as([]);
}
return TPromise.join<any>([galleryService.query(), extensionsService.getInstalled()])
.then(result => {
const available = result[0];
const installed = result[1];
return available.filter(extension => {
const local = installed.filter(local => extensionEquals(local, extension))[0];
return local && semver.lt(local.version, extension.version);
});
});
}
\ No newline at end of file
......@@ -3,12 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/extensions';
import platform = require('vs/platform/platform');
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import statusbar = require('vs/workbench/browser/parts/statusbar/statusbar');
import { ExtensionsStatusbarItem } from 'vs/workbench/parts/extensions/electron-browser/extensionsWidgets';
import { IGalleryService } from 'vs/workbench/parts/extensions/common/extensions';
import { GalleryService } from 'vs/workbench/parts/extensions/node/vsoGalleryService';
import { GalleryService } from 'vs/workbench/parts/extensions/common/vsoGalleryService';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { ExtensionsWorkbenchExtension } from 'vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchExtension';
......@@ -24,5 +25,5 @@ registerSingleton(IGalleryService, GalleryService);
(<statusbar.IStatusbarRegistry>platform.Registry.as(statusbar.Extensions.Statusbar)).registerStatusbarItem(new statusbar.StatusbarItemDescriptor(
ExtensionsStatusbarItem,
statusbar.StatusbarAlignment.LEFT,
10 /* Low Priority */
10000
));
......@@ -3,8 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/extensions';
import nls = require('vs/nls');
import { IDisposable, disposeAll } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
......@@ -24,9 +22,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IWorkspaceContextService } from 'vs/workbench/services/workspace/common/contextService';
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
import { Action } from 'vs/base/common/actions';
import * as semver from 'semver';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { shell } from 'electron';
import { extensionEquals, getOutdatedExtensions } from 'vs/workbench/parts/extensions/common/extensionsUtil';
const $ = dom.emmet;
......@@ -76,10 +74,6 @@ function getHighlights(input: string, extension: IExtension): IHighlights {
return { id, name, displayName, description };
}
function extensionEquals(one: IExtension, other: IExtension): boolean {
return one.publisher === other.publisher && one.name === other.name;
}
function extensionEntryCompare(one: IExtensionEntry, other: IExtensionEntry): number {
const oneInstallCount = one.extension.galleryInformation ? one.extension.galleryInformation.installCount : 0;
const otherInstallCount = other.extension.galleryInformation ? other.extension.galleryInformation.installCount : 0;
......@@ -263,7 +257,7 @@ class Renderer implements IRenderer<IExtensionEntry> {
updateActions();
data.disposables = disposeAll(data.disposables);
data.disposables.push(this.extensionsService.onDidInstallExtension(e => onExtensionStateChange(e, ExtensionState.Installed)));
data.disposables.push(this.extensionsService.onDidInstallExtension(e => onExtensionStateChange(e.extension, ExtensionState.Installed)));
data.disposables.push(this.extensionsService.onDidUninstallExtension(e => onExtensionStateChange(e, ExtensionState.Uninstalled)));
data.displayName.set(extension.displayName, entry.highlights.displayName);
......@@ -480,8 +474,7 @@ class OutdatedExtensionsModel implements IModel<IExtensionEntry> {
public entries: IExtensionEntry[];
constructor(
private galleryExtensions: IExtension[],
private localExtensions: IExtension[],
private outdatedExtensions: IExtension[],
@IInstantiationService instantiationService: IInstantiationService
) {
this.renderer = instantiationService.createInstance(Renderer);
......@@ -490,12 +483,9 @@ class OutdatedExtensionsModel implements IModel<IExtensionEntry> {
}
public set input(input: string) {
this.entries = this.galleryExtensions
this.entries = this.outdatedExtensions
.map(extension => ({ extension, highlights: getHighlights(input.trim(), extension) }))
.filter(({ extension, highlights }) => {
const local = this.localExtensions.filter(local => extensionEquals(local, extension))[0];
return local && semver.lt(local.version, extension.version) && !!highlights;
})
.filter(({ highlights }) => !!highlights)
.map(({ extension, highlights }: { extension: IExtension, highlights: IHighlights }) => ({
extension,
highlights,
......@@ -525,8 +515,8 @@ export class OutdatedExtensionsHandler extends QuickOpenHandler {
getResults(input: string): TPromise<IModel<IExtensionEntry>> {
if (!this.modelPromise) {
this.telemetryService.publicLog('extensionGallery:open');
this.modelPromise = TPromise.join<any>([this.galleryService.query(), this.extensionsService.getInstalled()])
.then(result => this.instantiationService.createInstance(OutdatedExtensionsModel, result[0], result[1]));
this.modelPromise = this.instantiationService.invokeFunction(getOutdatedExtensions)
.then(outdated => this.instantiationService.createInstance(OutdatedExtensionsModel, outdated));
}
return this.modelPromise.then(model => {
......
......@@ -5,80 +5,166 @@
import nls = require('vs/nls');
import Severity from 'vs/base/common/severity';
import dom = require('vs/base/browser/dom');
import lifecycle = require('vs/base/common/lifecycle');
import {onUnexpectedError} from 'vs/base/common/errors';
import { ThrottledDelayer } from 'vs/base/common/async';
import { TPromise } from 'vs/base/common/winjs.base';
import { emmet as $, append, toggleClass } from 'vs/base/browser/dom';
import { IDisposable, combinedDispose } from 'vs/base/common/lifecycle';
import { onUnexpectedPromiseError } from 'vs/base/common/errors';
import { assign } from 'vs/base/common/objects';
import { Action } from 'vs/base/common/actions';
import statusbar = require('vs/workbench/browser/parts/statusbar/statusbar');
import { IExtensionService, IExtensionsStatus } from 'vs/platform/extensions/common/extensions';
import { IExtensionService, IMessage } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IMessageService, CloseAction } from 'vs/platform/message/common/message';
import { UninstallAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions';
import { IExtensionsService } from 'vs/workbench/parts/extensions/common/extensions';
import { IExtensionsService, commandCategory, IExtension, IExtensionManifest } from 'vs/workbench/parts/extensions/common/extensions';
import { IQuickOpenService } from 'vs/workbench/services/quickopen/common/quickOpenService';
import { getOutdatedExtensions } from 'vs/workbench/parts/extensions/common/extensionsUtil';
var $ = dom.emmet;
interface IState {
errors: IMessage[];
installing: IExtensionManifest[];
outdated: IExtension[];
}
const InitialState: IState = {
errors: [],
installing: [],
outdated: []
};
function extensionEquals(one: IExtensionManifest, other: IExtensionManifest): boolean {
return one.publisher === other.publisher && one.name === other.name;
}
const OutdatedPeriod = 5 * 60 * 1000; // every 5 minutes
export class ExtensionsStatusbarItem implements statusbar.IStatusbarItem {
private toDispose: lifecycle.IDisposable[];
private domNode: HTMLElement;
private status: { [id: string]: IExtensionsStatus };
private container: HTMLElement;
private messageCount: number;
private state: IState = InitialState;
private outdatedDelayer = new ThrottledDelayer<void>(OutdatedPeriod);
constructor(
@IExtensionService extensionService: IExtensionService,
@IExtensionService private extensionService: IExtensionService,
@IMessageService private messageService: IMessageService,
@IExtensionsService protected extensionsService: IExtensionsService,
@IInstantiationService protected instantiationService: IInstantiationService
@IInstantiationService protected instantiationService: IInstantiationService,
@IQuickOpenService protected quickOpenService: IQuickOpenService
) {}
render(container: HTMLElement): IDisposable {
this.domNode = append(container, $('a.extensions-statusbar'));
append(this.domNode, $('.icon'));
this.domNode.onclick = () => this.onClick();
this.checkErrors();
this.checkOutdated();
const disposables = [];
this.extensionsService.onInstallExtension(this.onInstallExtension, this, disposables);
this.extensionsService.onDidInstallExtension(this.onDidInstallExtension, this, disposables);
this.extensionsService.onDidUninstallExtension(this.onDidUninstallExtension, this, disposables);
return combinedDispose(...disposables);
}
) {
this.toDispose = [];
this.messageCount = 0;
private updateState(obj: any): void {
this.state = assign(this.state, obj);
this.onStateChange();
}
private get hasErrors() { return this.state.errors.length > 0; }
private get isInstalling() { return this.state.installing.length > 0; }
private get hasUpdates() { return this.state.outdated.length > 0; }
private onStateChange(): void {
toggleClass(this.domNode, 'has-errors', this.hasErrors);
toggleClass(this.domNode, 'is-installing', !this.hasErrors && this.isInstalling);
toggleClass(this.domNode, 'has-updates', !this.hasErrors && !this.isInstalling && this.hasUpdates);
if (this.hasErrors) {
const singular = nls.localize('oneIssue', "Extensions (1 issue)");
const plural = nls.localize('multipleIssues', "Extensions ({0} issues)", this.state.errors.length);
this.domNode.title = this.state.errors.length > 1 ? plural : singular;
} else if (this.isInstalling) {
this.domNode.title = nls.localize('extensionsInstalling', "Extensions ({0} installing...)", this.state.installing.length);
} else if (this.hasUpdates) {
const singular = nls.localize('oneUpdate', "Extensions (1 update available)");
const plural = nls.localize('multipleUpdates', "Extensions ({0} updates available)", this.state.outdated.length);
this.domNode.title = this.state.outdated.length > 1 ? plural : singular;
} else {
this.domNode.title = nls.localize('extensions', "Extensions");
}
}
extensionService.onReady().then(() => {
this.status = extensionService.getExtensionsStatus();
Object.keys(this.status).forEach(key => {
this.messageCount += this.status[key].messages.filter(message => message.type > Severity.Info).length;
private onClick(): void {
if (this.hasErrors) {
this.showErrors(this.state.errors);
this.updateState({ errors: [] });
} else if (this.hasUpdates) {
this.quickOpenService.show(`ext update `);
} else {
this.quickOpenService.show(`>${commandCategory}: `);
}
}
private showErrors(errors: IMessage[]): void {
const promise = onUnexpectedPromiseError(this.extensionsService.getInstalled());
promise.done(installed => {
errors.forEach(m => {
const extension = installed.filter(ext => ext.path === m.source).pop();
const actions = [CloseAction];
const name = (extension && extension.name) || m.source;
const message = `${ name }: ${ m.message }`;
if (extension) {
const actionLabel = nls.localize('uninstall', "Uninstall");
actions.push(new Action('extensions.uninstall2', actionLabel, null, true, () => this.instantiationService.createInstance(UninstallAction).run(extension)));
}
this.messageService.show(m.type, { message, actions });
});
this.render(this.container);
});
}
public render(container: HTMLElement): lifecycle.IDisposable {
this.container = container;
if (this.messageCount > 0) {
this.domNode = dom.append(container, $('a.extensions-statusbar'));
const issueLabel = this.messageCount > 1 ? nls.localize('issues', "issues") : nls.localize('issue', "issue");
const extensionLabel = nls.localize('extension', "extension");
this.domNode.title = `${ this.messageCount } ${ extensionLabel } ${ issueLabel }`;
this.domNode.textContent = `${ this.messageCount } ${ issueLabel }`;
this.toDispose.push(dom.addDisposableListener(this.domNode, 'click', () => {
this.extensionsService.getInstalled().done(installed => {
Object.keys(this.status).forEach(key => {
this.status[key].messages.forEach(m => {
if (m.type > Severity.Info) {
const extension = installed.filter(ext => ext.path === m.source).pop();
const actions = [CloseAction];
const name = (extension && extension.name) || m.source;
const message = `${ name }: ${ m.message }`;
if (extension) {
const actionLabel = nls.localize('uninstall', "Uninstall");
actions.push(new Action('extensions.uninstall2', actionLabel, null, true, () => this.instantiationService.createInstance(UninstallAction).run(extension)));
}
this.messageService.show(m.type, { message, actions });
}
});
});
}, onUnexpectedError);
}));
}
private onInstallExtension(manifest: IExtensionManifest): void {
const installing = [...this.state.installing, manifest];
this.updateState({ installing });
}
private onDidInstallExtension({ extension }: { extension: IExtension; }): void {
const installing = this.state.installing
.filter(e => !extensionEquals(extension, e));
this.updateState({ installing });
this.outdatedDelayer.trigger(() => this.checkOutdated(), 0);
}
return {
dispose: () => lifecycle.disposeAll(this.toDispose)
};
private onDidUninstallExtension(): void {
this.outdatedDelayer.trigger(() => this.checkOutdated(), 0);
}
private checkErrors(): void {
const promise = onUnexpectedPromiseError(this.extensionService.onReady());
promise.done(() => {
const status = this.extensionService.getExtensionsStatus();
const errors = Object.keys(status)
.map(k => status[k].messages)
.reduce((r, m) => r.concat(m), [])
.filter(m => m.type > Severity.Info);
this.updateState({ errors });
});
}
private checkOutdated(): TPromise<void> {
return this.instantiationService.invokeFunction(getOutdatedExtensions)
.then(null, _ => []) // ignore errors
.then(outdated => {
this.updateState({ outdated });
// repeat this later
this.outdatedDelayer.trigger(() => this.checkOutdated());
});
}
}
\ No newline at end of file
......@@ -8,7 +8,7 @@ import errors = require('vs/base/common/errors');
import platform = require('vs/platform/platform');
import { Promise } from 'vs/base/common/winjs.base';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IExtensionsService, IGalleryService, IExtensionTipsService } from 'vs/workbench/parts/extensions/common/extensions';
import { IExtensionsService, IGalleryService, IExtensionTipsService, commandCategory } from 'vs/workbench/parts/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IMessageService } from 'vs/platform/message/common/message';
import Severity from 'vs/base/common/severity';
......@@ -46,9 +46,8 @@ export class ExtensionsWorkbenchExtension implements IWorkbenchContribution {
// add service
instantiationService.addSingleton(IExtensionTipsService, this.instantiationService.createInstance(ExtensionTipsService));
const extensionsCategory = nls.localize('extensionsCategory', "Extensions");
const actionRegistry = (<wbaregistry.IWorkbenchActionRegistry> platform.Registry.as(wbaregistry.Extensions.WorkbenchActions));
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ListExtensionsAction, ListExtensionsAction.ID, ListExtensionsAction.LABEL), extensionsCategory);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ListExtensionsAction, ListExtensionsAction.ID, ListExtensionsAction.LABEL), commandCategory);
(<IQuickOpenRegistry>platform.Registry.as(Extensions.Quickopen)).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
......@@ -62,7 +61,7 @@ export class ExtensionsWorkbenchExtension implements IWorkbenchContribution {
if (galleryService.isEnabled()) {
this.instantiationService.invokeFunction(checkForLegacyExtensionNeeds);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(InstallExtensionAction, InstallExtensionAction.ID, InstallExtensionAction.LABEL), extensionsCategory);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(InstallExtensionAction, InstallExtensionAction.ID, InstallExtensionAction.LABEL), commandCategory);
(<IQuickOpenRegistry>platform.Registry.as(Extensions.Quickopen)).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
......@@ -73,7 +72,7 @@ export class ExtensionsWorkbenchExtension implements IWorkbenchContribution {
)
);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ListOutdatedExtensionsAction, ListOutdatedExtensionsAction.ID, ListOutdatedExtensionsAction.LABEL), extensionsCategory);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ListOutdatedExtensionsAction, ListOutdatedExtensionsAction.ID, ListOutdatedExtensionsAction.LABEL), commandCategory);
(<IQuickOpenRegistry>platform.Registry.as(Extensions.Quickopen)).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
......@@ -85,7 +84,7 @@ export class ExtensionsWorkbenchExtension implements IWorkbenchContribution {
);
// add extension tips services
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ListSuggestedExtensionsAction, ListSuggestedExtensionsAction.ID, ListSuggestedExtensionsAction.LABEL), extensionsCategory);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ListSuggestedExtensionsAction, ListSuggestedExtensionsAction.ID, ListSuggestedExtensionsAction.LABEL), commandCategory);
(<IQuickOpenRegistry>platform.Registry.as(Extensions.Quickopen)).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
......
......@@ -135,23 +135,54 @@
/* Status bar */
.monaco-shell .extensions-statusbar {
padding: 0 5px 0 25px;
line-height: 22px;
background: url('extensions-status.svg') center center no-repeat;
background-size: 14px;
background-position: 4px 50%;
}
.monaco-shell .extensions-suggestions {
padding: 0 5px 0 5px;
-webkit-transition: visibility 250ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
transition: visibility 250ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.monaco-shell .extensions-suggestions > .octicon {
font-size: 14px;
position: relative;
padding: 0 5px;
width: 14px;
}
.monaco-shell .extensions-suggestions.disabled {
display: none;
.monaco-shell .extensions-statusbar > .icon {
background: url('extensions-status.svg') center center no-repeat;
background-size: 14px;
width: 14px;
height: 100%;
}
.monaco-shell .extensions-statusbar.has-updates::after {
content: '';
width: 6px;
height: 6px;
background-color: #39A71E;
position: absolute;
top: 2px;
right: 3px;
border-radius: 10px;
border: 1px solid white;
}
.monaco-shell .extensions-statusbar.has-errors::after {
content: '';
width: 6px;
height: 6px;
background-color: #CC6633;
position: absolute;
top: 2px;
right: 3px;
border-radius: 10px;
border: 1px solid white;
}
@keyframes shake {
from { transform: rotate(0deg); }
30% { transform: rotate(0deg); }
40% { transform: rotate(-20deg); }
48% { transform: rotate(10deg); }
52% { transform: rotate(-10deg); }
60% { transform: rotate(20deg); }
70% { transform: rotate(0deg); }
to { transform: rotate(0deg); }
}
.monaco-shell .extensions-statusbar.is-installing .icon {
animation: 2s ease-in-out infinite shake;
opacity: 0.7;
}
\ No newline at end of file
......@@ -92,10 +92,10 @@ export class ExtensionsService implements IExtensionsService {
private obsoleteFileLimiter: Limiter<void>;
private _onInstallExtension = new Emitter<IExtensionManifest>();
@ServiceEvent onInstallExtension: Event<IExtension> = this._onInstallExtension.event;
@ServiceEvent onInstallExtension: Event<IExtensionManifest> = this._onInstallExtension.event;
private _onDidInstallExtension = new Emitter<IExtension>();
@ServiceEvent onDidInstallExtension: Event<IExtension> = this._onDidInstallExtension.event;
private _onDidInstallExtension = new Emitter<{ extension: IExtension; error?: Error; }>();
@ServiceEvent onDidInstallExtension: Event<{ extension: IExtension; error?: Error; }> = this._onDidInstallExtension.event;
private _onUninstallExtension = new Emitter<IExtension>();
@ServiceEvent onUninstallExtension: Event<IExtension> = this._onUninstallExtension.event;
......@@ -129,6 +129,8 @@ export class ExtensionsService implements IExtensionsService {
return TPromise.wrapError(new Error(nls.localize('missingGalleryInformation', "Gallery information is missing")));
}
this._onInstallExtension.fire(extension);
return this.getLastValidExtensionVersion(extension, extension.galleryInformation.versions).then(versionInfo => {
const version = versionInfo.version;
const url = versionInfo.downloadUrl;
......@@ -139,13 +141,11 @@ export class ExtensionsService implements IExtensionsService {
return this.request(url)
.then(opts => download(zipPath, opts))
.then(() => validate(zipPath, extension, version))
.then(manifest => { this._onInstallExtension.fire(manifest); return manifest; })
.then(manifest => extract(zipPath, extensionPath, { sourcePath: 'extension', overwrite: true }).then(() => manifest))
.then(manifest => {
manifest = assign({ __metadata: galleryInformation }, manifest);
return pfs.writeFile(manifestPath, JSON.stringify(manifest, null, '\t'));
})
.then(() => { this._onDidInstallExtension.fire(extension); return extension; });
.then(manifest => assign({ __metadata: galleryInformation }, manifest))
.then(manifest => pfs.writeFile(manifestPath, JSON.stringify(manifest, null, '\t')))
.then(() => { this._onDidInstallExtension.fire({ extension }); return extension; })
.then(null, error => { this._onDidInstallExtension.fire({ extension, error }); return TPromise.wrapError(error); });
});
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册