From ce9394297de8b8375b69f5ee7342af0df18885eb Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Tue, 26 Sep 2017 18:19:53 -0400 Subject: [PATCH] Adds new file layouts to the custom view --- CHANGELOG.md | 7 +++ README.md | 3 ++ package.json | 20 ++++++++ src/configuration.ts | 41 ++++++++++----- src/system/array.ts | 100 +++++++++++++++++++++++++++++++++++++ src/views/commitFileNode.ts | 28 +++++++++-- src/views/commitNode.ts | 27 ++++++++-- src/views/explorerNode.ts | 1 + src/views/folderNode.ts | 85 +++++++++++++++++++++++++++++++ src/views/statusFileCommitsNode.ts | 31 ++++++++++-- src/views/statusFilesNode.ts | 35 +++++++++---- 11 files changed, 346 insertions(+), 32 deletions(-) create mode 100644 src/views/folderNode.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 88be184..d9c81f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ## [Unreleased] ### Added +- Adds new file layouts to the `GitLens` custom view + - `auto` - automatically switches between displaying files as a `tree` or `list` based on the `gitlens.gitExplorer.files.threshold` setting and the number of files at each nesting level + - `list` - displays files as a list + - `tree` - displays files as a tree +- Adds `gitlens.gitExplorer.files.layout` setting to specify how the `GitLens` custom view will display files +- Adds `gitlens.gitExplorer.files.compact` setting to specify whether or not to compact (flatten) unnecessary file nesting in the `GitLens` custom view +- Adds `gitlens.gitExplorer.files.threshold` setting to specify when to switch between displaying files as a `tree` or `list` based on the number of files in a nesting level in the `GitLens` custom view - Adds `${directory}` token to the file formatting settings ### Changed diff --git a/README.md b/README.md index b2324dc..219ff2f 100644 --- a/README.md +++ b/README.md @@ -356,6 +356,9 @@ GitLens is highly customizable and provides many configuration settings to allow |-----|------------ |`gitlens.gitExplorer.enabled`|Specifies whether or not to show the `GitLens` custom view" |`gitlens.gitExplorer.view`|Specifies the starting view (mode) of the `GitLens` custom view
`auto` - shows the last selected view, defaults to `repository`
`history` - shows the commit history of the active file
`repository` - shows a repository explorer" +|`gitlens.gitExplorer.files.layout`|Specifies how the `GitLens` custom view will display files
`auto` - automatically switches between displaying files as a `tree` or `list` based on the `gitlens.gitExplorer.files.threshold` setting and the number of files at each nesting level
`list` - displays files as a list
`tree` - displays files as a tree +|`gitlens.gitExplorer.files.compact`|Specifies whether or not to compact (flatten) unnecessary file nesting in the `GitLens` custom view
Only applies when displaying files as a `tree` or `auto` +|`gitlens.gitExplorer.files.threshold`|Specifies when to switch between displaying files as a `tree` or `list` based on the number of files in a nesting level in the `GitLens` custom view
Only applies when displaying files as `auto` |`gitlens.gitExplorer.includeWorkingTree`|Specifies whether or not to include working tree files inside the `Repository Status` node of the `GitLens` custom view |`gitlens.gitExplorer.showTrackingBranch`|Specifies whether or not to show the tracking branch when displaying local branches in the `GitLens` custom view" |`gitlens.gitExplorer.commitFormat`|Specifies the format of committed changes in the `GitLens` custom view
Available tokens
${id} - commit id
${author} - commit author
${message} - commit message
${ago} - relative commit date (e.g. 1 day ago)
${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)
${authorAgo} - commit author, relative commit date
See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting diff --git a/package.json b/package.json index b055007..5834e36 100644 --- a/package.json +++ b/package.json @@ -428,6 +428,26 @@ "default": true, "description": "Specifies whether or not to show the `GitLens` custom view" }, + "gitlens.gitExplorer.files.layout": { + "type": "string", + "default": "auto", + "enum": [ + "auto", + "list", + "tree" + ], + "description": "Specifies how the `GitLens` custom view will display files\n `auto` - automatically switches between displaying files as a `tree` or `list` based on the `gitlens.gitExplorer.files.threshold` setting and the number of files at each nesting level\n `list` - displays files as a list\n `tree` - displays files as a tree" + }, + "gitlens.gitExplorer.files.compact": { + "type": "boolean", + "default": true, + "description": "Specifies whether or not to compact (flatten) unnecessary file nesting in the `GitLens` custom view\nOnly applies when displaying files as a `tree` or `auto`" + }, + "gitlens.gitExplorer.files.threshold": { + "type": "number", + "default": 5, + "description": "Specifies when to switch between displaying files as a `tree` or `list` based on the number of files in a nesting level in the `GitLens` custom view\nOnly applies when displaying files as `auto`" + }, "gitlens.gitExplorer.includeWorkingTree": { "type": "boolean", "default": true, diff --git a/src/configuration.ts b/src/configuration.ts index 7de453f..15a9eb4 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -53,6 +53,16 @@ export const CustomRemoteType = { GitLab: 'GitLab' as CustomRemoteType }; +export type GitExplorerFilesLayout = + 'auto' | + 'list' | + 'tree'; +export const GitExplorerFilesLayout = { + Auto: 'auto' as GitExplorerFilesLayout, + List: 'list' as GitExplorerFilesLayout, + Tree: 'tree' as GitExplorerFilesLayout +}; + export type StatusBarCommand = 'gitlens.toggleFileBlame' | 'gitlens.showBlameHistory' | @@ -132,6 +142,24 @@ export interface ICodeLensLanguageLocation { customSymbols?: string[]; } +export interface IGitExplorerConfig { + enabled: boolean; + view: GitExplorerView; + files: { + layout: GitExplorerFilesLayout; + compact: boolean; + threshold: number; + }; + includeWorkingTree: boolean; + showTrackingBranch: boolean; + commitFormat: string; + commitFileFormat: string; + stashFormat: string; + stashFileFormat: string; + statusFileFormat: string; + // dateFormat: string | null; +} + export interface IRemotesConfig { type: CustomRemoteType; domain: string; @@ -316,18 +344,7 @@ export interface IConfig { defaultDateFormat: string | null; - gitExplorer: { - enabled: boolean; - view: GitExplorerView; - includeWorkingTree: boolean; - showTrackingBranch: boolean; - commitFormat: string; - commitFileFormat: string; - stashFormat: string; - stashFileFormat: string; - statusFileFormat: string; - // dateFormat: string | null; - }; + gitExplorer: IGitExplorerConfig; remotes: IRemotesConfig[]; diff --git a/src/system/array.ts b/src/system/array.ts index 13afa42..b314b4c 100644 --- a/src/system/array.ts +++ b/src/system/array.ts @@ -1,6 +1,16 @@ 'use strict'; +import { Objects } from './object'; export namespace Arrays { + export function countUniques(array: T[], accessor: (item: T) => string): { [key: string]: number } { + const uniqueCounts = Object.create(null); + for (const item of array) { + const value = accessor(item); + uniqueCounts[value] = (uniqueCounts[value] || 0) + 1; + } + return uniqueCounts; + } + export function groupBy(array: T[], accessor: (item: T) => string): { [key: string]: T[] } { return array.reduce((previous, current) => { const value = accessor(current); @@ -10,6 +20,96 @@ export namespace Arrays { }, Object.create(null)); } + export interface IHierarchicalItem { + name: string; + relativePath: string; + value?: T; + + // parent?: IHierarchicalItem; + children: { [key: string]: IHierarchicalItem } | undefined; + descendants: T[] | undefined; + } + + export function makeHierarchical(values: T[], splitPath: (i: T) => string[], joinPath: (...paths: string[]) => string, compact: boolean = false): IHierarchicalItem { + const seed = { + name: '', + relativePath: '', + children: Object.create(null), + descendants: [] + }; + + const hierarchy = values.reduce((root: IHierarchicalItem, value) => { + let folder = root; + + let relativePath = ''; + for (const folderName of splitPath(value)) { + relativePath = joinPath(relativePath, folderName); + + if (folder.children === undefined) { + folder.children = Object.create(null); + } + + let f = folder.children![folderName]; + if (f === undefined) { + folder.children![folderName] = f = { + name: folderName, + relativePath: relativePath, + // parent: folder, + children: undefined, + descendants: undefined + }; + } + + if (folder.descendants === undefined) { + folder.descendants = []; + } + folder.descendants.push(value); + folder = f; + } + + folder.value = value; + + return root; + }, seed); + + if (compact) return compactHierarchy(hierarchy, joinPath, true); + return hierarchy; + } + + export function compactHierarchy(root: IHierarchicalItem, joinPath: (...paths: string[]) => string, isRoot: boolean = true): IHierarchicalItem { + if (root.children === undefined) return root; + + const children = [...Objects.values(root.children)]; + + // // Attempts less nesting but duplicate roots + // if (!isRoot && children.every(c => c.value === undefined)) { + // const parentSiblings = root.parent!.children!; + // if (parentSiblings[root.name] !== undefined) { + // delete parentSiblings[root.name]; + + // for (const child of children) { + // child.name = joinPath(root.name, child.name); + // parentSiblings[child.name] = child; + // } + // } + // } + + for (const child of children) { + compactHierarchy(child, joinPath, false); + } + + if (!isRoot && children.length === 1) { + const child = children[0]; + if (child.value === undefined) { + root.name = joinPath(root.name, child.name); + root.relativePath = child.relativePath; + root.children = child.children; + } + } + + return root; + } + export function uniqueBy(array: T[], accessor: (item: T) => any, predicate?: (item: T) => boolean): T[] { const uniqueValues = Object.create(null); return array.filter(_ => { diff --git a/src/views/commitFileNode.ts b/src/views/commitFileNode.ts index 431ae9b..35542a3 100644 --- a/src/views/commitFileNode.ts +++ b/src/views/commitFileNode.ts @@ -2,7 +2,7 @@ import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode'; import { Commands, DiffWithPreviousCommandArgs } from '../commands'; import { ExplorerNode, ResourceType } from './explorerNode'; -import { CommitFormatter, getGitStatusIcon, GitBranch, GitCommit, GitService, GitUri, ICommitFormatOptions, IGitStatusFile, StatusFileFormatter } from '../gitService'; +import { CommitFormatter, getGitStatusIcon, GitBranch, GitCommit, GitService, GitUri, ICommitFormatOptions, IGitStatusFile, IStatusFormatOptions, StatusFileFormatter } from '../gitService'; import * as path from 'path'; export enum CommitFileNodeDisplayAs { @@ -17,6 +17,8 @@ export enum CommitFileNodeDisplayAs { export class CommitFileNode extends ExplorerNode { + readonly priority: boolean = false; + readonly repoPath: string; readonly resourceType: ResourceType = 'gitlens:commit-file'; constructor( @@ -28,6 +30,7 @@ export class CommitFileNode extends ExplorerNode { public readonly branch?: GitBranch ) { super(new GitUri(Uri.file(path.resolve(commit.repoPath, status.fileName)), { repoPath: commit.repoPath, fileName: status.fileName, sha: commit.sha })); + this.repoPath = commit.repoPath; } async getChildren(): Promise { @@ -36,7 +39,7 @@ export class CommitFileNode extends ExplorerNode { async getTreeItem(): Promise { if (this.commit.type !== 'file') { - const log = await this.git.getLogForFile(this.commit.repoPath, this.status.fileName, this.commit.sha, { maxCount: 2 }); + const log = await this.git.getLogForFile(this.repoPath, this.status.fileName, this.commit.sha, { maxCount: 2 }); if (log !== undefined) { this.commit = log.commits.get(this.commit.sha) || this.commit; } @@ -62,6 +65,14 @@ export class CommitFileNode extends ExplorerNode { return item; } + private _folderName: string | undefined; + get folderName() { + if (this._folderName === undefined) { + this._folderName = path.dirname(this.uri.getRelativePath()); + } + return this._folderName; + } + private _label: string | undefined; get label() { if (this._label === undefined) { @@ -70,11 +81,22 @@ export class CommitFileNode extends ExplorerNode { truncateMessageAtNewLine: true, dataFormat: this.git.config.defaultDateFormat } as ICommitFormatOptions) - : StatusFileFormatter.fromTemplate(this.getCommitFileTemplate(), this.status); + : StatusFileFormatter.fromTemplate(this.getCommitFileTemplate(), + this.status, + { relativePath: this.relativePath } as IStatusFormatOptions); } return this._label; } + private _relativePath: string | undefined; + get relativePath(): string | undefined { + return this._relativePath; + } + set relativePath(value: string | undefined) { + this._relativePath = value; + this._label = undefined; + } + protected getCommitTemplate() { return this.git.config.gitExplorer.commitFormat; } diff --git a/src/views/commitNode.ts b/src/views/commitNode.ts index 17718e5..38c0aa9 100644 --- a/src/views/commitNode.ts +++ b/src/views/commitNode.ts @@ -1,13 +1,17 @@ 'use strict'; -import { Iterables } from '../system'; +import { Arrays, Iterables } from '../system'; import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode'; import { Commands, DiffWithPreviousCommandArgs } from '../commands'; import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode'; +import { GitExplorerFilesLayout } from '../configuration'; +import { FolderNode, IFileExplorerNode } from './folderNode'; import { ExplorerNode, ResourceType } from './explorerNode'; import { CommitFormatter, GitBranch, GitLogCommit, GitService, GitUri, ICommitFormatOptions } from '../gitService'; +import * as path from 'path'; export class CommitNode extends ExplorerNode { + readonly repoPath: string; readonly resourceType: ResourceType = 'gitlens:commit'; constructor( @@ -17,17 +21,32 @@ export class CommitNode extends ExplorerNode { public readonly branch?: GitBranch ) { super(new GitUri(commit.uri, commit)); + this.repoPath = commit.repoPath; } async getChildren(): Promise { - const log = await this.git.getLogForRepo(this.commit.repoPath, this.commit.sha, 1); + const repoPath = this.repoPath; + + const log = await this.git.getLogForRepo(repoPath, this.commit.sha, 1); if (log === undefined) return []; const commit = Iterables.first(log.commits.values()); if (commit === undefined) return []; - const children = [...Iterables.map(commit.fileStatuses, s => new CommitFileNode(s, commit, this.context, this.git, CommitFileNodeDisplayAs.File, this.branch))]; - children.sort((a, b) => a.label!.localeCompare(b.label!)); + let children: IFileExplorerNode[] = [ + ...Iterables.map(commit.fileStatuses, s => new CommitFileNode(s, commit, this.context, this.git, CommitFileNodeDisplayAs.File, this.branch)) + ]; + + if (this.git.config.gitExplorer.files.layout !== GitExplorerFilesLayout.List) { + const hierarchy = Arrays.makeHierarchical(children, n => n.uri.getRelativePath().split('/'), + (...paths: string[]) => GitService.normalizePath(path.join(...paths)), this.git.config.gitExplorer.files.compact); + + const root = new FolderNode(repoPath, '', undefined, hierarchy, this.git.config.gitExplorer); + children = await root.getChildren() as IFileExplorerNode[]; + } + else { + children.sort((a, b) => a.label!.localeCompare(b.label!)); + } return children; } diff --git a/src/views/explorerNode.ts b/src/views/explorerNode.ts index 3c71eff..36f6521 100644 --- a/src/views/explorerNode.ts +++ b/src/views/explorerNode.ts @@ -10,6 +10,7 @@ export declare type ResourceType = 'gitlens:commit' | 'gitlens:commit-file' | 'gitlens:file-history' | + 'gitlens:folder' | 'gitlens:history' | 'gitlens:message' | 'gitlens:pager' | diff --git a/src/views/folderNode.ts b/src/views/folderNode.ts new file mode 100644 index 0000000..be2648f --- /dev/null +++ b/src/views/folderNode.ts @@ -0,0 +1,85 @@ +'use strict'; +import { Arrays, Objects } from '../system'; +import { TreeItem, TreeItemCollapsibleState, Uri } from 'vscode'; +import { GitExplorerFilesLayout, IGitExplorerConfig } from '../configuration'; +import { ExplorerNode, ResourceType } from './explorerNode'; +import { GitUri } from '../gitService'; + +export interface IFileExplorerNode extends ExplorerNode { + folderName: string; + label?: string; + priority: boolean; + relativePath?: string; + root?: Arrays.IHierarchicalItem; +} + +export class FolderNode extends ExplorerNode { + + readonly priority: boolean = true; + readonly resourceType: ResourceType = 'gitlens:folder'; + + constructor( + public readonly repoPath: string, + public folderName: string, + public relativePath: string | undefined, + public readonly root: Arrays.IHierarchicalItem, + private readonly config: IGitExplorerConfig + ) { + super(new GitUri(Uri.file(repoPath), { repoPath: repoPath, fileName: repoPath })); + } + + async getChildren(): Promise<(FolderNode | IFileExplorerNode)[]> { + if (this.root.descendants === undefined || this.root.children === undefined) return []; + + let children: (FolderNode | IFileExplorerNode)[]; + + const nesting = FolderNode.getFileNesting(this.config, this.root.descendants, this.relativePath === undefined); + if (nesting !== GitExplorerFilesLayout.List) { + children = []; + for (const folder of Objects.values(this.root.children)) { + if (folder.value === undefined) { + children.push(new FolderNode(this.repoPath, folder.name, folder.relativePath, folder, this.config)); + continue; + } + + folder.value.relativePath = this.root.relativePath; + children.push(folder.value); + } + } + else { + this.root.descendants.forEach(n => n.relativePath = this.root.relativePath); + children = this.root.descendants; + } + + children.sort((a, b) => { + return ((a instanceof FolderNode) ? -1 : 1) - ((b instanceof FolderNode) ? -1 : 1) || + (a.priority ? -1 : 1) - (b.priority ? -1 : 1) || + a.label!.localeCompare(b.label!); + }); + + return children; + } + + async getTreeItem(): Promise { + // TODO: Change this to expanded once https://github.com/Microsoft/vscode/issues/30918 is fixed + const item = new TreeItem(this.label, TreeItemCollapsibleState.Collapsed); + item.contextValue = this.resourceType; + return item; + } + + get label(): string { + return this.folderName; + } + + static getFileNesting(config: IGitExplorerConfig, children: T[], isRoot: boolean): GitExplorerFilesLayout { + const nesting = config.files.layout || GitExplorerFilesLayout.Auto; + if (nesting === GitExplorerFilesLayout.Auto) { + if (isRoot || config.files.compact) { + const nestingThreshold = config.files.threshold || 5; + if (children.length <= nestingThreshold) return GitExplorerFilesLayout.List; + } + return GitExplorerFilesLayout.Tree; + } + return nesting; + } +} \ No newline at end of file diff --git a/src/views/statusFileCommitsNode.ts b/src/views/statusFileCommitsNode.ts index 49c3f6c..557a356 100644 --- a/src/views/statusFileCommitsNode.ts +++ b/src/views/statusFileCommitsNode.ts @@ -3,7 +3,7 @@ import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } fr import { Commands, DiffWithPreviousCommandArgs } from '../commands'; import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode'; import { ExplorerNode, ResourceType } from './explorerNode'; -import { getGitStatusIcon, GitBranch, GitLogCommit, GitService, GitUri, IGitStatusFile, IGitStatusFileWithCommit, StatusFileFormatter } from '../gitService'; +import { getGitStatusIcon, GitBranch, GitLogCommit, GitService, GitUri, IGitStatusFile, IGitStatusFileWithCommit, IStatusFormatOptions, StatusFileFormatter } from '../gitService'; import * as path from 'path'; export class StatusFileCommitsNode extends ExplorerNode { @@ -11,7 +11,7 @@ export class StatusFileCommitsNode extends ExplorerNode { readonly resourceType: ResourceType = 'gitlens:status-file-commits'; constructor( - repoPath: string, + public readonly repoPath: string, public readonly status: IGitStatusFile, public commits: GitLogCommit[], protected readonly context: ExtensionContext, @@ -47,10 +47,20 @@ export class StatusFileCommitsNode extends ExplorerNode { return item; } + private _folderName: string | undefined; + get folderName() { + if (this._folderName === undefined) { + this._folderName = path.dirname(this.uri.getRelativePath()); + } + return this._folderName; + } + private _label: string | undefined; get label() { if (this._label === undefined) { - this._label = StatusFileFormatter.fromTemplate(this.git.config.gitExplorer.statusFileFormat, { ...this.status, commit: this.commit } as IGitStatusFileWithCommit); + this._label = StatusFileFormatter.fromTemplate(this.git.config.gitExplorer.statusFileFormat, + { ...this.status, commit: this.commit } as IGitStatusFileWithCommit, + { relativePath: this.relativePath } as IStatusFormatOptions); } return this._label; } @@ -59,12 +69,25 @@ export class StatusFileCommitsNode extends ExplorerNode { return this.commits[0]; } + get priority(): boolean { + return this.commit.isUncommitted; + } + + private _relativePath: string | undefined; + get relativePath(): string | undefined { + return this._relativePath; + } + set relativePath(value: string | undefined) { + this._relativePath = value; + this._label = undefined; + } + getCommand(): Command | undefined { return { title: 'Compare File with Previous Revision', command: Commands.DiffWithPrevious, arguments: [ - GitUri.fromFileStatus(this.status, this.uri.repoPath!), + GitUri.fromFileStatus(this.status, this.repoPath), { commit: this.commit, line: 0, diff --git a/src/views/statusFilesNode.ts b/src/views/statusFilesNode.ts index 8c71ec0..7a5482d 100644 --- a/src/views/statusFilesNode.ts +++ b/src/views/statusFilesNode.ts @@ -1,12 +1,16 @@ 'use strict'; import { Arrays, Iterables, Objects } from '../system'; import { ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode'; +import { GitExplorerFilesLayout } from '../configuration'; import { ExplorerNode, ResourceType, ShowAllNode } from './explorerNode'; +import { FolderNode, IFileExplorerNode } from './folderNode'; import { GitBranch, GitLog, GitLogCommit, GitService, GitStatus, GitUri, IGitStatusFileWithCommit } from '../gitService'; import { StatusFileCommitsNode } from './statusFileCommitsNode'; +import * as path from 'path'; export class StatusFilesNode extends ExplorerNode { + readonly repoPath: string; readonly resourceType: ResourceType = 'gitlens:status-files'; maxCount: number | undefined = undefined; @@ -19,14 +23,17 @@ export class StatusFilesNode extends ExplorerNode { public readonly branch?: GitBranch ) { super(new GitUri(Uri.file(status.repoPath), { repoPath: status.repoPath, fileName: status.repoPath })); + this.repoPath = status.repoPath; } async getChildren(): Promise { let statuses: IGitStatusFileWithCommit[] = []; + const repoPath = this.repoPath; + let log: GitLog | undefined; if (this.range !== undefined) { - log = await this.git.getLogForRepo(this.status.repoPath, this.range, this.maxCount); + log = await this.git.getLogForRepo(repoPath, this.range, this.maxCount); if (log !== undefined) { statuses = Array.from(Iterables.flatMap(log.commits.values(), c => { return c.fileStatuses.map(s => { @@ -38,22 +45,33 @@ export class StatusFilesNode extends ExplorerNode { if (this.status.files.length !== 0 && this.includeWorkingTree) { statuses.splice(0, 0, ...this.status.files.map(s => { - return { ...s, commit: new GitLogCommit('file', this.status.repoPath, GitService.uncommittedSha, s.fileName, 'You', new Date(), '', s.status, [s], s.originalFileName, 'HEAD', s.fileName) } as IGitStatusFileWithCommit; + return { + ...s, + commit: new GitLogCommit('file', repoPath, GitService.uncommittedSha, s.fileName, 'You', new Date(), '', s.status, [s], s.originalFileName, 'HEAD', s.fileName) + } as IGitStatusFileWithCommit; })); } statuses.sort((a, b) => b.commit.date.getTime() - a.commit.date.getTime()); const groups = Arrays.groupBy(statuses, s => s.fileName); - const children: (StatusFileCommitsNode | ShowAllNode)[] = [ - ...Iterables.map(Objects.values(groups), - statuses => new StatusFileCommitsNode(this.uri.repoPath!, statuses[statuses.length - 1], statuses.map(s => s.commit), this.context, this.git, this.branch)) + let children: IFileExplorerNode[] = [ + ...Iterables.map(Objects.values(groups), statuses => new StatusFileCommitsNode(repoPath, statuses[statuses.length - 1], statuses.map(s => s.commit), this.context, this.git, this.branch)) ]; - children.sort((a: StatusFileCommitsNode, b: StatusFileCommitsNode) => (a.commit.isUncommitted ? -1 : 1) - (b.commit.isUncommitted ? -1 : 1) || a.label!.localeCompare(b.label!)); + if (this.git.config.gitExplorer.files.layout !== GitExplorerFilesLayout.List) { + const hierarchy = Arrays.makeHierarchical(children, n => n.uri.getRelativePath().split('/'), + (...paths: string[]) => GitService.normalizePath(path.join(...paths)), this.git.config.gitExplorer.files.compact); + + const root = new FolderNode(repoPath, '', undefined, hierarchy, this.git.config.gitExplorer); + children = await root.getChildren() as IFileExplorerNode[]; + } + else { + children.sort((a, b) => (a.priority ? -1 : 1) - (b.priority ? -1 : 1) || a.label!.localeCompare(b.label!)); + } if (log !== undefined && log.truncated) { - children.push(new ShowAllNode('Show All Changes', this, this.context)); + (children as (IFileExplorerNode | ShowAllNode)[]).push(new ShowAllNode('Show All Changes', this, this.context)); } return children; } @@ -62,7 +80,7 @@ export class StatusFilesNode extends ExplorerNode { let files = (this.status.files !== undefined && this.includeWorkingTree) ? this.status.files.length : 0; if (this.status.upstream !== undefined) { - const stats = await this.git.getChangedFilesCount(this.status.repoPath, `${this.status.upstream}...`); + const stats = await this.git.getChangedFilesCount(this.repoPath, `${this.status.upstream}...`); if (stats !== undefined) { files += stats.files; } @@ -82,5 +100,4 @@ export class StatusFilesNode extends ExplorerNode { private get includeWorkingTree(): boolean { return this.git.config.gitExplorer.includeWorkingTree; } - } \ No newline at end of file