From 135b0a8afe3f82e3874dd5bb0a1ba4c6887349ae Mon Sep 17 00:00:00 2001 From: ZLY Date: Fri, 25 Jul 2025 10:38:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(extension):=20=E5=A2=9E=E5=8A=A0=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E5=8C=BA=E5=8F=98=E6=9B=B4=E7=AE=A1=E7=90=86=E5=92=8C?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=B7=AE=E5=BC=82=E5=AF=B9=E6=AF=94=E5=8A=9F?= =?UTF-8?q?=E8=83=BD-=20=E6=96=B0=E5=A2=9E=E5=B7=A5=E4=BD=9C=E5=8C=BA?= =?UTF-8?q?=E5=8F=98=E6=9B=B4=E5=88=97=E8=A1=A8=EF=BC=8C=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=8A=B6=E6=80=81=E5=92=8C=E5=8F=98=E6=9B=B4?= =?UTF-8?q?=E5=86=85=E5=AE=B9=20-=20=E5=AE=9E=E7=8E=B0=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=B7=AE=E5=BC=82=E5=AF=B9=E6=AF=94=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=8E=A5=E5=8F=97=E6=88=96=E6=8B=92=E7=BB=9D=E5=8F=98=E6=9B=B4?= =?UTF-8?q?=20-=20=E4=BC=98=E5=8C=96=E4=B8=8A=E4=B8=8B=E6=96=87=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E7=95=8C=E9=9D=A2=E5=92=8C=E6=B6=88=E6=81=AF=E5=B1=95?= =?UTF-8?q?=E7=A4=BA=E6=A0=B7=E5=BC=8F-=20=E6=B7=BB=E5=8A=A0=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E6=8C=87=E7=A4=BA=E5=92=8C=E9=94=99=E8=AF=AF=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- media/webview/index.html | 75 +++++-- media/webview/script.js | 160 ++++++++++++++- media/webview/style.css | 427 +++++++++++++++++++++++---------------- src/extension.ts | 195 +++++++++++++++++- 4 files changed, 651 insertions(+), 206 deletions(-) diff --git a/media/webview/index.html b/media/webview/index.html index f7757b1..d274c5a 100644 --- a/media/webview/index.html +++ b/media/webview/index.html @@ -1,29 +1,74 @@ - + AI Chat +
+
+ + +
+ + +
+ 添加上下文 +
+ + +
+ + +
+

工作区变更

