extensionsViewlet.ts 8.7 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

J
Joao Moreno 已提交
8
import 'vs/css!./media/extensionsViewlet';
J
Joao Moreno 已提交
9 10
import { localize } from 'vs/nls';
import { ThrottledDelayer, always } from 'vs/base/common/async';
J
Joao Moreno 已提交
11 12 13
import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Builder, Dimension } from 'vs/base/browser/builder';
J
Joao Moreno 已提交
14
import { onUnexpectedError } from 'vs/base/common/errors';
J
Joao Moreno 已提交
15
import { mapEvent, filterEvent } from 'vs/base/common/event';
16 17
import { IAction, IActionItem } from 'vs/base/common/actions';
import { DropdownMenuActionItem } from 'vs/base/browser/ui/toolbar/toolbar';
J
Joao Moreno 已提交
18
import { domEvent } from 'vs/base/browser/event';
J
Joao Moreno 已提交
19 20
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
J
Joao Moreno 已提交
21
import { Viewlet } from 'vs/workbench/browser/viewlet';
J
Joao Moreno 已提交
22
import { append, emmet as $ } from 'vs/base/browser/dom';
J
Joao Moreno 已提交
23
import { IPager, PagedModel } from 'vs/base/common/paging';
J
Joao Moreno 已提交
24
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
J
Joao Moreno 已提交
25 26
import { PagedList } from 'vs/base/browser/ui/list/listPaging';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
J
Joao Moreno 已提交
27
import { Delegate, Renderer } from './extensionsList';
J
Joao Moreno 已提交
28
import { IExtensionsWorkbenchService, IExtension, IExtensionsViewlet, VIEWLET_ID } from './extensions';
29
import { ShowExtensionRecommendationsAction, ShowPopularExtensionsAction, ShowInstalledExtensionsAction, ListOutdatedExtensionsAction, FilterExtensionsAction } from './extensionsActions';
J
Joao Moreno 已提交
30
import { IExtensionManagementService, IExtensionGalleryService, SortBy } from 'vs/platform/extensionManagement/common/extensionManagement';
31
import { ExtensionsInput } from './extensionsInput';
J
Joao Moreno 已提交
32
import { IProgressService } from 'vs/platform/progress/common/progress';
33
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
J
Joao Moreno 已提交
34
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
J
Joao Moreno 已提交
35

J
Joao Moreno 已提交
36
export class ExtensionsViewlet extends Viewlet implements IExtensionsViewlet {
J
Joao Moreno 已提交
37

J
Joao Moreno 已提交
38
	private searchDelayer: ThrottledDelayer<any>;
J
Joao Moreno 已提交
39
	private root: HTMLElement;
J
Joao Moreno 已提交
40 41
	private searchBox: HTMLInputElement;
	private extensionsBox: HTMLElement;
J
Joao Moreno 已提交
42
	private list: PagedList<IExtension>;
J
Joao Moreno 已提交
43
	private disposables: IDisposable[] = [];
44
	private focusInvokedByTab: boolean;
J
Joao Moreno 已提交
45

J
Joao Moreno 已提交
46 47
	constructor(
		@ITelemetryService telemetryService: ITelemetryService,
48
		@IExtensionGalleryService private galleryService: IExtensionGalleryService,
J
Joao Moreno 已提交
49
		@IExtensionManagementService private extensionService: IExtensionManagementService,
J
Joao Moreno 已提交
50
		@IProgressService private progressService: IProgressService,
J
Joao Moreno 已提交
51
		@IInstantiationService private instantiationService: IInstantiationService,
52
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
53 54
		@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
		@IContextMenuService private contextMenuService: IContextMenuService
J
Joao Moreno 已提交
55
	) {
J
Joao Moreno 已提交
56
		super(VIEWLET_ID, telemetryService);
J
Joao Moreno 已提交
57
		this.searchDelayer = new ThrottledDelayer(500);
J
Joao Moreno 已提交
58 59 60 61
	}

