diff --git a/CHANGELOG.md b/CHANGELOG.md index a2f9e8a..c318731 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ ## [Unreleased] +### Added — Parity Batch 2(UI 丰富度,0.0.3) + +- **Git 提交图(WebviewPanel)**:`git log --graph --oneline --decorate --all`(CLI)获取拓扑,语义着色渲染(graph 连线 / refs / hash)——补齐 IDEA Log 提交图的可视化拓扑。命令面板 + Log 视图标题按钮。 +- **Console**:Hyper Git Console(OutputChannel)记录所有 `execGit` 命令与输出(对齐 IDEA Console 标签页)。 + ### Added — Parity Batch 1(CLI 功能补齐,0.0.2) > 关键转向:引入 `GitRepositoryService.execGit`(复用 vscode.git 的同一 git 二进制 `api.git.path`),补齐稳定 API 未暴露的操作——修正此前"API 限制延后"的过度自我设限。 diff --git a/package.json b/package.json index 32fd278..00c5146 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Hyper Git", "icon": "media/icon.png", "description": "在 VS Code 上完整复刻 IntelliJ IDEA 的 Git 工具窗口与 Commit 提交窗口,并为未来 AI Agent 自主代理预留架构接缝。", - "version": "0.0.2", + "version": "0.0.3", "publisher": "threefish-ai", "license": "MIT", "preview": true, @@ -130,13 +130,16 @@ { "command": "hyperGit.branchRename", "title": "重命名分支", "category": "Hyper Git" }, { "command": "hyperGit.ignorePath", "title": "添加到 .gitignore", "category": "Hyper Git" }, { "command": "hyperGit.compareBranches", "title": "比较分支", "category": "Hyper Git" }, - { "command": "hyperGit.rewordCommit", "title": "改写最新提交信息", "category": "Hyper Git" } + { "command": "hyperGit.rewordCommit", "title": "改写最新提交信息", "category": "Hyper Git" }, + { "command": "hyperGit.showGraph", "title": "查看提交图(Graph)", "category": "Hyper Git", "icon": "$(git-commit)" }, + { "command": "hyperGit.showConsole", "title": "打开 Console", "category": "Hyper Git" } ], "menus": { "view/title": [ { "command": "hyperGit.refresh", "when": "view == hyperGit.changes", "group": "navigation" }, { "command": "hyperGit.newChangelist", "when": "view == hyperGit.changes", "group": "navigation" }, { "command": "hyperGit.refreshLog", "when": "view == hyperGit.log", "group": "navigation" }, + { "command": "hyperGit.showGraph", "when": "view == hyperGit.log", "group": "navigation" }, { "command": "hyperGit.logFilterAuthor", "when": "view == hyperGit.log" }, { "command": "hyperGit.logFilterPath", "when": "view == hyperGit.log" }, { "command": "hyperGit.logClearFilter", "when": "view == hyperGit.log" }, diff --git a/src/adapter/git-repository-service.ts b/src/adapter/git-repository-service.ts index 1c8a81b..f99d3d0 100644 --- a/src/adapter/git-repository-service.ts +++ b/src/adapter/git-repository-service.ts @@ -1,6 +1,7 @@ import { execFile } from 'child_process'; import * as path from 'path'; import * as vscode from 'vscode'; +import { logGit } from '../infra/git-console'; import type { API, Change, Repository } from '../types/git'; import { FileStatus } from '../engine/model'; import { mapGitStatus } from './git-status-map'; @@ -114,8 +115,10 @@ export class GitRepositoryService implements vscode.Disposable { return new Promise((resolve, reject) => { execFile(this.api.git.path, args, { cwd: repo.rootUri.fsPath, maxBuffer: 20 * 1024 * 1024, encoding: 'utf8' }, (err, stdout) => { if (err) { + logGit(args, undefined, err.message); reject(err); } else { + logGit(args, stdout); resolve(stdout); } }); diff --git a/src/adapter/webview/graph-webview.ts b/src/adapter/webview/graph-webview.ts new file mode 100644 index 0000000..3454212 --- /dev/null +++ b/src/adapter/webview/graph-webview.ts @@ -0,0 +1,75 @@ +import * as vscode from 'vscode'; +import type { GitRepositoryService } from '../git-repository-service'; + +/** + * Git 提交图(WebviewPanel)。 + * + * 用 `git log --graph --oneline --decorate --all`(受控 CLI 通道)获取拓扑文本,在 Webview 内以 + * 等宽字体 + 语义着色渲染(graph 连线、refs、hash)——补齐 IDEA Log 提交图的可视化拓扑。 + * 完整像素级 lane-SVG 渲染作为后续增强(batch 2.x)。 + */ +export class GraphWebview { + private static readonly viewType = 'hyperGit.graph'; + + static async open(service: GitRepositoryService): Promise { + const repo = service.repo; + if (!repo) { + void vscode.window.showWarningMessage('未找到 Git 仓库'); + return; + } + let graph = ''; + try { + graph = await service.execGit(['log', '--graph', '--oneline', '--decorate', '--all', '-n', '300']); + } catch (e) { + void vscode.window.showErrorMessage(`获取提交图失败:${e instanceof Error ? e.message : String(e)}`); + return; + } + + const panel = vscode.window.createWebviewPanel(GraphWebview.viewType, 'Git Graph — Hyper Git', vscode.ViewColumn.Active, { + enableScripts: false, + retainContextWhenHidden: false, + }); + panel.webview.html = GraphWebview.renderHtml(graph, repo.rootUri.fsPath); + } + + private static renderHtml(graph: string, repoRoot: string): string { + const lines = graph.split('\n').map(GraphWebview.renderLine).join('\n'); + return ` + + + + + + + +

