scmViewlet.ts 22.4 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4 5 6 7 8
/*---------------------------------------------------------------------------------------------
 *  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/scmViewlet';
J
Joao Moreno 已提交
9
import { localize } from 'vs/nls';
J
Joao Moreno 已提交
10
import { TPromise } from 'vs/base/common/winjs.base';
J
Joao Moreno 已提交
11
import { chain } from 'vs/base/common/event';
J
Joao Moreno 已提交
12
import { onUnexpectedError } from 'vs/base/common/errors';
J
Joao Moreno 已提交
13
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
J
Joao Moreno 已提交
14
import { Builder } from 'vs/base/browser/builder';
J
Joao 已提交
15
import { PersistentViewsViewlet, CollapsibleView, IViewletViewOptions, IView, IViewOptions } from 'vs/workbench/parts/views/browser/views';
J
Joao Moreno 已提交
16
import { append, $, toggleClass, trackFocus } from 'vs/base/browser/dom';
J
Joao Moreno 已提交
17 18
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { List } from 'vs/base/browser/ui/list/listWidget';
19
import { IDelegate, IRenderer, IListContextMenuEvent } from 'vs/base/browser/ui/list/list';
J
Joao Moreno 已提交
20
import { VIEWLET_ID } from 'vs/workbench/parts/scm/common/scm';
J
Joao Moreno 已提交
21
import { FileLabel } from 'vs/workbench/browser/labels';
J
Joao Moreno 已提交
22
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
J
Joao Moreno 已提交
23
import { ISCMService, ISCMRepository, ISCMResourceGroup, ISCMResource } from 'vs/workbench/services/scm/common/scm';
J
Joao Moreno 已提交
24 25
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
J
Joao Moreno 已提交
26
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
J
Joao Moreno 已提交
27
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
J
Joao Moreno 已提交
28
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
J
Joao Moreno 已提交
29
import { ICommandService } from 'vs/platform/commands/common/commands';
J
Joao Moreno 已提交
30
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
J
Joao Moreno 已提交
31
import { IMessageService } from 'vs/platform/message/common/message';
32
import { IListService } from 'vs/platform/list/browser/listService';
J
Joao Moreno 已提交
33
import { MenuItemAction } from 'vs/platform/actions/common/actions';
J
Joao Moreno 已提交
34
import { IAction, Action, IActionItem, ActionRunner } from 'vs/base/common/actions';
35
import { MenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem';
J
Joao Moreno 已提交
36
import { SCMMenus } from './scmMenus';
37
import { ActionBar, IActionItemProvider, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
38
import { IThemeService, LIGHT } from 'vs/platform/theme/common/themeService';
J
Joao Moreno 已提交
39
import { comparePaths } from 'vs/base/common/comparers';
40
import { isSCMResource } from './scmUtil';
J
Joao Moreno 已提交
41
import { attachListStyler, attachBadgeStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler';
42
import Severity from 'vs/base/common/severity';
J
Joao Moreno 已提交
43 44 45 46
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ViewLocation, ViewsRegistry, IViewDescriptor } from 'vs/workbench/parts/views/browser/viewsRegistry';
J
Joao Moreno 已提交
47
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
J
Joao Moreno 已提交
48
import { ViewSizing } from 'vs/base/browser/ui/splitview/splitview';
J
Joao Moreno 已提交
49
import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/parts/extensions/common/extensions';
J
Joao Moreno 已提交
50
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
J
Joao 已提交
51 52 53
import { domEvent } from 'vs/base/browser/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73

// TODO@Joao
// Need to subclass MenuItemActionItem in order to respect
// the action context coming from any action bar, without breaking
// existing users
class SCMMenuItemActionItem extends MenuItemActionItem {

	onClick(event: MouseEvent): void {
		event.preventDefault();
		event.stopPropagation();

		this.actionRunner.run(this._commandAction, this._context)
			.done(undefined, err => this._messageService.show(Severity.Error, err));
	}
}

function identityProvider(r: ISCMResourceGroup | ISCMResource): string {
	if (isSCMResource(r)) {
		const group = r.resourceGroup;
		const provider = group.provider;
J
Joao Moreno 已提交
74
		return `${provider.contextValue}/${group.id}/${r.sourceUri.toString()}`;
75 76
	} else {
		const provider = r.provider;
J
Joao Moreno 已提交
77
		return `${provider.contextValue}/${r.id}`;
78 79
	}
}
J
Joao Moreno 已提交
80

J
Joao Moreno 已提交
81 82 83 84 85
interface SearchInputEvent extends Event {
	target: HTMLInputElement;
	immediate?: boolean;
}

J
Joao Moreno 已提交
86
interface ResourceGroupTemplate {
J
Joao Moreno 已提交
87 88
	name: HTMLElement;
	count: CountBadge;
J
Joao Moreno 已提交
89
	actionBar: ActionBar;
90
	dispose: () => void;
J
Joao Moreno 已提交
91
}
J
Joao Moreno 已提交
92

J
Joao Moreno 已提交
93
class ResourceGroupRenderer implements IRenderer<ISCMResourceGroup, ResourceGroupTemplate> {
J
Joao Moreno 已提交
94

J
Joao Moreno 已提交
95 96
	static TEMPLATE_ID = 'resource group';
	get templateId(): string { return ResourceGroupRenderer.TEMPLATE_ID; }
J
Joao Moreno 已提交
97

J
Joao Moreno 已提交
98
	constructor(
J
Joao Moreno 已提交
99
		private scmMenus: SCMMenus,
100 101
		private actionItemProvider: IActionItemProvider,
		private themeService: IThemeService
J
Joao Moreno 已提交
102 103
	) { }

J
Joao Moreno 已提交
104
	renderTemplate(container: HTMLElement): ResourceGroupTemplate {
J
Joao Moreno 已提交
105 106
		const element = append(container, $('.resource-group'));
		const name = append(element, $('.name'));
J
Joao Moreno 已提交
107 108
		const actionsContainer = append(element, $('.actions'));
		const actionBar = new ActionBar(actionsContainer, { actionItemProvider: this.actionItemProvider });
109 110
		const countContainer = append(element, $('.count'));
		const count = new CountBadge(countContainer);
111
		const styler = attachBadgeStyler(count, this.themeService);
J
Joao Moreno 已提交
112

113 114 115 116 117 118
		return {
			name, count, actionBar, dispose: () => {
				actionBar.dispose();
				styler.dispose();
			}
		};
J
Joao Moreno 已提交
119
	}
J
Joao Moreno 已提交
120

J
Joao Moreno 已提交
121
	renderElement(group: ISCMResourceGroup, index: number, template: ResourceGroupTemplate): void {
J
Joao Moreno 已提交
122
		template.name.textContent = group.label;
J
Joao Moreno 已提交
123
		template.count.setCount(group.resources.length);
J
Joao Moreno 已提交
124
		template.actionBar.clear();
125
		template.actionBar.context = group;
J
Joao Moreno 已提交
126
		template.actionBar.push(this.scmMenus.getResourceGroupActions(group), { icon: true, label: false });
J
Joao Moreno 已提交
127 128
	}

J
Joao Moreno 已提交
129
	disposeTemplate(template: ResourceGroupTemplate): void {
130
		template.dispose();
J
Joao Moreno 已提交
131 132 133 134
	}
}

interface ResourceTemplate {
135
	element: HTMLElement;
J
Joao Moreno 已提交
136
	name: HTMLElement;
J
Joao Moreno 已提交
137
	fileLabel: FileLabel;
J
Joao Moreno 已提交
138
	decorationIcon: HTMLElement;
J
Joao Moreno 已提交
139
	actionBar: ActionBar;
J
Joao Moreno 已提交
140
	dispose: () => void;
J
Joao Moreno 已提交
141 142
}

143 144
class MultipleSelectionActionRunner extends ActionRunner {

145
	constructor(private getSelectedResources: () => ISCMResource[]) {
146 147 148
		super();
	}

149
	runAction(action: IAction, context: ISCMResource): TPromise<any> {
150 151
		if (action instanceof MenuItemAction) {
			const selection = this.getSelectedResources();
152 153 154 155 156 157 158
			const filteredSelection = selection.filter(s => s !== context);

			if (selection.length === filteredSelection.length || selection.length === 1) {
				return action.run(context);
			}

			return action.run(context, ...filteredSelection);
159 160 161 162 163 164
		}

		return super.runAction(action, context);
	}
}

J
Joao Moreno 已提交
165 166 167 168
class ResourceRenderer implements IRenderer<ISCMResource, ResourceTemplate> {

	static TEMPLATE_ID = 'resource';
	get templateId(): string { return ResourceRenderer.TEMPLATE_ID; }
J
Joao Moreno 已提交
169

J
Joao Moreno 已提交
170
	constructor(
J
Joao Moreno 已提交
171 172
		private scmMenus: SCMMenus,
		private actionItemProvider: IActionItemProvider,
173
		private getSelectedResources: () => ISCMResource[],
174
		@IThemeService private themeService: IThemeService,
J
Joao Moreno 已提交
175
		@IInstantiationService private instantiationService: IInstantiationService
J
cleanup  
Joao Moreno 已提交
176
	) { }
J
Joao Moreno 已提交
177

J
Joao Moreno 已提交
178
	renderTemplate(container: HTMLElement): ResourceTemplate {
J
Joao Moreno 已提交
179 180 181 182
		const element = append(container, $('.resource'));
		const name = append(element, $('.name'));
		const fileLabel = this.instantiationService.createInstance(FileLabel, name, void 0);
		const actionsContainer = append(element, $('.actions'));
183 184
		const actionBar = new ActionBar(actionsContainer, {
			actionItemProvider: this.actionItemProvider,
185
			actionRunner: new MultipleSelectionActionRunner(this.getSelectedResources)
186 187
		});

188
		const decorationIcon = append(element, $('.decoration-icon'));
J
Joao Moreno 已提交
189

J
Joao Moreno 已提交
190 191 192 193 194 195
		return {
			element, name, fileLabel, decorationIcon, actionBar, dispose: () => {
				actionBar.dispose();
				fileLabel.dispose();
			}
		};
J
Joao Moreno 已提交
196 197
	}

J
Joao Moreno 已提交
198
	renderElement(resource: ISCMResource, index: number, template: ResourceTemplate): void {
199
		template.fileLabel.setFile(resource.sourceUri);
J
Joao Moreno 已提交
200
		template.actionBar.clear();
201
		template.actionBar.context = resource;
J
Joao Moreno 已提交
202
		template.actionBar.push(this.scmMenus.getResourceActions(resource), { icon: true, label: false });
J
Joao Moreno 已提交
203
		toggleClass(template.name, 'strike-through', resource.decorations.strikeThrough);
204
		toggleClass(template.element, 'faded', resource.decorations.faded);
J
Joao Moreno 已提交
205

206 207
		const theme = this.themeService.getTheme();
		const icon = theme.type === LIGHT ? resource.decorations.icon : resource.decorations.iconDark;
J
Joao Moreno 已提交
208 209 210

		if (icon) {
			template.decorationIcon.style.backgroundImage = `url('${icon}')`;
211
			template.decorationIcon.title = resource.decorations.tooltip;
J
Joao Moreno 已提交
212
		} else {
J
Joao Moreno 已提交
213
			template.decorationIcon.style.backgroundImage = '';
J
Joao Moreno 已提交
214
		}
J
Joao Moreno 已提交
215
	}
J
Joao Moreno 已提交
216

J
Joao Moreno 已提交
217
	disposeTemplate(template: ResourceTemplate): void {
J
Joao Moreno 已提交
218
		template.dispose();
J
Joao Moreno 已提交
219 220 221
	}
}

J
Joao Moreno 已提交
222 223 224 225 226
class Delegate implements IDelegate<ISCMResourceGroup | ISCMResource> {

	getHeight() { return 22; }

	getTemplateId(element: ISCMResourceGroup | ISCMResource) {
J
Joao Moreno 已提交
227
		return isSCMResource(element) ? ResourceRenderer.TEMPLATE_ID : ResourceGroupRenderer.TEMPLATE_ID;
J
Joao Moreno 已提交
228
	}
J
Joao Moreno 已提交
229 230
}

J
Joao Moreno 已提交
231
function resourceSorter(a: ISCMResource, b: ISCMResource): number {
232
	return comparePaths(a.sourceUri.fsPath, b.sourceUri.fsPath);
J
Joao Moreno 已提交
233 234
}

J
Joao Moreno 已提交
235 236
class SourceControlViewDescriptor implements IViewDescriptor {

J
Joao 已提交
237 238 239 240 241 242 243
	// This ID magic needs to happen in order to preserve
	// good splitview state when reloading the workbench
	static idCount = 0;
	static freeIds: string[] = [];

	readonly id: string;

J
Joao Moreno 已提交
244 245
	get repository(): ISCMRepository { return this._repository; }
	get name(): string { return this._repository.provider.label; }
J
Joao Moreno 已提交
246 247 248
	get ctor(): any { return null; }
	get location(): ViewLocation { return ViewLocation.SCM; }

J
Joao 已提交
249 250 251 252 253 254 255 256 257 258 259
	constructor(private _repository: ISCMRepository) {
		if (SourceControlViewDescriptor.freeIds.length > 0) {
			this.id = SourceControlViewDescriptor.freeIds.shift();
		} else {
			this.id = `scm${SourceControlViewDescriptor.idCount++}`;
		}
	}

	dispose(): void {
		SourceControlViewDescriptor.freeIds.push(this.id);
	}
J
Joao Moreno 已提交
260 261 262
}

class SourceControlView extends CollapsibleView {
J
Joao Moreno 已提交
263

J
Joao Moreno 已提交
264
	private cachedHeight: number | undefined;
J
Joao Moreno 已提交
265 266
	private inputBoxContainer: HTMLElement;
	private inputBox: InputBox;
J
Joao Moreno 已提交
267
	private listContainer: HTMLElement;
J
Joao Moreno 已提交
268
	private list: List<ISCMResourceGroup | ISCMResource>;
J
Joao Moreno 已提交
269
	private menus: SCMMenus;
J
Joao Moreno 已提交
270 271 272
	private disposables: IDisposable[] = [];

	constructor(
J
Joao 已提交
273
		initialSize: number,
J
Joao Moreno 已提交
274
		private repository: ISCMRepository,
J
Joao Moreno 已提交
275 276
		options: IViewletViewOptions,
		@IKeybindingService protected keybindingService: IKeybindingService,
277
		@IThemeService protected themeService: IThemeService,
J
Joao Moreno 已提交
278
		@IContextMenuService protected contextMenuService: IContextMenuService,
J
Joao Moreno 已提交
279
		@IContextViewService protected contextViewService: IContextViewService,
J
Joao Moreno 已提交
280 281
		@IListService protected listService: IListService,
		@ICommandService protected commandService: ICommandService,
J
Joao Moreno 已提交
282
		@IMessageService protected messageService: IMessageService,
J
Joao Moreno 已提交
283 284 285
		@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
		@IEditorGroupService protected editorGroupService: IEditorGroupService,
		@IInstantiationService protected instantiationService: IInstantiationService
J
Joao Moreno 已提交
286
	) {
J
Joao 已提交
287
		super(initialSize, { ...(options as IViewOptions), sizing: ViewSizing.Flexible }, keybindingService, contextMenuService);
J
Joao Moreno 已提交
288

J
Joao Moreno 已提交
289
		this.menus = instantiationService.createInstance(SCMMenus, repository.provider);
J
Joao Moreno 已提交
290
		this.menus.onDidChangeTitle(this.updateActions, this, this.disposables);
J
Joao Moreno 已提交
291 292
	}

J
Joao Moreno 已提交
293 294 295
	renderHeader(container: HTMLElement): void {
		const title = append(container, $('div.title'));
		title.textContent = this.name;
J
Joao Moreno 已提交
296

J
Joao Moreno 已提交
297
		super.renderHeader(container);
J
Joao Moreno 已提交
298 299
	}

J
Joao Moreno 已提交
300
	renderBody(container: HTMLElement): void {
J
Joao Moreno 已提交
301 302 303
		const focusTracker = trackFocus(container);
		this.disposables.push(focusTracker.addFocusListener(() => this.repository.focus()));
		this.disposables.push(focusTracker);
J
Joao Moreno 已提交
304

J
Joao Moreno 已提交
305
		// Input
J
Joao Moreno 已提交
306 307 308 309 310 311 312 313
		this.inputBoxContainer = append(container, $('.scm-editor'));

		this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, {
			flexibleHeight: true
		});
		this.disposables.push(attachInputBoxStyler(this.inputBox, this.themeService));
		this.disposables.push(this.inputBox);

J
Joao Moreno 已提交
314
		this.inputBox.value = this.repository.input.value;
315
		this.inputBox.setPlaceHolder(this.repository.input.placeholder);
J
Joao Moreno 已提交
316 317
		this.inputBox.onDidChange(value => this.repository.input.value = value, null, this.disposables);
		this.repository.input.onDidChange(value => this.inputBox.value = value, null, this.disposables);
318
		this.repository.input.onDidChangePlaceholder(placeholder => this.inputBox.setPlaceHolder(placeholder), null, this.disposables);
J
Joao Moreno 已提交
319
		this.disposables.push(this.inputBox.onDidHeightChange(() => this.layoutBody()));
J
Joao Moreno 已提交
320 321 322 323 324 325

		chain(domEvent(this.inputBox.inputElement, 'keydown'))
			.map(e => new StandardKeyboardEvent(e))
			.filter(e => e.equals(KeyMod.CtrlCmd | KeyCode.Enter) || e.equals(KeyMod.CtrlCmd | KeyCode.KEY_S))
			.on(this.onDidAcceptInput, this, this.disposables);

J
Joao Moreno 已提交
326 327
		if (this.repository.provider.onDidChangeCommitTemplate) {
			this.repository.provider.onDidChangeCommitTemplate(this.updateInputBox, this, this.disposables);
J
Joao Moreno 已提交
328 329 330 331 332 333
		}

		this.updateInputBox();

		// List

J
Joao Moreno 已提交
334
		this.listContainer = append(container, $('.scm-status.show-file-icons'));
J
Joao Moreno 已提交
335
		const delegate = new Delegate();
J
Joao Moreno 已提交
336

J
Joao Moreno 已提交
337
		const actionItemProvider = action => this.getActionItem(action);
338

J
Joao Moreno 已提交
339
		const renderers = [
340
			new ResourceGroupRenderer(this.menus, actionItemProvider, this.themeService),
341
			this.instantiationService.createInstance(ResourceRenderer, this.menus, actionItemProvider, () => this.getSelectedResources()),
J
Joao Moreno 已提交
342 343 344
		];

		this.list = new List(this.listContainer, delegate, renderers, {
345
			identityProvider,
J
Joao 已提交
346 347 348 349 350 351 352
			keyboardSupport: false,

			// sad... we must do this because the list can be rerendered between a
			// mousedown and the respective mouseup events. since the list isn't as
			// cool as react (yet), it always removes all DOM nodes on rerender
			// https://github.com/Microsoft/vscode/issues/30323
			selectOnMouseDown: true
J
Joao Moreno 已提交
353
		});
354

B
Benjamin Pasero 已提交
355
		this.disposables.push(attachListStyler(this.list, this.themeService));
356
		this.disposables.push(this.listService.register(this.list));
J
Joao Moreno 已提交
357

J
Joao Moreno 已提交
358
		chain(this.list.onOpen)
J
Joao Moreno 已提交
359
			.map(e => e.elements[0])
J
Joao Moreno 已提交
360
			.filter(e => !!e && isSCMResource(e))
J
Joao Moreno 已提交
361
			.on(this.open, this, this.disposables);
J
Joao Moreno 已提交
362

J
Joao Moreno 已提交
363 364 365 366 367
		chain(this.list.onPin)
			.map(e => e.elements[0])
			.filter(e => !!e && isSCMResource(e))
			.on(this.pin, this, this.disposables);

J
Joao Moreno 已提交
368
		this.list.onContextMenu(this.onListContextMenu, this, this.disposables);
J
Joao Moreno 已提交
369
		this.disposables.push(this.list);
J
Joao Moreno 已提交
370

J
Joao Moreno 已提交
371
		this.repository.provider.onDidChange(this.updateList, this, this.disposables);
J
Joao Moreno 已提交
372
		this.updateList();
J
Joao Moreno 已提交
373 374
	}

J
Joao Moreno 已提交
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
	layoutBody(height: number = this.cachedHeight): void {
		if (!height === undefined) {
			return;
		}

		this.list.layout(height);
		this.cachedHeight = height;
		this.inputBox.layout();

		const editorHeight = this.inputBox.height;
		const listHeight = height - (editorHeight + 12 /* margin */);
		this.listContainer.style.height = `${listHeight}px`;
		this.list.layout(listHeight);

		toggleClass(this.inputBoxContainer, 'scroll', editorHeight >= 134);
	}

