272 lines
10 KiB
TypeScript
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
|
|
});
|
|
}
|
|
}
|
|
}
|