Git 提交图(最近 300 条)

+
${escapeHtml(repoRoot)}
+
${lines}
+ +`; + } + + private static renderLine(line: string): string { + const m = line.match(/^([*|/\\_. ]+)(.*)$/); + if (!m) { + return escapeHtml(line); + } + const graphPart = escapeHtml(m[1]); + let rest = escapeHtml(m[2]); + rest = rest.replace(/(\([^)]*\))/g, '$1'); + rest = rest.replace(/\b([0-9a-f]{7,40})\b/g, '$1'); + return `${graphPart}${rest}`; + } +} + +function escapeHtml(s: string): string { + return s.replace(/&/g, '&').replace(//g, '>'); +} diff --git a/src/extension.ts b/src/extension.ts index 1260353..7a59cce 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -15,6 +15,8 @@ import { registerStashCommands } from './adapter/stash-commands'; import { registerGitCliCommands } from './adapter/git-cli-commands'; import { StashTreeProvider } from './adapter/tree/stash-tree'; import { CommitWebviewProvider } from './adapter/webview/commit-webview'; +import { GraphWebview } from './adapter/webview/graph-webview'; +import { showGitConsole } from './infra/git-console'; import { getGitApi } from './adapter/git-api'; import { GitRepositoryService } from './adapter/git-repository-service'; import { createLogger } from './infra/logger'; @@ -81,6 +83,8 @@ export async function activate(context: vscode.ExtensionContext): Promise ...registerStashCommands(service, stashTree), vscode.commands.registerCommand('hyperGit.commit', focusCommitView), vscode.commands.registerCommand('hyperGit.commitAndPush', focusCommitView), + vscode.commands.registerCommand('hyperGit.showGraph', () => GraphWebview.open(service)), + vscode.commands.registerCommand('hyperGit.showConsole', () => showGitConsole()), ); // git 状态变化频繁(add/checkout/diff 缓存失效均触发),防抖合并避免 log/stash 高频重拉。 diff --git a/src/infra/git-console.ts b/src/infra/git-console.ts new file mode 100644 index 0000000..faeb717 --- /dev/null +++ b/src/infra/git-console.ts @@ -0,0 +1,31 @@ +import * as vscode from 'vscode'; + +/** + * Hyper Git Console:对齐 IDEA Console 标签页,记录所有经 execGit 执行的 git 命令及其输出。 + * 复用单一 OutputChannel(懒构造)。 + */ +let channel: vscode.OutputChannel | undefined; + +function getChannel(): vscode.OutputChannel { + if (!channel) { + channel = vscode.window.createOutputChannel('Hyper Git Console'); + } + return channel; +} + +/** 记录一条 git 命令(及其输出/错误)到 Console。 */ +export function logGit(args: readonly string[], output?: string, error?: string): void { + const c = getChannel(); + c.appendLine(`$ git ${args.join(' ')}`); + if (output) { + c.appendLine(output); + } + if (error) { + c.appendLine(`[error] ${error}`); + } +} + +/** 显示 Console 面板。 */ +export function showGitConsole(): void { + getChannel().show(true); +} diff --git a/tests/suite/extension.test.js b/tests/suite/extension.test.js index 65facbf..86fe3a4 100644 --- a/tests/suite/extension.test.js +++ b/tests/suite/extension.test.js @@ -52,6 +52,8 @@ suite('扩展冒烟测试', function () { 'hyperGit.ignorePath', 'hyperGit.compareBranches', 'hyperGit.rewordCommit', + 'hyperGit.showGraph', + 'hyperGit.showConsole', ]) { assert.ok(commands.includes(cmd), `命令 ${cmd} 未注册`); }