repositoryPane.ts 45.4 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';
J
Joao Moreno 已提交
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';
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';
70
import { IOpenerService } from 'vs/platform/opener/common/opener';
J
Joao Moreno 已提交
71

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

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

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

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

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

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

J
Joao Moreno 已提交
97 98 99 100 101 102 103
		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 已提交
104 105
		const elementDisposables = Disposable.None;
		const disposables = combinedDisposable(actionBar, styler);
J
Joao Moreno 已提交
106

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

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

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

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

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

	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 已提交
130
		template.elementDisposables.dispose();
J
Joao Moreno 已提交
131 132 133
	}

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

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

J
Joao Moreno 已提交
149
class RepositoryPaneActionRunner extends ActionRunner {
J
Joao Moreno 已提交
150

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

J
Joao Moreno 已提交
158
	async runAction(action: IAction, context: ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>): Promise<any> {
J
Joao Moreno 已提交
159 160 161 162 163
		if (!(action instanceof MenuItemAction)) {
			return super.runAction(action, context);
		}

		const selection = this.getSelectedResources();
164 165
		const contextIsSelected = selection.some(s => s === context);
		const actualContext = contextIsSelected ? selection : [context];
166
		const args = flatten(actualContext.map(e => ResourceTree.isResourceNode(e) ? ResourceTree.collect(e) : [e]));
J
Joao Moreno 已提交
167 168
		await action.run(...args);
		this.focus();
J
Joao Moreno 已提交
169 170 171
	}
}

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

174
	static readonly TEMPLATE_ID = 'resource';
J
Joao Moreno 已提交
175 176 177
	get templateId(): string { return ResourceRenderer.TEMPLATE_ID; }

	constructor(
J
Joao Moreno 已提交
178
		private viewModelProvider: () => ViewModel,
J
Joao Moreno 已提交
179 180
		private labels: ResourceLabels,
		private actionViewItemProvider: IActionViewItemProvider,
181
		private getSelectedResources: () => (ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>)[],
J
Joao Moreno 已提交
182
		private focus: () => void,
J
Joao Moreno 已提交
183 184 185 186 187 188 189 190 191 192 193
		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,
J
Joao Moreno 已提交
194
			actionRunner: new RepositoryPaneActionRunner(this.getSelectedResources, this.focus)
J
Joao Moreno 已提交
195 196 197
		});

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

J
Joao Moreno 已提交
200
		return { element, name, fileLabel, decorationIcon, actionBar, elementDisposables: Disposable.None, disposables };
J
Joao Moreno 已提交
201 202
	}

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

J
Joao Moreno 已提交
206
		const elementDisposables = new DisposableStore();
J
Joao Moreno 已提交
207
		const resourceOrFolder = node.element;
J
Joao Moreno 已提交
208
		const theme = this.themeService.getTheme();
J
Joao Moreno 已提交
209 210
		const iconResource = ResourceTree.isResourceNode(resourceOrFolder) ? resourceOrFolder.element : resourceOrFolder;
		const icon = iconResource && (theme.type === LIGHT ? iconResource.decorations.icon : iconResource.decorations.iconDark);
J
Joao Moreno 已提交
211

212 213
		const uri = ResourceTree.isResourceNode(resourceOrFolder) ? resourceOrFolder.uri : resourceOrFolder.sourceUri;
		const fileKind = ResourceTree.isResourceNode(resourceOrFolder) ? FileKind.FOLDER : FileKind.FILE;
J
Joao Moreno 已提交
214 215
		const viewModel = this.viewModelProvider();

J
Joao Moreno 已提交
216 217
		template.fileLabel.setFile(uri, {
			fileDecorations: { colors: false, badges: !icon },
J
Joao Moreno 已提交
218
			hidePath: viewModel.mode === ViewModelMode.Tree,
J
Joao Moreno 已提交
219 220 221
			fileKind,
			matches: createMatches(node.filterData)
		});
J
Joao Moreno 已提交
222

J
Joao Moreno 已提交
223
		template.actionBar.clear();
J
Joao Moreno 已提交
224
		template.actionBar.context = resourceOrFolder;
J
Joao Moreno 已提交
225

226
		if (ResourceTree.isResourceNode(resourceOrFolder)) {
J
Joao Moreno 已提交
227 228 229 230 231 232 233 234 235
			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 已提交
236
		} else {
J
Joao Moreno 已提交
237 238 239
			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 已提交
240 241
		}

