"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/extension.ts
var extension_exports = {};
__export(extension_exports, {
activate: () => activate,
deactivate: () => deactivate
});
module.exports = __toCommonJS(extension_exports);
var vscode2 = __toESM(require("vscode"));
var fs = __toESM(require("fs"));
var path = __toESM(require("path"));
var os = __toESM(require("os"));
// src/webviewProvider.ts
var vscode = __toESM(require("vscode"));
var DiffExplanationPanel = class _DiffExplanationPanel {
static currentPanel;
_panel;
_extensionUri;
_disposables = [];
_currentEditor = "cursor";
static createOrShow(extensionUri, data) {
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : void 0;
if (_DiffExplanationPanel.currentPanel) {
_DiffExplanationPanel.currentPanel._panel.reveal(column);
_DiffExplanationPanel.currentPanel._update(data);
return;
}
const panel = vscode.window.createWebviewPanel(
"explainChanges",
data.title,
column || vscode.ViewColumn.One,
{
enableScripts: true,
retainContextWhenHidden: true,
localResourceRoots: [extensionUri]
}
);
_DiffExplanationPanel.currentPanel = new _DiffExplanationPanel(
panel,
extensionUri,
data
);
}
constructor(panel, extensionUri, data) {
this._panel = panel;
this._extensionUri = extensionUri;
this._update(data);
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
this._panel.webview.onDidReceiveMessage(
(message) => this._handleMessage(message),
null,
this._disposables
);
}
_handleMessage(message) {
console.log("Received message from webview:", message.command);
switch (message.command) {
case "openFile":
const filePath = message.file;
const line = message.line;
this._openFileInEditor(filePath, line);
break;
case "executeAction":
const prompt = message.prompt;
console.log("executeAction received, prompt length:", prompt?.length);
this._executeAction(prompt);
break;
}
}
async _openFileInEditor(filePath, line) {
try {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders)
return;
const fullPath = vscode.Uri.joinPath(workspaceFolders[0].uri, filePath);
const doc = await vscode.workspace.openTextDocument(fullPath);
const editor = await vscode.window.showTextDocument(doc);
if (line && line > 0) {
const position = new vscode.Position(line - 1, 0);
editor.selection = new vscode.Selection(position, position);
editor.revealRange(new vscode.Range(position, position));
}
} catch (err) {
vscode.window.showErrorMessage(`Could not open file: ${filePath}`);
}
}
async _executeAction(prompt) {
const deepLink = `cursor://anysphere.cursor-deeplink/prompt?text=${encodeURIComponent(prompt)}`;
try {
await vscode.env.openExternal(vscode.Uri.parse(deepLink));
} catch (err) {
await vscode.env.clipboard.writeText(prompt);
vscode.window.showInformationMessage(
"Prompt copied to clipboard. Press Cmd+L and paste to start a chat."
);
}
}
_update(data) {
this._panel.title = data.title;
this._currentEditor = data.editor || "cursor";
this._panel.webview.html = this._getHtmlContent(data);
}
_getHtmlContent(data) {
const { title, summary, diff, annotations, editor } = data;
const escapedDiff = this._escapeForJs(diff);
const annotationsJson = JSON.stringify(annotations);
const diffStyle = "side-by-side";
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src https://fonts.gstatic.com; script-src 'unsafe-inline' https://cdn.jsdelivr.net; img-src data:;">
<title>${this._escapeHtml(title)}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css">
<script src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Inter', var(--vscode-font-family), sans-serif;
background: var(--vscode-editor-background, #0d1117);
color: var(--vscode-editor-foreground, #e6edf3);
line-height: 1.5;
}
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--vscode-scrollbarSlider-background, #484f58); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--vscode-scrollbarSlider-hoverBackground, #6e7681); }
.header {
position: sticky;
top: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 24px;
background: var(--vscode-editor-background, #0d1117);
border-bottom: 1px solid var(--vscode-panel-border, #30363d);
}
.header-title {
font-size: 16px;
font-weight: 600;
color: var(--vscode-editor-foreground, #e6edf3);
}
.header-actions {
display: flex;
align-items: center;
gap: 12px;
}
.view-toggle {
display: flex;
background: var(--vscode-input-background, #21262d);
border: 1px solid var(--vscode-input-border, rgba(240, 246, 252, 0.1));
border-radius: 6px;
overflow: hidden;
}
.view-toggle-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
font-size: 12px;
font-weight: 500;
color: var(--vscode-descriptionForeground, #8b949e);
background: transparent;
border: none;
cursor: pointer;
transition: all 0.15s ease;
}
.view-toggle-btn:hover {
color: var(--vscode-editor-foreground, #e6edf3);
background: rgba(255,255,255,0.05);
}
.view-toggle-btn.active {
color: var(--vscode-editor-foreground, #e6edf3);
background: var(--vscode-button-secondaryBackground, #30363d);
}
.view-toggle-btn svg {
width: 14px;
height: 14px;
}
.summary {
max-width: 1400px;
margin: 24px auto 0;
padding: 0 24px;
}
.summary-box {
padding: 16px 20px;
background: rgba(56, 139, 253, 0.1);
border: 1px solid rgba(56, 139, 253, 0.2);
border-radius: 8px;
}
.summary-text {
font-size: 14px;
color: var(--vscode-editor-foreground, #e6edf3);
line-height: 1.5;
}
.content {
max-width: 1400px;
margin: 24px auto;
padding: 0 24px;
}
#diff-container {
display: flex;
flex-direction: column;
gap: 20px;
}
.d2h-file-wrapper {
border-radius: 6px;
overflow: hidden;
margin-bottom: 0 !important;
}
.d2h-file-header {
padding: 10px 16px;
background: var(--vscode-editor-background, #161b22) !important;
}
.d2h-file-name {
font-family: 'JetBrains Mono', ui-monospace, monospace;
font-size: 13px;
font-weight: 600;
}
.d2h-file-link {
color: var(--vscode-textLink-foreground, #58a6ff);
text-decoration: none;
cursor: pointer;
}
.d2h-file-link:hover {
text-decoration: underline;
}
.d2h-diff-table {
font-family: 'JetBrains Mono', ui-monospace, monospace;
font-size: 12px;
width: 100% !important;
}
/* Make line number columns transparent */
.d2h-code-linenumber,
.d2h-code-side-linenumber {
background: transparent !important;
}
.ai-annotation {
display: flex;
gap: 16px;
margin: 8px 0 16px;
padding: 16px 20px;
background: var(--vscode-editor-inactiveSelectionBackground, #1c2128);
border: 1px solid var(--vscode-panel-border, #30363d);
border-radius: 8px;
position: relative;
overflow: hidden;
}
.ai-annotation::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: linear-gradient(180deg, #8b5cf6 0%, #6366f1 100%);
}
.ai-annotation-content {
flex: 1;
min-width: 0;
}
.ai-annotation-text {
font-size: 13px;
color: var(--vscode-editor-foreground, #e6edf3);
line-height: 1.6;
}
.ai-annotation-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.action-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
font-size: 12px;
font-weight: 500;
color: var(--vscode-editor-foreground, #e6edf3);
background: var(--vscode-button-secondaryBackground, #21262d);
border: 1px solid var(--vscode-input-border, rgba(240, 246, 252, 0.1));
border-radius: 6px;
cursor: pointer;
transition: all 0.15s ease;
}
.action-btn:hover {
background: var(--vscode-button-secondaryHoverBackground, #30363d);
border-color: var(--vscode-focusBorder, #58a6ff);
}
.action-btn svg {
width: 14px;
height: 14px;
}
.footer {
max-width: 1400px;
margin: 32px auto 16px;
padding: 16px 24px;
text-align: center;
border-top: 1px solid var(--vscode-panel-border, rgba(48, 54, 61, 0.5));
}
.footer-text {
font-size: 11px;
color: var(--vscode-descriptionForeground, #484f58);
font-family: 'JetBrains Mono', monospace;
}
</style>
</head>
<body>
<header class="header">
<h1 class="header-title">${this._escapeHtml(title)}</h1>
<div class="header-actions">
<div class="view-toggle">
<button class="view-toggle-btn${diffStyle === "line-by-line" ? " active" : ""}" data-view="line-by-line">
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M4 6h16M4 12h16M4 18h16"/>
</svg>
<span>Unified</span>
</button>
<button class="view-toggle-btn${diffStyle === "side-by-side" ? " active" : ""}" data-view="side-by-side">
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M9 4v16M4 4h16v16H4z"/>
</svg>
<span>Split</span>
</button>
</div>
</div>
</header>
${summary ? `
<div class="summary">
<div class="summary-box">
<p class="summary-text">${this._escapeHtml(summary)}</p>
</div>
</div>
` : ""}
<div class="content">
<div id="diff-container" class="d2h-dark-color-scheme"></div>
</div>
<footer class="footer">
<p class="footer-text">Explain Changes</p>
</footer>
<script>
const vscode = acquireVsCodeApi();
const diffString = \`${escapedDiff}\`;
const annotations = ${annotationsJson};
let currentView = '${diffStyle}';
function splitDiffByFile(diff) {
const files = [];
const parts = diff.split(/(?=diff --git)/);
for (const part of parts) {
if (part.trim()) files.push(part);
}
return files;
}
function isNewFile(fileDiff) {
return fileDiff.includes('--- /dev/null') ||
(fileDiff.includes('new file mode') && !fileDiff.includes('deleted file mode'));
}
function isDeletedFile(fileDiff) {
return fileDiff.includes('+++ /dev/null') || fileDiff.includes('deleted file mode');
}
function renderDiff(outputFormat) {
const targetElement = document.getElementById('diff-container');
targetElement.innerHTML = '';
if (!diffString || diffString.trim() === '') {
targetElement.innerHTML = '<div style="padding: 40px; text-align: center; color: var(--vscode-descriptionForeground);">No diff content</div>';
return;
}
const fileDiffs = splitDiffByFile(diffString);
fileDiffs.forEach((fileDiff) => {
const fileContainer = document.createElement('div');
fileContainer.className = 'file-diff-section';
targetElement.appendChild(fileContainer);
const isNew = isNewFile(fileDiff);
const isDeleted = isDeletedFile(fileDiff);
const fileOutputFormat = (isNew || isDeleted) ? 'line-by-line' : outputFormat;
const configuration = {
drawFileList: false,
fileListToggle: false,
fileContentToggle: false,
matching: 'lines',
outputFormat: fileOutputFormat,
synchronisedScroll: true,
highlight: true,
renderNothingWhenEmpty: false,
};
try {
const diff2htmlUi = new Diff2HtmlUI(fileContainer, fileDiff, configuration);
diff2htmlUi.draw();
diff2htmlUi.highlightCode();
} catch (err) {
fileContainer.innerHTML = '<div style="padding: 20px; color: var(--vscode-errorForeground);">Error rendering diff</div>';
}
});
setTimeout(enhanceFileHeaders, 150);
setTimeout(insertAnnotations, 200);
}
renderDiff(currentView);
document.querySelectorAll('.view-toggle-btn').forEach(btn => {
btn.addEventListener('click', () => {
const view = btn.dataset.view;
if (view === currentView) return;
currentView = view;
document.querySelectorAll('.view-toggle-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
renderDiff(view);
});
});
const CURSOR_LOGO = '<svg fill="none" height="16" width="16" viewBox="0 0 22 22"><g clip-path="url(#a)" fill="currentColor"><path d="M19.162 5.452 10.698.565a.88.88 0 0 0-.879 0L1.356 5.452a.74.74 0 0 0-.37.64v9.853a.74.74 0 0 0 .37.64l8.464 4.887a.879.879 0 0 0 .879 0l8.464-4.886a.74.74 0 0 0 .37-.64V6.091a.74.74 0 0 0-.37-.64Zm-.531 1.035L10.46 20.639c-.055.095-.201.056-.201-.055v-9.266a.52.52 0 0 0-.26-.45L1.975 6.237c-.096-.056-.057-.202.054-.202h16.34c.233 0 .378.252.262.453Z"/></g></svg>';
const VSCODE_LOGO = '<svg fill="none" height="16" width="16" viewBox="0 0 24 24"><path fill="currentColor" d="M17.583 3.104l-5.477 4.984-5.45-4.239-2.656 1.27v13.762l2.656 1.27 5.45-4.239 5.477 4.984L21 18.986V5.014l-3.417-1.91zM5.5 16.5v-9l3.5 4.5-3.5 4.5zm7.5-4.5l-5 4.5V7.5l5 4.5zm5.5 4.5l-3.5-4.5 3.5-4.5v9z"/></svg>';
const currentEditor = '${editor || "cursor"}';
const EDITOR_LOGO = currentEditor === 'cursor' ? CURSOR_LOGO : VSCODE_LOGO;
function renderActions(actions) {
if (!actions || actions.length === 0) return '';
return actions.map((action, idx) => \`
<button class="action-btn" data-action-idx="\${idx}" data-prompt="\${btoa(encodeURIComponent(action.prompt))}">
\${EDITOR_LOGO}
<span>\${escapeHtml(action.label)}</span>
</button>
\`).join('');
}
function insertAnnotations() {
annotations.forEach(annotation => {
const fileHeaders = document.querySelectorAll('.d2h-file-header');
fileHeaders.forEach(header => {
const fileName = header.querySelector('.d2h-file-name');
if (!fileName) return;
const headerFileName = fileName.textContent.trim();
if (!headerFileName.includes(annotation.file) && !annotation.file.includes(headerFileName)) return;
const fileWrapper = header.closest('.d2h-file-wrapper');
if (!fileWrapper) return;
const diffBodies = fileWrapper.querySelectorAll('.d2h-diff-tbody');
if (diffBodies.length === 0) return;
const isSideBySide = diffBodies.length > 1;
let targetRowIndex = -1;
if (annotation.line) {
const searchBody = diffBodies[diffBodies.length - 1];
const lineNumbers = searchBody.querySelectorAll('.d2h-code-linenumber, .d2h-code-side-linenumber');
lineNumbers.forEach(ln => {
const lineNum = parseInt(ln.textContent.trim(), 10);
if (lineNum === annotation.line) {
const row = ln.closest('tr');
if (row && row.parentElement) {
targetRowIndex = Array.from(row.parentElement.children).indexOf(row);
}
}
});
}
if (targetRowIndex === -1) {
targetRowIndex = diffBodies[diffBodies.length - 1].children.length - 1;
}
diffBodies.forEach((diffBody, index) => {
const rows = diffBody.children;
let targetRow = rows[targetRowIndex];
if (!targetRow && rows.length > 0) {
targetRow = rows[rows.length - 1];
}
if (targetRow) {
const firstRow = diffBody.querySelector('tr');
const colCount = firstRow ? firstRow.querySelectorAll('td, th').length : (isSideBySide ? 2 : 3);
const showContent = !isSideBySide || index === 1;
const actionsHtml = annotation.actions ? \`<div class="ai-annotation-actions">\${renderActions(annotation.actions)}</div>\` : '';
const annotationRow = document.createElement('tr');
annotationRow.innerHTML = \`
<td colspan="\${colCount}" style="padding: 0; width: 100%;">
<div class="ai-annotation" \${!showContent ? 'style="visibility: hidden;"' : ''}>
<div class="ai-annotation-content">
<p class="ai-annotation-text">\${escapeHtml(annotation.explanation)}</p>
\${actionsHtml}
</div>
</div>
</td>
\`;
targetRow.insertAdjacentElement('afterend', annotationRow);
}
});
});
});
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function escapeForAttr(text) {
return text.replace(/'/g, "\\\\'").replace(/\\n/g, "\\\\n");
}
function enhanceFileHeaders() {
document.querySelectorAll('.d2h-file-wrapper').forEach(wrapper => {
const fileNameEl = wrapper.querySelector('.d2h-file-name');
if (!fileNameEl || fileNameEl.dataset.enhanced) return;
fileNameEl.dataset.enhanced = 'true';
const filePath = fileNameEl.textContent.trim();
let startLine = 1;
const infoEl = wrapper.querySelector('td.d2h-info');
if (infoEl) {
const match = (infoEl.textContent || '').match(/[+](\\d+)/);
if (match) startLine = parseInt(match[1], 10);
}
const link = document.createElement('a');
link.href = '#';
link.className = 'd2h-file-link';
link.textContent = filePath;
link.title = 'Open in editor';
link.onclick = (e) => {
e.preventDefault();
vscode.postMessage({ command: 'openFile', file: filePath, line: startLine });
};
fileNameEl.textContent = '';
fileNameEl.appendChild(link);
});
}
function executeAction(prompt) {
console.log('executeAction called with prompt:', prompt.substring(0, 100));
vscode.postMessage({ command: 'executeAction', prompt: prompt });
}
// Event delegation for action buttons
document.addEventListener('click', (e) => {
console.log('Click event:', e.target);
const btn = e.target.closest('.action-btn');
console.log('Found button:', btn);
if (btn) {
console.log('Button dataset:', btn.dataset);
if (btn.dataset.prompt) {
try {
const decoded = atob(btn.dataset.prompt);
console.log('Decoded base64:', decoded.substring(0, 50));
const prompt = decodeURIComponent(decoded);
console.log('Final prompt:', prompt.substring(0, 100));
executeAction(prompt);
} catch (err) {
console.error('Error decoding prompt:', err);
}
} else {
console.log('No data-prompt on button');
}
}
});
</script>
</body>
</html>`;
}
_escapeHtml(text) {
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
}
_escapeForJs(text) {
return text.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$").replace(/'/g, "\\'");
}
dispose() {
_DiffExplanationPanel.currentPanel = void 0;
this._panel.dispose();
while (this._disposables.length) {
const x = this._disposables.pop();
if (x) {
x.dispose();
}
}
}
};
// src/extension.ts
var WATCH_DIR = path.join(os.homedir(), ".explain-changes");
var WATCH_FILE = path.join(WATCH_DIR, "pending.json");
var MCP_SERVER_NAME = "explain-changes";
var MCP_COMMAND = "npx";
var MCP_ARGS = ["-y", "explain-changes-mcp"];
var fileWatcher = null;
var lastTimestamp = 0;
function getEditorInfo() {
const appName = vscode2.env.appName.toLowerCase();
if (appName.includes("cursor")) {
return {
name: "Cursor",
scheme: "cursor",
mcpConfigPath: path.join(os.homedir(), ".cursor", "mcp.json")
};
}
if (appName.includes("windsurf")) {
return {
name: "Windsurf",
scheme: "windsurf",
mcpConfigPath: path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json")
};
}
return {
name: "VS Code",
scheme: "vscode",
mcpConfigPath: null
};
}
async function ensureMcpServerInstalled(mcpConfigPath) {
try {
let config = { mcpServers: {} };
if (fs.existsSync(mcpConfigPath)) {
try {
const content = fs.readFileSync(mcpConfigPath, "utf-8");
const parsed = JSON.parse(content);
if (parsed && typeof parsed === "object" && parsed.mcpServers) {
config = parsed;
}
} catch {
}
}
const existingServer = config.mcpServers[MCP_SERVER_NAME];
if (existingServer && existingServer.command === MCP_COMMAND && JSON.stringify(existingServer.args) === JSON.stringify(MCP_ARGS)) {
return false;
}
config.mcpServers[MCP_SERVER_NAME] = {
command: MCP_COMMAND,
args: MCP_ARGS
};
fs.mkdirSync(path.dirname(mcpConfigPath), { recursive: true });
fs.writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2), "utf-8");
return true;
} catch (err) {
console.error("Failed to configure MCP server:", err);
return false;
}
}
async function activate(context) {
const editorInfo = getEditorInfo();
console.log(`Explain Changes extension activated in ${editorInfo.name}`);
if (!fs.existsSync(WATCH_DIR)) {
fs.mkdirSync(WATCH_DIR, { recursive: true });
}
if (editorInfo.mcpConfigPath) {
const wasInstalled = await ensureMcpServerInstalled(editorInfo.mcpConfigPath);
if (wasInstalled) {
vscode2.window.showInformationMessage(
`Explain Changes MCP server has been configured. Restart ${editorInfo.name} to enable it.`
);
}
}
const showPanelCommand = vscode2.commands.registerCommand(
"explainChanges.showPanel",
() => {
const data = readPendingFile();
if (data) {
DiffExplanationPanel.createOrShow(context.extensionUri, data);
} else {
vscode2.window.showInformationMessage(
"No pending diff explanation found."
);
}
}
);
context.subscriptions.push(showPanelCommand);
const uriHandler = vscode2.window.registerUriHandler({
handleUri(uri) {
if (uri.path === "/show" || uri.path === "") {
const data = readPendingFile();
if (data) {
DiffExplanationPanel.createOrShow(context.extensionUri, data);
} else {
vscode2.window.showInformationMessage(
"No pending diff explanation found."
);
}
}
}
});
context.subscriptions.push(uriHandler);
startFileWatcher(context);
const existingData = readPendingFile();
if (existingData && existingData.timestamp > lastTimestamp && isWorkspaceMatch(existingData)) {
lastTimestamp = existingData.timestamp;
DiffExplanationPanel.createOrShow(context.extensionUri, existingData);
}
}
function startFileWatcher(context) {
try {
fileWatcher = fs.watch(WATCH_DIR, (eventType, filename) => {
if (filename === "pending.json") {
handleFileChange(context);
}
});
context.subscriptions.push({
dispose: () => {
if (fileWatcher) {
fileWatcher.close();
fileWatcher = null;
}
}
});
} catch (err) {
console.error("Failed to start file watcher:", err);
}
}
function handleFileChange(context) {
setTimeout(() => {
const data = readPendingFile();
if (data && data.timestamp > lastTimestamp && isWorkspaceMatch(data)) {
lastTimestamp = data.timestamp;
DiffExplanationPanel.createOrShow(context.extensionUri, data);
vscode2.window.showInformationMessage("New diff explanation received!");
}
}, 100);
}
function readPendingFile() {
try {
if (!fs.existsSync(WATCH_FILE)) {
return null;
}
const content = fs.readFileSync(WATCH_FILE, "utf-8");
return JSON.parse(content);
} catch (err) {
console.error("Failed to read pending file:", err);
return null;
}
}
function isWorkspaceMatch(data) {
if (!data.workspacePath) {
return true;
}
const workspaceFolders = vscode2.workspace.workspaceFolders;
if (!workspaceFolders || workspaceFolders.length === 0) {
return false;
}
const normalizedDataPath = data.workspacePath.replace(/\/$/, "").toLowerCase();
return workspaceFolders.some((folder) => {
const normalizedFolderPath = folder.uri.fsPath.replace(/\/$/, "").toLowerCase();
return normalizedFolderPath === normalizedDataPath;
});
}
function deactivate() {
if (fileWatcher) {
fileWatcher.close();
fileWatcher = null;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
activate,
deactivate
});