瀏覽代碼

Splits & moves abstract node classes to new files

main
Eric Amodio 1 年之前
父節點
當前提交
8aa81e99a9
共有 77 個文件被更改,包括 1115 次插入1044 次删除
  1. +2
    -1
      src/commands/base.ts
  2. +1
    -1
      src/extension.ts
  3. +1
    -1
      src/plus/webviews/graph/graphWebview.ts
  4. +2
    -2
      src/plus/webviews/timeline/timelineWebview.ts
  5. +1
    -1
      src/system/decorators/resolver.ts
  6. +3
    -2
      src/views/branchesView.ts
  7. +3
    -2
      src/views/commitsView.ts
  8. +3
    -2
      src/views/contributorsView.ts
  9. +3
    -2
      src/views/nodes/UncommittedFileNode.ts
  10. +1
    -1
      src/views/nodes/UncommittedFilesNode.ts
  11. +36
    -0
      src/views/nodes/abstract/cacheableChildrenViewNode.ts
  12. +51
    -0
      src/views/nodes/abstract/repositoriesSubscribeableNode.ts
  13. +213
    -0
      src/views/nodes/abstract/repositoryFolderNode.ts
  14. +169
    -0
      src/views/nodes/abstract/subscribeableViewNode.ts
  15. +33
    -0
      src/views/nodes/abstract/viewFileNode.ts
  16. +408
    -0
      src/views/nodes/abstract/viewNode.ts
  17. +45
    -0
      src/views/nodes/abstract/viewRefNode.ts
  18. +1
    -1
      src/views/nodes/autolinkedItemNode.ts
  19. +3
    -2
      src/views/nodes/autolinkedItemsNode.ts
  20. +3
    -2
      src/views/nodes/branchNode.ts
  21. +1
    -1
      src/views/nodes/branchOrTagFolderNode.ts
  22. +1
    -1
      src/views/nodes/branchTrackingStatusFilesNode.ts
  23. +2
    -2
      src/views/nodes/branchTrackingStatusNode.ts
  24. +3
    -2
      src/views/nodes/branchesNode.ts
  25. +3
    -2
      src/views/nodes/commitFileNode.ts
  26. +3
    -2
      src/views/nodes/commitNode.ts
  27. +2
    -2
      src/views/nodes/common.ts
  28. +3
    -2
      src/views/nodes/compareBranchNode.ts
  29. +1
    -1
      src/views/nodes/comparePickerNode.ts
  30. +3
    -2
      src/views/nodes/compareResultsNode.ts
  31. +2
    -2
      src/views/nodes/contributorNode.ts
  32. +3
    -2
      src/views/nodes/contributorsNode.ts
  33. +3
    -2
      src/views/nodes/fileHistoryNode.ts
  34. +3
    -2
      src/views/nodes/fileHistoryTrackerNode.ts
  35. +3
    -2
      src/views/nodes/fileRevisionAsCommitNode.ts
  36. +2
    -2
      src/views/nodes/folderNode.ts
  37. +2
    -2
      src/views/nodes/helpers.ts
  38. +3
    -2
      src/views/nodes/lineHistoryNode.ts
  39. +3
    -2
      src/views/nodes/lineHistoryTrackerNode.ts
  40. +1
    -1
      src/views/nodes/mergeConflictCurrentChangesNode.ts
  41. +3
    -2
      src/views/nodes/mergeConflictFileNode.ts
  42. +1
    -1
      src/views/nodes/mergeConflictFilesNode.ts
  43. +1
    -1
      src/views/nodes/mergeConflictIncomingChangesNode.ts
  44. +1
    -1
      src/views/nodes/mergeStatusNode.ts
  45. +1
    -1
      src/views/nodes/pullRequestNode.ts
  46. +1
    -1
      src/views/nodes/rebaseCommitNode.ts
  47. +1
    -1
      src/views/nodes/rebaseStatusNode.ts
  48. +3
    -2
      src/views/nodes/reflogNode.ts
  49. +2
    -2
      src/views/nodes/reflogRecordNode.ts
  50. +1
    -1
      src/views/nodes/remoteNode.ts
  51. +3
    -2
      src/views/nodes/remotesNode.ts
  52. +3
    -2
      src/views/nodes/repositoriesNode.ts
  53. +3
    -2
      src/views/nodes/repositoryNode.ts
  54. +2
    -2
      src/views/nodes/resultsCommitsNode.ts
  55. +3
    -2
      src/views/nodes/resultsFileNode.ts
  56. +1
    -1
      src/views/nodes/resultsFilesNode.ts
  57. +2
    -2
      src/views/nodes/searchResultsNode.ts
  58. +2
    -2
      src/views/nodes/stashFileNode.ts
  59. +3
    -2
      src/views/nodes/stashNode.ts
  60. +3
    -2
      src/views/nodes/stashesNode.ts
  61. +3
    -2
      src/views/nodes/statusFileNode.ts
  62. +1
    -1
      src/views/nodes/statusFilesNode.ts
  63. +3
    -2
      src/views/nodes/tagNode.ts
  64. +3
    -2
      src/views/nodes/tagsNode.ts
  65. +0
    -920
      src/views/nodes/viewNode.ts
  66. +1
    -1
      src/views/nodes/workspaceMissingRepositoryNode.ts
  67. +3
    -2
      src/views/nodes/workspaceNode.ts
  68. +3
    -2
      src/views/nodes/worktreeNode.ts
  69. +3
    -2
      src/views/nodes/worktreesNode.ts
  70. +3
    -2
      src/views/remotesView.ts
  71. +2
    -1
      src/views/searchAndCompareView.ts
  72. +3
    -2
      src/views/stashesView.ts
  73. +3
    -2
      src/views/tagsView.ts
  74. +2
    -2
      src/views/viewBase.ts
  75. +9
    -10
      src/views/viewCommands.ts
  76. +1
    -1
      src/views/workspacesView.ts
  77. +3
    -2
      src/views/worktreesView.ts