242
		const tooltip = !ResourceTree.isResourceNode(resourceOrFolder) && resourceOrFolder.decorations.tooltip || '';
J
Joao Moreno 已提交
243 244 245 246 247 248 249 250

		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 已提交
251
			template.decorationIcon.title = '';
J
Joao Moreno 已提交
252 253 254
		}

		template.element.setAttribute('data-tooltip', tooltip);
J
Joao Moreno 已提交
255
		template.elementDisposables = elementDisposables;
J
Joao Moreno 已提交
256 257
	}

258
	disposeElement(resource: ITreeNode<ISCMResource, FuzzyScore> | ITreeNode<IResourceNode<ISCMResource, ISCMResourceGroup>, FuzzyScore>, index: number, template: ResourceTemplate): void {
J
Joao Moreno 已提交
259
		template.elementDisposables.dispose();
J
Joao Moreno 已提交
260
	}
J
Joao Moreno 已提交
261

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

		const elementDisposables = new DisposableStore();
266
		const compressed = node.element as ICompressedTreeNode<IResourceNode<ISCMResource, ISCMResourceGroup>>;
J
Joao Moreno 已提交
267
		const folder = compressed.elements[compressed.elements.length - 1];
J
Joao Moreno 已提交
268 269 270

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

J
Joao Moreno 已提交
272
		template.fileLabel.setResource({ resource: folder.uri, name: label }, {
J
Joao Moreno 已提交
273 274 275 276
			fileDecorations: { colors: false, badges: true },
			fileKind,
			matches: createMatches(node.filterData)
		});
J
Joao Moreno 已提交
277

J
Joao Moreno 已提交
278
		template.actionBar.clear();
J
Joao Moreno 已提交
279
		template.actionBar.context = folder;
J
Joao Moreno 已提交
280

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

J
Joao Moreno 已提交
283 284
		removeClass(template.name, 'strike-through');
		removeClass(template.element, 'faded');
J
Joao Moreno 已提交
285 286 287
		template.decorationIcon.style.display = 'none';
		template.decorationIcon.style.backgroundImage = '';

J
Joao Moreno 已提交
288 289
		template.element.setAttribute('data-tooltip', '');
		template.elementDisposables = elementDisposables;
J
Joao Moreno 已提交
290 291
	}

292
	disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<ISCMResource> | ICompressedTreeNode<IResourceNode<ISCMResource, ISCMResourceGroup>>, FuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void {
J
Joao Moreno 已提交
293
		template.elementDisposables.dispose();
J
Joao Moreno 已提交
294 295 296
	}

	disposeTemplate(template: ResourceTemplate): void {
J
Joao Moreno 已提交
297 298
		template.elementDisposables.dispose();
		template.disposables.dispose();
J
Joao Moreno 已提交
299 300 301 302 303 304 305 306
	}
}

class ProviderListDelegate implements IListVirtualDelegate<TreeElement> {

	getHeight() { return 22; }

	getTemplateId(element: TreeElement) {
307
		if (ResourceTree.isResourceNode(element) || isSCMResource(element)) {
J
Joao Moreno 已提交
308 309 310 311 312 313 314 315 316 317
			return ResourceRenderer.TEMPLATE_ID;
		} else {
			return ResourceGroupRenderer.TEMPLATE_ID;
		}
	}
}

class SCMTreeFilter implements ITreeFilter<TreeElement> {

