'use strict'; import { CancellationToken, commands, ConfigurationChangeEvent, ProgressLocation, TreeItem, TreeItemCollapsibleState, window, } from 'vscode'; import { configuration, StashesViewConfig, ViewFilesLayout } from '../configuration'; import { Container } from '../container'; import { GitReference, GitStashReference, Repository, RepositoryChange, RepositoryChangeEvent } from '../git/git'; import { GitUri } from '../git/gitUri'; import { ContextValues, MessageNode, RepositoryNode, StashesNode, StashNode, SubscribeableViewNode, unknownGitUri, ViewNode, } from './nodes'; import { debug, gate } from '../system'; import { ViewBase } from './viewBase'; export class StashesRepositoryNode extends SubscribeableViewNode { protected splatted = true; private child: StashesNode | undefined; constructor(uri: GitUri, view: StashesView, parent: ViewNode, public readonly repo: Repository, splatted: boolean) { super(uri, view, parent); this.splatted = splatted; } get id(): string { return RepositoryNode.getId(this.repo.path); } async getChildren(): Promise { if (this.child == null) { this.child = new StashesNode(this.uri, this.view, this, this.repo); } return this.child.getChildren(); } getTreeItem(): TreeItem { this.splatted = false; const item = new TreeItem( this.repo.formattedName ?? this.uri.repoPath ?? '', TreeItemCollapsibleState.Expanded, ); item.contextValue = ContextValues.RepositoryFolder; return item; } async getSplattedChild() { if (this.child == null) { await this.getChildren(); } return this.child; } @gate() @debug() async refresh(reset: boolean = false) { await this.child?.triggerChange(reset); await this.ensureSubscription(); } @debug() protected subscribe() { return this.repo.onDidChange(this.onRepositoryChanged, this); } @debug({ args: { 0: (e: RepositoryChangeEvent) => `{ repository: ${e.repository?.name ?? ''}, changes: ${e.changes.join()} }`, }, }) private onRepositoryChanged(e: RepositoryChangeEvent) { if (e.changed(RepositoryChange.Closed)) { this.dispose(); void this.parent?.triggerChange(true); return; } if (e.changed(RepositoryChange.Config) || e.changed(RepositoryChange.Stash)) { void this.triggerChange(true); } } } export class StashesViewNode extends ViewNode { protected splatted = true; private children: StashesRepositoryNode[] | undefined; constructor(view: StashesView) { super(unknownGitUri, view); } async getChildren(): Promise { if (this.children == null) { const repositories = await Container.git.getOrderedRepositories(); if (repositories.length === 0) return [new MessageNode(this.view, this, 'No stashes could be found.')]; const splat = repositories.length === 1; this.children = repositories.map( r => new StashesRepositoryNode(GitUri.fromRepoPath(r.path), this.view, this, r, splat), ); } if (this.children.length === 1) { const [child] = this.children; const stash = await child.repo.getStash(); this.view.title = `Stashes (${stash?.commits.size ?? 0})`; return child.getChildren(); } return this.children; } getTreeItem(): TreeItem { const item = new TreeItem('Stashes', TreeItemCollapsibleState.Expanded); return item; } async getSplattedChild() { if (this.children == null) { await this.getChildren(); } return this.children?.length === 1 ? this.children[0] : undefined; } @gate() @debug() refresh(reset: boolean = false) { if (reset && this.children != null) { for (const child of this.children) { child.dispose(); } this.children = undefined; } } } export class StashesView extends ViewBase { protected readonly configKey = 'stashes'; constructor() { super('gitlens.views.stashes', 'Stashes'); } getRoot() { return new StashesViewNode(this); } protected registerCommands() { void Container.viewCommands; commands.registerCommand( this.getQualifiedCommand('copy'), () => commands.executeCommand('gitlens.views.copy', this.selection), this, ); commands.registerCommand(this.getQualifiedCommand('refresh'), () => this.refresh(true), this); commands.registerCommand( this.getQualifiedCommand('setFilesLayoutToAuto'), () => this.setFilesLayout(ViewFilesLayout.Auto), this, ); commands.registerCommand( this.getQualifiedCommand('setFilesLayoutToList'), () => this.setFilesLayout(ViewFilesLayout.List), this, ); commands.registerCommand( this.getQualifiedCommand('setFilesLayoutToTree'), () => this.setFilesLayout(ViewFilesLayout.Tree), this, ); } protected filterConfigurationChanged(e: ConfigurationChangeEvent) { const changed = super.filterConfigurationChanged(e); if ( !changed && !configuration.changed(e, 'defaultDateFormat') && !configuration.changed(e, 'defaultDateSource') && !configuration.changed(e, 'defaultDateStyle') && !configuration.changed(e, 'defaultGravatarsStyle') ) { return false; } return true; } findStash(stash: GitStashReference, token?: CancellationToken) { const repoNodeId = RepositoryNode.getId(stash.repoPath); return this.findNode(StashNode.getId(stash.repoPath, stash.ref), { maxDepth: 2, canTraverse: n => { if (n instanceof StashesViewNode) return true; if (n instanceof StashesRepositoryNode) { return n.id.startsWith(repoNodeId); } return false; }, token: token, }); } @gate(() => '') async revealStash( stash: GitStashReference, options?: { select?: boolean; focus?: boolean; expand?: boolean | number; }, ) { return window.withProgress( { location: ProgressLocation.Notification, title: `Revealing ${GitReference.toString(stash, { icon: false })} in the side bar...`, cancellable: true, }, async (progress, token) => { const node = await this.findStash(stash, token); if (node == null) return undefined; await this.ensureRevealNode(node, options); return node; }, ); } private setFilesLayout(layout: ViewFilesLayout) { return configuration.updateEffective('views', this.configKey, 'files', 'layout', layout); } }