diff --git a/images/dark/icon-ellipsis.svg b/images/dark/icon-ellipsis.svg new file mode 100644 index 0000000..52cb665 --- /dev/null +++ b/images/dark/icon-ellipsis.svg @@ -0,0 +1,4 @@ + + + + diff --git a/images/light/icon-ellipsis.svg b/images/light/icon-ellipsis.svg new file mode 100644 index 0000000..badcae1 --- /dev/null +++ b/images/light/icon-ellipsis.svg @@ -0,0 +1,4 @@ + + + + diff --git a/package.json b/package.json index 32ac931..ed96a2f 100644 --- a/package.json +++ b/package.json @@ -2959,6 +2959,15 @@ "dark": "images/dark/icon-refresh.svg", "light": "images/light/icon-refresh.svg" } + }, + { + "command": "gitlens.views.showAllChildren", + "title": "Show All", + "category": "GitLens", + "icon": { + "dark": "images/dark/icon-unfold.svg", + "light": "images/light/icon-unfold.svg" + } } ], "menus": { @@ -3618,6 +3627,10 @@ { "command": "gitlens.views.refreshNode", "when": "false" + }, + { + "command": "gitlens.views.showAllChildren", + "when": "false" } ], "editor/context": [ @@ -4806,6 +4819,16 @@ "command": "gitlens.views.refreshNode", "when": "viewItem =~ /gitlens:(?!file\\b)/", "group": "9_gitlens@1" + }, + { + "command": "gitlens.views.showAllChildren", + "when": "viewItem =~ /gitlens:pager\\b/", + "group": "inline@1" + }, + { + "command": "gitlens.views.showAllChildren", + "when": "viewItem =~ /gitlens:pager\\b/", + "group": "1_gitlens@1" } ] }, diff --git a/src/views/nodes/branchNode.ts b/src/views/nodes/branchNode.ts index c613abb..2d713ff 100644 --- a/src/views/nodes/branchNode.ts +++ b/src/views/nodes/branchNode.ts @@ -79,7 +79,7 @@ export class BranchNode extends ViewRefNode implements Pageabl } const log = await Container.git.getLog(this.uri.repoPath!, { - maxCount: this.maxCount || this.view.config.defaultItemLimit, + maxCount: this.maxCount !== undefined ? this.maxCount : this.view.config.defaultItemLimit, ref: this.ref }); if (log === undefined) return [new MessageNode(this.view, this, 'No commits could be found.')]; diff --git a/src/views/nodes/branchTrackingStatusNode.ts b/src/views/nodes/branchTrackingStatusNode.ts index dabfbc2..a6e829a 100644 --- a/src/views/nodes/branchTrackingStatusNode.ts +++ b/src/views/nodes/branchTrackingStatusNode.ts @@ -44,7 +44,7 @@ export class BranchTrackingStatusNode extends ViewNode implements : `${this.status.ref}..${this.status.upstream}`; const log = await Container.git.getLog(this.uri.repoPath!, { - maxCount: this.maxCount || this.view.config.defaultItemLimit, + maxCount: this.maxCount !== undefined ? this.maxCount : this.view.config.defaultItemLimit, ref: range }); if (log === undefined) return []; diff --git a/src/views/nodes/branchesNode.ts b/src/views/nodes/branchesNode.ts index 55455f2..e831739 100644 --- a/src/views/nodes/branchesNode.ts +++ b/src/views/nodes/branchesNode.ts @@ -3,7 +3,7 @@ import { TreeItem, TreeItemCollapsibleState } from 'vscode'; import { ViewBranchesLayout } from '../../configuration'; import { Container } from '../../container'; import { GitUri, Repository } from '../../git/gitService'; -import { Arrays, Iterables } from '../../system'; +import { Arrays, debug, gate, Iterables } from '../../system'; import { RepositoriesView } from '../repositoriesView'; import { BranchNode } from './branchNode'; import { BranchOrTagFolderNode } from './branchOrTagFolderNode'; @@ -78,6 +78,8 @@ export class BranchesNode extends ViewNode { return item; } + @gate() + @debug() refresh() { this._children = undefined; } diff --git a/src/views/nodes/common.ts b/src/views/nodes/common.ts index 11544e2..ae3f2b9 100644 --- a/src/views/nodes/common.ts +++ b/src/views/nodes/common.ts @@ -152,6 +152,10 @@ export abstract class PagerNode extends ViewNode { this._args.previousNode = previousNode; } + showAll() { + this.view.refreshNode(this.parent!, true, { ...this._args, maxCount: 0 }); + } + getChildren(): ViewNode[] | Promise { return []; } @@ -161,8 +165,8 @@ export abstract class PagerNode extends ViewNode { item.contextValue = ResourceType.Pager; item.command = this.getCommand(); item.iconPath = { - dark: Container.context.asAbsolutePath('images/dark/icon-unfold.svg'), - light: Container.context.asAbsolutePath('images/light/icon-unfold.svg') + dark: Container.context.asAbsolutePath('images/dark/icon-ellipsis.svg'), + light: Container.context.asAbsolutePath('images/light/icon-ellipsis.svg') }; return item; } @@ -171,7 +175,7 @@ export abstract class PagerNode extends ViewNode { return { title: 'Refresh', command: 'gitlens.views.refreshNode', - arguments: [this.parent, false, this._args] + arguments: [this.parent, true, this._args] }; } } diff --git a/src/views/nodes/compareResultsNode.ts b/src/views/nodes/compareResultsNode.ts index 23e16c1..f99e550 100644 --- a/src/views/nodes/compareResultsNode.ts +++ b/src/views/nodes/compareResultsNode.ts @@ -3,13 +3,15 @@ import { TreeItem, TreeItemCollapsibleState } from 'vscode'; import { NamedRef } from '../../constants'; import { Container } from '../../container'; import { GitService, GitUri } from '../../git/gitService'; -import { log, Strings } from '../../system'; +import { debug, gate, log, Strings } from '../../system'; import { CompareView } from '../compareView'; import { CommitsQueryResults, ResultsCommitsNode } from './resultsCommitsNode'; import { ResultsFilesNode } from './resultsFilesNode'; import { ResourceType, SubscribeableViewNode, ViewNode } from './viewNode'; export class CompareResultsNode extends SubscribeableViewNode { + private _children: ViewNode[] | undefined; + constructor( view: CompareView, public readonly repoPath: string, @@ -39,10 +41,21 @@ export class CompareResultsNode extends SubscribeableViewNode { } getChildren(): ViewNode[] { - return [ - new ResultsCommitsNode(this.view, this, this.uri.repoPath!, this.getCommitsQuery.bind(this)), - new ResultsFilesNode(this.view, this, this.uri.repoPath!, this._ref1.ref, this._ref2.ref) - ]; + if (this._children === undefined) { + this._children = [ + new ResultsCommitsNode( + this.view, + this, + this.uri.repoPath!, + '? commits', + this.getCommitsQuery.bind(this), + true, + false + ), + new ResultsFilesNode(this.view, this, this.uri.repoPath!, this._ref1.ref, this._ref2.ref) + ]; + } + return this._children; } async getTreeItem(): Promise { @@ -86,14 +99,12 @@ export class CompareResultsNode extends SubscribeableViewNode { void this.triggerChange(); } - @log() - async unpin() { - if (!this._pinned) return; - - await this.view.updatePinnedComparison(this.getPinnableId()); + @gate() + @debug() + refresh(reset: boolean = false) { + if (!reset) return; - this._pinned = false; - void this.triggerChange(); + this._children = undefined; } @log() @@ -118,6 +129,16 @@ export class CompareResultsNode extends SubscribeableViewNode { this.view.triggerNodeChange(this); } + @log() + async unpin() { + if (!this._pinned) return; + + await this.view.updatePinnedComparison(this.getPinnableId()); + + this._pinned = false; + void this.triggerChange(); + } + protected subscribe() { return undefined; } diff --git a/src/views/nodes/contributorNode.ts b/src/views/nodes/contributorNode.ts index 7e1c18b..2eaf1ea 100644 --- a/src/views/nodes/contributorNode.ts +++ b/src/views/nodes/contributorNode.ts @@ -26,7 +26,7 @@ export class ContributorNode extends ViewNode implements Pagea async getChildren(): Promise { const log = await Container.git.getLog(this.uri.repoPath!, { - maxCount: this.maxCount || this.view.config.defaultItemLimit, + maxCount: this.maxCount !== undefined ? this.maxCount : this.view.config.defaultItemLimit, authors: [this.contributor.name] }); if (log === undefined) return [new MessageNode(this.view, this, 'No commits could be found.')]; diff --git a/src/views/nodes/lineHistoryTrackerNode.ts b/src/views/nodes/lineHistoryTrackerNode.ts index 8ed57a8..1df7552 100644 --- a/src/views/nodes/lineHistoryTrackerNode.ts +++ b/src/views/nodes/lineHistoryTrackerNode.ts @@ -90,7 +90,9 @@ export class LineHistoryTrackerNode extends SubscribeableViewNode `returned ${r}` + }) async refresh(reset: boolean = false) { const cc = Logger.getCorrelationContext(); diff --git a/src/views/nodes/resultsCommitsNode.ts b/src/views/nodes/resultsCommitsNode.ts index 639b81d..00c474a 100644 --- a/src/views/nodes/resultsCommitsNode.ts +++ b/src/views/nodes/resultsCommitsNode.ts @@ -2,12 +2,12 @@ import { TreeItem, TreeItemCollapsibleState } from 'vscode'; import { Container } from '../../container'; import { GitLog, GitUri } from '../../git/gitService'; -import { Iterables } from '../../system'; +import { debug, gate, Iterables } from '../../system'; import { ViewWithFiles } from '../viewBase'; import { CommitNode } from './commitNode'; import { ShowMoreNode } from './common'; import { getBranchesAndTagTipsFn, insertDateMarkers } from './helpers'; -import { PageableViewNode, ResourceType, ViewNode } from './viewNode'; +import { getNextId, PageableViewNode, ResourceType, ViewNode } from './viewNode'; export interface CommitsQueryResults { label: string; @@ -18,15 +18,25 @@ export class ResultsCommitsNode extends ViewNode implements Pagea readonly supportsPaging: boolean = true; maxCount: number | undefined; + // Generate a unique id so the node order is preserved, since we update the label when the query completes + private readonly _uniqueId: number = getNextId('ResultsCommitsNode'); + constructor( view: ViewWithFiles, parent: ViewNode, public readonly repoPath: string, - private readonly _commitsQuery: (maxCount: number | undefined) => Promise + private _label: string, + private readonly _commitsQuery: (maxCount: number | undefined) => Promise, + private _querying = true, + private readonly _expand = true ) { super(GitUri.fromRepoPath(repoPath), view, parent); } + get id(): string { + return `${this._uniqueId}|${this._instanceId}:${this.type}(${this.repoPath})`; + } + get type(): ResourceType { return ResourceType.ResultsCommits; } @@ -47,14 +57,41 @@ export class ResultsCommitsNode extends ViewNode implements Pagea ]; if (log.truncated) { - children.push(new ShowMoreNode(this.view, this, 'Results', children[children.length - 1])); + children.push(new ShowMoreNode(this.view, this, 'Results', children[children.length - 1], this.maxCount)); } return children; } async getTreeItem(): Promise { - const { label, log } = await this.getCommitsQueryResults(); + let state; + let label; + let log; + if (this._querying) { + // Need to use Collapsed before we have results or the item won't show up in the view until the children are awaited + state = TreeItemCollapsibleState.Collapsed; + label = this._label; + + this.getCommitsQueryResults().then(({ log }) => { + this._querying = false; + if (log != null) { + this.maxCount = log.maxCount; + } + + this.triggerChange(false); + }); + } + else { + ({ label, log } = await this.getCommitsQueryResults()); + if (log != null) { + this.maxCount = log.maxCount; + } + + state = this._expand ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.Collapsed; + if (log == null || log.count === 0) { + state = TreeItemCollapsibleState.None; + } + } let description; if ((await Container.git.getRepositoryCount()) > 1) { @@ -62,17 +99,19 @@ export class ResultsCommitsNode extends ViewNode implements Pagea description = (repo && repo.formattedName) || this.repoPath; } - const item = new TreeItem( - label, - log && log.count > 0 ? TreeItemCollapsibleState.Collapsed : TreeItemCollapsibleState.None - ); + const item = new TreeItem(label, state); item.contextValue = this.type; item.description = description; + item.id = this.id; return item; } - refresh() { + @gate() + @debug() + refresh(reset: boolean = false) { + if (!reset) return; + this._commitsQueryResults = this._commitsQuery(this.maxCount); } diff --git a/src/views/nodes/resultsFilesNode.ts b/src/views/nodes/resultsFilesNode.ts index b2bf292..ef000f4 100644 --- a/src/views/nodes/resultsFilesNode.ts +++ b/src/views/nodes/resultsFilesNode.ts @@ -4,11 +4,11 @@ import { TreeItem, TreeItemCollapsibleState } from 'vscode'; import { ViewFilesLayout } from '../../configuration'; import { Container } from '../../container'; import { GitFile, GitUri } from '../../git/gitService'; -import { Arrays, Iterables, Strings } from '../../system'; +import { Arrays, debug, gate, Iterables, Strings } from '../../system'; import { ViewWithFiles } from '../viewBase'; import { FileNode, FolderNode } from './folderNode'; import { ResultsFileNode } from './resultsFileNode'; -import { ResourceType, ViewNode } from './viewNode'; +import { getNextId, ResourceType, ViewNode } from './viewNode'; export interface FilesQueryResults { label: string; @@ -16,6 +16,9 @@ export interface FilesQueryResults { } export class ResultsFilesNode extends ViewNode { + // Generate a unique id so the node order is preserved, since we update the label when the query completes + private readonly _uniqueId: number = getNextId('ResultsFilesNode'); + constructor( view: ViewWithFiles, parent: ViewNode, @@ -26,6 +29,10 @@ export class ResultsFilesNode extends ViewNode { super(GitUri.fromRepoPath(repoPath), view, parent); } + get id(): string { + return `${this._uniqueId}|${this._instanceId}:gitlens:results:files(${this.repoPath})`; + } + async getChildren(): Promise { const { diff } = await this.getFilesQueryResults(); if (diff === undefined) return []; @@ -57,21 +64,45 @@ export class ResultsFilesNode extends ViewNode { } async getTreeItem(): Promise { - const { diff, label } = await this.getFilesQueryResults(); + let state; + let label; + let diff; + if (this._querying) { + // Need to use Collapsed before we have results or the item won't show up in the view until the children are awaited + state = TreeItemCollapsibleState.Collapsed; + label = '? files changed'; + + this.getFilesQueryResults().then(_ => { + this._querying = false; + this.triggerChange(false); + }); + } + else { + ({ label, diff } = await this.getFilesQueryResults()); + + state = TreeItemCollapsibleState.Expanded; + if (diff == null || diff.length === 0) { + state = TreeItemCollapsibleState.None; + } + } - const item = new TreeItem( - label, - diff && diff.length > 0 ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.None - ); + const item = new TreeItem(label, state); item.contextValue = ResourceType.ResultsFiles; + item.id = this.id; + return item; } - refresh() { + @gate() + @debug() + refresh(reset: boolean = false) { + if (!reset) return; + this._filesQueryResults = this.getFilesQueryResultsCore(); } private _filesQueryResults: Promise | undefined; + private _querying = true; private getFilesQueryResults() { if (this._filesQueryResults === undefined) { diff --git a/src/views/nodes/searchResultsCommitsNode.ts b/src/views/nodes/searchResultsCommitsNode.ts index 4cf5333..0717190 100644 --- a/src/views/nodes/searchResultsCommitsNode.ts +++ b/src/views/nodes/searchResultsCommitsNode.ts @@ -1,5 +1,5 @@ 'use strict'; -import { TreeItem } from 'vscode'; +import { TreeItem, TreeItemCollapsibleState } from 'vscode'; import { SearchCommitsCommandArgs } from '../../commands'; import { Commands } from '../../commands/common'; import { GitRepoSearchBy } from '../../git/gitService'; @@ -14,9 +14,11 @@ export class SearchResultsCommitsNode extends ResultsCommitsNode { repoPath: string, public readonly search: string, public readonly searchBy: GitRepoSearchBy, - commitsQuery: (maxCount: number | undefined) => Promise + label: string, + commitsQuery: (maxCount: number | undefined) => Promise, + _querying = true ) { - super(view, parent, repoPath, commitsQuery); + super(view, parent, repoPath, label, commitsQuery, _querying, true); } get type(): ResourceType { @@ -24,11 +26,9 @@ export class SearchResultsCommitsNode extends ResultsCommitsNode { } async getTreeItem(): Promise { - const { log } = await super.getCommitsQueryResults(); - const item = await super.getTreeItem(); - if (log == null || log.count === 0) { + if (item.collapsibleState === TreeItemCollapsibleState.None) { const args: SearchCommitsCommandArgs = { search: this.search, searchBy: this.searchBy, @@ -40,6 +40,7 @@ export class SearchResultsCommitsNode extends ResultsCommitsNode { arguments: [args] }; } + item.id = undefined; return item; } diff --git a/src/views/nodes/tagNode.ts b/src/views/nodes/tagNode.ts index 0ba0d6f..518e5a1 100644 --- a/src/views/nodes/tagNode.ts +++ b/src/views/nodes/tagNode.ts @@ -32,7 +32,7 @@ export class TagNode extends ViewRefNode implements PageableVi async getChildren(): Promise { const log = await Container.git.getLog(this.uri.repoPath!, { - maxCount: this.maxCount || this.view.config.defaultItemLimit, + maxCount: this.maxCount !== undefined ? this.maxCount : this.view.config.defaultItemLimit, ref: this.tag.name }); if (log === undefined) return [new MessageNode(this.view, this, 'No commits could be found.')]; diff --git a/src/views/nodes/viewNode.ts b/src/views/nodes/viewNode.ts index 1b6e44d..b62b48b 100644 --- a/src/views/nodes/viewNode.ts +++ b/src/views/nodes/viewNode.ts @@ -53,12 +53,21 @@ export interface ViewNode { readonly id?: string; } -const counter = 0; -function getViewNodeInstanceId() { - // if (counter === Number.MAX_SAFE_INTEGER) { - // counter = 0; - // } - // counter++; +const counters: { [key: string]: number } = { + instanceId: 0 +}; +export function getNextId(key?: string) { + if (key === undefined) { + key = 'instanceId'; + } + + let counter = counters[key] || 0; + if (counter === Number.MAX_SAFE_INTEGER) { + counter = 0; + } + counter++; + + counters[key] = counter; return counter; } @@ -67,7 +76,7 @@ export abstract class ViewNode { protected readonly _instanceId: number; constructor(uri: GitUri, public readonly view: TView, protected readonly parent?: ViewNode) { - this._instanceId = getViewNodeInstanceId(); + this._instanceId = 0; //getNextId(); this._uri = uri; if (Logger.isDebugging) { @@ -110,8 +119,8 @@ export abstract class ViewNode { @gate() @debug() - triggerChange(): Promise { - return this.view.refreshNode(this); + triggerChange(reset: boolean = false): Promise { + return this.view.refreshNode(this, reset); } } diff --git a/src/views/searchView.ts b/src/views/searchView.ts index e8b9c6b..b4417c5 100644 --- a/src/views/searchView.ts +++ b/src/views/searchView.ts @@ -118,7 +118,16 @@ export class SearchView extends ViewBase { ); return this.addResults( - new SearchResultsCommitsNode(this, this._root!, repoPath, search, searchBy, searchQueryFn) + new SearchResultsCommitsNode( + this, + this._root!, + repoPath, + search, + searchBy, + `Searching for ${typeof options.label === 'string' ? options.label : options.label.label}`, + searchQueryFn, + true + ) ); } @@ -143,7 +152,7 @@ export class SearchView extends ViewBase { }); return this.addResults( - new SearchResultsCommitsNode(this, this._root!, repoPath, search, searchBy, searchQueryFn) + new SearchResultsCommitsNode(this, this._root!, repoPath, search, searchBy, label, searchQueryFn, false) ); } diff --git a/src/views/viewCommands.ts b/src/views/viewCommands.ts index 039aa6c..3a3b56b 100644 --- a/src/views/viewCommands.ts +++ b/src/views/viewCommands.ts @@ -39,6 +39,7 @@ import { viewSupportsNodeDismissal } from './nodes'; import { Strings } from '../system/string'; +import { PagerNode } from './nodes/common'; export interface RefreshNodeCommandArgs { maxCount?: number; @@ -73,6 +74,7 @@ export class ViewCommands implements Disposable { (node: ViewNode) => viewSupportsNodeDismissal(node.view) && node.view.dismissNode(node), this ); + commands.registerCommand('gitlens.views.showAllChildren', (node: PagerNode) => node.showAll(), this); commands.registerCommand('gitlens.views.fetch', this.fetch, this); commands.registerCommand('gitlens.views.pull', this.pull, this);