diff --git a/package.json b/package.json index ab3c05a..66ef4d2 100644 --- a/package.json +++ b/package.json @@ -2979,6 +2979,15 @@ } }, { + "command": "gitlens.views.showMoreChildren", + "title": "Show More", + "category": "GitLens", + "icon": { + "dark": "images/dark/icon-unfold.svg", + "light": "images/light/icon-unfold.svg" + } + }, + { "command": "gitlens.views.showAllChildren", "title": "Show All", "category": "GitLens", @@ -3647,6 +3656,10 @@ "when": "false" }, { + "command": "gitlens.views.showMoreChildren", + "when": "false" + }, + { "command": "gitlens.views.showAllChildren", "when": "false" } diff --git a/src/views/nodes/branchNode.ts b/src/views/nodes/branchNode.ts index 11a9999..dd03d52 100644 --- a/src/views/nodes/branchNode.ts +++ b/src/views/nodes/branchNode.ts @@ -13,8 +13,9 @@ import { getBranchesAndTagTipsFn, insertDateMarkers } from './helpers'; import { PageableViewNode, ResourceType, ViewNode, ViewRefNode } from './viewNode'; export class BranchNode extends ViewRefNode implements PageableViewNode { - readonly supportsPaging: boolean = true; - maxCount: number | undefined; + readonly supportsPaging = true; + readonly rememberLastMaxCount = true; + maxCount: number | undefined = this.view.getNodeLastMaxCount(this); private _children: ViewNode[] | undefined; @@ -94,7 +95,9 @@ export class BranchNode extends ViewRefNode implements Pageabl ); if (log.truncated) { - children.push(new ShowMoreNode(this.view, this, 'Commits', children[children.length - 1])); + children.push( + new ShowMoreNode(this.view, this, 'Commits', log.maxCount, children[children.length - 1]) + ); } this._children = children; @@ -204,7 +207,7 @@ export class BranchNode extends ViewRefNode implements Pageabl @gate() @debug() - refresh(reset: boolean = false) { + refresh() { this._children = undefined; } } diff --git a/src/views/nodes/branchTrackingStatusNode.ts b/src/views/nodes/branchTrackingStatusNode.ts index f0e78f3..9e78a33 100644 --- a/src/views/nodes/branchTrackingStatusNode.ts +++ b/src/views/nodes/branchTrackingStatusNode.ts @@ -17,8 +17,9 @@ export interface BranchTrackingStatus { } export class BranchTrackingStatusNode extends ViewNode implements PageableViewNode { - readonly supportsPaging: boolean = true; - maxCount: number | undefined; + readonly supportsPaging = true; + readonly rememberLastMaxCount = true; + maxCount: number | undefined = this.view.getNodeLastMaxCount(this); constructor( view: ViewWithFiles, @@ -74,7 +75,7 @@ export class BranchTrackingStatusNode extends ViewNode implements } if (log.truncated) { - children.push(new ShowMoreNode(this.view, this, 'Commits', children[children.length - 1])); + children.push(new ShowMoreNode(this.view, this, 'Commits', log.maxCount, children[children.length - 1])); } return children; } diff --git a/src/views/nodes/common.ts b/src/views/nodes/common.ts index ae3f2b9..770cbf0 100644 --- a/src/views/nodes/common.ts +++ b/src/views/nodes/common.ts @@ -2,8 +2,7 @@ import { Command, ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri } from 'vsc import { GlyphChars } from '../../constants'; import { Container } from '../../container'; import { View } from '../viewBase'; -import { RefreshNodeCommandArgs } from '../viewCommands'; -import { ResourceType, unknownGitUri, ViewNode } from './viewNode'; +import { PageableViewNode, ResourceType, unknownGitUri, ViewNode } from './viewNode'; export class MessageNode extends ViewNode { constructor( @@ -137,23 +136,29 @@ export class UpdateableMessageNode extends ViewNode { } export abstract class PagerNode extends ViewNode { - protected _args: RefreshNodeCommandArgs = {}; - constructor( view: View, - parent: ViewNode, + parent: ViewNode & PageableViewNode, protected readonly message: string, - previousNode?: ViewNode, - maxCount: number = Container.config.views.pageItemLimit + maxCount: number | undefined, + private readonly _previousNode?: ViewNode, + private readonly _pageSize: number = Container.config.views.pageItemLimit ) { super(unknownGitUri, view, parent); - this._args.maxCount = maxCount; - this._args.previousNode = previousNode; + parent.maxCount = maxCount; + } + + showMore() { + return this.view.showMoreNodeChildren( + this.parent! as ViewNode & PageableViewNode, + this._pageSize, + this._previousNode + ); } showAll() { - this.view.refreshNode(this.parent!, true, { ...this._args, maxCount: 0 }); + return this.view.showMoreNodeChildren(this.parent! as ViewNode & PageableViewNode, 0, this._previousNode); } getChildren(): ViewNode[] | Promise { @@ -173,23 +178,31 @@ export abstract class PagerNode extends ViewNode { getCommand(): Command | undefined { return { - title: 'Refresh', - command: 'gitlens.views.refreshNode', - arguments: [this.parent, true, this._args] + title: 'Show More', + command: 'gitlens.views.showMoreChildren', + arguments: [this] }; } } export class ShowMoreNode extends PagerNode { - constructor(view: View, parent: ViewNode, itemType: string, previousNode: ViewNode, maxCount?: number) { + constructor( + view: View, + parent: ViewNode & PageableViewNode, + itemType: string, + maxCount: number | undefined, + previousNode: ViewNode, + pageSize?: number + ) { super( view, parent, - maxCount === 0 + pageSize === 0 ? `Show All ${itemType} ${GlyphChars.Space}${GlyphChars.Dash}${GlyphChars.Space} this may take a while` : `Show More ${itemType}`, + maxCount, previousNode, - maxCount + pageSize ); } } diff --git a/src/views/nodes/contributorNode.ts b/src/views/nodes/contributorNode.ts index 6cfec0c..9aa9fff 100644 --- a/src/views/nodes/contributorNode.ts +++ b/src/views/nodes/contributorNode.ts @@ -11,8 +11,9 @@ import { CommitNode } from './commitNode'; import { GlyphChars } from '../../constants'; export class ContributorNode extends ViewNode implements PageableViewNode { - readonly supportsPaging: boolean = true; - maxCount: number | undefined; + readonly supportsPaging = true; + readonly rememberLastMaxCount = true; + maxCount: number | undefined = this.view.getNodeLastMaxCount(this); constructor(uri: GitUri, view: RepositoriesView, parent: ViewNode, public readonly contributor: GitContributor) { super(uri, view, parent); @@ -41,7 +42,7 @@ export class ContributorNode extends ViewNode implements Pagea ]; if (log.truncated) { - children.push(new ShowMoreNode(this.view, this, 'Commits', children[children.length - 1])); + children.push(new ShowMoreNode(this.view, this, 'Commits', log.maxCount, children[children.length - 1])); } return children; } diff --git a/src/views/nodes/fileHistoryNode.ts b/src/views/nodes/fileHistoryNode.ts index 07e1317..16b2832 100644 --- a/src/views/nodes/fileHistoryNode.ts +++ b/src/views/nodes/fileHistoryNode.ts @@ -19,7 +19,7 @@ import { insertDateMarkers } from './helpers'; import { PageableViewNode, ResourceType, SubscribeableViewNode, ViewNode } from './viewNode'; export class FileHistoryNode extends SubscribeableViewNode implements PageableViewNode { - readonly supportsPaging: boolean = true; + readonly supportsPaging = true; maxCount: number | undefined; constructor(uri: GitUri, view: View, parent: ViewNode) { @@ -89,7 +89,9 @@ export class FileHistoryNode extends SubscribeableViewNode implements PageableVi ); if (log.truncated) { - children.push(new ShowMoreNode(this.view, this, 'Commits', children[children.length - 1])); + children.push( + new ShowMoreNode(this.view, this, 'Commits', log.maxCount, children[children.length - 1]) + ); } } diff --git a/src/views/nodes/lineHistoryNode.ts b/src/views/nodes/lineHistoryNode.ts index c1ce548..0d14d21 100644 --- a/src/views/nodes/lineHistoryNode.ts +++ b/src/views/nodes/lineHistoryNode.ts @@ -18,7 +18,7 @@ import { insertDateMarkers } from './helpers'; import { PageableViewNode, ResourceType, SubscribeableViewNode, ViewNode } from './viewNode'; export class LineHistoryNode extends SubscribeableViewNode implements PageableViewNode { - readonly supportsPaging: boolean = true; + readonly supportsPaging = true; maxCount: number | undefined; constructor( @@ -111,7 +111,9 @@ export class LineHistoryNode extends SubscribeableViewNode implements PageableVi ); if (log.truncated) { - children.push(new ShowMoreNode(this.view, this, 'Commits', children[children.length - 1])); + children.push( + new ShowMoreNode(this.view, this, 'Commits', log.maxCount, children[children.length - 1]) + ); } } diff --git a/src/views/nodes/reflogNode.ts b/src/views/nodes/reflogNode.ts index 5f91e88..24e9e08 100644 --- a/src/views/nodes/reflogNode.ts +++ b/src/views/nodes/reflogNode.ts @@ -9,8 +9,9 @@ import { debug, gate } from '../../system'; import { MessageNode, ShowMoreNode } from './common'; export class ReflogNode extends ViewNode implements PageableViewNode { - readonly supportsPaging: boolean = true; - maxCount: number | undefined; + readonly supportsPaging = true; + readonly rememberLastMaxCount = true; + maxCount: number | undefined = this.view.getNodeLastMaxCount(this); private _children: ViewNode[] | undefined; @@ -37,7 +38,9 @@ export class ReflogNode extends ViewNode implements PageableVi children.push(...reflog.records.map(r => new ReflogRecordNode(this.view, this, r))); if (reflog.truncated) { - children.push(new ShowMoreNode(this.view, this, 'Activity', children[children.length - 1])); + children.push( + new ShowMoreNode(this.view, this, 'Activity', reflog.maxCount, children[children.length - 1]) + ); } this._children = children; diff --git a/src/views/nodes/reflogRecordNode.ts b/src/views/nodes/reflogRecordNode.ts index 11d25ce..e2b8940 100644 --- a/src/views/nodes/reflogRecordNode.ts +++ b/src/views/nodes/reflogRecordNode.ts @@ -10,8 +10,9 @@ import { MessageNode, ShowMoreNode } from './common'; import { PageableViewNode, ResourceType, ViewNode } from './viewNode'; export class ReflogRecordNode extends ViewNode implements PageableViewNode { - readonly supportsPaging: boolean = true; - maxCount: number | undefined; + readonly supportsPaging = true; + readonly rememberLastMaxCount = true; + maxCount: number | undefined = this.view.getNodeLastMaxCount(this); constructor(view: ViewWithFiles, parent: ViewNode, public readonly record: GitReflogRecord) { super(GitUri.fromRepoPath(record.repoPath), view, parent); @@ -37,7 +38,7 @@ export class ReflogRecordNode extends ViewNode implements Pageabl ]; if (log.truncated) { - children.push(new ShowMoreNode(this.view, this, 'Commits', children[children.length - 1])); + children.push(new ShowMoreNode(this.view, this, 'Commits', log.maxCount, children[children.length - 1])); } return children; } diff --git a/src/views/nodes/resultsCommitsNode.ts b/src/views/nodes/resultsCommitsNode.ts index 2f64052..a4d97e2 100644 --- a/src/views/nodes/resultsCommitsNode.ts +++ b/src/views/nodes/resultsCommitsNode.ts @@ -15,11 +15,9 @@ export interface CommitsQueryResults { } export class ResultsCommitsNode extends ViewNode implements PageableViewNode { - 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'); + readonly supportsPaging = true; + readonly rememberLastMaxCount = true; + maxCount: number | undefined = this.view.getNodeLastMaxCount(this); constructor( view: ViewWithFiles, @@ -58,7 +56,9 @@ export class ResultsCommitsNode extends ViewNode implements Pagea ]; if (log.truncated) { - children.push(new ShowMoreNode(this.view, this, 'Results', children[children.length - 1], this.maxCount)); + children.push( + new ShowMoreNode(this.view, this, 'Results', log.maxCount, children[children.length - 1], this.maxCount) + ); } return children; diff --git a/src/views/nodes/tagNode.ts b/src/views/nodes/tagNode.ts index 6aeab66..ccaf235 100644 --- a/src/views/nodes/tagNode.ts +++ b/src/views/nodes/tagNode.ts @@ -11,8 +11,9 @@ import { getBranchesAndTagTipsFn, insertDateMarkers } from './helpers'; import { PageableViewNode, ResourceType, ViewNode, ViewRefNode } from './viewNode'; export class TagNode extends ViewRefNode implements PageableViewNode { - readonly supportsPaging: boolean = true; - maxCount: number | undefined; + readonly supportsPaging = true; + readonly rememberLastMaxCount = true; + maxCount: number | undefined = this.view.getNodeLastMaxCount(this); constructor(uri: GitUri, view: RepositoriesView, parent: ViewNode, public readonly tag: GitTag) { super(uri, view, parent); @@ -49,7 +50,7 @@ export class TagNode extends ViewRefNode implements PageableVi ]; if (log.truncated) { - children.push(new ShowMoreNode(this.view, this, 'Commits', children[children.length - 1])); + children.push(new ShowMoreNode(this.view, this, 'Commits', log.maxCount, children[children.length - 1])); } return children; } diff --git a/src/views/nodes/viewNode.ts b/src/views/nodes/viewNode.ts index fea675d..f3b5118 100644 --- a/src/views/nodes/viewNode.ts +++ b/src/views/nodes/viewNode.ts @@ -112,23 +112,20 @@ export abstract class ViewRefFileNode extends ViewRef } } +export function nodeSupportsConditionalDismissal(node: ViewNode): node is ViewNode & { canDismiss(): boolean } { + return typeof (node as ViewNode & { canDismiss(): boolean }).canDismiss === 'function'; +} + export interface PageableViewNode { + readonly id?: string; readonly supportsPaging: boolean; + readonly rememberLastMaxCount?: boolean; maxCount: number | undefined; } - -export function isPageable(node: ViewNode): node is ViewNode & PageableViewNode { +export function nodeSupportsPaging(node: ViewNode): node is ViewNode & PageableViewNode { return Functions.is(node, 'supportsPaging', true); } -interface AutoRefreshableView { - autoRefresh: boolean; - onDidChangeAutoRefresh: Event; -} -export function supportsAutoRefresh(view: View): view is View & AutoRefreshableView { - return Functions.is(view, 'onDidChangeAutoRefresh'); -} - export abstract class SubscribeableViewNode extends ViewNode { protected _disposable: Disposable; protected _subscription: Promise | undefined; @@ -141,7 +138,7 @@ export abstract class SubscribeableViewNode extends V this.view.onDidChangeNodeState(this.onNodeStateChanged, this) ]; - if (supportsAutoRefresh(this.view)) { + if (viewSupportsAutoRefresh(this.view)) { disposables.push(this.view.onDidChangeAutoRefresh(this.onAutoRefreshChanged, this)); } @@ -200,15 +197,15 @@ export abstract class SubscribeableViewNode extends V if (e.element === this) { this._state = e.state; if (this.onStateChanged !== undefined) { - this.onStateChanged(e.state); - } + this.onStateChanged(e.state); + } } else if (e.element === this.parent) { if (this.onParentStateChanged !== undefined) { - this.onParentStateChanged(e.state); + this.onParentStateChanged(e.state); + } } } - } @debug() protected onVisibilityChanged(e: TreeViewVisibilityChangeEvent) { @@ -222,7 +219,11 @@ export abstract class SubscribeableViewNode extends V @debug() async ensureSubscription() { // We only need to subscribe if we are visible and if auto-refresh enabled (when supported) - if (!this.canSubscribe || !this.view.visible || (supportsAutoRefresh(this.view) && !this.view.autoRefresh)) { + if ( + !this.canSubscribe || + !this.view.visible || + (viewSupportsAutoRefresh(this.view) && !this.view.autoRefresh) + ) { await this.unsubscribe(); return; @@ -236,8 +237,12 @@ export abstract class SubscribeableViewNode extends V } } -export function nodeSupportsConditionalDismissal(node: ViewNode): node is ViewNode & { canDismiss(): boolean } { - return typeof (node as ViewNode & { canDismiss(): boolean }).canDismiss === 'function'; +interface AutoRefreshableView { + autoRefresh: boolean; + onDidChangeAutoRefresh: Event; +} +export function viewSupportsAutoRefresh(view: View): view is View & AutoRefreshableView { + return Functions.is(view, 'onDidChangeAutoRefresh'); } export function viewSupportsNodeDismissal(view: View): view is View & { dismissNode(node: ViewNode): void } { diff --git a/src/views/viewBase.ts b/src/views/viewBase.ts index 8c4b908..de2cb21 100644 --- a/src/views/viewBase.ts +++ b/src/views/viewBase.ts @@ -23,10 +23,9 @@ import { CompareView } from './compareView'; import { FileHistoryView } from './fileHistoryView'; import { LineHistoryView } from './lineHistoryView'; import { ViewNode } from './nodes'; -import { isPageable } from './nodes/viewNode'; +import { nodeSupportsPaging, PageableViewNode } from './nodes/viewNode'; import { RepositoriesView } from './repositoriesView'; import { SearchView } from './searchView'; -import { RefreshNodeCommandArgs } from './viewCommands'; export type View = RepositoriesView | FileHistoryView | LineHistoryView | CompareView | SearchView; export type ViewWithFiles = RepositoriesView | CompareView | SearchView; @@ -52,6 +51,7 @@ export abstract class ViewBase> implements TreeData } protected _disposable: Disposable | undefined; + private readonly _lastMaxCounts = new Map(); protected _root: TRoot | undefined; protected _tree: TreeView | undefined; @@ -128,6 +128,11 @@ export abstract class ViewBase> implements TreeData } protected onElementCollapsed(e: TreeViewExpansionEvent) { + // Clear any last max count if the node was collapsed + if (nodeSupportsPaging(e.element)) { + this.resetNodeLastMaxCount(e.element); + } + this._onDidChangeNodeState.fire({ ...e, state: TreeItemCollapsibleState.Collapsed }); } @@ -161,25 +166,10 @@ export abstract class ViewBase> implements TreeData @debug({ args: { 0: (n: ViewNode) => n.toString() } }) - async refreshNode(node: ViewNode, reset: boolean = false, args?: RefreshNodeCommandArgs) { - if (args !== undefined) { - if (isPageable(node)) { - if (args.maxCount === undefined || args.maxCount === 0) { - node.maxCount = args.maxCount; - } - else { - node.maxCount = (node.maxCount || args.maxCount) + args.maxCount; - } - - if (args.previousNode !== undefined) { - void (await this.reveal(args.previousNode, { select: true })); - } - } - } - + async refreshNode(node: ViewNode, reset: boolean = false) { if (node.refresh !== undefined) { - const cancel = await node.refresh(reset); - if (cancel === true) return; + const cancel = await node.refresh(reset); + if (cancel === true) return; } this.triggerNodeChange(node); @@ -237,6 +227,51 @@ export abstract class ViewBase> implements TreeData } @debug({ + args: { 0: (n: ViewNode) => n.toString() } + }) + getNodeLastMaxCount(node: PageableViewNode) { + return node.id === undefined ? undefined : this._lastMaxCounts.get(node.id); + } + + @debug({ + args: { 0: (n: ViewNode) => n.toString() } + }) + resetNodeLastMaxCount(node: PageableViewNode) { + if (node.id === undefined || !node.rememberLastMaxCount) return; + + this._lastMaxCounts.delete(node.id); + } + + @debug({ + args: { + 0: (n: ViewNode & PageableViewNode) => n.toString(), + 3: (n?: ViewNode) => (n === undefined ? '' : n.toString()) + } + }) + async showMoreNodeChildren( + node: ViewNode & PageableViewNode, + maxCount: number | undefined, + previousNode?: ViewNode + ) { + if (maxCount === undefined || maxCount === 0) { + node.maxCount = maxCount; + } + else { + node.maxCount = (node.maxCount || maxCount) + maxCount; + } + + if (node.rememberLastMaxCount) { + this._lastMaxCounts.set(node.id!, node.maxCount); + } + + if (previousNode !== undefined) { + void (await this.reveal(previousNode, { select: true })); + } + + return this.refreshNode(node); + } + + @debug({ args: { 0: (n: ViewNode) => (n != null ? n.toString() : '') } }) triggerNodeChange(node?: ViewNode) { diff --git a/src/views/viewCommands.ts b/src/views/viewCommands.ts index be8bb9c..5b64fe2 100644 --- a/src/views/viewCommands.ts +++ b/src/views/viewCommands.ts @@ -27,6 +27,7 @@ import { FileHistoryNode, FolderNode, LineHistoryNode, + nodeSupportsPaging, RemoteNode, RepositoryNode, ResultsFileNode, @@ -42,11 +43,6 @@ import { import { Strings } from '../system/string'; import { PagerNode } from './nodes/common'; -export interface RefreshNodeCommandArgs { - maxCount?: number; - previousNode?: ViewNode; -} - interface CompareSelectedInfo { ref: string; repoPath: string | undefined; @@ -61,8 +57,14 @@ export class ViewCommands implements Disposable { constructor() { commands.registerCommand( 'gitlens.views.refreshNode', - (node: ViewNode, reset?: boolean, args?: RefreshNodeCommandArgs) => - node.view.refreshNode(node, reset === undefined ? true : reset, args), + (node: ViewNode, reset?: boolean) => { + if (reset === undefined && nodeSupportsPaging(node)) { + node.maxCount = undefined; + node.view.resetNodeLastMaxCount(node); + } + + return node.view.refreshNode(node, reset === undefined ? true : reset); + }, this ); commands.registerCommand( @@ -76,6 +78,7 @@ export class ViewCommands implements Disposable { this ); commands.registerCommand('gitlens.views.executeNodeCallback', (fn: () => Promise) => fn(), this); + commands.registerCommand('gitlens.views.showMoreChildren', (node: PagerNode) => node.showMore(), this); commands.registerCommand('gitlens.views.showAllChildren', (node: PagerNode) => node.showAll(), this); commands.registerCommand('gitlens.views.fetch', this.fetch, this);