提交 699c236b 编写于 作者: J Joao Moreno

Merge branch 'extension-viewlet'

......@@ -6,6 +6,10 @@
"win32MutexName": "vscodeoss",
"licenseUrl": "https://github.com/Microsoft/vscode/blob/master/LICENSE.txt",
"darwinBundleIdentifier": "com.visualstudio.code.oss",
"welcomePage": "https://go.microsoft.com/fwlink/?LinkId=723048",
"reportIssueUrl": "https://github.com/Microsoft/vscode/issues/new"
,"extensionsGallery": {
"serviceUrl": "https://marketplace.visualstudio.com/_apis/public/gallery",
"cacheUrl": "https://vscode.blob.core.windows.net/gallery/index",
"itemUrl": "https://marketplace.visualstudio.com/items"
}
}
\ No newline at end of file
......@@ -90,16 +90,18 @@ if (!!process.send && process.env.PIPE_LOGGING === 'true') {
console.error = function () { safeSend({ type: '__$console', severity: 'error', arguments: safeStringify(arguments) }); };
}
// Let stdout, stderr and stdin be no-op streams. This prevents an issue where we would get an EBADF
// error when we are inside a forked process and this process tries to access those channels.
var stream = require('stream');
var writable = new stream.Writable({
write: function () { /* No OP */ }
});
process.__defineGetter__('stdout', function() { return writable; });
process.__defineGetter__('stderr', function() { return writable; });
process.__defineGetter__('stdin', function() { return writable; });
if (!process.env['VSCODE_ALLOW_IO']) {
// Let stdout, stderr and stdin be no-op streams. This prevents an issue where we would get an EBADF
// error when we are inside a forked process and this process tries to access those channels.
var stream = require('stream');
var writable = new stream.Writable({
write: function () { /* No OP */ }
});
process.__defineGetter__('stdout', function() { return writable; });
process.__defineGetter__('stderr', function() { return writable; });
process.__defineGetter__('stdin', function() { return writable; });
}
// Handle uncaught exceptions
process.on('uncaughtException', function (err) {
......
......@@ -922,7 +922,7 @@ export function append<T extends Node>(parent: HTMLElement, child: T): T {
const SELECTOR_REGEX = /([\w\-]+)?(#([\w\-]+))?((.([\w\-]+))*)/;
// Similar to builder, but much more lightweight
export function emmet(description: string): HTMLElement {
export function emmet<T extends HTMLElement>(description: string): T {
let match = SELECTOR_REGEX.exec(description);
if (!match) {
......@@ -930,6 +930,7 @@ export function emmet(description: string): HTMLElement {
}
let result = document.createElement(match[1] || 'div');
if (match[3]) {
result.id = match[3];
}
......@@ -937,7 +938,7 @@ export function emmet(description: string): HTMLElement {
result.className = match[4].replace(/\./g, ' ').trim();
}
return result;
return result as T;
}
export function show(...elements: HTMLElement[]): void {
......@@ -985,4 +986,12 @@ export function removeTabIndexAndUpdateFocus(node: HTMLElement): void {
export function getElementsByTagName(tag: string): HTMLElement[] {
return Array.prototype.slice.call(document.getElementsByTagName(tag), 0);
}
export function finalHandler<T extends Event>(fn: (event: T)=>any): (event: T)=>any {
return e => {
e.preventDefault();
e.stopPropagation();
fn(e);
};
}
\ 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.
*--------------------------------------------------------------------------------------------*/
'use strict';
import _Event, { Emitter } from 'vs/base/common/event';
export interface IDomEvent {
(element: HTMLElement, type: "MSContentZoom", useCapture?: boolean): _Event<UIEvent>;
(element: HTMLElement, type: "MSGestureChange", useCapture?: boolean): _Event<MSGestureEvent>;
(element: HTMLElement, type: "MSGestureDoubleTap", useCapture?: boolean): _Event<MSGestureEvent>;
(element: HTMLElement, type: "MSGestureEnd", useCapture?: boolean): _Event<MSGestureEvent>;
(element: HTMLElement, type: "MSGestureHold", useCapture?: boolean): _Event<MSGestureEvent>;
(element: HTMLElement, type: "MSGestureStart", useCapture?: boolean): _Event<MSGestureEvent>;
(element: HTMLElement, type: "MSGestureTap", useCapture?: boolean): _Event<MSGestureEvent>;
(element: HTMLElement, type: "MSGotPointerCapture", useCapture?: boolean): _Event<MSPointerEvent>;
(element: HTMLElement, type: "MSInertiaStart", useCapture?: boolean): _Event<MSGestureEvent>;
(element: HTMLElement, type: "MSLostPointerCapture", useCapture?: boolean): _Event<MSPointerEvent>;
(element: HTMLElement, type: "MSManipulationStateChanged", useCapture?: boolean): _Event<MSManipulationEvent>;
(element: HTMLElement, type: "MSPointerCancel", useCapture?: boolean): _Event<MSPointerEvent>;
(element: HTMLElement, type: "MSPointerDown", useCapture?: boolean): _Event<MSPointerEvent>;
(element: HTMLElement, type: "MSPointerEnter", useCapture?: boolean): _Event<MSPointerEvent>;
(element: HTMLElement, type: "MSPointerLeave", useCapture?: boolean): _Event<MSPointerEvent>;
(element: HTMLElement, type: "MSPointerMove", useCapture?: boolean): _Event<MSPointerEvent>;
(element: HTMLElement, type: "MSPointerOut", useCapture?: boolean): _Event<MSPointerEvent>;
(element: HTMLElement, type: "MSPointerOver", useCapture?: boolean): _Event<MSPointerEvent>;
(element: HTMLElement, type: "MSPointerUp", useCapture?: boolean): _Event<MSPointerEvent>;
(element: HTMLElement, type: "abort", useCapture?: boolean): _Event<UIEvent>;
(element: HTMLElement, type: "activate", useCapture?: boolean): _Event<UIEvent>;
(element: HTMLElement, type: "ariarequest", useCapture?: boolean): _Event<AriaRequestEvent>;
(element: HTMLElement, type: "beforeactivate", useCapture?: boolean): _Event<UIEvent>;
(element: HTMLElement, type: "beforecopy", useCapture?: boolean): _Event<DragEvent>;
(element: HTMLElement, type: "beforecut", useCapture?: boolean): _Event<DragEvent>;
(element: HTMLElement, type: "beforedeactivate", useCapture?: boolean): _Event<UIEvent>;
(element: HTMLElement, type: "beforepaste", useCapture?: boolean): _Event<DragEvent>;
(element: HTMLElement, type: "blur", useCapture?: boolean): _Event<FocusEvent>;
(element: HTMLElement, type: "canplay", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "canplaythrough", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "change", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "click", useCapture?: boolean): _Event<MouseEvent>;
(element: HTMLElement, type: "command", useCapture?: boolean): _Event<CommandEvent>;
(element: HTMLElement, type: "contextmenu", useCapture?: boolean): _Event<PointerEvent>;
(element: HTMLElement, type: "copy", useCapture?: boolean): _Event<DragEvent>;
(element: HTMLElement, type: "cuechange", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "cut", useCapture?: boolean): _Event<DragEvent>;
(element: HTMLElement, type: "dblclick", useCapture?: boolean): _Event<MouseEvent>;
(element: HTMLElement, type: "deactivate", useCapture?: boolean): _Event<UIEvent>;
(element: HTMLElement, type: "drag", useCapture?: boolean): _Event<DragEvent>;
(element: HTMLElement, type: "dragend", useCapture?: boolean): _Event<DragEvent>;
(element: HTMLElement, type: "dragenter", useCapture?: boolean): _Event<DragEvent>;
(element: HTMLElement, type: "dragleave", useCapture?: boolean): _Event<DragEvent>;
(element: HTMLElement, type: "dragover", useCapture?: boolean): _Event<DragEvent>;
(element: HTMLElement, type: "dragstart", useCapture?: boolean): _Event<DragEvent>;
(element: HTMLElement, type: "drop", useCapture?: boolean): _Event<DragEvent>;
(element: HTMLElement, type: "durationchange", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "emptied", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "ended", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "error", useCapture?: boolean): _Event<ErrorEvent>;
(element: HTMLElement, type: "focus", useCapture?: boolean): _Event<FocusEvent>;
(element: HTMLElement, type: "gotpointercapture", useCapture?: boolean): _Event<PointerEvent>;
(element: HTMLElement, type: "input", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "keydown", useCapture?: boolean): _Event<KeyboardEvent>;
(element: HTMLElement, type: "keypress", useCapture?: boolean): _Event<KeyboardEvent>;
(element: HTMLElement, type: "keyup", useCapture?: boolean): _Event<KeyboardEvent>;
(element: HTMLElement, type: "load", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "loadeddata", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "loadedmetadata", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "loadstart", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "lostpointercapture", useCapture?: boolean): _Event<PointerEvent>;
(element: HTMLElement, type: "mousedown", useCapture?: boolean): _Event<MouseEvent>;
(element: HTMLElement, type: "mouseenter", useCapture?: boolean): _Event<MouseEvent>;
(element: HTMLElement, type: "mouseleave", useCapture?: boolean): _Event<MouseEvent>;
(element: HTMLElement, type: "mousemove", useCapture?: boolean): _Event<MouseEvent>;
(element: HTMLElement, type: "mouseout", useCapture?: boolean): _Event<MouseEvent>;
(element: HTMLElement, type: "mouseover", useCapture?: boolean): _Event<MouseEvent>;
(element: HTMLElement, type: "mouseup", useCapture?: boolean): _Event<MouseEvent>;
(element: HTMLElement, type: "mousewheel", useCapture?: boolean): _Event<MouseWheelEvent>;
(element: HTMLElement, type: "paste", useCapture?: boolean): _Event<DragEvent>;
(element: HTMLElement, type: "pause", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "play", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "playing", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "pointercancel", useCapture?: boolean): _Event<PointerEvent>;
(element: HTMLElement, type: "pointerdown", useCapture?: boolean): _Event<PointerEvent>;
(element: HTMLElement, type: "pointerenter", useCapture?: boolean): _Event<PointerEvent>;
(element: HTMLElement, type: "pointerleave", useCapture?: boolean): _Event<PointerEvent>;
(element: HTMLElement, type: "pointermove", useCapture?: boolean): _Event<PointerEvent>;
(element: HTMLElement, type: "pointerout", useCapture?: boolean): _Event<PointerEvent>;
(element: HTMLElement, type: "pointerover", useCapture?: boolean): _Event<PointerEvent>;
(element: HTMLElement, type: "pointerup", useCapture?: boolean): _Event<PointerEvent>;
(element: HTMLElement, type: "progress", useCapture?: boolean): _Event<ProgressEvent>;
(element: HTMLElement, type: "ratechange", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "reset", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "scroll", useCapture?: boolean): _Event<UIEvent>;
(element: HTMLElement, type: "seeked", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "seeking", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "select", useCapture?: boolean): _Event<UIEvent>;
(element: HTMLElement, type: "selectstart", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "stalled", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "submit", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "suspend", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "timeupdate", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "touchcancel", useCapture?: boolean): _Event<TouchEvent>;
(element: HTMLElement, type: "touchend", useCapture?: boolean): _Event<TouchEvent>;
(element: HTMLElement, type: "touchmove", useCapture?: boolean): _Event<TouchEvent>;
(element: HTMLElement, type: "touchstart", useCapture?: boolean): _Event<TouchEvent>;
(element: HTMLElement, type: "volumechange", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "waiting", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "webkitfullscreenchange", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "webkitfullscreenerror", useCapture?: boolean): _Event<Event>;
(element: HTMLElement, type: "wheel", useCapture?: boolean): _Event<WheelEvent>;
(element: HTMLElement, type: string, useCapture?: boolean): _Event<any>;
}
export const domEvent: IDomEvent = (element: HTMLElement, type: string, useCapture?) => {
const fn = e => emitter.fire(e);
const emitter = new Emitter<any>({
onFirstListenerAdd: () => {
element.addEventListener(type, fn);
},
onLastListenerRemove: () => {
element.removeEventListener(type, fn);
}
});
return emitter.event;
};
......@@ -36,7 +36,7 @@
cursor: default;
}
.monaco-action-bar .action-item.active {
.monaco-action-bar.animated .action-item.active {
-ms-transform: translate(0, -3px);
-webkit-transform: translate(0, -3px);
-moz-transform: translate(0, -3px);
......@@ -77,7 +77,7 @@
margin-right: .8em;
}
.monaco-action-bar.vertical .action-item.active {
.monaco-action-bar.animated.vertical .action-item.active {
-ms-transform: translate(5px, 0);
-webkit-transform: translate(5px, 0);
-moz-transform: translate(5px, 0);
......
......@@ -137,7 +137,7 @@ export class BaseActionItem extends EventEmitter implements IActionItem {
public onClick(event: Event): void {
DOM.EventHelper.stop(event, true);
let context: any;
if (types.isUndefinedOrNull(this._context)) {
context = event;
......@@ -388,6 +388,7 @@ export interface IActionBarOptions {
actionItemProvider?: IActionItemProvider;
actionRunner?: IActionRunner;
ariaLabel?: string;
animated?: boolean;
}
let defaultOptions: IActionBarOptions = {
......@@ -440,6 +441,10 @@ export class ActionBar extends EventEmitter implements IActionRunner {
this.domNode = document.createElement('div');
this.domNode.className = 'monaco-action-bar';
if (options.animated !== false) {
DOM.addClass(this.domNode, 'animated');
}
let isVertical = this.options.orientation === ActionsOrientation.VERTICAL;
if (isVertical) {
this.domNode.className += ' vertical';
......
......@@ -47,12 +47,12 @@
.vs-dark .monaco-list-row:hover { background-color: rgba(255, 255, 255, 0.08); }
.hc-black .monaco-list-row:hover { outline: 1px dashed #f38518; background: transparent; }
/* Selection */
.monaco-list-row.selected { background-color: #4FA7FF; color: white; }
.vs-dark .monaco-list-row.selected { background-color: #0E639C; color: white; }
.hc-black .monaco-list-row.selected { border: 1px solid #f38518; }
/* Focus */
.monaco-list-row.focused { background-color: #DCEBFC; }
.vs-dark .monaco-list-row.focused { background-color: #073655; }
.hc-black .monaco-list-row.focused { outline: 1px dotted #f38518; background: transparent }
/* Selection */
.monaco-list-row.selected { background-color: #4FA7FF; color: white; }
.vs-dark .monaco-list-row.selected { background-color: #0E639C; color: white; }
.hc-black .monaco-list-row.selected { border: 1px solid #f38518; }
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./list';
import { IDisposable } from 'vs/base/common/lifecycle';
import { range } from 'vs/base/common/arrays';
import { IDelegate, IRenderer, IFocusChangeEvent, ISelectionChangeEvent } from './list';
import { List } from './listWidget';
import { PagedModel } from 'vs/base/common/paging';
import Event, { mapEvent } from 'vs/base/common/event';
export interface IPagedRenderer<TElement, TTemplateData> extends IRenderer<TElement, TTemplateData> {
renderPlaceholder(index: number, templateData: TTemplateData): void;
}
export interface ITemplateData<T> {
data: T;
disposable: IDisposable;
}
class PagedRenderer<TElement, TTemplateData> implements IRenderer<number, ITemplateData<TTemplateData>> {
get templateId(): string { return this.renderer.templateId; }
constructor(
private renderer: IPagedRenderer<TElement, TTemplateData>,
private modelProvider: () => PagedModel<TElement>
) {}
renderTemplate(container: HTMLElement): ITemplateData<TTemplateData> {
const data = this.renderer.renderTemplate(container);
return { data, disposable: { dispose: () => {} } };
}
renderElement(index: number, _: number, data: ITemplateData<TTemplateData>): void {
data.disposable.dispose();
const model = this.modelProvider();
if (model.isResolved(index)) {
return this.renderer.renderElement(model.get(index), index, data.data);
}
const promise = model.resolve(index);
data.disposable = { dispose: () => promise.cancel() };
this.renderer.renderPlaceholder(index, data.data);
promise.done(entry => this.renderer.renderElement(entry, index, data.data));
}
disposeTemplate(data: ITemplateData<TTemplateData>): void {
data.disposable.dispose();
data.disposable = null;
this.renderer.disposeTemplate(data.data);
data.data = null;
}
}
export class PagedList<T> {
private list: List<number>;
private _model: PagedModel<T>;
get onDOMFocus(): Event<FocusEvent> { return this.list.onDOMFocus; }
constructor(
container: HTMLElement,
delegate: IDelegate<number>,
renderers: IPagedRenderer<T, any>[]
) {
const pagedRenderers = renderers.map(r => new PagedRenderer<T, ITemplateData<T>>(r, () => this.model));
this.list = new List(container, delegate, pagedRenderers);
}
get onFocusChange(): Event<IFocusChangeEvent<T>> {
return mapEvent(this.list.onFocusChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
}
get onSelectionChange(): Event<ISelectionChangeEvent<T>> {
return mapEvent(this.list.onSelectionChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
}
get model(): PagedModel<T> {
return this._model;
}
set model(model: PagedModel<T>) {
this._model = model;
this.list.splice(0, this.list.length, ...range(model.length));
}
get scrollTop(): number {
return this.list.scrollTop;
}
focusNext(n?: number, loop?: boolean): void {
this.list.focusNext(n, loop);
}
focusPrevious(n?: number, loop?: boolean): void {
this.list.focusPrevious(n, loop);
}
selectNext(n?: number, loop?: boolean): void {
this.list.selectNext(n, loop);
}
selectPrevious(n?: number, loop?: boolean): void {
this.list.selectPrevious(n, loop);
}
getFocus(): number[] {
return this.list.getFocus();
}
setSelection(...indexes: number[]): void {
this.list.setSelection(...indexes);
}
layout(height?: number): void {
this.list.layout(height);
}
}
\ No newline at end of file
......@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { toObject, assign } from 'vs/base/common/objects';
import { toObject, assign, getOrDefault } from 'vs/base/common/objects';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Gesture } from 'vs/base/browser/touch';
import * as DOM from 'vs/base/browser/dom';
......@@ -38,6 +38,14 @@ const MouseEventTypes = [
'contextmenu'
];
export interface IListViewOptions {
useShadows?: boolean;
}
const DefaultOptions: IListViewOptions = {
useShadows: true
};
export class ListView<T> implements IDisposable {
private items: IItem<T>[];
......@@ -56,7 +64,8 @@ export class ListView<T> implements IDisposable {
constructor(
container: HTMLElement,
private delegate: IDelegate<T>,
renderers: IRenderer<T, any>[]
renderers: IRenderer<T, any>[],
options: IListViewOptions = DefaultOptions
) {
this.items = [];
this.itemId = 0;
......@@ -69,7 +78,6 @@ export class ListView<T> implements IDisposable {
this._domNode = document.createElement('div');
this._domNode.className = 'monaco-list';
this._domNode.tabIndex = 0;
this.rowsContainer = document.createElement('div');
this.rowsContainer.className = 'monaco-list-rows';
......@@ -79,7 +87,7 @@ export class ListView<T> implements IDisposable {
canUseTranslate3d: false,
horizontal: ScrollbarVisibility.Hidden,
vertical: ScrollbarVisibility.Auto,
useShadows: false,
useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows),
saveLastScrollTimeOnClassName: 'monaco-list-row'
});
......
......@@ -8,8 +8,9 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { isNumber } from 'vs/base/common/types';
import * as DOM from 'vs/base/browser/dom';
import Event, { Emitter, mapEvent, EventBufferer } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { IDelegate, IRenderer, IListMouseEvent, IFocusChangeEvent, ISelectionChangeEvent } from './list';
import { ListView } from './listView';
import { ListView, IListViewOptions } from './listView';
interface ITraitTemplateData<D> {
container: HTMLElement;
......@@ -138,6 +139,8 @@ class Controller<T> implements IDisposable {
private onClick(e: IListMouseEvent<T>) {
e.preventDefault();
e.stopPropagation();
this.view.domNode.focus();
this.list.setFocus(e.index);
this.list.setSelection(e.index);
}
......@@ -146,6 +149,11 @@ class Controller<T> implements IDisposable {
}
}
export interface IListOptions extends IListViewOptions {
}
const DefaultOptions: IListOptions = {};
export class List<T> implements IDisposable {
private static InstanceCount = 0;
......@@ -165,10 +173,14 @@ export class List<T> implements IDisposable {
return this.eventBufferer.wrapEvent(mapEvent(this.selection.onChange, e => this.toListEvent(e)));
}
private _onDOMFocus: Event<FocusEvent>;
get onDOMFocus(): Event<FocusEvent> { return this._onDOMFocus; }
constructor(
container: HTMLElement,
delegate: IDelegate<T>,
renderers: IRenderer<T, any>[]
renderers: IRenderer<T, any>[],
options: IListOptions = DefaultOptions
) {
this.focus = new FocusTrait(i => this.getElementId(i));
this.selection = new Trait('selected');
......@@ -180,9 +192,12 @@ export class List<T> implements IDisposable {
return r;
});
this.view = new ListView(container, delegate, renderers);
this.view = new ListView(container, delegate, renderers, options);
this.view.domNode.setAttribute('role', 'listbox');
this.view.domNode.tabIndex = 0;
this.controller = new Controller(this, this.view);
this._onDOMFocus = domEvent(this.view.domNode, 'focus');
}
splice(start: number, deleteCount: number, ...elements: T[]): void {
......@@ -201,6 +216,10 @@ export class List<T> implements IDisposable {
return this.view.getContentHeight();
}
get scrollTop(): number {
return this.view.getScrollTop();
}
layout(height?: number): void {
this.view.layout(height);
}
......@@ -229,6 +248,10 @@ export class List<T> implements IDisposable {
this.setSelection(Math.max(index, 0));
}
getSelection(): number[] {
return this.selection.get();
}
setFocus(...indexes: number[]): void {
this.eventBufferer.bufferEvents(() => {
indexes = indexes.concat(this.focus.set(...indexes));
......@@ -255,7 +278,7 @@ export class List<T> implements IDisposable {
let lastPageIndex = this.view.indexAt(this.view.getScrollTop() + this.view.renderHeight);
lastPageIndex = lastPageIndex === 0 ? 0 : lastPageIndex - 1;
const lastPageElement = this.view.element(lastPageIndex);
const currentlyFocusedElement = this.getFocus()[0];
const currentlyFocusedElement = this.getFocusedElements()[0];
if (currentlyFocusedElement !== lastPageElement) {
this.setFocus(lastPageIndex);
......@@ -281,7 +304,7 @@ export class List<T> implements IDisposable {
}
const firstPageElement = this.view.element(firstPageIndex);
const currentlyFocusedElement = this.getFocus()[0];
const currentlyFocusedElement = this.getFocusedElements()[0];
if (currentlyFocusedElement !== firstPageElement) {
this.setFocus(firstPageIndex);
......@@ -296,8 +319,12 @@ export class List<T> implements IDisposable {
}
}
getFocus(): T[] {
return this.focus.get().map(i => this.view.element(i));
getFocus(): number[] {
return this.focus.get();
}
getFocusedElements(): T[] {
return this.getFocus().map(i => this.view.element(i));
}
reveal(index: number, relativeTop?: number): void {
......
......@@ -216,10 +216,26 @@ export function flatten<T>(arr: T[][]): T[] {
return arr.reduce((r, v) => r.concat(v), []);
}
export function range(to: number, from = 0): number[] {
const result = [];
for (let i = from; i < to; i++) {
result.push(i);
}
return result;
}
export function fill<T>(num: number, valueFn: () => T, arr: T[] = []): T[] {
for (let i = 0; i < num; i++) {
arr[i] = valueFn();
}
return arr;
}
export function index<T>(array: T[], indexer: (t: T) => string): { [key: string]: T; } {
const result = Object.create(null);
array.forEach(t => result[indexer(t)] = t);
return result;
}
\ No newline at end of file
......@@ -152,8 +152,11 @@ export function fromEventEmitter<T>(emitter: EventEmitter, eventType: string): E
}
export function mapEvent<I,O>(event: Event<I>, map: (i:I)=>O): Event<O> {
return (listener, thisArgs?, disposables?) =>
event(i => listener(map(i)), thisArgs, disposables);
return (listener, thisArgs?, disposables?) => event(i => listener(map(i)), thisArgs, disposables);
}
export function filterEvent<T>(event: Event<T>, filter: (e:T)=>boolean): Event<T> {
return (listener, thisArgs?, disposables?) => event(e => filter(e) && listener(e), thisArgs, disposables);
}
enum EventDelayerState {
......
......@@ -8,6 +8,9 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { ArraySet } from 'vs/base/common/set';
/**
* A Pager is a stateless abstraction over a paged collection.
*/
export interface IPager<T> {
firstPage: T[];
total: number;
......@@ -22,7 +25,17 @@ interface IPage<T> {
elements: T[];
}
export class PagedModel<T> {
/**
* A PagedModel is a stateful model over an abstracted paged collection.
*/
export interface IPagedModel<T> {
length: number;
isResolved(index: number): boolean;
get(index: number): T;
resolve(index: number): TPromise<T>;
}
export class PagedModel<T> implements IPagedModel<T> {
private pages: IPage<T>[] = [];
......@@ -92,6 +105,21 @@ export class PagedModel<T> {
}
}
export class SinglePagePagedModel<T> extends PagedModel<T> {
constructor(elements: T[]) {
super({
firstPage: elements,
total: elements.length,
pageSize: elements.length,
getPage: null
});
}
}
/**
* Similar to array.map, `mapPager` lets you map the elements of an
* abstract paged collection to another type.
*/
export function mapPager<T,R>(pager: IPager<T>, fn: (t: T) => R): IPager<R> {
return {
firstPage: pager.firstPage.map(fn),
......
......@@ -86,6 +86,23 @@ export function download(filePath: string, opts: IRequestOptions): TPromise<void
}));
}
export function text(opts: IRequestOptions): TPromise<string> {
return request(opts).then(pair => new Promise((c, e) => {
if (!((pair.res.statusCode >= 200 && pair.res.statusCode < 300) || pair.res.statusCode === 1223)) {
return e('Server returned ' + pair.res.statusCode);
}
if (pair.res.statusCode === 204) {
return c(null);
}
let buffer: string[] = [];
pair.res.on('data', d => buffer.push(d));
pair.res.on('end', () => c(buffer.join('')));
pair.res.on('error', e);
}));
}
export function json<T>(opts: IRequestOptions): TPromise<T> {
return request(opts).then(pair => new Promise((c, e) => {
if (!((pair.res.statusCode >= 200 && pair.res.statusCode < 300) || pair.res.statusCode === 1223)) {
......
......@@ -109,7 +109,7 @@ function main(accessor: ServicesAccessor, ipcServer: Server, userEnv: IProcessEn
process.env['VSCODE_SHARED_IPC_HOOK'] = envService.sharedIPCHandle;
// Spawn shared process
const sharedProcess = instantiationService.invokeFunction(spawnSharedProcess);
const sharedProcess = spawnSharedProcess(!envService.isBuilt || envService.cliArgs.verboseLogging);
// Make sure we associate the program with the app user model id
// This will help Windows to associate the running program with
......
......@@ -7,15 +7,18 @@ import * as cp from 'child_process';
import URI from 'vs/base/common/uri';
import { IDisposable } from 'vs/base/common/lifecycle';
import { assign } from 'vs/base/common/objects';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
const boostrapPath = URI.parse(require.toUrl('bootstrap')).fsPath;
function _spawnSharedProcess(): cp.ChildProcess {
function _spawnSharedProcess(allowOutput: boolean): cp.ChildProcess {
const env = assign({}, process.env, {
AMD_ENTRYPOINT: 'vs/code/node/sharedProcessMain'
});
if (allowOutput) {
env['VSCODE_ALLOW_IO'] = 'true';
}
const result = cp.fork(boostrapPath, ['--type=SharedProcess'], { env });
// handshake
......@@ -26,7 +29,7 @@ function _spawnSharedProcess(): cp.ChildProcess {
let spawnCount = 0;
export function spawnSharedProcess(accessor: ServicesAccessor): IDisposable {
export function spawnSharedProcess(allowOutput: boolean): IDisposable {
let child: cp.ChildProcess;
const spawn = () => {
......@@ -34,7 +37,7 @@ export function spawnSharedProcess(accessor: ServicesAccessor): IDisposable {
return;
}
child = _spawnSharedProcess();
child = _spawnSharedProcess(allowOutput);
child.on('exit', spawn);
};
......
......@@ -9,6 +9,7 @@ import pkg from 'vs/platform/package';
import { ParsedArgs } from 'vs/code/node/argv';
import { TPromise } from 'vs/base/common/winjs.base';
import { sequence } from 'vs/base/common/async';
import { IPager } from 'vs/base/common/paging';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
......@@ -17,8 +18,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IEventService } from 'vs/platform/event/common/event';
import { EventService } from 'vs/platform/event/common/eventService';
import { IExtensionManagementService, IExtensionGalleryService, IQueryResult } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
import { IExtensionManagementService, IExtensionGalleryService, IExtensionManifest, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { ITelemetryService, combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry';
......@@ -34,6 +34,10 @@ const notFound = id => localize('notFound', "Extension '{0}' not found.", id);
const notInstalled = id => localize('notInstalled', "Extension '{0}' is not installed.", id);
const useId = localize('useId', "Make sure you use the full extension ID, eg: {0}", 'ms-vscode.csharp');
function getId(manifest: IExtensionManifest): string {
return `${ manifest.publisher }.${ manifest.name }`;
}
class Main {
constructor(
......@@ -59,22 +63,22 @@ class Main {
private listExtensions(): TPromise<any> {
return this.extensionManagementService.getInstalled().then(extensions => {
extensions.forEach(e => console.log(getExtensionId(e)));
extensions.forEach(e => console.log(getId(e.manifest)));
});
}
private installExtension(ids: string[]): TPromise<any> {
return sequence(ids.map(id => () => {
return this.extensionManagementService.getInstalled().then(installed => {
const isInstalled = installed.some(e => getExtensionId(e) === id);
const isInstalled = installed.some(e => getId(e.manifest) === id);
if (isInstalled) {
console.log(localize('alreadyInstalled', "Extension '{0}' is already installed.", id));
return TPromise.as(null);
}
return this.extensionGalleryService.query({ ids: [id] })
.then<IQueryResult>(null, err => {
return this.extensionGalleryService.query({ names: [id] })
.then<IPager<IGalleryExtension>>(null, err => {
if (err.responseText) {
try {
const response = JSON.parse(err.responseText);
......@@ -94,8 +98,8 @@ class Main {
console.log(localize('foundExtension', "Found '{0}' in the marketplace.", id));
console.log(localize('installing', "Installing..."));
return this.extensionManagementService.install(extension).then(extension => {
console.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed!", id, extension.version));
return this.extensionManagementService.install(extension).then(() => {
console.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed!", id, extension.versions[0].version));
});
});
});
......@@ -105,7 +109,7 @@ class Main {
private uninstallExtension(ids: string[]): TPromise<any> {
return sequence(ids.map(id => () => {
return this.extensionManagementService.getInstalled().then(installed => {
const [extension] = installed.filter(e => getExtensionId(e) === id);
const [extension] = installed.filter(e => getId(e.manifest) === id);
if (!extension) {
return TPromise.wrapError(`${ notInstalled(id) }\n${ useId }`);
......
......@@ -148,7 +148,7 @@ class Delegate implements IDelegate<CompletionItem> {
constructor(private listProvider: () => List<CompletionItem>) { }
getHeight(element: CompletionItem): number {
const focus = this.listProvider().getFocus()[0];
const focus = this.listProvider().getFocusedElements()[0];
if (element.suggestion.documentationLabel && element === focus) {
return FocusHeight;
......@@ -369,7 +369,9 @@ export class SuggestWidget implements IContentWidget, IDisposable {
let renderer: IRenderer<CompletionItem, any> = instantiationService.createInstance(Renderer, this, this.editor);
this.delegate = new Delegate(() => this.list);
this.list = new List(this.listElement, this.delegate, [renderer]);
this.list = new List(this.listElement, this.delegate, [renderer], {
useShadows: false
});
this.toDispose = [
editor.onDidBlurEditorText(() => this.onEditorBlur()),
......@@ -738,7 +740,7 @@ export class SuggestWidget implements IContentWidget, IDisposable {
case State.Loading:
return !this.isAuto;
default:
const focus = this.list.getFocus()[0];
const focus = this.list.getFocusedElements()[0];
if (focus) {
this.list.setSelection(this.completionModel.items.indexOf(focus));
} else {
......@@ -759,7 +761,7 @@ export class SuggestWidget implements IContentWidget, IDisposable {
return;
}
const item = this.list.getFocus()[0];
const item = this.list.getFocusedElements()[0];
if (!item || !item.suggestion.documentationLabel) {
return;
......@@ -823,7 +825,7 @@ export class SuggestWidget implements IContentWidget, IDisposable {
} else if (this.state === State.Details) {
height = 12 * UnfocusedHeight;
} else {
const focus = this.list.getFocus()[0];
const focus = this.list.getFocusedElements()[0];
const focusHeight = focus ? this.delegate.getHeight(focus) : UnfocusedHeight;
height = focusHeight;
......@@ -842,7 +844,7 @@ export class SuggestWidget implements IContentWidget, IDisposable {
if (this.state !== State.Details) {
this.details.render(null);
} else {
this.details.render(this.list.getFocus()[0]);
this.details.render(this.list.getFocusedElements()[0]);
}
}
......
......@@ -8,6 +8,7 @@
import nls = require('vs/nls');
import { TPromise } from 'vs/base/common/winjs.base';
import Event from 'vs/base/common/event';
import { IPager } from 'vs/base/common/paging';
import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
export interface IExtensionManifest {
......@@ -18,70 +19,108 @@ export interface IExtensionManifest {
displayName?: string;
description?: string;
main?: string;
icon?: string;
}
export interface IGalleryVersion {
version: string;
date: string;
manifestUrl: string;
readmeUrl: string;
downloadUrl: string;
iconUrl: string;
downloadHeaders: { [key: string]: string; };
}
export interface IGalleryMetadata {
galleryApiUrl: string;
export interface IExtensionIdentity {
name: string;
publisher: string;
}
export interface IGalleryExtension {
id: string;
name: string;
displayName: string;
publisherId: string;
publisher: string;
publisherDisplayName: string;
description: string;
installCount: number;
rating: number;
ratingCount: number;
versions: IGalleryVersion[];
}
export interface IExtension extends IExtensionManifest {
galleryInformation?: IGalleryMetadata;
path?: string;
export interface IGalleryMetadata {
id: string;
publisherId: string;
publisherDisplayName: string;
}
export interface ILocalExtension {
id: string;
manifest: IExtensionManifest;
metadata: IGalleryMetadata;
path: string;
readmeUrl: string;
}
export const IExtensionManagementService = createDecorator<IExtensionManagementService>('extensionManagementService');
export const IExtensionGalleryService = createDecorator<IExtensionGalleryService>('extensionGalleryService');
export enum SortBy {
NoneOrRelevance = 0,
LastUpdatedDate = 1,
Title = 2,
PublisherName = 3,
InstallCount = 4,
PublishedDate = 5,
AverageRating = 6
}
export enum SortOrder {
Default = 0,
Ascending = 1,
Descending = 2
}
export interface IQueryOptions {
text?: string;
ids?: string[];
names?: string[];
pageSize?: number;
}
export interface IQueryResult {
firstPage: IExtension[];
total: number;
pageSize: number;
getPage(pageNumber: number): TPromise<IExtension[]>;
sortBy?: SortBy;
sortOrder?: SortOrder;
}
export interface IExtensionGalleryService {
serviceId: ServiceIdentifier<any>;
isEnabled(): boolean;
query(options?: IQueryOptions): TPromise<IQueryResult>;
query(options?: IQueryOptions): TPromise<IPager<IGalleryExtension>>;
}
export type InstallExtensionEvent = { id: string; gallery?: IGalleryExtension; };
export type DidInstallExtensionEvent = { id: string; local?: ILocalExtension; error?: Error; };
export interface IExtensionManagementService {
serviceId: ServiceIdentifier<any>;
onInstallExtension: Event<IExtensionManifest>;
onDidInstallExtension: Event<{ extension: IExtension; error?: Error; }>;
onUninstallExtension: Event<IExtension>;
onDidUninstallExtension: Event<IExtension>;
install(extension: IExtension): TPromise<IExtension>;
install(zipPath: string): TPromise<IExtension>;
uninstall(extension: IExtension): TPromise<void>;
getInstalled(includeDuplicateVersions?: boolean): TPromise<IExtension[]>;
onInstallExtension: Event<InstallExtensionEvent>;
onDidInstallExtension: Event<DidInstallExtensionEvent>;
onUninstallExtension: Event<string>;
onDidUninstallExtension: Event<string>;
install(extension: IGalleryExtension): TPromise<void>;
install(zipPath: string): TPromise<void>;
uninstall(extension: ILocalExtension): TPromise<void>;
getInstalled(includeDuplicateVersions?: boolean): TPromise<ILocalExtension[]>;
}
export const IExtensionTipsService = createDecorator<IExtensionTipsService>('extensionTipsService');
export interface IExtensionTipsService {
serviceId: ServiceIdentifier<IExtensionTipsService>;
getRecommendations(): TPromise<IExtension[]>;
getRecommendations(): TPromise<ILocalExtension[]>;
}
export const ExtensionsLabel = nls.localize('extensions', "Extensions");
......
......@@ -7,7 +7,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionManagementService, IExtension, IExtensionManifest } from './extensionManagement';
import { IExtensionManagementService, ILocalExtension, IGalleryExtension, InstallExtensionEvent, DidInstallExtensionEvent } from './extensionManagement';
import Event from 'vs/base/common/event';
export interface IExtensionManagementChannel extends IChannel {
......@@ -15,9 +15,9 @@ export interface IExtensionManagementChannel extends IChannel {
call(command: 'event:onDidInstallExtension'): TPromise<void>;
call(command: 'event:onUninstallExtension'): TPromise<void>;
call(command: 'event:onDidUninstallExtension'): TPromise<void>;
call(command: 'install', extensionOrPath: IExtension | string): TPromise<IExtension>;
call(command: 'uninstall', extension: IExtension): TPromise<void>;
call(command: 'getInstalled', includeDuplicateVersions: boolean): TPromise<IExtension[]>;
call(command: 'install', extensionOrPath: ILocalExtension | string): TPromise<ILocalExtension>;
call(command: 'uninstall', extension: ILocalExtension): TPromise<void>;
call(command: 'getInstalled', includeDuplicateVersions: boolean): TPromise<ILocalExtension[]>;
call(command: string, arg: any): TPromise<any>;
}
......@@ -44,29 +44,29 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
constructor(private channel: IExtensionManagementChannel) { }
private _onInstallExtension = eventFromCall(this.channel, 'event:onInstallExtension');
get onInstallExtension(): Event<IExtensionManifest> { return this._onInstallExtension; }
private _onInstallExtension = eventFromCall<InstallExtensionEvent>(this.channel, 'event:onInstallExtension');
get onInstallExtension(): Event<InstallExtensionEvent> { return this._onInstallExtension; }
private _onDidInstallExtension = eventFromCall(this.channel, 'event:onDidInstallExtension');
get onDidInstallExtension(): Event<{ extension: IExtension; error?: Error; }> { return this._onDidInstallExtension; }
private _onDidInstallExtension = eventFromCall<DidInstallExtensionEvent>(this.channel, 'event:onDidInstallExtension');
get onDidInstallExtension(): Event<DidInstallExtensionEvent> { return this._onDidInstallExtension; }
private _onUninstallExtension = eventFromCall(this.channel, 'event:onUninstallExtension');
get onUninstallExtension(): Event<IExtension> { return this._onUninstallExtension; }
private _onUninstallExtension = eventFromCall<string>(this.channel, 'event:onUninstallExtension');
get onUninstallExtension(): Event<string> { return this._onUninstallExtension; }
private _onDidUninstallExtension = eventFromCall(this.channel, 'event:onDidUninstallExtension');
get onDidUninstallExtension(): Event<IExtension> { return this._onDidUninstallExtension; }
private _onDidUninstallExtension = eventFromCall<string>(this.channel, 'event:onDidUninstallExtension');
get onDidUninstallExtension(): Event<string> { return this._onDidUninstallExtension; }
install(extension: IExtension): TPromise<IExtension>;
install(zipPath: string): TPromise<IExtension>;
install(arg: any): TPromise<IExtension> {
install(extension: IGalleryExtension): TPromise<void>;
install(zipPath: string): TPromise<void>;
install(arg: any): TPromise<void> {
return this.channel.call('install', arg);
}
uninstall(extension: IExtension): TPromise<void> {
uninstall(extension: ILocalExtension): TPromise<void> {
return this.channel.call('uninstall', extension);
}
getInstalled(includeDuplicateVersions?: boolean): TPromise<IExtension[]> {
getInstalled(includeDuplicateVersions?: boolean): TPromise<ILocalExtension[]> {
return this.channel.call('getInstalled', includeDuplicateVersions);
}
}
\ No newline at end of file
......@@ -4,37 +4,37 @@
*--------------------------------------------------------------------------------------------*/
import { TPromise } from 'vs/base/common/winjs.base';
import { IExtension, IExtensionGalleryService, IGalleryVersion, IQueryOptions, IQueryResult } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IGalleryExtension, IExtensionGalleryService, IGalleryVersion, IQueryOptions, SortBy, SortOrder } from 'vs/platform/extensionManagement/common/extensionManagement';
import { isUndefined } from 'vs/base/common/types';
import { assign, getOrDefault } from 'vs/base/common/objects';
import { IRequestService } from 'vs/platform/request/common/request';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IPager } from 'vs/base/common/paging';
import pkg from 'vs/platform/package';
import product from 'vs/platform/product';
export interface IGalleryExtensionFile {
interface IRawGalleryExtensionFile {
assetType: string;
}
export interface IGalleryExtensionVersion {
interface IRawGalleryExtensionVersion {
version: string;
lastUpdated: string;
assetUri: string;
files: IGalleryExtensionFile[];
files: IRawGalleryExtensionFile[];
}
export interface IGalleryExtension {
interface IRawGalleryExtension {
extensionId: string;
extensionName: string;
displayName: string;
shortDescription: string;
publisher: { displayName: string, publisherId: string, publisherName: string; };
versions: IGalleryExtensionVersion[];
galleryApiUrl: string;
statistics: IGalleryExtensionStatistics[];
versions: IRawGalleryExtensionVersion[];
statistics: IRawGalleryExtensionStatistics[];
}
export interface IGalleryExtensionStatistics {
interface IRawGalleryExtensionStatistics {
statisticName: string;
value: number;
}
......@@ -63,22 +63,6 @@ enum FilterType {
SearchText = 10
}
enum SortBy {
NoneOrRelevance = 0,
LastUpdatedDate = 1,
Title = 2,
PublisherName = 3,
InstallCount = 4,
PublishedDate = 5,
AverageRating = 6
}
enum SortOrder {
Default = 0,
Ascending = 1,
Descending = 2
}
interface ICriterium {
filterType: FilterType;
value?: string;
......@@ -130,8 +114,12 @@ class Query {
return new Query(assign({}, this.state, { criteria }));
}
withSort(sortBy: SortBy, sortOrder = SortOrder.Default): Query {
return new Query(assign({}, this.state, { sortBy, sortOrder }));
withSortBy(sortBy: SortBy): Query {
return new Query(assign({}, this.state, { sortBy }));
}
withSortOrder(sortOrder): Query {
return new Query(assign({}, this.state, { sortOrder }));
}
withFlags(...flags: Flags[]): Query {
......@@ -152,39 +140,34 @@ class Query {
}
}
function getInstallCount(statistics: IGalleryExtensionStatistics[]): number {
if (!statistics) {
return 0;
}
const result = statistics.filter(s => s.statisticName === 'install')[0];
function getStatistic(statistics: IRawGalleryExtensionStatistics[], name: string): number {
const result = (statistics || []).filter(s => s.statisticName === name)[0];
return result ? result.value : 0;
}
function toExtension(galleryExtension: IGalleryExtension, extensionsGalleryUrl: string, downloadHeaders: any): IExtension {
function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUrl: string, downloadHeaders: any): IGalleryExtension {
const versions = galleryExtension.versions.map<IGalleryVersion>(v => ({
version: v.version,
date: v.lastUpdated,
downloadHeaders,
downloadUrl: `${ v.assetUri }/Microsoft.VisualStudio.Services.VSIXPackage?install=true`,
manifestUrl: `${ v.assetUri }/Microsoft.VisualStudio.Code.Manifest`
manifestUrl: `${ v.assetUri }/Microsoft.VisualStudio.Code.Manifest`,
readmeUrl: `${ v.assetUri }/Microsoft.VisualStudio.Services.Content.Details`,
iconUrl: `${ v.assetUri }/Microsoft.VisualStudio.Services.Icons.Default`
}));
return {
id: galleryExtension.extensionId,
name: galleryExtension.extensionName,
displayName: galleryExtension.displayName || galleryExtension.extensionName,
displayName: galleryExtension.displayName,
publisherId: galleryExtension.publisher.publisherId,
publisher: galleryExtension.publisher.publisherName,
version: versions[0].version,
engines: { vscode: void 0 }, // TODO: ugly
publisherDisplayName: galleryExtension.publisher.displayName,
description: galleryExtension.shortDescription || '',
galleryInformation: {
galleryApiUrl: extensionsGalleryUrl,
id: galleryExtension.extensionId,
publisherId: galleryExtension.publisher.publisherId,
publisherDisplayName: galleryExtension.publisher.displayName,
installCount: getInstallCount(galleryExtension.statistics),
versions
}
installCount: getStatistic(galleryExtension.statistics, 'install'),
rating: getStatistic(galleryExtension.statistics, 'averagerating'),
ratingCount: getStatistic(galleryExtension.statistics, 'ratingcount'),
versions
};
}
......@@ -212,12 +195,12 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return !!this.extensionsGalleryUrl;
}
query(options: IQueryOptions = {}): TPromise<IQueryResult> {
query(options: IQueryOptions = {}): TPromise<IPager<IGalleryExtension>> {
if (!this.isEnabled()) {
return TPromise.wrapError(new Error('No extension gallery service configured.'));
}
const type = options.ids ? 'ids' : (options.text ? 'text' : 'all');
const type = options.names ? 'ids' : (options.text ? 'text' : 'all');
const text = options.text || '';
const pageSize = getOrDefault(options, o => o.pageSize, 50);
......@@ -229,14 +212,21 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code');
if (text) {
query = query.withFilter(FilterType.SearchText, text)
.withSort(SortBy.NoneOrRelevance);
query = query.withFilter(FilterType.SearchText, text).withSortBy(SortBy.NoneOrRelevance);
} else if (options.ids) {
options.ids.forEach(id => {
query = query.withFilter(FilterType.ExtensionName, id);
});
query = options.ids.reduce((query, id) => query.withFilter(FilterType.ExtensionId, id), query);
} else if (options.names) {
query = options.names.reduce((query, name) => query.withFilter(FilterType.ExtensionName, name), query);
} else {
query = query.withSort(SortBy.InstallCount);
query = query.withSortBy(SortBy.InstallCount);
}
if (typeof options.sortBy === 'number') {
query = query.withSortBy(options.sortBy);
}
if (typeof options.sortOrder === 'number') {
query = query.withSortOrder(options.sortOrder);
}
return this.queryGallery(query).then(({ galleryExtensions, total }) => {
......@@ -251,7 +241,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
});
}
private queryGallery(query: Query): TPromise<{ galleryExtensions: IGalleryExtension[], total: number; }> {
private queryGallery(query: Query): TPromise<{ galleryExtensions: IRawGalleryExtension[], total: number; }> {
const data = JSON.stringify(query.raw);
return this.getRequestHeaders()
......
......@@ -15,8 +15,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { flatten } from 'vs/base/common/arrays';
import { extract, buffer } from 'vs/base/node/zip';
import { Promise, TPromise } from 'vs/base/common/winjs.base';
import { IExtensionManagementService, IExtension, IExtensionManifest, IGalleryMetadata, IGalleryVersion } from 'vs/platform/extensionManagement/common/extensionManagement';
import { extensionEquals, getTelemetryData } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
import { IExtensionManagementService, ILocalExtension, IGalleryExtension, IExtensionIdentity, IExtensionManifest, IGalleryVersion, IGalleryMetadata, InstallExtensionEvent, DidInstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement';
import { download, json, IRequestOptions } from 'vs/base/node/request';
import { getProxyAgent } from 'vs/base/node/proxy';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
......@@ -29,20 +28,23 @@ import { groupBy, values } from 'vs/base/common/collections';
import { isValidExtensionVersion } from 'vs/platform/extensions/node/extensionValidator';
import pkg from 'vs/platform/package';
function parseManifest(raw: string): TPromise<IExtensionManifest> {
function parseManifest(raw: string): TPromise<{ manifest: IExtensionManifest; metadata: IGalleryMetadata; }> {
return new Promise((c, e) => {
try {
c(JSON.parse(raw));
const manifest = JSON.parse(raw);
const metadata = manifest.__metadata || null;
delete manifest.__metadata;
c({ manifest, metadata });
} catch (err) {
e(new Error(nls.localize('invalidManifest', "Extension invalid: package.json is not a JSON file.")));
}
});
}
function validate(zipPath: string, extension?: IExtension, version = extension && extension.version): TPromise<IExtension> {
function validate(zipPath: string, extension?: IExtensionIdentity, version?: string): TPromise<IExtensionManifest> {
return buffer(zipPath, 'extension/package.json')
.then(buffer => parseManifest(buffer.toString('utf8')))
.then(manifest => {
.then(({ manifest }) => {
if (extension) {
if (extension.name !== manifest.name) {
return Promise.wrapError(Error(nls.localize('invalidName', "Extension invalid: manifest name mismatch.")));
......@@ -61,28 +63,7 @@ function validate(zipPath: string, extension?: IExtension, version = extension &
});
}
function createExtension(manifest: IExtensionManifest, galleryInformation?: IGalleryMetadata, path?: string): IExtension {
const extension: IExtension = {
name: manifest.name,
displayName: manifest.displayName || manifest.name,
publisher: manifest.publisher,
version: manifest.version,
engines: { vscode: manifest.engines.vscode },
description: manifest.description || ''
};
if (galleryInformation) {
extension.galleryInformation = galleryInformation;
}
if (path) {
extension.path = path;
}
return extension;
}
function getExtensionId(extension: IExtensionManifest, version = extension.version): string {
function getExtensionId(extension: IExtensionIdentity, version: string): string {
return `${ extension.publisher }.${ extension.name }-${ version }`;
}
......@@ -95,17 +76,17 @@ export class ExtensionManagementService implements IExtensionManagementService {
private obsoleteFileLimiter: Limiter<void>;
private disposables: IDisposable[];
private _onInstallExtension = new Emitter<IExtensionManifest>();
onInstallExtension: Event<IExtensionManifest> = this._onInstallExtension.event;
private _onInstallExtension = new Emitter<InstallExtensionEvent>();
onInstallExtension: Event<InstallExtensionEvent> = this._onInstallExtension.event;
private _onDidInstallExtension = new Emitter<{ extension: IExtension; isUpdate: boolean; error?: Error; }>();
onDidInstallExtension: Event<{ extension: IExtension; isUpdate: boolean; error?: Error; }> = this._onDidInstallExtension.event;
private _onDidInstallExtension = new Emitter<DidInstallExtensionEvent>();
onDidInstallExtension: Event<DidInstallExtensionEvent> = this._onDidInstallExtension.event;
private _onUninstallExtension = new Emitter<IExtension>();
onUninstallExtension: Event<IExtension> = this._onUninstallExtension.event;
private _onUninstallExtension = new Emitter<string>();
onUninstallExtension: Event<string> = this._onUninstallExtension.event;
private _onDidUninstallExtension = new Emitter<IExtension>();
onDidUninstallExtension: Event<IExtension> = this._onDidUninstallExtension.event;
private _onDidUninstallExtension = new Emitter<string>();
onDidUninstallExtension: Event<string> = this._onDidUninstallExtension.event;
constructor(
@IEnvironmentService private environmentService: IEnvironmentService,
......@@ -115,71 +96,73 @@ export class ExtensionManagementService implements IExtensionManagementService {
this.obsoletePath = path.join(this.extensionsPath, '.obsolete');
this.obsoleteFileLimiter = new Limiter(1);
this.disposables = [
this.onDidInstallExtension(({ extension, isUpdate, error }) => telemetryService.publicLog(
isUpdate ? 'extensionGallery2:update' : 'extensionGallery2:install',
assign(getTelemetryData(extension), { success: !error })
)),
this.onDidUninstallExtension(extension => telemetryService.publicLog(
'extensionGallery2:uninstall',
assign(getTelemetryData(extension), { success: true })
))
];
// this.disposables = [
// this.onDidInstallExtension(({ extension, isUpdate, error }) => telemetryService.publicLog(
// isUpdate ? 'extensionGallery2:update' : 'extensionGallery2:install',
// assign(getTelemetryData(extension), { success: !error })
// )),
// this.onDidUninstallExtension(extension => telemetryService.publicLog(
// 'extensionGallery2:uninstall',
// assign(getTelemetryData(extension), { success: true })
// ))
// ];
}
install(extension: IExtension): TPromise<IExtension>;
install(zipPath: string): TPromise<IExtension>;
install(arg: any): TPromise<IExtension> {
install(extension: IGalleryExtension): TPromise<void>;
install(zipPath: string): TPromise<void>;
install(arg: any): TPromise<void> {
if (types.isString(arg)) {
return this.installFromZip(arg);
const zipPath = arg as string;
return validate(zipPath).then(manifest => {
const id = getExtensionId(manifest, manifest.version);
this._onInstallExtension.fire({ id });
return this.installValidExtension(zipPath, id);
});
}
const extension = arg as IExtension;
return this.isObsolete(extension).then(obsolete => {
const extension = arg as IGalleryExtension;
const id = getExtensionId(extension, extension.versions[0].version);
this._onInstallExtension.fire({ id, gallery: extension });
return this.isObsolete(id).then(obsolete => {
if (obsolete) {
return TPromise.wrapError(new Error(nls.localize('restartCode', "Please restart Code before reinstalling {0}.", extension.name)));
return TPromise.wrapError<void>(new Error(nls.localize('restartCode', "Please restart Code before reinstalling {0}.", extension.displayName || extension.name)));
}
return this.installFromGallery(arg);
});
}
private installFromGallery(extension: IExtension): TPromise<IExtension> {
const galleryInformation = extension.galleryInformation;
if (!galleryInformation) {
return TPromise.wrapError(new Error(nls.localize('missingGalleryInformation', "Gallery information is missing")));
}
private installFromGallery(extension: IGalleryExtension): TPromise<void> {
return this.getLastValidExtensionVersion(extension).then(versionInfo => {
const version = versionInfo.version;
const url = versionInfo.downloadUrl;
const headers = versionInfo.downloadHeaders;
const zipPath = path.join(tmpdir(), extension.id);
const id = getExtensionId(extension, version);
const metadata = {
id: extension.id,
publisherId: extension.publisherId,
publisherDisplayName: extension.publisherDisplayName
};
this._onInstallExtension.fire(extension);
return this.getLastValidExtensionVersion(extension, extension.galleryInformation.versions).then(versionInfo => {
return this.getInstalled()
.then(installed => installed.some(e => extensionEquals(e, extension)))
.then(isUpdate => {
const version = versionInfo.version;
const url = versionInfo.downloadUrl;
const headers = versionInfo.downloadHeaders;
const zipPath = path.join(tmpdir(), galleryInformation.id);
const extensionPath = path.join(this.extensionsPath, getExtensionId(extension, version));
const manifestPath = path.join(extensionPath, 'package.json');
return this.request(url)
.then(opts => assign(opts, { headers }))
.then(opts => download(zipPath, opts))
.then(() => validate(zipPath, extension, version))
.then(manifest => extract(zipPath, extensionPath, { sourcePath: 'extension', overwrite: true }).then(() => manifest))
.then(manifest => assign({ __metadata: galleryInformation }, manifest))
.then(manifest => pfs.writeFile(manifestPath, JSON.stringify(manifest, null, '\t')))
.then(() => { this._onDidInstallExtension.fire({ extension, isUpdate }); return extension; })
.then(null, error => { this._onDidInstallExtension.fire({ extension, isUpdate, error }); return TPromise.wrapError(error); });
});
return this.request(url)
.then(opts => assign(opts, { headers }))
.then(opts => download(zipPath, opts))
.then(() => validate(zipPath, extension, version))
.then(() => this.installValidExtension(zipPath, id, metadata));
});
}
private getLastValidExtensionVersion(extension: IExtension, versions: IGalleryVersion[]): TPromise<IGalleryVersion> {
private getLastValidExtensionVersion(extension: IGalleryExtension): TPromise<IGalleryVersion> {
return this._getLastValidExtensionVersion(extension, extension.versions);
}
private _getLastValidExtensionVersion(extension: IGalleryExtension, versions: IGalleryVersion[]): TPromise<IGalleryVersion> {
if (!versions.length) {
return TPromise.wrapError(new Error(nls.localize('noCompatible', "Couldn't find a compatible version of {0} with this version of Code.", extension.displayName)));
return TPromise.wrapError(new Error(nls.localize('noCompatible', "Couldn't find a compatible version of {0} with this version of Code.", extension.displayName || extension.name)));
}
const version = versions[0];
......@@ -193,42 +176,49 @@ export class ExtensionManagementService implements IExtensionManagementService {
};
if (!isValidExtensionVersion(pkg.version, desc, [])) {
return this.getLastValidExtensionVersion(extension, versions.slice(1));
return this._getLastValidExtensionVersion(extension, versions.slice(1));
}
return version;
});
}
private installFromZip(zipPath: string): TPromise<IExtension> {
return validate(zipPath).then(manifest => {
const extensionPath = path.join(this.extensionsPath, getExtensionId(manifest));
this._onInstallExtension.fire(manifest);
private installValidExtension(zipPath: string, id: string, metadata: IGalleryMetadata = null): TPromise<void> {
const extensionPath = path.join(this.extensionsPath, id);
const manifestPath = path.join(extensionPath, 'package.json');
return this.getInstalled()
.then(installed => installed.some(e => extensionEquals(e, manifest)))
.then(isUpdate => {
return extract(zipPath, extensionPath, { sourcePath: 'extension', overwrite: true })
.then(() => pfs.readFile(manifestPath, 'utf8'))
.then(raw => parseManifest(raw))
.then(({ manifest }) => {
return pfs.readdir(extensionPath).then(children => {
const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0];
const readmeUrl = readme ? `file://${ extensionPath }/${ readme }` : null;
return extract(zipPath, extensionPath, { sourcePath: 'extension', overwrite: true })
.then(() => createExtension(manifest, (<any> manifest).__metadata, extensionPath))
.then(extension => { this._onDidInstallExtension.fire({ extension, isUpdate }); return extension; });
});
});
const local: ILocalExtension = { id, manifest, metadata, path: extensionPath, readmeUrl };
const rawManifest = assign(manifest, { __metadata: metadata });
return pfs.writeFile(manifestPath, JSON.stringify(rawManifest, null, '\t'))
.then(() => this._onDidInstallExtension.fire({ id, local }));
});
})
.then<void>(null, error => { this._onDidInstallExtension.fire({ id, error }); return TPromise.wrapError(error); });
}
uninstall(extension: IExtension): TPromise<void> {
const extensionPath = extension.path || path.join(this.extensionsPath, getExtensionId(extension));
uninstall(extension: ILocalExtension): TPromise<void> {
const id = extension.id;
const extensionPath = path.join(this.extensionsPath, id);
return pfs.exists(extensionPath)
.then(exists => exists ? null : Promise.wrapError(new Error(nls.localize('notExists', "Could not find extension"))))
.then(() => this._onUninstallExtension.fire(extension))
.then(() => this.setObsolete(extension))
.then(() => this._onUninstallExtension.fire(id))
.then(() => this.setObsolete(id))
.then(() => pfs.rimraf(extensionPath))
.then(() => this.unsetObsolete(extension))
.then(() => this._onDidUninstallExtension.fire(extension));
.then(() => this.unsetObsolete(id))
.then(() => this._onDidUninstallExtension.fire(id));
}
getInstalled(includeDuplicateVersions: boolean = false): TPromise<IExtension[]> {
getInstalled(includeDuplicateVersions: boolean = false): TPromise<ILocalExtension[]> {
const all = this.getAllInstalled();
if (includeDuplicateVersions) {
......@@ -236,35 +226,39 @@ export class ExtensionManagementService implements IExtensionManagementService {
}
return all.then(extensions => {
const byId = values(groupBy(extensions, p => `${ p.publisher }.${ p.name }`));
return byId.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version))[0]);
const byId = values(groupBy(extensions, p => `${ p.manifest.publisher }.${ p.manifest.name }`));
return byId.map(p => p.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0]);
});
}
private getAllInstalled(): TPromise<IExtension[]> {
private getAllInstalled(): TPromise<ILocalExtension[]> {
const limiter = new Limiter(10);
return this.getObsoleteExtensions()
.then(obsolete => {
return pfs.readdir(this.extensionsPath)
.then(extensions => extensions.filter(e => !obsolete[e]))
.then<IExtension[]>(extensions => Promise.join(extensions.map(e => {
const extensionPath = path.join(this.extensionsPath, e);
.then(extensions => extensions.filter(id => !obsolete[id]))
.then<ILocalExtension[]>(extensionIds => Promise.join(extensionIds.map(id => {
const extensionPath = path.join(this.extensionsPath, id);
const each = () => pfs.readdir(extensionPath).then(children => {
const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0];
const readmeUrl = readme ? `file://${ extensionPath }/${ readme }` : null;
return limiter.queue(
() => pfs.readFile(path.join(extensionPath, 'package.json'), 'utf8')
return pfs.readFile(path.join(extensionPath, 'package.json'), 'utf8')
.then(raw => parseManifest(raw))
.then(manifest => createExtension(manifest, (<any> manifest).__metadata, extensionPath))
.then(null, () => null)
);
.then<ILocalExtension>(({ manifest, metadata }) => ({ id, manifest, metadata, path: extensionPath, readmeUrl }));
}).then(null, () => null);
return limiter.queue(each);
})))
.then(result => result.filter(a => !!a));
});
}
removeDeprecatedExtensions(): TPromise<void> {
const outdated = this.getOutdatedExtensions()
.then(extensions => extensions.map(e => getExtensionId(e)));
const outdated = this.getOutdatedExtensionIds()
.then(extensions => extensions.map(e => getExtensionId(e.manifest, e.manifest.version)));
const obsolete = this.getObsoleteExtensions()
.then(obsolete => Object.keys(obsolete));
......@@ -279,28 +273,21 @@ export class ExtensionManagementService implements IExtensionManagementService {
});
}
private getOutdatedExtensions(): TPromise<IExtension[]> {
return this.getAllInstalled().then(plugins => {
const byId = values(groupBy(plugins, p => `${ p.publisher }.${ p.name }`));
const extensions = flatten(byId.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version)).slice(1)));
return extensions
.filter(e => !!e.path);
});
private getOutdatedExtensionIds(): TPromise<ILocalExtension[]> {
return this.getAllInstalled()
.then(extensions => values(groupBy(extensions, p => `${ p.manifest.publisher }.${ p.manifest.name }`)))
.then(versions => flatten(versions.map(p => p.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version)).slice(1))));
}
private isObsolete(extension: IExtension): TPromise<boolean> {
const id = getExtensionId(extension);
private isObsolete(id: string): TPromise<boolean> {
return this.withObsoleteExtensions(obsolete => !!obsolete[id]);
}
private setObsolete(extension: IExtension): TPromise<void> {
const id = getExtensionId(extension);
private setObsolete(id: string): TPromise<void> {
return this.withObsoleteExtensions(obsolete => assign(obsolete, { [id]: true }));
}
private unsetObsolete(extension: IExtension): TPromise<void> {
const id = getExtensionId(extension);
private unsetObsolete(id: string): TPromise<void> {
return this.withObsoleteExtensions<void>(obsolete => delete obsolete[id]);
}
......
......@@ -5,48 +5,62 @@
'use strict';
import { IExtension, IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionIdentity, ILocalExtension, IGalleryExtension, IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { TPromise } from 'vs/base/common/winjs.base';
import * as semver from 'semver';
export function getExtensionId(extension: IExtension): string {
return `${ extension.publisher }.${ extension.name }`;
}
export function extensionEquals(one: IExtension, other: IExtension): boolean {
export function extensionEquals(one: IExtensionIdentity, other: IExtensionIdentity): boolean {
return one.publisher === other.publisher && one.name === other.name;
}
export function getTelemetryData(extension: IExtension): any {
return {
id: getExtensionId(extension),
name: extension.name,
galleryId: extension.galleryInformation ? extension.galleryInformation.id : null,
publisherId: extension.galleryInformation ? extension.galleryInformation.publisherId : null,
publisherName: extension.publisher,
publisherDisplayName: extension.galleryInformation ? extension.galleryInformation.publisherDisplayName : null
};
export function getTelemetryData(extension: ILocalExtension | IGalleryExtension): any {
const local = extension as ILocalExtension;
const gallery = extension as IGalleryExtension;
if (local.path) {
return {
id: `${ local.manifest.publisher }.${ local.manifest.name }`,
name: local.manifest.name,
galleryId: local.metadata ? local.metadata.id : null,
publisherId: local.metadata ? local.metadata.publisherId : null,
publisherName: local.manifest.publisher,
publisherDisplayName: local.metadata ? local.metadata.publisherDisplayName : null
};
} else {
return {
id: `${ gallery.publisher }.${ gallery.name }`,
name: gallery.name,
galleryId: gallery.id,
publisherId: gallery.publisherId,
publisherName: gallery.publisher,
publisherDisplayName: gallery.publisherDisplayName
};
}
}
export function getOutdatedExtensions(extensionsService: IExtensionManagementService, galleryService: IExtensionGalleryService): TPromise<IExtension[]> {
export function getOutdatedExtensions(extensionsService: IExtensionManagementService, galleryService: IExtensionGalleryService): TPromise<ILocalExtension[]> {
if (!galleryService.isEnabled()) {
return TPromise.as([]);
}
return extensionsService.getInstalled().then(installed => {
const ids = installed.map(getExtensionId);
const names = installed.map(({ manifest }) => `${ manifest.publisher }.${ manifest.name }`);
if (installed.length === 0) {
return TPromise.as([]);
}
return galleryService.query({ ids, pageSize: ids.length }).then(result => {
return galleryService.query({ names, pageSize: names.length }).then(result => {
const available = result.firstPage;
return available.filter(extension => {
const local = installed.filter(local => extensionEquals(local, extension))[0];
return local && semver.lt(local.version, extension.version);
});
return available.map(extension => {
const local = installed.filter(local => extensionEquals(local.manifest, extension))[0];
if (local && semver.lt(local.manifest.version, extension.versions[0].version)) {
return local;
} else {
return null;
}
}).filter(e => !!e);
});
});
}
\ No newline at end of file
......@@ -12,7 +12,7 @@ import {Builder, $} from 'vs/base/browser/builder';
import {Action} from 'vs/base/common/actions';
import errors = require('vs/base/common/errors');
import {ActionsOrientation, ActionBar, IActionItem} from 'vs/base/browser/ui/actionbar/actionbar';
import {ToolBar} from 'vs/base/browser/ui/toolbar/toolbar';
import {CONTEXT, ToolBar} from 'vs/base/browser/ui/toolbar/toolbar';
import {Registry} from 'vs/platform/platform';
import {CompositeEvent, EventType} from 'vs/workbench/common/events';
import {ViewletDescriptor, ViewletRegistry, Extensions as ViewletExtensions} from 'vs/workbench/browser/viewlet';
......@@ -27,10 +27,15 @@ import {IInstantiationService} from 'vs/platform/instantiation/common/instantiat
import {IMessageService} from 'vs/platform/message/common/message';
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService';
import {Scope, IActionBarRegistry, Extensions as ActionBarExtensions, prepareActions} from 'vs/workbench/browser/actionBarRegistry';
import Severity from 'vs/base/common/severity';
import {IAction} from 'vs/base/common/actions';
import events = require('vs/base/common/events');
export class ActivitybarPart extends Part implements IActivityService {
public serviceId = IActivityService;
private viewletSwitcherBar: ActionBar;
private globalViewletSwitcherBar: ActionBar;
private globalToolBar: ToolBar;
private activityActionItems: { [actionId: string]: IActionItem; };
private viewletIdToActions: { [viewletId: string]: ActivityAction; };
......@@ -103,7 +108,7 @@ export class ActivitybarPart extends Part implements IActivityService {
this.createViewletSwitcher($result.clone());
// Bottom Toolbar with action items for global actions
// this.createGlobalToolBarArea($result.clone()); // not used currently
this.createGlobalToolBarArea($result.clone()); // not used currently
return $result;
}
......@@ -118,106 +123,125 @@ export class ActivitybarPart extends Part implements IActivityService {
});
this.viewletSwitcherBar.getContainer().addClass('position-top');
// Global viewlet switcher is right below
this.globalViewletSwitcherBar = new ActionBar(div, {
actionItemProvider: (action: Action) => this.activityActionItems[action.id],
orientation: ActionsOrientation.VERTICAL,
ariaLabel: nls.localize('globalActivityBarAriaLabel', "Active Global View Switcher")
});
this.globalViewletSwitcherBar.getContainer().addClass('position-bottom');
// Build Viewlet Actions in correct order
let activeViewlet = this.viewletService.getActiveViewlet();
let registry = (<ViewletRegistry>Registry.as(ViewletExtensions.Viewlets));
let viewletActions: Action[] = registry.getViewlets()
.sort((v1: ViewletDescriptor, v2: ViewletDescriptor) => v1.order - v2.order)
.map((viewlet: ViewletDescriptor) => {
let action = this.instantiationService.createInstance(ViewletActivityAction, viewlet.id + '.activity-bar-action', viewlet);
const activeViewlet = this.viewletService.getActiveViewlet();
const registry = (<ViewletRegistry>Registry.as(ViewletExtensions.Viewlets));
const allViewletActions = registry.getViewlets();
const actionOptions = { label: true, icon: true };
const toAction = (viewlet: ViewletDescriptor) => {
let action = this.instantiationService.createInstance(ViewletActivityAction, viewlet.id + '.activity-bar-action', viewlet);
let keybinding: string = null;
let keys = this.keybindingService.lookupKeybindings(viewlet.id).map(k => this.keybindingService.getLabelFor(k));
if (keys && keys.length) {
keybinding = keys[0];
}
this.activityActionItems[action.id] = new ActivityActionItem(action, viewlet.name, keybinding);
this.viewletIdToActions[viewlet.id] = action;
// Mark active viewlet action as active
if (activeViewlet && activeViewlet.getId() === viewlet.id) {
action.activate();
}
return action;
};
// Add to viewlet switcher
this.viewletSwitcherBar.push(allViewletActions
.filter(v => !v.isGlobal)
.sort((v1, v2) => v1.order - v2.order)
.map(toAction)
, actionOptions);
// Add to viewlet switcher
this.globalViewletSwitcherBar.push(allViewletActions
.filter(v => v.isGlobal)
.sort((v1, v2) => v1.order - v2.order)
.map(toAction),
actionOptions);
}
private createGlobalToolBarArea(div: Builder): void {
// Global action bar is on the bottom
this.globalToolBar = new ToolBar(div.getHTMLElement(), this.contextMenuService, {
actionItemProvider: (action: Action) => this.activityActionItems[action.id],
orientation: ActionsOrientation.VERTICAL
});
this.globalToolBar.getContainer().addClass('global');
this.globalToolBar.actionRunner.addListener2(events.EventType.RUN, (e: any) => {
// Check for Error
if (e.error && !errors.isPromiseCanceledError(e.error)) {
this.messageService.show(Severity.Error, e.error);
}
// Log in telemetry
if (this.telemetryService) {
this.telemetryService.publicLog('workbenchActionExecuted', { id: e.action.id, from: 'activityBar' });
}
});
// Build Global Actions in correct order
let primaryActions = this.getGlobalActions(true);
let secondaryActions = this.getGlobalActions(false);
if (primaryActions.length + secondaryActions.length > 0) {
this.globalToolBar.getContainer().addClass('position-bottom');
}
// Add to global action bar
this.globalToolBar.setActions(prepareActions(primaryActions), prepareActions(secondaryActions))();
}
private getGlobalActions(primary: boolean): IAction[] {
let actionBarRegistry = <IActionBarRegistry>Registry.as(ActionBarExtensions.Actionbar);
// Collect actions from actionbar contributor
let actions: IAction[];
if (primary) {
actions = actionBarRegistry.getActionBarActionsForContext(Scope.GLOBAL, CONTEXT);
} else {
actions = actionBarRegistry.getSecondaryActionBarActionsForContext(Scope.GLOBAL, CONTEXT);
}
return actions.map((action: Action) => {
if (primary) {
let keybinding: string = null;
let keys = this.keybindingService.lookupKeybindings(viewlet.id).map(k => this.keybindingService.getLabelFor(k));
let keys = this.keybindingService.lookupKeybindings(action.id).map(k => this.keybindingService.getLabelFor(k));
if (keys && keys.length) {
keybinding = keys[0];
}
this.activityActionItems[action.id] = new ActivityActionItem(action, viewlet.name, keybinding);
this.viewletIdToActions[viewlet.id] = action;
let actionItem = actionBarRegistry.getActionItemForContext(Scope.GLOBAL, CONTEXT, action);
// Mark active viewlet action as active
if (activeViewlet && activeViewlet.getId() === viewlet.id) {
action.activate();
if (!actionItem) {
actionItem = new ActivityActionItem(action, action.label, keybinding);
}
return action;
if (actionItem instanceof ActivityActionItem) {
(<ActivityActionItem> actionItem).keybinding = keybinding;
}
this.activityActionItems[action.id] = actionItem;
}
);
// Add to viewlet switcher
this.viewletSwitcherBar.push(viewletActions, { label: true, icon: true });
return action;
});
}
// private createGlobalToolBarArea(div: Builder): void {
// // Global action bar is on the bottom
// this.globalToolBar = new ToolBar(div.getHTMLElement(), this.contextMenuService, {
// actionItemProvider: (action: Action) => this.activityActionItems[action.id],
// orientation: ActionsOrientation.VERTICAL
// });
// this.globalToolBar.getContainer().addClass('global');
// this.globalToolBar.actionRunner.addListener(events.EventType.RUN, (e: any) => {
// // Check for Error
// if (e.error && !errors.isPromiseCanceledError(e.error)) {
// this.messageService.show(Severity.Error, e.error);
// }
// // Log in telemetry
// if (this.telemetryService) {
// this.telemetryService.publicLog('workbenchActionExecuted', { id: e.action.id, from: 'activityBar' });
// }
// });
// // Build Global Actions in correct order
// let primaryActions = this.getGlobalActions(true);
// let secondaryActions = this.getGlobalActions(false);
// if (primaryActions.length + secondaryActions.length > 0) {
// this.globalToolBar.getContainer().addClass('position-bottom');
// }
// // Add to global action bar
// this.globalToolBar.setActions(prepareActions(primaryActions), prepareActions(secondaryActions))();
// }
// private getGlobalActions(primary: boolean): IAction[] {
// let actionBarRegistry = <IActionBarRegistry>Registry.as(ActionBarExtensions.Actionbar);
// // Collect actions from actionbar contributor
// let actions: IAction[];
// if (primary) {
// actions = actionBarRegistry.getActionBarActionsForContext(Scope.GLOBAL, CONTEXT);
// } else {
// actions = actionBarRegistry.getSecondaryActionBarActionsForContext(Scope.GLOBAL, CONTEXT);
// }
// return actions.map((action: Action) => {
// if (primary) {
// let keybinding: string = null;
// let keys = this.keybindingService.lookupKeybindings(action.id).map(k => this.keybindingService.getLabelFor(k));
// if (keys && keys.length) {
// keybinding = keys[0];
// }
// let actionItem = actionBarRegistry.getActionItemForContext(Scope.GLOBAL, CONTEXT, action);
// if (!actionItem) {
// actionItem = new ActivityActionItem(action, action.label, keybinding);
// }
// if (actionItem instanceof ActivityActionItem) {
// (<ActivityActionItem> actionItem).keybinding = keybinding;
// }
// this.activityActionItems[action.id] = actionItem;
// }
// return action;
// });
// }
public dispose(): void {
if (this.viewletSwitcherBar) {
this.viewletSwitcherBar.dispose();
......
......@@ -151,7 +151,14 @@ export abstract class ViewerViewlet extends Viewlet {
/**
* A viewlet descriptor is a leightweight descriptor of a viewlet in the workbench.
*/
export class ViewletDescriptor extends CompositeDescriptor<Viewlet> { }
export class ViewletDescriptor extends CompositeDescriptor<Viewlet> {
public isGlobal: boolean;
constructor(moduleId: string, ctorName: string, id: string, name: string, cssClass?: string, order?: number, isGlobal?: boolean) {
super(moduleId, ctorName, id, name, cssClass, order);
this.isGlobal = isGlobal || false;
}
}
export const Extensions = {
Viewlets: 'workbench.contributions.viewlets'
......@@ -171,14 +178,14 @@ export class ViewletRegistry extends CompositeRegistry<Viewlet> {
* Returns the viewlet descriptor for the given id or null if none.
*/
public getViewlet(id: string): ViewletDescriptor {
return this.getComposite(id);
return this.getComposite(id) as ViewletDescriptor;
}
/**
* Returns an array of registered viewlets known to the platform.
*/
public getViewlets(): ViewletDescriptor[] {
return this.getComposits();
return this.getComposits() as ViewletDescriptor[];
}
/**
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { EditorInput } from 'vs/workbench/common/editor';
// TODO@joao: layer breaker
import { IExtension, ExtensionsModel } from '../electron-browser/extensionsModel';
export class ExtensionsInput extends EditorInput {
static get ID() { return 'workbench.extensions.input2'; }
get model(): ExtensionsModel { return this._model; }
get extension(): IExtension { return this._extension; }
constructor(private _model: ExtensionsModel, private _extension: IExtension) {
super();
}
getTypeId(): string {
return ExtensionsInput.ID;
}
getName(): string {
return this.extension.displayName;
}
matches(other: any): boolean {
if (!(other instanceof ExtensionsInput)) {
return false;
}
const otherExtensionInput = other as ExtensionsInput;
// TODO@joao is this correct?
return this.extension === otherExtensionInput.extension;
}
resolve(refresh?: boolean): TPromise<any> {
return TPromise.as(null);
}
}
\ 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.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/extensionEditor';
import { TPromise } from 'vs/base/common/winjs.base';
import { marked } from 'vs/base/common/marked/marked';
import { IDisposable, empty, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { Builder } from 'vs/base/browser/builder';
import { append, emmet as $, addClass, removeClass, finalHandler } from 'vs/base/browser/dom';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IViewletService } from 'vs/workbench/services/viewlet/common/viewletService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IRequestService } from 'vs/platform/request/common/request';
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionsInput } from '../common/extensionsInput';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITemplateData } from './extensionsList';
import { RatingsWidget, Label } from './extensionsWidgets';
import { EditorOptions } from 'vs/workbench/common/editor';
import { shell } from 'electron';
import product from 'vs/platform/product';
import { IExtensionsViewlet } from './extensions';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { CombinedInstallAction, UpdateAction } from './extensionsActions';
const actionOptions = { icon: true, label: true };
export class ExtensionEditor extends BaseEditor {
static ID: string = 'workbench.editor.extension';
private icon: HTMLElement;
private name: HTMLAnchorElement;
private publisher: HTMLAnchorElement;
private installCount: HTMLElement;
private rating: HTMLAnchorElement;
private description: HTMLElement;
private actionBar: ActionBar;
private body: HTMLElement;
private _highlight: ITemplateData;
private highlightDisposable: IDisposable;
private transientDisposables: IDisposable[];
private disposables: IDisposable[];
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService,
@IConfigurationService private configurationService: IConfigurationService,
@IInstantiationService private instantiationService: IInstantiationService,
@IRequestService private requestService: IRequestService,
@IViewletService private viewletService: IViewletService
) {
super(ExtensionEditor.ID, telemetryService);
this._highlight = null;
this.highlightDisposable = empty;
this.disposables = [];
}
createEditor(parent: Builder): void {
const container = parent.getHTMLElement();
const root = append(container, $('.extension-editor'));
const header = append(root, $('.header'));
this.icon = append(header, $('.icon'));
const details = append(header, $('.details'));
this.name = append(details, $<HTMLAnchorElement>('a.name'));
this.name.href = '#';
const subtitle = append(details, $('.subtitle'));
this.publisher = append(subtitle, $<HTMLAnchorElement>('a.publisher'));
this.publisher.href = '#';
const install = append(subtitle, $('span.install'));
append(install, $('span.octicon.octicon-cloud-download'));
this.installCount = append(install, $('span.count'));
this.rating = append(subtitle, $<HTMLAnchorElement>('a.rating'));
this.rating.href = '#';
this.description = append(details, $('.description'));
const actions = append(details, $('.actions'));
this.actionBar = new ActionBar(actions, { animated: false });
this.body = append(root, $('.body'));
}
setInput(input: ExtensionsInput, options: EditorOptions): TPromise<void> {
this.transientDisposables = dispose(this.transientDisposables);
this.body.innerHTML = '';
let promise = TPromise.as(null);
const extension = input.extension;
this.icon.style.backgroundImage = `url("${ extension.iconUrl }")`;
this.name.textContent = extension.displayName;
this.publisher.textContent = extension.publisherDisplayName;
this.description.textContent = extension.description;
if (product.extensionsGallery) {
const extensionUrl = `${ product.extensionsGallery.itemUrl }?itemName=${ extension.publisher }.${ extension.name }`;
this.name.onclick = finalHandler(() => shell.openExternal(extensionUrl));
this.rating.onclick = finalHandler(() => shell.openExternal(`${ extensionUrl }#review-details`));
this.publisher.onclick = finalHandler(() => {
this.viewletService.openViewlet('workbench.viewlet.extensions', true)
.then(viewlet => viewlet as IExtensionsViewlet)
.done(viewlet => viewlet.search(`publisher:"${ extension.publisherDisplayName }"`, true));
});
}
const install = new Label(this.installCount, input.model, extension, e => `${ e.installCount }`);
this.transientDisposables.push(install);
const ratings = new RatingsWidget(this.rating, input.model, extension);
this.transientDisposables.push(ratings);
const installAction = new CombinedInstallAction(input.model, extension);
const updateAction = new UpdateAction(input.model, extension);
this.actionBar.clear();
this.actionBar.push([updateAction, installAction], actionOptions);
this.transientDisposables.push(updateAction, installAction);
addClass(this.body, 'loading');
if (extension.readmeUrl) {
promise = super.setInput(input, options)
.then(() => this.requestService.makeRequest({ url: extension.readmeUrl }))
.then(response => response.responseText)
.then(marked.parse)
.then<void>(html => this.body.innerHTML = html)
.then(null, () => null)
.then(() => removeClass(this.body, 'loading'));
}
this.transientDisposables.push(toDisposable(() => promise.cancel()));
return TPromise.as(null);
}
layout(): void {
return;
}
dispose(): void {
this._highlight = null;
this.transientDisposables = dispose(this.transientDisposables);
this.disposables = dispose(this.disposables);
super.dispose();
}
}
......@@ -8,7 +8,7 @@ import {forEach} from 'vs/base/common/collections';
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
import {TPromise as Promise} from 'vs/base/common/winjs.base';
import {match} from 'vs/base/common/glob';
import {IExtensionGalleryService, IExtensionTipsService, IExtension} from 'vs/platform/extensionManagement/common/extensionManagement';
import {IExtensionGalleryService, IExtensionTipsService, ILocalExtension} from 'vs/platform/extensionManagement/common/extensionManagement';
import {IModelService} from 'vs/editor/common/services/modelService';
import {IStorageService, StorageScope} from 'vs/platform/storage/common/storage';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
......@@ -62,10 +62,10 @@ export class ExtensionTipsService implements IExtensionTipsService {
}
}
getRecommendations(): Promise<IExtension[]> {
const ids = Object.keys(this._recommendations);
getRecommendations(): Promise<ILocalExtension[]> {
const names = Object.keys(this._recommendations);
return this._galleryService.query({ ids, pageSize: ids.length })
return this._galleryService.query({ names, pageSize: names.length })
.then(result => result.firstPage, () => []);
}
......
......@@ -4,16 +4,34 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/extensions';
import { localize } from 'vs/nls';
import { Registry } from 'vs/platform/platform';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IStatusbarRegistry, Extensions as StatusbarExtensions, StatusbarItemDescriptor, StatusbarAlignment } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { ExtensionsStatusbarItem } from 'vs/workbench/parts/extensions/electron-browser/extensionsWidgets';
import { IExtensionGalleryService, IExtensionTipsService, ExtensionsLabel, ExtensionsChannelId } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { ExtensionsWorkbenchExtension } from 'vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchExtension';
import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/parts/output/common/output';
import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/parts/editor/baseEditor';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput';
// import { EditorInput } from 'vs/workbench/common/editor';
// import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet';
// class ExtensionsInputFactory implements IEditorInputFactory {
// constructor() {}
// public serialize(editorInput: EditorInput): string {
// return '';
// }
// public deserialize(instantiationService: IInstantiationService, resourceRaw: string): EditorInput {
// return instantiationService.createInstance(ExtensionsInput);
// }
// }
registerSingleton(IExtensionGalleryService, ExtensionGalleryService);
registerSingleton(IExtensionTipsService, ExtensionTipsService);
......@@ -21,8 +39,31 @@ registerSingleton(IExtensionTipsService, ExtensionTipsService);
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
.registerWorkbenchContribution(ExtensionsWorkbenchExtension);
Registry.as<IStatusbarRegistry>(StatusbarExtensions.Statusbar)
.registerStatusbarItem(new StatusbarItemDescriptor(ExtensionsStatusbarItem, StatusbarAlignment.LEFT,10000));
Registry.as<IOutputChannelRegistry>(OutputExtensions.OutputChannels)
.registerChannel(ExtensionsChannelId, ExtensionsLabel);
\ No newline at end of file
.registerChannel(ExtensionsChannelId, ExtensionsLabel);
// Registry.as<IEditorRegistry>(EditorExtensions.Editors)
// .registerEditorInputFactory(ExtensionsInput.ID, ExtensionsInputFactory);
const editorDescriptor = new EditorDescriptor(
'workbench.editor.extension',
localize('extension', "Extension"),
'vs/workbench/parts/extensions/electron-browser/extensionEditor',
'ExtensionEditor'
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(editorDescriptor, [new SyncDescriptor(ExtensionsInput)]);
const viewletDescriptor = new ViewletDescriptor(
'vs/workbench/parts/extensions/electron-browser/extensionsViewlet',
'ExtensionsViewlet',
'workbench.viewlet.extensions',
localize('extensions', "Extensions"),
'extensions',
100,
true
);
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets)
.registerViewlet(viewletDescriptor);
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import {IViewlet} from 'vs/workbench/common/viewlet';
export interface IExtensionsViewlet extends IViewlet {
search(text: string, immediate?: boolean): void;
}
\ No newline at end of file
......@@ -3,216 +3,360 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/extensionActions';
import nls = require('vs/nls');
import { Promise, TPromise } from 'vs/base/common/winjs.base';
import { TPromise } from 'vs/base/common/winjs.base';
import { Action } from 'vs/base/common/actions';
import { assign } from 'vs/base/common/objects';
import Severity from 'vs/base/common/severity';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IMessageService } from 'vs/platform/message/common/message';
import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions';
import { IExtensionManagementService, IExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { extensionEquals, getTelemetryData } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
import { IQuickOpenService } from 'vs/workbench/services/quickopen/common/quickOpenService';
// import { assign } from 'vs/base/common/objects';
// import Severity from 'vs/base/common/severity';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
// import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
// import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
// import { IMessageService } from 'vs/platform/message/common/message';
// import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions';
import { IExtension, ExtensionsModel, ExtensionState } from './extensionsModel';
// import { extensionEquals, getTelemetryData } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
// import { IQuickOpenService } from 'vs/workbench/services/quickopen/common/quickOpenService';
// const CloseAction = new Action('action.close', nls.localize('close', "Close"));
// export class ListExtensionsAction extends Action {
// static ID = 'workbench.extensions.action.listExtensions';
// static LABEL = nls.localize('showInstalledExtensions', "Show Installed Extensions");
// constructor(
// id: string,
// label: string,
// @IExtensionManagementService private extensionManagementService: IExtensionManagementService,
// @IQuickOpenService private quickOpenService: IQuickOpenService
// ) {
// super(id, label, null, true);
// }
// run(): Promise {
// return this.quickOpenService.show('ext ');
// }
// protected isEnabled(): boolean {
// return true;
// }
// }
// export class InstallExtensionAction extends Action {
// static ID = 'workbench.extensions.action.installExtension';
// static LABEL = nls.localize('installExtension', "Install Extension");
// constructor(
// id: string,
// label: string,
// @IExtensionManagementService private extensionManagementService: IExtensionManagementService,
// @IQuickOpenService private quickOpenService: IQuickOpenService
// ) {
// super(id, label, null, true);
// }
// run(): Promise {
// return this.quickOpenService.show('ext install ');
// }
// protected isEnabled(): boolean {
// return true;
// }
// }
// export class ListOutdatedExtensionsAction extends Action {
// static ID = 'workbench.extensions.action.listOutdatedExtensions';
// static LABEL = nls.localize('showOutdatedExtensions', "Show Outdated Extensions");
// constructor(
// id: string,
// label: string,
// @IExtensionManagementService private extensionManagementService: IExtensionManagementService,
// @IQuickOpenService private quickOpenService: IQuickOpenService
// ) {
// super(id, label, null, true);
// }
// run(): Promise {
// return this.quickOpenService.show('ext update ');
// }
// protected isEnabled(): boolean {
// return true;
// }
// }
// export class ListSuggestedExtensionsAction extends Action {
// static ID = 'workbench.extensions.action.listSuggestedExtensions';
// static LABEL = nls.localize('showExtensionRecommendations', "Show Extension Recommendations");
// constructor(
// id: string,
// label: string,
// @IExtensionManagementService private extensionManagementService: IExtensionManagementService,
// @IQuickOpenService private quickOpenService: IQuickOpenService
// ) {
// super(id, label, null, true);
// }
// run(): Promise {
// return this.quickOpenService.show('ext recommend ');
// }
// protected isEnabled(): boolean {
// return true;
// }
// }
const CloseAction = new Action('action.close', nls.localize('close', "Close"));
export class ListExtensionsAction extends Action {
export class InstallAction extends Action {
static ID = 'workbench.extensions.action.listExtensions';
static LABEL = nls.localize('showInstalledExtensions', "Show Installed Extensions");
private disposables: IDisposable[] = [];
constructor(
id: string,
label: string,
@IExtensionManagementService private extensionManagementService: IExtensionManagementService,
@IQuickOpenService private quickOpenService: IQuickOpenService
) {
super(id, label, null, true);
}
constructor(private model: ExtensionsModel, private extension: IExtension) {
super('extensions.install', nls.localize('installAction', "Install"), 'extension-action install', false);
public run(): Promise {
return this.quickOpenService.show('ext ');
this.disposables.push(this.model.onChange(() => this.updateEnablement()));
this.updateEnablement();
}
protected isEnabled(): boolean {
return true;
private updateEnablement(): void {
this.enabled = this.model.canInstall(this.extension) && this.extension.state === ExtensionState.Uninstalled;
}
}
export class InstallExtensionAction extends Action {
run(): TPromise<any> {
return this.model.install(this.extension);
static ID = 'workbench.extensions.action.installExtension';
static LABEL = nls.localize('installExtension', "Install Extension");
// this.enabled = false;
constructor(
id: string,
label: string,
@IExtensionManagementService private extensionManagementService: IExtensionManagementService,
@IQuickOpenService private quickOpenService: IQuickOpenService
) {
super(id, label, null, true);
// return this.extensionManagementService.getInstalled()
// .then(installed => installed.some(({ manifest }) => extensionEquals(manifest, extension)))
// .then(isUpdate => {
// return this.extensionManagementService
// .install(extension)
// .then(() => this.onSuccess(extension, isUpdate), err => this.onError(err, extension, isUpdate))
// .then(() => this.enabled = true)
// .then(() => null);
// });
}
public run(): Promise {
return this.quickOpenService.show('ext install ');
}
protected isEnabled(): boolean {
return true;
// private onSuccess(extension: IGalleryExtension, isUpdate: boolean) {
// this.reportTelemetry(extension, isUpdate, true);
// this.messageService.show(Severity.Info, {
// message: nls.localize('success-installed', "'{0}' was successfully installed. Restart to enable it.", extension.displayName || extension.name),
// actions: [
// CloseAction,
// this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, nls.localize('restartNow', "Restart Now"))
// ]
// });
// }
// private onError(err: Error, extension: IGalleryExtension, isUpdate: boolean) {
// this.reportTelemetry(extension, isUpdate, false);
// this.messageService.show(Severity.Error, err);
// }
// private reportTelemetry(extension: IGalleryExtension, isUpdate: boolean, success: boolean) {
// const event = isUpdate ? 'extensionGallery:update' : 'extensionGallery:install';
// const data = assign(getTelemetryData(extension), { success });
// this.telemetryService.publicLog(event, data);
// }
dispose(): void {
super.dispose();
this.disposables = dispose(this.disposables);
}
}
export class ListOutdatedExtensionsAction extends Action {
export class UninstallAction extends Action {
static ID = 'workbench.extensions.action.listOutdatedExtensions';
static LABEL = nls.localize('showOutdatedExtensions', "Show Outdated Extensions");
private disposables: IDisposable[] = [];
constructor(
id: string,
label: string,
@IExtensionManagementService private extensionManagementService: IExtensionManagementService,
@IQuickOpenService private quickOpenService: IQuickOpenService
) {
super(id, label, null, true);
}
constructor(private model: ExtensionsModel, private extension: IExtension) {
super('extensions.uninstall', nls.localize('uninstall', "Uninstall"), 'extension-action uninstall', false);
public run(): Promise {
return this.quickOpenService.show('ext update ');
this.disposables.push(this.model.onChange(() => this.updateEnablement()));
this.updateEnablement();
}
protected isEnabled(): boolean {
return true;
private updateEnablement(): void {
this.enabled = this.extension.state === ExtensionState.Installed;
}
}
export class ListSuggestedExtensionsAction extends Action {
run(): TPromise<any> {
// const name = extension.manifest.displayName || extension.manifest.name;
static ID = 'workbench.extensions.action.listSuggestedExtensions';
static LABEL = nls.localize('showExtensionRecommendations', "Show Extension Recommendations");
if (!window.confirm(nls.localize('deleteSure', "Are you sure you want to uninstall '{0}'?", this.extension.displayName))) {
return TPromise.as(null);
}
constructor(
id: string,
label: string,
@IExtensionManagementService private extensionManagementService: IExtensionManagementService,
@IQuickOpenService private quickOpenService: IQuickOpenService
) {
super(id, label, null, true);
}
return this.model.uninstall(this.extension);
public run(): Promise {
return this.quickOpenService.show('ext recommend ');
// this.enabled = false;
// return this.extensionManagementService.getInstalled().then(localExtensions => {
// const [local] = localExtensions.filter(local => extensionEquals(local.manifest, extension.manifest));
// if (!local) {
// return TPromise.wrapError(nls.localize('notFound', "Extension '{0}' not installed.", name));
// }
// return this.extensionManagementService.uninstall(local)
// .then(() => this.onSuccess(local), err => this.onError(err, local))
// .then(() => this.enabled = true)
// .then(() => null);
// });
}
protected isEnabled(): boolean {
return true;
// private onSuccess(extension: ILocalExtension) {
// const name = extension.manifest.displayName || extension.manifest.name;
// this.reportTelemetry(extension, true);
// this.messageService.show(Severity.Info, {
// message: nls.localize('success-uninstalled', "'{0}' was successfully uninstalled. Restart to deactivate it.", name),
// actions: [
// CloseAction,
// this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, nls.localize('restartNow2', "Restart Now"))
// ]
// });
// }
// private onError(err: Error, extension: ILocalExtension) {
// this.reportTelemetry(extension, false);
// this.messageService.show(Severity.Error, err);
// }
// private reportTelemetry(extension: ILocalExtension, success: boolean) {
// const data = assign(getTelemetryData(extension), { success });
// this.telemetryService.publicLog('extensionGallery:uninstall', data);
// }
dispose(): void {
super.dispose();
this.disposables = dispose(this.disposables);
}
}
export class InstallAction extends Action {
export class CombinedInstallAction extends Action {
constructor(
label: string,
@IQuickOpenService protected quickOpenService: IQuickOpenService,
@IExtensionManagementService protected extensionManagementService: IExtensionManagementService,
@IMessageService protected messageService: IMessageService,
@ITelemetryService protected telemetryService: ITelemetryService,
@IInstantiationService protected instantiationService: IInstantiationService
) {
super('extensions.install', label, 'octicon octicon-cloud-download', true);
}
private installAction: InstallAction;
private uninstallAction: UninstallAction;
private disposables: IDisposable[] = [];
public run(extension: IExtension): TPromise<any> {
this.enabled = false;
constructor(private model: ExtensionsModel, private extension: IExtension) {
super('extensions.combinedInstall', '', '', false);
return this.extensionManagementService.getInstalled()
.then(installed => installed.some(e => extensionEquals(e, extension)))
.then(isUpdate => {
return this.extensionManagementService
.install(extension)
.then(() => this.onSuccess(extension, isUpdate), err => this.onError(err, extension, isUpdate))
.then(() => this.enabled = true)
.then(() => null);
});
}
this.installAction = new InstallAction(model, extension);
this.uninstallAction = new UninstallAction(model, extension);
this.disposables.push(this.installAction, this.uninstallAction);
private onSuccess(extension: IExtension, isUpdate: boolean) {
this.reportTelemetry(extension, isUpdate, true);
this.messageService.show(Severity.Info, {
message: nls.localize('success-installed', "'{0}' was successfully installed. Restart to enable it.", extension.displayName),
actions: [
CloseAction,
this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, nls.localize('restartNow', "Restart Now"))
]
});
this.disposables.push(this.installAction.addListener2(Action.ENABLED, () => this.update()));
this.disposables.push(this.uninstallAction.addListener2(Action.ENABLED, () => this.update()));
this.update();
}
private onError(err: Error, extension: IExtension, isUpdate: boolean) {
this.reportTelemetry(extension, isUpdate, false);
this.messageService.show(Severity.Error, err);
private update(): void {
if (this.installAction.enabled) {
this.enabled = true;
this.label = this.installAction.label;
this.class = this.installAction.class;
} else if (this.uninstallAction.enabled) {
this.enabled = true;
this.label = this.uninstallAction.label;
this.class = this.uninstallAction.class;
} else if (this.extension.state === ExtensionState.Installing) {
this.enabled = false;
this.label = this.installAction.label;
this.class = this.installAction.class;
} else {
this.enabled = false;
}
}
private reportTelemetry(extension: IExtension, isUpdate: boolean, success: boolean) {
const event = isUpdate ? 'extensionGallery:update' : 'extensionGallery:install';
const data = assign(getTelemetryData(extension), { success });
run(): TPromise<any> {
if (this.installAction.enabled) {
return this.installAction.run();
} else if (this.uninstallAction.enabled) {
return this.uninstallAction.run();
}
this.telemetryService.publicLog(event, data);
return TPromise.as(null);
}
}
export class UninstallAction extends Action {
constructor(
@IQuickOpenService protected quickOpenService: IQuickOpenService,
@IExtensionManagementService protected extensionManagementService: IExtensionManagementService,
@IMessageService protected messageService: IMessageService,
@ITelemetryService protected telemetryService: ITelemetryService,
@IInstantiationService protected instantiationService: IInstantiationService
) {
super('extensions.uninstall', nls.localize('uninstall', "Uninstall Extension"), 'octicon octicon-x', true);
dispose(): void {
super.dispose();
this.disposables = dispose(this.disposables);
}
}
public run(extension: IExtension): TPromise<any> {
if (!window.confirm(nls.localize('deleteSure', "Are you sure you want to uninstall '{0}'?", extension.displayName))) {
return TPromise.as(null);
}
export class UpdateAction extends Action {
this.enabled = false;
private static EnabledClass = 'extension-action update';
private static DisabledClass = `${ UpdateAction.EnabledClass } disabled`;
return this.extensionManagementService.getInstalled().then(localExtensions => {
const [local] = localExtensions.filter(local => extensionEquals(local, extension));
private disposables: IDisposable[] = [];
if (!local) {
return TPromise.wrapError(nls.localize('notFound', "Extension '{0}' not installed.", extension.displayName));
}
constructor(private model: ExtensionsModel, private extension: IExtension) {
super('extensions.update', nls.localize('updateAction', "Update"), UpdateAction.DisabledClass, false);
return this.extensionManagementService.uninstall(local)
.then(() => this.onSuccess(local), err => this.onError(err, local))
.then(() => this.enabled = true)
.then(() => null);
});
this.disposables.push(this.model.onChange(() => this.updateEnablement()));
this.updateEnablement();
}
private onSuccess(extension: IExtension) {
this.reportTelemetry(extension, true);
this.messageService.show(Severity.Info, {
message: nls.localize('success-uninstalled', "'{0}' was successfully uninstalled. Restart to deactivate it.", extension.displayName),
actions: [
CloseAction,
this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, nls.localize('restartNow2', "Restart Now"))
]
});
}
private updateEnablement(): void {
const canInstall = this.model.canInstall(this.extension);
const isInstalled = this.extension.state === ExtensionState.Installed;
private onError(err: Error, extension: IExtension) {
this.reportTelemetry(extension, false);
this.messageService.show(Severity.Error, err);
this.enabled = canInstall && isInstalled && this.extension.outdated;
this.class = this.enabled ? UpdateAction.EnabledClass : UpdateAction.DisabledClass;
}
private reportTelemetry(extension: IExtension, success: boolean) {
const data = assign(getTelemetryData(extension), { success });
run(): TPromise<any> {
return this.model.install(this.extension);
// this.enabled = false;
this.telemetryService.publicLog('extensionGallery:uninstall', data);
// return this.extensionManagementService.getInstalled()
// .then(installed => installed.some(({ manifest }) => extensionEquals(manifest, extension)))
// .then(isUpdate => {
// return this.extensionManagementService
// .install(extension)
// .then(() => this.onSuccess(extension, isUpdate), err => this.onError(err, extension, isUpdate))
// .then(() => this.enabled = true)
// .then(() => null);
// });
}
}
// private onSuccess(extension: IGalleryExtension, isUpdate: boolean) {
// this.reportTelemetry(extension, isUpdate, true);
// this.messageService.show(Severity.Info, {
// message: nls.localize('success-installed', "'{0}' was successfully installed. Restart to enable it.", extension.displayName || extension.name),
// actions: [
// CloseAction,
// this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, nls.localize('restartNow', "Restart Now"))
// ]
// });
// }
// private onError(err: Error, extension: IGalleryExtension, isUpdate: boolean) {
// this.reportTelemetry(extension, isUpdate, false);
// this.messageService.show(Severity.Error, err);
// }
// private reportTelemetry(extension: IGalleryExtension, isUpdate: boolean, success: boolean) {
// const event = isUpdate ? 'extensionGallery:update' : 'extensionGallery:install';
// const data = assign(getTelemetryData(extension), { success });
// this.telemetryService.publicLog(event, data);
// }
dispose(): void {
super.dispose();
this.disposables = dispose(this.disposables);
}
}
\ 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.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { append, emmet as $, addClass, removeClass } from 'vs/base/browser/dom';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDelegate } from 'vs/base/browser/ui/list/list';
import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging';
import { IExtension, ExtensionsModel } from './extensionsModel';
import { CombinedInstallAction, UpdateAction } from './extensionsActions';
import { Label } from './extensionsWidgets';
export interface ITemplateData {
extension: IExtension;
element: HTMLElement;
icon: HTMLElement;
name: HTMLElement;
version: HTMLElement;
author: HTMLElement;
description: HTMLElement;
actionbar: ActionBar;
disposables: IDisposable[];
}
export class Delegate implements IDelegate<IExtension> {
getHeight() { return 62; }
getTemplateId() { return 'extension'; }
}
const actionOptions = { icon: true, label: true };
export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
private _templates: ITemplateData[];
get templates(): ITemplateData[] { return this._templates; }
constructor(
private model: ExtensionsModel,
@IInstantiationService private instantiationService: IInstantiationService
) {
this._templates = [];
}
get templateId() { return 'extension'; }
renderTemplate(root: HTMLElement): ITemplateData {
const element = append(root, $('.extension'));
const icon = append(element, $('.icon'));
const details = append(element, $('.details'));
const header = append(details, $('.header'));
const name = append(header, $('span.name.ellipsis'));
const version = append(header, $('span.version.ellipsis'));
const author = append(header, $('span.author.ellipsis'));
const description = append(details, $('.description.ellipsis'));
const actionbar = new ActionBar(details, { animated: false });
const disposables = [];
const result = { extension: null, element, icon, name, version, author, description, actionbar, disposables };
this._templates.push(result);
return result;
}
renderPlaceholder(index: number, data: ITemplateData): void {
data.disposables = dispose(data.disposables);
addClass(data.element, 'loading');
data.extension = null;
data.icon.style.backgroundImage = '';
data.name.textContent = '';
data.version.textContent = '';
data.author.textContent = '';
data.description.textContent = '';
data.actionbar.clear();
}
renderElement(extension: IExtension, index: number, data: ITemplateData): void {
data.disposables = dispose(data.disposables);
removeClass(data.element, 'loading');
data.extension = extension;
data.icon.style.backgroundImage = `url("${ extension.iconUrl }")`;
data.name.textContent = extension.displayName;
data.author.textContent = extension.publisherDisplayName;
data.description.textContent = extension.description;
const version = new Label(data.version, this.model, extension, e => e.version);
const installAction = new CombinedInstallAction(this.model, extension);
const updateAction = new UpdateAction(this.model, extension);
data.actionbar.clear();
data.actionbar.push([updateAction, installAction], actionOptions);
data.disposables.push(version, installAction, updateAction);
}
disposeTemplate(data: ITemplateData): void {
const index = this._templates.indexOf(data);
if (index > -1) {
this._templates.splice(index, 1);
}
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/extensionsViewlet';
import Event, { Emitter } from 'vs/base/common/event';
import { index } from 'vs/base/common/arrays';
import { ThrottledDelayer } from 'vs/base/common/async';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IPager, mapPager } from 'vs/base/common/paging';
import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import * as semver from 'semver';
export enum ExtensionState {
Installing,
Installed,
// Uninstalling,
Uninstalled
}
export interface IExtension {
state: ExtensionState;
name: string;
displayName: string;
publisher: string;
publisherDisplayName: string;
version: string;
latestVersion: string;
description: string;
readmeUrl: string;
iconUrl: string;
installCount: number;
rating: number;
ratingCount: number;
outdated: boolean;
}
interface IExtensionStateProvider {
(extension: Extension): ExtensionState;
}
class Extension implements IExtension {
constructor(
private stateProvider: IExtensionStateProvider,
public local: ILocalExtension,
public gallery: IGalleryExtension = null
) {}
get name(): string {
return this.local ? this.local.manifest.name : this.gallery.name;
}
get displayName(): string {
if (this.local) {
return this.local.manifest.displayName || this.local.manifest.name;
}
return this.gallery.displayName || this.gallery.name;
}
get publisher(): string {
return this.local ? this.local.manifest.publisher : this.gallery.publisher;
}
get publisherDisplayName(): string {
if (this.local) {
if (this.local.metadata && this.local.metadata.publisherDisplayName) {
return this.local.metadata.publisherDisplayName;
}
return this.local.manifest.publisher;
}
return this.gallery.publisherDisplayName || this.gallery.publisher;
}
get version(): string {
return this.local ? this.local.manifest.version : this.gallery.versions[0].version;
}
get latestVersion(): string {
return this.gallery ? this.gallery.versions[0].version : this.local.manifest.version;
}
get description(): string {
return this.local ? this.local.manifest.description : this.gallery.description;
}
get readmeUrl(): string {
if (this.local && this.local.readmeUrl) {
return this.local.readmeUrl;
}
if (this.gallery && this.gallery.versions[0].readmeUrl) {
return this.gallery.versions[0].readmeUrl;
}
return null;
}
get iconUrl(): string {
if (this.local && this.local.manifest.icon) {
return `file://${ this.local.path }/${ this.local.manifest.icon }`;
}
if (this.gallery && this.gallery.versions[0].iconUrl) {
return this.gallery.versions[0].iconUrl;
}
return require.toUrl('./media/defaultIcon.png');
}
get state(): ExtensionState {
return this.stateProvider(this);
}
get installCount(): number {
return this.gallery ? this.gallery.installCount : null;
}
get rating(): number {
return this.gallery ? this.gallery.rating : null;
}
get ratingCount(): number {
return this.gallery ? this.gallery.ratingCount : null;
}
get outdated(): boolean {
return semver.gt(this.latestVersion, this.version);
}
}
export class ExtensionsModel {
private static SyncPeriod = 1000 * 60 * 60 * 12; // 12 hours
private stateProvider: IExtensionStateProvider;
private installing: { id: string; extension: Extension; }[] = [];
private installed: Extension[] = [];
private didTriggerSync: boolean = false;
private syncDelayer: ThrottledDelayer<void>;
private disposables: IDisposable[] = [];
private _onChange: Emitter<void> = new Emitter<void>();
get onChange(): Event<void> { return this._onChange.event; }
constructor(
@IExtensionManagementService private extensionService: IExtensionManagementService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService
) {
this.stateProvider = ext => this.getExtensionState(ext);
this.disposables.push(extensionService.onInstallExtension(({ id, gallery }) => this.onInstallExtension(id, gallery)));
this.disposables.push(extensionService.onDidInstallExtension(({ id, local, error }) => this.onDidInstallExtension(id, local, error)));
this.disposables.push(extensionService.onUninstallExtension((id) => this.onUninstallExtension(id)));
this.syncDelayer = new ThrottledDelayer<void>(ExtensionsModel.SyncPeriod);
}
getLocal(): TPromise<IExtension[]> {
return this.extensionService.getInstalled().then(result => {
const installedById = index(this.installed, e => e.local.id);
this.installed = result.map(local => {
const extension = installedById[local.id] || new Extension(this.stateProvider, local);
extension.local = local;
return extension;
});
const installing = this.installing
.filter(e => !this.installed.some(installed => installed.local.id === e.id))
.map(e => e.extension);
if (!this.didTriggerSync) {
this.didTriggerSync = true;
this.syncWithGallery(true);
}
this._onChange.fire();
return [...this.installed, ...installing];
});
}
queryGallery(options: IQueryOptions = {}): TPromise<IPager<IExtension>> {
return this.galleryService.query(options).then(result => {
const installedByGalleryId = index(this.installed, e => e.local.metadata ? e.local.metadata.id : '');
return mapPager(result, gallery => {
const id = gallery.id;
const installed = installedByGalleryId[id];
if (installed) {
installed.gallery = gallery;
this._onChange.fire();
return installed;
}
return new Extension(this.stateProvider, null, gallery);
});
});
}
private syncWithGallery(immediate = false): void {
const loop = () => this.doSyncWithGallery().then(() => this.syncWithGallery());
const delay = immediate ? 0 : ExtensionsModel.SyncPeriod;
this.syncDelayer.trigger(loop, delay);
}
private doSyncWithGallery(): TPromise<void> {
const ids = this.installed
.filter(e => !!(e.local && e.local.metadata))
.map(e => e.local.metadata.id);
if (ids.length === 0) {
return TPromise.as(null);
}
return this.queryGallery({ ids, pageSize: ids.length }) as TPromise<any>;
}
canInstall(extension: IExtension): boolean {
if (!(extension instanceof Extension)) {
return;
}
return !!(extension as Extension).gallery;
}
install(extension: IExtension): TPromise<void> {
if (!(extension instanceof Extension)) {
return;
}
const ext = extension as Extension;
const gallery = ext.gallery;
if (!gallery) {
return TPromise.wrapError<void>(new Error('Missing gallery'));
}
return this.extensionService.install(gallery);
}
uninstall(extension: IExtension): TPromise<void> {
if (!(extension instanceof Extension)) {
return;
}
const ext = extension as Extension;
const local = ext.local || this.installed.filter(e => e.local.metadata && ext.gallery && e.local.metadata.id === ext.gallery.id)[0].local;
if (!local) {
return TPromise.wrapError<void>(new Error('Missing local'));
}
return this.extensionService.uninstall(local);
}
private onInstallExtension(id: string, gallery: IGalleryExtension): void {
if (!gallery) {
return;
}
let extension = this.installed.filter(e => (e.local.metadata && e.local.metadata.id) === gallery.id)[0];
if (!extension) {
extension = new Extension(this.stateProvider, null, gallery);
}
extension.gallery = gallery;
this.installing.push({ id, extension });
this._onChange.fire();
}
private onDidInstallExtension(id: string, local: ILocalExtension, error: Error): void {
const installing = this.installing.filter(e => e.id === id)[0];
if (!installing) {
return;
}
const extension = installing.extension;
extension.local = local;
this.installing = this.installing.filter(e => e.id !== id);
const galleryId = local.metadata && local.metadata.id;
const installed = this.installed.filter(e => (e.local.metadata && e.local.metadata.id) === galleryId)[0];
if (galleryId && installed) {
installed.local = local;
} else {
this.installed.push(extension);
}
this._onChange.fire();
}
private onUninstallExtension(id: string): void {
const previousLength = this.installed.length;
this.installed = this.installed.filter(e => e.local.id !== id);
if (previousLength === this.installed.length) {
return;
}
this._onChange.fire();
}
private getExtensionState(extension: Extension): ExtensionState {
if (this.installed.some(e => e === extension || (e.gallery && extension.gallery && e.gallery.id === extension.gallery.id))) {
return ExtensionState.Installed;
}
if (extension.gallery && this.installing.some(e => e.extension.gallery.id === extension.gallery.id)) {
return ExtensionState.Installing;
}
return ExtensionState.Uninstalled;
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/extensionsViewlet';
import { localize } from 'vs/nls';
import { ThrottledDelayer, always } from 'vs/base/common/async';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Builder, Dimension } from 'vs/base/browser/builder';
import { mapEvent, filterEvent } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Viewlet } from 'vs/workbench/browser/viewlet';
import { append, emmet as $ } from 'vs/base/browser/dom';
import { PagedModel, SinglePagePagedModel } from 'vs/base/common/paging';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { PagedList } from 'vs/base/browser/ui/list/listPaging';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Delegate, Renderer } from './extensionsList';
import { ExtensionsModel, IExtension } from './extensionsModel';
import { IExtensionsViewlet } from './extensions';
import { IExtensionManagementService, IExtensionGalleryService, SortBy } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionsInput } from '../common/extensionsInput';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
export class ExtensionsViewlet extends Viewlet implements IExtensionsViewlet {
static ID: string = 'workbench.viewlet.extensions';
private searchDelayer: ThrottledDelayer<any>;
private root: HTMLElement;
private searchBox: HTMLInputElement;
private extensionsBox: HTMLElement;
private model: ExtensionsModel;
private list: PagedList<IExtension>;
private disposables: IDisposable[] = [];
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService,
@IExtensionManagementService private extensionService: IExtensionManagementService,
@IProgressService private progressService: IProgressService,
@IInstantiationService private instantiationService: IInstantiationService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
) {
super(ExtensionsViewlet.ID, telemetryService);
this.searchDelayer = new ThrottledDelayer(500);
this.model = instantiationService.createInstance(ExtensionsModel);
}
create(parent: Builder): TPromise<void> {
super.create(parent);
parent.addClass('extensions-viewlet');
this.root = parent.getHTMLElement();
const header = append(this.root, $('.header'));
this.searchBox = append(header, $<HTMLInputElement>('input.search-box'));
this.searchBox.type = 'search';
this.searchBox.placeholder = localize('searchExtensions', "Search Extensions in Marketplace");
this.extensionsBox = append(this.root, $('.extensions'));
const delegate = new Delegate();
const renderer = this.instantiationService.createInstance(Renderer, this.model);
this.list = new PagedList(this.extensionsBox, delegate, [renderer]);
const onRawKeyDown = domEvent(this.searchBox, 'keydown');
const onKeyDown = mapEvent(onRawKeyDown, e => new StandardKeyboardEvent(e));
const onEnter = filterEvent(onKeyDown, e => e.keyCode === KeyCode.Enter);
const onEscape = filterEvent(onKeyDown, e => e.keyCode === KeyCode.Escape);
const onUpArrow = filterEvent(onKeyDown, e => e.keyCode === KeyCode.UpArrow);
const onDownArrow = filterEvent(onKeyDown, e => e.keyCode === KeyCode.DownArrow);
onEnter(() => this.onEnter(), null, this.disposables);
onEscape(() => this.onEscape(), null, this.disposables);
onUpArrow(() => this.onUpArrow(), null, this.disposables);
onDownArrow(() => this.onDownArrow(), null, this.disposables);
const onInput = domEvent(this.searchBox, 'input');
onInput(() => this.triggerSearch(), null, this.disposables);
this.list.onDOMFocus(() => this.searchBox.focus(), null, this.disposables);
this.list.onSelectionChange(e => {
const [extension] = e.elements;
if (!extension) {
return;
}
return this.editorService.openEditor(new ExtensionsInput(this.model, extension));
}, null, this.disposables);
return TPromise.as(null);
}
setVisible(visible:boolean): TPromise<void> {
return super.setVisible(visible).then(() => {
if (visible) {
this.searchBox.focus();
this.searchBox.setSelectionRange(0,this.searchBox.value.length);
this.triggerSearch(true);
} else {
this.list.model = new SinglePagePagedModel([]);
}
});
}
focus(): void {
this.searchBox.focus();
}
layout({ height }: Dimension):void {
this.list.layout(height - 38);
}
search(text: string, immediate = false): void {
this.searchBox.value = text;
this.triggerSearch(immediate);
}
private triggerSearch(immediate = false): void {
const text = this.searchBox.value;
this.searchDelayer.trigger(() => this.doSearch(text), immediate || !text ? 0 : 500);
}
private doSearch(text: string = ''): TPromise<any> {
const progressRunner = this.progressService.show(true);
let promise: TPromise<PagedModel<IExtension>>;
if (!text) {
promise = this.model.getLocal()
.then(result => new SinglePagePagedModel(result));
} else if (/@outdated/i.test(text)) {
promise = this.model.getLocal()
.then(result => result.filter(e => e.outdated))
.then(result => new SinglePagePagedModel(result));
} else if (/@popular/i.test(text)) {
promise = this.model.queryGallery({ sortBy: SortBy.InstallCount })
.then(result => new PagedModel(result));
} else {
promise = this.model.queryGallery({ text })
.then(result => new PagedModel(result));
}
return always(promise, () => progressRunner.done())
.then(model => this.list.model = model);
}
private onEnter(): void {
this.list.setSelection(...this.list.getFocus());
}
private onEscape(): void {
this.searchBox.value = '';
this.triggerSearch(true);
}
private onUpArrow(): void {
this.list.focusPrevious();
}
private onDownArrow(): void {
this.list.focusNext();
}
dispose(): void {
this.disposables = dispose(this.disposables);
super.dispose();
}
}
......@@ -3,168 +3,82 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import Severity from 'vs/base/common/severity';
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, combinedDisposable } from 'vs/base/common/lifecycle';
import { onUnexpectedPromiseError } from 'vs/base/common/errors';
import { assign } from 'vs/base/common/objects';
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { IOutputService } from 'vs/workbench/parts/output/common/output';
import { IExtensionService, IMessage } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionManagementService, IExtensionGalleryService, ExtensionsLabel, ExtensionsChannelId, IExtension, IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IQuickOpenService } from 'vs/workbench/services/quickopen/common/quickOpenService';
import { getOutdatedExtensions } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
interface IState {
errors: IMessage[];
installing: IExtensionManifest[];
outdated: IExtension[];
}
'use strict';
const InitialState: IState = {
errors: [],
installing: [],
outdated: []
};
import 'vs/css!./media/extensionsWidgets';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IExtension, ExtensionsModel } from './extensionsModel';
import { append, emmet as $, addClass } from 'vs/base/browser/dom';
function extensionEquals(one: IExtensionManifest, other: IExtensionManifest): boolean {
return one.publisher === other.publisher && one.name === other.name;
export interface IOptions {
small?: boolean;
}
const OutdatedPeriod = 12 * 60 * 60 * 1000; // every 12 hours
export class ExtensionsStatusbarItem implements IStatusbarItem {
export class Label implements IDisposable {
private domNode: HTMLElement;
private state: IState = InitialState;
private outdatedDelayer = new ThrottledDelayer<void>(OutdatedPeriod);
private listener: IDisposable;
constructor(
@IExtensionService private extensionService: IExtensionService,
@IOutputService private outputService: IOutputService,
@IExtensionManagementService protected extensionManagementService: IExtensionManagementService,
@IExtensionGalleryService protected extensionGalleryService: IExtensionGalleryService,
@IInstantiationService protected instantiationService: IInstantiationService,
@IQuickOpenService protected quickOpenService: IQuickOpenService,
@ITelemetryService protected telemetrService: ITelemetryService
) {}
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.extensionManagementService.onInstallExtension(this.onInstallExtension, this, disposables);
this.extensionManagementService.onDidInstallExtension(this.onDidInstallExtension, this, disposables);
this.extensionManagementService.onDidUninstallExtension(this.onDidUninstallExtension, this, disposables);
return combinedDisposable(disposables);
element: HTMLElement,
model: ExtensionsModel,
extension: IExtension,
fn: (extension: IExtension) => string
) {
const render = () => element.textContent = fn(extension);
render();
this.listener = model.onChange(render);
}
private updateState(obj: any): void {
this.state = assign(this.state, obj);
this.onStateChange();
dispose(): void {
this.listener = dispose(this.listener);
}
}
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");
}
}
export class RatingsWidget implements IDisposable {
static ID: string = 'workbench.editor.extension';
private onClick(): void {
if (this.hasErrors) {
this.telemetrService.publicLog('extensionWidgetClick', {mode : 'hasErrors'});
this.showErrors(this.state.errors);
this.updateState({ errors: [] });
} else if (this.hasUpdates) {
this.telemetrService.publicLog('extensionWidgetClick', {mode : 'hasUpdate'});
this.quickOpenService.show(`ext update `);
} else {
this.telemetrService.publicLog('extensionWidgetClick', {mode : 'none'});
this.quickOpenService.show(`>${ExtensionsLabel}: `);
private disposables: IDisposable[] = [];
constructor(
private container: HTMLElement,
private model: ExtensionsModel,
private extension: IExtension,
options: IOptions = {}
) {
this.disposables.push(this.model.onChange(() => this.render()));
addClass(container, 'extension-ratings');
if (options.small) {
addClass(container, 'small');
}
}
private showErrors(errors: IMessage[]): void {
const promise = onUnexpectedPromiseError(this.extensionManagementService.getInstalled());
promise.done(installed => {
errors.forEach(m => {
const extension = installed.filter(ext => ext.path === m.source).pop();
const name = extension && extension.name;
const message = name ? `${ name }: ${ m.message }` : m.message;
const outputChannel = this.outputService.getChannel(ExtensionsChannelId);
outputChannel.append(message);
outputChannel.show(true);
});
});
this.render();
}
private onInstallExtension(manifest: IExtensionManifest): void {
const installing = [...this.state.installing, manifest];
this.updateState({ installing });
}
private render(): void {
const rating = this.extension.rating;
this.container.innerHTML = '';
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);
}
if (rating === null) {
return;
}
private onDidUninstallExtension(): void {
this.outdatedDelayer.trigger(() => this.checkOutdated(), 0);
}
for (let i = 1; i <= 5; i++) {
if (rating >= i) {
append(this.container, $('span.full.star'));
} else if (rating >= i - 0.5) {
append(this.container, $('span.half.star'));
} else {
append(this.container, $('span.empty.star'));
}
}
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 });
});
const count = append(this.container, $('span.count'));
count.textContent = String(this.extension.ratingCount);
}
private checkOutdated(): TPromise<void> {
return getOutdatedExtensions(this.extensionManagementService, this.extensionGalleryService)
.then(null, _ => []) // ignore errors
.then(outdated => {
this.updateState({ outdated });
// repeat this later
this.outdatedDelayer.trigger(() => this.checkOutdated());
});
dispose(): void {
this.disposables = dispose(this.disposables);
}
}
\ No newline at end of file
}
......@@ -5,19 +5,14 @@
import nls = require('vs/nls');
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 { IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService, ExtensionsLabel } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IMessageService } from 'vs/platform/message/common/message';
import Severity from 'vs/base/common/severity';
import { IWorkspaceContextService } from 'vs/workbench/services/workspace/common/contextService';
import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions';
import wbaregistry = require('vs/workbench/common/actionRegistry');
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { ListExtensionsAction, InstallExtensionAction, ListOutdatedExtensionsAction, ListSuggestedExtensionsAction } from './extensionsActions';
import { IQuickOpenRegistry, Extensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
import {ipcRenderer as ipc} from 'electron';
interface IInstallExtensionsRequest {
......@@ -42,55 +37,8 @@ export class ExtensionsWorkbenchExtension implements IWorkbenchContribution {
this.install(options.extensionsToInstall).done(null, errors.onUnexpectedError);
}
const actionRegistry = (<wbaregistry.IWorkbenchActionRegistry> platform.Registry.as(wbaregistry.Extensions.WorkbenchActions));
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ListExtensionsAction, ListExtensionsAction.ID, ListExtensionsAction.LABEL), 'Extensions: Show Installed Extensions', ExtensionsLabel);
(<IQuickOpenRegistry>platform.Registry.as(Extensions.Quickopen)).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
'vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen',
'LocalExtensionsHandler',
'ext ',
nls.localize('localExtensionsCommands', "Show Local Extensions")
)
);
if (galleryService.isEnabled()) {
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(InstallExtensionAction, InstallExtensionAction.ID, InstallExtensionAction.LABEL), 'Extensions: Install Extension', ExtensionsLabel);
(<IQuickOpenRegistry>platform.Registry.as(Extensions.Quickopen)).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
'vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen',
'GalleryExtensionsHandler',
'ext install ',
nls.localize('galleryExtensionsCommands', "Install Gallery Extensions"),
true
)
);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ListOutdatedExtensionsAction, ListOutdatedExtensionsAction.ID, ListOutdatedExtensionsAction.LABEL), 'Extensions: Show Outdated Extensions', ExtensionsLabel);
(<IQuickOpenRegistry>platform.Registry.as(Extensions.Quickopen)).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
'vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen',
'OutdatedExtensionsHandler',
'ext update ',
nls.localize('outdatedExtensionsCommands', "Update Outdated Extensions")
)
);
// add extension tips services
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ListSuggestedExtensionsAction, ListSuggestedExtensionsAction.ID, ListSuggestedExtensionsAction.LABEL), 'Extensions: Show Extension Recommendations', ExtensionsLabel);
(<IQuickOpenRegistry>platform.Registry.as(Extensions.Quickopen)).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
'vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen',
'SuggestedExtensionHandler',
'ext recommend ',
nls.localize('suggestedExtensionsCommands', "Show Extension Recommendations")
)
);
}
// const actionRegistry = (<wbaregistry.IWorkbenchActionRegistry> platform.Registry.as(wbaregistry.Extensions.WorkbenchActions));
// actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ListExtensionsAction, ListExtensionsAction.ID, ListExtensionsAction.LABEL), 'Extensions: Show Installed Extensions', ExtensionsLabel);
}
private registerListeners(): void {
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 10.3 10" style="enable-background:new 0 0 10.3 10;" xml:space="preserve">
<style type="text/css">
.st0{display:none;}
.st1{display:inline;opacity:0.2;fill:#ED1C24;}
.st2{display:inline;opacity:0.43;fill:#39B54A;}
.st3{fill:#353535;}
.st4{fill:#FFFFFF;}
.st5{display:inline;opacity:0.8;fill:#353535;}
.st6{display:inline;}
.st7{display:inline;opacity:0.8;fill:#321F5A;}
.st8{opacity:0.15;fill:#353535;}
.st9{display:inline;opacity:0.34;}
.st10{fill:#070909;}
.st11{fill:#333333;}
.st12{fill:#7551A0;}
.st13{fill:#D2D2D2;}
.st14{fill:#E6E7E8;}
.st15{fill:#3076BC;}
.st16{display:none;fill:#3076BC;}
.st17{display:inline;fill:#321F5A;}
.st18{fill:#D63F26;}
.st19{display:none;fill:none;stroke:#BCBEC0;stroke-miterlimit:10;}
.st20{opacity:0.52;}
.st21{fill:none;stroke:#BE1E2D;stroke-miterlimit:10;}
.st22{fill:#BE1E2D;}
.st23{display:none;opacity:0.2;}
.st24{display:inline;opacity:0.2;}
.st25{fill:#656565;}
.st26{fill:#D1D3D4;}
.st27{fill:#EEEEEE;}
.st28{display:none;fill-rule:evenodd;clip-rule:evenodd;fill:#F4F4F4;}
.st29{fill:#505150;}
.st30{display:none;fill:#BFD8E8;}
.st31{fill:#224096;}
.st32{fill:#17244E;}
.st33{display:inline;fill:#353535;}
.st34{display:none;fill:#F1F2F2;}
.st35{fill:#009444;}
.st36{fill:none;stroke:#505150;stroke-miterlimit:10;}
.st37{fill:#B9D532;}
.st38{fill:none;stroke:#D9D9D8;stroke-width:0.72;stroke-linejoin:round;stroke-miterlimit:10;}
.st39{fill:none;stroke:#D2D2D2;stroke-miterlimit:10;}
.st40{display:inline;fill:#333333;}
.st41{display:inline;fill:#7551A0;}
.st42{display:inline;fill:#D2D2D2;}
.st43{fill-rule:evenodd;clip-rule:evenodd;fill:#EEEEEE;}
.st44{fill:#321F5A;}
.st45{fill:none;stroke:#BCBEC0;stroke-miterlimit:10;}
.st46{opacity:0.53;fill:#FFFFFF;}
.st47{fill:none;stroke:#ED1C24;stroke-miterlimit:10;}
.st48{fill:#ED1C24;}
.st49{display:inline;opacity:0.1;fill:#E71E27;}
.st50{display:inline;opacity:0.3;}
.st51{fill:#E71E27;}
.st52{display:inline;opacity:0.2;fill:#E71E27;}
.st53{display:inline;fill:none;stroke:#ED1C24;stroke-miterlimit:10;}
.st54{fill:none;stroke:#2E3192;stroke-miterlimit:10;}
.st55{fill:#2E3192;}
.st56{fill:none;stroke:#FFF200;stroke-miterlimit:10;}
.st57{fill:#FFF200;}
</style>
<g id="grids">
</g>
<g id="Layer_2" class="st0">
</g>
<g id="home">
</g>
<g id="VSOdetails">
<g id="overview2">
</g>
</g>
<g id="reviews">
</g>
<g id="QA_2">
</g>
<g id="CARDS-RL">
<g>
<g>
<path class="st13" d="M8,9.7c-0.1,0-0.2,0-0.2-0.1L5.1,7.8L2.5,9.6c-0.1,0.1-0.3,0.1-0.5,0C1.9,9.5,1.9,9.4,1.9,9.2l1-3.1
L0.3,4.2C0.2,4.1,0.1,4,0.2,3.8c0.1-0.2,0.2-0.3,0.4-0.3h3.2l1-3.1C4.8,0.3,5,0.2,5.1,0.2c0.2,0,0.3,0.1,0.4,0.3l1,3.1h3.2
c0.2,0,0.3,0.1,0.4,0.3c0.1,0.2,0,0.3-0.1,0.5L7.4,6.1l1,3.1c0.1,0.2,0,0.3-0.1,0.5C8.2,9.7,8.1,9.7,8,9.7z"/>
</g>
</g>
</g>
<g id="sticky">
</g>
<g id="REDLINES" class="st0">
</g>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 10.3 10" style="enable-background:new 0 0 10.3 10;" xml:space="preserve">
<style type="text/css">
.st0{display:none;}
.st1{display:inline;opacity:0.2;fill:#ED1C24;}
.st2{display:inline;opacity:0.43;fill:#39B54A;}
.st3{fill:#353535;}
.st4{fill:#FFFFFF;}
.st5{display:inline;opacity:0.8;fill:#353535;}
.st6{display:inline;}
.st7{display:inline;opacity:0.8;fill:#321F5A;}
.st8{opacity:0.15;fill:#353535;}
.st9{display:inline;opacity:0.34;}
.st10{fill:#070909;}
.st11{fill:#333333;}
.st12{fill:#7551A0;}
.st13{fill:#D2D2D2;}
.st14{fill:#E6E7E8;}
.st15{fill:#3076BC;}
.st16{display:none;fill:#3076BC;}
.st17{display:inline;fill:#321F5A;}
.st18{fill:#FF8E00;}
.st19{display:none;fill:none;stroke:#BCBEC0;stroke-miterlimit:10;}
.st20{opacity:0.52;}
.st21{fill:none;stroke:#BE1E2D;stroke-miterlimit:10;}
.st22{fill:#BE1E2D;}
.st23{display:none;opacity:0.2;}
.st24{display:inline;opacity:0.2;}
.st25{fill:#656565;}
.st26{fill:#D1D3D4;}
.st27{fill:#EEEEEE;}
.st28{display:none;fill-rule:evenodd;clip-rule:evenodd;fill:#F4F4F4;}
.st29{fill:#505150;}
.st30{display:none;fill:#BFD8E8;}
.st31{fill:#224096;}
.st32{fill:#17244E;}
.st33{display:inline;fill:#353535;}
.st34{display:none;fill:#F1F2F2;}
.st35{fill:#009444;}
.st36{fill:none;stroke:#505150;stroke-miterlimit:10;}
.st37{fill:#B9D532;}
.st38{fill:none;stroke:#D9D9D8;stroke-width:0.72;stroke-linejoin:round;stroke-miterlimit:10;}
.st39{fill:none;stroke:#D2D2D2;stroke-miterlimit:10;}
.st40{display:inline;fill:#333333;}
.st41{display:inline;fill:#7551A0;}
.st42{display:inline;fill:#D2D2D2;}
.st43{fill-rule:evenodd;clip-rule:evenodd;fill:#EEEEEE;}
.st44{fill:#321F5A;}
.st45{fill:none;stroke:#BCBEC0;stroke-miterlimit:10;}
.st46{opacity:0.53;fill:#FFFFFF;}
.st47{fill:none;stroke:#ED1C24;stroke-miterlimit:10;}
.st48{fill:#ED1C24;}
.st49{display:inline;opacity:0.1;fill:#E71E27;}
.st50{display:inline;opacity:0.3;}
.st51{fill:#E71E27;}
.st52{display:inline;opacity:0.2;fill:#E71E27;}
.st53{display:inline;fill:none;stroke:#ED1C24;stroke-miterlimit:10;}
.st54{fill:none;stroke:#2E3192;stroke-miterlimit:10;}
.st55{fill:#2E3192;}
.st56{fill:none;stroke:#FFF200;stroke-miterlimit:10;}
.st57{fill:#FFF200;}
</style>
<g id="grids">
</g>
<g id="Layer_2" class="st0">
</g>
<g id="home">
</g>
<g id="VSOdetails">
<g id="overview2">
</g>
</g>
<g id="reviews">
</g>
<g id="QA_2">
</g>
<g id="CARDS-RL">
<g>
<g>
<path class="st18" d="M8,9.8c-0.1,0-0.2,0-0.2-0.1L5.2,7.8L2.6,9.7c-0.1,0.1-0.3,0.1-0.5,0C1.9,9.6,1.9,9.4,1.9,9.2l1-3.1
L0.3,4.3C0.2,4.2,0.1,4,0.2,3.8c0.1-0.2,0.2-0.3,0.4-0.3h3.2l1-3.1C4.8,0.3,5,0.2,5.2,0.2c0.2,0,0.3,0.1,0.4,0.3l1,3.1h3.2
c0.2,0,0.3,0.1,0.4,0.3c0.1,0.2,0,0.3-0.1,0.5L7.4,6.2l1,3.1c0.1,0.2,0,0.3-0.1,0.5C8.2,9.7,8.1,9.8,8,9.8z"/>
</g>
</g>
</g>
<g id="sticky">
</g>
<g id="REDLINES" class="st0">
</g>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 10.3 10" style="enable-background:new 0 0 10.3 10;" xml:space="preserve">
<style type="text/css">
.st0{display:none;}
.st1{display:inline;opacity:0.2;fill:#ED1C24;}
.st2{display:inline;opacity:0.43;fill:#39B54A;}
.st3{fill:#353535;}
.st4{fill:#FFFFFF;}
.st5{display:inline;opacity:0.8;fill:#353535;}
.st6{display:inline;}
.st7{display:inline;opacity:0.8;fill:#321F5A;}
.st8{opacity:0.15;fill:#353535;}
.st9{display:inline;opacity:0.34;}
.st10{fill:#070909;}
.st11{fill:#333333;}
.st12{fill:#7551A0;}
.st13{fill:#D2D2D2;}
.st14{fill:#E6E7E8;}
.st15{fill:#3076BC;}
.st16{display:none;fill:#3076BC;}
.st17{display:inline;fill:#321F5A;}
.st18{fill:#FF8E00;}
.st19{display:none;fill:none;stroke:#BCBEC0;stroke-miterlimit:10;}
.st20{opacity:0.52;}
.st21{fill:none;stroke:#BE1E2D;stroke-miterlimit:10;}
.st22{fill:#BE1E2D;}
.st23{display:none;opacity:0.2;}
.st24{display:inline;opacity:0.2;}
.st25{fill:#656565;}
.st26{fill:#D1D3D4;}
.st27{fill:#EEEEEE;}
.st28{display:none;fill-rule:evenodd;clip-rule:evenodd;fill:#F4F4F4;}
.st29{fill:#505150;}
.st30{display:none;fill:#BFD8E8;}
.st31{fill:#224096;}
.st32{fill:#17244E;}
.st33{display:inline;fill:#353535;}
.st34{display:none;fill:#F1F2F2;}
.st35{fill:#009444;}
.st36{fill:none;stroke:#505150;stroke-miterlimit:10;}
.st37{fill:#B9D532;}
.st38{fill:none;stroke:#D9D9D8;stroke-width:0.72;stroke-linejoin:round;stroke-miterlimit:10;}
.st39{fill:none;stroke:#D2D2D2;stroke-miterlimit:10;}
.st40{display:inline;fill:#333333;}
.st41{display:inline;fill:#7551A0;}
.st42{display:inline;fill:#D2D2D2;}
.st43{fill-rule:evenodd;clip-rule:evenodd;fill:#EEEEEE;}
.st44{fill:#321F5A;}
.st45{fill:none;stroke:#BCBEC0;stroke-miterlimit:10;}
.st46{opacity:0.53;fill:#FFFFFF;}
.st47{fill:none;stroke:#ED1C24;stroke-miterlimit:10;}
.st48{fill:#ED1C24;}
.st49{display:inline;opacity:0.1;fill:#E71E27;}
.st50{display:inline;opacity:0.3;}
.st51{fill:#E71E27;}
.st52{display:inline;opacity:0.2;fill:#E71E27;}
.st53{display:inline;fill:none;stroke:#ED1C24;stroke-miterlimit:10;}
.st54{fill:none;stroke:#2E3192;stroke-miterlimit:10;}
.st55{fill:#2E3192;}
.st56{fill:none;stroke:#FFF200;stroke-miterlimit:10;}
.st57{fill:#FFF200;}
</style>
<g id="grids">
</g>
<g id="Layer_2" class="st0">
</g>
<g id="home">
</g>
<g id="VSOdetails">
<g id="overview2">
</g>
</g>
<g id="reviews">
</g>
<g id="QA_2">
</g>
<g id="CARDS-RL">
<g>
<path class="st13" d="M10.1,3.8c-0.1-0.2-0.2-0.3-0.4-0.3H6.5l-1-3.1C5.5,0.3,5.3,0.2,5.2,0.2v7.6l2.6,1.9C7.8,9.7,7.9,9.7,8,9.7
s0.2,0,0.2-0.1c0.1-0.1,0.2-0.3,0.1-0.5l-1-3.1L10,4.2C10.1,4.1,10.2,4,10.1,3.8z"/>
<path class="st18" d="M5.2,0.2L5.2,0.2C5,0.2,4.8,0.3,4.8,0.5l-1,3.1H0.6c-0.2,0-0.3,0.1-0.4,0.3C0.1,4,0.2,4.1,0.3,4.2l2.6,1.9
l-1,3.1c-0.1,0.2,0,0.3,0.1,0.5c0.1,0.1,0.3,0.1,0.5,0l2.6-1.9l0,0V0.2z"/>
</g>
</g>
<g id="sticky">
</g>
<g id="REDLINES" class="st0">
</g>
</svg>
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-action-bar .action-item .action-label.extension-action {
border: 1px solid #CCC;
color: #6C6C6C;
background-color: #E2E2E2;
padding: 0 5px;
line-height: initial;
}
.monaco-action-bar .action-item:not(.disabled):hover .action-label.extension-action {
background-color: #D9D9D9;
}
.monaco-action-bar .action-item:not(.disabled):active .action-label.extension-action {
background-color: #C9C9C9;
}
.vs-dark .monaco-action-bar .action-item .action-label.extension-action {
border: 1px solid #545454;
color: #CCC;
background-color: #3A3A3A;
}
.vs-dark .monaco-action-bar .action-item:not(.disabled):hover .action-label.extension-action {
background-color: #464646;
}
.vs-dark .monaco-action-bar .action-item:not(.disabled):active .action-label.extension-action {
background-color: #505050;
}
.monaco-action-bar .action-item .action-label.extension-action.install,
.monaco-action-bar .action-item .action-label.extension-action.update {
color: white;
background-color: #327e36;
border-color: #519A55;
}
.monaco-action-bar .action-item:not(.disabled):hover .action-label.extension-action.install,
.monaco-action-bar .action-item:not(.disabled):hover .action-label.extension-action.update {
background-color: #478E4B;
}
.monaco-action-bar .action-item:not(.disabled):active .action-label.extension-action.install,
.monaco-action-bar .action-item:not(.disabled):active .action-label.extension-action.update {
background-color: #6DA770;
}
.monaco-action-bar .action-item.disabled .action-label.extension-action.update {
display: none;
}
\ 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.
*--------------------------------------------------------------------------------------------*/
.extension-editor {
height: 100%;
overflow-y: scroll;
display: flex;
flex-direction: column;
}
.extension-editor a {
color: inherit;
}
.extension-editor > .header {
display: flex;
height: 128px;
background: rgba(128, 128, 128, 0.15);
padding: 20px;
overflow: hidden;
font-size: 14px;
}
.extension-editor > .header > .icon {
height: 128px;
width: 128px;
min-width: 128px;
background-size: 128px;
background-repeat: no-repeat;
background-position: center center;
}
.extension-editor > .header > .details {
flex: 1;
padding-left: 20px;
overflow: hidden;
}
.extension-editor > .header > .details > .name {
font-size: 26px;
font-weight: 600;
line-height: normal;
white-space: nowrap;
}
.extension-editor > .header > .details > .subtitle {
padding-top: 10px;
white-space: nowrap;
height: 20px;
line-height: 20px;
}
.extension-editor > .header > .details > .subtitle > .publisher {
font-size: 18px;
}
.extension-editor > .header > .details > .subtitle > .install > .count {
margin-left: 6px;
}
.extension-editor > .header > .details > .subtitle > span:not(:first-child):not(:empty),
.extension-editor > .header > .details > .subtitle > a:not(:first-child):not(:empty) {
border-left: 1px solid rgba(128, 128, 128, 0.7);
margin-left: 14px;
padding-left: 14px;
}
.extension-editor > .header > .details > .description {
margin-top: 14px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.extension-editor > .header > .details > .actions {
margin-top: 14px;
}
.extension-editor > .header > .details > .actions > .monaco-action-bar {
text-align: initial;
}
.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container {
justify-content: flex-start;
}
.extension-editor > .body {
flex: 1;
overflow-y: scroll;
overflow-x: hidden;
padding: 20px;
}
.extension-editor > .body img {
max-width: 100%;
}
.extension-editor > .body.loading {
background-image: url('loading.svg');
background-position: center center;
background-repeat: no-repeat;
}
\ No newline at end of file
......@@ -198,4 +198,13 @@
background-size: 8px;
background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.5) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.5) 50%, rgba(255, 255, 255, 0.5) 75%, transparent 75%, transparent);
animation: move-background 0.5s linear infinite;
}
/* Global action */
.monaco-workbench > .activitybar .monaco-action-bar .action-label.extensions {
background: url('extensions-status.svg');
background-size: 22px;
background-repeat: no-repeat;
background-position: 50% !important;
}
\ 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.
*--------------------------------------------------------------------------------------------*/
.extensions-viewlet {
height: 100%;
}
.extensions-viewlet > .header {
height: 38px;
box-sizing: border-box;
padding: 5px 9px 5px 16px;
}
.extensions-viewlet > .header > .search-box {
width: 100%;
height: 26px;
box-sizing: border-box;
padding: 0 3px;
}
.extensions-viewlet > .extensions {
height: calc(100% - 38px);
}
.extensions-viewlet > .extensions .extension {
box-sizing: border-box;
width: 100%;
height: 100%;
padding: 0 19px 0 16px;
overflow: hidden;
display: flex;
}
.extensions-viewlet > .extensions .extension.loading {
background-image: url('loading.svg');
background-position: center center;
background-repeat: no-repeat;
}
.extensions-viewlet > .extensions .extension > .icon {
width: 42px;
height: 42px;
padding: 10px 14px 10px 0;
flex-shrink: 0;
background-repeat: no-repeat;
background-size: 42px;
background-position: left center;
}
.extensions-viewlet > .extensions .extension.loading > .icon {
display: none;
}
.extensions-viewlet > .extensions .extension > .details {
flex: 1;
padding: 4px 0;
overflow: hidden;
}
.extensions-viewlet > .extensions .extension > .details > .header {
display: flex;
}
.extensions-viewlet > .extensions .extension > .details > .header > .name {
font-weight: bold;
}
.extensions-viewlet > .extensions .extension > .details > .header > .version {
opacity: 0.6;
font-size: 80%;
padding-left: 6px;
}
.extensions-viewlet > .extensions .extension > .details > .header > .author {
flex: 1;
font-size: 90%;
text-align: right;
padding-left: 6px;
}
.extensions-viewlet > .extensions .extension > .details > .monaco-action-bar .action-label {
margin-right: 0;
margin-left: 0.3em;
}
.extensions-viewlet > .extensions .extension .ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/*.extensions-viewlet > .extensions .extension > .details {
width: 100%;
}*/
\ 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.
*--------------------------------------------------------------------------------------------*/
.extension-ratings {
display: inline-block;
}
.extension-ratings > .star {
display: inline-block;
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-position: center center;
}
.extension-ratings > .star:not(:first-child) {
margin-left: 3px;
}
.extension-ratings.small > .star {
width: 12px;
height: 12px;
}
.extension-ratings > .full {
background-image: url('./FullStarLight.svg');
}
.extension-ratings > .half {
background-image: url('./HalfStarLight.svg');
}
.extension-ratings > .empty {
background-image: url('./EmptyStar.svg');
}
.extension-ratings > .count {
margin-left: 6px;
}
\ No newline at end of file
......@@ -44,7 +44,6 @@ import 'vs/workbench/parts/markers/markers.contribution';
import 'vs/workbench/parts/html/browser/html.contribution';
import 'vs/workbench/parts/extensions/electron-browser/extensions.contribution';
import 'vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen';
import 'vs/workbench/parts/output/browser/output.contribution';
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册