	filter(element: TreeElement): boolean {
318
		if (ResourceTree.isResourceNode(element)) {
J
Joao Moreno 已提交
319 320 321 322 323 324 325 326 327 328 329
			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 已提交
330 331 332 333 334
	@memoize
	private get viewModel(): ViewModel { return this.viewModelProvider(); }

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

J
Joao Moreno 已提交
335
	compare(one: TreeElement, other: TreeElement): number {
J
Joao Moreno 已提交
336 337 338 339
		if (this.viewModel.mode === ViewModelMode.List) {
			return 0;
		}

J
Joao Moreno 已提交
340 341 342 343
		if (isSCMResourceGroup(one) && isSCMResourceGroup(other)) {
			return 0;
		}

344 345
		const oneIsDirectory = ResourceTree.isResourceNode(one);
		const otherIsDirectory = ResourceTree.isResourceNode(other);
J
Joao Moreno 已提交
346 347 348 349 350

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

351 352
		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 已提交
353 354 355 356 357

		return compareFileNames(oneName, otherName);
	}
}

358
export class SCMTreeKeyboardNavigationLabelProvider implements ICompressibleKeyboardNavigationLabelProvider<TreeElement> {
J
Joao Moreno 已提交
359 360

	getKeyboardNavigationLabel(element: TreeElement): { toString(): string; } | undefined {
361
		if (ResourceTree.isResourceNode(element)) {
362 363
			return element.name;
		} else if (isSCMResourceGroup(element)) {
J
Joao Moreno 已提交
364
			return element.label;
365
		} else {
J
Joao Moreno 已提交
366 367
			return basename(element.sourceUri);
		}
368
	}
J
Joao Moreno 已提交
369

370
	getCompressedNodeKeyboardNavigationLabel(elements: TreeElement[]): { toString(): string | undefined; } | undefined {
371
		const folders = elements as IResourceNode<ISCMResource, ISCMResourceGroup>[];
372
		return folders.map(e => e.name).join('/');
J
Joao Moreno 已提交
373 374 375
	}
}

J
Joao Moreno 已提交
376 377 378
class SCMResourceIdentityProvider implements IIdentityProvider<TreeElement> {

	getId(element: TreeElement): string {
379
		if (ResourceTree.isResourceNode(element)) {
J
Joao Moreno 已提交
380 381 382 383
			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 已提交
384
			const provider = group.provider;
J
Joao Moreno 已提交
385
			return `${provider.contextValue}/${group.id}/${element.sourceUri.toString()}`;
J
Joao Moreno 已提交
386
		} else {
J
Joao Moreno 已提交
387 388
			const provider = element.provider;
			return `${provider.contextValue}/${element.id}`;
J
Joao Moreno 已提交
389 390
		}
	}
J
Joao Moreno 已提交
391
}
J
Joao Moreno 已提交
392 393 394 395

interface IGroupItem {
	readonly group: ISCMResourceGroup;
	readonly resources: ISCMResource[];
J
Joao Moreno 已提交
396
	readonly tree: ResourceTree<ISCMResource, ISCMResourceGroup>;
J
Joao Moreno 已提交
397 398 399
	readonly disposable: IDisposable;
}

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

J
Joao Moreno 已提交
405
	return { element: item.group, children, incompressible: true, collapsible: true };
406 407
}

408 409
function asTreeElement(node: IResourceNode<ISCMResource, ISCMResourceGroup>, forceIncompressible: boolean): ICompressedTreeElement<TreeElement> {
	return {
J
Joao Moreno 已提交
410
		element: (node.childrenCount === 0 && node.element) ? node.element : node,
411
		children: Iterator.map(node.children, node => asTreeElement(node, false)),
J
Joao Moreno 已提交
412
		incompressible: !!node.element || forceIncompressible
413
	};
J
Joao Moreno 已提交
414 415
}

J
Joao Moreno 已提交
416
const enum ViewModelMode {
J
Joao Moreno 已提交
417 418
	List = 'list',
	Tree = 'tree'
J
Joao Moreno 已提交
419 420
}

421
class ViewModel {
J
Joao Moreno 已提交
422

423
	private readonly _onDidChangeMode = new Emitter<ViewModelMode>();
J
Joao Moreno 已提交
424 425 426 427 428
	readonly onDidChangeMode = this._onDidChangeMode.event;

	get mode(): ViewModelMode { return this._mode; }
	set mode(mode: ViewModelMode) {
		this._mode = mode;
429 430 431 432 433 434 435 436 437 438 439

		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 已提交
440 441 442 443
		this.refresh();
		this._onDidChangeMode.fire(mode);
	}

J
Joao Moreno 已提交
444
	private items: IGroupItem[] = [];
445
	private visibilityDisposables = new DisposableStore();
J
Joao Moreno 已提交
446
	private scrollTop: number | undefined;
J
Joao Moreno 已提交
447
	private firstVisible = true;
J
Joao Moreno 已提交
448 449 450
	private disposables = new DisposableStore();