	create(parent: Builder): TPromise<void> {
		super.create(parent);
J
Joao Moreno 已提交
62 63 64
		parent.addClass('extensions-viewlet');
		this.root = parent.getHTMLElement();

J
Joao Moreno 已提交
65 66 67
		const header = append(this.root, $('.header'));

		this.searchBox = append(header, $<HTMLInputElement>('input.search-box'));
J
Joao Moreno 已提交
68
		this.searchBox.type = 'search';
J
Joao Moreno 已提交
69
		this.searchBox.placeholder = localize('searchExtensions', "Search Extensions in Marketplace");
J
Joao Moreno 已提交
70 71 72
		this.extensionsBox = append(this.root, $('.extensions'));

		const delegate = new Delegate();
73
		const renderer = this.instantiationService.createInstance(Renderer);
J
Joao Moreno 已提交
74
		this.list = new PagedList(this.extensionsBox, delegate, [renderer]);
J
Joao Moreno 已提交
75

J
Joao Moreno 已提交
76 77 78 79 80 81
		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);
82
		const onTab = filterEvent(onKeyDown, e => e.keyCode === KeyCode.Tab);
J
Joao Moreno 已提交
83

84 85 86 87
		onEnter(this.onEnter, this, this.disposables);
		onEscape(this.onEscape, this, this.disposables);
		onUpArrow(this.onUpArrow, this, this.disposables);
		onDownArrow(this.onDownArrow, this, this.disposables);
88
		onTab(this.onTab, this, this.disposables);
J
Joao Moreno 已提交
89 90 91 92

		const onInput = domEvent(this.searchBox, 'input');
		onInput(() => this.triggerSearch(), null, this.disposables);

93 94 95 96 97 98 99
		this.list.onDOMFocus(focusEvent => {
			// Allow tab to move focus out of search box #7966
			if (!this.focusInvokedByTab) {
				this.searchBox.focus();
			}
			this.focusInvokedByTab = false;
		}, null, this.disposables);
J
Joao Moreno 已提交
100

J
Joao Moreno 已提交
101 102
		const onSelectedExtension = filterEvent(mapEvent(this.list.onSelectionChange, e => e.elements[0]), e => !!e);
		onSelectedExtension(this.onExtensionSelected, this, this.disposables);
J
Joao Moreno 已提交
103

J
Joao Moreno 已提交
104 105 106 107
		return TPromise.as(null);
	}

	setVisible(visible:boolean): TPromise<void> {
J
Joao Moreno 已提交
108 109
		return super.setVisible(visible).then(() => {
			if (visible) {
J
Joao Moreno 已提交
110 111
				this.searchBox.focus();
				this.searchBox.setSelectionRange(0,this.searchBox.value.length);
J
Joao Moreno 已提交
112
				this.triggerSearch(true, true);
J
Joao Moreno 已提交
113
			} else {
J
Joao Moreno 已提交
114
				this.list.model = new PagedModel([]);
J
Joao Moreno 已提交
115 116
			}
		});
J
Joao Moreno 已提交
117 118 119
	}

	focus(): void {
J
Joao Moreno 已提交
120
		this.searchBox.focus();
J
Joao Moreno 已提交
121 122
	}

J
Joao Moreno 已提交
123
	layout({ height }: Dimension):void {
J
Joao Moreno 已提交
124
		this.list.layout(height - 38);
J
Joao Moreno 已提交
125 126
	}

J
Joao Moreno 已提交
127 128
	getActions(): IAction[] {
		return [
129
			this.instantiationService.createInstance(FilterExtensionsAction, FilterExtensionsAction.ID, FilterExtensionsAction.LABEL)
J
Joao Moreno 已提交
130 131 132
		];
	}

