gitcommitfilter/src/view.provider.ts

272 lines
10 KiB
TypeScript

import * as vscode from "vscode";
import * as path from "path";
import { GitService } from "./GitService";
export class GViewProvider implements vscode.WebviewViewProvider {
public static readonly viewType = "gitcommitfilter.view";
private _view?: vscode.WebviewView;
constructor(
private context: vscode.ExtensionContext,
private readonly _gitService: GitService
) {}
public resolveWebviewView(
webviewView: vscode.WebviewView,
context: vscode.WebviewViewResolveContext
) {
webviewView.webview.options = { enableScripts: true };
this._view = webviewView;
webviewView.webview.html = this.generateHtml(webviewView);
// 添加事件监听器
webviewView.webview.onDidReceiveMessage(
(message: { command: any; text: any; filePath: any; hash: any; }) => {
switch (message.command) {
case "filterCommits":
const filterText = message.text;
this.filterCommits(filterText);
return;
case "openFile":
const filePath = message.filePath;
const commit = message.hash;
this.openDiff(commit, filePath);
return;
}
},
undefined,
this.context.subscriptions
);
}
private makeUri(repoPath:string, filePath:string, commit:string) {
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) });
}
private async openDiff(commitId: string, filePath: 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(commitId);
const previousCommitId = rawPrevCommit?.trim(); // 去除可能的换行符
if (!previousCommitId) {
vscode.window.showErrorMessage('Cannot find previous commit');
return;
}
// 编码处理
const encodedCommit = encodeURIComponent(commitId.trim());
const encodedPrevCommit = encodeURIComponent(previousCommitId);
const sanitizedWorkPath = workspaceFolder.replace(/\\/g, '/')
.replace(/'/g, "'")
.replace(/ /g, " ");
const sanitizedFilePath = filePath.replace(/\\/g, '/')
.replace(/'/g, "'")
.replace(/ /g, " ");
// 构造 URI
const currentUri = this.makeUri(sanitizedWorkPath, sanitizedFilePath, encodedCommit);
const previousUri = this.makeUri(sanitizedWorkPath, sanitizedFilePath, encodedPrevCommit);
await vscode.commands.executeCommand(
"vscode.diff",
previousUri,
currentUri,
`Changes: ${path.basename(filePath)} (${previousCommitId.slice(0,7)}${commitId.slice(0,7)})`
);
} catch (error) {
vscode.window.showErrorMessage(`Failed to open diff: ${error instanceof Error ? error.message : error}`);
}
}
generateHtml(webviewView: vscode.WebviewView) {
return `
<!DOCTYPE html>
<html lang="en" class="dark">
<head >
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Git Commit Filter</title>
<link rel="stylesheet" href="${webviewView.webview.asWebviewUri(
vscode.Uri.file(
path.join(this.context.extensionPath, "media", "elui.css")
)
)}">
<style>
body {
font-family: var(--vscode-font-family);
color: var(--vscode-foreground);
background-color: var(--vscode-editor-background);
padding: 20px;
margin: 0;
}
input[type="text"] {
width: 100%;
padding: 8px;
margin-bottom: 10px;
background-color: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border: 1px solid var(--vscode-input-border);
}
button {
padding: 8px 16px;
background-color: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border: 1px solid var(--vscode-button-border);
cursor: pointer;
}
button:hover {
background-color: var(--vscode-button-hoverBackground);
}
</style>
<link rel="stylesheet" href="${webviewView.webview.asWebviewUri(
vscode.Uri.file(
path.join(this.context.extensionPath, "media", "elui.css")
)
)}">
<link rel="stylesheet" href="${webviewView.webview.asWebviewUri(
vscode.Uri.file(
path.join(this.context.extensionPath, "media", "elui.color.css")
)
)}">
</head>
<body>
<div id="app">
<el-input v-model="filterText" placeholder="Enter commit message to filter"></el-input>
<el-button @click="filterCommits">过滤</el-button>
<el-tree :data="commits" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</div>
<script src="${webviewView.webview.asWebviewUri(
vscode.Uri.file(
path.join(this.context.extensionPath, "media", "vue.js")
)
)}"></script>
<script src="${webviewView.webview.asWebviewUri(
vscode.Uri.file(
path.join(this.context.extensionPath, "media", "elui.js")
)
)}"></script>
<script>
const vscode = acquireVsCodeApi();
new Vue({
el: '#app',
data() {
return {
filterText: '',
commits: [],
defaultProps: {
children: 'children',
label: 'message'
}
};
},
methods: {
handleNodeClick(data, node, component) {
if(data.type === 'file'){
vscode.postMessage({ command: 'openFile', filePath: data.fullPath, hash: data.hash });
}
},
filterCommits() {
vscode.postMessage({ command: 'filterCommits', text: this.filterText });
},
updateCommits(commits) {
// this.commits = commits.map(commit => ({
// message: commit.msg,
// files: commit.files.map(file => ({ message: file }))
// }));
const nodes = [];
for(let commit of commits){
const node = {
message: commit.msg + ' -- ' + commit.author + '@'+ commit.hash+ " " + commit.date,
type: 'commit',
children:[]
}
if(commit.files) {
//group path to tree
for(let file of commit.files){
const path = file.split('/');
let currentNode = node;
for(let i = 0; i < path.length; i++){
const pathItem = path[i];
let childNode = currentNode.children.find(child => child.message === pathItem);
if(!childNode){
childNode= {}
currentNode.children.push(childNode);
}
if(i === path.length - 1){
childNode.message = pathItem;
childNode.type = 'file';
childNode.fullPath = file;
childNode.hash = commit.hash;
} else {
childNode.message = childNode.message||pathItem;
childNode.type = 'folder';
childNode.children = childNode.children||[];
}
currentNode = childNode;
}
}
}
nodes.push(node);
}
this.commits = nodes;
}
},
mounted() {
const root = document.documentElement;
const observer = new MutationObserver(() => {
root.style.setProperty('--el-color-primary', getComputedStyle(root).getPropertyValue('--vscode-button-background'));
});
observer.observe(root, { attributes: true, attributeFilter: ['style'] });
console.log("mounted")
debugger;
window.addEventListener('message', event => {
console.log("recv update",JSON.stringify(event.data));
debugger;
const message = event.data;
switch (message.command) {
case 'updateCommits':
this.updateCommits(message.commits);
break;
}
});
setTimeout(()=> vscode.postMessage({ command: 'filterCommits', text: '' }),100);
}
});
</script>
</body>
</html>
`;
}
private async filterCommits(filterText: string) {
if (this._view) {
const commits = await this._gitService.getCommits(filterText);
this._view.webview.postMessage({
command: "updateCommits",
commits
});
}
}
}