	constructor(
451
		private groups: ISequence<ISCMResourceGroup>,
J
Joao Moreno 已提交
452
		private tree: WorkbenchCompressibleObjectTree<TreeElement, FuzzyScore>,
J
Joao Moreno 已提交
453 454 455
		private _mode: ViewModelMode,
		@IEditorService protected editorService: IEditorService,
		@IConfigurationService protected configurationService: IConfigurationService,
456
	) { }
J
Joao Moreno 已提交
457 458 459 460 461

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

		for (const group of toInsert) {
J
Joao Moreno 已提交
462
			const tree = new ResourceTree<ISCMResource, ISCMResourceGroup>(group, group.provider.rootUri || URI.file('/'));
J
Joao Moreno 已提交
463 464
			const resources: ISCMResource[] = [...group.elements];
			const disposable = combinedDisposable(
465
				group.onDidChange(() => this.tree.refilter()),
J
Joao Moreno 已提交
466 467
				group.onDidSplice(splice => this.onDidSpliceGroup(item, splice))
			);
468

469
			const item: IGroupItem = { group, resources, tree, disposable };
470

471 472 473 474
			if (this._mode === ViewModelMode.Tree) {
				for (const resource of resources) {
					item.tree.add(resource.sourceUri, resource);
				}
475
			}
J
Joao Moreno 已提交
476 477 478 479 480 481 482 483 484 485

			itemsToInsert.push(item);
		}

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

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

486
		this.refresh();
J
Joao Moreno 已提交
487 488 489 490 491
	}

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

492 493 494 495
		if (this._mode === ViewModelMode.Tree) {
			for (const resource of deleted) {
				item.tree.delete(resource.sourceUri);
			}
J
Joao Moreno 已提交
496 497 498 499

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

502 503
		this.refresh(item);
	}
J
Joao Moreno 已提交
504

505 506 507
	setVisible(visible: boolean): void {
		if (visible) {
			this.visibilityDisposables = new DisposableStore();
508 509
			this.groups.onDidSplice(this.onDidSpliceGroups, this, this.visibilityDisposables);
			this.onDidSpliceGroups({ start: 0, deleteCount: this.items.length, toInsert: this.groups.elements });
J
Joao Moreno 已提交
510 511 512 513 514

			if (typeof this.scrollTop === 'number') {
				this.tree.scrollTop = this.scrollTop;
				this.scrollTop = undefined;
			}
J
Joao Moreno 已提交
515 516 517

			this.editorService.onDidActiveEditorChange(this.onDidActiveEditorChange, this, this.visibilityDisposables);
			this.onDidActiveEditorChange();
518 519 520
		} else {
			this.visibilityDisposables.dispose();
			this.onDidSpliceGroups({ start: 0, deleteCount: this.items.length, toInsert: [] });
J
Joao Moreno 已提交
521
			this.scrollTop = this.tree.scrollTop;
522 523
		}
	}
J
Joao Moreno 已提交
524

525 526
	private refresh(item?: IGroupItem): void {
		if (item) {
J
Joao Moreno 已提交
527
			this.tree.setChildren(item.group, groupItemAsTreeElement(item, this.mode).children);
528
		} else {
J
Joao Moreno 已提交
529
			this.tree.setChildren(null, this.items.map(item => groupItemAsTreeElement(item, this.mode)));
530
		}
J
Joao Moreno 已提交
531 532
	}

J
Joao Moreno 已提交
533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
	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
557
		for (let i = this.items.length - 1; i >= 0; i--) {
J
Joao Moreno 已提交
558 559 560 561 562 563 564 565 566
			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]);
567
				return;
J
Joao Moreno 已提交
568 569 570 571
			}
		}
	}

J
Joao Moreno 已提交
572
	dispose(): void {
573 574
		this.visibilityDisposables.dispose();
		this.disposables.dispose();
J
Joao Moreno 已提交
575 576 577 578 579 580

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

		this.items = [];
J
Joao Moreno 已提交
581 582 583
	}
}

J
Joao Moreno 已提交
584 585 586
export class ToggleViewModeAction extends Action {

	static readonly ID = 'workbench.scm.action.toggleViewMode';
587
	static readonly LABEL = localize('toggleViewMode', "Toggle View Mode");
J
Joao Moreno 已提交
588 589 590 591 592 593 594 595 596 597 598 599 600

	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 已提交
601
		const iconClass = mode === ViewModelMode.List ? 'codicon-list-tree' : 'codicon-list-flat';
J
Joao Moreno 已提交
602
		this.class = `scm-action toggle-view-mode ${iconClass}`;
J
Joao Moreno 已提交
603 604 605
	}
}

