新增: Git 能力闭环(Diff 行号解析/分支只读展示/提交详情/写入后自动刷新变更视图)
- Diff 行号修复:解析 @@ -a,b +c,d @@ 头,计算旧/新行号(原恒空字符串) - 提交详情增强:显示父提交哈希/作者/日期/完整消息(git show -s --format=%P\t%an) - 分支只读展示:list_branches IPC + 变更面板顶部下拉查看分支(切换走 AI 工具) - AI 工具写入后自动跳转变更视图:监听 df-data-changed entity=file 切 Tab + 刷新
This commit is contained in:
@@ -152,6 +152,11 @@ export const moduleApi = {
|
||||
},
|
||||
|
||||
/** 分页查询工程 Git 提交历史。返回 { commits, has_more }。 */
|
||||
/** 列出工程本地分支(只读)。返回 { current, branches: [{ name, is_current }] }。 */
|
||||
listBranches(moduleId: string): Promise<{ current: string; branches: { name: string; is_current: boolean }[] }> {
|
||||
return invoke('list_branches', { moduleId })
|
||||
},
|
||||
|
||||
getModuleCommits(moduleId: string, skip?: number, limit?: number): Promise<{
|
||||
commits: { hash: string; subject: string; timestamp: number; author: string }[]
|
||||
has_more: boolean
|
||||
@@ -163,6 +168,10 @@ export const moduleApi = {
|
||||
getCommitDetail(moduleId: string, commitHash: string): Promise<{
|
||||
files: { status: string; path: string }[]
|
||||
diff: string
|
||||
parents: string[]
|
||||
author: string
|
||||
date: string
|
||||
full_message: string
|
||||
}> {
|
||||
return invoke('get_commit_detail', { moduleId, commitHash })
|
||||
},
|
||||
|
||||
@@ -181,6 +181,7 @@
|
||||
*/
|
||||
import { ref, computed, watch, reactive, onMounted, onUnmounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { listen, type UnlistenFn } from '@tauri-apps/api/event'
|
||||
import { moduleApi, type ProjectModuleRecord, type FileTreeEntry, type GitStatusResult } from '@/api/module'
|
||||
import FileTree from './FileTree.vue'
|
||||
import FilePreview from './FilePreview.vue'
|
||||
@@ -364,6 +365,25 @@ function closeDropdown() { moduleDropdownOpen.value = false }
|
||||
onMounted(() => document.addEventListener('click', closeDropdown))
|
||||
onUnmounted(() => document.removeEventListener('click', closeDropdown))
|
||||
|
||||
// AI 工具写入文件后(df-data-changed entity=file)自动跳转变更视图 + 刷新
|
||||
let _unlistenDataChanged: UnlistenFn | null = null
|
||||
onMounted(async () => {
|
||||
_unlistenDataChanged = await listen<{ entity: string; action: string }>('df-data-changed', (e) => {
|
||||
if (e.payload.entity !== 'file') return
|
||||
// 刷新 git 状态缓存,使变更计数角标即时更新
|
||||
gitStatusData.value = null
|
||||
if (viewMode.value === 'changes') {
|
||||
// 已在变更视图,触发 GitChanges 重新拉取(切一次 viewMode 触发 watch)
|
||||
viewMode.value = 'tree'
|
||||
viewMode.value = 'changes'
|
||||
} else {
|
||||
// 不在变更视图,自动切换到变更视图,让用户看到 AI 写入的内容
|
||||
viewMode.value = 'changes'
|
||||
}
|
||||
})
|
||||
})
|
||||
onUnmounted(() => { _unlistenDataChanged?.() })
|
||||
|
||||
// 自动扫描子仓库
|
||||
async function onScanSubmodules() {
|
||||
moduleDropdownOpen.value = false
|
||||
|
||||
@@ -130,21 +130,36 @@ interface DiffLine {
|
||||
newNum: string
|
||||
}
|
||||
|
||||
/** 解析 @@ -a,b +c,d @@ 头,返回旧/新行起始号 */
|
||||
function parseHunkHeader(line: string): { oldStart: number; newStart: number } {
|
||||
// 格式: @@ -10,7 +10,8 @@
|
||||
const m = line.match(/@@\s+-(\d+)(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@/)
|
||||
if (!m) return { oldStart: 1, newStart: 1 }
|
||||
return { oldStart: parseInt(m[1], 10), newStart: parseInt(m[2], 10) }
|
||||
}
|
||||
|
||||
const diffLines = computed<DiffLine[]>(() => {
|
||||
if (!diffContent.value) return []
|
||||
const lines: DiffLine[] = []
|
||||
let oldNum = 0
|
||||
let newNum = 0
|
||||
for (const raw of diffContent.value.split('\n')) {
|
||||
if (raw.startsWith('@@')) {
|
||||
const h = parseHunkHeader(raw)
|
||||
oldNum = h.oldStart
|
||||
newNum = h.newStart
|
||||
lines.push({ type: 'hdr', prefix: '', text: raw, oldNum: '', newNum: '' })
|
||||
} else if (raw.startsWith('+')) {
|
||||
lines.push({ type: 'add', prefix: '+', text: raw.slice(1), oldNum: '', newNum: '' })
|
||||
lines.push({ type: 'add', prefix: '+', text: raw.slice(1), oldNum: '', newNum: String(newNum++) })
|
||||
} else if (raw.startsWith('-')) {
|
||||
lines.push({ type: 'del', prefix: '-', text: raw.slice(1), oldNum: '', newNum: '' })
|
||||
lines.push({ type: 'del', prefix: '-', text: raw.slice(1), oldNum: String(oldNum++), newNum: '' })
|
||||
} else if (raw.startsWith('\\')) {
|
||||
// No newline at end of file 等元信息
|
||||
lines.push({ type: 'ctx', prefix: '', text: raw, oldNum: '', newNum: '' })
|
||||
} else {
|
||||
lines.push({ type: 'ctx', prefix: ' ', text: raw, oldNum: '', newNum: '' })
|
||||
// 空串(diff 头前的空行)或 context 行(以空格开头)
|
||||
const text = raw.startsWith(' ') ? raw.slice(1) : raw
|
||||
lines.push({ type: 'ctx', prefix: ' ', text, oldNum: String(oldNum++), newNum: String(newNum++) })
|
||||
}
|
||||
}
|
||||
return lines
|
||||
|
||||
Reference in New Issue
Block a user