J
Joao 已提交
392
	focus(): void {
J
Joao 已提交
393 394 395
		if (this.isExpanded()) {
			this.inputBox.focus();
		}
J
Joao Moreno 已提交
396 397
	}

J
Joao Moreno 已提交
398 399 400 401 402 403 404 405
	getActions(): IAction[] {
		return this.menus.getTitleActions();
	}

	getSecondaryActions(): IAction[] {
		return this.menus.getTitleSecondaryActions();
	}

J
Joao Moreno 已提交
406 407 408 409 410 411 412 413 414
	getActionItem(action: IAction): IActionItem {
		if (!(action instanceof MenuItemAction)) {
			return undefined;
		}

		return new SCMMenuItemActionItem(action, this.keybindingService, this.messageService);
	}

	getActionsContext(): any {
J
Joao Moreno 已提交
415
		return this.repository.provider;
J
Joao Moreno 已提交
416 417
	}

J
Joao Moreno 已提交
418
	private updateList(): void {
J
Joao Moreno 已提交
419
		const elements = this.repository.provider.resources
J
Joao Moreno 已提交
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
			.reduce<(ISCMResourceGroup | ISCMResource)[]>((r, g) => [...r, g, ...g.resources.sort(resourceSorter)], []);

		this.list.splice(0, this.list.length, elements);
	}

	private open(e: ISCMResource): void {
		if (!e.command) {
			return;
		}

		this.commandService.executeCommand(e.command.id, ...e.command.arguments)
			.done(undefined, onUnexpectedError);
	}

	private pin(): void {
		const activeEditor = this.editorService.getActiveEditor();
		const activeEditorInput = this.editorService.getActiveEditorInput();
		this.editorGroupService.pinEditor(activeEditor.position, activeEditorInput);
	}

	private onListContextMenu(e: IListContextMenuEvent<ISCMResourceGroup | ISCMResource>): void {
		const element = e.element;
		let actions: IAction[];

		if (isSCMResource(element)) {
			actions = this.menus.getResourceContextActions(element);
		} else {
			actions = this.menus.getResourceGroupContextActions(element);
		}

		this.contextMenuService.showContextMenu({
			getAnchor: () => e.anchor,
			getActions: () => TPromise.as(actions),
			getActionsContext: () => element,
			actionRunner: new MultipleSelectionActionRunner(() => this.getSelectedResources())
		});
	}

	private getSelectedResources(): ISCMResource[] {
		return this.list.getSelectedElements()
			.filter(r => isSCMResource(r)) as ISCMResource[];
	}

