SCM - Add List/Tree view mode to the SCM Sync view (#193696)

* Add actions

* Saving my work

* Moved the actions into the '...' menu

* Automatically expand history item

* Remove unused import statement
This commit is contained in:
Ladislau Szomoru
2023-09-21 20:02:26 +02:00
committed by GitHub
parent 029ba9ec98
commit fdad923393

View File

@@ -14,23 +14,23 @@ import { ThemeIcon } from 'vs/base/common/themables';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { ICommandService } from 'vs/platform/commands/common/commands'; import { ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IOpenEvent, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { IOpenEvent, WorkbenchCompressibleAsyncDataTree } from 'vs/platform/list/browser/listService';
import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles'; import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels';
import { API_OPEN_DIFF_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { API_OPEN_DIFF_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewPaneOptions, ViewAction, ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IViewDescriptorService } from 'vs/workbench/common/views';
import { RepositoryRenderer } from 'vs/workbench/contrib/scm/browser/scmRepositoryRenderer'; import { RepositoryRenderer } from 'vs/workbench/contrib/scm/browser/scmRepositoryRenderer';
import { ActionButtonRenderer } from 'vs/workbench/contrib/scm/browser/scmViewPane'; import { ActionButtonRenderer } from 'vs/workbench/contrib/scm/browser/scmViewPane';
import { getActionViewItemProvider, isSCMActionButton, isSCMRepository, isSCMRepositoryArray } from 'vs/workbench/contrib/scm/browser/util'; import { getActionViewItemProvider, isSCMActionButton, isSCMRepository, isSCMRepositoryArray } from 'vs/workbench/contrib/scm/browser/util';
import { ISCMActionButton, ISCMRepository, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent } from 'vs/workbench/contrib/scm/common/scm'; import { ISCMActionButton, ISCMRepository, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, SYNC_VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm';
import { comparePaths } from 'vs/base/common/comparers'; import { comparePaths } from 'vs/base/common/comparers';
import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGroup } from 'vs/workbench/contrib/scm/common/history'; import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGroup } from 'vs/workbench/contrib/scm/common/history';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
@@ -41,8 +41,18 @@ import { basename, dirname } from 'vs/base/common/resources';
import { ILabelService } from 'vs/platform/label/common/label'; import { ILabelService } from 'vs/platform/label/common/label';
import { stripIcons } from 'vs/base/common/iconLabels'; import { stripIcons } from 'vs/base/common/iconLabels';
import { FileKind } from 'vs/platform/files/common/files'; import { FileKind } from 'vs/platform/files/common/files';
import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { Codicon } from 'vs/base/common/codicons';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { Emitter } from 'vs/base/common/event';
import { ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree';
import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import { IResourceNode, ResourceTree } from 'vs/base/common/resourceTree';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
type TreeElement = ISCMRepository[] | ISCMRepository | ISCMActionButton | SCMHistoryItemGroupTreeElement | SCMHistoryItemTreeElement | SCMHistoryItemChangeTreeElement; type SCMHistoryItemChangeResourceTreeNode = IResourceNode<SCMHistoryItemChangeTreeElement, SCMHistoryItemTreeElement>;
type TreeElement = ISCMRepository[] | ISCMRepository | ISCMActionButton | SCMHistoryItemGroupTreeElement | SCMHistoryItemTreeElement | SCMHistoryItemChangeTreeElement | SCMHistoryItemChangeResourceTreeNode;
function isSCMHistoryItemGroupTreeElement(obj: any): obj is SCMHistoryItemGroupTreeElement { function isSCMHistoryItemGroupTreeElement(obj: any): obj is SCMHistoryItemGroupTreeElement {
return (obj as SCMHistoryItemGroupTreeElement).type === 'historyItemGroup'; return (obj as SCMHistoryItemGroupTreeElement).type === 'historyItemGroup';
@@ -86,11 +96,25 @@ function getSCMResourceId(element: TreeElement): string {
const historyItemGroup = historyItem.historyItemGroup; const historyItemGroup = historyItem.historyItemGroup;
const provider = historyItemGroup.repository.provider; const provider = historyItemGroup.repository.provider;
return `historyItemChange:${provider.id}/${historyItemGroup.id}/${historyItem.id}/${element.uri.toString()}`; return `historyItemChange:${provider.id}/${historyItemGroup.id}/${historyItem.id}/${element.uri.toString()}`;
} else if (ResourceTree.isResourceNode(element)) {
const historyItem = element.context;
const historyItemGroup = historyItem.historyItemGroup;
const provider = historyItemGroup.repository.provider;
return `folder:${provider.id}/${historyItemGroup.id}/${historyItem.id}/$FOLDER/${element.uri.toString()}`;
} else { } else {
throw new Error('Invalid tree element'); throw new Error('Invalid tree element');
} }
} }
const enum ViewMode {
List = 'list',
Tree = 'tree'
}
const ContextKeys = {
ViewMode: new RawContextKey<ViewMode>('scmSyncViewMode', ViewMode.List),
};
interface SCMHistoryItemGroupTreeElement extends ISCMHistoryItemGroup { interface SCMHistoryItemGroupTreeElement extends ISCMHistoryItemGroup {
readonly description?: string; readonly description?: string;
readonly ancestor?: string; readonly ancestor?: string;
@@ -130,12 +154,26 @@ class ListDelegate implements IListVirtualDelegate<any> {
return HistoryItemRenderer.TEMPLATE_ID; return HistoryItemRenderer.TEMPLATE_ID;
} else if (isSCMHistoryItemChangeTreeElement(element)) { } else if (isSCMHistoryItemChangeTreeElement(element)) {
return HistoryItemChangeRenderer.TEMPLATE_ID; return HistoryItemChangeRenderer.TEMPLATE_ID;
} else if (ResourceTree.isResourceNode(element)) {
return HistoryItemChangeRenderer.TEMPLATE_ID;
} else { } else {
throw new Error('Invalid tree element'); throw new Error('Invalid tree element');
} }
} }
} }
class CompressionDelegate implements ITreeCompressionDelegate<TreeElement> {
isIncompressible(element: TreeElement): boolean {
if (ResourceTree.isResourceNode(element)) {
return element.childrenCount === 0 || !element.parent || !element.parent.parent;
}
return true;
}
}
interface HistoryItemGroupTemplate { interface HistoryItemGroupTemplate {
readonly label: IconLabel; readonly label: IconLabel;
readonly count: CountBadge; readonly count: CountBadge;
@@ -236,12 +274,15 @@ interface HistoryItemChangeTemplate {
readonly disposables: IDisposable; readonly disposables: IDisposable;
} }
class HistoryItemChangeRenderer implements ITreeRenderer<SCMHistoryItemChangeTreeElement, void, HistoryItemChangeTemplate> { class HistoryItemChangeRenderer implements ICompressibleTreeRenderer<SCMHistoryItemChangeTreeElement | SCMHistoryItemChangeResourceTreeNode, void, HistoryItemChangeTemplate> {
static readonly TEMPLATE_ID = 'historyItemChange'; static readonly TEMPLATE_ID = 'historyItemChange';
get templateId(): string { return HistoryItemChangeRenderer.TEMPLATE_ID; } get templateId(): string { return HistoryItemChangeRenderer.TEMPLATE_ID; }
constructor(private labels: ResourceLabels) { } constructor(
private readonly viewMode: () => ViewMode,
private readonly labels: ResourceLabels,
@ILabelService private labelService: ILabelService) { }
renderTemplate(container: HTMLElement): HistoryItemChangeTemplate { renderTemplate(container: HTMLElement): HistoryItemChangeTemplate {
const element = append(container, $('.change')); const element = append(container, $('.change'));
@@ -252,11 +293,27 @@ class HistoryItemChangeRenderer implements ITreeRenderer<SCMHistoryItemChangeTre
return { element, name, fileLabel, decorationIcon, disposables: new DisposableStore() }; return { element, name, fileLabel, decorationIcon, disposables: new DisposableStore() };
} }
renderElement(node: ITreeNode<SCMHistoryItemChangeTreeElement, void>, index: number, templateData: HistoryItemChangeTemplate, height: number | undefined): void { renderElement(node: ITreeNode<SCMHistoryItemChangeTreeElement | SCMHistoryItemChangeResourceTreeNode, void>, index: number, templateData: HistoryItemChangeTemplate, height: number | undefined): void {
const historyItemChangeOrFolder = node.element;
const fileKind = ResourceTree.isResourceNode(historyItemChangeOrFolder) && historyItemChangeOrFolder.childrenCount > 0 ? FileKind.FOLDER : FileKind.FILE;
templateData.fileLabel.setFile(node.element.uri, { templateData.fileLabel.setFile(node.element.uri, {
fileDecorations: { colors: false, badges: true }, fileDecorations: { colors: false, badges: true },
fileKind: FileKind.FILE, fileKind,
hidePath: false, hidePath: this.viewMode() === ViewMode.Tree,
});
}
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<SCMHistoryItemChangeTreeElement | SCMHistoryItemChangeResourceTreeNode>, void>, index: number, templateData: HistoryItemChangeTemplate, height: number | undefined): void {
const compressed = node.element as ICompressedTreeNode<SCMHistoryItemChangeResourceTreeNode>;
const folder = compressed.elements[compressed.elements.length - 1];
const label = compressed.elements.map(e => e.name);
templateData.fileLabel.setResource({ resource: folder.uri, name: label }, {
fileDecorations: { colors: false, badges: true },
fileKind: FileKind.FOLDER,
separator: this.labelService.getSeparator(folder.uri.scheme)
}); });
} }
@@ -292,15 +349,7 @@ class SCMSyncViewPaneAccessibilityProvider implements IListAccessibilityProvider
} else if (isSCMHistoryItemTreeElement(element)) { } else if (isSCMHistoryItemTreeElement(element)) {
return `${stripIcons(element.label).trim()}${element.description ? `, ${element.description}` : ''}`; return `${stripIcons(element.label).trim()}${element.description ? `, ${element.description}` : ''}`;
} else if (isSCMHistoryItemChangeTreeElement(element)) { } else if (isSCMHistoryItemChangeTreeElement(element)) {
const result: string[] = []; const result = [basename(element.uri)];
result.push(basename(element.uri));
// TODO - add decoration
// if (element.decorations.tooltip) {
// result.push(element.decorations.tooltip);
// }
const path = this.labelService.getUriLabel(dirname(element.uri), { relative: true, noPrefix: true }); const path = this.labelService.getUriLabel(dirname(element.uri), { relative: true, noPrefix: true });
if (path) { if (path) {
@@ -375,7 +424,7 @@ export class SCMSyncViewPane extends ViewPane {
private listLabels!: ResourceLabels; private listLabels!: ResourceLabels;
private treeContainer!: HTMLElement; private treeContainer!: HTMLElement;
private _tree!: WorkbenchAsyncDataTree<TreeElement, TreeElement>; private _tree!: WorkbenchCompressibleAsyncDataTree<TreeElement, TreeElement>;
private _viewModel!: SCMSyncPaneViewModel; private _viewModel!: SCMSyncPaneViewModel;
get viewModel(): SCMSyncPaneViewModel { return this._viewModel; } get viewModel(): SCMSyncPaneViewModel { return this._viewModel; }
@@ -407,24 +456,28 @@ export class SCMSyncViewPane extends ViewPane {
this._register(this.listLabels); this._register(this.listLabels);
this._tree = this.instantiationService.createInstance( this._tree = this.instantiationService.createInstance(
WorkbenchAsyncDataTree, WorkbenchCompressibleAsyncDataTree,
'SCM Sync View', 'SCM Sync View',
this.treeContainer, this.treeContainer,
new ListDelegate(), new ListDelegate(),
new CompressionDelegate(),
[ [
this.instantiationService.createInstance(RepositoryRenderer, getActionViewItemProvider(this.instantiationService)), this.instantiationService.createInstance(RepositoryRenderer, getActionViewItemProvider(this.instantiationService)),
this.instantiationService.createInstance(ActionButtonRenderer), this.instantiationService.createInstance(ActionButtonRenderer),
this.instantiationService.createInstance(HistoryItemGroupRenderer), this.instantiationService.createInstance(HistoryItemGroupRenderer),
this.instantiationService.createInstance(HistoryItemRenderer), this.instantiationService.createInstance(HistoryItemRenderer),
this.instantiationService.createInstance(HistoryItemChangeRenderer, this.listLabels), this.instantiationService.createInstance(HistoryItemChangeRenderer, () => this.viewModel.mode, this.listLabels),
], ],
this.instantiationService.createInstance(SCMSyncDataSource), this.instantiationService.createInstance(SCMSyncDataSource, () => this.viewModel.mode),
{ {
compressionEnabled: true,
horizontalScrolling: false, horizontalScrolling: false,
autoExpandSingleChildren: true,
collapseByDefault: (e) => !ResourceTree.isResourceNode(e),
accessibilityProvider: this.instantiationService.createInstance(SCMSyncViewPaneAccessibilityProvider), accessibilityProvider: this.instantiationService.createInstance(SCMSyncViewPaneAccessibilityProvider),
identityProvider: this.instantiationService.createInstance(SCMSyncViewPaneTreeIdentityProvider), identityProvider: this.instantiationService.createInstance(SCMSyncViewPaneTreeIdentityProvider),
sorter: this.instantiationService.createInstance(SCMSyncViewPaneTreeSorter), sorter: this.instantiationService.createInstance(SCMSyncViewPaneTreeSorter),
}) as WorkbenchAsyncDataTree<TreeElement, TreeElement>; }) as WorkbenchCompressibleAsyncDataTree<TreeElement, TreeElement>;
this._register(this._tree); this._register(this._tree);
this._register(this._tree.onDidOpen(this.onDidOpen, this)); this._register(this._tree.onDidOpen(this.onDidOpen, this));
@@ -436,6 +489,7 @@ export class SCMSyncViewPane extends ViewPane {
this.updateIndentStyles(this.themeService.getFileIconTheme()); this.updateIndentStyles(this.themeService.getFileIconTheme());
this._register(this.themeService.onDidFileIconThemeChange(this.updateIndentStyles, this)); this._register(this.themeService.onDidFileIconThemeChange(this.updateIndentStyles, this));
this._register(this._viewModel.onDidChangeMode(this.onDidChangeMode, this));
} }
protected override layoutBody(height: number, width: number): void { protected override layoutBody(height: number, width: number): void {
@@ -443,18 +497,30 @@ export class SCMSyncViewPane extends ViewPane {
this._tree.layout(height, width); this._tree.layout(height, width);
} }
private onDidChangeMode(): void {
this.updateIndentStyles(this.themeService.getFileIconTheme());
}
private async onDidOpen(e: IOpenEvent<TreeElement | undefined>): Promise<void> { private async onDidOpen(e: IOpenEvent<TreeElement | undefined>): Promise<void> {
if (!e.element) { if (!e.element) {
return; return;
} else if (isSCMHistoryItemChangeTreeElement(e.element)) { }
if (isSCMHistoryItemChangeTreeElement(e.element)) {
if (e.element.originalUri && e.element.modifiedUri) { if (e.element.originalUri && e.element.modifiedUri) {
await this.commandService.executeCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, ...toDiffEditorArguments(e.element.uri, e.element.originalUri, e.element.modifiedUri), e); await this.commandService.executeCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, ...toDiffEditorArguments(e.element.uri, e.element.originalUri, e.element.modifiedUri), e);
} }
} else if (ResourceTree.isResourceNode(e.element) && e.element.childrenCount === 0) {
if (e.element.element?.originalUri && e.element.element?.modifiedUri) {
await this.commandService.executeCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, ...toDiffEditorArguments(e.element.element.uri, e.element.element.originalUri, e.element.element.modifiedUri), e);
}
} }
} }
private updateIndentStyles(theme: any): void { private updateIndentStyles(theme: any): void {
this.treeContainer.classList.toggle('align-icons-and-twisties', theme.hasFileIcons || (theme.hasFileIcons && !theme.hasFolderIcons)); this.treeContainer.classList.toggle('list-view-mode', this._viewModel.mode === ViewMode.List);
this.treeContainer.classList.toggle('tree-view-mode', this._viewModel.mode === ViewMode.Tree);
this.treeContainer.classList.toggle('align-icons-and-twisties', (this._viewModel.mode === ViewMode.List && theme.hasFileIcons) || (theme.hasFileIcons && !theme.hasFolderIcons));
} }
override dispose(): void { override dispose(): void {
@@ -465,6 +531,26 @@ export class SCMSyncViewPane extends ViewPane {
class SCMSyncPaneViewModel { class SCMSyncPaneViewModel {
private readonly _onDidChangeMode = new Emitter<ViewMode>();
readonly onDidChangeMode = this._onDidChangeMode.event;
private _mode: ViewMode;
get mode(): ViewMode { return this._mode; }
set mode(mode: ViewMode) {
if (this._mode === mode) {
return;
}
this._mode = mode;
this.refresh();
this.modeContextKey.set(mode);
this._onDidChangeMode.fire(mode);
this.storageService.store(`scm.syncViewMode`, mode, StorageScope.WORKSPACE, StorageTarget.USER);
}
private modeContextKey: IContextKey<ViewMode>;
private repositories = new Map<ISCMRepository, IDisposable>(); private repositories = new Map<ISCMRepository, IDisposable>();
private historyProviders = new Map<ISCMRepository, IDisposable>(); private historyProviders = new Map<ISCMRepository, IDisposable>();
@@ -473,19 +559,25 @@ class SCMSyncPaneViewModel {
private readonly disposables = new DisposableStore(); private readonly disposables = new DisposableStore();
constructor( constructor(
private readonly tree: WorkbenchAsyncDataTree<TreeElement, TreeElement>, private readonly tree: WorkbenchCompressibleAsyncDataTree<TreeElement, TreeElement>,
@IContextKeyService contextKeyService: IContextKeyService,
@ISCMViewService scmViewService: ISCMViewService, @ISCMViewService scmViewService: ISCMViewService,
@IConfigurationService private readonly configurationService: IConfigurationService, @IConfigurationService private readonly configurationService: IConfigurationService,
@IStorageService private readonly storageService: IStorageService,
) { ) {
configurationService.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables); configurationService.onDidChangeConfiguration(this._onDidChangeConfiguration, this, this.disposables);
this.onDidChangeConfiguration(); this._onDidChangeConfiguration();
scmViewService.onDidChangeVisibleRepositories(this._onDidChangeVisibleRepositories, this, this.disposables); scmViewService.onDidChangeVisibleRepositories(this._onDidChangeVisibleRepositories, this, this.disposables);
this._onDidChangeVisibleRepositories({ added: scmViewService.visibleRepositories, removed: [] }); this._onDidChangeVisibleRepositories({ added: scmViewService.visibleRepositories, removed: [] });
this._mode = this.getViewMode();
this.modeContextKey = ContextKeys.ViewMode.bindTo(contextKeyService);
this.modeContextKey.set(this._mode);
} }
private onDidChangeConfiguration(e?: IConfigurationChangeEvent): void { private _onDidChangeConfiguration(e?: IConfigurationChangeEvent): void {
if (!e || e.affectsConfiguration('scm.alwaysShowRepositories')) { if (!e || e.affectsConfiguration('scm.alwaysShowRepositories')) {
this.alwaysShowRepositories = this.configurationService.getValue<boolean>('scm.alwaysShowRepositories'); this.alwaysShowRepositories = this.configurationService.getValue<boolean>('scm.alwaysShowRepositories');
this.refresh(); this.refresh();
@@ -524,6 +616,16 @@ class SCMSyncPaneViewModel {
} }
} }
private getViewMode(): ViewMode {
let mode = this.configurationService.getValue<'tree' | 'list'>('scm.defaultViewMode') === 'list' ? ViewMode.List : ViewMode.Tree;
const storageMode = this.storageService.get(`scm.syncViewMode`, StorageScope.WORKSPACE) as ViewMode;
if (typeof storageMode === 'string') {
mode = storageMode;
}
return mode;
}
private async refresh(repository?: ISCMRepository): Promise<void> { private async refresh(repository?: ISCMRepository): Promise<void> {
if (this.repositories.size === 0) { if (this.repositories.size === 0) {
return; return;
@@ -548,6 +650,10 @@ class SCMSyncPaneViewModel {
class SCMSyncDataSource implements IAsyncDataSource<TreeElement, TreeElement> { class SCMSyncDataSource implements IAsyncDataSource<TreeElement, TreeElement> {
constructor(
private readonly viewMode: () => ViewMode,
@IUriIdentityService private uriIdentityService: IUriIdentityService) { }
hasChildren(element: TreeElement): boolean { hasChildren(element: TreeElement): boolean {
if (isSCMRepositoryArray(element)) { if (isSCMRepositoryArray(element)) {
return true; return true;
@@ -561,6 +667,8 @@ class SCMSyncDataSource implements IAsyncDataSource<TreeElement, TreeElement> {
return true; return true;
} else if (isSCMHistoryItemChangeTreeElement(element)) { } else if (isSCMHistoryItemChangeTreeElement(element)) {
return false; return false;
} else if (ResourceTree.isResourceNode(element)) {
return element.childrenCount > 0;
} else { } else {
throw new Error('hasChildren not implemented.'); throw new Error('hasChildren not implemented.');
} }
@@ -651,14 +759,35 @@ class SCMSyncDataSource implements IAsyncDataSource<TreeElement, TreeElement> {
// History Item Changes // History Item Changes
const changes = await historyProvider.provideHistoryItemChanges(element.id) ?? []; const changes = await historyProvider.provideHistoryItemChanges(element.id) ?? [];
children.push(...changes.map(change => ({
uri: change.uri, if (this.viewMode() === ViewMode.List) {
originalUri: change.originalUri, // List
modifiedUri: change.modifiedUri, children.push(...changes.map(change => ({
renameUri: change.renameUri, uri: change.uri,
historyItem: element, originalUri: change.originalUri,
type: 'historyItemChange' modifiedUri: change.modifiedUri,
} as SCMHistoryItemChangeTreeElement))); renameUri: change.renameUri,
historyItem: element,
type: 'historyItemChange'
} as SCMHistoryItemChangeTreeElement)));
} else {
// Tree
const tree = new ResourceTree<SCMHistoryItemChangeTreeElement, SCMHistoryItemTreeElement>(element, repository.provider.rootUri ?? URI.file('/'), this.uriIdentityService.extUri);
for (const change of changes) {
tree.add(change.uri, {
uri: change.uri,
originalUri: change.originalUri,
modifiedUri: change.modifiedUri,
renameUri: change.renameUri,
historyItem: element,
type: 'historyItemChange'
} as SCMHistoryItemChangeTreeElement);
}
children.push(...tree.root.children);
}
} else if (ResourceTree.isResourceNode(element)) {
children.push(...element.children);
} else { } else {
throw new Error('getChildren Method not implemented.'); throw new Error('getChildren Method not implemented.');
} }
@@ -666,3 +795,50 @@ class SCMSyncDataSource implements IAsyncDataSource<TreeElement, TreeElement> {
return children; return children;
} }
} }
class SetListViewModeAction extends ViewAction<SCMSyncViewPane> {
constructor() {
super({
id: 'workbench.scm.sync.action.setListViewMode',
title: localize('setListViewMode', "View as List"),
viewId: SYNC_VIEW_PANE_ID,
f1: false,
icon: Codicon.listTree,
toggled: ContextKeys.ViewMode.isEqualTo(ViewMode.List),
menu: {
id: MenuId.ViewTitle,
group: '1_viewmode'
}
});
}
async runInView(_: ServicesAccessor, view: SCMSyncViewPane): Promise<void> {
view.viewModel.mode = ViewMode.List;
}
}
class SetTreeViewModeAction extends ViewAction<SCMSyncViewPane> {
constructor() {
super({
id: 'workbench.scm.sync.action.setTreeViewMode',
title: localize('setTreeViewMode', "View as Tree"),
viewId: SYNC_VIEW_PANE_ID,
f1: false,
icon: Codicon.listFlat,
toggled: ContextKeys.ViewMode.isEqualTo(ViewMode.Tree),
menu: {
id: MenuId.ViewTitle,
group: '1_viewmode'
}
});
}
async runInView(_: ServicesAccessor, view: SCMSyncViewPane): Promise<void> {
view.viewModel.mode = ViewMode.Tree;
}
}
registerAction2(SetListViewModeAction);
registerAction2(SetTreeViewModeAction);