S
SteVen Batten 已提交
606
export class RepositoryPane extends ViewPane {
J
Joao Moreno 已提交
607 608 609

	private cachedHeight: number | undefined = undefined;
	private cachedWidth: number | undefined = undefined;
J
Joao Moreno 已提交
610
	private inputContainer!: HTMLElement;
J
Joao Moreno 已提交
611
	private validationContainer!: HTMLElement;
J
Joao Moreno 已提交
612 613
	private inputEditor!: CodeEditorWidget;
	private inputModel!: ITextModel;
J
Joao Moreno 已提交
614
	private listContainer!: HTMLElement;
J
Joao Moreno 已提交
615
	private tree!: WorkbenchCompressibleObjectTree<TreeElement, FuzzyScore>;
J
Joao Moreno 已提交
616 617
	private viewModel!: ViewModel;
	private listLabels!: ResourceLabels;
J
Joao Moreno 已提交
618
	private menus: SCMMenus;
J
Joao Moreno 已提交
619
	private toggleViewModelModeAction: ToggleViewModeAction | undefined;
J
Joao Moreno 已提交
620
	protected contextKeyService: IContextKeyService;
621
	private commitTemplate = '';
J
Joao Moreno 已提交
622

J
Joao Moreno 已提交
623
	shouldShowWelcome() { return true; }
J
Joao Moreno 已提交
624

J
Joao Moreno 已提交
625 626
	constructor(
		readonly repository: ISCMRepository,
S
SteVen Batten 已提交
627
		options: IViewPaneOptions,
J
Joao Moreno 已提交
628
		@IKeybindingService protected keybindingService: IKeybindingService,
629
		@IWorkbenchThemeService protected themeService: IWorkbenchThemeService,
J
Joao Moreno 已提交
630 631 632 633 634 635
		@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,
636
		@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
J
Joao Moreno 已提交
637 638
		@IConfigurationService protected configurationService: IConfigurationService,
		@IContextKeyService contextKeyService: IContextKeyService,
J
Joao Moreno 已提交
639
		@IMenuService protected menuService: IMenuService,
J
Joao Moreno 已提交
640
		@IStorageService private storageService: IStorageService,
J
Joao Moreno 已提交
641
		@IModelService private modelService: IModelService,
642
		@IOpenerService openerService: IOpenerService,
J
Joao Moreno 已提交
643
	) {
644
		super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService);
J
Joao Moreno 已提交
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676

		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 {
J
Joao Moreno 已提交
677 678
		super.renderBody(container);

J
Joao Moreno 已提交
679 680 681 682 683
		const focusTracker = trackFocus(container);
		this._register(focusTracker.onDidFocus(() => this.repository.focus()));
		this._register(focusTracker);

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

J
Joao Moreno 已提交
687
		const placeholderTextContainer = append(editorContainer, $('.scm-editor-placeholder'));
J
Joao Moreno 已提交
688
		const updatePlaceholder = () => {
J
Joao Moreno 已提交
689 690 691
			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 已提交
692

J
Joao Moreno 已提交
693
			placeholderTextContainer.textContent = placeholderText;
J
Joao Moreno 已提交
694 695
		};

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

J
Joao Moreno 已提交
698 699 700 701 702 703 704 705
		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 已提交
706 707 708 709 710 711 712
					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 已提交
713
				} else {
J
Joao Moreno 已提交
714 715 716 717 718 719 720
					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 已提交
721 722 723
				}
			});
		};
J
Joao Moreno 已提交
724

J
Joao Moreno 已提交
725
		const triggerValidation = () => validationDelayer.trigger(validate);
J
Joao Moreno 已提交
726 727

		const editorOptions: IEditorConstructionOptions = {
J
Joao Moreno 已提交
728 729 730
			...getSimpleEditorOptions(),
			lineDecorationsWidth: 4,
			dragAndDrop: false,
J
Joao Moreno 已提交
731 732 733 734
			cursorWidth: 1,
			fontSize: 13,
			lineHeight: 20,
			fontFamily: ' -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif',
735
			wrappingStrategy: 'advanced',
736
			wrappingIndent: 'none',
737
			padding: { top: 3, bottom: 3 },
A
Alex Ross 已提交
738 739
			suggest: { showWords: false },
			quickSuggestions: false
J
Joao Moreno 已提交
740
		};
