repositoryPane.ts 45.0 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
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';
44
import { IViewDescriptor, IViewDescriptorService } 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, inputBackground, inputForeground, inputBorder, focusBorder } 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
		@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,
622
		@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
J
Joao Moreno 已提交
623 624
		@IConfigurationService protected configurationService: IConfigurationService,
		@IContextKeyService contextKeyService: IContextKeyService,
J
Joao Moreno 已提交
625
		@IMenuService protected menuService: IMenuService,
J
Joao Moreno 已提交
626
		@IStorageService private storageService: IStorageService,
J
Joao Moreno 已提交
627
		@IModelService private modelService: IModelService,
J
Joao Moreno 已提交
628
	) {
629
		super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService);
J
Joao Moreno 已提交
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 666

		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 已提交
667 668
		this.inputContainer = append(container, $('.scm-editor'));
		const editorContainer = append(this.inputContainer, $('.scm-editor-container'));
J
Joao Moreno 已提交
669

J
Joao Moreno 已提交
670
		const placeholderTextContainer = append(editorContainer, $('.scm-editor-placeholder'));
J
Joao Moreno 已提交
671
		const updatePlaceholder = () => {
J
Joao Moreno 已提交
672 673 674
			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 已提交
675

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

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

J
Joao Moreno 已提交
681 682 683 684 685 686 687 688
		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 已提交
689 690 691 692 693 694 695
					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 已提交
696
				} else {
J
Joao Moreno 已提交
697 698 699 700 701 702 703
					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 已提交
704 705 706
				}
			});
		};
J
Joao Moreno 已提交
707

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

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

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

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

		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 已提交
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
		});

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

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

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

766 767 768
		// 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 已提交
769

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

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

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

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

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

		// 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 已提交
811
			new ResourceRenderer(() => this.viewModel, this.listLabels, actionViewItemProvider, () => this.getSelectedResources(), this.themeService, this.menus)
J
Joao Moreno 已提交
812 813 814
		];

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

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

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

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

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

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

851
		const rootUri = this.repository.provider.rootUri;
J
Joao Moreno 已提交
852

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

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

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

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

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

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

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

		this.updateActions();
J
Joao Moreno 已提交
877 878
	}

J
Joao Moreno 已提交
879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897
	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 已提交
898 899 900 901 902 903
	layoutBody(height: number | undefined = this.cachedHeight, width: number | undefined = this.cachedWidth): void {
		if (height === undefined) {
			return;
		}

		this.cachedHeight = height;
J
Joao Moreno 已提交
904
		this.cachedWidth = width;
J
Joao Moreno 已提交
905 906

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

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

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

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

J
Joao Moreno 已提交
921
			this.inputEditor.onHide();
J
Joao Moreno 已提交
922 923 924 925 926 927 928 929 930 931
			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 已提交
932
				this.inputEditor.focus();
J
Joao Moreno 已提交
933 934 935 936 937 938 939 940
			} else {
				this.tree.domFocus();
			}

			this.repository.focus();
		}
	}

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

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

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

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

	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 已提交
983 984 985 986 987 988 989
	private pin(): void {
		const activeControl = this.editorService.activeControl;

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

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

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

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

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

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

J
Joao Moreno 已提交
1024
	private onDidChangeCommitTemplate(): void {
1025 1026 1027 1028 1029 1030
		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 已提交
1031

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

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

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

	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 已提交
1054
	readonly ctorDescriptor: SyncDescriptor<RepositoryPane>;
J
Joao Moreno 已提交
1055 1056 1057 1058 1059 1060
	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 已提交
1061 1062 1063 1064
		const hasher = new Hasher();
		hasher.hash(repository.provider.label);
		hasher.hash(repoId);
		this.id = `scm:repository:${hasher.value}`;
J
Joao Moreno 已提交
1065 1066
		this.name = repository.provider.rootUri ? basename(repository.provider.rootUri) : repository.provider.label;

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

registerThemingParticipant((theme, collector) => {
J
Joao Moreno 已提交
1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094
	const inputBackgroundColor = theme.getColor(inputBackground);
	if (inputBackgroundColor) {
		collector.addRule(`.scm-viewlet .scm-editor-container .monaco-editor-background,
		.scm-viewlet .scm-editor-container .monaco-editor,
		.scm-viewlet .scm-editor-container .monaco-editor .margin
		{ background-color: ${inputBackgroundColor}; }`);
	}

	const inputForegroundColor = theme.getColor(inputForeground);
	if (inputForegroundColor) {
		collector.addRule(`.scm-viewlet .scm-editor-container .mtk1 { color: ${inputForegroundColor}; }`);
	}

	const inputBorderColor = theme.getColor(inputBorder);
	if (inputBorderColor) {
		collector.addRule(`.scm-viewlet .scm-editor-container { outline: 1px solid ${inputBorderColor}; }`);
	}

	const focusBorderColor = theme.getColor(focusBorder);
	if (focusBorderColor) {
		collector.addRule(`.scm-viewlet .scm-editor-container.synthetic-focus { outline: 1px solid ${focusBorderColor}; }`);
	}

J
Joao Moreno 已提交
1095
	const inputPlaceholderForegroundColor = theme.getColor(inputPlaceholderForeground);
J
Joao Moreno 已提交
1096 1097 1098
	if (inputPlaceholderForegroundColor) {
		collector.addRule(`.scm-viewlet .scm-editor-placeholder { color: ${inputPlaceholderForegroundColor}; }`);
	}
J
Joao Moreno 已提交
1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146

	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 已提交
1147
});