133 134 135 136 137 138 139 140 141 142 143 144 145
	getActionItem(action: IAction): IActionItem {
		if (action.id === FilterExtensionsAction.ID) {
			return new DropdownMenuActionItem(action, [
				this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL),
				this.instantiationService.createInstance(ListOutdatedExtensionsAction, ListOutdatedExtensionsAction.ID, ListOutdatedExtensionsAction.LABEL),
				this.instantiationService.createInstance(ShowExtensionRecommendationsAction, ShowExtensionRecommendationsAction.ID, ShowExtensionRecommendationsAction.LABEL),
				this.instantiationService.createInstance(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL)
			], this.contextMenuService, (action: IAction) => this.getActionItem(action), this.actionRunner, null, 'filter-extensions');
		}

		return null;
	}

J
Joao Moreno 已提交
146 147 148 149 150
	search(text: string, immediate = false): void {
		this.searchBox.value = text;
		this.triggerSearch(immediate);
	}

J
Joao Moreno 已提交
151
	private triggerSearch(immediate = false, suggestPopular = false): void {
J
Joao Moreno 已提交
152
		const text = this.searchBox.value;
J
Joao Moreno 已提交
153
		this.searchDelayer.trigger(() => this.doSearch(text, suggestPopular), immediate || !text ? 0 : 500);
J
Joao Moreno 已提交
154 155
	}

J
Joao Moreno 已提交
156
	private doSearch(text: string = '', suggestPopular = false): TPromise<any> {
J
Joao Moreno 已提交
157
		const progressRunner = this.progressService.show(true);
J
Joao Moreno 已提交
158
		let promise: TPromise<IPager<IExtension> | IExtension[]>;
J
Joao Moreno 已提交
159

J
Joao Moreno 已提交
160
		if (!text) {
161
			promise = this.extensionsWorkbenchService.queryLocal()
J
Joao Moreno 已提交
162 163 164 165 166
				.then(result => {
					if (result.length === 0 && suggestPopular) {
						this.search('@popular', true);
					}

J
Joao Moreno 已提交
167
					return result;
J
Joao Moreno 已提交
168
				});
J
Joao Moreno 已提交
169
		} else if (/@outdated/i.test(text)) {
170
			promise = this.extensionsWorkbenchService.queryLocal()
J
Joao Moreno 已提交
171
				.then(result => result.filter(e => e.outdated));
J
Joao Moreno 已提交
172
		} else if (/@popular/i.test(text)) {
J
Joao Moreno 已提交
173 174 175
			promise = this.extensionsWorkbenchService.queryGallery({ sortBy: SortBy.InstallCount });
		} else if (/@recommended/i.test(text)) {
			promise = this.extensionsWorkbenchService.getRecommendations();
J
Joao Moreno 已提交
176
		} else {
J
Joao Moreno 已提交
177
			promise = this.extensionsWorkbenchService.queryGallery({ text });
J
Joao Moreno 已提交
178 179 180
		}

		return always(promise, () => progressRunner.done())
J
Joao Moreno 已提交
181
			.then(result => new PagedModel<IExtension>(result))
J
Joao Moreno 已提交
182
			.then(model => this.list.model = model);
J
Joao Moreno 已提交
183 184
	}

J
Joao Moreno 已提交
185 186 187 188 189
	private onExtensionSelected(extension: IExtension): void {
		this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension))
			.done(null, onUnexpectedError);
	}

J
Joao Moreno 已提交
190 191 192 193 194 195
	private onEnter(): void {
		this.list.setSelection(...this.list.getFocus());
	}

	private onEscape(): void {
		this.searchBox.value = '';
J
Joao Moreno 已提交
196
		this.triggerSearch(true);
J
Joao Moreno 已提交
197 198 199 200 201 202 203 204
	}

	private onUpArrow(): void {
		this.list.focusPrevious();
	}

	private onDownArrow(): void {
		this.list.focusNext();
J
Joao Moreno 已提交
205 206
	}

207 208 209 210
	private onTab(): void {
		this.focusInvokedByTab = true;
	}

J
Joao Moreno 已提交
211
	dispose(): void {
J
Joao Moreno 已提交
212
		this.disposables = dispose(this.disposables);
J
Joao Moreno 已提交
213 214 215
		super.dispose();
	}
}