J
Joao Moreno 已提交
741

J
Joao Moreno 已提交
742
		const codeEditorWidgetOptions: ICodeEditorWidgetOptions = {
J
Joao Moreno 已提交
743 744
			isSimpleWidget: true,
			contributions: EditorExtensionsRegistry.getSomeEditorContributions([
745 746
				SuggestController.ID,
				SnippetController2.ID,
J
Joao Moreno 已提交
747 748
				MenuPreventer.ID,
				SelectionClipboardContributionID,
J
Joao Moreno 已提交
749
				ContextMenuController.ID,
J
Joao Moreno 已提交
750
			])
J
Joao Moreno 已提交
751 752
		};

J
Joao Moreno 已提交
753 754 755
		const services = new ServiceCollection([IContextKeyService, this.contextKeyService]);
		const instantiationService = this.instantiationService.createChild(services);
		this.inputEditor = instantiationService.createInstance(CodeEditorWidget, editorContainer, editorOptions, codeEditorWidgetOptions);
J
Joao Moreno 已提交
756 757 758 759 760

		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 已提交
761

762 763 764 765 766 767 768 769 770 771 772 773
		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 已提交
774
		this.inputModel = this.modelService.getModel(uri) || this.modelService.createModel('', null, uri);
J
Joao Moreno 已提交
775
		this.inputEditor.setModel(this.inputModel);
J
Joao Moreno 已提交
776

J
Joao Moreno 已提交
777
		this._register(this.inputEditor.onDidChangeCursorPosition(triggerValidation));
J
Joao Moreno 已提交
778

779 780
		// Keep model in sync with API
		this.inputModel.setValue(this.repository.input.value);
J
jeanp413 已提交
781 782 783 784 785 786
		this._register(this.repository.input.onDidChange(value => {
			if (value === this.inputModel.getValue()) {
				return;
			}
			this.inputModel.setValue(value);
		}));
J
Joao Moreno 已提交
787

788
		// Keep API in sync with model and update placeholder and validation
J
Joao Moreno 已提交
789
		toggleClass(placeholderTextContainer, 'hidden', this.inputModel.getValueLength() > 0);
J
Joao Moreno 已提交
790
		this.inputModel.onDidChangeContent(() => {
791
			this.repository.input.value = this.inputModel.getValue();
J
Joao Moreno 已提交
792
			toggleClass(placeholderTextContainer, 'hidden', this.inputModel.getValueLength() > 0);
793
			triggerValidation();
J
Joao Moreno 已提交
794 795
		});

J
Joao Moreno 已提交
796 797 798 799
		updatePlaceholder();
		this._register(this.repository.input.onDidChangePlaceholder(updatePlaceholder, null));
		this._register(this.keybindingService.onDidUpdateKeybindings(updatePlaceholder, null));

800
		const onDidChangeContentHeight = Event.filter(this.inputEditor.onDidContentSizeChange, e => e.contentHeightChanged);
J
Joao Moreno 已提交
801
		this._register(onDidChangeContentHeight(() => this.layoutBody()));
J
Joao Moreno 已提交
802 803

		if (this.repository.provider.onDidChangeCommitTemplate) {
J
Joao Moreno 已提交
804
			this._register(this.repository.provider.onDidChangeCommitTemplate(this.onDidChangeCommitTemplate, this));
J
Joao Moreno 已提交
805 806
		}

J
Joao Moreno 已提交
807
		this.onDidChangeCommitTemplate();
J
Joao Moreno 已提交
808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828

		// 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 已提交
829
			new ResourceRenderer(() => this.viewModel, this.listLabels, actionViewItemProvider, () => this.getSelectedResources(), () => this.tree.domFocus(), this.themeService, this.menus)
J
Joao Moreno 已提交
830 831 832
		];

		const filter = new SCMTreeFilter();
J
Joao Moreno 已提交
833
		const sorter = new SCMTreeSorter(() => this.viewModel);
J
Joao Moreno 已提交
834
		const keyboardNavigationLabelProvider = new SCMTreeKeyboardNavigationLabelProvider();
J
Joao Moreno 已提交
835
		const identityProvider = new SCMResourceIdentityProvider();
J
Joao Moreno 已提交
836

