diff --git a/src/commands/base.ts b/src/commands/base.ts index de0f1e7e5e421..917555760351d 100644 --- a/src/commands/base.ts +++ b/src/commands/base.ts @@ -24,7 +24,8 @@ import { isTag } from '../git/models/tag'; import { CloudWorkspace, LocalWorkspace } from '../plus/workspaces/models'; import { registerCommand } from '../system/command'; import { sequentialize } from '../system/function'; -import { ViewNode, ViewRefFileNode, ViewRefNode } from '../views/nodes/viewNode'; +import { ViewNode } from '../views/nodes/abstract/viewNode'; +import { ViewRefFileNode, ViewRefNode } from '../views/nodes/abstract/viewRefNode'; export function getCommandUri(uri?: Uri, editor?: TextEditor): Uri | undefined { // Always use the editor.uri (if we have one), so we are correct for a split diff diff --git a/src/extension.ts b/src/extension.ts index af01aebb2474c..c6553c663facc 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -26,7 +26,7 @@ import { flatten } from './system/object'; import { Stopwatch } from './system/stopwatch'; import { Storage } from './system/storage'; import { compare, fromString, satisfies } from './system/version'; -import { isViewNode } from './views/nodes/viewNode'; +import { isViewNode } from './views/nodes/abstract/viewNode'; import './commands'; export async function activate(context: ExtensionContext): Promise { diff --git a/src/plus/webviews/graph/graphWebview.ts b/src/plus/webviews/graph/graphWebview.ts index 00aebb70b01de..f094749f89cc5 100644 --- a/src/plus/webviews/graph/graphWebview.ts +++ b/src/plus/webviews/graph/graphWebview.ts @@ -80,7 +80,7 @@ import { updateRecordValue } from '../../../system/object'; import { getSettledValue } from '../../../system/promise'; import { isDarkTheme, isLightTheme } from '../../../system/utils'; import { isWebviewItemContext, isWebviewItemGroupContext, serializeWebviewItemContext } from '../../../system/webview'; -import { RepositoryFolderNode } from '../../../views/nodes/viewNode'; +import { RepositoryFolderNode } from '../../../views/nodes/abstract/repositoryFolderNode'; import type { IpcMessage, IpcNotificationType } from '../../../webviews/protocol'; import { onIpc } from '../../../webviews/protocol'; import type { WebviewController, WebviewProvider } from '../../../webviews/webviewController'; diff --git a/src/plus/webviews/timeline/timelineWebview.ts b/src/plus/webviews/timeline/timelineWebview.ts index 7e1d7df5d71e9..c972e257e01a4 100644 --- a/src/plus/webviews/timeline/timelineWebview.ts +++ b/src/plus/webviews/timeline/timelineWebview.ts @@ -17,8 +17,8 @@ import type { Deferrable } from '../../../system/function'; import { debounce } from '../../../system/function'; import { filter } from '../../../system/iterable'; import { hasVisibleTextEditor, isTextEditor } from '../../../system/utils'; -import type { ViewFileNode } from '../../../views/nodes/viewNode'; -import { isViewFileNode } from '../../../views/nodes/viewNode'; +import type { ViewFileNode } from '../../../views/nodes/abstract/viewFileNode'; +import { isViewFileNode } from '../../../views/nodes/abstract/viewFileNode'; import type { IpcMessage } from '../../../webviews/protocol'; import { onIpc } from '../../../webviews/protocol'; import type { WebviewController, WebviewProvider } from '../../../webviews/webviewController'; diff --git a/src/system/decorators/resolver.ts b/src/system/decorators/resolver.ts index 21969e851992e..7f9a7e954da1d 100644 --- a/src/system/decorators/resolver.ts +++ b/src/system/decorators/resolver.ts @@ -3,7 +3,7 @@ import { isContainer } from '../../container'; import { isBranch } from '../../git/models/branch'; import { isCommit } from '../../git/models/commit'; import { isTag } from '../../git/models/tag'; -import { isViewNode } from '../../views/nodes/viewNode'; +import { isViewNode } from '../../views/nodes/abstract/viewNode'; function replacer(key: string, value: any): any { if (key === '') return value; diff --git a/src/views/branchesView.ts b/src/views/branchesView.ts index a0de753d9a4c3..7d8ae26ddee3c 100644 --- a/src/views/branchesView.ts +++ b/src/views/branchesView.ts @@ -13,11 +13,12 @@ import { RepositoryChange, RepositoryChangeComparisonMode } from '../git/models/ import { executeCommand } from '../system/command'; import { configuration } from '../system/configuration'; import { gate } from '../system/decorators/gate'; +import { RepositoriesSubscribeableNode } from './nodes/abstract/repositoriesSubscribeableNode'; +import { RepositoryFolderNode } from './nodes/abstract/repositoryFolderNode'; +import type { ViewNode } from './nodes/abstract/viewNode'; import { BranchesNode } from './nodes/branchesNode'; import { BranchNode } from './nodes/branchNode'; import { BranchOrTagFolderNode } from './nodes/branchOrTagFolderNode'; -import type { ViewNode } from './nodes/viewNode'; -import { RepositoriesSubscribeableNode, RepositoryFolderNode } from './nodes/viewNode'; import { ViewBase } from './viewBase'; import { registerViewCommand } from './viewCommands'; diff --git a/src/views/commitsView.ts b/src/views/commitsView.ts index cfc2049af925c..def4a68b1775a 100644 --- a/src/views/commitsView.ts +++ b/src/views/commitsView.ts @@ -21,11 +21,12 @@ import { gate } from '../system/decorators/gate'; import { debug } from '../system/decorators/log'; import { disposableInterval } from '../system/function'; import type { UsageChangeEvent } from '../telemetry/usageTracker'; +import { RepositoriesSubscribeableNode } from './nodes/abstract/repositoriesSubscribeableNode'; +import { RepositoryFolderNode } from './nodes/abstract/repositoryFolderNode'; +import type { ViewNode } from './nodes/abstract/viewNode'; import { BranchNode } from './nodes/branchNode'; import { BranchTrackingStatusNode } from './nodes/branchTrackingStatusNode'; import { CommandMessageNode } from './nodes/common'; -import type { ViewNode } from './nodes/viewNode'; -import { RepositoriesSubscribeableNode, RepositoryFolderNode } from './nodes/viewNode'; import { ViewBase } from './viewBase'; import { registerViewCommand } from './viewCommands'; diff --git a/src/views/contributorsView.ts b/src/views/contributorsView.ts index 9a906f65842b4..8626e025de842 100644 --- a/src/views/contributorsView.ts +++ b/src/views/contributorsView.ts @@ -12,10 +12,11 @@ import { executeCommand } from '../system/command'; import { configuration } from '../system/configuration'; import { gate } from '../system/decorators/gate'; import { debug } from '../system/decorators/log'; +import { RepositoriesSubscribeableNode } from './nodes/abstract/repositoriesSubscribeableNode'; +import { RepositoryFolderNode } from './nodes/abstract/repositoryFolderNode'; +import type { ViewNode } from './nodes/abstract/viewNode'; import { ContributorNode } from './nodes/contributorNode'; import { ContributorsNode } from './nodes/contributorsNode'; -import type { ViewNode } from './nodes/viewNode'; -import { RepositoriesSubscribeableNode, RepositoryFolderNode } from './nodes/viewNode'; import { ViewBase } from './viewBase'; import { registerViewCommand } from './viewCommands'; diff --git a/src/views/nodes/UncommittedFileNode.ts b/src/views/nodes/UncommittedFileNode.ts index 71a9cc2ee34ea..2e534ccce5568 100644 --- a/src/views/nodes/UncommittedFileNode.ts +++ b/src/views/nodes/UncommittedFileNode.ts @@ -8,9 +8,10 @@ import type { GitFile } from '../../git/models/file'; import { getGitFileStatusIcon } from '../../git/models/file'; import { dirname, joinPaths } from '../../system/path'; import type { ViewsWithCommits } from '../viewBase'; +import { ViewFileNode } from './abstract/viewFileNode'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues } from './abstract/viewNode'; import type { FileNode } from './folderNode'; -import type { ViewNode } from './viewNode'; -import { ContextValues, ViewFileNode } from './viewNode'; export class UncommittedFileNode extends ViewFileNode<'uncommitted-file', ViewsWithCommits> implements FileNode { constructor(view: ViewsWithCommits, parent: ViewNode, repoPath: string, file: GitFile) { diff --git a/src/views/nodes/UncommittedFilesNode.ts b/src/views/nodes/UncommittedFilesNode.ts index 58fd67b9fb6ca..d3d3e0bc3376e 100644 --- a/src/views/nodes/UncommittedFilesNode.ts +++ b/src/views/nodes/UncommittedFilesNode.ts @@ -7,10 +7,10 @@ import { groupBy, makeHierarchical } from '../../system/array'; import { flatMap } from '../../system/iterable'; import { joinPaths, normalizePath } from '../../system/path'; import type { ViewsWithWorkingTree } from '../viewBase'; +import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode'; import type { FileNode } from './folderNode'; import { FolderNode } from './folderNode'; import { UncommittedFileNode } from './UncommittedFileNode'; -import { ContextValues, getViewNodeId, ViewNode } from './viewNode'; export class UncommittedFilesNode extends ViewNode<'uncommitted-files', ViewsWithWorkingTree> { constructor( diff --git a/src/views/nodes/abstract/cacheableChildrenViewNode.ts b/src/views/nodes/abstract/cacheableChildrenViewNode.ts new file mode 100644 index 0000000000000..824d0856f6521 --- /dev/null +++ b/src/views/nodes/abstract/cacheableChildrenViewNode.ts @@ -0,0 +1,36 @@ +import type { TreeViewNodeTypes } from '../../../constants'; +import { debug } from '../../../system/decorators/log'; +import type { View } from '../../viewBase'; +import { disposeChildren } from '../../viewBase'; +import { ViewNode } from './viewNode'; + +export abstract class CacheableChildrenViewNode< + Type extends TreeViewNodeTypes = TreeViewNodeTypes, + TView extends View = View, + TChild extends ViewNode = ViewNode, + State extends object = any, +> extends ViewNode { + private _children: TChild[] | undefined; + protected get children(): TChild[] | undefined { + return this._children; + } + protected set children(value: TChild[] | undefined) { + if (this._children === value) return; + + disposeChildren(this._children, value); + this._children = value; + } + + @debug() + override dispose() { + super.dispose(); + this.children = undefined; + } + + @debug() + override refresh(reset: boolean = false) { + if (reset) { + this.children = undefined; + } + } +} diff --git a/src/views/nodes/abstract/repositoriesSubscribeableNode.ts b/src/views/nodes/abstract/repositoriesSubscribeableNode.ts new file mode 100644 index 0000000000000..3f4420a284334 --- /dev/null +++ b/src/views/nodes/abstract/repositoriesSubscribeableNode.ts @@ -0,0 +1,51 @@ +import { Disposable } from 'vscode'; +import type { RepositoriesChangeEvent } from '../../../git/gitProviderService'; +import { unknownGitUri } from '../../../git/gitUri'; +import type { SubscriptionChangeEvent } from '../../../plus/subscription/subscriptionService'; +import { debug } from '../../../system/decorators/log'; +import { weakEvent } from '../../../system/event'; +import { szudzikPairing } from '../../../system/function'; +import type { View } from '../../viewBase'; +import { SubscribeableViewNode } from './subscribeableViewNode'; +import type { ViewNode } from './viewNode'; + +export abstract class RepositoriesSubscribeableNode< + TView extends View = View, + TChild extends ViewNode = ViewNode, +> extends SubscribeableViewNode<'repositories', TView, TChild> { + protected override splatted = true; + + constructor(view: TView) { + super('repositories', unknownGitUri, view); + } + + override async getSplattedChild() { + if (this.children == null) { + await this.getChildren(); + } + + return this.children?.length === 1 ? this.children[0] : undefined; + } + + protected override etag(): number { + return szudzikPairing(this.view.container.git.etag, this.view.container.subscription.etag); + } + + @debug() + protected subscribe(): Disposable | Promise { + return Disposable.from( + weakEvent(this.view.container.git.onDidChangeRepositories, this.onRepositoriesChanged, this), + weakEvent(this.view.container.subscription.onDidChange, this.onSubscriptionChanged, this), + ); + } + + private onRepositoriesChanged(_e: RepositoriesChangeEvent) { + void this.triggerChange(true); + } + + private onSubscriptionChanged(e: SubscriptionChangeEvent) { + if (e.current.plan !== e.previous.plan) { + void this.triggerChange(true); + } + } +} diff --git a/src/views/nodes/abstract/repositoryFolderNode.ts b/src/views/nodes/abstract/repositoryFolderNode.ts new file mode 100644 index 0000000000000..f45c7f70538d4 --- /dev/null +++ b/src/views/nodes/abstract/repositoryFolderNode.ts @@ -0,0 +1,213 @@ +import type { Disposable } from 'vscode'; +import { MarkdownString, TreeItem, TreeItemCollapsibleState } from 'vscode'; +import { GlyphChars } from '../../../constants'; +import type { GitUri } from '../../../git/gitUri'; +import { GitRemote } from '../../../git/models/remote'; +import type { RepositoryChangeEvent } from '../../../git/models/repository'; +import { Repository, RepositoryChange, RepositoryChangeComparisonMode } from '../../../git/models/repository'; +import { gate } from '../../../system/decorators/gate'; +import { debug, log } from '../../../system/decorators/log'; +import { weakEvent } from '../../../system/event'; +import { pad } from '../../../system/string'; +import type { View } from '../../viewBase'; +import { SubscribeableViewNode } from './subscribeableViewNode'; +import type { ViewNode } from './viewNode'; +import { ContextValues, getViewNodeId } from './viewNode'; + +export abstract class RepositoryFolderNode< + TView extends View = View, + TChild extends ViewNode = ViewNode, +> extends SubscribeableViewNode<'repo-folder', TView> { + protected override splatted = true; + + constructor( + uri: GitUri, + view: TView, + protected override readonly parent: ViewNode, + public readonly repo: Repository, + splatted: boolean, + private readonly options?: { showBranchAndLastFetched?: boolean }, + ) { + super('repo-folder', uri, view, parent); + + this.updateContext({ repository: this.repo }); + this._uniqueId = getViewNodeId(this.type, this.context); + + this.splatted = splatted; + } + + private _child: TChild | undefined; + protected get child(): TChild | undefined { + return this._child; + } + protected set child(value: TChild | undefined) { + if (this._child === value) return; + + this._child?.dispose(); + this._child = value; + } + + @debug() + override dispose() { + super.dispose(); + this.child = undefined; + } + + override get id(): string { + return this._uniqueId; + } + + override toClipboard(): string { + return this.repo.path; + } + + get repoPath(): string { + return this.repo.path; + } + + async getTreeItem(): Promise { + this.splatted = false; + + const branch = await this.repo.getBranch(); + const ahead = (branch?.state.ahead ?? 0) > 0; + const behind = (branch?.state.behind ?? 0) > 0; + + const expand = ahead || behind || this.repo.starred || this.view.container.git.isRepositoryForEditor(this.repo); + + const item = new TreeItem( + this.repo.formattedName ?? this.uri.repoPath ?? '', + expand ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.Collapsed, + ); + item.contextValue = `${ContextValues.RepositoryFolder}${this.repo.starred ? '+starred' : ''}`; + if (ahead) { + item.contextValue += '+ahead'; + } + if (behind) { + item.contextValue += '+behind'; + } + if (this.view.type === 'commits' && this.view.state.filterCommits.get(this.repo.id)?.length) { + item.contextValue += '+filtered'; + } + + if (branch != null && this.options?.showBranchAndLastFetched) { + const lastFetched = (await this.repo.getLastFetched()) ?? 0; + + const status = branch.getTrackingStatus(); + item.description = `${status ? `${status}${pad(GlyphChars.Dot, 1, 1)}` : ''}${branch.name}${ + lastFetched + ? `${pad(GlyphChars.Dot, 1, 1)}Last fetched ${Repository.formatLastFetched(lastFetched)}` + : '' + }`; + + let providerName; + if (branch.upstream != null) { + const providers = GitRemote.getHighlanderProviders( + await this.view.container.git.getRemotesWithProviders(branch.repoPath), + ); + providerName = providers?.length ? providers[0].name : undefined; + } else { + const remote = await branch.getRemote(); + providerName = remote?.provider?.name; + } + + item.tooltip = new MarkdownString( + `${this.repo.formattedName ?? this.uri.repoPath ?? ''}${ + lastFetched + ? `${pad(GlyphChars.Dash, 2, 2)}Last fetched ${Repository.formatLastFetched( + lastFetched, + false, + )}` + : '' + }${this.repo.formattedName ? `\n${this.uri.repoPath}` : ''}\n\nCurrent branch $(git-branch) ${ + branch.name + }${ + branch.upstream != null + ? ` is ${branch.getTrackingStatus({ + empty: branch.upstream.missing + ? `missing upstream $(git-branch) ${branch.upstream.name}` + : `up to date with $(git-branch) ${branch.upstream.name}${ + providerName ? ` on ${providerName}` : '' + }`, + expand: true, + icons: true, + separator: ', ', + suffix: ` $(git-branch) ${branch.upstream.name}${ + providerName ? ` on ${providerName}` : '' + }`, + })}` + : `hasn't been published to ${providerName ?? 'a remote'}` + }`, + true, + ); + } else { + item.tooltip = `${ + this.repo.formattedName ? `${this.repo.formattedName}\n${this.uri.repoPath}` : this.uri.repoPath ?? '' + }`; + } + + return item; + } + + override async getSplattedChild() { + if (this.child == null) { + await this.getChildren(); + } + + return this.child; + } + + @gate() + @debug() + override async refresh(reset: boolean = false) { + super.refresh(reset); + await this.child?.triggerChange(reset, false, this); + + await this.ensureSubscription(); + } + + @log() + async star() { + await this.repo.star(); + // void this.parent!.triggerChange(); + } + + @log() + async unstar() { + await this.repo.unstar(); + // void this.parent!.triggerChange(); + } + + @debug() + protected subscribe(): Disposable | Promise { + return weakEvent(this.repo.onDidChange, this.onRepositoryChanged, this); + } + + protected override etag(): number { + return this.repo.etag; + } + + protected abstract changed(e: RepositoryChangeEvent): boolean; + + @debug({ args: { 0: e => e.toString() } }) + private onRepositoryChanged(e: RepositoryChangeEvent) { + if (e.changed(RepositoryChange.Closed, RepositoryChangeComparisonMode.Any)) { + this.dispose(); + void this.parent?.triggerChange(true); + + return; + } + + if ( + e.changed(RepositoryChange.Opened, RepositoryChangeComparisonMode.Any) || + e.changed(RepositoryChange.Starred, RepositoryChangeComparisonMode.Any) + ) { + void this.parent?.triggerChange(true); + + return; + } + + if (this.changed(e)) { + void (this.loaded ? this : this.parent ?? this).triggerChange(true); + } + } +} diff --git a/src/views/nodes/abstract/subscribeableViewNode.ts b/src/views/nodes/abstract/subscribeableViewNode.ts new file mode 100644 index 0000000000000..03998431a95b8 --- /dev/null +++ b/src/views/nodes/abstract/subscribeableViewNode.ts @@ -0,0 +1,169 @@ +import type { TreeViewVisibilityChangeEvent } from 'vscode'; +import { Disposable } from 'vscode'; +import type { TreeViewSubscribableNodeTypes } from '../../../constants'; +import type { GitUri } from '../../../git/gitUri'; +import { gate } from '../../../system/decorators/gate'; +import { debug } from '../../../system/decorators/log'; +import { weakEvent } from '../../../system/event'; +import type { View } from '../../viewBase'; +import { CacheableChildrenViewNode } from './cacheableChildrenViewNode'; +import type { ViewNode } from './viewNode'; +import { canAutoRefreshView } from './viewNode'; + +export abstract class SubscribeableViewNode< + Type extends TreeViewSubscribableNodeTypes = TreeViewSubscribableNodeTypes, + TView extends View = View, + TChild extends ViewNode = ViewNode, + State extends object = any, +> extends CacheableChildrenViewNode { + protected disposable: Disposable; + protected subscription: Promise | undefined; + + protected loaded: boolean = false; + + constructor(type: Type, uri: GitUri, view: TView, parent?: ViewNode) { + super(type, uri, view, parent); + + const disposables = [ + weakEvent(this.view.onDidChangeVisibility, this.onVisibilityChanged, this), + // weak(this.view.onDidChangeNodeCollapsibleState, this.onNodeCollapsibleStateChanged, this), + ]; + + if (canAutoRefreshView(this.view)) { + disposables.push(weakEvent(this.view.onDidChangeAutoRefresh, this.onAutoRefreshChanged, this)); + } + + const getTreeItem = this.getTreeItem; + this.getTreeItem = function (this: SubscribeableViewNode) { + this.loaded = true; + void this.ensureSubscription(); + return getTreeItem.apply(this); + }; + + const getChildren = this.getChildren; + this.getChildren = function (this: SubscribeableViewNode) { + this.loaded = true; + void this.ensureSubscription(); + return getChildren.apply(this); + }; + + this.disposable = Disposable.from(...disposables); + } + + @debug() + override dispose() { + super.dispose(); + void this.unsubscribe(); + this.disposable?.dispose(); + } + + @gate((reset, force) => `${reset}|${force}`) + @debug() + override async triggerChange(reset: boolean = false, force: boolean = false): Promise { + if (!this.loaded || this._disposed) return; + + if (reset && !this.view.visible) { + this._pendingReset = reset; + } + await super.triggerChange(reset, force); + } + + private _canSubscribe: boolean = true; + protected get canSubscribe(): boolean { + return this._canSubscribe && !this._disposed; + } + protected set canSubscribe(value: boolean) { + if (this._canSubscribe === value) return; + + this._canSubscribe = value; + + void this.ensureSubscription(); + if (value) { + void this.triggerChange(); + } + } + + private _etag: number | undefined; + protected abstract etag(): number; + + private _pendingReset: boolean = false; + private get requiresResetOnVisible(): boolean { + let reset = this._pendingReset; + this._pendingReset = false; + + const etag = this.etag(); + if (etag !== this._etag) { + this._etag = etag; + reset = true; + } + + return reset; + } + + protected abstract subscribe(): Disposable | undefined | Promise; + + @debug() + protected async unsubscribe(): Promise { + this._etag = this.etag(); + + if (this.subscription != null) { + const subscriptionPromise = this.subscription; + this.subscription = undefined; + + (await subscriptionPromise)?.dispose(); + } + } + + @debug() + protected onAutoRefreshChanged() { + this.onVisibilityChanged({ visible: this.view.visible }); + } + + // protected onParentCollapsibleStateChanged?(state: TreeItemCollapsibleState): void; + // protected onCollapsibleStateChanged?(state: TreeItemCollapsibleState): void; + // protected collapsibleState: TreeItemCollapsibleState | undefined; + // protected onNodeCollapsibleStateChanged(e: TreeViewNodeCollapsibleStateChangeEvent) { + // if (e.element === this) { + // this.collapsibleState = e.state; + // if (this.onCollapsibleStateChanged !== undefined) { + // this.onCollapsibleStateChanged(e.state); + // } + // } else if (e.element === this.parent) { + // if (this.onParentCollapsibleStateChanged !== undefined) { + // this.onParentCollapsibleStateChanged(e.state); + // } + // } + // } + @debug() + protected onVisibilityChanged(e: TreeViewVisibilityChangeEvent) { + void this.ensureSubscription(); + + if (e.visible) { + void this.triggerChange(this.requiresResetOnVisible); + } + } + + @gate() + @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 || (canAutoRefreshView(this.view) && !this.view.autoRefresh)) { + await this.unsubscribe(); + + return; + } + + // If we already have a subscription, just kick out + if (this.subscription != null) return; + + this.subscription = Promise.resolve(this.subscribe()); + void (await this.subscription); + } + + @gate() + @debug() + async resetSubscription() { + await this.unsubscribe(); + await this.ensureSubscription(); + } +} diff --git a/src/views/nodes/abstract/viewFileNode.ts b/src/views/nodes/abstract/viewFileNode.ts new file mode 100644 index 0000000000000..8ad36597a7f0a --- /dev/null +++ b/src/views/nodes/abstract/viewFileNode.ts @@ -0,0 +1,33 @@ +import type { TreeViewFileNodeTypes } from '../../../constants'; +import type { GitUri } from '../../../git/gitUri'; +import type { GitFile } from '../../../git/models/file'; +import type { View } from '../../viewBase'; +import { ViewNode } from './viewNode'; + +export abstract class ViewFileNode< + Type extends TreeViewFileNodeTypes = TreeViewFileNodeTypes, + TView extends View = View, + State extends object = any, +> extends ViewNode { + constructor( + type: Type, + uri: GitUri, + view: TView, + public override parent: ViewNode, + public readonly file: GitFile, + ) { + super(type, uri, view, parent); + } + + get repoPath(): string { + return this.uri.repoPath!; + } + + override toString(): string { + return `${super.toString()}:${this.file.path}`; + } +} + +export function isViewFileNode(node: unknown): node is ViewFileNode { + return node instanceof ViewFileNode; +} diff --git a/src/views/nodes/abstract/viewNode.ts b/src/views/nodes/abstract/viewNode.ts new file mode 100644 index 0000000000000..f55041bedf4d5 --- /dev/null +++ b/src/views/nodes/abstract/viewNode.ts @@ -0,0 +1,408 @@ +import type { Command, Disposable, Event, TreeItem } from 'vscode'; +import type { TreeViewNodeTypes } from '../../../constants'; +import type { GitUri } from '../../../git/gitUri'; +import type { GitBranch } from '../../../git/models/branch'; +import type { GitCommit } from '../../../git/models/commit'; +import type { GitContributor } from '../../../git/models/contributor'; +import type { GitFile } from '../../../git/models/file'; +import type { GitReflogRecord } from '../../../git/models/reflog'; +import type { GitRemote } from '../../../git/models/remote'; +import type { Repository } from '../../../git/models/repository'; +import type { GitTag } from '../../../git/models/tag'; +import type { GitWorktree } from '../../../git/models/worktree'; +import type { + CloudWorkspace, + CloudWorkspaceRepositoryDescriptor, + LocalWorkspace, + LocalWorkspaceRepositoryDescriptor, +} from '../../../plus/workspaces/models'; +import { gate } from '../../../system/decorators/gate'; +import { debug, logName } from '../../../system/decorators/log'; +import { is as isA } from '../../../system/function'; +import { getLoggableName } from '../../../system/logger'; +import type { View } from '../../viewBase'; +import type { BranchNode } from '../branchNode'; +import type { BranchTrackingStatus } from '../branchTrackingStatusNode'; +import type { CommitFileNode } from '../commitFileNode'; +import type { CommitNode } from '../commitNode'; +import type { CompareBranchNode } from '../compareBranchNode'; +import type { CompareResultsNode } from '../compareResultsNode'; +import type { FileRevisionAsCommitNode } from '../fileRevisionAsCommitNode'; +import type { FolderNode } from '../folderNode'; +import type { LineHistoryTrackerNode } from '../lineHistoryTrackerNode'; +import type { MergeConflictFileNode } from '../mergeConflictFileNode'; +import type { RepositoryNode } from '../repositoryNode'; +import type { ResultsCommitsNode } from '../resultsCommitsNode'; +import type { ResultsFileNode } from '../resultsFileNode'; +import type { StashFileNode } from '../stashFileNode'; +import type { StashNode } from '../stashNode'; +import type { StatusFileNode } from '../statusFileNode'; +import type { TagNode } from '../tagNode'; +import type { UncommittedFileNode } from '../UncommittedFileNode'; +import type { RepositoryFolderNode } from './repositoryFolderNode'; + +export const enum ContextValues { + ActiveFileHistory = 'gitlens:history:active:file', + ActiveLineHistory = 'gitlens:history:active:line', + AutolinkedItems = 'gitlens:autolinked:items', + AutolinkedIssue = 'gitlens:autolinked:issue', + AutolinkedItem = 'gitlens:autolinked:item', + Branch = 'gitlens:branch', + Branches = 'gitlens:branches', + BranchStatusAheadOfUpstream = 'gitlens:status-branch:upstream:ahead', + BranchStatusBehindUpstream = 'gitlens:status-branch:upstream:behind', + BranchStatusNoUpstream = 'gitlens:status-branch:upstream:none', + BranchStatusSameAsUpstream = 'gitlens:status-branch:upstream:same', + BranchStatusFiles = 'gitlens:status-branch:files', + Commit = 'gitlens:commit', + Commits = 'gitlens:commits', + Compare = 'gitlens:compare', + CompareBranch = 'gitlens:compare:branch', + ComparePicker = 'gitlens:compare:picker', + ComparePickerWithRef = 'gitlens:compare:picker:ref', + CompareResults = 'gitlens:compare:results', + CompareResultsCommits = 'gitlens:compare:results:commits', + Contributor = 'gitlens:contributor', + Contributors = 'gitlens:contributors', + DateMarker = 'gitlens:date-marker', + File = 'gitlens:file', + FileHistory = 'gitlens:history:file', + Folder = 'gitlens:folder', + LineHistory = 'gitlens:history:line', + Merge = 'gitlens:merge', + MergeConflictCurrentChanges = 'gitlens:merge-conflict:current', + MergeConflictIncomingChanges = 'gitlens:merge-conflict:incoming', + Message = 'gitlens:message', + MessageSignIn = 'gitlens:message:signin', + Pager = 'gitlens:pager', + PullRequest = 'gitlens:pullrequest', + Rebase = 'gitlens:rebase', + Reflog = 'gitlens:reflog', + ReflogRecord = 'gitlens:reflog-record', + Remote = 'gitlens:remote', + Remotes = 'gitlens:remotes', + Repositories = 'gitlens:repositories', + Repository = 'gitlens:repository', + RepositoryFolder = 'gitlens:repo-folder', + ResultsFile = 'gitlens:file:results', + ResultsFiles = 'gitlens:results:files', + SearchAndCompare = 'gitlens:searchAndCompare', + SearchResults = 'gitlens:search:results', + SearchResultsCommits = 'gitlens:search:results:commits', + Stash = 'gitlens:stash', + Stashes = 'gitlens:stashes', + StatusFileCommits = 'gitlens:status:file:commits', + StatusFiles = 'gitlens:status:files', + StatusAheadOfUpstream = 'gitlens:status:upstream:ahead', + StatusBehindUpstream = 'gitlens:status:upstream:behind', + StatusNoUpstream = 'gitlens:status:upstream:none', + StatusSameAsUpstream = 'gitlens:status:upstream:same', + Tag = 'gitlens:tag', + Tags = 'gitlens:tags', + UncommittedFiles = 'gitlens:uncommitted:files', + Workspace = 'gitlens:workspace', + WorkspaceMissingRepository = 'gitlens:workspaceMissingRepository', + Workspaces = 'gitlens:workspaces', + Worktree = 'gitlens:worktree', + Worktrees = 'gitlens:worktrees', +} + +export interface AmbientContext { + readonly autolinksId?: string; + readonly branch?: GitBranch; + readonly branchStatus?: BranchTrackingStatus; + readonly branchStatusUpstreamType?: 'ahead' | 'behind' | 'same' | 'none'; + readonly commit?: GitCommit; + readonly comparisonId?: string; + readonly comparisonFiltered?: boolean; + readonly contributor?: GitContributor; + readonly file?: GitFile; + readonly reflog?: GitReflogRecord; + readonly remote?: GitRemote; + readonly repository?: Repository; + readonly root?: boolean; + readonly searchId?: string; + readonly status?: 'merging' | 'rebasing'; + readonly storedComparisonId?: string; + readonly tag?: GitTag; + readonly workspace?: CloudWorkspace | LocalWorkspace; + readonly wsRepositoryDescriptor?: CloudWorkspaceRepositoryDescriptor | LocalWorkspaceRepositoryDescriptor; + readonly worktree?: GitWorktree; +} + +export function getViewNodeId(type: string, context: AmbientContext): string { + let uniqueness = ''; + if (context.root) { + uniqueness += '/root'; + } + if (context.workspace != null) { + uniqueness += `/ws/${context.workspace.id}`; + } + if (context.wsRepositoryDescriptor != null) { + uniqueness += `/wsrepo/${context.wsRepositoryDescriptor.id}`; + } + if (context.repository != null) { + uniqueness += `/repo/${context.repository.id}`; + } + if (context.worktree != null) { + uniqueness += `/worktree/${context.worktree.uri.path}`; + } + if (context.remote != null) { + uniqueness += `/remote/${context.remote.name}`; + } + if (context.tag != null) { + uniqueness += `/tag/${context.tag.id}`; + } + if (context.branch != null) { + uniqueness += `/branch/${context.branch.id}`; + } + if (context.branchStatus != null) { + uniqueness += `/branch-status/${context.branchStatus.upstream ?? '-'}`; + } + if (context.branchStatusUpstreamType != null) { + uniqueness += `/branch-status-direction/${context.branchStatusUpstreamType}`; + } + if (context.status != null) { + uniqueness += `/status/${context.status}`; + } + if (context.reflog != null) { + uniqueness += `/reflog/${context.reflog.sha}+${context.reflog.selector}+${context.reflog.command}+${ + context.reflog.commandArgs ?? '' + }+${context.reflog.date.getTime()}`; + } + if (context.contributor != null) { + uniqueness += `/contributor/${ + context.contributor.id ?? + `${context.contributor.username}+${context.contributor.email}+${context.contributor.name}` + }`; + } + if (context.autolinksId != null) { + uniqueness += `/autolinks/${context.autolinksId}`; + } + if (context.comparisonId != null) { + uniqueness += `/comparison/${context.comparisonId}`; + } + if (context.searchId != null) { + uniqueness += `/search/${context.searchId}`; + } + if (context.commit != null) { + uniqueness += `/commit/${context.commit.sha}`; + } + if (context.file != null) { + uniqueness += `/file/${context.file.path}+${context.file.status}`; + } + + return `gitlens://viewnode/${type}${uniqueness}`; +} + +@logName((c, name) => `${name}${c.id != null ? `(${c.id})` : ''}`) +export abstract class ViewNode< + Type extends TreeViewNodeTypes = TreeViewNodeTypes, + TView extends View = View, + State extends object = any, +> implements Disposable +{ + is(type: T): this is TreeViewNodesByType[T] { + return this.type === (type as unknown as Type); + } + + protected _uniqueId!: string; + protected splatted = false; + // NOTE: @eamodio uncomment to track node leaks + // readonly uuid = uuid(); + + constructor( + public readonly type: Type, + // public readonly id: string | undefined, + uri: GitUri, + public readonly view: TView, + protected parent?: ViewNode, + ) { + // NOTE: @eamodio uncomment to track node leaks + // queueMicrotask(() => this.view.registerNode(this)); + this._uri = uri; + } + + protected _disposed = false; + @debug() + dispose() { + this._disposed = true; + // NOTE: @eamodio uncomment to track node leaks + // this.view.unregisterNode(this); + } + + get id(): string | undefined { + return this._uniqueId; + } + + private _context: AmbientContext | undefined; + protected get context(): AmbientContext { + return this._context ?? this.parent?.context ?? {}; + } + + protected updateContext(context: AmbientContext, reset: boolean = false) { + this._context = this.getNewContext(context, reset); + } + + protected getNewContext(context: AmbientContext, reset: boolean = false) { + return { ...(reset ? this.parent?.context : this.context), ...context }; + } + + toClipboard?(): string; + + toString(): string { + const id = this.id; + return `${getLoggableName(this)}${id != null ? `(${id})` : ''}`; + } + + protected _uri: GitUri; + get uri(): GitUri { + return this._uri; + } + + abstract getChildren(): ViewNode[] | Promise; + + getParent(): ViewNode | undefined { + // If this node's parent has been splatted (e.g. not shown itself, but its children are), then return its grandparent + return this.parent?.splatted ? this.parent?.getParent() : this.parent; + } + + abstract getTreeItem(): TreeItem | Promise; + + resolveTreeItem?(item: TreeItem): TreeItem | Promise; + + getCommand(): Command | undefined { + return undefined; + } + + refresh?(reset?: boolean): boolean | void | Promise | Promise; + + @gate((reset, force, avoidSelf) => `${reset}|${force}|${avoidSelf?.toString()}`) + @debug() + triggerChange(reset: boolean = false, force: boolean = false, avoidSelf?: ViewNode): Promise { + if (this._disposed) return Promise.resolve(); + + // If this node has been splatted (e.g. not shown itself, but its children are), then delegate the change to its parent + if (this.splatted && this.parent != null && this.parent !== avoidSelf) { + return this.parent.triggerChange(reset, force); + } + + return this.view.refreshNode(this, reset, force); + } + + getSplattedChild?(): Promise; + + deleteState = StateKey>(key?: T): void { + if (this.id == null) { + debugger; + throw new Error('Id is required to delete state'); + } + this.view.nodeState.deleteState(this.id, key as string); + } + + getState = StateKey>(key: T): StateValue | undefined { + if (this.id == null) { + debugger; + throw new Error('Id is required to get state'); + } + return this.view.nodeState.getState(this.id, key as string); + } + + storeState = StateKey>( + key: T, + value: StateValue, + sticky?: boolean, + ): void { + if (this.id == null) { + debugger; + throw new Error('Id is required to store state'); + } + this.view.nodeState.storeState(this.id, key as string, value, sticky); + } +} + +type StateKey = keyof T; +type StateValue> = P extends keyof T ? T[P] : never; + +export interface PageableViewNode extends ViewNode { + readonly id: string; + limit?: number; + readonly hasMore: boolean; + loadMore(limit?: number | { until?: string | undefined }, context?: Record): Promise; +} + +export function isPageableViewNode(node: ViewNode): node is ViewNode & PageableViewNode { + return isA(node, 'loadMore'); +} + +interface AutoRefreshableView { + autoRefresh: boolean; + onDidChangeAutoRefresh: Event; +} + +export function canAutoRefreshView(view: View): view is View & AutoRefreshableView { + return isA(view, 'onDidChangeAutoRefresh'); +} + +export function canEditNode(node: ViewNode): node is ViewNode & { edit(): void | Promise } { + return typeof (node as ViewNode & { edit(): void | Promise }).edit === 'function'; +} + +export function canGetNodeRepoPath(node?: ViewNode): node is ViewNode & { repoPath: string | undefined } { + return node != null && 'repoPath' in node && typeof node.repoPath === 'string'; +} + +export function canViewDismissNode(view: View): view is View & { dismissNode(node: ViewNode): void } { + return typeof (view as View & { dismissNode(node: ViewNode): void }).dismissNode === 'function'; +} + +export function getNodeRepoPath(node?: ViewNode): string | undefined { + return canGetNodeRepoPath(node) ? node.repoPath : undefined; +} + +type TreeViewNodesByType = { + [T in TreeViewNodeTypes]: T extends 'branch' + ? BranchNode + : T extends 'commit' + ? CommitNode + : T extends 'commit-file' + ? CommitFileNode + : T extends 'compare-branch' + ? CompareBranchNode + : T extends 'compare-results' + ? CompareResultsNode + : T extends 'conflict-file' + ? MergeConflictFileNode + : T extends 'file-commit' + ? FileRevisionAsCommitNode + : T extends 'folder' + ? FolderNode + : T extends 'line-history-tracker' + ? LineHistoryTrackerNode + : T extends 'repository' + ? RepositoryNode + : T extends 'repo-folder' + ? RepositoryFolderNode + : T extends 'results-commits' + ? ResultsCommitsNode + : T extends 'results-file' + ? ResultsFileNode + : T extends 'stash' + ? StashNode + : T extends 'stash-file' + ? StashFileNode + : T extends 'status-file' + ? StatusFileNode + : T extends 'tag' + ? TagNode + : T extends 'uncommitted-file' + ? UncommittedFileNode + : ViewNode; +}; + +export function isViewNode(node: unknown): node is ViewNode; +export function isViewNode(node: unknown, type: T): node is TreeViewNodesByType[T]; +export function isViewNode(node: unknown, type?: T): node is ViewNode { + if (node == null) return false; + return node instanceof ViewNode ? type == null || node.type === type : false; +} diff --git a/src/views/nodes/abstract/viewRefNode.ts b/src/views/nodes/abstract/viewRefNode.ts new file mode 100644 index 0000000000000..8c7a7f369cc14 --- /dev/null +++ b/src/views/nodes/abstract/viewRefNode.ts @@ -0,0 +1,45 @@ +import type { TreeViewRefFileNodeTypes, TreeViewRefNodeTypes } from '../../../constants'; +import type { GitUri } from '../../../git/gitUri'; +import type { GitReference, GitRevisionReference } from '../../../git/models/reference'; +import { getReferenceLabel } from '../../../git/models/reference'; +import type { View } from '../../viewBase'; +import { ViewFileNode } from './viewFileNode'; +import { ViewNode } from './viewNode'; + +export abstract class ViewRefNode< + Type extends TreeViewRefNodeTypes = TreeViewRefNodeTypes, + TView extends View = View, + TReference extends GitReference = GitReference, + State extends object = any, +> extends ViewNode { + constructor( + type: Type, + uri: GitUri, + view: TView, + protected override readonly parent: ViewNode, + ) { + super(type, uri, view, parent); + } + + abstract get ref(): TReference; + + get repoPath(): string { + return this.uri.repoPath!; + } + + override toString(): string { + return `${super.toString()}:${getReferenceLabel(this.ref, false)}`; + } +} + +export abstract class ViewRefFileNode< + Type extends TreeViewRefFileNodeTypes = TreeViewRefFileNodeTypes, + TView extends View = View, + State extends object = any, +> extends ViewFileNode { + abstract get ref(): GitRevisionReference; + + override toString(): string { + return `${super.toString()}:${this.file.path}`; + } +} diff --git a/src/views/nodes/autolinkedItemNode.ts b/src/views/nodes/autolinkedItemNode.ts index 7de5a432e5b49..6a8be012e0a8a 100644 --- a/src/views/nodes/autolinkedItemNode.ts +++ b/src/views/nodes/autolinkedItemNode.ts @@ -6,7 +6,7 @@ import { getIssueOrPullRequestMarkdownIcon, getIssueOrPullRequestThemeIcon } fro import { fromNow } from '../../system/date'; import { isPromise } from '../../system/promise'; import type { ViewsWithCommits } from '../viewBase'; -import { ContextValues, getViewNodeId, ViewNode } from './viewNode'; +import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode'; export class AutolinkedItemNode extends ViewNode<'autolink', ViewsWithCommits> { constructor( diff --git a/src/views/nodes/autolinkedItemsNode.ts b/src/views/nodes/autolinkedItemsNode.ts index d7e0ed74ffb10..812d5a39dbfdf 100644 --- a/src/views/nodes/autolinkedItemsNode.ts +++ b/src/views/nodes/autolinkedItemsNode.ts @@ -5,11 +5,12 @@ import { PullRequest } from '../../git/models/pullRequest'; import { pauseOnCancelOrTimeoutMapTuple } from '../../system/cancellation'; import { getSettledValue } from '../../system/promise'; import type { ViewsWithCommits } from '../viewBase'; +import { CacheableChildrenViewNode } from './abstract/cacheableChildrenViewNode'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; import { AutolinkedItemNode } from './autolinkedItemNode'; import { LoadMoreNode, MessageNode } from './common'; import { PullRequestNode } from './pullRequestNode'; -import type { ViewNode } from './viewNode'; -import { CacheableChildrenViewNode, ContextValues, getViewNodeId } from './viewNode'; let instanceId = 0; diff --git a/src/views/nodes/branchNode.ts b/src/views/nodes/branchNode.ts index f0aa4475429dd..f32a1b9641c9c 100644 --- a/src/views/nodes/branchNode.ts +++ b/src/views/nodes/branchNode.ts @@ -19,6 +19,9 @@ import { defer, getSettledValue } from '../../system/promise'; import { pad } from '../../system/string'; import type { ViewsWithBranches } from '../viewBase'; import { disposeChildren } from '../viewBase'; +import type { PageableViewNode, ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; +import { ViewRefNode } from './abstract/viewRefNode'; import { BranchTrackingStatusNode } from './branchTrackingStatusNode'; import { CommitNode } from './commitNode'; import { LoadMoreNode, MessageNode } from './common'; @@ -27,8 +30,6 @@ import { insertDateMarkers } from './helpers'; import { MergeStatusNode } from './mergeStatusNode'; import { PullRequestNode } from './pullRequestNode'; import { RebaseStatusNode } from './rebaseStatusNode'; -import type { PageableViewNode, ViewNode } from './viewNode'; -import { ContextValues, getViewNodeId, ViewRefNode } from './viewNode'; type State = { pullRequest: PullRequest | null | undefined; diff --git a/src/views/nodes/branchOrTagFolderNode.ts b/src/views/nodes/branchOrTagFolderNode.ts index 9a71912a00065..da722d669f9f5 100644 --- a/src/views/nodes/branchOrTagFolderNode.ts +++ b/src/views/nodes/branchOrTagFolderNode.ts @@ -2,9 +2,9 @@ import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from 'vscode'; import { GitUri } from '../../git/gitUri'; import type { HierarchicalItem } from '../../system/array'; import type { View } from '../viewBase'; +import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode'; import type { BranchNode } from './branchNode'; import type { TagNode } from './tagNode'; -import { ContextValues, getViewNodeId, ViewNode } from './viewNode'; export class BranchOrTagFolderNode extends ViewNode<'branch-tag-folder'> { constructor( diff --git a/src/views/nodes/branchTrackingStatusFilesNode.ts b/src/views/nodes/branchTrackingStatusFilesNode.ts index 9d0dd975a6fdb..adafd1b9c80a5 100644 --- a/src/views/nodes/branchTrackingStatusFilesNode.ts +++ b/src/views/nodes/branchTrackingStatusFilesNode.ts @@ -8,11 +8,11 @@ import { filter, flatMap, map } from '../../system/iterable'; import { joinPaths, normalizePath } from '../../system/path'; import { pluralize, sortCompare } from '../../system/string'; import type { ViewsWithCommits } from '../viewBase'; +import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode'; import type { BranchTrackingStatus } from './branchTrackingStatusNode'; import type { FileNode } from './folderNode'; import { FolderNode } from './folderNode'; import { StatusFileNode } from './statusFileNode'; -import { ContextValues, getViewNodeId, ViewNode } from './viewNode'; export class BranchTrackingStatusFilesNode extends ViewNode<'tracking-status-files', ViewsWithCommits> { constructor( diff --git a/src/views/nodes/branchTrackingStatusNode.ts b/src/views/nodes/branchTrackingStatusNode.ts index 9d299fd7540a3..c946bb0e1d0ff 100644 --- a/src/views/nodes/branchTrackingStatusNode.ts +++ b/src/views/nodes/branchTrackingStatusNode.ts @@ -12,12 +12,12 @@ import { debug } from '../../system/decorators/log'; import { first, map } from '../../system/iterable'; import { pluralize } from '../../system/string'; import type { ViewsWithCommits } from '../viewBase'; +import type { PageableViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode'; import { BranchTrackingStatusFilesNode } from './branchTrackingStatusFilesNode'; import { CommitNode } from './commitNode'; import { LoadMoreNode } from './common'; import { insertDateMarkers } from './helpers'; -import type { PageableViewNode } from './viewNode'; -import { ContextValues, getViewNodeId, ViewNode } from './viewNode'; export interface BranchTrackingStatus { ref: string; diff --git a/src/views/nodes/branchesNode.ts b/src/views/nodes/branchesNode.ts index f3528abc76a88..631c9d34f17df 100644 --- a/src/views/nodes/branchesNode.ts +++ b/src/views/nodes/branchesNode.ts @@ -4,11 +4,12 @@ import type { Repository } from '../../git/models/repository'; import { makeHierarchical } from '../../system/array'; import { debug } from '../../system/decorators/log'; import type { ViewsWithBranchesNode } from '../viewBase'; +import { CacheableChildrenViewNode } from './abstract/cacheableChildrenViewNode'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; import { BranchNode } from './branchNode'; import { BranchOrTagFolderNode } from './branchOrTagFolderNode'; import { MessageNode } from './common'; -import type { ViewNode } from './viewNode'; -import { CacheableChildrenViewNode, ContextValues, getViewNodeId } from './viewNode'; export class BranchesNode extends CacheableChildrenViewNode<'branches', ViewsWithBranchesNode> { constructor( diff --git a/src/views/nodes/commitFileNode.ts b/src/views/nodes/commitFileNode.ts index da5b4ea037989..0d63ef0434bdb 100644 --- a/src/views/nodes/commitFileNode.ts +++ b/src/views/nodes/commitFileNode.ts @@ -12,8 +12,9 @@ import { getGitFileStatusIcon } from '../../git/models/file'; import type { GitRevisionReference } from '../../git/models/reference'; import { joinPaths, relativeDir } from '../../system/path'; import type { ViewsWithCommits, ViewsWithStashes } from '../viewBase'; -import type { ViewNode } from './viewNode'; -import { ContextValues, getViewNodeId, ViewRefFileNode } from './viewNode'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; +import { ViewRefFileNode } from './abstract/viewRefNode'; export abstract class CommitFileNodeBase< Type extends TreeViewRefFileNodeTypes, diff --git a/src/views/nodes/commitNode.ts b/src/views/nodes/commitNode.ts index d4b15c7bcf0b2..95d27f909486f 100644 --- a/src/views/nodes/commitNode.ts +++ b/src/views/nodes/commitNode.ts @@ -22,12 +22,13 @@ import { sortCompare } from '../../system/string'; import type { FileHistoryView } from '../fileHistoryView'; import type { ViewsWithCommits } from '../viewBase'; import { disposeChildren } from '../viewBase'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; +import { ViewRefNode } from './abstract/viewRefNode'; import { CommitFileNode } from './commitFileNode'; import type { FileNode } from './folderNode'; import { FolderNode } from './folderNode'; import { PullRequestNode } from './pullRequestNode'; -import type { ViewNode } from './viewNode'; -import { ContextValues, getViewNodeId, ViewRefNode } from './viewNode'; type State = { pullRequest: PullRequest | null | undefined; diff --git a/src/views/nodes/common.ts b/src/views/nodes/common.ts index 2974defd50e17..47afbeb7a85a6 100644 --- a/src/views/nodes/common.ts +++ b/src/views/nodes/common.ts @@ -4,8 +4,8 @@ import { GlyphChars } from '../../constants'; import { unknownGitUri } from '../../git/gitUri'; import { configuration } from '../../system/configuration'; import type { View } from '../viewBase'; -import type { PageableViewNode } from './viewNode'; -import { ContextValues, ViewNode } from './viewNode'; +import type { PageableViewNode } from './abstract/viewNode'; +import { ContextValues, ViewNode } from './abstract/viewNode'; export class MessageNode extends ViewNode<'message'> { constructor( diff --git a/src/views/nodes/compareBranchNode.ts b/src/views/nodes/compareBranchNode.ts index 3ee29eae1d5a3..5867c8951d393 100644 --- a/src/views/nodes/compareBranchNode.ts +++ b/src/views/nodes/compareBranchNode.ts @@ -15,6 +15,9 @@ import { getSettledValue } from '../../system/promise'; import { pluralize } from '../../system/string'; import type { ViewsWithBranches } from '../viewBase'; import type { WorktreesView } from '../worktreesView'; +import { SubscribeableViewNode } from './abstract/subscribeableViewNode'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; import { getComparisonCheckedFiles, getComparisonStoragePrefix, @@ -25,8 +28,6 @@ import type { CommitsQueryResults } from './resultsCommitsNode'; import { ResultsCommitsNode } from './resultsCommitsNode'; import type { FilesQueryResults } from './resultsFilesNode'; import { ResultsFilesNode } from './resultsFilesNode'; -import type { ViewNode } from './viewNode'; -import { ContextValues, getViewNodeId, SubscribeableViewNode } from './viewNode'; type State = { filterCommits: GitUser[] | undefined; diff --git a/src/views/nodes/comparePickerNode.ts b/src/views/nodes/comparePickerNode.ts index f9adbb87d5920..2fe0f52aff7ff 100644 --- a/src/views/nodes/comparePickerNode.ts +++ b/src/views/nodes/comparePickerNode.ts @@ -3,7 +3,7 @@ import type { StoredNamedRef } from '../../constants'; import { GlyphChars } from '../../constants'; import { unknownGitUri } from '../../git/gitUri'; import type { SearchAndCompareView, SearchAndCompareViewNode } from '../searchAndCompareView'; -import { ContextValues, ViewNode } from './viewNode'; +import { ContextValues, ViewNode } from './abstract/viewNode'; interface RepoRef { label: string; diff --git a/src/views/nodes/compareResultsNode.ts b/src/views/nodes/compareResultsNode.ts index e4b515437f9f3..cb1df946e5711 100644 --- a/src/views/nodes/compareResultsNode.ts +++ b/src/views/nodes/compareResultsNode.ts @@ -12,12 +12,13 @@ import { getSettledValue } from '../../system/promise'; import { pluralize } from '../../system/string'; import type { SearchAndCompareView } from '../searchAndCompareView'; import type { View } from '../viewBase'; +import { SubscribeableViewNode } from './abstract/subscribeableViewNode'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; import type { CommitsQueryResults } from './resultsCommitsNode'; import { ResultsCommitsNode } from './resultsCommitsNode'; import type { FilesQueryResults } from './resultsFilesNode'; import { ResultsFilesNode } from './resultsFilesNode'; -import type { ViewNode } from './viewNode'; -import { ContextValues, getViewNodeId, SubscribeableViewNode } from './viewNode'; let instanceId = 0; diff --git a/src/views/nodes/contributorNode.ts b/src/views/nodes/contributorNode.ts index 6aab0b194c7cf..8def3156b4a98 100644 --- a/src/views/nodes/contributorNode.ts +++ b/src/views/nodes/contributorNode.ts @@ -11,11 +11,11 @@ import { map } from '../../system/iterable'; import { pluralize } from '../../system/string'; import type { ContactPresence } from '../../vsls/vsls'; import type { ViewsWithContributors } from '../viewBase'; +import type { PageableViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode'; import { CommitNode } from './commitNode'; import { LoadMoreNode, MessageNode } from './common'; import { insertDateMarkers } from './helpers'; -import type { PageableViewNode } from './viewNode'; -import { ContextValues, getViewNodeId, ViewNode } from './viewNode'; export class ContributorNode extends ViewNode<'contributor', ViewsWithContributors> implements PageableViewNode { limit: number | undefined; diff --git a/src/views/nodes/contributorsNode.ts b/src/views/nodes/contributorsNode.ts index a6e968988dd80..4f0195e06aa23 100644 --- a/src/views/nodes/contributorsNode.ts +++ b/src/views/nodes/contributorsNode.ts @@ -5,10 +5,11 @@ import type { Repository } from '../../git/models/repository'; import { configuration } from '../../system/configuration'; import { debug } from '../../system/decorators/log'; import type { ViewsWithContributorsNode } from '../viewBase'; +import { CacheableChildrenViewNode } from './abstract/cacheableChildrenViewNode'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; import { MessageNode } from './common'; import { ContributorNode } from './contributorNode'; -import type { ViewNode } from './viewNode'; -import { CacheableChildrenViewNode, ContextValues, getViewNodeId } from './viewNode'; export class ContributorsNode extends CacheableChildrenViewNode< 'contributors', diff --git a/src/views/nodes/fileHistoryNode.ts b/src/views/nodes/fileHistoryNode.ts index 9a69644746fb3..ddbfd59241c09 100644 --- a/src/views/nodes/fileHistoryNode.ts +++ b/src/views/nodes/fileHistoryNode.ts @@ -14,13 +14,14 @@ import { filterMap, flatMap, map, uniqueBy } from '../../system/iterable'; import { Logger } from '../../system/logger'; import { basename } from '../../system/path'; import type { FileHistoryView } from '../fileHistoryView'; +import { SubscribeableViewNode } from './abstract/subscribeableViewNode'; +import type { PageableViewNode, ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; import { CommitNode } from './commitNode'; import { LoadMoreNode, MessageNode } from './common'; import { FileHistoryTrackerNode } from './fileHistoryTrackerNode'; import { FileRevisionAsCommitNode } from './fileRevisionAsCommitNode'; import { insertDateMarkers } from './helpers'; -import type { PageableViewNode, ViewNode } from './viewNode'; -import { ContextValues, getViewNodeId, SubscribeableViewNode } from './viewNode'; export class FileHistoryNode extends SubscribeableViewNode<'file-history', FileHistoryView> diff --git a/src/views/nodes/fileHistoryTrackerNode.ts b/src/views/nodes/fileHistoryTrackerNode.ts index d854e1c26dd7f..224182745b5e9 100644 --- a/src/views/nodes/fileHistoryTrackerNode.ts +++ b/src/views/nodes/fileHistoryTrackerNode.ts @@ -15,9 +15,10 @@ import { Logger } from '../../system/logger'; import { getLogScope, setLogScopeExit } from '../../system/logger.scope'; import { isVirtualUri } from '../../system/utils'; import type { FileHistoryView } from '../fileHistoryView'; +import { SubscribeableViewNode } from './abstract/subscribeableViewNode'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues } from './abstract/viewNode'; import { FileHistoryNode } from './fileHistoryNode'; -import type { ViewNode } from './viewNode'; -import { ContextValues, SubscribeableViewNode } from './viewNode'; export class FileHistoryTrackerNode extends SubscribeableViewNode<'file-history-tracker', FileHistoryView> { private _base: string | undefined; diff --git a/src/views/nodes/fileRevisionAsCommitNode.ts b/src/views/nodes/fileRevisionAsCommitNode.ts index 9d48e4022438f..96afcdd9db922 100644 --- a/src/views/nodes/fileRevisionAsCommitNode.ts +++ b/src/views/nodes/fileRevisionAsCommitNode.ts @@ -19,10 +19,11 @@ import { getSettledValue } from '../../system/promise'; import type { FileHistoryView } from '../fileHistoryView'; import type { LineHistoryView } from '../lineHistoryView'; import type { ViewsWithCommits } from '../viewBase'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues } from './abstract/viewNode'; +import { ViewRefFileNode } from './abstract/viewRefNode'; import { MergeConflictCurrentChangesNode } from './mergeConflictCurrentChangesNode'; import { MergeConflictIncomingChangesNode } from './mergeConflictIncomingChangesNode'; -import type { ViewNode } from './viewNode'; -import { ContextValues, ViewRefFileNode } from './viewNode'; export class FileRevisionAsCommitNode extends ViewRefFileNode< 'file-commit', diff --git a/src/views/nodes/folderNode.ts b/src/views/nodes/folderNode.ts index fe48e11228ad2..cb03e6c452a42 100644 --- a/src/views/nodes/folderNode.ts +++ b/src/views/nodes/folderNode.ts @@ -5,8 +5,8 @@ import type { HierarchicalItem } from '../../system/array'; import { sortCompare } from '../../system/string'; import type { StashesView } from '../stashesView'; import type { ViewsWithCommits } from '../viewBase'; -import type { ViewFileNode } from './viewNode'; -import { ContextValues, getViewNodeId, ViewNode } from './viewNode'; +import type { ViewFileNode } from './abstract/viewFileNode'; +import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode'; export interface FileNode extends ViewFileNode { folderName: string; diff --git a/src/views/nodes/helpers.ts b/src/views/nodes/helpers.ts index 47d9c4b236a89..01619e1fea458 100644 --- a/src/views/nodes/helpers.ts +++ b/src/views/nodes/helpers.ts @@ -1,7 +1,7 @@ import type { GitCommit } from '../../git/models/commit'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues } from './abstract/viewNode'; import { MessageNode } from './common'; -import type { ViewNode } from './viewNode'; -import { ContextValues } from './viewNode'; const markers: [number, string][] = [ [0, 'Less than a week ago'], diff --git a/src/views/nodes/lineHistoryNode.ts b/src/views/nodes/lineHistoryNode.ts index 195fb19616515..40cc720a727f5 100644 --- a/src/views/nodes/lineHistoryNode.ts +++ b/src/views/nodes/lineHistoryNode.ts @@ -16,12 +16,13 @@ import { filterMap } from '../../system/iterable'; import { Logger } from '../../system/logger'; import type { FileHistoryView } from '../fileHistoryView'; import type { LineHistoryView } from '../lineHistoryView'; +import { SubscribeableViewNode } from './abstract/subscribeableViewNode'; +import type { PageableViewNode, ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; import { LoadMoreNode, MessageNode } from './common'; import { FileRevisionAsCommitNode } from './fileRevisionAsCommitNode'; import { insertDateMarkers } from './helpers'; import { LineHistoryTrackerNode } from './lineHistoryTrackerNode'; -import type { PageableViewNode, ViewNode } from './viewNode'; -import { ContextValues, getViewNodeId, SubscribeableViewNode } from './viewNode'; export class LineHistoryNode extends SubscribeableViewNode<'line-history', FileHistoryView | LineHistoryView> diff --git a/src/views/nodes/lineHistoryTrackerNode.ts b/src/views/nodes/lineHistoryTrackerNode.ts index 32bd00aa2d9b9..538440b572720 100644 --- a/src/views/nodes/lineHistoryTrackerNode.ts +++ b/src/views/nodes/lineHistoryTrackerNode.ts @@ -16,9 +16,10 @@ import { getLogScope, setLogScopeExit } from '../../system/logger.scope'; import type { LinesChangeEvent } from '../../trackers/lineTracker'; import type { FileHistoryView } from '../fileHistoryView'; import type { LineHistoryView } from '../lineHistoryView'; +import { SubscribeableViewNode } from './abstract/subscribeableViewNode'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues } from './abstract/viewNode'; import { LineHistoryNode } from './lineHistoryNode'; -import type { ViewNode } from './viewNode'; -import { ContextValues, SubscribeableViewNode } from './viewNode'; export class LineHistoryTrackerNode extends SubscribeableViewNode< 'line-history-tracker', diff --git a/src/views/nodes/mergeConflictCurrentChangesNode.ts b/src/views/nodes/mergeConflictCurrentChangesNode.ts index cab59763c736a..67c0758dc4e80 100644 --- a/src/views/nodes/mergeConflictCurrentChangesNode.ts +++ b/src/views/nodes/mergeConflictCurrentChangesNode.ts @@ -13,8 +13,8 @@ import { configuration } from '../../system/configuration'; import type { FileHistoryView } from '../fileHistoryView'; import type { LineHistoryView } from '../lineHistoryView'; import type { ViewsWithCommits } from '../viewBase'; +import { ContextValues, ViewNode } from './abstract/viewNode'; import { getFileRevisionAsCommitTooltip } from './fileRevisionAsCommitNode'; -import { ContextValues, ViewNode } from './viewNode'; export class MergeConflictCurrentChangesNode extends ViewNode< 'conflict-current-changes', diff --git a/src/views/nodes/mergeConflictFileNode.ts b/src/views/nodes/mergeConflictFileNode.ts index fde1b6ca0cacf..dcfcb66a7cab4 100644 --- a/src/views/nodes/mergeConflictFileNode.ts +++ b/src/views/nodes/mergeConflictFileNode.ts @@ -8,11 +8,12 @@ import type { GitRebaseStatus } from '../../git/models/rebase'; import { createCoreCommand } from '../../system/command'; import { relativeDir } from '../../system/path'; import type { ViewsWithCommits } from '../viewBase'; +import { ViewFileNode } from './abstract/viewFileNode'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues } from './abstract/viewNode'; import type { FileNode } from './folderNode'; import { MergeConflictCurrentChangesNode } from './mergeConflictCurrentChangesNode'; import { MergeConflictIncomingChangesNode } from './mergeConflictIncomingChangesNode'; -import type { ViewNode } from './viewNode'; -import { ContextValues, ViewFileNode } from './viewNode'; export class MergeConflictFileNode extends ViewFileNode<'conflict-file', ViewsWithCommits> implements FileNode { constructor( diff --git a/src/views/nodes/mergeConflictFilesNode.ts b/src/views/nodes/mergeConflictFilesNode.ts index fb313d4950ffd..f603bbc9a642f 100644 --- a/src/views/nodes/mergeConflictFilesNode.ts +++ b/src/views/nodes/mergeConflictFilesNode.ts @@ -7,10 +7,10 @@ import { makeHierarchical } from '../../system/array'; import { joinPaths, normalizePath } from '../../system/path'; import { pluralize, sortCompare } from '../../system/string'; import type { ViewsWithCommits } from '../viewBase'; +import { ViewNode } from './abstract/viewNode'; import type { FileNode } from './folderNode'; import { FolderNode } from './folderNode'; import { MergeConflictFileNode } from './mergeConflictFileNode'; -import { ViewNode } from './viewNode'; export class MergeConflictFilesNode extends ViewNode<'conflict-files', ViewsWithCommits> { constructor( diff --git a/src/views/nodes/mergeConflictIncomingChangesNode.ts b/src/views/nodes/mergeConflictIncomingChangesNode.ts index 120a8e380c6ed..306c2d54ab027 100644 --- a/src/views/nodes/mergeConflictIncomingChangesNode.ts +++ b/src/views/nodes/mergeConflictIncomingChangesNode.ts @@ -13,8 +13,8 @@ import { configuration } from '../../system/configuration'; import type { FileHistoryView } from '../fileHistoryView'; import type { LineHistoryView } from '../lineHistoryView'; import type { ViewsWithCommits } from '../viewBase'; +import { ContextValues, ViewNode } from './abstract/viewNode'; import { getFileRevisionAsCommitTooltip } from './fileRevisionAsCommitNode'; -import { ContextValues, ViewNode } from './viewNode'; export class MergeConflictIncomingChangesNode extends ViewNode< 'conflict-incoming-changes', diff --git a/src/views/nodes/mergeStatusNode.ts b/src/views/nodes/mergeStatusNode.ts index 05b9a75d735fa..80567d236d5dd 100644 --- a/src/views/nodes/mergeStatusNode.ts +++ b/src/views/nodes/mergeStatusNode.ts @@ -7,8 +7,8 @@ import { getReferenceLabel } from '../../git/models/reference'; import type { GitStatus } from '../../git/models/status'; import { pluralize } from '../../system/string'; import type { ViewsWithCommits } from '../viewBase'; +import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode'; import { MergeConflictFilesNode } from './mergeConflictFilesNode'; -import { ContextValues, getViewNodeId, ViewNode } from './viewNode'; export class MergeStatusNode extends ViewNode<'merge-status', ViewsWithCommits> { constructor( diff --git a/src/views/nodes/pullRequestNode.ts b/src/views/nodes/pullRequestNode.ts index 666d2847462f2..669978935cae6 100644 --- a/src/views/nodes/pullRequestNode.ts +++ b/src/views/nodes/pullRequestNode.ts @@ -5,7 +5,7 @@ import type { GitCommit } from '../../git/models/commit'; import { getIssueOrPullRequestMarkdownIcon, getIssueOrPullRequestThemeIcon } from '../../git/models/issue'; import type { PullRequest } from '../../git/models/pullRequest'; import type { ViewsWithCommits } from '../viewBase'; -import { ContextValues, getViewNodeId, ViewNode } from './viewNode'; +import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode'; export class PullRequestNode extends ViewNode<'pullrequest', ViewsWithCommits> { readonly repoPath: string; diff --git a/src/views/nodes/rebaseCommitNode.ts b/src/views/nodes/rebaseCommitNode.ts index b4eff4c6c6125..58df769d00b3b 100644 --- a/src/views/nodes/rebaseCommitNode.ts +++ b/src/views/nodes/rebaseCommitNode.ts @@ -1,7 +1,7 @@ import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from 'vscode'; import { CommitFormatter } from '../../git/formatters/commitFormatter'; +import { ContextValues } from './abstract/viewNode'; import { CommitNode } from './commitNode'; -import { ContextValues } from './viewNode'; export class RebaseCommitNode extends CommitNode { // eslint-disable-next-line @typescript-eslint/require-await diff --git a/src/views/nodes/rebaseStatusNode.ts b/src/views/nodes/rebaseStatusNode.ts index c3752c55c686c..9ece677b3e591 100644 --- a/src/views/nodes/rebaseStatusNode.ts +++ b/src/views/nodes/rebaseStatusNode.ts @@ -8,9 +8,9 @@ import type { GitStatus } from '../../git/models/status'; import { executeCoreCommand } from '../../system/command'; import { pluralize } from '../../system/string'; import type { ViewsWithCommits } from '../viewBase'; +import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode'; import { MergeConflictFilesNode } from './mergeConflictFilesNode'; import { RebaseCommitNode } from './rebaseCommitNode'; -import { ContextValues, getViewNodeId, ViewNode } from './viewNode'; export class RebaseStatusNode extends ViewNode<'rebase-status', ViewsWithCommits> { constructor( diff --git a/src/views/nodes/reflogNode.ts b/src/views/nodes/reflogNode.ts index e8ee9944e019a..0f85969f7d20b 100644 --- a/src/views/nodes/reflogNode.ts +++ b/src/views/nodes/reflogNode.ts @@ -5,10 +5,11 @@ import type { Repository } from '../../git/models/repository'; import { debug } from '../../system/decorators/log'; import type { RepositoriesView } from '../repositoriesView'; import type { WorkspacesView } from '../workspacesView'; +import { CacheableChildrenViewNode } from './abstract/cacheableChildrenViewNode'; +import type { PageableViewNode, ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; import { LoadMoreNode, MessageNode } from './common'; import { ReflogRecordNode } from './reflogRecordNode'; -import type { PageableViewNode, ViewNode } from './viewNode'; -import { CacheableChildrenViewNode, ContextValues, getViewNodeId } from './viewNode'; export class ReflogNode extends CacheableChildrenViewNode<'reflog', RepositoriesView | WorkspacesView> diff --git a/src/views/nodes/reflogRecordNode.ts b/src/views/nodes/reflogRecordNode.ts index 85b08e4329e75..c6cdd28e5b667 100644 --- a/src/views/nodes/reflogRecordNode.ts +++ b/src/views/nodes/reflogRecordNode.ts @@ -7,10 +7,10 @@ import { gate } from '../../system/decorators/gate'; import { debug } from '../../system/decorators/log'; import { map } from '../../system/iterable'; import type { ViewsWithCommits } from '../viewBase'; +import type { PageableViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode'; import { CommitNode } from './commitNode'; import { LoadMoreNode, MessageNode } from './common'; -import type { PageableViewNode } from './viewNode'; -import { ContextValues, getViewNodeId, ViewNode } from './viewNode'; export class ReflogRecordNode extends ViewNode<'reflog-record', ViewsWithCommits> implements PageableViewNode { limit: number | undefined; diff --git a/src/views/nodes/remoteNode.ts b/src/views/nodes/remoteNode.ts index 7a2ac383a0aac..f3c4aadf71665 100644 --- a/src/views/nodes/remoteNode.ts +++ b/src/views/nodes/remoteNode.ts @@ -7,10 +7,10 @@ import type { Repository } from '../../git/models/repository'; import { makeHierarchical } from '../../system/array'; import { log } from '../../system/decorators/log'; import type { ViewsWithRemotes } from '../viewBase'; +import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode'; import { BranchNode } from './branchNode'; import { BranchOrTagFolderNode } from './branchOrTagFolderNode'; import { MessageNode } from './common'; -import { ContextValues, getViewNodeId, ViewNode } from './viewNode'; export class RemoteNode extends ViewNode<'remote', ViewsWithRemotes> { constructor( diff --git a/src/views/nodes/remotesNode.ts b/src/views/nodes/remotesNode.ts index c36436ab5de28..f534e8ec0d103 100644 --- a/src/views/nodes/remotesNode.ts +++ b/src/views/nodes/remotesNode.ts @@ -3,10 +3,11 @@ import type { GitUri } from '../../git/gitUri'; import type { Repository } from '../../git/models/repository'; import { debug } from '../../system/decorators/log'; import type { ViewsWithRemotesNode } from '../viewBase'; +import { CacheableChildrenViewNode } from './abstract/cacheableChildrenViewNode'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; import { MessageNode } from './common'; import { RemoteNode } from './remoteNode'; -import type { ViewNode } from './viewNode'; -import { CacheableChildrenViewNode, ContextValues, getViewNodeId } from './viewNode'; export class RemotesNode extends CacheableChildrenViewNode<'remotes', ViewsWithRemotesNode> { constructor( diff --git a/src/views/nodes/repositoriesNode.ts b/src/views/nodes/repositoriesNode.ts index b1ad2da6f2d97..0c64a7dd94659 100644 --- a/src/views/nodes/repositoriesNode.ts +++ b/src/views/nodes/repositoriesNode.ts @@ -8,10 +8,11 @@ import { weakEvent } from '../../system/event'; import { debounce, szudzikPairing } from '../../system/function'; import { Logger } from '../../system/logger'; import type { ViewsWithRepositoriesNode } from '../viewBase'; +import { SubscribeableViewNode } from './abstract/subscribeableViewNode'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues } from './abstract/viewNode'; import { MessageNode } from './common'; import { RepositoryNode } from './repositoryNode'; -import type { ViewNode } from './viewNode'; -import { ContextValues, SubscribeableViewNode } from './viewNode'; export class RepositoriesNode extends SubscribeableViewNode< 'repositories', diff --git a/src/views/nodes/repositoryNode.ts b/src/views/nodes/repositoryNode.ts index bb4b4ea0d627b..0e2d454b9a9e1 100644 --- a/src/views/nodes/repositoryNode.ts +++ b/src/views/nodes/repositoryNode.ts @@ -20,6 +20,9 @@ import { weakEvent } from '../../system/event'; import { disposableInterval } from '../../system/function'; import { pad } from '../../system/string'; import type { ViewsWithRepositories } from '../viewBase'; +import { SubscribeableViewNode } from './abstract/subscribeableViewNode'; +import type { AmbientContext, ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; import { BranchesNode } from './branchesNode'; import { BranchNode } from './branchNode'; import { BranchTrackingStatusNode } from './branchTrackingStatusNode'; @@ -33,8 +36,6 @@ import { RemotesNode } from './remotesNode'; import { StashesNode } from './stashesNode'; import { StatusFilesNode } from './statusFilesNode'; import { TagsNode } from './tagsNode'; -import type { AmbientContext, ViewNode } from './viewNode'; -import { ContextValues, getViewNodeId, SubscribeableViewNode } from './viewNode'; import { WorktreesNode } from './worktreesNode'; export class RepositoryNode extends SubscribeableViewNode<'repository', ViewsWithRepositories> { diff --git a/src/views/nodes/resultsCommitsNode.ts b/src/views/nodes/resultsCommitsNode.ts index 7eae290ede5c5..ba866a46729df 100644 --- a/src/views/nodes/resultsCommitsNode.ts +++ b/src/views/nodes/resultsCommitsNode.ts @@ -9,6 +9,8 @@ import { map } from '../../system/iterable'; import type { Deferred } from '../../system/promise'; import { cancellable, defer, PromiseCancelledError } from '../../system/promise'; import type { ViewsWithCommits } from '../viewBase'; +import type { PageableViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode'; import { AutolinkedItemsNode } from './autolinkedItemsNode'; import { CommitNode } from './commitNode'; import { LoadMoreNode } from './common'; @@ -16,8 +18,6 @@ import { insertDateMarkers } from './helpers'; import type { FilesQueryResults } from './resultsFilesNode'; import { ResultsFilesNode } from './resultsFilesNode'; import { StashNode } from './stashNode'; -import type { PageableViewNode } from './viewNode'; -import { ContextValues, getViewNodeId, ViewNode } from './viewNode'; export interface CommitsQueryResults { readonly label: string; diff --git a/src/views/nodes/resultsFileNode.ts b/src/views/nodes/resultsFileNode.ts index 8f17418aaf7b5..8d957a46ac3e4 100644 --- a/src/views/nodes/resultsFileNode.ts +++ b/src/views/nodes/resultsFileNode.ts @@ -10,10 +10,11 @@ import type { GitRevisionReference } from '../../git/models/reference'; import { createReference } from '../../git/models/reference'; import { joinPaths, relativeDir } from '../../system/path'; import type { View } from '../viewBase'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; +import { ViewRefFileNode } from './abstract/viewRefNode'; import { getComparisonStoragePrefix } from './compareResultsNode'; import type { FileNode } from './folderNode'; -import type { ViewNode } from './viewNode'; -import { ContextValues, getViewNodeId, ViewRefFileNode } from './viewNode'; type State = { checked: TreeItemCheckboxState; diff --git a/src/views/nodes/resultsFilesNode.ts b/src/views/nodes/resultsFilesNode.ts index a8e4548c47504..309e34f88d416 100644 --- a/src/views/nodes/resultsFilesNode.ts +++ b/src/views/nodes/resultsFilesNode.ts @@ -10,10 +10,10 @@ import { joinPaths, normalizePath } from '../../system/path'; import { cancellable, PromiseCancelledError } from '../../system/promise'; import { pluralize, sortCompare } from '../../system/string'; import type { ViewsWithCommits } from '../viewBase'; +import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode'; import type { FileNode } from './folderNode'; import { FolderNode } from './folderNode'; import { ResultsFileNode } from './resultsFileNode'; -import { ContextValues, getViewNodeId, ViewNode } from './viewNode'; type State = { filter: FilesQueryFilter | undefined; diff --git a/src/views/nodes/searchResultsNode.ts b/src/views/nodes/searchResultsNode.ts index 3d312a60d121e..cb2d824a2d923 100644 --- a/src/views/nodes/searchResultsNode.ts +++ b/src/views/nodes/searchResultsNode.ts @@ -10,10 +10,10 @@ import { gate } from '../../system/decorators/gate'; import { debug } from '../../system/decorators/log'; import { pluralize } from '../../system/string'; import type { SearchAndCompareView } from '../searchAndCompareView'; +import type { PageableViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode'; import type { CommitsQueryResults } from './resultsCommitsNode'; import { ResultsCommitsNode } from './resultsCommitsNode'; -import type { PageableViewNode } from './viewNode'; -import { ContextValues, getViewNodeId, ViewNode } from './viewNode'; let instanceId = 0; diff --git a/src/views/nodes/stashFileNode.ts b/src/views/nodes/stashFileNode.ts index 0015ffdf90f67..69142d9da90a1 100644 --- a/src/views/nodes/stashFileNode.ts +++ b/src/views/nodes/stashFileNode.ts @@ -1,9 +1,9 @@ import type { GitStashCommit } from '../../git/models/commit'; import type { GitFile } from '../../git/models/file'; import type { ViewsWithStashes } from '../viewBase'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues } from './abstract/viewNode'; import { CommitFileNodeBase } from './commitFileNode'; -import type { ViewNode } from './viewNode'; -import { ContextValues } from './viewNode'; export class StashFileNode extends CommitFileNodeBase<'stash-file', ViewsWithStashes> { constructor(view: ViewsWithStashes, parent: ViewNode, file: GitFile, commit: GitStashCommit) { diff --git a/src/views/nodes/stashNode.ts b/src/views/nodes/stashNode.ts index ba788b7f5ce3b..8d007cfc63ade 100644 --- a/src/views/nodes/stashNode.ts +++ b/src/views/nodes/stashNode.ts @@ -7,11 +7,12 @@ import { configuration } from '../../system/configuration'; import { joinPaths, normalizePath } from '../../system/path'; import { sortCompare } from '../../system/string'; import type { ViewsWithStashes } from '../viewBase'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; +import { ViewRefNode } from './abstract/viewRefNode'; import type { FileNode } from './folderNode'; import { FolderNode } from './folderNode'; import { StashFileNode } from './stashFileNode'; -import type { ViewNode } from './viewNode'; -import { ContextValues, getViewNodeId, ViewRefNode } from './viewNode'; export class StashNode extends ViewRefNode<'stash', ViewsWithStashes, GitStashReference> { constructor( diff --git a/src/views/nodes/stashesNode.ts b/src/views/nodes/stashesNode.ts index 0643b197a459e..10b7ba0fd4bbf 100644 --- a/src/views/nodes/stashesNode.ts +++ b/src/views/nodes/stashesNode.ts @@ -4,10 +4,11 @@ import type { Repository } from '../../git/models/repository'; import { debug } from '../../system/decorators/log'; import { map } from '../../system/iterable'; import type { ViewsWithStashesNode } from '../viewBase'; +import { CacheableChildrenViewNode } from './abstract/cacheableChildrenViewNode'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; import { MessageNode } from './common'; import { StashNode } from './stashNode'; -import type { ViewNode } from './viewNode'; -import { CacheableChildrenViewNode, ContextValues, getViewNodeId } from './viewNode'; export class StashesNode extends CacheableChildrenViewNode<'stashes', ViewsWithStashesNode> { constructor( diff --git a/src/views/nodes/statusFileNode.ts b/src/views/nodes/statusFileNode.ts index a1cd0fb63140b..48d6ad0893753 100644 --- a/src/views/nodes/statusFileNode.ts +++ b/src/views/nodes/statusFileNode.ts @@ -11,10 +11,11 @@ import { getGitFileStatusIcon } from '../../git/models/file'; import { joinPaths, relativeDir } from '../../system/path'; import { pluralize } from '../../system/string'; import type { ViewsWithCommits } from '../viewBase'; +import { ViewFileNode } from './abstract/viewFileNode'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues } from './abstract/viewNode'; import { FileRevisionAsCommitNode } from './fileRevisionAsCommitNode'; import type { FileNode } from './folderNode'; -import type { ViewNode } from './viewNode'; -import { ContextValues, ViewFileNode } from './viewNode'; export class StatusFileNode extends ViewFileNode<'status-file', ViewsWithCommits> implements FileNode { public readonly commits: GitCommit[]; diff --git a/src/views/nodes/statusFilesNode.ts b/src/views/nodes/statusFilesNode.ts index f8692ea54d252..cadf0d7a28b24 100644 --- a/src/views/nodes/statusFilesNode.ts +++ b/src/views/nodes/statusFilesNode.ts @@ -10,10 +10,10 @@ import { filter, flatMap, map } from '../../system/iterable'; import { joinPaths, normalizePath } from '../../system/path'; import { pluralize, sortCompare } from '../../system/string'; import type { ViewsWithWorkingTree } from '../viewBase'; +import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode'; import type { FileNode } from './folderNode'; import { FolderNode } from './folderNode'; import { StatusFileNode } from './statusFileNode'; -import { ContextValues, getViewNodeId, ViewNode } from './viewNode'; export class StatusFilesNode extends ViewNode<'status-files', ViewsWithWorkingTree> { constructor( diff --git a/src/views/nodes/tagNode.ts b/src/views/nodes/tagNode.ts index fffdb65355a22..2337b521e522e 100644 --- a/src/views/nodes/tagNode.ts +++ b/src/views/nodes/tagNode.ts @@ -11,11 +11,12 @@ import { debug } from '../../system/decorators/log'; import { map } from '../../system/iterable'; import { pad } from '../../system/string'; import type { ViewsWithTags } from '../viewBase'; +import type { PageableViewNode, ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; +import { ViewRefNode } from './abstract/viewRefNode'; import { CommitNode } from './commitNode'; import { LoadMoreNode, MessageNode } from './common'; import { insertDateMarkers } from './helpers'; -import type { PageableViewNode, ViewNode } from './viewNode'; -import { ContextValues, getViewNodeId, ViewRefNode } from './viewNode'; export class TagNode extends ViewRefNode<'tag', ViewsWithTags, GitTagReference> implements PageableViewNode { limit: number | undefined; diff --git a/src/views/nodes/tagsNode.ts b/src/views/nodes/tagsNode.ts index c6e72ac96990a..1ed3a3875b2af 100644 --- a/src/views/nodes/tagsNode.ts +++ b/src/views/nodes/tagsNode.ts @@ -4,11 +4,12 @@ import type { Repository } from '../../git/models/repository'; import { makeHierarchical } from '../../system/array'; import { debug } from '../../system/decorators/log'; import type { ViewsWithTagsNode } from '../viewBase'; +import { CacheableChildrenViewNode } from './abstract/cacheableChildrenViewNode'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; import { BranchOrTagFolderNode } from './branchOrTagFolderNode'; import { MessageNode } from './common'; import { TagNode } from './tagNode'; -import type { ViewNode } from './viewNode'; -import { CacheableChildrenViewNode, ContextValues, getViewNodeId } from './viewNode'; export class TagsNode extends CacheableChildrenViewNode<'tags', ViewsWithTagsNode> { constructor( diff --git a/src/views/nodes/viewNode.ts b/src/views/nodes/viewNode.ts deleted file mode 100644 index 972bb138cce75..0000000000000 --- a/src/views/nodes/viewNode.ts +++ /dev/null @@ -1,920 +0,0 @@ -import type { Command, Event, TreeViewVisibilityChangeEvent } from 'vscode'; -import { Disposable, MarkdownString, TreeItem, TreeItemCollapsibleState } from 'vscode'; -import type { - TreeViewFileNodeTypes, - TreeViewNodeTypes, - TreeViewRefFileNodeTypes, - TreeViewRefNodeTypes, - TreeViewSubscribableNodeTypes, -} from '../../constants'; -import { GlyphChars } from '../../constants'; -import type { RepositoriesChangeEvent } from '../../git/gitProviderService'; -import type { GitUri } from '../../git/gitUri'; -import { unknownGitUri } from '../../git/gitUri'; -import type { GitBranch } from '../../git/models/branch'; -import type { GitCommit } from '../../git/models/commit'; -import type { GitContributor } from '../../git/models/contributor'; -import type { GitFile } from '../../git/models/file'; -import type { GitReference, GitRevisionReference } from '../../git/models/reference'; -import { getReferenceLabel } from '../../git/models/reference'; -import type { GitReflogRecord } from '../../git/models/reflog'; -import { GitRemote } from '../../git/models/remote'; -import type { RepositoryChangeEvent } from '../../git/models/repository'; -import { Repository, RepositoryChange, RepositoryChangeComparisonMode } from '../../git/models/repository'; -import type { GitTag } from '../../git/models/tag'; -import type { GitWorktree } from '../../git/models/worktree'; -import type { SubscriptionChangeEvent } from '../../plus/subscription/subscriptionService'; -import type { - CloudWorkspace, - CloudWorkspaceRepositoryDescriptor, - LocalWorkspace, - LocalWorkspaceRepositoryDescriptor, -} from '../../plus/workspaces/models'; -import { gate } from '../../system/decorators/gate'; -import { debug, log, logName } from '../../system/decorators/log'; -import { weakEvent } from '../../system/event'; -import { is as isA, szudzikPairing } from '../../system/function'; -import { getLoggableName } from '../../system/logger'; -import { pad } from '../../system/string'; -import type { View } from '../viewBase'; -import { disposeChildren } from '../viewBase'; -import type { BranchNode } from './branchNode'; -import type { BranchTrackingStatus } from './branchTrackingStatusNode'; -import type { CommitFileNode } from './commitFileNode'; -import type { CommitNode } from './commitNode'; -import type { CompareBranchNode } from './compareBranchNode'; -import type { CompareResultsNode } from './compareResultsNode'; -import type { FileRevisionAsCommitNode } from './fileRevisionAsCommitNode'; -import type { FolderNode } from './folderNode'; -import type { LineHistoryTrackerNode } from './lineHistoryTrackerNode'; -import type { MergeConflictFileNode } from './mergeConflictFileNode'; -import type { RepositoryNode } from './repositoryNode'; -import type { ResultsCommitsNode } from './resultsCommitsNode'; -import type { ResultsFileNode } from './resultsFileNode'; -import type { StashFileNode } from './stashFileNode'; -import type { StashNode } from './stashNode'; -import type { StatusFileNode } from './statusFileNode'; -import type { TagNode } from './tagNode'; -import type { UncommittedFileNode } from './UncommittedFileNode'; - -export const enum ContextValues { - ActiveFileHistory = 'gitlens:history:active:file', - ActiveLineHistory = 'gitlens:history:active:line', - AutolinkedItems = 'gitlens:autolinked:items', - AutolinkedIssue = 'gitlens:autolinked:issue', - AutolinkedItem = 'gitlens:autolinked:item', - Branch = 'gitlens:branch', - Branches = 'gitlens:branches', - BranchStatusAheadOfUpstream = 'gitlens:status-branch:upstream:ahead', - BranchStatusBehindUpstream = 'gitlens:status-branch:upstream:behind', - BranchStatusNoUpstream = 'gitlens:status-branch:upstream:none', - BranchStatusSameAsUpstream = 'gitlens:status-branch:upstream:same', - BranchStatusFiles = 'gitlens:status-branch:files', - Commit = 'gitlens:commit', - Commits = 'gitlens:commits', - Compare = 'gitlens:compare', - CompareBranch = 'gitlens:compare:branch', - ComparePicker = 'gitlens:compare:picker', - ComparePickerWithRef = 'gitlens:compare:picker:ref', - CompareResults = 'gitlens:compare:results', - CompareResultsCommits = 'gitlens:compare:results:commits', - Contributor = 'gitlens:contributor', - Contributors = 'gitlens:contributors', - DateMarker = 'gitlens:date-marker', - File = 'gitlens:file', - FileHistory = 'gitlens:history:file', - Folder = 'gitlens:folder', - LineHistory = 'gitlens:history:line', - Merge = 'gitlens:merge', - MergeConflictCurrentChanges = 'gitlens:merge-conflict:current', - MergeConflictIncomingChanges = 'gitlens:merge-conflict:incoming', - Message = 'gitlens:message', - MessageSignIn = 'gitlens:message:signin', - Pager = 'gitlens:pager', - PullRequest = 'gitlens:pullrequest', - Rebase = 'gitlens:rebase', - Reflog = 'gitlens:reflog', - ReflogRecord = 'gitlens:reflog-record', - Remote = 'gitlens:remote', - Remotes = 'gitlens:remotes', - Repositories = 'gitlens:repositories', - Repository = 'gitlens:repository', - RepositoryFolder = 'gitlens:repo-folder', - ResultsFile = 'gitlens:file:results', - ResultsFiles = 'gitlens:results:files', - SearchAndCompare = 'gitlens:searchAndCompare', - SearchResults = 'gitlens:search:results', - SearchResultsCommits = 'gitlens:search:results:commits', - Stash = 'gitlens:stash', - Stashes = 'gitlens:stashes', - StatusFileCommits = 'gitlens:status:file:commits', - StatusFiles = 'gitlens:status:files', - StatusAheadOfUpstream = 'gitlens:status:upstream:ahead', - StatusBehindUpstream = 'gitlens:status:upstream:behind', - StatusNoUpstream = 'gitlens:status:upstream:none', - StatusSameAsUpstream = 'gitlens:status:upstream:same', - Tag = 'gitlens:tag', - Tags = 'gitlens:tags', - UncommittedFiles = 'gitlens:uncommitted:files', - Workspace = 'gitlens:workspace', - WorkspaceMissingRepository = 'gitlens:workspaceMissingRepository', - Workspaces = 'gitlens:workspaces', - Worktree = 'gitlens:worktree', - Worktrees = 'gitlens:worktrees', -} - -export interface AmbientContext { - readonly autolinksId?: string; - readonly branch?: GitBranch; - readonly branchStatus?: BranchTrackingStatus; - readonly branchStatusUpstreamType?: 'ahead' | 'behind' | 'same' | 'none'; - readonly commit?: GitCommit; - readonly comparisonId?: string; - readonly comparisonFiltered?: boolean; - readonly contributor?: GitContributor; - readonly file?: GitFile; - readonly reflog?: GitReflogRecord; - readonly remote?: GitRemote; - readonly repository?: Repository; - readonly root?: boolean; - readonly searchId?: string; - readonly status?: 'merging' | 'rebasing'; - readonly storedComparisonId?: string; - readonly tag?: GitTag; - readonly workspace?: CloudWorkspace | LocalWorkspace; - readonly wsRepositoryDescriptor?: CloudWorkspaceRepositoryDescriptor | LocalWorkspaceRepositoryDescriptor; - readonly worktree?: GitWorktree; -} - -export function getViewNodeId(type: string, context: AmbientContext): string { - let uniqueness = ''; - if (context.root) { - uniqueness += '/root'; - } - if (context.workspace != null) { - uniqueness += `/ws/${context.workspace.id}`; - } - if (context.wsRepositoryDescriptor != null) { - uniqueness += `/wsrepo/${context.wsRepositoryDescriptor.id}`; - } - if (context.repository != null) { - uniqueness += `/repo/${context.repository.id}`; - } - if (context.worktree != null) { - uniqueness += `/worktree/${context.worktree.uri.path}`; - } - if (context.remote != null) { - uniqueness += `/remote/${context.remote.name}`; - } - if (context.tag != null) { - uniqueness += `/tag/${context.tag.id}`; - } - if (context.branch != null) { - uniqueness += `/branch/${context.branch.id}`; - } - if (context.branchStatus != null) { - uniqueness += `/branch-status/${context.branchStatus.upstream ?? '-'}`; - } - if (context.branchStatusUpstreamType != null) { - uniqueness += `/branch-status-direction/${context.branchStatusUpstreamType}`; - } - if (context.status != null) { - uniqueness += `/status/${context.status}`; - } - if (context.reflog != null) { - uniqueness += `/reflog/${context.reflog.sha}+${context.reflog.selector}+${context.reflog.command}+${ - context.reflog.commandArgs ?? '' - }+${context.reflog.date.getTime()}`; - } - if (context.contributor != null) { - uniqueness += `/contributor/${ - context.contributor.id ?? - `${context.contributor.username}+${context.contributor.email}+${context.contributor.name}` - }`; - } - if (context.autolinksId != null) { - uniqueness += `/autolinks/${context.autolinksId}`; - } - if (context.comparisonId != null) { - uniqueness += `/comparison/${context.comparisonId}`; - } - if (context.searchId != null) { - uniqueness += `/search/${context.searchId}`; - } - if (context.commit != null) { - uniqueness += `/commit/${context.commit.sha}`; - } - if (context.file != null) { - uniqueness += `/file/${context.file.path}+${context.file.status}`; - } - - return `gitlens://viewnode/${type}${uniqueness}`; -} - -@logName((c, name) => `${name}${c.id != null ? `(${c.id})` : ''}`) -export abstract class ViewNode< - Type extends TreeViewNodeTypes = TreeViewNodeTypes, - TView extends View = View, - State extends object = any, -> implements Disposable -{ - is(type: T): this is TreeViewNodesByType[T] { - return this.type === (type as unknown as Type); - } - - protected _uniqueId!: string; - protected splatted = false; - // NOTE: @eamodio uncomment to track node leaks - // readonly uuid = uuid(); - - constructor( - public readonly type: Type, - // public readonly id: string | undefined, - uri: GitUri, - public readonly view: TView, - protected parent?: ViewNode, - ) { - // NOTE: @eamodio uncomment to track node leaks - // queueMicrotask(() => this.view.registerNode(this)); - this._uri = uri; - } - - protected _disposed = false; - @debug() - dispose() { - this._disposed = true; - // NOTE: @eamodio uncomment to track node leaks - // this.view.unregisterNode(this); - } - - get id(): string | undefined { - return this._uniqueId; - } - - private _context: AmbientContext | undefined; - protected get context(): AmbientContext { - return this._context ?? this.parent?.context ?? {}; - } - - protected updateContext(context: AmbientContext, reset: boolean = false) { - this._context = this.getNewContext(context, reset); - } - - protected getNewContext(context: AmbientContext, reset: boolean = false) { - return { ...(reset ? this.parent?.context : this.context), ...context }; - } - - toClipboard?(): string; - - toString(): string { - const id = this.id; - return `${getLoggableName(this)}${id != null ? `(${id})` : ''}`; - } - - protected _uri: GitUri; - get uri(): GitUri { - return this._uri; - } - - abstract getChildren(): ViewNode[] | Promise; - - getParent(): ViewNode | undefined { - // If this node's parent has been splatted (e.g. not shown itself, but its children are), then return its grandparent - return this.parent?.splatted ? this.parent?.getParent() : this.parent; - } - - abstract getTreeItem(): TreeItem | Promise; - - resolveTreeItem?(item: TreeItem): TreeItem | Promise; - - getCommand(): Command | undefined { - return undefined; - } - - refresh?(reset?: boolean): boolean | void | Promise | Promise; - - @gate((reset, force, avoidSelf) => `${reset}|${force}|${avoidSelf?.toString()}`) - @debug() - triggerChange(reset: boolean = false, force: boolean = false, avoidSelf?: ViewNode): Promise { - if (this._disposed) return Promise.resolve(); - - // If this node has been splatted (e.g. not shown itself, but its children are), then delegate the change to its parent - if (this.splatted && this.parent != null && this.parent !== avoidSelf) { - return this.parent.triggerChange(reset, force); - } - - return this.view.refreshNode(this, reset, force); - } - - getSplattedChild?(): Promise; - - deleteState = StateKey>(key?: T): void { - if (this.id == null) { - debugger; - throw new Error('Id is required to delete state'); - } - this.view.nodeState.deleteState(this.id, key as string); - } - - getState = StateKey>(key: T): StateValue | undefined { - if (this.id == null) { - debugger; - throw new Error('Id is required to get state'); - } - return this.view.nodeState.getState(this.id, key as string); - } - - storeState = StateKey>( - key: T, - value: StateValue, - sticky?: boolean, - ): void { - if (this.id == null) { - debugger; - throw new Error('Id is required to store state'); - } - this.view.nodeState.storeState(this.id, key as string, value, sticky); - } -} - -type StateKey = keyof T; -type StateValue> = P extends keyof T ? T[P] : never; - -export abstract class CacheableChildrenViewNode< - Type extends TreeViewNodeTypes = TreeViewNodeTypes, - TView extends View = View, - TChild extends ViewNode = ViewNode, - State extends object = any, -> extends ViewNode { - private _children: TChild[] | undefined; - protected get children(): TChild[] | undefined { - return this._children; - } - protected set children(value: TChild[] | undefined) { - if (this._children === value) return; - - disposeChildren(this._children, value); - this._children = value; - } - - @debug() - override dispose() { - super.dispose(); - this.children = undefined; - } - - @debug() - override refresh(reset: boolean = false) { - if (reset) { - this.children = undefined; - } - } -} - -export abstract class ViewFileNode< - Type extends TreeViewFileNodeTypes = TreeViewFileNodeTypes, - TView extends View = View, - State extends object = any, -> extends ViewNode { - constructor( - type: Type, - uri: GitUri, - view: TView, - public override parent: ViewNode, - public readonly file: GitFile, - ) { - super(type, uri, view, parent); - } - - get repoPath(): string { - return this.uri.repoPath!; - } - - override toString(): string { - return `${super.toString()}:${this.file.path}`; - } -} - -export abstract class ViewRefNode< - Type extends TreeViewRefNodeTypes = TreeViewRefNodeTypes, - TView extends View = View, - TReference extends GitReference = GitReference, - State extends object = any, -> extends ViewNode { - constructor( - type: Type, - uri: GitUri, - view: TView, - protected override readonly parent: ViewNode, - ) { - super(type, uri, view, parent); - } - - abstract get ref(): TReference; - - get repoPath(): string { - return this.uri.repoPath!; - } - - override toString(): string { - return `${super.toString()}:${getReferenceLabel(this.ref, false)}`; - } -} - -export abstract class ViewRefFileNode< - Type extends TreeViewRefFileNodeTypes = TreeViewRefFileNodeTypes, - TView extends View = View, - State extends object = any, -> extends ViewFileNode { - abstract get ref(): GitRevisionReference; - - override toString(): string { - return `${super.toString()}:${this.file.path}`; - } -} - -export interface PageableViewNode extends ViewNode { - readonly id: string; - limit?: number; - readonly hasMore: boolean; - loadMore(limit?: number | { until?: string | undefined }, context?: Record): Promise; -} - -export function isPageableViewNode(node: ViewNode): node is ViewNode & PageableViewNode { - return isA(node, 'loadMore'); -} - -export abstract class SubscribeableViewNode< - Type extends TreeViewSubscribableNodeTypes = TreeViewSubscribableNodeTypes, - TView extends View = View, - TChild extends ViewNode = ViewNode, - State extends object = any, -> extends CacheableChildrenViewNode { - protected disposable: Disposable; - protected subscription: Promise | undefined; - - protected loaded: boolean = false; - - constructor(type: Type, uri: GitUri, view: TView, parent?: ViewNode) { - super(type, uri, view, parent); - - const disposables = [ - weakEvent(this.view.onDidChangeVisibility, this.onVisibilityChanged, this), - // weak(this.view.onDidChangeNodeCollapsibleState, this.onNodeCollapsibleStateChanged, this), - ]; - - if (canAutoRefreshView(this.view)) { - disposables.push(weakEvent(this.view.onDidChangeAutoRefresh, this.onAutoRefreshChanged, this)); - } - - const getTreeItem = this.getTreeItem; - this.getTreeItem = function (this: SubscribeableViewNode) { - this.loaded = true; - void this.ensureSubscription(); - return getTreeItem.apply(this); - }; - - const getChildren = this.getChildren; - this.getChildren = function (this: SubscribeableViewNode) { - this.loaded = true; - void this.ensureSubscription(); - return getChildren.apply(this); - }; - - this.disposable = Disposable.from(...disposables); - } - - @debug() - override dispose() { - super.dispose(); - void this.unsubscribe(); - - this.disposable?.dispose(); - } - - @gate((reset, force) => `${reset}|${force}`) - @debug() - override async triggerChange(reset: boolean = false, force: boolean = false): Promise { - if (!this.loaded || this._disposed) return; - - if (reset && !this.view.visible) { - this._pendingReset = reset; - } - await super.triggerChange(reset, force); - } - - private _canSubscribe: boolean = true; - protected get canSubscribe(): boolean { - return this._canSubscribe && !this._disposed; - } - protected set canSubscribe(value: boolean) { - if (this._canSubscribe === value) return; - - this._canSubscribe = value; - - void this.ensureSubscription(); - if (value) { - void this.triggerChange(); - } - } - - private _etag: number | undefined; - protected abstract etag(): number; - - private _pendingReset: boolean = false; - private get requiresResetOnVisible(): boolean { - let reset = this._pendingReset; - this._pendingReset = false; - - const etag = this.etag(); - if (etag !== this._etag) { - this._etag = etag; - reset = true; - } - - return reset; - } - - protected abstract subscribe(): Disposable | undefined | Promise; - - @debug() - protected async unsubscribe(): Promise { - this._etag = this.etag(); - - if (this.subscription != null) { - const subscriptionPromise = this.subscription; - this.subscription = undefined; - - (await subscriptionPromise)?.dispose(); - } - } - - @debug() - protected onAutoRefreshChanged() { - this.onVisibilityChanged({ visible: this.view.visible }); - } - - // protected onParentCollapsibleStateChanged?(state: TreeItemCollapsibleState): void; - // protected onCollapsibleStateChanged?(state: TreeItemCollapsibleState): void; - // protected collapsibleState: TreeItemCollapsibleState | undefined; - // protected onNodeCollapsibleStateChanged(e: TreeViewNodeCollapsibleStateChangeEvent) { - // if (e.element === this) { - // this.collapsibleState = e.state; - // if (this.onCollapsibleStateChanged !== undefined) { - // this.onCollapsibleStateChanged(e.state); - // } - // } else if (e.element === this.parent) { - // if (this.onParentCollapsibleStateChanged !== undefined) { - // this.onParentCollapsibleStateChanged(e.state); - // } - // } - // } - - @debug() - protected onVisibilityChanged(e: TreeViewVisibilityChangeEvent) { - void this.ensureSubscription(); - - if (e.visible) { - void this.triggerChange(this.requiresResetOnVisible); - } - } - - @gate() - @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 || (canAutoRefreshView(this.view) && !this.view.autoRefresh)) { - await this.unsubscribe(); - - return; - } - - // If we already have a subscription, just kick out - if (this.subscription != null) return; - - this.subscription = Promise.resolve(this.subscribe()); - void (await this.subscription); - } - - @gate() - @debug() - async resetSubscription() { - await this.unsubscribe(); - await this.ensureSubscription(); - } -} - -export abstract class RepositoryFolderNode< - TView extends View = View, - TChild extends ViewNode = ViewNode, -> extends SubscribeableViewNode<'repo-folder', TView> { - protected override splatted = true; - - constructor( - uri: GitUri, - view: TView, - protected override readonly parent: ViewNode, - public readonly repo: Repository, - splatted: boolean, - private readonly options?: { showBranchAndLastFetched?: boolean }, - ) { - super('repo-folder', uri, view, parent); - - this.updateContext({ repository: this.repo }); - this._uniqueId = getViewNodeId(this.type, this.context); - - this.splatted = splatted; - } - - private _child: TChild | undefined; - protected get child(): TChild | undefined { - return this._child; - } - protected set child(value: TChild | undefined) { - if (this._child === value) return; - - this._child?.dispose(); - this._child = value; - } - - @debug() - override dispose() { - super.dispose(); - this.child = undefined; - } - - override get id(): string { - return this._uniqueId; - } - - override toClipboard(): string { - return this.repo.path; - } - - get repoPath(): string { - return this.repo.path; - } - - async getTreeItem(): Promise { - this.splatted = false; - - const branch = await this.repo.getBranch(); - const ahead = (branch?.state.ahead ?? 0) > 0; - const behind = (branch?.state.behind ?? 0) > 0; - - const expand = ahead || behind || this.repo.starred || this.view.container.git.isRepositoryForEditor(this.repo); - - const item = new TreeItem( - this.repo.formattedName ?? this.uri.repoPath ?? '', - expand ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.Collapsed, - ); - item.contextValue = `${ContextValues.RepositoryFolder}${this.repo.starred ? '+starred' : ''}`; - if (ahead) { - item.contextValue += '+ahead'; - } - if (behind) { - item.contextValue += '+behind'; - } - if (this.view.type === 'commits' && this.view.state.filterCommits.get(this.repo.id)?.length) { - item.contextValue += '+filtered'; - } - - if (branch != null && this.options?.showBranchAndLastFetched) { - const lastFetched = (await this.repo.getLastFetched()) ?? 0; - - const status = branch.getTrackingStatus(); - item.description = `${status ? `${status}${pad(GlyphChars.Dot, 1, 1)}` : ''}${branch.name}${ - lastFetched - ? `${pad(GlyphChars.Dot, 1, 1)}Last fetched ${Repository.formatLastFetched(lastFetched)}` - : '' - }`; - - let providerName; - if (branch.upstream != null) { - const providers = GitRemote.getHighlanderProviders( - await this.view.container.git.getRemotesWithProviders(branch.repoPath), - ); - providerName = providers?.length ? providers[0].name : undefined; - } else { - const remote = await branch.getRemote(); - providerName = remote?.provider?.name; - } - - item.tooltip = new MarkdownString( - `${this.repo.formattedName ?? this.uri.repoPath ?? ''}${ - lastFetched - ? `${pad(GlyphChars.Dash, 2, 2)}Last fetched ${Repository.formatLastFetched( - lastFetched, - false, - )}` - : '' - }${this.repo.formattedName ? `\n${this.uri.repoPath}` : ''}\n\nCurrent branch $(git-branch) ${ - branch.name - }${ - branch.upstream != null - ? ` is ${branch.getTrackingStatus({ - empty: branch.upstream.missing - ? `missing upstream $(git-branch) ${branch.upstream.name}` - : `up to date with $(git-branch) ${branch.upstream.name}${ - providerName ? ` on ${providerName}` : '' - }`, - expand: true, - icons: true, - separator: ', ', - suffix: ` $(git-branch) ${branch.upstream.name}${ - providerName ? ` on ${providerName}` : '' - }`, - })}` - : `hasn't been published to ${providerName ?? 'a remote'}` - }`, - true, - ); - } else { - item.tooltip = `${ - this.repo.formattedName ? `${this.repo.formattedName}\n${this.uri.repoPath}` : this.uri.repoPath ?? '' - }`; - } - - return item; - } - - override async getSplattedChild() { - if (this.child == null) { - await this.getChildren(); - } - - return this.child; - } - - @gate() - @debug() - override async refresh(reset: boolean = false) { - super.refresh(reset); - await this.child?.triggerChange(reset, false, this); - - await this.ensureSubscription(); - } - - @log() - async star() { - await this.repo.star(); - // void this.parent!.triggerChange(); - } - - @log() - async unstar() { - await this.repo.unstar(); - // void this.parent!.triggerChange(); - } - - @debug() - protected subscribe(): Disposable | Promise { - return weakEvent(this.repo.onDidChange, this.onRepositoryChanged, this); - } - - protected override etag(): number { - return this.repo.etag; - } - - protected abstract changed(e: RepositoryChangeEvent): boolean; - - @debug({ args: { 0: e => e.toString() } }) - private onRepositoryChanged(e: RepositoryChangeEvent) { - if (e.changed(RepositoryChange.Closed, RepositoryChangeComparisonMode.Any)) { - this.dispose(); - void this.parent?.triggerChange(true); - - return; - } - - if ( - e.changed(RepositoryChange.Opened, RepositoryChangeComparisonMode.Any) || - e.changed(RepositoryChange.Starred, RepositoryChangeComparisonMode.Any) - ) { - void this.parent?.triggerChange(true); - - return; - } - - if (this.changed(e)) { - void (this.loaded ? this : this.parent ?? this).triggerChange(true); - } - } -} - -export abstract class RepositoriesSubscribeableNode< - TView extends View = View, - TChild extends ViewNode = ViewNode, -> extends SubscribeableViewNode<'repositories', TView, TChild> { - protected override splatted = true; - - constructor(view: TView) { - super('repositories', unknownGitUri, view); - } - - override async getSplattedChild() { - if (this.children == null) { - await this.getChildren(); - } - - return this.children?.length === 1 ? this.children[0] : undefined; - } - - protected override etag(): number { - return szudzikPairing(this.view.container.git.etag, this.view.container.subscription.etag); - } - - @debug() - protected subscribe(): Disposable | Promise { - return Disposable.from( - weakEvent(this.view.container.git.onDidChangeRepositories, this.onRepositoriesChanged, this), - weakEvent(this.view.container.subscription.onDidChange, this.onSubscriptionChanged, this), - ); - } - - private onRepositoriesChanged(_e: RepositoriesChangeEvent) { - void this.triggerChange(true); - } - - private onSubscriptionChanged(e: SubscriptionChangeEvent) { - if (e.current.plan !== e.previous.plan) { - void this.triggerChange(true); - } - } -} - -interface AutoRefreshableView { - autoRefresh: boolean; - onDidChangeAutoRefresh: Event; -} - -export function canAutoRefreshView(view: View): view is View & AutoRefreshableView { - return isA(view, 'onDidChangeAutoRefresh'); -} - -export function canEditNode(node: ViewNode): node is ViewNode & { edit(): void | Promise } { - return typeof (node as ViewNode & { edit(): void | Promise }).edit === 'function'; -} - -export function canGetNodeRepoPath(node?: ViewNode): node is ViewNode & { repoPath: string | undefined } { - return node != null && 'repoPath' in node && typeof node.repoPath === 'string'; -} - -export function canViewDismissNode(view: View): view is View & { dismissNode(node: ViewNode): void } { - return typeof (view as View & { dismissNode(node: ViewNode): void }).dismissNode === 'function'; -} - -export function getNodeRepoPath(node?: ViewNode): string | undefined { - return canGetNodeRepoPath(node) ? node.repoPath : undefined; -} - -type TreeViewNodesByType = { - [T in TreeViewNodeTypes]: T extends 'branch' - ? BranchNode - : T extends 'commit' - ? CommitNode - : T extends 'commit-file' - ? CommitFileNode - : T extends 'compare-branch' - ? CompareBranchNode - : T extends 'compare-results' - ? CompareResultsNode - : T extends 'conflict-file' - ? MergeConflictFileNode - : T extends 'file-commit' - ? FileRevisionAsCommitNode - : T extends 'folder' - ? FolderNode - : T extends 'line-history-tracker' - ? LineHistoryTrackerNode - : T extends 'repository' - ? RepositoryNode - : T extends 'repo-folder' - ? RepositoryFolderNode - : T extends 'results-commits' - ? ResultsCommitsNode - : T extends 'results-file' - ? ResultsFileNode - : T extends 'stash' - ? StashNode - : T extends 'stash-file' - ? StashFileNode - : T extends 'status-file' - ? StatusFileNode - : T extends 'tag' - ? TagNode - : T extends 'uncommitted-file' - ? UncommittedFileNode - : ViewNode; -}; - -export function isViewNode(node: unknown): node is ViewNode; -export function isViewNode(node: unknown, type: T): node is TreeViewNodesByType[T]; -export function isViewNode(node: unknown, type?: T): node is ViewNode { - if (node == null) return false; - return node instanceof ViewNode ? type == null || node.type === type : false; -} - -export function isViewFileNode(node: unknown): node is ViewFileNode { - return node instanceof ViewFileNode; -} diff --git a/src/views/nodes/workspaceMissingRepositoryNode.ts b/src/views/nodes/workspaceMissingRepositoryNode.ts index 302c16e3c2432..16a8dba4292e0 100644 --- a/src/views/nodes/workspaceMissingRepositoryNode.ts +++ b/src/views/nodes/workspaceMissingRepositoryNode.ts @@ -8,7 +8,7 @@ import type { LocalWorkspaceRepositoryDescriptor, } from '../../plus/workspaces/models'; import type { WorkspacesView } from '../workspacesView'; -import { ContextValues, getViewNodeId, ViewNode } from './viewNode'; +import { ContextValues, getViewNodeId, ViewNode } from './abstract/viewNode'; export class WorkspaceMissingRepositoryNode extends ViewNode<'workspace-missing-repository', WorkspacesView> { constructor( diff --git a/src/views/nodes/workspaceNode.ts b/src/views/nodes/workspaceNode.ts index dbcb30ce7f445..49e1d601adfd5 100644 --- a/src/views/nodes/workspaceNode.ts +++ b/src/views/nodes/workspaceNode.ts @@ -6,10 +6,11 @@ import { createCommand } from '../../system/command'; import { debug } from '../../system/decorators/log'; import { weakEvent } from '../../system/event'; import type { WorkspacesView } from '../workspacesView'; +import { SubscribeableViewNode } from './abstract/subscribeableViewNode'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; import { CommandMessageNode, MessageNode } from './common'; import { RepositoryNode } from './repositoryNode'; -import type { ViewNode } from './viewNode'; -import { ContextValues, getViewNodeId, SubscribeableViewNode } from './viewNode'; import { WorkspaceMissingRepositoryNode } from './workspaceMissingRepositoryNode'; export class WorkspaceNode extends SubscribeableViewNode< diff --git a/src/views/nodes/worktreeNode.ts b/src/views/nodes/worktreeNode.ts index fda5f730aee99..99459f9ffbc43 100644 --- a/src/views/nodes/worktreeNode.ts +++ b/src/views/nodes/worktreeNode.ts @@ -16,14 +16,15 @@ import type { Deferred } from '../../system/promise'; import { defer, getSettledValue } from '../../system/promise'; import { pad } from '../../system/string'; import type { ViewsWithWorktrees } from '../viewBase'; +import { CacheableChildrenViewNode } from './abstract/cacheableChildrenViewNode'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; import { CommitNode } from './commitNode'; import { LoadMoreNode, MessageNode } from './common'; import { CompareBranchNode } from './compareBranchNode'; import { insertDateMarkers } from './helpers'; import { PullRequestNode } from './pullRequestNode'; import { UncommittedFilesNode } from './UncommittedFilesNode'; -import type { ViewNode } from './viewNode'; -import { CacheableChildrenViewNode, ContextValues, getViewNodeId } from './viewNode'; type State = { pullRequest: PullRequest | null | undefined; diff --git a/src/views/nodes/worktreesNode.ts b/src/views/nodes/worktreesNode.ts index 33dae0140d005..ccc5b6629303d 100644 --- a/src/views/nodes/worktreesNode.ts +++ b/src/views/nodes/worktreesNode.ts @@ -5,9 +5,10 @@ import type { GitUri } from '../../git/gitUri'; import type { Repository } from '../../git/models/repository'; import { debug } from '../../system/decorators/log'; import type { ViewsWithWorktreesNode } from '../viewBase'; +import { CacheableChildrenViewNode } from './abstract/cacheableChildrenViewNode'; +import type { ViewNode } from './abstract/viewNode'; +import { ContextValues, getViewNodeId } from './abstract/viewNode'; import { MessageNode } from './common'; -import type { ViewNode } from './viewNode'; -import { CacheableChildrenViewNode, ContextValues, getViewNodeId } from './viewNode'; import { WorktreeNode } from './worktreeNode'; export class WorktreesNode extends CacheableChildrenViewNode<'worktrees', ViewsWithWorktreesNode, WorktreeNode> { diff --git a/src/views/remotesView.ts b/src/views/remotesView.ts index 82472bcf65f83..fc91ba1cf0bb0 100644 --- a/src/views/remotesView.ts +++ b/src/views/remotesView.ts @@ -15,13 +15,14 @@ import { RepositoryChange, RepositoryChangeComparisonMode } from '../git/models/ import { executeCommand } from '../system/command'; import { configuration } from '../system/configuration'; import { gate } from '../system/decorators/gate'; +import { RepositoriesSubscribeableNode } from './nodes/abstract/repositoriesSubscribeableNode'; +import { RepositoryFolderNode } from './nodes/abstract/repositoryFolderNode'; +import type { ViewNode } from './nodes/abstract/viewNode'; import { BranchNode } from './nodes/branchNode'; import { BranchOrTagFolderNode } from './nodes/branchOrTagFolderNode'; import { RemoteNode } from './nodes/remoteNode'; import { RemotesNode } from './nodes/remotesNode'; import { RepositoryNode } from './nodes/repositoryNode'; -import type { ViewNode } from './nodes/viewNode'; -import { RepositoriesSubscribeableNode, RepositoryFolderNode } from './nodes/viewNode'; import { ViewBase } from './viewBase'; import { registerViewCommand } from './viewCommands'; diff --git a/src/views/searchAndCompareView.ts b/src/views/searchAndCompareView.ts index e01d6e3df34b4..d44d56ca7c95b 100644 --- a/src/views/searchAndCompareView.ts +++ b/src/views/searchAndCompareView.ts @@ -19,11 +19,12 @@ import { gate } from '../system/decorators/gate'; import { debug, log } from '../system/decorators/log'; import { updateRecordValue } from '../system/object'; import { isPromise } from '../system/promise'; +import { RepositoryFolderNode } from './nodes/abstract/repositoryFolderNode'; +import { ContextValues, ViewNode } from './nodes/abstract/viewNode'; import { ComparePickerNode } from './nodes/comparePickerNode'; import { CompareResultsNode, restoreComparisonCheckedFiles } from './nodes/compareResultsNode'; import { FilesQueryFilter, ResultsFilesNode } from './nodes/resultsFilesNode'; import { SearchResultsNode } from './nodes/searchResultsNode'; -import { ContextValues, RepositoryFolderNode, ViewNode } from './nodes/viewNode'; import { disposeChildren, ViewBase } from './viewBase'; import { registerViewCommand } from './viewCommands'; diff --git a/src/views/stashesView.ts b/src/views/stashesView.ts index df0f809876e1a..aaf66affba8e6 100644 --- a/src/views/stashesView.ts +++ b/src/views/stashesView.ts @@ -11,9 +11,10 @@ import { RepositoryChange, RepositoryChangeComparisonMode } from '../git/models/ import { executeCommand } from '../system/command'; import { configuration } from '../system/configuration'; import { gate } from '../system/decorators/gate'; +import { RepositoriesSubscribeableNode } from './nodes/abstract/repositoriesSubscribeableNode'; +import { RepositoryFolderNode } from './nodes/abstract/repositoryFolderNode'; +import type { ViewNode } from './nodes/abstract/viewNode'; import { StashesNode } from './nodes/stashesNode'; -import type { ViewNode } from './nodes/viewNode'; -import { RepositoriesSubscribeableNode, RepositoryFolderNode } from './nodes/viewNode'; import { ViewBase } from './viewBase'; import { registerViewCommand } from './viewCommands'; diff --git a/src/views/tagsView.ts b/src/views/tagsView.ts index 47b953cf141c1..028bba5e6ce5a 100644 --- a/src/views/tagsView.ts +++ b/src/views/tagsView.ts @@ -11,10 +11,11 @@ import { RepositoryChange, RepositoryChangeComparisonMode } from '../git/models/ import { executeCommand } from '../system/command'; import { configuration } from '../system/configuration'; import { gate } from '../system/decorators/gate'; +import { RepositoriesSubscribeableNode } from './nodes/abstract/repositoriesSubscribeableNode'; +import { RepositoryFolderNode } from './nodes/abstract/repositoryFolderNode'; +import type { ViewNode } from './nodes/abstract/viewNode'; import { BranchOrTagFolderNode } from './nodes/branchOrTagFolderNode'; import { TagsNode } from './nodes/tagsNode'; -import type { ViewNode } from './nodes/viewNode'; -import { RepositoriesSubscribeableNode, RepositoryFolderNode } from './nodes/viewNode'; import { ViewBase } from './viewBase'; import { registerViewCommand } from './viewCommands'; diff --git a/src/views/viewBase.ts b/src/views/viewBase.ts index b5b004a0d0503..191cf5ffbd83f 100644 --- a/src/views/viewBase.ts +++ b/src/views/viewBase.ts @@ -44,8 +44,8 @@ import type { CommitsView } from './commitsView'; import type { ContributorsView } from './contributorsView'; import type { FileHistoryView } from './fileHistoryView'; import type { LineHistoryView } from './lineHistoryView'; -import type { PageableViewNode, ViewNode } from './nodes/viewNode'; -import { isPageableViewNode } from './nodes/viewNode'; +import type { PageableViewNode, ViewNode } from './nodes/abstract/viewNode'; +import { isPageableViewNode } from './nodes/abstract/viewNode'; import type { RemotesView } from './remotesView'; import type { RepositoriesView } from './repositoriesView'; import type { SearchAndCompareView } from './searchAndCompareView'; diff --git a/src/views/viewCommands.ts b/src/views/viewCommands.ts index 388fcf38d3ec8..5cd3818827372 100644 --- a/src/views/viewCommands.ts +++ b/src/views/viewCommands.ts @@ -37,6 +37,15 @@ import { log } from '../system/decorators/log'; import { sequentialize } from '../system/function'; import type { OpenWorkspaceLocation } from '../system/utils'; import { openWorkspace } from '../system/utils'; +import { RepositoryFolderNode } from './nodes/abstract/repositoryFolderNode'; +import { + canEditNode, + canViewDismissNode, + getNodeRepoPath, + isPageableViewNode, + ViewNode, +} from './nodes/abstract/viewNode'; +import { ViewRefFileNode, ViewRefNode } from './nodes/abstract/viewRefNode'; import type { BranchesNode } from './nodes/branchesNode'; import { BranchNode } from './nodes/branchNode'; import { BranchTrackingStatusNode } from './nodes/branchTrackingStatusNode'; @@ -60,16 +69,6 @@ import { StashNode } from './nodes/stashNode'; import { StatusFileNode } from './nodes/statusFileNode'; import { TagNode } from './nodes/tagNode'; import type { TagsNode } from './nodes/tagsNode'; -import { - canEditNode, - canViewDismissNode, - getNodeRepoPath, - isPageableViewNode, - RepositoryFolderNode, - ViewNode, - ViewRefFileNode, - ViewRefNode, -} from './nodes/viewNode'; import { WorktreeNode } from './nodes/worktreeNode'; import { WorktreesNode } from './nodes/worktreesNode'; diff --git a/src/views/workspacesView.ts b/src/views/workspacesView.ts index fdd52cc1a3e88..2d5874829c58b 100644 --- a/src/views/workspacesView.ts +++ b/src/views/workspacesView.ts @@ -10,10 +10,10 @@ import { executeCommand } from '../system/command'; import { gate } from '../system/decorators/gate'; import { debug } from '../system/decorators/log'; import { openWorkspace } from '../system/utils'; +import { ViewNode } from './nodes/abstract/viewNode'; import { MessageNode } from './nodes/common'; import { RepositoriesNode } from './nodes/repositoriesNode'; import { RepositoryNode } from './nodes/repositoryNode'; -import { ViewNode } from './nodes/viewNode'; import type { WorkspaceMissingRepositoryNode } from './nodes/workspaceMissingRepositoryNode'; import { WorkspaceNode } from './nodes/workspaceNode'; import { disposeChildren, ViewBase } from './viewBase'; diff --git a/src/views/worktreesView.ts b/src/views/worktreesView.ts index 91ec8283b4de4..2a6eb62509f26 100644 --- a/src/views/worktreesView.ts +++ b/src/views/worktreesView.ts @@ -13,8 +13,9 @@ import { ensurePlusFeaturesEnabled } from '../plus/subscription/utils'; import { executeCommand } from '../system/command'; import { configuration } from '../system/configuration'; import { gate } from '../system/decorators/gate'; -import type { ViewNode } from './nodes/viewNode'; -import { RepositoriesSubscribeableNode, RepositoryFolderNode } from './nodes/viewNode'; +import { RepositoriesSubscribeableNode } from './nodes/abstract/repositoriesSubscribeableNode'; +import { RepositoryFolderNode } from './nodes/abstract/repositoryFolderNode'; +import type { ViewNode } from './nodes/abstract/viewNode'; import { WorktreeNode } from './nodes/worktreeNode'; import { WorktreesNode } from './nodes/worktreesNode'; import { ViewBase } from './viewBase';