repositoryPane.ts 43.8 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  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!./media/scmViewlet';
J
Joao Moreno 已提交
7
import { Event, Emitter } from 'vs/base/common/event';
J
Joao Moreno 已提交
8
import { basename, isEqual } from 'vs/base/common/resources';
9
import { IDisposable, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle';
S
SteVen Batten 已提交
10
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
J
Joao Moreno 已提交
11
import { append, $, addClass, toggleClass, trackFocus, removeClass } from 'vs/base/browser/dom';
12
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
J
Joao Moreno 已提交
13
import { ISCMRepository, ISCMResourceGroup, ISCMResource, InputValidationType } from 'vs/workbench/contrib/scm/common/scm';
J
Joao Moreno 已提交
14 15 16 17 18 19 20 21 22
import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels';
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { MenuItemAction, IMenuService } from 'vs/platform/actions/common/actions';
J
Joao Moreno 已提交
23
import { IAction, IActionViewItem, ActionRunner, Action } from 'vs/base/common/actions';
J
Joao Moreno 已提交
24 25 26
import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { SCMMenus } from './menus';
import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
J
Joao Moreno 已提交
27
import { IThemeService, LIGHT, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
J
Joao Moreno 已提交
28
import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar } from './util';
J
Joao Moreno 已提交
29
import { attachBadgeStyler } from 'vs/platform/theme/common/styler';
J
Joao Moreno 已提交
30 31
import { WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
J
Joao Moreno 已提交
32
import { disposableTimeout, ThrottledDelayer } from 'vs/base/common/async';
J
Joao Moreno 已提交
33
import { INotificationService } from 'vs/platform/notification/common/notification';
34
import { ITreeNode, ITreeFilter, ITreeSorter, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
35
import { ResourceTree, IResourceNode } from 'vs/base/common/resourceTree';
36
import { ISequence, ISplice } from 'vs/base/common/sequence';
J
Joao Moreno 已提交
37
import { ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/tree/objectTree';
J
Joao Moreno 已提交
38 39 40 41 42 43 44
import { Iterator } from 'vs/base/common/iterator';
import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import { URI } from 'vs/base/common/uri';
import { FileKind } from 'vs/platform/files/common/files';
import { compareFileNames } from 'vs/base/common/comparers';
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { IViewDescriptor } from 'vs/workbench/common/views';
J
Joao Moreno 已提交
45
import { localize } from 'vs/nls';
J
Joao Moreno 已提交
46
import { flatten, find } from 'vs/base/common/arrays';
J
Joao Moreno 已提交
47
import { memoize } from 'vs/base/common/decorators';
48
import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
J
Joao Moreno 已提交
49
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
J
Joao Moreno 已提交
50
import { toResource, SideBySideEditor } from 'vs/workbench/common/editor';
J
Joao Moreno 已提交
51
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
J
Joao Moreno 已提交
52
import { Hasher } from 'vs/base/common/hash';
J
Joao Moreno 已提交
53 54 55 56 57
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
import { ITextModel } from 'vs/editor/common/model';
import { IEditorConstructionOptions } from 'vs/editor/common/config/editorOptions';
import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
import { IModelService } from 'vs/editor/common/services/modelService';
J
Joao Moreno 已提交
58 59 60 61
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer';
import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard';
import { ContextMenuController } from 'vs/editor/contrib/contextmenu/contextmenu';
S
Sandeep Somavarapu 已提交
62
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
J
Joao Moreno 已提交
63 64
import * as platform from 'vs/base/common/platform';
import { format } from 'vs/base/common/strings';
J
Joao Moreno 已提交
65
import { inputPlaceholderForeground, inputValidationInfoBorder, inputValidationWarningBorder, inputValidationErrorBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationErrorBackground, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry';
J
Joao Moreno 已提交
66 67
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
68
import { Schemas } from 'vs/base/common/network';
J
Joao Moreno 已提交
69
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
J
Joao Moreno 已提交
70

71
type TreeElement = ISCMResourceGroup | IResourceNode<ISCMResource, ISCMResourceGroup> | ISCMResource;
J
Joao Moreno 已提交
72

J
Joao Moreno 已提交
73
interface ResourceGroupTemplate {
J
Joao Moreno 已提交
74 75 76
	readonly name: HTMLElement;
	readonly count: CountBadge;
	readonly actionBar: ActionBar;
J
Joao Moreno 已提交
77
	elementDisposables: IDisposable;
J
Joao Moreno 已提交
78
	readonly disposables: IDisposable;
J
Joao Moreno 已提交
79 80 81 82
}

class ResourceGroupRenderer implements ICompressibleTreeRenderer<ISCMResourceGroup, FuzzyScore, ResourceGroupTemplate> {

83
	static readonly TEMPLATE_ID = 'resource group';
J
Joao Moreno 已提交
84 85 86 87 88 89 90 91 92
	get templateId(): string { return ResourceGroupRenderer.TEMPLATE_ID; }

	constructor(
		private actionViewItemProvider: IActionViewItemProvider,
		private themeService: IThemeService,
		private menus: SCMMenus
	) { }

	renderTemplate(container: HTMLElement): ResourceGroupTemplate {
93 94 95
		// hack
		addClass(container.parentElement!.parentElement!.querySelector('.monaco-tl-twistie')! as HTMLElement, 'force-twistie');

J
Joao Moreno 已提交
96 97 98 99 100 101 102
		const element = append(container, $('.resource-group'));
		const name = append(element, $('.name'));
		const actionsContainer = append(element, $('.actions'));
		const actionBar = new ActionBar(actionsContainer, { actionViewItemProvider: this.actionViewItemProvider });
		const countContainer = append(element, $('.count'));
		const count = new CountBadge(countContainer);
		const styler = attachBadgeStyler(count, this.themeService);
J
Joao Moreno 已提交
103 104
		const elementDisposables = Disposable.None;
		const disposables = combinedDisposable(actionBar, styler);
J
Joao Moreno 已提交
105

J
Joao Moreno 已提交
106
		return { name, count, actionBar, elementDisposables, disposables };
J
Joao Moreno 已提交
107 108 109
	}

	renderElement(node: ITreeNode<ISCMResourceGroup, FuzzyScore>, index: number, template: ResourceGroupTemplate): void {
J
Joao Moreno 已提交
110
		template.elementDisposables.dispose();
J
Joao Moreno 已提交
111 112 113 114 115

		const group = node.element;
		template.name.textContent = group.label;
		template.actionBar.clear();
		template.actionBar.context = group;
J
Joao Moreno 已提交
116
		template.count.setCount(group.elements.length);
J
Joao Moreno 已提交
117 118 119 120

		const disposables = new DisposableStore();
		disposables.add(connectPrimaryMenuToInlineActionBar(this.menus.getResourceGroupMenu(group), template.actionBar));

J
Joao Moreno 已提交
121
		template.elementDisposables = disposables;
J
Joao Moreno 已提交
122 123 124 125 126 127 128
	}

	renderCompressedElements(node: ITreeNode<ICompressedTreeNode<ISCMResourceGroup>, FuzzyScore>, index: number, templateData: ResourceGroupTemplate, height: number | undefined): void {
		throw new Error('Should never happen since node is incompressible');
	}

	disposeElement(group: ITreeNode<ISCMResourceGroup, FuzzyScore>, index: number, template: ResourceGroupTemplate): void {
J
Joao Moreno 已提交
129
		template.elementDisposables.dispose();
J
Joao Moreno 已提交
130 131 132
	}

	disposeTemplate(template: ResourceGroupTemplate): void {
J
Joao Moreno 已提交
133
		template.elementDisposables.dispose();
J
Joao Moreno 已提交
134
		template.disposables.dispose();
J
Joao Moreno 已提交
135 136 137 138 139 140 141 142 143
	}
}

interface ResourceTemplate {
	element: HTMLElement;
	name: HTMLElement;
	fileLabel: IResourceLabel;
	decorationIcon: HTMLElement;
	actionBar: ActionBar;
J
Joao Moreno 已提交
144 145
	elementDisposables: IDisposable;
	disposables: IDisposable;
J
Joao Moreno 已提交
146 147 148 149
}

class MultipleSelectionActionRunner extends ActionRunner {

150
	constructor(private getSelectedResources: () => (ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>)[]) {
J
Joao Moreno 已提交
151 152 153
		super();
	}

154
	runAction(action: IAction, context: ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>): Promise<any> {
J
Joao Moreno 已提交
155 156 157 158 159
		if (!(action instanceof MenuItemAction)) {
			return super.runAction(action, context);
		}

		const selection = this.getSelectedResources();
160 161
		const contextIsSelected = selection.some(s => s === context);
		const actualContext = contextIsSelected ? selection : [context];
162
		const args = flatten(actualContext.map(e => ResourceTree.isResourceNode(e) ? ResourceTree.collect(e) : [e]));
163
		return action.run(...args);
J
Joao Moreno 已提交
164 165 166
	}
}

167
class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>, FuzzyScore, ResourceTemplate> {
J
Joao Moreno 已提交
168

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

	constructor(
J
Joao Moreno 已提交
173
		private viewModelProvider: () => ViewModel,
J
Joao Moreno 已提交
174 175
		private labels: ResourceLabels,
		private actionViewItemProvider: IActionViewItemProvider,
176
		private getSelectedResources: () => (ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>)[],
J
Joao Moreno 已提交
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
		private themeService: IThemeService,
		private menus: SCMMenus
	) { }

	renderTemplate(container: HTMLElement): ResourceTemplate {
		const element = append(container, $('.resource'));
		const name = append(element, $('.name'));
		const fileLabel = this.labels.create(name, { supportHighlights: true });
		const actionsContainer = append(fileLabel.element, $('.actions'));
		const actionBar = new ActionBar(actionsContainer, {
			actionViewItemProvider: this.actionViewItemProvider,
			actionRunner: new MultipleSelectionActionRunner(this.getSelectedResources)
		});

		const decorationIcon = append(element, $('.decoration-icon'));
J
Joao Moreno 已提交
192
		const disposables = combinedDisposable(actionBar, fileLabel);
J
Joao Moreno 已提交
193

J
Joao Moreno 已提交
194
		return { element, name, fileLabel, decorationIcon, actionBar, elementDisposables: Disposable.None, disposables };
J
Joao Moreno 已提交
195 196
	}

197
	renderElement(node: ITreeNode<ISCMResource, FuzzyScore> | ITreeNode<ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>, FuzzyScore>, index: number, template: ResourceTemplate): void {
J
Joao Moreno 已提交
198
		template.elementDisposables.dispose();
J
Joao Moreno 已提交
199

J
Joao Moreno 已提交
200
		const elementDisposables = new DisposableStore();
J
Joao Moreno 已提交
201
		const resourceOrFolder = node.element;
J
Joao Moreno 已提交
202
		const theme = this.themeService.getTheme();
J
Joao Moreno 已提交
203 204
		const iconResource = ResourceTree.isResourceNode(resourceOrFolder) ? resourceOrFolder.element : resourceOrFolder;
		const icon = iconResource && (theme.type === LIGHT ? iconResource.decorations.icon : iconResource.decorations.iconDark);
J
Joao Moreno 已提交
205

206 207
		const uri = ResourceTree.isResourceNode(resourceOrFolder) ? resourceOrFolder.uri : resourceOrFolder.sourceUri;
		const fileKind = ResourceTree.isResourceNode(resourceOrFolder) ? FileKind.FOLDER : FileKind.FILE;
J
Joao Moreno 已提交
208 209
		const viewModel = this.viewModelProvider();

J
Joao Moreno 已提交
210 211
		template.fileLabel.setFile(uri, {
			fileDecorations: { colors: false, badges: !icon },
J
Joao Moreno 已提交
212
			hidePath: viewModel.mode === ViewModelMode.Tree,
J
Joao Moreno 已提交
213 214 215
			fileKind,
			matches: createMatches(node.filterData)
		});
J
Joao Moreno 已提交
216

J
Joao Moreno 已提交
217
		template.actionBar.clear();
J
Joao Moreno 已提交
218
		template.actionBar.context = resourceOrFolder;
J
Joao Moreno 已提交
219

220
		if (ResourceTree.isResourceNode(resourceOrFolder)) {
J
Joao Moreno 已提交
221 222 223 224 225 226 227 228 229
			if (resourceOrFolder.element) {
				elementDisposables.add(connectPrimaryMenuToInlineActionBar(this.menus.getResourceMenu(resourceOrFolder.element.resourceGroup), template.actionBar));
				toggleClass(template.name, 'strike-through', resourceOrFolder.element.decorations.strikeThrough);
				toggleClass(template.element, 'faded', resourceOrFolder.element.decorations.faded);
			} else {
				elementDisposables.add(connectPrimaryMenuToInlineActionBar(this.menus.getResourceFolderMenu(resourceOrFolder.context), template.actionBar));
				removeClass(template.name, 'strike-through');
				removeClass(template.element, 'faded');
			}
J
Joao Moreno 已提交
230
		} else {
J
Joao Moreno 已提交
231 232 233
			elementDisposables.add(connectPrimaryMenuToInlineActionBar(this.menus.getResourceMenu(resourceOrFolder.resourceGroup), template.actionBar));
			toggleClass(template.name, 'strike-through', resourceOrFolder.decorations.strikeThrough);
			toggleClass(template.element, 'faded', resourceOrFolder.decorations.faded);
J
Joao Moreno 已提交
234 235
		}

236
		const tooltip = !ResourceTree.isResourceNode(resourceOrFolder) && resourceOrFolder.decorations.tooltip || '';
J
Joao Moreno 已提交
237 238 239 240 241 242 243 244

		if (icon) {
			template.decorationIcon.style.display = '';
			template.decorationIcon.style.backgroundImage = `url('${icon}')`;
			template.decorationIcon.title = tooltip;
		} else {
			template.decorationIcon.style.display = 'none';
			template.decorationIcon.style.backgroundImage = '';
J
Joao Moreno 已提交
245
			template.decorationIcon.title = '';
J
Joao Moreno 已提交
246 247 248
		}

		template.element.setAttribute('data-tooltip', tooltip);
J
Joao Moreno 已提交
249
		template.elementDisposables = elementDisposables;
J
Joao Moreno 已提交
250 251
	}

252
	disposeElement(resource: ITreeNode<ISCMResource, FuzzyScore> | ITreeNode<IResourceNode<ISCMResource, ISCMResourceGroup>, FuzzyScore>, index: number, template: ResourceTemplate): void {
J
Joao Moreno 已提交
253
		template.elementDisposables.dispose();
J
Joao Moreno 已提交
254
	}
J
Joao Moreno 已提交
255

256
	renderCompressedElements(node: ITreeNode<ICompressedTreeNode<ISCMResource> | ICompressedTreeNode<IResourceNode<ISCMResource, ISCMResourceGroup>>, FuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void {
J
Joao Moreno 已提交
257 258 259
		template.elementDisposables.dispose();

		const elementDisposables = new DisposableStore();
260
		const compressed = node.element as ICompressedTreeNode<IResourceNode<ISCMResource, ISCMResourceGroup>>;
J
Joao Moreno 已提交
261
		const folder = compressed.elements[compressed.elements.length - 1];
J
Joao Moreno 已提交
262 263 264

		const label = compressed.elements.map(e => e.name).join('/');
		const fileKind = FileKind.FOLDER;
J
Joao Moreno 已提交
265

J
Joao Moreno 已提交
266
		template.fileLabel.setResource({ resource: folder.uri, name: label }, {
J
Joao Moreno 已提交
267 268 269 270
			fileDecorations: { colors: false, badges: true },
			fileKind,
			matches: createMatches(node.filterData)
		});
J
Joao Moreno 已提交
271

J
Joao Moreno 已提交
272
		template.actionBar.clear();
J
Joao Moreno 已提交
273
		template.actionBar.context = folder;
J
Joao Moreno 已提交
274

J
Joao Moreno 已提交
275
		elementDisposables.add(connectPrimaryMenuToInlineActionBar(this.menus.getResourceFolderMenu(folder.context), template.actionBar));
J
Joao Moreno 已提交
276

J
Joao Moreno 已提交
277 278
		removeClass(template.name, 'strike-through');
		removeClass(template.element, 'faded');
J
Joao Moreno 已提交
279 280 281
		template.decorationIcon.style.display = 'none';
		template.decorationIcon.style.backgroundImage = '';

J
Joao Moreno 已提交
282 283
		template.element.setAttribute('data-tooltip', '');
		template.elementDisposables = elementDisposables;
J
Joao Moreno 已提交
284 285
	}

286
	disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<ISCMResource> | ICompressedTreeNode<IResourceNode<ISCMResource, ISCMResourceGroup>>, FuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void {
J
Joao Moreno 已提交
287
		template.elementDisposables.dispose();
J
Joao Moreno 已提交
288 289 290
	}

	disposeTemplate(template: ResourceTemplate): void {
J
Joao Moreno 已提交
291 292
		template.elementDisposables.dispose();
		template.disposables.dispose();
J
Joao Moreno 已提交
293 294 295 296 297 298 299 300
	}
}

class ProviderListDelegate implements IListVirtualDelegate<TreeElement> {

	getHeight() { return 22; }

	getTemplateId(element: TreeElement) {
301
		if (ResourceTree.isResourceNode(element) || isSCMResource(element)) {
J
Joao Moreno 已提交
302 303 304 305 306 307 308 309 310 311
			return ResourceRenderer.TEMPLATE_ID;
		} else {
			return ResourceGroupRenderer.TEMPLATE_ID;
		}
	}
}

class SCMTreeFilter implements ITreeFilter<TreeElement> {

	filter(element: TreeElement): boolean {
312
		if (ResourceTree.isResourceNode(element)) {
J
Joao Moreno 已提交
313 314 315 316 317 318 319 320 321 322 323
			return true;
		} else if (isSCMResourceGroup(element)) {
			return element.elements.length > 0 || !element.hideWhenEmpty;
		} else {
			return true;
		}
	}
}

export class SCMTreeSorter implements ITreeSorter<TreeElement> {

J
Joao Moreno 已提交
324 325 326 327 328
	@memoize
	private get viewModel(): ViewModel { return this.viewModelProvider(); }

	constructor(private viewModelProvider: () => ViewModel) { }

J
Joao Moreno 已提交
329
	compare(one: TreeElement, other: TreeElement): number {
J
Joao Moreno 已提交
330 331 332 333
		if (this.viewModel.mode === ViewModelMode.List) {
			return 0;
		}

J
Joao Moreno 已提交
334 335 336 337
		if (isSCMResourceGroup(one) && isSCMResourceGroup(other)) {
			return 0;
		}

338 339
		const oneIsDirectory = ResourceTree.isResourceNode(one);
		const otherIsDirectory = ResourceTree.isResourceNode(other);
J
Joao Moreno 已提交
340 341 342 343 344

		if (oneIsDirectory !== otherIsDirectory) {
			return oneIsDirectory ? -1 : 1;
		}

345 346
		const oneName = ResourceTree.isResourceNode(one) ? one.name : basename((one as ISCMResource).sourceUri);
		const otherName = ResourceTree.isResourceNode(other) ? other.name : basename((other as ISCMResource).sourceUri);
J
Joao Moreno 已提交
347 348 349 350 351

		return compareFileNames(oneName, otherName);
	}
}

352
export class SCMTreeKeyboardNavigationLabelProvider implements ICompressibleKeyboardNavigationLabelProvider<TreeElement> {
J
Joao Moreno 已提交
353 354

	getKeyboardNavigationLabel(element: TreeElement): { toString(): string; } | undefined {
355
		if (ResourceTree.isResourceNode(element)) {
356 357
			return element.name;
		} else if (isSCMResourceGroup(element)) {
J
Joao Moreno 已提交
358
			return element.label;
359
		} else {
J
Joao Moreno 已提交
360 361
			return basename(element.sourceUri);
		}
362
	}
J
Joao Moreno 已提交
363

364
	getCompressedNodeKeyboardNavigationLabel(elements: TreeElement[]): { toString(): string | undefined; } | undefined {
365
		const folders = elements as IResourceNode<ISCMResource, ISCMResourceGroup>[];
366
		return folders.map(e => e.name).join('/');
J
Joao Moreno 已提交
367 368 369
	}
}

J
Joao Moreno 已提交
370 371 372
class SCMResourceIdentityProvider implements IIdentityProvider<TreeElement> {

	getId(element: TreeElement): string {
373
		if (ResourceTree.isResourceNode(element)) {
J
Joao Moreno 已提交
374 375 376 377
			const group = element.context;
			return `${group.provider.contextValue}/${group.id}/$FOLDER/${element.uri.toString()}`;
		} else if (isSCMResource(element)) {
			const group = element.resourceGroup;
J
Joao Moreno 已提交
378
			const provider = group.provider;
J
Joao Moreno 已提交
379
			return `${provider.contextValue}/${group.id}/${element.sourceUri.toString()}`;
J
Joao Moreno 已提交
380
		} else {
J
Joao Moreno 已提交
381 382
			const provider = element.provider;
			return `${provider.contextValue}/${element.id}`;
J
Joao Moreno 已提交
383 384
		}
	}
J
Joao Moreno 已提交
385
}
J
Joao Moreno 已提交
386 387 388 389

interface IGroupItem {
	readonly group: ISCMResourceGroup;
	readonly resources: ISCMResource[];
J
Joao Moreno 已提交
390
	readonly tree: ResourceTree<ISCMResource, ISCMResourceGroup>;
J
Joao Moreno 已提交
391 392 393
	readonly disposable: IDisposable;
}

J
Joao Moreno 已提交
394 395
function groupItemAsTreeElement(item: IGroupItem, mode: ViewModelMode): ICompressedTreeElement<TreeElement> {
	const children = mode === ViewModelMode.List
396 397 398
		? Iterator.map(Iterator.fromArray(item.resources), element => ({ element, incompressible: true }))
		: Iterator.map(item.tree.root.children, node => asTreeElement(node, true));

J
Joao Moreno 已提交
399
	return { element: item.group, children, incompressible: true, collapsible: true };
400 401
}

402 403
function asTreeElement(node: IResourceNode<ISCMResource, ISCMResourceGroup>, forceIncompressible: boolean): ICompressedTreeElement<TreeElement> {
	return {
J
Joao Moreno 已提交
404
		element: (node.childrenCount === 0 && node.element) ? node.element : node,
405
		children: Iterator.map(node.children, node => asTreeElement(node, false)),
J
Joao Moreno 已提交
406
		incompressible: !!node.element || forceIncompressible
407
	};
J
Joao Moreno 已提交
408 409
}

J
Joao Moreno 已提交
410
const enum ViewModelMode {
J
Joao Moreno 已提交
411 412
	List = 'list',
	Tree = 'tree'
J
Joao Moreno 已提交
413 414
}

415
class ViewModel {
J
Joao Moreno 已提交
416

417
	private readonly _onDidChangeMode = new Emitter<ViewModelMode>();
J
Joao Moreno 已提交
418 419 420 421 422
	readonly onDidChangeMode = this._onDidChangeMode.event;

	get mode(): ViewModelMode { return this._mode; }
	set mode(mode: ViewModelMode) {
		this._mode = mode;
423 424 425 426 427 428 429 430 431 432 433

		for (const item of this.items) {
			item.tree.clear();

			if (mode === ViewModelMode.Tree) {
				for (const resource of item.resources) {
					item.tree.add(resource.sourceUri, resource);
				}
			}
		}

J
Joao Moreno 已提交
434 435 436 437
		this.refresh();
		this._onDidChangeMode.fire(mode);
	}

J
Joao Moreno 已提交
438
	private items: IGroupItem[] = [];
439
	private visibilityDisposables = new DisposableStore();
J
Joao Moreno 已提交
440
	private scrollTop: number | undefined;
J
Joao Moreno 已提交
441
	private firstVisible = true;
J
Joao Moreno 已提交
442 443 444
	private disposables = new DisposableStore();

	constructor(
445
		private groups: ISequence<ISCMResourceGroup>,
J
Joao Moreno 已提交
446
		private tree: WorkbenchCompressibleObjectTree<TreeElement, FuzzyScore>,
J
Joao Moreno 已提交
447 448 449
		private _mode: ViewModelMode,
		@IEditorService protected editorService: IEditorService,
		@IConfigurationService protected configurationService: IConfigurationService,
450
	) { }
J
Joao Moreno 已提交
451 452 453 454 455

	private onDidSpliceGroups({ start, deleteCount, toInsert }: ISplice<ISCMResourceGroup>): void {
		const itemsToInsert: IGroupItem[] = [];

		for (const group of toInsert) {
J
Joao Moreno 已提交
456
			const tree = new ResourceTree<ISCMResource, ISCMResourceGroup>(group, group.provider.rootUri || URI.file('/'));
J
Joao Moreno 已提交
457 458
			const resources: ISCMResource[] = [...group.elements];
			const disposable = combinedDisposable(
459
				group.onDidChange(() => this.tree.refilter()),
J
Joao Moreno 已提交
460 461
				group.onDidSplice(splice => this.onDidSpliceGroup(item, splice))
			);
462

463
			const item: IGroupItem = { group, resources, tree, disposable };
464

465 466 467 468
			if (this._mode === ViewModelMode.Tree) {
				for (const resource of resources) {
					item.tree.add(resource.sourceUri, resource);
				}
469
			}
J
Joao Moreno 已提交
470 471 472 473 474 475 476 477 478 479

			itemsToInsert.push(item);
		}

		const itemsToDispose = this.items.splice(start, deleteCount, ...itemsToInsert);

		for (const item of itemsToDispose) {
			item.disposable.dispose();
		}

480
		this.refresh();
J
Joao Moreno 已提交
481 482 483 484 485
	}

	private onDidSpliceGroup(item: IGroupItem, { start, deleteCount, toInsert }: ISplice<ISCMResource>): void {
		const deleted = item.resources.splice(start, deleteCount, ...toInsert);

486 487 488 489
		if (this._mode === ViewModelMode.Tree) {
			for (const resource of deleted) {
				item.tree.delete(resource.sourceUri);
			}
J
Joao Moreno 已提交
490 491 492 493

			for (const resource of toInsert) {
				item.tree.add(resource.sourceUri, resource);
			}
J
Joao Moreno 已提交
494 495
		}

496 497
		this.refresh(item);
	}
J
Joao Moreno 已提交
498

499 500 501
	setVisible(visible: boolean): void {
		if (visible) {
			this.visibilityDisposables = new DisposableStore();
502 503
			this.groups.onDidSplice(this.onDidSpliceGroups, this, this.visibilityDisposables);
			this.onDidSpliceGroups({ start: 0, deleteCount: this.items.length, toInsert: this.groups.elements });
J
Joao Moreno 已提交
504 505 506 507 508

			if (typeof this.scrollTop === 'number') {
				this.tree.scrollTop = this.scrollTop;
				this.scrollTop = undefined;
			}
J
Joao Moreno 已提交
509 510 511

			this.editorService.onDidActiveEditorChange(this.onDidActiveEditorChange, this, this.visibilityDisposables);
			this.onDidActiveEditorChange();
512 513 514
		} else {
			this.visibilityDisposables.dispose();
			this.onDidSpliceGroups({ start: 0, deleteCount: this.items.length, toInsert: [] });
J
Joao Moreno 已提交
515
			this.scrollTop = this.tree.scrollTop;
516 517
		}
	}
J
Joao Moreno 已提交
518

519 520
	private refresh(item?: IGroupItem): void {
		if (item) {
J
Joao Moreno 已提交
521
			this.tree.setChildren(item.group, groupItemAsTreeElement(item, this.mode).children);
522
		} else {
J
Joao Moreno 已提交
523
			this.tree.setChildren(null, this.items.map(item => groupItemAsTreeElement(item, this.mode)));
524
		}
J
Joao Moreno 已提交
525 526
	}

J
Joao Moreno 已提交
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550
	private onDidActiveEditorChange(): void {
		if (!this.configurationService.getValue<boolean>('scm.autoReveal')) {
			return;
		}

		if (this.firstVisible) {
			this.firstVisible = false;
			this.visibilityDisposables.add(disposableTimeout(() => this.onDidActiveEditorChange(), 250));
			return;
		}

		const editor = this.editorService.activeEditor;

		if (!editor) {
			return;
		}

		const uri = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });

		if (!uri) {
			return;
		}

		// go backwards from last group
551
		for (let i = this.items.length - 1; i >= 0; i--) {
J
Joao Moreno 已提交
552 553 554 555 556 557 558 559 560
			const item = this.items[i];
			const resource = this.mode === ViewModelMode.Tree
				? item.tree.getNode(uri)?.element
				: find(item.resources, r => isEqual(r.sourceUri, uri));

			if (resource) {
				this.tree.reveal(resource);
				this.tree.setSelection([resource]);
				this.tree.setFocus([resource]);
561
				return;
J
Joao Moreno 已提交
562 563 564 565
			}
		}
	}

J
Joao Moreno 已提交
566
	dispose(): void {
567 568
		this.visibilityDisposables.dispose();
		this.disposables.dispose();
J
Joao Moreno 已提交
569 570 571
	}
}

J
Joao Moreno 已提交
572 573 574
export class ToggleViewModeAction extends Action {

	static readonly ID = 'workbench.scm.action.toggleViewMode';
575
	static readonly LABEL = localize('toggleViewMode', "Toggle View Mode");
J
Joao Moreno 已提交
576 577 578 579 580 581 582 583 584 585 586 587 588

	constructor(private viewModel: ViewModel) {
		super(ToggleViewModeAction.ID, ToggleViewModeAction.LABEL);

		this._register(this.viewModel.onDidChangeMode(this.onDidChangeMode, this));
		this.onDidChangeMode(this.viewModel.mode);
	}

	async run(): Promise<void> {
		this.viewModel.mode = this.viewModel.mode === ViewModelMode.List ? ViewModelMode.Tree : ViewModelMode.List;
	}

	private onDidChangeMode(mode: ViewModelMode): void {
M
Miguel Solorio 已提交
589
		const iconClass = mode === ViewModelMode.List ? 'codicon-list-tree' : 'codicon-list-flat';
J
Joao Moreno 已提交
590
		this.class = `scm-action toggle-view-mode ${iconClass}`;
J
Joao Moreno 已提交
591 592 593
	}
}

S
SteVen Batten 已提交
594
export class RepositoryPane extends ViewPane {
J
Joao Moreno 已提交
595 596 597

	private cachedHeight: number | undefined = undefined;
	private cachedWidth: number | undefined = undefined;
J
Joao Moreno 已提交
598
	private inputContainer!: HTMLElement;
J
Joao Moreno 已提交
599
	private validationContainer!: HTMLElement;
J
Joao Moreno 已提交
600 601
	private inputEditor!: CodeEditorWidget;
	private inputModel!: ITextModel;
J
Joao Moreno 已提交
602
	private listContainer!: HTMLElement;
J
Joao Moreno 已提交
603
	private tree!: WorkbenchCompressibleObjectTree<TreeElement, FuzzyScore>;
J
Joao Moreno 已提交
604 605
	private viewModel!: ViewModel;
	private listLabels!: ResourceLabels;
J
Joao Moreno 已提交
606
	private menus: SCMMenus;
J
Joao Moreno 已提交
607
	private toggleViewModelModeAction: ToggleViewModeAction | undefined;
J
Joao Moreno 已提交
608
	protected contextKeyService: IContextKeyService;
609
	private commitTemplate = '';
J
Joao Moreno 已提交
610 611 612

	constructor(
		readonly repository: ISCMRepository,
S
SteVen Batten 已提交
613
		options: IViewPaneOptions,
J
Joao Moreno 已提交
614
		@IKeybindingService protected keybindingService: IKeybindingService,
615
		@IWorkbenchThemeService protected themeService: IWorkbenchThemeService,
J
Joao Moreno 已提交
616 617 618 619 620 621 622 623
		@IContextMenuService protected contextMenuService: IContextMenuService,
		@IContextViewService protected contextViewService: IContextViewService,
		@ICommandService protected commandService: ICommandService,
		@INotificationService private readonly notificationService: INotificationService,
		@IEditorService protected editorService: IEditorService,
		@IInstantiationService protected instantiationService: IInstantiationService,
		@IConfigurationService protected configurationService: IConfigurationService,
		@IContextKeyService contextKeyService: IContextKeyService,
J
Joao Moreno 已提交
624
		@IMenuService protected menuService: IMenuService,
J
Joao Moreno 已提交
625
		@IStorageService private storageService: IStorageService,
J
Joao Moreno 已提交
626
		@IModelService private modelService: IModelService,
J
Joao Moreno 已提交
627
	) {
628
		super(options, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService);
J
Joao Moreno 已提交
629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665

		this.menus = instantiationService.createInstance(SCMMenus, this.repository.provider);
		this._register(this.menus);
		this._register(this.menus.onDidChangeTitle(this._onDidChangeTitleArea.fire, this._onDidChangeTitleArea));

		this.contextKeyService = contextKeyService.createScoped(this.element);
		this.contextKeyService.createKey('scmRepository', this.repository);
	}

	render(): void {
		super.render();
		this._register(this.menus.onDidChangeTitle(this.updateActions, this));
	}

	protected renderHeaderTitle(container: HTMLElement): void {
		let title: string;
		let type: string;

		if (this.repository.provider.rootUri) {
			title = basename(this.repository.provider.rootUri);
			type = this.repository.provider.label;
		} else {
			title = this.repository.provider.label;
			type = '';
		}

		super.renderHeaderTitle(container, title);
		addClass(container, 'scm-provider');
		append(container, $('span.type', undefined, type));
	}

	protected renderBody(container: HTMLElement): void {
		const focusTracker = trackFocus(container);
		this._register(focusTracker.onDidFocus(() => this.repository.focus()));
		this._register(focusTracker);

		// Input
J
Joao Moreno 已提交
666 667
		this.inputContainer = append(container, $('.scm-editor'));
		const editorContainer = append(this.inputContainer, $('.scm-editor-container'));
J
Joao Moreno 已提交
668

J
Joao Moreno 已提交
669
		const placeholderTextContainer = append(editorContainer, $('.scm-editor-placeholder'));
J
Joao Moreno 已提交
670
		const updatePlaceholder = () => {
J
Joao Moreno 已提交
671 672 673
			const binding = this.keybindingService.lookupKeybinding('scm.acceptInput');
			const label = binding ? binding.getLabel() : (platform.isMacintosh ? 'Cmd+Enter' : 'Ctrl+Enter');
			const placeholderText = format(this.repository.input.placeholder, label);
J
Joao Moreno 已提交
674

J
Joao Moreno 已提交
675
			placeholderTextContainer.textContent = placeholderText;
J
Joao Moreno 已提交
676 677
		};

J
Joao Moreno 已提交
678 679
		this.validationContainer = append(editorContainer, $('.scm-editor-validation'));

J
Joao Moreno 已提交
680 681 682 683 684 685 686 687
		const validationDelayer = new ThrottledDelayer<any>(200);
		const validate = () => {
			const position = this.inputEditor.getSelection()?.getStartPosition();
			const offset = position && this.inputModel.getOffsetAt(position);
			const value = this.inputModel.getValue();

			return this.repository.input.validateInput(value, offset || 0).then(result => {
				if (!result) {
J
Joao Moreno 已提交
688 689 690 691 692 693 694
					removeClass(editorContainer, 'validation-info');
					removeClass(editorContainer, 'validation-warning');
					removeClass(editorContainer, 'validation-error');
					removeClass(this.validationContainer, 'validation-info');
					removeClass(this.validationContainer, 'validation-warning');
					removeClass(this.validationContainer, 'validation-error');
					this.validationContainer.textContent = null;
J
Joao Moreno 已提交
695
				} else {
J
Joao Moreno 已提交
696 697 698 699 700 701 702
					toggleClass(editorContainer, 'validation-info', result.type === InputValidationType.Information);
					toggleClass(editorContainer, 'validation-warning', result.type === InputValidationType.Warning);
					toggleClass(editorContainer, 'validation-error', result.type === InputValidationType.Error);
					toggleClass(this.validationContainer, 'validation-info', result.type === InputValidationType.Information);
					toggleClass(this.validationContainer, 'validation-warning', result.type === InputValidationType.Warning);
					toggleClass(this.validationContainer, 'validation-error', result.type === InputValidationType.Error);
					this.validationContainer.textContent = result.message;
J
Joao Moreno 已提交
703 704 705
				}
			});
		};
J
Joao Moreno 已提交
706

J
Joao Moreno 已提交
707
		const triggerValidation = () => validationDelayer.trigger(validate);
J
Joao Moreno 已提交
708 709

		const editorOptions: IEditorConstructionOptions = {
J
Joao Moreno 已提交
710 711 712
			...getSimpleEditorOptions(),
			lineDecorationsWidth: 4,
			dragAndDrop: false,
J
Joao Moreno 已提交
713 714 715 716 717
			cursorWidth: 1,
			fontSize: 13,
			lineHeight: 20,
			fontFamily: ' -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif',
			wrappingAlgorithm: 'dom',
718 719 720 721
			wrappingIndent: 'none',
			suggest: {
				showWords: false
			}
J
Joao Moreno 已提交
722
		};
J
Joao Moreno 已提交
723

J
Joao Moreno 已提交
724
		const codeEditorWidgetOptions: ICodeEditorWidgetOptions = {
J
Joao Moreno 已提交
725 726
			isSimpleWidget: true,
			contributions: EditorExtensionsRegistry.getSomeEditorContributions([
J
Joao Moreno 已提交
727 728
				SuggestController.ID,
				SnippetController2.ID,
J
Joao Moreno 已提交
729 730
				MenuPreventer.ID,
				SelectionClipboardContributionID,
J
Joao Moreno 已提交
731
				ContextMenuController.ID,
J
Joao Moreno 已提交
732
			])
J
Joao Moreno 已提交
733 734
		};

J
Joao Moreno 已提交
735 736 737
		const services = new ServiceCollection([IContextKeyService, this.contextKeyService]);
		const instantiationService = this.instantiationService.createChild(services);
		this.inputEditor = instantiationService.createInstance(CodeEditorWidget, editorContainer, editorOptions, codeEditorWidgetOptions);
J
Joao Moreno 已提交
738 739 740 741 742

		this._register(this.inputEditor);

		this._register(this.inputEditor.onDidFocusEditorText(() => addClass(editorContainer, 'synthetic-focus')));
		this._register(this.inputEditor.onDidBlurEditorText(() => removeClass(editorContainer, 'synthetic-focus')));
J
Joao Moreno 已提交
743

744 745 746 747 748 749 750 751 752 753 754 755 756
		let query: string | undefined;

		if (this.repository.provider.rootUri) {
			query = `rootUri=${encodeURIComponent(this.repository.provider.rootUri.toString())}`;
		}

		const uri = URI.from({
			scheme: Schemas.vscode,
			path: `scm/${this.repository.provider.contextValue}/${this.repository.provider.id}/input`,
			query
		});

		this.inputModel = this.modelService.createModel('', null, uri);
J
Joao Moreno 已提交
757
		this.inputEditor.setModel(this.inputModel);
J
Joao Moreno 已提交
758

J
Joao Moreno 已提交
759 760 761 762
		this.inputEditor.changeViewZones(accessor => {
			accessor.addZone({ afterLineNumber: 0, domNode: $('div'), heightInPx: 3 });
		});

J
Joao Moreno 已提交
763
		this._register(this.inputEditor.onDidChangeCursorPosition(triggerValidation));
J
Joao Moreno 已提交
764

765 766 767
		// Keep model in sync with API
		this.inputModel.setValue(this.repository.input.value);
		this._register(this.repository.input.onDidChange(value => this.inputModel.setValue(value)));
J
Joao Moreno 已提交
768

769
		// Keep API in sync with model and update placeholder and validation
J
Joao Moreno 已提交
770
		this.inputModel.onDidChangeContent(() => {
771
			this.repository.input.value = this.inputModel.getValue();
J
Joao Moreno 已提交
772
			toggleClass(placeholderTextContainer, 'hidden', this.inputModel.getValueLength() > 0);
773
			triggerValidation();
J
Joao Moreno 已提交
774 775
		});

J
Joao Moreno 已提交
776 777 778 779
		updatePlaceholder();
		this._register(this.repository.input.onDidChangePlaceholder(updatePlaceholder, null));
		this._register(this.keybindingService.onDidUpdateKeybindings(updatePlaceholder, null));

780
		const onDidChangeContentHeight = Event.filter(this.inputEditor.onDidContentSizeChange, e => e.contentHeightChanged);
J
Joao Moreno 已提交
781
		this._register(onDidChangeContentHeight(() => this.layoutBody()));
J
Joao Moreno 已提交
782 783

		if (this.repository.provider.onDidChangeCommitTemplate) {
J
Joao Moreno 已提交
784
			this._register(this.repository.provider.onDidChangeCommitTemplate(this.onDidChangeCommitTemplate, this));
J
Joao Moreno 已提交
785 786
		}

J
Joao Moreno 已提交
787
		this.onDidChangeCommitTemplate();
J
Joao Moreno 已提交
788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808

		// Input box visibility
		this._register(this.repository.input.onDidChangeVisibility(this.updateInputBoxVisibility, this));
		this.updateInputBoxVisibility();

		// List
		this.listContainer = append(container, $('.scm-status.show-file-icons'));

		const updateActionsVisibility = () => toggleClass(this.listContainer, 'show-actions', this.configurationService.getValue<boolean>('scm.alwaysShowActions'));
		Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.alwaysShowActions'))(updateActionsVisibility);
		updateActionsVisibility();

		const delegate = new ProviderListDelegate();

		const actionViewItemProvider = (action: IAction) => this.getActionViewItem(action);

		this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility });
		this._register(this.listLabels);

		const renderers = [
			new ResourceGroupRenderer(actionViewItemProvider, this.themeService, this.menus),
J
Joao Moreno 已提交
809
			new ResourceRenderer(() => this.viewModel, this.listLabels, actionViewItemProvider, () => this.getSelectedResources(), this.themeService, this.menus)
J
Joao Moreno 已提交
810 811 812
		];

		const filter = new SCMTreeFilter();
J
Joao Moreno 已提交
813
		const sorter = new SCMTreeSorter(() => this.viewModel);
J
Joao Moreno 已提交
814
		const keyboardNavigationLabelProvider = new SCMTreeKeyboardNavigationLabelProvider();
J
Joao Moreno 已提交
815
		const identityProvider = new SCMResourceIdentityProvider();
J
Joao Moreno 已提交
816

817
		this.tree = this.instantiationService.createInstance<typeof WorkbenchCompressibleObjectTree, WorkbenchCompressibleObjectTree<TreeElement, FuzzyScore>>(
J
Joao Moreno 已提交
818
			WorkbenchCompressibleObjectTree,
J
Joao Moreno 已提交
819
			'SCM Tree Repo',
J
Joao Moreno 已提交
820 821 822 823
			this.listContainer,
			delegate,
			renderers,
			{
J
Joao Moreno 已提交
824
				identityProvider,
J
Joao Moreno 已提交
825 826 827
				horizontalScrolling: false,
				filter,
				sorter,
J
Joao Moreno 已提交
828 829 830 831
				keyboardNavigationLabelProvider,
				overrideStyles: {
					listBackground: SIDE_BAR_BACKGROUND
				}
J
Joao Moreno 已提交
832 833 834 835
			});

		this._register(Event.chain(this.tree.onDidOpen)
			.map(e => e.elements[0])
836
			.filter(e => !!e && !isSCMResourceGroup(e) && !ResourceTree.isResourceNode(e))
J
Joao Moreno 已提交
837 838
			.on(this.open, this));

J
Joao Moreno 已提交
839 840
		this._register(Event.chain(this.tree.onDidPin)
			.map(e => e.elements[0])
841
			.filter(e => !!e && !isSCMResourceGroup(e) && !ResourceTree.isResourceNode(e))
J
Joao Moreno 已提交
842
			.on(this.pin, this));
J
Joao Moreno 已提交
843

844
		this._register(this.tree.onContextMenu(this.onListContextMenu, this));
J
Joao Moreno 已提交
845 846
		this._register(this.tree);

847
		let mode = this.configurationService.getValue<'tree' | 'list'>('scm.defaultViewMode') === 'list' ? ViewModelMode.List : ViewModelMode.Tree;
J
Joao Moreno 已提交
848

849
		const rootUri = this.repository.provider.rootUri;
J
Joao Moreno 已提交
850

851 852
		if (typeof rootUri !== 'undefined') {
			const storageMode = this.storageService.get(`scm.repository.viewMode:${rootUri.toString()}`, StorageScope.WORKSPACE) as ViewModelMode;
J
Joao Moreno 已提交
853

854 855
			if (typeof storageMode === 'string') {
				mode = storageMode;
J
Joao Moreno 已提交
856 857 858
			}
		}

859
		this.viewModel = this.instantiationService.createInstance(ViewModel, this.repository.provider.groups, this.tree, mode);
860
		this._register(this.viewModel);
J
Joao Moreno 已提交
861

862 863 864
		addClass(this.listContainer, 'file-icon-themable-tree');
		addClass(this.listContainer, 'show-file-icons');

J
Joao Moreno 已提交
865 866 867
		this.updateIndentStyles(this.themeService.getFileIconTheme());
		this._register(this.themeService.onDidFileIconThemeChange(this.updateIndentStyles, this));
		this._register(this.viewModel.onDidChangeMode(this.onDidChangeMode, this));
868

869 870
		this.toggleViewModelModeAction = new ToggleViewModeAction(this.viewModel);
		this._register(this.toggleViewModelModeAction);
J
Joao Moreno 已提交
871

872
		this._register(this.onDidChangeBodyVisibility(this._onDidChangeVisibility, this));
J
Joao Moreno 已提交
873 874

		this.updateActions();
J
Joao Moreno 已提交
875 876
	}

J
Joao Moreno 已提交
877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895
	private updateIndentStyles(theme: IFileIconTheme): void {
		toggleClass(this.listContainer, 'list-view-mode', this.viewModel.mode === ViewModelMode.List);
		toggleClass(this.listContainer, 'tree-view-mode', this.viewModel.mode === ViewModelMode.Tree);
		toggleClass(this.listContainer, 'align-icons-and-twisties', this.viewModel.mode === ViewModelMode.Tree && theme.hasFileIcons && !theme.hasFolderIcons);
		toggleClass(this.listContainer, 'hide-arrows', this.viewModel.mode === ViewModelMode.Tree && theme.hidesExplorerArrows === true);
	}

	private onDidChangeMode(): void {
		this.updateIndentStyles(this.themeService.getFileIconTheme());

		const rootUri = this.repository.provider.rootUri;

		if (typeof rootUri === 'undefined') {
			return;
		}

		this.storageService.store(`scm.repository.viewMode:${rootUri.toString()}`, this.viewModel.mode, StorageScope.WORKSPACE);
	}

J
Joao Moreno 已提交
896 897 898 899 900 901
	layoutBody(height: number | undefined = this.cachedHeight, width: number | undefined = this.cachedWidth): void {
		if (height === undefined) {
			return;
		}

		this.cachedHeight = height;
J
Joao Moreno 已提交
902
		this.cachedWidth = width;
J
Joao Moreno 已提交
903 904

		if (this.repository.input.visible) {
J
Joao Moreno 已提交
905
			removeClass(this.inputContainer, 'hidden');
J
Joao Moreno 已提交
906

907 908
			const editorContentHeight = this.inputEditor.getContentHeight();
			const editorHeight = Math.min(editorContentHeight + 3, 134);
J
Joao Moreno 已提交
909
			this.inputEditor.layout({ height: editorHeight, width: width! - 12 - 16 - 2 });
J
Joao Moreno 已提交
910

J
Joao Moreno 已提交
911 912
			this.validationContainer.style.top = `${editorHeight + 1}px`;

J
Joao Moreno 已提交
913
			const listHeight = height - (editorHeight + 5 + 2 + 5);
J
Joao Moreno 已提交
914 915 916
			this.listContainer.style.height = `${listHeight}px`;
			this.tree.layout(listHeight, width);
		} else {
J
Joao Moreno 已提交
917
			addClass(this.inputContainer, 'hidden');
J
Joao Moreno 已提交
918

J
Joao Moreno 已提交
919
			this.inputEditor.onHide();
J
Joao Moreno 已提交
920 921 922 923 924 925 926 927 928 929
			this.listContainer.style.height = `${height}px`;
			this.tree.layout(height, width);
		}
	}

	focus(): void {
		super.focus();

		if (this.isExpanded()) {
			if (this.repository.input.visible) {
J
Joao Moreno 已提交
930
				this.inputEditor.focus();
J
Joao Moreno 已提交
931 932 933 934 935 936 937 938
			} else {
				this.tree.domFocus();
			}

			this.repository.focus();
		}
	}

939 940
	private _onDidChangeVisibility(visible: boolean): void {
		this.viewModel.setVisible(visible);
J
Joao Moreno 已提交
941 942 943 944 945 946

		if (this.repository.input.visible && visible) {
			this.inputEditor.onVisible();
		} else {
			this.inputEditor.onHide();
		}
947 948
	}

J
Joao Moreno 已提交
949
	getActions(): IAction[] {
J
Joao Moreno 已提交
950 951 952 953 954 955 956 957 958
		if (this.toggleViewModelModeAction) {

			return [
				this.toggleViewModelModeAction,
				...this.menus.getTitleActions()
			];
		} else {
			return this.menus.getTitleActions();
		}
J
Joao Moreno 已提交
959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980
	}

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

	getActionViewItem(action: IAction): IActionViewItem | undefined {
		if (!(action instanceof MenuItemAction)) {
			return undefined;
		}

		return new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService);
	}

	getActionsContext(): any {
		return this.repository.provider;
	}

	private open(e: ISCMResource): void {
		e.open();
	}

J
Joao Moreno 已提交
981 982 983 984 985 986 987
	private pin(): void {
		const activeControl = this.editorService.activeControl;

		if (activeControl) {
			activeControl.group.pinEditor(activeControl.input);
		}
	}
J
Joao Moreno 已提交
988

989 990 991 992
	private onListContextMenu(e: ITreeContextMenuEvent<TreeElement>): void {
		if (!e.element) {
			return;
		}
J
Joao Moreno 已提交
993

994
		const element = e.element;
J
Joao Moreno 已提交
995
		let actions: IAction[] = [];
J
Joao Moreno 已提交
996

997 998 999
		if (isSCMResourceGroup(element)) {
			actions = this.menus.getResourceGroupContextActions(element);
		} else if (ResourceTree.isResourceNode(element)) {
J
Joao Moreno 已提交
1000 1001 1002 1003 1004
			if (element.element) {
				actions = this.menus.getResourceContextActions(element.element);
			} else {
				actions = this.menus.getResourceFolderContextActions(element.context);
			}
1005
		} else {
1006
			actions = this.menus.getResourceContextActions(element);
1007
		}
J
Joao Moreno 已提交
1008

1009 1010 1011 1012 1013 1014 1015
		this.contextMenuService.showContextMenu({
			getAnchor: () => e.anchor,
			getActions: () => actions,
			getActionsContext: () => element,
			actionRunner: new MultipleSelectionActionRunner(() => this.getSelectedResources())
		});
	}
J
Joao Moreno 已提交
1016

1017
	private getSelectedResources(): (ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>)[] {
J
Joao Moreno 已提交
1018
		return this.tree.getSelection()
1019
			.filter(r => !!r && !isSCMResourceGroup(r))! as any;
J
Joao Moreno 已提交
1020 1021
	}

J
Joao Moreno 已提交
1022
	private onDidChangeCommitTemplate(): void {
1023 1024 1025 1026 1027 1028
		if (typeof this.repository.provider.commitTemplate === 'undefined' || !this.repository.input.visible) {
			return;
		}

		const oldCommitTemplate = this.commitTemplate;
		this.commitTemplate = this.repository.provider.commitTemplate;
J
Joao Moreno 已提交
1029

1030
		const value = this.inputModel.getValue();
J
Joao Moreno 已提交
1031

1032 1033 1034
		if (value && value !== oldCommitTemplate) {
			return;
		}
J
Joao Moreno 已提交
1035

1036
		this.inputModel.setValue(this.commitTemplate);
J
Joao Moreno 已提交
1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051
	}

	private updateInputBoxVisibility(): void {
		if (this.cachedHeight) {
			this.layoutBody(this.cachedHeight);
		}
	}
}

export class RepositoryViewDescriptor implements IViewDescriptor {

	private static counter = 0;

	readonly id: string;
	readonly name: string;
S
Sandeep Somavarapu 已提交
1052
	readonly ctorDescriptor: SyncDescriptor<RepositoryPane>;
J
Joao Moreno 已提交
1053 1054 1055 1056 1057 1058
	readonly canToggleVisibility = true;
	readonly order = -500;
	readonly workspace = true;

	constructor(readonly repository: ISCMRepository, readonly hideByDefault: boolean) {
		const repoId = repository.provider.rootUri ? repository.provider.rootUri.toString() : `#${RepositoryViewDescriptor.counter++}`;
J
Joao Moreno 已提交
1059 1060 1061 1062
		const hasher = new Hasher();
		hasher.hash(repository.provider.label);
		hasher.hash(repoId);
		this.id = `scm:repository:${hasher.value}`;
J
Joao Moreno 已提交
1063 1064
		this.name = repository.provider.rootUri ? basename(repository.provider.rootUri) : repository.provider.label;

S
Sandeep Somavarapu 已提交
1065
		this.ctorDescriptor = new SyncDescriptor(RepositoryPane, [repository]);
J
Joao Moreno 已提交
1066 1067
	}
}
J
Joao Moreno 已提交
1068 1069

registerThemingParticipant((theme, collector) => {
J
Joao Moreno 已提交
1070
	const inputPlaceholderForegroundColor = theme.getColor(inputPlaceholderForeground);
J
Joao Moreno 已提交
1071 1072 1073
	if (inputPlaceholderForegroundColor) {
		collector.addRule(`.scm-viewlet .scm-editor-placeholder { color: ${inputPlaceholderForegroundColor}; }`);
	}
J
Joao Moreno 已提交
1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121

	const inputValidationInfoBorderColor = theme.getColor(inputValidationInfoBorder);
	if (inputValidationInfoBorderColor) {
		collector.addRule(`.scm-viewlet .scm-editor-container.validation-info { outline: 1px solid ${inputValidationInfoBorderColor}; }`);
		collector.addRule(`.scm-viewlet .scm-editor-validation.validation-info { border: 1px solid ${inputValidationInfoBorderColor}; }`);
	}

	const inputValidationInfoBackgroundColor = theme.getColor(inputValidationInfoBackground);
	if (inputValidationInfoBackgroundColor) {
		collector.addRule(`.scm-viewlet .scm-editor-validation.validation-info { background-color: ${inputValidationInfoBackgroundColor}; }`);
	}

	const inputValidationInfoForegroundColor = theme.getColor(inputValidationInfoForeground);
	if (inputValidationInfoForegroundColor) {
		collector.addRule(`.scm-viewlet .scm-editor-validation.validation-info { color: ${inputValidationInfoForegroundColor}; }`);
	}

	const inputValidationWarningBorderColor = theme.getColor(inputValidationWarningBorder);
	if (inputValidationWarningBorderColor) {
		collector.addRule(`.scm-viewlet .scm-editor-container.validation-warning { outline: 1px solid ${inputValidationWarningBorderColor}; }`);
		collector.addRule(`.scm-viewlet .scm-editor-validation.validation-warning { border: 1px solid ${inputValidationWarningBorderColor}; }`);
	}

	const inputValidationWarningBackgroundColor = theme.getColor(inputValidationWarningBackground);
	if (inputValidationWarningBackgroundColor) {
		collector.addRule(`.scm-viewlet .scm-editor-validation.validation-warning { background-color: ${inputValidationWarningBackgroundColor}; }`);
	}

	const inputValidationWarningForegroundColor = theme.getColor(inputValidationWarningForeground);
	if (inputValidationWarningForegroundColor) {
		collector.addRule(`.scm-viewlet .scm-editor-validation.validation-warning { color: ${inputValidationWarningForegroundColor}; }`);
	}

	const inputValidationErrorBorderColor = theme.getColor(inputValidationErrorBorder);
	if (inputValidationErrorBorderColor) {
		collector.addRule(`.scm-viewlet .scm-editor-container.validation-error { outline: 1px solid ${inputValidationErrorBorderColor}; }`);
		collector.addRule(`.scm-viewlet .scm-editor-validation.validation-error { border: 1px solid ${inputValidationErrorBorderColor}; }`);
	}

	const inputValidationErrorBackgroundColor = theme.getColor(inputValidationErrorBackground);
	if (inputValidationErrorBackgroundColor) {
		collector.addRule(`.scm-viewlet .scm-editor-validation.validation-error { background-color: ${inputValidationErrorBackgroundColor}; }`);
	}

	const inputValidationErrorForegroundColor = theme.getColor(inputValidationErrorForeground);
	if (inputValidationErrorForegroundColor) {
		collector.addRule(`.scm-viewlet .scm-editor-validation.validation-error { color: ${inputValidationErrorForegroundColor}; }`);
	}
J
Joao Moreno 已提交
1122
});