+ 2
- 1
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

+ 1
- 1
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<GitLensApi | undefined> {

+ 1
- 1
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';

+ 2
- 2
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';

+ 1
- 1
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;

+ 3
- 2
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';

+ 3
- 2
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';

+ 3
- 2
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';

+ 3
- 2
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) {

+ 1
- 1
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(

+ 36
- 0
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<Type, TView, State> {
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;
}
}
}

+ 51
- 0
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<Disposable> {
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);
}
}
}

+ 213
- 0
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<TreeItem> {
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<Disposable> {
return weakEvent(this.repo.onDidChange, this.onRepositoryChanged, this);
}
protected override etag(): number {
return this.repo.etag;
}
protected abstract changed(e: RepositoryChangeEvent): boolean;
@debug<RepositoryFolderNode['onRepositoryChanged']>({ 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);
}
}
}

+ 169
- 0
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<Type, TView, TChild, State> {
protected disposable: Disposable;
protected subscription: Promise<Disposable | undefined> | 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<Type, TView>) {
this.loaded = true;
void this.ensureSubscription();
return getTreeItem.apply(this);
};
const getChildren = this.getChildren;
this.getChildren = function (this: SubscribeableViewNode<Type, TView>) {
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<ViewNode['triggerChange']>((reset, force) => `${reset}|${force}`)
@debug()
override async triggerChange(reset: boolean = false, force: boolean = false): Promise<void> {
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<Disposable | undefined>;
@debug()
protected async unsubscribe(): Promise<void> {
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<ViewNode>) {
// 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();
}
}

+ 33
- 0
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<Type, TView, State> {
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;
}

+ 408
- 0
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<ViewNode>((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<T extends keyof TreeViewNodesByType>(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<ViewNode[]>;
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<TreeItem>;
resolveTreeItem?(item: TreeItem): TreeItem | Promise<TreeItem>;
getCommand(): Command | undefined {
return undefined;
}
refresh?(reset?: boolean): boolean | void | Promise<void> | Promise<boolean>;
@gate<ViewNode['triggerChange']>((reset, force, avoidSelf) => `${reset}|${force}|${avoidSelf?.toString()}`)
@debug()
triggerChange(reset: boolean = false, force: boolean = false, avoidSelf?: ViewNode): Promise<void> {
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<ViewNode | undefined>;
deleteState<T extends StateKey<State> = StateKey<State>>(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<T extends StateKey<State> = StateKey<State>>(key: T): StateValue<State, T> | 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<T extends StateKey<State> = StateKey<State>>(
key: T,
value: StateValue<State, T>,
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<T> = keyof T;
type StateValue<T, P extends StateKey<T>> = 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<string, unknown>): Promise<void>;
}
export function isPageableViewNode(node: ViewNode): node is ViewNode & PageableViewNode {
return isA<ViewNode & PageableViewNode>(node, 'loadMore');
}
interface AutoRefreshableView {
autoRefresh: boolean;
onDidChangeAutoRefresh: Event<void>;
}
export function canAutoRefreshView(view: View): view is View & AutoRefreshableView {
return isA<View & AutoRefreshableView>(view, 'onDidChangeAutoRefresh');
}
export function canEditNode(node: ViewNode): node is ViewNode & { edit(): void | Promise<void> } {
return typeof (node as ViewNode & { edit(): void | Promise<void> }).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<T>;
};
export function isViewNode(node: unknown): node is ViewNode;
export function isViewNode<T extends keyof TreeViewNodesByType>(node: unknown, type: T): node is TreeViewNodesByType[T];
export function isViewNode<T extends keyof TreeViewNodesByType>(node: unknown, type?: T): node is ViewNode {
if (node == null) return false;
return node instanceof ViewNode ? type == null || node.type === type : false;
}

+ 45
- 0
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<Type, TView, State> {
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<Type, TView, State> {
abstract get ref(): GitRevisionReference;
override toString(): string {
return `${super.toString()}:${this.file.path}`;
}
}

+ 1
- 1
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(

+ 3
- 2
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;

+ 3
- 2
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;

+ 1
- 1
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(

+ 1
- 1
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(

+ 2
- 2
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;

+ 3
- 2
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(

+ 3
- 2
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,

+ 3
- 2
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;

+ 2
- 2
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(

+ 3
- 2
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;

+ 1
- 1
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;

+ 3
- 2
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;

+ 2
- 2
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;

+ 3
- 2
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',

+ 3
- 2
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>

+ 3
- 2
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;

+ 3
- 2
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',

+ 2
- 2
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;

+ 2
- 2
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'],

+ 3
- 2
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>

+ 3
- 2
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',

+ 1
- 1
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',

+ 3
- 2
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(

+ 1
- 1
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(

+ 1
- 1
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',

+ 1
- 1
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(

+ 1
- 1
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;

+ 1
- 1
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

+ 1
- 1
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(

+ 3
- 2
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>

+ 2
- 2
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;

+ 1
- 1
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(

+ 3
- 2
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(

+ 3
- 2
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',

+ 3
- 2
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> {

+ 2
- 2
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;

+ 3
- 2
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;

+ 1
- 1
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;

+ 2
- 2
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;

+ 2
- 2
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) {

+ 3
- 2
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(

+ 3
- 2
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(

+ 3
- 2
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[];

+ 1
- 1
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(

+ 3
- 2
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;

+ 3
- 2
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(

+ 0
- 920
src/views/nodes/viewNode.ts 查看文件

@ -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<ViewNode>((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<T extends keyof TreeViewNodesByType>(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<ViewNode[]>;
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<TreeItem>;
resolveTreeItem?(item: TreeItem): TreeItem | Promise<TreeItem>;
getCommand(): Command | undefined {
return undefined;
}
refresh?(reset?: boolean): boolean | void | Promise<void> | Promise<boolean>;
@gate<ViewNode['triggerChange']>((reset, force, avoidSelf) => `${reset}|${force}|${avoidSelf?.toString()}`)
@debug()
triggerChange(reset: boolean = false, force: boolean = false, avoidSelf?: ViewNode): Promise<void> {
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<ViewNode | undefined>;
deleteState<T extends StateKey<State> = StateKey<State>>(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<T extends StateKey<State> = StateKey<State>>(key: T): StateValue<State, T> | 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<T extends StateKey<State> = StateKey<State>>(
key: T,
value: StateValue<State, T>,
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<T> = keyof T;
type StateValue<T, P extends StateKey<T>> = 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<Type, TView, State> {
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<Type, TView, State> {
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<Type, TView, State> {
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<Type, TView, State> {
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<string, unknown>): Promise<void>;
}
export function isPageableViewNode(node: ViewNode): node is ViewNode & PageableViewNode {
return isA<ViewNode & PageableViewNode>(node, 'loadMore');
}
export abstract class SubscribeableViewNode<
Type extends TreeViewSubscribableNodeTypes = TreeViewSubscribableNodeTypes,
TView extends View = View,
TChild extends ViewNode = ViewNode,
State extends object = any,
> extends CacheableChildrenViewNode<Type, TView, TChild, State> {
protected disposable: Disposable;
protected subscription: Promise<Disposable | undefined> | 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<Type, TView>) {
this.loaded = true;
void this.ensureSubscription();
return getTreeItem.apply(this);
};
const getChildren = this.getChildren;
this.getChildren = function (this: SubscribeableViewNode<Type, TView>) {
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<ViewNode['triggerChange']>((reset, force) => `${reset}|${force}`)
@debug()
override async triggerChange(reset: boolean = false, force: boolean = false): Promise<void> {
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<Disposable | undefined>;
@debug()
protected async unsubscribe(): Promise<void> {
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<ViewNode>) {
// 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<TreeItem> {
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<Disposable> {
return weakEvent(this.repo.onDidChange, this.onRepositoryChanged, this);
}
protected override etag(): number {
return this.repo.etag;
}
protected abstract changed(e: RepositoryChangeEvent): boolean;
@debug<RepositoryFolderNode['onRepositoryChanged']>({ 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<Disposable> {
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<void>;
}
export function canAutoRefreshView(view: View): view is View & AutoRefreshableView {
return isA<View & AutoRefreshableView>(view, 'onDidChangeAutoRefresh');
}
export function canEditNode(node: ViewNode): node is ViewNode & { edit(): void | Promise<void> } {
return typeof (node as ViewNode & { edit(): void | Promise<void> }).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<T>;
};
export function isViewNode(node: unknown): node is ViewNode;
export function isViewNode<T extends keyof TreeViewNodesByType>(node: unknown, type: T): node is TreeViewNodesByType[T];
export function isViewNode<T extends keyof TreeViewNodesByType>(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;
}

+ 1
- 1
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(

+ 3
- 2
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<

+ 3
- 2
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;

+ 3
- 2
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> {

+ 3
- 2
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';

+ 2
- 1
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';

+ 3
- 2
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';

+ 3
- 2
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';

+ 2
- 2
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';

+ 9
- 10
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';

+ 1
- 1
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';

+ 3
- 2
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';

Loading…
取消
儲存