From c315c18b4ebef3432b7e9638cfccc93312365270 Mon Sep 17 00:00:00 2001 From: kingecg Date: Sat, 8 Mar 2025 19:25:58 +0800 Subject: [PATCH] complete tree provider refactor --- package.json | 3 +- src/GitService.ts | 34 +++++++++--------- src/commands.ts | 71 +++++++++++++++++++++++++++++++++++++ src/commit-list.provider.ts | 29 ++++++++++----- src/commit-tree.item.ts | 6 ++++ src/extension.ts | 46 +++++++++++------------- src/view.provider.ts | 18 +++++----- 7 files changed, 146 insertions(+), 61 deletions(-) create mode 100644 src/commands.ts diff --git a/package.json b/package.json index bad63fc..73f3f60 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "title": "Show as list" }, { - "command": "gitcommitfilter.compare" + "command": "gitcommitfilter.compare", + "title": "比较文件变化" } ], "menus": { diff --git a/src/GitService.ts b/src/GitService.ts index fecd5b4..39ee21f 100644 --- a/src/GitService.ts +++ b/src/GitService.ts @@ -35,6 +35,10 @@ export class GitService { this._repoPath = workspaceFolders[0].uri.fsPath; } + get repoPath(): string { + return this._repoPath; + } + private transformFilter(filter: GitCommitFilter): string[] { const gitArgs: string[] = []; if(filter.committer) { @@ -54,7 +58,8 @@ export class GitService { return gitArgs; } - getDateStr(d:Date) :string { + getDateStr(d:Date|undefined) :string { + if(!d) {return '';} return d.toLocaleDateString('zh-CN'); } private defaultFilter(): string[] { @@ -75,17 +80,11 @@ export class GitService { return result.toString().trim(); } - public async getCommits(filter: string = ""): Promise { - const filterParts = filter.split(" "); - let nFilter: string[] = []; - filterParts.forEach((part) => { - const t = part.split(":"); - const key = t[0]; - const v = t[1]; - if (key in GitFilter) { - nFilter.push(`--${GitFilter[key]}=${v}`); - } - }); + public async getCommits(filter: GitCommitFilter = {}): Promise { + let nFilter :string[] = []; + if(Object.keys(filter).length !== 0){ + nFilter = this.transformFilter(filter); + } if(nFilter.length === 0){ nFilter = this.defaultFilter(); } @@ -97,6 +96,9 @@ export class GitService { "--date=format:%Y-%m-%d", "--name-only", ]; + if(filter.path) { + gitArgs.push(filter.path); + } const that = this; return new Promise((resolve, reject) => { @@ -146,7 +148,7 @@ export class GitService { if(lines.length > 0){ const commit = makeCommit(lines); if(commit){ - that.logger.info('push commit', commit.hash) + that.logger.info('push commit', commit.hash); commits.push(commit); } @@ -154,11 +156,11 @@ export class GitService { lines = []; lines.push(nline); } else if (nline.startsWith("---") ) { - continue + continue; }else if( nline.trim() === ""){ const commit = makeCommit(lines); if(commit){ - that.logger.info('push commit 2', commit.hash) + that.logger.info('push commit 2', commit.hash); commits.push(commit); } lines = []; @@ -174,7 +176,7 @@ export class GitService { const commit = makeCommit(lines); if(commit){ - that.logger.info('push commit 3', commit.hash) + that.logger.info('push commit 3', commit.hash); commits.push(commit); } } diff --git a/src/commands.ts b/src/commands.ts new file mode 100644 index 0000000..5ad8945 --- /dev/null +++ b/src/commands.ts @@ -0,0 +1,71 @@ +import path from "path"; +import { EventBus } from "./event-bus"; +import { GitService } from "./GitService"; +import * as vscode from "vscode"; + +export class Commander { + constructor(private _gitService: GitService, private eventBus: EventBus) { + console.log("Commander"); + } + + public async compare(commit: string, file: string) { + const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; + if (!workspaceFolder) { + vscode.window.showErrorMessage('Please open a workspace folder first'); + return; + } + try { + const rawPrevCommit = await this._gitService.getPreviousCommit(commit); + const previousCommitId = rawPrevCommit?.trim(); // 去除可能的换行符 + if (!previousCommitId) { + vscode.window.showErrorMessage('Cannot find previous commit'); + return; + } + + // 编码处理 + const encodedCommit = encodeURIComponent(commit.trim()); + const encodedPrevCommit = encodeURIComponent(previousCommitId); + const sanitizedWorkPath = workspaceFolder.replace(/\\/g, '/') + .replace(/'/g, "'") + .replace(/ /g, " "); + const sanitizedFilePath = file.replace(/\\/g, '/') + .replace(/'/g, "'") + .replace(/ /g, " "); + // 构造 URI + const currentUri = this.makeUri(sanitizedFilePath, encodedCommit); + + const previousUri = this.makeUri(sanitizedFilePath, encodedPrevCommit); + + await vscode.commands.executeCommand( + "vscode.diff", + previousUri, + currentUri, + `Changes: ${path.basename(file)} (${previousCommitId.slice(0, 7)} → ${commit.slice(0, 7)})` + ); + } catch (error) { + vscode.window.showErrorMessage(`Failed to open diff: ${error instanceof Error ? error.message : error}`); + } + } + private makeUri(filePath: string, commit: string) { + const repoPath = this._gitService.repoPath; + const repoUri = vscode.Uri.file(repoPath); + const fUri = vscode.Uri.joinPath(repoUri, filePath); + const params = { + path: fUri.fsPath, + ref: commit + } + return fUri.with({ scheme: 'git', path: fUri.path, query: JSON.stringify(params) }); + } + + public changeToList() { + // set state to list + vscode.commands.executeCommand('setContext', 'gitcommitfilter:viewState', 'list'); + this.eventBus.emit('changeView', 'list'); + } + + public changeToTree() { + // set state to tree + vscode.commands.executeCommand('setContext', 'gitcommitfilter:viewState', 'tree'); + this.eventBus.emit('changeView', 'tree'); + } +} \ No newline at end of file diff --git a/src/commit-list.provider.ts b/src/commit-list.provider.ts index db830d2..47b9f8c 100644 --- a/src/commit-list.provider.ts +++ b/src/commit-list.provider.ts @@ -8,6 +8,7 @@ export class CommitListProvider implements vscode.TreeDataProvider { private state:string = "list"; private filter: any = {}; private _commits: GitCommit[] = []; + private _items: CommitItem[] = []; private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; @@ -18,17 +19,30 @@ export class CommitListProvider implements vscode.TreeDataProvider { this.filter = filter; this.refresh(); }); - this.event.on("refresh",()=>{ + this.event.on("changeView",(state)=>{ + this.state = state; this._makeCommitItems(); this._onDidChangeTreeData.fire(); }); } _makeCommitItems() { - + this._items = this._commits.map(commit => { + const item = new CommitItem( + commit.msg+' -- '+commit.author+'@'+commit.hash+' '+ commit.date, + vscode.TreeItemCollapsibleState.None, + commit.hash||'', + this.state, + '', + commit.files||[] + ); + return item; + }); } - refresh(): void { + async refresh() { + this._commits = await this._gitService.getCommits(this.filter); + this._makeCommitItems(); this._onDidChangeTreeData.fire(); } @@ -43,12 +57,9 @@ export class CommitListProvider implements vscode.TreeDataProvider { async getChildren(element?: CommitItem): Promise { if (!element) { - const commits = await this._gitService.getCommits(""); - return commits.map(commit => new CommitItem(`${commit.msg} -- ${commit.author}@${commit.hash} ${commit.date}`, vscode.TreeItemCollapsibleState.Expanded, commit.files)); + return this._items; + // return commits.map(commit => new CommitItem(`${commit.msg} -- ${commit.author}@${commit.hash} ${commit.date}`, vscode.TreeItemCollapsibleState.Expanded, commit.files)); } - if (element.files) { - return element.files.map(file => new CommitItem(this._showFullPath ? file : path.basename(file), vscode.TreeItemCollapsibleState.None, undefined, file)); - } - return []; + return element.children||[]; } } \ No newline at end of file diff --git a/src/commit-tree.item.ts b/src/commit-tree.item.ts index 341afd7..ad7cc5d 100644 --- a/src/commit-tree.item.ts +++ b/src/commit-tree.item.ts @@ -43,6 +43,7 @@ import * as path from "path"; export class CommitItem extends vscode.TreeItem { public iconPath?: string | vscode.IconPath | undefined = 'file'; public children?: CommitItem[]; + public command?: vscode.Command | undefined = undefined constructor( public readonly label:string, public state : vscode.TreeItemCollapsibleState, @@ -54,6 +55,11 @@ export class CommitItem extends vscode.TreeItem { super(label, state); if(this.isFileItem()){ this.tooltip = this.fpath; + this.command = { + command: 'gitcommitfilter.compare', + title: '比较文件变化', + arguments: [this.commitHash, this.fpath] + }; } if(this.isCommitItem()) { this.iconPath = 'git-commit'; diff --git a/src/extension.ts b/src/extension.ts index d255c0c..55c6c71 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,6 +2,9 @@ import * as vscode from 'vscode'; import { GitService } from './GitService'; import { GViewProvider } from './view.provider'; +import { EventBus } from './event-bus'; +import { CommitListProvider } from './commit-list.provider'; +import { Commander } from './commands'; function getGitApi(){ @@ -22,9 +25,25 @@ export async function activate(context: vscode.ExtensionContext) { return; } const logger = vscode.window.createOutputChannel('GitCommitFilter',{log: true}); + const eventBus = new EventBus(); const gitService = new GitService(gitPath,logger); - + const commitlist = new CommitListProvider(gitService,eventBus) + + const commander = new Commander(gitService,eventBus); + context.subscriptions.push(vscode.commands.registerCommand('gitcommitfilter.showaslist', () => { + commander.changeToList(); + })); + context.subscriptions.push(vscode.commands.registerCommand('gitcommitfilter.showastree', () => { + commander.changeToTree(); + })); + context.subscriptions.push(vscode.commands.registerCommand('gitcommitfilter.compare', (commit, file) => { + commander.compare(commit, file); + })); + //get view gitcommitfilter.commit-list const viewProvider = new GViewProvider(context, gitService,logger); + const d= vscode.window.registerTreeDataProvider('gitcommitfilter.commit-list', commitlist); + context.subscriptions.push(d); + commitlist.refresh(); const activityBarIcon = vscode.window.registerWebviewViewProvider(GViewProvider.viewType, viewProvider); @@ -33,28 +52,3 @@ export async function activate(context: vscode.ExtensionContext) { export function deactivate() {} -// 实现TreeDataProvider接口 -class CommitTreeDataProvider implements vscode.TreeDataProvider { - private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); - readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; - - private gitService: GitService; - - constructor(gitService: GitService) { - this.gitService = gitService; - } - - refresh(): void { - this._onDidChangeTreeData.fire(); - } - - getTreeItem(element: vscode.TreeItem): vscode.TreeItem { - return element; - } - - getChildren(element?: vscode.TreeItem): vscode.ProviderResult { - if (element) { - return []; - } - } -} \ No newline at end of file diff --git a/src/view.provider.ts b/src/view.provider.ts index 0467dd4..2131c0d 100644 --- a/src/view.provider.ts +++ b/src/view.provider.ts @@ -19,7 +19,7 @@ export class GViewProvider implements vscode.WebviewViewProvider { ) { webviewView.webview.options = { enableScripts: true }; this._view = webviewView; - webviewView.webview.html = this.generateHtml(webviewView); + webviewView.webview.html = '';//this.generateHtml(webviewView); // 添加事件监听器 webviewView.webview.onDidReceiveMessage( @@ -261,13 +261,13 @@ private async openDiff(commitId: string, filePath: string) { } private async filterCommits(filterText: string) { - if (this._view) { - const commits = await this._gitService.getCommits(filterText); - this._logger.info(`filteredCommits:, ${JSON.stringify(commits)}"`); - this._view.webview.postMessage({ - command: "updateCommits", - commits - }); - } + // if (this._view) { + // const commits = await this._gitService.getCommits(filterText); + // this._logger.info(`filteredCommits:, ${JSON.stringify(commits)}"`); + // this._view.webview.postMessage({ + // command: "updateCommits", + // commits + // }); + // } } }