J
Joao Moreno 已提交
463
	private updateInputBox(): void {
J
Joao Moreno 已提交
464
		if (typeof this.repository.provider.commitTemplate === 'undefined') {
J
Joao Moreno 已提交
465 466 467
			return;
		}

J
Joao Moreno 已提交
468
		this.inputBox.value = this.repository.provider.commitTemplate;
J
Joao Moreno 已提交
469 470 471
	}

	private onDidAcceptInput(): void {
J
Joao Moreno 已提交
472
		if (!this.repository.provider.acceptInputCommand) {
J
Joao Moreno 已提交
473 474 475
			return;
		}

J
Joao Moreno 已提交
476 477
		const id = this.repository.provider.acceptInputCommand.id;
		const args = this.repository.provider.acceptInputCommand.arguments;
J
Joao Moreno 已提交
478 479 480 481 482

		this.commandService.executeCommand(id, ...args)
			.done(undefined, onUnexpectedError);
	}

J
Joao Moreno 已提交
483 484 485 486 487 488
	dispose(): void {
		this.disposables = dispose(this.disposables);
		super.dispose();
	}
}

J
Joao Moreno 已提交
489 490
class InstallAdditionalSCMProvidersAction extends Action {

J
Joao Moreno 已提交
491
	constructor( @IViewletService private viewletService: IViewletService) {
J
Joao Moreno 已提交
492 493 494 495 496 497 498 499 500 501 502 503
		super('scm.installAdditionalSCMProviders', localize('installAdditionalSCMProviders', "Install Additional SCM Providers..."), '', true);
	}

