From a9c0288be8c1148b717c291567f131ca767e2838 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Thu, 6 Oct 2022 02:58:33 -0400 Subject: [PATCH] Adds avatar context menu on the graph Adds graph context menus for virtual GitHub repos Adds comparison commands to the graph Fixes `when` clause contitions for some commands --- package.json | 108 ++++++++++++++++++++++++--- src/env/node/git/localGitProvider.ts | 34 +++++---- src/plus/github/githubGitProvider.ts | 82 ++++++++++++++++++++- src/plus/webviews/graph/graphWebview.ts | 126 +++++++++++++++++++++++++++++--- 4 files changed, 313 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index db0d3c0..ae02f20 100644 --- a/package.json +++ b/package.json @@ -6443,6 +6443,40 @@ "icon": "$(copy)" }, { + "command": "gitlens.graph.compareAncestryWithWorking", + "title": "Compare Ancestry with Working Tree", + "category": "GitLens" + }, + { + "command": "gitlens.graph.compareWithHead", + "title": "Compare with HEAD", + "category": "GitLens", + "icon": "$(compare-changes)" + }, + { + "command": "gitlens.graph.compareWithUpstream", + "title": "Compare with Upstream", + "category": "GitLens" + }, + { + "command": "gitlens.graph.compareWithWorking", + "title": "Compare with Working Tree", + "category": "GitLens", + "icon": "$(gitlens-compare-ref-working)" + }, + { + "command": "gitlens.graph.addAuthor", + "title": "Add as Co-author", + "category": "GitLens", + "icon": "$(person-add)" + }, + { + "command": "gitlens.graph.copy", + "title": "Copy", + "category": "GitLens", + "icon": "$(copy)" + }, + { "command": "gitlens.graph.columnAuthorOn", "title": "Show Author", "category": "GitLens" @@ -7120,7 +7154,7 @@ }, { "command": "gitlens.addAuthors", - "when": "gitlens:enabled" + "when": "gitlens:enabled && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders" }, { "command": "gitlens.connectRemoteProvider", @@ -8395,6 +8429,30 @@ "when": "false" }, { + "command": "gitlens.graph.compareAncestryWithWorking", + "when": "false" + }, + { + "command": "gitlens.graph.compareWithHead", + "when": "false" + }, + { + "command": "gitlens.graph.compareWithUpstream", + "when": "false" + }, + { + "command": "gitlens.graph.compareWithWorking", + "when": "false" + }, + { + "command": "gitlens.graph.addAuthor", + "when": "false" + }, + { + "command": "gitlens.graph.copy", + "when": "false" + }, + { "command": "gitlens.graph.columnAuthorOn", "when": "false" }, @@ -9027,7 +9085,7 @@ }, { "command": "gitlens.views.addAuthors", - "when": "view =~ /^gitlens\\.views\\.contributors/", + "when": "!gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && view =~ /^gitlens\\.views\\.contributors/", "group": "navigation@10" }, { @@ -9739,7 +9797,7 @@ }, { "command": "gitlens.views.createWorktree", - "when": "!gitlens:readonly && viewItem =~ /gitlens:branch\\b/", + "when": "!gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && viewItem =~ /gitlens:branch\\b/", "group": "1_gitlens_actions_@9" }, { @@ -9810,12 +9868,12 @@ }, { "command": "gitlens.views.addAuthors", - "when": "viewItem =~ /gitlens:contributors\\b/", + "when": "!gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && viewItem =~ /gitlens:contributors\\b/", "group": "inline@1" }, { "command": "gitlens.views.addAuthors", - "when": "viewItem =~ /gitlens:contributors\\b/", + "when": "!gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && viewItem =~ /gitlens:contributors\\b/", "group": "1_gitlens_actions@1" }, { @@ -9825,7 +9883,7 @@ }, { "command": "gitlens.views.addAuthor", - "when": "viewItem =~ /gitlens:contributor\\b(?!.*?\\b\\+current\\b)/", + "when": "!gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && viewItem =~ /gitlens:contributor\\b(?!.*?\\b\\+current\\b)/", "group": "inline@2" }, { @@ -9840,7 +9898,7 @@ }, { "command": "gitlens.views.addAuthor", - "when": "viewItem =~ /gitlens:contributor\\b(?!.*?\\b\\+current\\b)/", + "when": "!gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && viewItem =~ /gitlens:contributor\\b(?!.*?\\b\\+current\\b)/", "group": "1_gitlens_actions@2" }, { @@ -10748,12 +10806,12 @@ }, { "command": "gitlens.views.createWorktree", - "when": "!gitlens:readonly && viewItem =~ /gitlens:worktrees\\b/", + "when": "!gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && viewItem =~ /gitlens:worktrees\\b/", "group": "inline@1" }, { "command": "gitlens.views.createWorktree", - "when": "!gitlens:readonly && viewItem =~ /gitlens:worktrees\\b/", + "when": "!gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && viewItem =~ /gitlens:worktrees\\b/", "group": "1_gitlens_actions@1" }, { @@ -10907,7 +10965,7 @@ }, { "command": "gitlens.graph.createWorktree", - "when": "!gitlens:readonly && webviewItem =~ /gitlens:branch\\b/", + "when": "!gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && webviewItem =~ /gitlens:branch\\b/", "group": "1_gitlens_actions_@9" }, { @@ -11018,6 +11076,36 @@ "group": "7_gitlens_cutcopypaste@1" }, { + "command": "gitlens.graph.compareWithUpstream", + "when": "!gitlens:hasVirtualFolders && webviewItem =~ /gitlens:branch\\b(?=.*?\\b\\+tracking\\b)/", + "group": "4_gitlens_compare@1" + }, + { + "command": "gitlens.graph.compareWithHead", + "when": "!gitlens:hasVirtualFolders && webviewItem =~ /gitlens:(branch\\b(?!.*?\\b\\+current\\b)|commit\\b|stash\\b|tag\\b)/", + "group": "4_gitlens_compare@2" + }, + { + "command": "gitlens.graph.compareWithWorking", + "when": "!gitlens:hasVirtualFolders && webviewItem =~ /gitlens:(branch|commit|stash|tag)\\b/", + "group": "4_gitlens_compare@3" + }, + { + "command": "gitlens.graph.compareAncestryWithWorking", + "when": "!gitlens:hasVirtualFolders && webviewItem =~ /gitlens:branch\\b(?!.*?\\b\\+current\\b)/", + "group": "4_gitlens_compare@4" + }, + { + "command": "gitlens.graph.addAuthor", + "when": "!gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && webviewItem =~ /gitlens:contributor\\b(?!.*?\\b\\+current\\b)/", + "group": "1_gitlens_actions@2" + }, + { + "command": "gitlens.graph.copy", + "when": "webviewItem =~ /gitlens:contributor\\b/", + "group": "7_gitlens_cutcopypaste@1" + }, + { "command": "gitlens.graph.columnAuthorOn", "when": "webviewItem =~ /gitlens:graph:columns\\b/ && webviewItemValue =~ /\\bauthor\\b/", "group": "1_columns@1" diff --git a/src/env/node/git/localGitProvider.ts b/src/env/node/git/localGitProvider.ts index 5b535ae..2937ead 100644 --- a/src/env/node/git/localGitProvider.ts +++ b/src/env/node/git/localGitProvider.ts @@ -1634,16 +1634,19 @@ export class LocalGitProvider implements GitProvider, Disposable { const defaultPageLimit = configuration.get('graph.pageItemLimit') ?? 1000; const ordering = configuration.get('graph.commitOrdering', undefined, 'date'); - const [refResult, stashResult, branchesResult, remotesResult] = await Promise.allSettled([ + const [refResult, stashResult, branchesResult, remotesResult, currentUserResult] = await Promise.allSettled([ this.git.log2(repoPath, undefined, ...refParser.arguments, '-n1', options?.ref ?? 'HEAD'), this.getStash(repoPath), this.getBranches(repoPath), this.getRemotes(repoPath), + this.getCurrentUser(repoPath), ]); const branches = getSettledValue(branchesResult)?.values; const branchMap = branches != null ? new Map(branches.map(r => [r.name, r])) : new Map(); + const currentUser = getSettledValue(currentUserResult); + const remotes = getSettledValue(remotesResult); const remoteMap = remotes != null @@ -1747,8 +1750,8 @@ export class LocalGitProvider implements GitProvider, Disposable { let branch: GitBranch | undefined; let branchName: string; - let current = false; - let headCommit = false; + let head = false; + let isCurrentUser = false; let refHeads: GitGraphRowHead[]; let refRemoteHeads: GitGraphRowRemoteHead[]; let refTags: GitGraphRowTag[]; @@ -1776,7 +1779,7 @@ export class LocalGitProvider implements GitProvider, Disposable { refRemoteHeads = []; refTags = []; contexts = undefined; - headCommit = false; + head = false; if (commit.tips) { for (let tip of commit.tips.split(', ')) { @@ -1803,9 +1806,8 @@ export class LocalGitProvider implements GitProvider, Disposable { continue; } - current = tip.startsWith('HEAD'); - if (current) { - headCommit = true; + head = tip.startsWith('HEAD'); + if (head) { reachableFromHEAD.add(commit.sha); if (tip !== 'HEAD') { @@ -1854,9 +1856,9 @@ export class LocalGitProvider implements GitProvider, Disposable { refHeads.push({ id: branchId, name: tip, - isCurrentHead: current, + isCurrentHead: head, context: serializeWebviewItemContext({ - webviewItem: `gitlens:branch${current ? '+current' : ''}${ + webviewItem: `gitlens:branch${head ? '+current' : ''}${ branch?.upstream != null ? '+tracking' : '' }`, webviewItemValue: { @@ -1898,6 +1900,8 @@ export class LocalGitProvider implements GitProvider, Disposable { } } + isCurrentUser = isUserMatch(currentUser, commit.author, commit.authorEmail); + contexts = {}; if (stashCommit != null) { contexts.row = serializeWebviewItemContext({ @@ -1913,7 +1917,7 @@ export class LocalGitProvider implements GitProvider, Disposable { }); } else { contexts.row = serializeWebviewItemContext({ - webviewItem: `gitlens:commit${headCommit ? '+HEAD' : ''}${ + webviewItem: `gitlens:commit${head ? '+HEAD' : ''}${ reachableFromHEAD.has(commit.sha) ? '+current' : '' }`, webviewItemValue: { @@ -1924,11 +1928,15 @@ export class LocalGitProvider implements GitProvider, Disposable { }), }, }); + contexts.avatar = serializeWebviewItemContext({ - webviewItem: 'gitlens:avatar', + webviewItem: `gitlens:contributor${isCurrentUser ? '+current' : ''}`, webviewItemValue: { - type: 'avatar', + type: 'contributor', + repoPath: repoPath, + name: commit.author, email: commit.authorEmail, + current: isCurrentUser, }, }); } @@ -1936,7 +1944,7 @@ export class LocalGitProvider implements GitProvider, Disposable { rows.push({ sha: commit.sha, parents: parents, - author: commit.author, + author: isCurrentUser ? 'You' : commit.author, email: commit.authorEmail, date: Number(ordering === 'author-date' ? commit.authorDate : commit.committerDate) * 1000, message: emojify(commit.message.trim()), diff --git a/src/plus/github/githubGitProvider.ts b/src/plus/github/githubGitProvider.ts index 3ddde47..0f3bcb1a 100644 --- a/src/plus/github/githubGitProvider.ts +++ b/src/plus/github/githubGitProvider.ts @@ -49,6 +49,7 @@ import { GitFileChange, GitFileIndexStatus } from '../../git/models/file'; import type { GitGraph, GitGraphRow, + GitGraphRowContexts, GitGraphRowHead, GitGraphRowRemoteHead, GitGraphRowTag, @@ -57,8 +58,8 @@ import { GitGraphRowType } from '../../git/models/graph'; import type { GitLog } from '../../git/models/log'; import type { GitMergeStatus } from '../../git/models/merge'; import type { GitRebaseStatus } from '../../git/models/rebase'; -import type { GitBranchReference, GitReference } from '../../git/models/reference'; -import { GitRevision } from '../../git/models/reference'; +import type { GitBranchReference } from '../../git/models/reference'; +import { GitReference, GitRevision } from '../../git/models/reference'; import type { GitReflog } from '../../git/models/reflog'; import { getRemoteIconUri, GitRemote, GitRemoteType } from '../../git/models/remote'; import type { RepositoryChangeEvent } from '../../git/models/repository'; @@ -83,11 +84,13 @@ import { debug, getLogScope, log } from '../../system/decorators/log'; import { filterMap, first, last, some } from '../../system/iterable'; import { isAbsolute, isFolderGlob, maybeUri, normalizePath, relative } from '../../system/path'; import { getSettledValue } from '../../system/promise'; +import { serializeWebviewItemContext } from '../../system/webview'; import type { CachedBlame, CachedLog } from '../../trackers/gitDocumentTracker'; import { GitDocumentState } from '../../trackers/gitDocumentTracker'; import type { TrackedDocument } from '../../trackers/trackedDocument'; import type { GitHubAuthorityMetadata, Metadata, RemoteHubApi } from '../remotehub'; import { getRemoteHubApi } from '../remotehub'; +import type { GraphItemContext, GraphItemRefContext } from '../webviews/graph/graphWebview'; import type { GitHubApi } from './github'; import { fromCommitFileStatus } from './models'; @@ -1076,11 +1079,12 @@ export class GitHubGitProvider implements GitProvider, Disposable { // const defaultPageLimit = configuration.get('graph.pageItemLimit') ?? 1000; const ordering = configuration.get('graph.commitOrdering', undefined, 'date'); - const [logResult, branchResult, remotesResult, tagsResult] = await Promise.allSettled([ + const [logResult, branchResult, remotesResult, tagsResult, currentUserResult] = await Promise.allSettled([ this.getLog(repoPath, { all: true, ordering: ordering, limit: defaultLimit }), this.getBranch(repoPath), this.getRemotes(repoPath), this.getTags(repoPath), + this.getCurrentUser(repoPath), ]); const avatars = new Map(); @@ -1093,6 +1097,7 @@ export class GitHubGitProvider implements GitProvider, Disposable { getSettledValue(branchResult), getSettledValue(remotesResult)?.[0], getSettledValue(tagsResult)?.values, + getSettledValue(currentUserResult), avatars, ids, options, @@ -1106,6 +1111,7 @@ export class GitHubGitProvider implements GitProvider, Disposable { branch: GitBranch | undefined, remote: GitRemote | undefined, tags: GitTag[] | undefined, + currentUser: GitUser | undefined, avatars: Map, ids: Set, options?: { @@ -1136,21 +1142,39 @@ export class GitHubGitProvider implements GitProvider, Disposable { const rows: GitGraphRow[] = []; + let head = false; + let isCurrentUser = false; let refHeads: GitGraphRowHead[]; let refRemoteHeads: GitGraphRowRemoteHead[]; let refTags: GitGraphRowTag[]; + let contexts: GitGraphRowContexts | undefined; const hasHeadShaAndRemote = branch?.sha != null && remote != null; for (const commit of commits) { ids.add(commit.sha); - if (hasHeadShaAndRemote && commit.sha === branch.sha) { + head = commit.sha === branch?.sha; + if (hasHeadShaAndRemote && head) { refHeads = [ { id: getBranchId(repoPath, false, branch.name), name: branch.name, isCurrentHead: true, + context: serializeWebviewItemContext({ + webviewItem: `gitlens:branch${head ? '+current' : ''}${ + branch?.upstream != null ? '+tracking' : '' + }`, + webviewItemValue: { + type: 'branch', + ref: GitReference.create(branch.name, repoPath, { + refType: 'branch', + name: branch.name, + remote: false, + upstream: branch.upstream, + }), + }, + }), }, ]; refRemoteHeads = [ @@ -1162,6 +1186,18 @@ export class GitHubGitProvider implements GitProvider, Disposable { avatarUrl: ( remote.provider?.avatarUri ?? getRemoteIconUri(this.container, remote, asWebviewUri) )?.toString(true), + context: serializeWebviewItemContext({ + webviewItem: 'gitlens:branch+remote', + webviewItemValue: { + type: 'branch', + ref: GitReference.create(branch.name, repoPath, { + refType: 'branch', + name: branch.name, + remote: true, + upstream: { name: remote.name, missing: false }, + }), + }, + }), }, ]; } else { @@ -1177,6 +1213,16 @@ export class GitHubGitProvider implements GitProvider, Disposable { return { name: t.name, annotated: Boolean(t.message), + context: serializeWebviewItemContext({ + webviewItem: 'gitlens:tag', + webviewItemValue: { + type: 'tag', + ref: GitReference.create(t.name, repoPath, { + refType: 'tag', + name: t.name, + }), + }, + }), }; }), ]; @@ -1191,6 +1237,32 @@ export class GitHubGitProvider implements GitProvider, Disposable { } } + isCurrentUser = commit.author.name === 'You'; + contexts = { + row: serializeWebviewItemContext({ + webviewItem: `gitlens:commit${ + hasHeadShaAndRemote && commit.sha === branch.sha ? '+HEAD' : '' + }+current`, + webviewItemValue: { + type: 'commit', + ref: GitReference.create(commit.sha, repoPath, { + refType: 'revision', + message: commit.message, + }), + }, + }), + avatar: serializeWebviewItemContext({ + webviewItem: `gitlens:contributor${isCurrentUser ? '+current' : ''}`, + webviewItemValue: { + type: 'contributor', + repoPath: repoPath, + name: isCurrentUser && currentUser?.name != null ? currentUser.name : commit.author.name, + email: commit.author.email, + current: isCurrentUser, + }, + }), + }; + rows.push({ sha: commit.sha, parents: commit.parents, @@ -1203,6 +1275,7 @@ export class GitHubGitProvider implements GitProvider, Disposable { heads: refHeads, remotes: refRemoteHeads, tags: refTags, + contexts: contexts, }); } @@ -1233,6 +1306,7 @@ export class GitHubGitProvider implements GitProvider, Disposable { branch, remote, tags, + currentUser, avatars, ids, options, diff --git a/src/plus/webviews/graph/graphWebview.ts b/src/plus/webviews/graph/graphWebview.ts index c8824dd..f567fbc 100644 --- a/src/plus/webviews/graph/graphWebview.ts +++ b/src/plus/webviews/graph/graphWebview.ts @@ -9,6 +9,7 @@ import type { import { CancellationTokenSource, Disposable, + env, EventEmitter, MarkdownString, StatusBarAlignment, @@ -34,6 +35,7 @@ import { getContext, onDidChangeContext, setContext } from '../../../context'; import { PlusFeatures } from '../../../features'; import { GitSearchError } from '../../../git/errors'; import type { GitCommit } from '../../../git/models/commit'; +import { GitContributor } from '../../../git/models/contributor'; import { GitGraphRowType } from '../../../git/models/graph'; import type { GitGraph } from '../../../git/models/graph'; import type { @@ -336,9 +338,17 @@ export class GraphWebview extends WebviewBase { this, ), + registerCommand('gitlens.graph.compareWithUpstream', this.compareWithUpstream, this), + registerCommand('gitlens.graph.compareWithHead', this.compareHeadWith, this), + registerCommand('gitlens.graph.compareWithWorking', this.compareWorkingWith, this), + registerCommand('gitlens.graph.compareAncestryWithWorking', this.compareAncestryWithWorking, this), + + registerCommand('gitlens.graph.copy', this.copy, this), registerCommand('gitlens.graph.copyMessage', this.copyMessage, this), registerCommand('gitlens.graph.copySha', this.copySha, this), + registerCommand('gitlens.graph.addAuthor', this.addAuthor, this), + registerCommand('gitlens.graph.columnAuthorOn', () => this.toggleColumn('author', true)), registerCommand('gitlens.graph.columnAuthorOff', () => this.toggleColumn('author', false)), registerCommand('gitlens.graph.columnDateTimeOn', () => this.toggleColumn('datetime', true)), @@ -1392,6 +1402,16 @@ export class GraphWebview extends WebviewBase { } @debug() + private async copy(item: GraphItemContext) { + if (isGraphItemTypedContext(item, 'contributor')) { + const { name, email } = item.webviewItemValue; + await env.clipboard.writeText(`${name}${email ? ` <${email}>` : ''}`); + } + + return Promise.resolve(); + } + + @debug() private copyMessage(item: GraphItemContext) { if (isGraphItemRefContext(item)) { const { ref } = item.webviewItemValue; @@ -1635,6 +1655,72 @@ export class GraphWebview extends WebviewBase { } @debug() + private async compareAncestryWithWorking(item: GraphItemContext) { + if (isGraphItemRefContext(item)) { + const { ref } = item.webviewItemValue; + + const branch = await this.container.git.getBranch(ref.repoPath); + if (branch == null) return undefined; + + const commonAncestor = await this.container.git.getMergeBase(ref.repoPath, branch.ref, ref.ref); + if (commonAncestor == null) return undefined; + + return this.container.searchAndCompareView.compare( + ref.repoPath, + { ref: commonAncestor, label: `ancestry with ${ref.ref} (${GitRevision.shorten(commonAncestor)})` }, + '', + ); + } + + return Promise.resolve(); + } + + @debug() + private compareHeadWith(item: GraphItemContext) { + if (isGraphItemRefContext(item)) { + const { ref } = item.webviewItemValue; + return this.container.searchAndCompareView.compare(ref.repoPath, 'HEAD', ref.ref); + } + + return Promise.resolve(); + } + + @debug() + private compareWithUpstream(item: GraphItemContext) { + if (isGraphItemRefContext(item, 'branch')) { + const { ref } = item.webviewItemValue; + if (ref.upstream != null) { + return this.container.searchAndCompareView.compare(ref.repoPath, ref.ref, ref.upstream.name); + } + } + + return Promise.resolve(); + } + + @debug() + private compareWorkingWith(item: GraphItemContext) { + if (isGraphItemRefContext(item)) { + const { ref } = item.webviewItemValue; + return this.container.searchAndCompareView.compare(ref.repoPath, '', ref.ref); + } + + return Promise.resolve(); + } + + @debug() + private addAuthor(item: GraphItemContext) { + if (isGraphItemTypedContext(item, 'contributor')) { + const { repoPath, name, email, current } = item.webviewItemValue; + return GitActions.Contributor.addAuthors( + repoPath, + new GitContributor(repoPath, name, email, 0, undefined, current), + ); + } + + return Promise.resolve(); + } + + @debug() private async toggleColumn(name: GraphColumnName, visible: boolean) { let columns = this.container.storage.getWorkspace('graph:columns'); let column = columns?.[name]; @@ -1669,19 +1755,22 @@ export type GraphItemRefContextValue = | GraphCommitContextValue | GraphStashContextValue | GraphTagContextValue; -export type GraphItemContextValue = - | GraphAvatarContextValue - | GraphColumnsContextValue - | GraphPullRequestContextValue - | GraphItemRefContextValue; - -export interface GraphAvatarContextValue { - type: 'avatar'; - email: string; -} + +export type GraphItemTypedContext = WebviewItemContext; +export type GraphItemTypedContextValue = GraphContributorContextValue | GraphPullRequestContextValue; + +export type GraphItemContextValue = GraphColumnsContextValue | GraphItemTypedContextValue | GraphItemRefContextValue; export type GraphColumnsContextValue = string; +export interface GraphContributorContextValue { + type: 'contributor'; + repoPath: string; + name: string; + email: string | undefined; + current?: boolean; +} + export interface GraphPullRequestContextValue { type: 'pullrequest'; id: string; @@ -1714,6 +1803,23 @@ function isGraphItemContext(item: unknown): item is GraphItemContext { return isWebviewItemContext(item) && item.webview === 'gitlens.graph'; } +function isGraphItemTypedContext( + item: unknown, + type: 'contributor', +): item is GraphItemTypedContext; +function isGraphItemTypedContext( + item: unknown, + type: 'pullrequest', +): item is GraphItemTypedContext; +function isGraphItemTypedContext( + item: unknown, + type: GraphItemTypedContextValue['type'], +): item is GraphItemTypedContext { + if (item == null) return false; + + return isGraphItemContext(item) && typeof item.webviewItemValue === 'object' && item.webviewItemValue.type === type; +} + function isGraphItemRefContext(item: unknown): item is GraphItemRefContext; function isGraphItemRefContext(item: unknown, refType: 'branch'): item is GraphItemRefContext; function isGraphItemRefContext(