837
		this.tree = <WorkbenchCompressibleObjectTree<TreeElement, FuzzyScore>>this.instantiationService.createInstance(
J
Joao Moreno 已提交
838
			WorkbenchCompressibleObjectTree,
J
Joao Moreno 已提交
839
			'SCM Tree Repo',
J
Joao Moreno 已提交
840 841 842 843
			this.listContainer,
			delegate,
			renderers,
			{
J
Joao Moreno 已提交
844
				identityProvider,
J
Joao Moreno 已提交
845 846 847
				horizontalScrolling: false,
				filter,
				sorter,
J
Joao Moreno 已提交
848 849 850 851
				keyboardNavigationLabelProvider,
				overrideStyles: {
					listBackground: SIDE_BAR_BACKGROUND
				}
J
Joao Moreno 已提交
852 853 854 855
			});

		this._register(Event.chain(this.tree.onDidOpen)
			.map(e => e.elements[0])
J
Joao Moreno 已提交
856
			.filter<ISCMResource>((e): e is ISCMResource => !!e && !isSCMResourceGroup(e) && !ResourceTree.isResourceNode(e))
J
Joao Moreno 已提交
857 858
			.on(this.open, this));

J
Joao Moreno 已提交
859 860
		this._register(Event.chain(this.tree.onDidPin)
			.map(e => e.elements[0])
861
			.filter(e => !!e && !isSCMResourceGroup(e) && !ResourceTree.isResourceNode(e))
J
Joao Moreno 已提交
862
			.on(this.pin, this));
J
Joao Moreno 已提交
863

864
		this._register(this.tree.onContextMenu(this.onListContextMenu, this));
J
Joao Moreno 已提交
865 866
		this._register(this.tree);

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

869
		const rootUri = this.repository.provider.rootUri;
J
Joao Moreno 已提交
870

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

874 875
			if (typeof storageMode === 'string') {
				mode = storageMode;
J
Joao Moreno 已提交
876 877 878
			}
		}

879
		this.viewModel = this.instantiationService.createInstance(ViewModel, this.repository.provider.groups, this.tree, mode);
880
		this._register(this.viewModel);
J
Joao Moreno 已提交
881

882 883 884
		addClass(this.listContainer, 'file-icon-themable-tree');
		addClass(this.listContainer, 'show-file-icons');

J
Joao Moreno 已提交
885 886 887
		this.updateIndentStyles(this.themeService.getFileIconTheme());
		this._register(this.themeService.onDidFileIconThemeChange(this.updateIndentStyles, this));
		this._register(this.viewModel.onDidChangeMode(this.onDidChangeMode, this));
888

889 890
		this.toggleViewModelModeAction = new ToggleViewModeAction(this.viewModel);
		this._register(this.toggleViewModelModeAction);
J
Joao Moreno 已提交
891

892
		this._register(this.onDidChangeBodyVisibility(this._onDidChangeVisibility, this));
J
Joao Moreno 已提交
893 894

		this.updateActions();
J
Joao Moreno 已提交
895 896
	}

J
Joao Moreno 已提交
897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915
	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 已提交
916 917 918 919 920 921
	layoutBody(height: number | undefined = this.cachedHeight, width: number | undefined = this.cachedWidth): void {
		if (height === undefined) {
			return;
		}

		this.cachedHeight = height;
J
Joao Moreno 已提交
922
		this.cachedWidth = width;
J
Joao Moreno 已提交
923 924

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

927
			const editorContentHeight = this.inputEditor.getContentHeight();
928
			const editorHeight = Math.min(editorContentHeight, 134);
J
Joao Moreno 已提交
929
			this.inputEditor.layout({ height: editorHeight, width: width! - 12 - 16 - 2 });
J
Joao Moreno 已提交
930

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

J
Joao Moreno 已提交
933
			const listHeight = height - (editorHeight + 5 + 2 + 5);
J
Joao Moreno 已提交
934 935 936
			this.listContainer.style.height = `${listHeight}px`;
			this.tree.layout(listHeight, width);
		} else {
J
Joao Moreno 已提交
937
			addClass(this.inputContainer, 'hidden');
J
Joao Moreno 已提交
938

J
Joao Moreno 已提交
939
			this.inputEditor.onHide();
J
Joao Moreno 已提交
940 941 942 943 944 945 946 947 948 949
			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 已提交
950
				this.inputEditor.focus();
J
Joao Moreno 已提交
951 952 953 954 955 956 957 958
			} else {
				this.tree.domFocus();
			}

			this.repository.focus();
		}
	}