	run(): TPromise<void> {
		return this.viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true).then(viewlet => viewlet as IExtensionsViewlet)
			.then(viewlet => {
				viewlet.search('category:"SCM Providers" @sort:installs');
				viewlet.focus();
			});
	}
}

J
Joao 已提交
504
export class SCMViewlet extends PersistentViewsViewlet {
J
Joao Moreno 已提交
505

J
Joao 已提交
506
	private menus: SCMMenus;
J
Joao 已提交
507
	private repositoryToViewDescriptor = new Map<string, SourceControlViewDescriptor>();
J
Joao Moreno 已提交
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
	private disposables: IDisposable[] = [];

	constructor(
		@ITelemetryService telemetryService: ITelemetryService,
		@ISCMService protected scmService: ISCMService,
		@IInstantiationService instantiationService: IInstantiationService,
		@IContextViewService protected contextViewService: IContextViewService,
		@IContextKeyService contextKeyService: IContextKeyService,
		@IKeybindingService protected keybindingService: IKeybindingService,
		@IMessageService protected messageService: IMessageService,
		@IListService protected listService: IListService,
		@IContextMenuService contextMenuService: IContextMenuService,
		@IThemeService protected themeService: IThemeService,
		@ICommandService protected commandService: ICommandService,
		@IEditorGroupService protected editorGroupService: IEditorGroupService,
		@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
		@IWorkspaceContextService contextService: IWorkspaceContextService,
		@IStorageService storageService: IStorageService,
		@IExtensionService extensionService: IExtensionService
	) {
J
Joao 已提交
528
		super(VIEWLET_ID, ViewLocation.SCM, 'scm', true,
J
Joao Moreno 已提交
529
			telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService);
J
Joao 已提交
530 531 532

		this.menus = instantiationService.createInstance(SCMMenus, undefined);
		this.menus.onDidChangeTitle(this.updateTitleArea, this, this.disposables);
J
Joao Moreno 已提交
533 534
	}

J
Joao Moreno 已提交
535
	private onDidAddRepository(repository: ISCMRepository): void {
J
Joao 已提交
536 537 538 539
		const viewDescriptor = new SourceControlViewDescriptor(repository);
		this.repositoryToViewDescriptor.set(repository.provider.id, viewDescriptor);

		ViewsRegistry.registerViews([viewDescriptor]);
J
Joao 已提交
540
		toggleClass(this.getContainer().getHTMLElement(), 'empty', this.views.length === 0);
J
Joao Moreno 已提交
541
		this.updateTitleArea();
J
Joao Moreno 已提交
542
	}
J
Joao Moreno 已提交
543

J
Joao Moreno 已提交
544
	private onDidRemoveRepository(repository: ISCMRepository): void {
J
Joao 已提交
545 546 547 548
		const viewDescriptor = this.repositoryToViewDescriptor.get(repository.provider.id);
		this.repositoryToViewDescriptor.delete(repository.provider.id);
		viewDescriptor.dispose();

J
Joao Moreno 已提交
549
		ViewsRegistry.deregisterViews([repository.provider.id], ViewLocation.SCM);
J
Joao 已提交
550
		toggleClass(this.getContainer().getHTMLElement(), 'empty', this.views.length === 0);
J
Joao Moreno 已提交
551
		this.updateTitleArea();
J
Joao Moreno 已提交
552
	}
J
Joao Moreno 已提交
553

J
Joao Moreno 已提交
554 555 556
	async create(parent: Builder): TPromise<void> {
		await super.create(parent);

J
Joao 已提交
557 558
		parent.addClass('scm-viewlet', 'empty');
		append(parent.getHTMLElement(), $('div.empty-message', null, localize('no open repo', "There are no source controls active.")));
J
Joao Moreno 已提交
559

J
Joao Moreno 已提交
560 561 562
		this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables);
		this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables);
		this.scmService.repositories.forEach(p => this.onDidAddRepository(p));
