Skip to content

Commit 0aae43a

Browse files
committed
feat: multiple select row
1 parent e49245c commit 0aae43a

File tree

8 files changed

+207
-135
lines changed

8 files changed

+207
-135
lines changed

src/commands/diff.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import { useDiffTreeView } from '@/views/diff'
99
export default function diffCommand() {
1010
const { getPreviousCommit } = useGitService()
1111
const workspaceFolders = useWorkspaceFolders()
12-
const { selectedCommitHash } = useDiffTreeView()
12+
const { selectedCommitHashes } = useDiffTreeView()
1313

14-
return async (fileInfo: { path: string, status: string }) => {
15-
const commit = selectedCommitHash.value
16-
if (!commit) {
14+
return async (fileInfo: { path: string, status: string, commitHash: string, oldPath?: string }) => {
15+
const commits = selectedCommitHashes.value
16+
if (!commits || commits.length === 0) {
1717
return
1818
}
1919

@@ -23,15 +23,15 @@ export default function diffCommand() {
2323
}
2424

2525
const uri = Uri.joinPath(workspaceRoot, fileInfo.path)
26-
const title = `${fileInfo.path} (${commit})`
26+
const title = `${fileInfo.path} (${fileInfo.commitHash})`
2727

2828
// For modified files, show diff between current commit and its parent
2929
if (fileInfo.status === GIT_STATUS.MODIFIED) {
3030
try {
31-
const previousCommit = await getPreviousCommit(commit)
31+
const previousCommit = await getPreviousCommit(fileInfo.commitHash)
3232
if (previousCommit) {
3333
const leftUri = toGitUri(uri, previousCommit)
34-
const rightUri = toGitUri(uri, commit)
34+
const rightUri = toGitUri(uri, fileInfo.commitHash)
3535
await executeCommand('vscode.diff', leftUri, rightUri, title)
3636
return
3737
}
@@ -44,7 +44,7 @@ export default function diffCommand() {
4444

4545
// For added files, show the entire file content
4646
if (fileInfo.status === GIT_STATUS.ADDED) {
47-
const gitUri = toGitUri(uri, commit)
47+
const gitUri = toGitUri(uri, fileInfo.commitHash)
4848
await executeCommand('vscode.open', gitUri)
4949
}
5050
}

src/views/diff/DiffTreeView.ts

Lines changed: 142 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { TreeItemCollapsibleState } from 'vscode'
21
import type { TreeViewNode } from 'reactive-vscode'
32
import { computed, createSingletonComposable, ref, useTreeView } from 'reactive-vscode'
3+
import { TreeItemCollapsibleState } from 'vscode'
44

55
import { CommitNode } from './entity/CommitNode'
66
import { useFileTreeView } from './FileTreeView'
7+
78
import type { CommitDetails } from './types'
9+
810
import { useGitService } from '@/git'
911
import { useStorage } from '@/storage'
1012
import { EXTENSION_SYMBOL } from '@/constant'
@@ -13,80 +15,155 @@ export const useDiffTreeView = createSingletonComposable(() => {
1315
const git = useGitService()
1416
const storage = useStorage()
1517
const fileTree = useFileTreeView()
16-
const selectedCommitHash = ref('')
17-
const commitDetails = ref<CommitDetails | null>(null)
18+
const selectedCommitHashes = ref<string[]>([])
19+
const commitDetailsList = ref<CommitDetails[]>([])
1820
const files = ref<TreeViewNode[]>([])
1921

20-
async function loadCommitDetails(hash: string) {
22+
async function loadCommitDetails(hashes: string[]) {
2123
try {
22-
let commit = storage.getCommit(hash)
24+
if (hashes.length === 0) {
25+
commitDetailsList.value = []
26+
files.value = []
27+
return
28+
}
29+
30+
const commitList: CommitDetails[] = []
31+
const allCommitFiles: Map<string, TreeViewNode[]> = new Map()
32+
let totalFiles = 0
2333

24-
if (!commit) {
25-
const { logResult, operations, branches } = await git.getHistory()
26-
const historyCommit = logResult.all.find(c => c.hash === hash)
27-
if (!historyCommit)
28-
return
34+
for (const hash of hashes) {
35+
let commit = storage.getCommit(hash)
2936

30-
commit = {
31-
...historyCommit,
37+
if (!commit) {
38+
const { logResult, operations, branches } = await git.getHistory()
39+
const historyCommit = logResult.all.find(c => c.hash === hash)
40+
if (!historyCommit)
41+
continue
42+
43+
commit = {
44+
...historyCommit,
45+
}
46+
47+
storage.saveCommits({ operations, branches, logResult })
3248
}
3349

34-
storage.saveCommits({ operations, branches, logResult })
50+
commitList.push(commit)
51+
52+
const { files: commitFiles, total } = await fileTree.getChildren(hash)
53+
54+
allCommitFiles.set(hash, commitFiles)
55+
totalFiles += total
3556
}
3657

37-
commitDetails.value = commit
38-
const { files: commitFiles, total } = await fileTree.getChildren(hash)
58+
commitDetailsList.value = commitList
3959

40-
files.value = [
41-
{
42-
treeItem: new CommitNode(
43-
'Changed Files',
44-
`${total} Files Changed`,
45-
total > 0 ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.None,
46-
'files',
47-
),
48-
children: commitFiles,
49-
},
50-
]
60+
// Aggregate file changes for all selected commits
61+
if (hashes.length === 1) {
62+
// Single commit view - show files directly
63+
files.value = [
64+
{
65+
treeItem: new CommitNode(
66+
'Changed Files',
67+
`${totalFiles} Files Changed`,
68+
TreeItemCollapsibleState.Expanded,
69+
'files',
70+
),
71+
children: allCommitFiles.get(hashes[0]) || [],
72+
},
73+
]
74+
}
75+
else {
76+
// Multiple commits view - group files by commit
77+
const commitNodes: TreeViewNode[] = []
78+
79+
for (const hash of hashes) {
80+
const commit = commitList.find(c => c.hash === hash)
81+
if (!commit)
82+
continue
83+
84+
const commitFiles = allCommitFiles.get(hash) || []
85+
const shortHash = hash.substring(0, 7)
86+
87+
commitNodes.push({
88+
treeItem: new CommitNode(
89+
`${commitFiles.length} Files Changed`,
90+
`${shortHash}`,
91+
TreeItemCollapsibleState.Expanded,
92+
'git-commit',
93+
),
94+
children: commitFiles,
95+
})
96+
}
97+
98+
files.value = [
99+
{
100+
treeItem: new CommitNode(
101+
'Selected Commits',
102+
`${hashes.length} Commits Selected`,
103+
TreeItemCollapsibleState.Expanded,
104+
'git-commit',
105+
),
106+
children: commitNodes,
107+
},
108+
]
109+
}
51110
}
52111
catch (error) {
53112
console.error('Error getting commit details:', error)
54-
commitDetails.value = null
113+
commitDetailsList.value = []
55114
files.value = []
56115
}
57116
}
58117

59118
const treeNodes = computed<TreeViewNode[]>(() => {
60-
if (!commitDetails.value)
119+
if (commitDetailsList.value.length === 0)
61120
return []
62121

63-
return [
64-
{
65-
treeItem: new CommitNode(
66-
'Author',
67-
`${commitDetails.value.authorName} <${commitDetails.value.authorEmail}>`,
68-
TreeItemCollapsibleState.None,
69-
'person',
70-
),
71-
},
72-
{
73-
treeItem: new CommitNode(
74-
'Date',
75-
new Date(commitDetails.value.date).toLocaleString(),
76-
TreeItemCollapsibleState.None,
77-
'calendar',
78-
),
79-
},
80-
{
81-
treeItem: new CommitNode(
82-
'Hash',
83-
commitDetails.value.hash,
84-
TreeItemCollapsibleState.None,
85-
'git-commit',
86-
),
87-
},
88-
...files.value,
89-
]
122+
if (commitDetailsList.value.length === 1) {
123+
// Single commit view
124+
const commit = commitDetailsList.value[0]
125+
return [
126+
{
127+
treeItem: new CommitNode(
128+
'Author',
129+
`${commit.authorName} <${commit.authorEmail}>`,
130+
TreeItemCollapsibleState.None,
131+
'person',
132+
),
133+
},
134+
{
135+
treeItem: new CommitNode(
136+
'Date',
137+
new Date(commit.date).toLocaleString(),
138+
TreeItemCollapsibleState.None,
139+
'calendar',
140+
),
141+
},
142+
{
143+
treeItem: new CommitNode(
144+
'Hash',
145+
commit.hash,
146+
TreeItemCollapsibleState.None,
147+
'git-commit',
148+
),
149+
},
150+
...files.value,
151+
]
152+
}
153+
else {
154+
// Multiple commits view - summary information
155+
return [
156+
{
157+
treeItem: new CommitNode(
158+
'Multiple Commits Selected',
159+
`${commitDetailsList.value.length} commits`,
160+
TreeItemCollapsibleState.None,
161+
'git-commit',
162+
),
163+
},
164+
...files.value,
165+
]
166+
}
90167
})
91168

92169
const tree = useTreeView(
@@ -97,18 +174,22 @@ export const useDiffTreeView = createSingletonComposable(() => {
97174
},
98175
)
99176

100-
async function refresh(hash: string) {
101-
if (hash === selectedCommitHash.value)
177+
async function refresh(hashes: string[]) {
178+
const currentHashes = new Set(selectedCommitHashes.value)
179+
const newHashes = new Set(hashes)
180+
181+
if (currentHashes.size === newHashes.size
182+
&& [...currentHashes].every(hash => newHashes.has(hash))) {
102183
return
184+
}
103185

104-
selectedCommitHash.value = hash
105-
fileTree.refresh(hash)
106-
await loadCommitDetails(hash)
186+
selectedCommitHashes.value = [...hashes]
187+
await loadCommitDetails(hashes)
107188
}
108189

109190
return {
110191
tree,
111192
refresh,
112-
selectedCommitHash,
193+
selectedCommitHashes,
113194
}
114195
})

src/views/diff/FileTreeView.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type { TreeViewNode } from 'reactive-vscode'
22
import { createSingletonComposable, ref } from 'reactive-vscode'
33
import { FileNode } from './entity/FileNode'
44
import { FolderNode } from './entity/FolderNode'
5-
import { EXTENSION_SYMBOL } from '@/constant'
65
import { type CommitFile, useGitService } from '@/git'
76
import { useStorage } from '@/storage'
87
import { parseGitStatus } from '@/utils'
@@ -11,7 +10,7 @@ export const useFileTreeView = createSingletonComposable(() => {
1110
const git = useGitService()
1211
const storage = useStorage()
1312

14-
function buildFileTree(files: CommitFile[]): TreeViewNode[] {
13+
function buildFileTree(files: CommitFile[], commitHash: string): TreeViewNode[] {
1514
const root = new Map<string, TreeViewNode>()
1615
const folderChildren = new Map<string, TreeViewNode[]>()
1716

@@ -25,6 +24,7 @@ export const useFileTreeView = createSingletonComposable(() => {
2524
treeItem: new FileNode(
2625
normalizedPath,
2726
file.status,
27+
commitHash,
2828
'oldPath' in file ? file.oldPath : undefined,
2929
),
3030
})
@@ -75,6 +75,7 @@ export const useFileTreeView = createSingletonComposable(() => {
7575
treeItem: new FileNode(
7676
normalizedPath,
7777
file.status,
78+
commitHash,
7879
'oldPath' in file ? file.oldPath : undefined,
7980
),
8081
}
@@ -96,7 +97,7 @@ export const useFileTreeView = createSingletonComposable(() => {
9697

9798
if (commit?.files) {
9899
return {
99-
files: buildFileTree(commit.files),
100+
files: buildFileTree(commit.files, commit?.hash),
100101
total: commit.files.length,
101102
}
102103
}
@@ -137,7 +138,7 @@ export const useFileTreeView = createSingletonComposable(() => {
137138
})
138139

139140
return {
140-
files: buildFileTree(files),
141+
files: buildFileTree(files, commitHash),
141142
total: files.length,
142143
}
143144
}
@@ -147,12 +148,7 @@ export const useFileTreeView = createSingletonComposable(() => {
147148
}
148149
}
149150

150-
const refresh = (commitHash: string) => {
151-
getChildren(commitHash)
152-
}
153-
154151
return {
155152
getChildren,
156-
refresh,
157153
}
158154
})

src/views/diff/entity/FileNode.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { TreeItem, TreeItemCollapsibleState, Uri } from 'vscode'
2-
import type { FileTreeItem } from '../types'
32
import { EXTENSION_SYMBOL } from '@/constant'
43

54
export class FileNode extends TreeItem {
65
constructor(
76
public readonly path: string,
87
public readonly status: string,
8+
public readonly commitHash: string,
99
public readonly oldPath?: string,
1010
) {
1111
const label = path.split('/').pop() || path
@@ -28,15 +28,7 @@ export class FileNode extends TreeItem {
2828
this.command = {
2929
command: `${EXTENSION_SYMBOL}.openDiff`,
3030
title: 'Show Changes',
31-
arguments: [{ path: this.path, status: this.status, oldPath: this.oldPath }],
31+
arguments: [{ path: this.path, status: this.status, oldPath: this.oldPath, commitHash: this.commitHash }],
3232
}
3333
}
34-
35-
static fromFileItem(item: FileTreeItem): FileNode {
36-
return new FileNode(
37-
item.path,
38-
item.status,
39-
'oldPath' in item ? item.oldPath : undefined,
40-
)
41-
}
4234
}

0 commit comments

Comments
 (0)