959 960
	private _onDidChangeVisibility(visible: boolean): void {
		this.viewModel.setVisible(visible);
J
Joao Moreno 已提交
961 962 963 964 965 966

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

J
Joao Moreno 已提交
969
	getActions(): IAction[] {
J
Joao Moreno 已提交
970 971 972 973 974 975 976 977 978
		if (this.toggleViewModelModeAction) {

			return [
				this.toggleViewModelModeAction,
				...this.menus.getTitleActions()
			];
		} else {
			return this.menus.getTitleActions();
		}
J
Joao Moreno 已提交
979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000
	}

	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 已提交
1001 1002 1003 1004 1005 1006 1007
	private pin(): void {
		const activeControl = this.editorService.activeControl;

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

J
Joao Moreno 已提交
1009
	private onListContextMenu(e: ITreeContextMenuEvent<TreeElement | null>): void {
1010 1011 1012
		if (!e.element) {
			return;
		}
J
Joao Moreno 已提交
1013

1014
		const element = e.element;
J
Joao Moreno 已提交
1015
		let actions: IAction[] = [];
J
Joao Moreno 已提交
1016

1017 1018 1019
		if (isSCMResourceGroup(element)) {
			actions = this.menus.getResourceGroupContextActions(element);
		} else if (ResourceTree.isResourceNode(element)) {
J
Joao Moreno 已提交
1020 1021 1022 1023 1024
			if (element.element) {
				actions = this.menus.getResourceContextActions(element.element);
			} else {
				actions = this.menus.getResourceFolderContextActions(element.context);
			}
1025
		} else {
1026
			actions = this.menus.getResourceContextActions(element);
1027
		}
J
Joao Moreno 已提交
1028

1029 1030 1031 1032
		this.contextMenuService.showContextMenu({
			getAnchor: () => e.anchor,
			getActions: () => actions,
			getActionsContext: () => element,
J
Joao Moreno 已提交
1033
			actionRunner: new RepositoryPaneActionRunner(() => this.getSelectedResources(), () => this.tree.domFocus())
1034 1035
		});
	}
J
Joao Moreno 已提交
1036

1037
	private getSelectedResources(): (ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>)[] {
J
Joao Moreno 已提交
1038
		return this.tree.getSelection()
1039
			.filter(r => !!r && !isSCMResourceGroup(r))! as any;
J
Joao Moreno 已提交
1040 1041
	}

J
Joao Moreno 已提交
1042
	private onDidChangeCommitTemplate(): void {
1043 1044 1045 1046 1047 1048
		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 已提交
1049

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

1052 1053 1054
		if (value && value !== oldCommitTemplate) {
			return;
		}
J
Joao Moreno 已提交
1055

1056
		this.inputModel.setValue(this.commitTemplate);
J
Joao Moreno 已提交
1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071
	}

	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 已提交
1072
	readonly ctorDescriptor: SyncDescriptor<RepositoryPane>;
J
Joao Moreno 已提交
1073 1074 1075 1076 1077 1078
	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 已提交
1079 1080 1081 1082
		const hasher = new Hasher();
		hasher.hash(repository.provider.label);
		hasher.hash(repoId);
		this.id = `scm:repository:${hasher.value}`;
J
Joao Moreno 已提交
1083 1084
		this.name = repository.provider.rootUri ? basename(repository.provider.rootUri) : repository.provider.label;

S
Sandeep Somavarapu 已提交
1085
		this.ctorDescriptor = new SyncDescriptor(RepositoryPane, [repository]);
J
Joao Moreno 已提交
1086 1087
	}
}
J
Joao Moreno 已提交
1088 1089

registerThemingParticipant((theme, collector) => {
J
Joao Moreno 已提交
1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112
	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 已提交
1113
	const inputPlaceholderForegroundColor = theme.getColor(inputPlaceholderForeground);
J
Joao Moreno 已提交
1114 1115 1116
	if (inputPlaceholderForegroundColor) {
		collector.addRule(`.scm-viewlet .scm-editor-placeholder { color: ${inputPlaceholderForegroundColor}; }`);
	}
J
Joao Moreno 已提交
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 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164

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