J
Joao Moreno 已提交
563 564
	}

J
Joao 已提交
565
	protected createView(viewDescriptor: IViewDescriptor, initialSize: number, options: IViewletViewOptions): IView {
J
Joao Moreno 已提交
566
		if (viewDescriptor instanceof SourceControlViewDescriptor) {
J
Joao 已提交
567
			return this.instantiationService.createInstance(SourceControlView, initialSize, viewDescriptor.repository, options);
J
Joao Moreno 已提交
568 569
		}

J
Joao 已提交
570
		return this.instantiationService.createInstance(viewDescriptor.ctor, initialSize, options);
J
Joao Moreno 已提交
571 572 573 574 575 576
	}

	getOptimalWidth(): number {
		return 400;
	}

577 578
	getTitle(): string {
		const title = localize('source control', "Source Control");
J
Joao Moreno 已提交
579
		const views = ViewsRegistry.getViews(ViewLocation.SCM);
580

J
Joao Moreno 已提交
581 582 583 584 585 586
		if (views.length === 1) {
			const view = views[0];
			return localize('viewletTitle', "{0}: {1}", title, view.name);
		} else {
			return title;
		}
587 588
	}

589 590 591 592 593
	getActions(): IAction[] {
		if (this.showHeaderInTitleArea() && this.views.length === 1) {
			return this.views[0].getActions();
		}

J
Joao 已提交
594
		return this.menus.getTitleActions();
595 596
	}

J
Joao Moreno 已提交
597
	getSecondaryActions(): IAction[] {
J
Joao 已提交
598
		let result: IAction[];
599 600 601 602 603 604

		if (this.showHeaderInTitleArea() && this.views.length === 1) {
			result = [
				...this.views[0].getSecondaryActions(),
				new Separator()
			];
J
Joao 已提交
605 606 607 608 609 610
		} else {
			result = this.menus.getTitleSecondaryActions();

			if (result.length > 0) {
				result.push(new Separator());
			}
611 612 613 614 615
		}

		result.push(this.instantiationService.createInstance(InstallAdditionalSCMProvidersAction));

		return result;
J
Joao Moreno 已提交
616 617 618
	}

	getActionItem(action: IAction): IActionItem {
619 620 621 622 623
		if (!(action instanceof MenuItemAction)) {
			return undefined;
		}

		return new SCMMenuItemActionItem(action, this.keybindingService, this.messageService);
J
Joao Moreno 已提交
624 625
	}

J
Joao Moreno 已提交
626 627 628 629 630
	dispose(): void {
		this.disposables = dispose(this.disposables);
		super.dispose();
	}
}