+
+
暂无文件变更
+
+
- - + --> - +
+ + + +
@@ -32,5 +77,7 @@ + + \ No newline at end of file diff --git a/media/webview/script.js b/media/webview/script.js index 89237f2..b4eea4d 100644 --- a/media/webview/script.js +++ b/media/webview/script.js @@ -1,4 +1,5 @@ let selectedCodeForContext = ''; +let contextFilePath = ''; // 存储当前上下文文件路径 const vscode = acquireVsCodeApi(); document.addEventListener('DOMContentLoaded', () => { @@ -6,6 +7,12 @@ document.addEventListener('DOMContentLoaded', () => { const userInput = document.getElementById('user-input'); const chatBox = document.getElementById('chat-box'); const loading = document.getElementById('loading'); + + // 初始化隐藏工作区 + const workspaceContainer = document.querySelector('.workspace-container'); + if (workspaceContainer) { + workspaceContainer.style.display = 'none'; + } chatForm.addEventListener('submit', (e) => { e.preventDefault(); @@ -15,7 +22,8 @@ document.addEventListener('DOMContentLoaded', () => { vscode.postMessage({ command: 'ask', text, - fileContent: selectedCodeForContext + fileContent: selectedCodeForContext, + fileContentPath: contextFilePath // 添加文件路径 }); // addMessage('user', text); @@ -70,7 +78,9 @@ document.addEventListener('DOMContentLoaded', () => { // 高亮代码块 const codeBlocks = msgDiv.querySelectorAll('pre code'); codeBlocks.forEach(block => { - hljs.highlightElement(block); + if (hljs) { + hljs.highlightElement(block); + } }); // 滚动到底部 @@ -86,27 +96,82 @@ document.addEventListener('DOMContentLoaded', () => { // 显示添加的行 if (codeDiff.added && codeDiff.added.length > 0) { - codeDiff.added.forEach(line => { + codeDiff.added.forEach((line, index) => { + const diffRow = document.createElement('div'); + diffRow.className = 'diff-row'; + + // 修复行号显示问题 + const lineNumber = document.createElement('div'); + lineNumber.className = 'diff-line-number'; + lineNumber.textContent = index + 1; // 使用索引+1作为行号 + const diffLine = document.createElement('div'); diffLine.className = 'diff-line diff-added'; diffLine.textContent = `+ ${line}`; - diffContent.appendChild(diffLine); + + diffRow.appendChild(lineNumber); + diffRow.appendChild(diffLine); + diffContent.appendChild(diffRow); }); } // 显示删除的行 if (codeDiff.removed && codeDiff.removed.length > 0) { - codeDiff.removed.forEach(line => { + codeDiff.removed.forEach((line, index) => { + const diffRow = document.createElement('div'); + diffRow.className = 'diff-row'; + + // 修复行号显示问题 + const lineNumber = document.createElement('div'); + lineNumber.className = 'diff-line-number'; + lineNumber.textContent = index + 1; // 使用索引+1作为行号 + const diffLine = document.createElement('div'); diffLine.className = 'diff-line diff-removed'; diffLine.textContent = `- ${line}`; - diffContent.appendChild(diffLine); + + diffRow.appendChild(lineNumber); + diffRow.appendChild(diffLine); + diffContent.appendChild(diffRow); }); } + // 添加接受和拒绝按钮的事件监听 + document.getElementById('accept-changes-btn').onclick = () => acceptChanges(codeDiff.modifiedCode); + document.getElementById('reject-changes-btn').onclick = () => rejectChanges(); + diffModal.classList.remove('hidden'); } + // 接受代码变更 + function acceptChanges(modifiedCode) { + if (contextFilePath) { + // 发送消息到插件以接受变更 + vscode.postMessage({ + command: 'acceptChanges', + filePath: contextFilePath, + modifiedContent: modifiedCode + }); + + // 关闭模态框 + document.getElementById('diff-modal').classList.add('hidden'); + } + } + + // 拒绝代码变更 + function rejectChanges() { + if (contextFilePath) { + // 发送消息到插件以拒绝变更 + vscode.postMessage({ + command: 'rejectChanges', + filePath: contextFilePath + }); + + // 关闭模态框 + document.getElementById('diff-modal').classList.add('hidden'); + } + } + function showLoading() { loading.classList.remove('hidden'); } @@ -115,6 +180,65 @@ document.addEventListener('DOMContentLoaded', () => { loading.classList.add('hidden'); } + // 更新工作区文件列表 + function updateWorkspaceFiles(files) { + const workspaceContainer = document.querySelector('.workspace-container'); + const workspaceFilesContainer = document.getElementById('workspace-files'); + + // 如果没有文件变更,则隐藏工作区 + if (!files || Object.keys(files).length === 0) { + workspaceContainer.style.display = 'none'; + return; + } + + // 显示工作区 + workspaceContainer.style.display = 'block'; + workspaceFilesContainer.innerHTML = ''; + + Object.keys(files).forEach(filePath => { + const fileChange = files[filePath]; + const fileItem = document.createElement('div'); + fileItem.className = 'workspace-file-item'; + + // 获取文件名 + const fileName = filePath.split(/[\/\\]/).pop(); + + // 根据状态设置不同的显示样式 + let statusText = ''; + let statusClass = ''; + + switch (fileChange.status) { + case 'accepted': + statusText = '✓ 已接受'; + statusClass = 'status-accepted'; + break; + case 'rejected': + statusText = '✗ 已拒绝'; + statusClass = 'status-rejected'; + break; + default: + statusText = '待处理'; + statusClass = 'status-pending'; + } + + fileItem.innerHTML = ` + 📄 + ${fileName} + ${statusText} + `; + + fileItem.addEventListener('click', () => { + // 发送消息到插件以打开文件 + vscode.postMessage({ + command: 'openWorkspaceFile', + filePath: filePath + }); + }); + + workspaceFilesContainer.appendChild(fileItem); + }); + } + window.addEventListener('message', (event) => { const message = event.data; console.log(message) @@ -157,6 +281,12 @@ document.addEventListener('DOMContentLoaded', () => { // 关闭模态框 document.getElementById('file-browser-modal').classList.add('hidden'); + + // 清空文件搜索输入框 + document.getElementById('file-search-input').value = ''; + + // 保存当前上下文文件路径 + contextFilePath = file.path; }); fileListContainer.appendChild(fileItem); @@ -171,10 +301,27 @@ document.addEventListener('DOMContentLoaded', () => { fileNameElement.innerText = fileName; fileNameElement.title = fullPath; selectedCodeForContext = message.fileContent; + contextFilePath = fullPath; // 保存上下文文件路径 // 隐藏占位符,显示文件标签 contextPlaceholder.classList.add('hidden'); contextTab.classList.remove('hidden'); + } else if (message.command === 'updateWorkspaceFiles') { + // 更新工作区文件列表 + updateWorkspaceFiles(message.files); + } else if (message.command === 'restoreState') { + // 恢复面板状态 + currentSessionHistory = message.history || []; + + // 恢复显示历史消息 + currentSessionHistory.forEach(msg => { + addMessage(msg.role, msg.content, null); + }); + + // 恢复工作区变更 + if (message.workspaceChanges) { + updateWorkspaceFiles(message.workspaceChanges); + } } }); @@ -221,6 +368,7 @@ document.addEventListener('DOMContentLoaded', () => { contextTab.classList.add('hidden'); contextPlaceholder.classList.remove('hidden'); selectedCodeForContext = ''; + contextFilePath = ''; }); // 关闭差异模态框 document.getElementById('close-diff-modal').addEventListener('click', () => { diff --git a/media/webview/style.css b/media/webview/style.css index 3f38981..f244a9d 100644 --- a/media/webview/style.css +++ b/media/webview/style.css @@ -1,214 +1,191 @@ /* 基础样式 */ body { - background-color: #282c34; - color: white; + font-family: var(--vscode-font-family); + font-size: var(--vscode-font-size); + background-color: var(--vscode-editor-background); + color: var(--vscode-editor-foreground); margin: 0; padding: 0; + height: 100vh; + display: flex; + flex-direction: column; } +/* 聊天容器 */ .chat-container { display: flex; flex-direction: column; height: 100vh; - overflow: hidden; + padding: 10px; + box-sizing: border-box; } +/* 对话区域 */ #chat-box { flex: 1; overflow-y: auto; + margin-bottom: 10px; padding: 10px; - border-bottom: 1px solid #444; + border-radius: 4px; + background-color: var(--vscode-editor-background); } -.user, -.ai { - align-self: flex-end; - color: #000000; - margin: 7px 3px; - padding: 7px; - border-radius: 10px; - font-size: 14px; +/* 消息样式 */ +.message { + margin-bottom: 15px; + padding: 8px 12px; + border-radius: 4px; + line-height: 1.4; } -.user { - background-color: #717a76; +.message.user { + background-color: var(--vscode-textBlockQuote-background); + align-self: flex-end; + margin-left: 20%; } -.ai { - background-color: #7a7575; +.message.ai { + background-color: var(--vscode-editor-background); + border: 1px solid var(--vscode-editorWidget-border); } -.context-area { - background-color: #3e4451; +/* 代码块样式 */ +pre { + background-color: var(--vscode-textCodeBlock-background); padding: 10px; - border: 1px solid #61afef; border-radius: 4px; - margin: 10px; - display: flex; - align-items: center; - justify-content: space-between; -} - -#add-context-btn { - background-color: transparent; - border: none; - color: #61afef; - cursor: pointer; - font-size: 16px; -} - -.context-options { - display: flex; - align-items: center; + overflow-x: auto; } -.context-options span, -.context-options select, -.context-options label { - margin-right: 10px; -} - -.keybinds { - font-size: 12px; - color: #abb2bf; +code { + font-family: var(--vscode-editor-font-family); + font-size: var(--vscode-editor-font-size); } +/* 表单区域 */ #chat-form { display: flex; - padding: 3px 10px; - background-color: #282c34; - /* border-top: 1px solid #444; */ + gap: 10px; + margin-top: auto; + padding: 10px 0; } #user-input { flex: 1; - padding: 10px; - border: none; - background-color: #3e4451; - color: white; - font-size: 14px; + padding: 8px 12px; + border: 1px solid var(--vscode-dropdown-border); + border-radius: 4px; + background-color: var(--vscode-input-background); + color: var(--vscode-input-foreground); + font-family: var(--vscode-font-family); + font-size: var(--vscode-font-size); +} + +#user-input:focus { outline: none; + border-color: var(--vscode-focusBorder); } #chat-form button { - padding: 10px; - background-color: #61afef; - color: white; + padding: 8px 16px; + background-color: var(--vscode-button-background); + color: var(--vscode-button-foreground); border: none; + border-radius: 4px; cursor: pointer; - margin-left: 10px; + font-family: var(--vscode-font-family); + font-size: var(--vscode-font-size); } +#chat-form button:hover { + background-color: var(--vscode-button-hoverBackground); +} + +/* 加载提示 */ #loading { text-align: center; padding: 10px; - background-color: #3e4451; - color: white; - border: 1px solid #61afef; - border-radius: 4px; - margin: 10px; + color: var(--vscode-descriptionForeground); } -.hidden { +#loading.hidden { display: none; } +/* 上下文容器 */ .context-container { display: flex; align-items: center; - margin: 10px 0; - gap: 10px; /* 元素间的间距 */ - padding: 1px 10px; + margin-bottom: 10px; + gap: 5px; } -.context-tab { +.select-context-btn { + width: 24px; + height: 24px; + border-radius: 50%; + background-color: #007acc; + color: white; + border: none; + cursor: pointer; display: flex; align-items: center; - padding: 6px 10px; - background-color: #e9e9e9; - border-radius: 4px; - font-size: 13px; - min-width: 0; /* 允许收缩 */ - flex: 0 1 auto; /* 不要占据多余空间 */ - color: #000000; - cursor: default; -} - -.context-tab.hidden { - display: none; + justify-content: center; + font-size: 16px; + padding: 0; } -.file-name { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-right: 10px; +.select-context-btn:hover { + background-color: #005a9e; } .context-placeholder { display: flex; align-items: center; - padding: 6px 2px; - color: #666; - font-size: 13px; - min-width: 0; + padding: 4px 8px; + border-radius: 4px; + background-color: var(--vscode-badge-background); + color: var(--vscode-badge-foreground); + font-size: 12px; } -.placeholder-text { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; +.context-tab { + display: flex; + align-items: center; + padding: 4px 8px; + border-radius: 4px; + background-color: var(--vscode-tab-activeBackground); + color: var(--vscode-tab-activeForeground); + font-size: 12px; } -.context-placeholder.hidden { +.context-tab.hidden { display: none; } +.file-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-right: 8px; +} .close-btn { background: none; border: none; - font-size: 18px; + color: var(--vscode-icon-foreground); cursor: pointer; - color: #666; - padding: 0; - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} - -.close-btn:hover { - color: #ff0000; - background-color: rgba(0,0,0,0.1); - border-radius: 50%; -} - -.select-context-btn { - width: 24px; - height: 24px; - padding: 0; - background-color: #007acc; - color: white; - border: none; - border-radius: 50%; font-size: 16px; - font-weight: bold; - cursor: pointer; + width: 16px; + height: 16px; display: flex; align-items: center; justify-content: center; - flex-shrink: 0; -} - -.select-context-btn:hover { - background-color: #005a9e; + padding: 0; } -/* 模态框样式 */ +/* 文件浏览器模态框 */ .modal { position: fixed; top: 0; @@ -217,8 +194,8 @@ body { height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; - justify-content: center; align-items: center; + justify-content: center; z-index: 1000; } @@ -227,10 +204,11 @@ body { } .modal-content { - background-color: #282c34; - border-radius: 8px; - width: 80%; - max-width: 600px; + background-color: var(--vscode-editorWidget-background); + border: 1px solid var(--vscode-editorWidget-border); + border-radius: 4px; + width: 90%; + max-width: 500px; max-height: 80vh; display: flex; flex-direction: column; @@ -240,88 +218,73 @@ body { display: flex; justify-content: space-between; align-items: center; - padding: 15px 20px; - border-bottom: 1px solid #444; + padding: 10px 15px; + border-bottom: 1px solid var(--vscode-editorWidget-border); } .modal-header h3 { margin: 0; - color: white; + font-size: 16px; + font-weight: 600; } .close-modal { - font-size: 24px; cursor: pointer; - color: #aaa; -} - -.close-modal:hover { - color: white; + font-size: 20px; + color: var(--vscode-icon-foreground); } .modal-body { flex: 1; + overflow-y: auto; padding: 15px; - overflow: hidden; - display: flex; - flex-direction: column; } .file-search { - flex: 1; margin-bottom: 15px; } .file-search input { - padding: 8px; - background-color: #3e4451; - border: 1px solid #444; + width: 100%; + padding: 6px 10px; + border: 1px solid var(--vscode-dropdown-border); border-radius: 4px; - color: white; + background-color: var(--vscode-input-background); + color: var(--vscode-input-foreground); + font-family: var(--vscode-font-family); + font-size: var(--vscode-font-size); + box-sizing: border-box; } .file-list { - flex: 1; - overflow-y: auto; - border: 1px solid #444; - border-radius: 4px; - background-color: #3e4451; + display: flex; + flex-direction: column; + gap: 5px; } .file-item { - padding: 10px; - border-bottom: 1px solid #444; - cursor: pointer; display: flex; align-items: center; + padding: 6px 10px; + border-radius: 4px; + cursor: pointer; } .file-item:hover { - background-color: #4e5461; -} - -.file-item:last-child { - border-bottom: none; + background-color: var(--vscode-list-hoverBackground); } .file-icon { - margin-right: 10px; - width: 16px; - text-align: center; -} - -.file-name { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + margin-right: 8px; } .loading { text-align: center; padding: 20px; - color: #abb2bf; + color: var(--vscode-descriptionForeground); } + +/* 差异模态框 */ .diff-modal-content { width: 90%; max-width: 900px; @@ -331,13 +294,28 @@ body { font-family: 'Courier New', monospace; font-size: 14px; line-height: 1.4; - background-color: #1e1e1e; + background-color: var(--vscode-editor-background); padding: 15px; border-radius: 4px; overflow-x: auto; + margin-bottom: 15px; + max-height: 60vh; +} + +.diff-row { + display: flex; +} + +.diff-line-number { + width: 40px; + text-align: right; + padding-right: 10px; + color: var(--vscode-descriptionForeground); + user-select: none; } .diff-line { + flex: 1; white-space: pre; } @@ -352,8 +330,35 @@ body { } .diff-unchanged { - color: #d4d4d4; + color: var(--vscode-editor-foreground); } + +.diff-actions { + display: flex; + justify-content: flex-end; + gap: 10px; + padding: 10px 0; +} + +.diff-action-btn { + padding: 6px 12px; + border: none; + border-radius: 4px; + cursor: pointer; + font-family: var(--vscode-font-family); + font-size: 12px; +} + +.diff-action-btn.accept { + background-color: #007acc; + color: white; +} + +.diff-action-btn.reject { + background-color: #d73a49; + color: white; +} + .diff-button { margin-top: 10px; padding: 5px 10px; @@ -368,3 +373,75 @@ body { .diff-button:hover { background-color: #005a9e; } + +/* 工作区容器 */ +.workspace-container { + margin: 10px 0; + padding: 10px; + border: 1px solid var(--vscode-editorWidget-border); + border-radius: 4px; + background-color: var(--vscode-editor-background); +} + +.workspace-container h3 { + margin: 0 0 10px 0; + font-size: 14px; + font-weight: 600; +} + +.workspace-files { + max-height: 150px; + overflow-y: auto; +} + +.workspace-file-item { + display: flex; + align-items: center; + padding: 6px 10px; + border-radius: 4px; + cursor: pointer; +} + +.workspace-file-item:hover { + background-color: var(--vscode-list-hoverBackground); +} + +.workspace-file-item .file-icon { + margin-right: 8px; +} + +.workspace-file-item .file-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.workspace-file-item .file-status { + font-size: 11px; + padding: 2px 6px; + border-radius: 3px; + margin-left: 8px; +} + +.file-status.status-pending { + background-color: #007acc; + color: white; +} + +.file-status.status-accepted { + background-color: #28a745; + color: white; +} + +.file-status.status-rejected { + background-color: #d73a49; + color: white; +} + +.no-changes { + text-align: center; + padding: 10px; + color: var(--vscode-descriptionForeground); + font-size: 12px; +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index c7a2b7c..224324d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,9 +2,17 @@ import * as vscode from 'vscode'; import * as path from 'path'; import * as diff from 'diff'; +// 定义全局变量来存储面板状态 let panel: vscode.WebviewPanel | undefined; let currentSessionHistory: { role: string; content: string }[] = []; - +// 存储工作区变更的文件,包含状态信息 +let workspaceChanges: { + [key: string]: { + original: string; + modified: string; + status: 'pending' | 'accepted' | 'rejected' + } +} = {}; export function activate(context: vscode.ExtensionContext) { console.log('Extension activated'); @@ -130,8 +138,19 @@ function openWebview( globalState: vscode.Memento, context: vscode.ExtensionContext // 添加 context 参数 ) { - // 每次打开新的 Webview 时清空当前会话历史 - currentSessionHistory = []; + // 如果面板已经存在,直接显示它而不是创建新的 + if (panel) { + panel.reveal(vscode.ViewColumn.Beside); + // 发送当前状态到 WebView + if (panel.webview) { + panel.webview.postMessage({ + command: 'restoreState', + history: currentSessionHistory, + workspaceChanges: workspaceChanges + }); + } + return; + } panel = vscode.window.createWebviewPanel( 'aiChatWebview', @@ -187,6 +206,23 @@ function openWebview( const codeDiff = generateCodeDiff(fileContent, aiMessage); console.log("codeDiff:",codeDiff) + // 如果有代码差异,保存到工作区变更中 + if (codeDiff && fileContent && message.fileContentPath) { + workspaceChanges[message.fileContentPath] = { + original: fileContent, + modified: codeDiff.modifiedCode, + status: 'pending' + }; + + // 通知 WebView 更新工作区文件列表 + if (panel && panel.webview) { + panel.webview.postMessage({ + command: 'updateWorkspaceFiles', + files: workspaceChanges + }); + } + } + // 更新当前会话历史 currentSessionHistory = [...currentSessionHistory, { role: 'assistant', content: aiMessage }]; @@ -262,16 +298,139 @@ function openWebview( vscode.window.showErrorMessage(`无法读取文件: ${error.message}`); } break; + case 'showCodeDiffInEditor': + // 在编辑器中显示代码差异 + try { + const originalCode = message.originalCode || ''; + const modifiedCode = message.modifiedCode || ''; + + // 创建原始代码的虚拟文档 + const originalUri = vscode.Uri.parse('untitled:original-code.txt'); + const originalDoc = await vscode.workspace.openTextDocument(originalUri); + const originalEditor = await vscode.window.showTextDocument(originalDoc, vscode.ViewColumn.One, true); + await originalEditor.edit(edit => { + edit.insert(new vscode.Position(0, 0), originalCode); + }); + + // 创建修改后代码的虚拟文档 + const modifiedUri = vscode.Uri.parse('untitled:modified-code.txt'); + const modifiedDoc = await vscode.workspace.openTextDocument(modifiedUri); + const modifiedEditor = await vscode.window.showTextDocument(modifiedDoc, vscode.ViewColumn.Two, true); + await modifiedEditor.edit(edit => { + edit.insert(new vscode.Position(0, 0), modifiedCode); + }); + + // 使用 VS Code 的 diff 命令显示差异 + await vscode.commands.executeCommand( + 'vscode.diff', + originalUri, + modifiedUri, + 'Code Diff - Original ↔ Modified' + ); + } catch (error) { + vscode.window.showErrorMessage(`显示代码差异失败: ${error.message}`); + } + break; + case 'acceptChanges': + // 接受代码变更 + try { + const { filePath, modifiedContent } = message; + const fileUri = vscode.Uri.file(filePath); + + // 将修改后的内容写入文件 + const encodedContent = new TextEncoder().encode(modifiedContent); + await vscode.workspace.fs.writeFile(fileUri, encodedContent); + + // 更新工作区变更状态 + if (workspaceChanges[filePath]) { + workspaceChanges[filePath].status = 'accepted'; + } + + // 通知 WebView 更新工作区文件列表 + if (panel && panel.webview) { + panel.webview.postMessage({ + command: 'updateWorkspaceFiles', + files: workspaceChanges + }); + } + + vscode.window.showInformationMessage('代码变更已应用'); + } catch (error) { + vscode.window.showErrorMessage(`应用代码变更失败: ${error.message}`); + } + break; + case 'rejectChanges': + // 拒绝代码变更 + try { + const { filePath } = message; + + // 更新工作区变更状态 + if (workspaceChanges[filePath]) { + workspaceChanges[filePath].status = 'rejected'; + } + + // 通知 WebView 更新工作区文件列表 + if (panel && panel.webview) { + panel.webview.postMessage({ + command: 'updateWorkspaceFiles', + files: workspaceChanges + }); + } + + vscode.window.showInformationMessage('代码变更已拒绝'); + } catch (error) { + vscode.window.showErrorMessage(`拒绝代码变更失败: ${error.message}`); + } + break; + case 'openWorkspaceFile': + // 打开工作区文件 + try { + const { filePath } = message; + const fileUri = vscode.Uri.file(filePath); + + // 检查文件是否已经打开 + let fileAlreadyOpen = false; + const openedTextDocuments = vscode.workspace.textDocuments; + + for (const doc of openedTextDocuments) { + if (doc.uri.fsPath === fileUri.fsPath) { + fileAlreadyOpen = true; + // 激活已打开的文件 + await vscode.window.showTextDocument(doc, { + viewColumn: vscode.ViewColumn.One // 切换到已打开的文件 + }); + break; + } + } + + // 如果文件未打开,则在新窗口打开 + if (!fileAlreadyOpen) { + await vscode.window.showTextDocument(fileUri, { + viewColumn: vscode.ViewColumn.One // 在新窗口打开文件 + }); + } + } catch (error) { + vscode.window.showErrorMessage(`打开文件失败: ${error.message}`); + } + break; + case 'getCodeChanges': + // 获取代码变更 + if (panel && panel.webview) { + panel.webview.postMessage({ + command: 'codeChanges', + changes: workspaceChanges + }); + } + break; } }, undefined, context.subscriptions ); - // 添加 dispose 监听器 + // // 添加 dispose 监听器 panel.onDidDispose(() => { - // 面板关闭时清空当前会话历史 - currentSessionHistory = []; + // 不再清空会话历史,让用户在重新打开面板时保留上下文 panel = undefined; }, null, context.subscriptions); } @@ -327,7 +486,7 @@ async function callQwenAPI( } } -function generateCodeDiff(originalCode: string, aiResponse: string): { added: string[]; removed: string[] } | null { +function generateCodeDiff(originalCode: string, aiResponse: string): { added: string[]; removed: string[]; modifiedCode: string } | null { console.log("模型返回值:",aiResponse) // 提取代码块中的代码 const codeBlockRegex = /```(?:([a-zA-Z0-9]+))?\s*[\n\r]([\s\S]*?)\s*```/g; @@ -370,10 +529,12 @@ function generateCodeDiff(originalCode: string, aiResponse: string): { added: st } }); - // 只有当确实有差异时才返回 - if (added.length > 0 || removed.length > 0) { - return { added, removed }; - } + // 返回差异结果,无论是否有差异 + return { + added, + removed, + modifiedCode: normalizedModified + }; } return null; @@ -409,6 +570,14 @@ function getWebviewContent(styleUri: vscode.Uri, scriptUri: vscode.Uri,highlight + + +