diff --git a/package.json b/package.json index a9a6a06..bad63fc 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,36 @@ "activationEvents": [], "main": "./dist/extension.js", "contributes": { + "commands": [ + { + "command": "gitcommitfilter.showastree", + "title": "Show as tree" + }, + { + "command": "gitcommitfilter.showaslist", + "title": "Show as list" + }, + { + "command": "gitcommitfilter.compare" + } + ], + "menus": { + "view/title":[ + { + "command": "gitcommitfilter.showastree", + "when": "view == gitcommitfilter.commit-list && gitcommitfilter.viewState=='list'", + "group": "navigation", + "icon":"${list-tree}" + }, + { + "command": "gitcommitfilter.showaslist", + "when": "view == gitcommitfilter.commit-list && gitcommitfilter.viewState=='tree'", + "group": "navigation", + "icon":"${list-flat}" + } + + ] + }, "viewsContainers": { "activitybar": [ { @@ -37,6 +67,11 @@ "id": "gitcommitfilter.view", "name": "MyCustomView", "when": "true" + }, + { + "id": "gitcommitfilter.commit-list", + "name": "Commitlist", + "type": "tree" } ] } diff --git a/src/GitService.ts b/src/GitService.ts index e333238..fecd5b4 100644 --- a/src/GitService.ts +++ b/src/GitService.ts @@ -15,6 +15,15 @@ export interface GitCommit { files?: string[]; } +export interface GitCommitFilter{ + path?:string; + contains?:string; + committer?:string; + dateRange?:[Date, Date] +} + + + export class GitService { private _repoPath: string; @@ -25,9 +34,46 @@ export class GitService { } this._repoPath = workspaceFolders[0].uri.fsPath; } + + private transformFilter(filter: GitCommitFilter): string[] { + const gitArgs: string[] = []; + if(filter.committer) { + gitArgs.push(`--author=${filter.committer}`); + } + if(filter.dateRange) { + const [from, to] = filter.dateRange; + gitArgs.push(`--since=${this.getDateStr(from)}`); + // get next day of to + const toNextDay = new Date(to); + toNextDay.setDate(toNextDay.getDate() + 1); + gitArgs.push(`--until=${this.getDateStr(toNextDay)}`); + } + if(filter.contains) { + gitArgs.push(`--grep=${filter.contains}`); + } + return gitArgs; + } + + getDateStr(d:Date) :string { + return d.toLocaleDateString('zh-CN'); + } private defaultFilter(): string[] { return ['--author=$(git config user.name)', '--since=\"7 days ago\"']; } + getCommitters() : string[] { + const command = `git log --format='%aN' | sort -u`; + const result = child_process.execSync(`git log --format='%aN' | sort -u`, { + cwd: this._repoPath, + }); + return result.toString().split("\n"); + } + + getCurrentUser(): string { + const result = child_process.execSync(`git config user.name`, { + cwd: this._repoPath, + }); + return result.toString().trim(); + } public async getCommits(filter: string = ""): Promise { const filterParts = filter.split(" "); diff --git a/src/commit-list.provider.ts b/src/commit-list.provider.ts new file mode 100644 index 0000000..db830d2 --- /dev/null +++ b/src/commit-list.provider.ts @@ -0,0 +1,54 @@ +import * as vscode from "vscode"; +import { GitService, GitCommit } from "./GitService"; +import path from "path"; +import { CommitItem } from "./commit-tree.item"; +import { EventBus } from "./event-bus"; + +export class CommitListProvider implements vscode.TreeDataProvider { + private state:string = "list"; + private filter: any = {}; + private _commits: GitCommit[] = []; + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + + private _showFullPath: boolean = false; + + constructor(private readonly _gitService: GitService,private event: EventBus) { + this.event.on("filter",(filter)=>{ + this.filter = filter; + this.refresh(); + }); + this.event.on("refresh",()=>{ + this._makeCommitItems(); + this._onDidChangeTreeData.fire(); + }); + } + + _makeCommitItems() { + + } + + refresh(): void { + this._onDidChangeTreeData.fire(); + } + + toggleFullPath(): void { + this._showFullPath = !this._showFullPath; + this.refresh(); + } + + getTreeItem(element: CommitItem): vscode.TreeItem { + return element; + } + + 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)); + } + if (element.files) { + return element.files.map(file => new CommitItem(this._showFullPath ? file : path.basename(file), vscode.TreeItemCollapsibleState.None, undefined, file)); + } + return []; + } +} \ No newline at end of file diff --git a/src/commit-tree.item.ts b/src/commit-tree.item.ts new file mode 100644 index 0000000..341afd7 --- /dev/null +++ b/src/commit-tree.item.ts @@ -0,0 +1,115 @@ +import * as vscode from "vscode"; +import * as path from "path"; + + +// interface PathItem { +// fpath?: string; +// label: string; +// children?: PathTree; +// } +// interface PathTree { +// [key: string]: PathItem; +// } +// function makePathTree(paths: string[]): PathTree{ +// const pathTree: PathTree = {}; +// for (const file of paths) { +// const pathParts = file.split("/"); +// if(pathParts.length === 1){ +// pathTree[pathParts[0]] = {fpath: file, label: pathParts[0]}; +// continue; +// } +// let currentNode = pathTree; +// for (let i = 0; i < pathParts.length; i++) { +// const pathItem = pathParts[i]; +// if(!currentNode.chilren){ +// currentNode.chilren = {}; +// } +// if (!currentNode.chilren[pathItem]) { +// currentNode[pathItem] = { +// label: pathItem, +// children: i !== pathParts.length - 1 ? {} : undefined, +// fpath: i === pathParts.length - 1 ? file : undefined, +// }; +// } else +// if(i !== pathParts.length - 1) { +// currentNode = currentNode[pathItem].children; +// } + +// } +// } +// return {}; +// } + +export class CommitItem extends vscode.TreeItem { + public iconPath?: string | vscode.IconPath | undefined = 'file'; + public children?: CommitItem[]; + constructor( + public readonly label:string, + public state : vscode.TreeItemCollapsibleState, + public readonly commitHash: string, + public readonly style:string, + public readonly fpath?: string, + public readonly files?: string[] + ){ + super(label, state); + if(this.isFileItem()){ + this.tooltip = this.fpath; + } + if(this.isCommitItem()) { + this.iconPath = 'git-commit'; + } + } + makeChildren() { + if(this.isCommitItem()) { + if(this.style === "list"){ + this.children = this.files?.map(file => { + return new CommitItem(path.basename(file), vscode.TreeItemCollapsibleState.None, this.commitHash, "list", file); + }); + } else { + if(!this.files) { + return + } + for(const file of this.files) { + const fileParts = file.split("/"); + if(fileParts.length === 1){ + this.addChildren(new CommitItem(fileParts[0], vscode.TreeItemCollapsibleState.None, this.commitHash, "tree", file)); + continue; + } + let currentNode: CommitItem = this; + for (let i = 0; i < fileParts.length; i++) { + const pathItem = fileParts[i]; + const child = currentNode.getChildItem(pathItem); + if(!child) { + const newChild = new CommitItem(pathItem, vscode.TreeItemCollapsibleState.Collapsed, this.commitHash, "tree"); + currentNode.addChildren(newChild); + currentNode = newChild; + } else { + currentNode = child; + } + } + } + } + } + } + + isCommitItem() { + return this.files && !this.fpath; + } + + isFileItem() { + return this.fpath && this.children?.length === 0; + } + isFolderPath() { + return this.fpath && this.children && this.children?.length > 0; + } + addChildren(item: CommitItem) { + this.iconPath = 'folder'; + const index = this.children?.findIndex(child => child.label === item.label); + if(index === -1) { + this.children?.push(item); + } + } + getChildItem(label: string) { + return this.children?.find(child => child.label === label); + } +} \ No newline at end of file diff --git a/src/event-bus.ts b/src/event-bus.ts new file mode 100644 index 0000000..571b67e --- /dev/null +++ b/src/event-bus.ts @@ -0,0 +1,8 @@ +import { EventEmitter } from "events"; + +export class EventBus extends EventEmitter { + constructor() { + super(); + } + +} \ No newline at end of file