feat: implement DnD roadmap Step 3 - editor path injection

This commit is contained in:
2026-04-30 15:03:34 +09:00
parent 11161b9f92
commit 79640f8943
3 changed files with 33 additions and 58 deletions
+32 -40
View File
@@ -157,6 +157,23 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
case 'proactiveTrigger':
await this._handleProactiveSuggestion(data.context);
break;
case 'filesDropped':
// ⭐ Roadmap Step 3: 핵심 데이터 변환 및 에디터 주입
if (data.paths && data.paths.length > 0) {
const editor = vscode.window.activeTextEditor;
if (editor) {
// 1. URI 변환 (크로스 플랫폼 호환성 보장)
const uris = data.paths.map((p: string) => vscode.Uri.file(p));
const formattedText = uris.map(u => u.fsPath).join('\n');
// 2. 에디터 API를 통한 주입
await editor.edit(editBuilder => {
editBuilder.insert(editor.selection.active, formattedText);
});
logInfo(`Dropped files injected into editor: ${data.paths.length} files`);
}
}
break;
case 'exportResponse':
const workspacePath = vscode.workspace.workspaceFolders?.[0].uri.fsPath || '';
const defaultPath = path.join(workspacePath, 'g1_response.md');
@@ -1049,8 +1066,6 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
position: relative; /* Toast 위치 앙커 */
}
.input-box:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
.input-box.is-dragging { border-color: var(--accent); background: rgba(88, 166, 255, 0.05); border-style: dashed; }
textarea {
width: 100%; background: transparent; border: none; color: var(--text-bright);
@@ -1356,9 +1371,8 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
<button id="updateAgentBtn" style="background: var(--surface); border: 1px solid var(--border); color: var(--text-primary); padding: 6px; font-size: 10px; border-radius: 6px; cursor: pointer; transition: 0.2s;">Update Agent Skill</button>
</div>
<div class="input-box" id="inputBox">
<div class="input-box">
<div id="attachPreview" class="attachment-preview"></div>
<div id="pathDisplay" style="font-size: 10px; color: var(--text-dim); padding: 4px 8px; border-bottom: 1px solid var(--border); display: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"></div>
<textarea id="input" rows="1" placeholder="Type your request..."></textarea>
<div class="input-footer">
<div class="footer-left">
@@ -1388,8 +1402,6 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
const sendBtn = document.getElementById('sendBtn');
const stopBtn = document.getElementById('stopBtn');
const cancelBtn = document.getElementById('cancelBtn');
const inputBox = document.getElementById('inputBox');
const pathDisplay = document.getElementById('pathDisplay');
const toastNotif = document.getElementById('toastNotif');
const thinkingBar = document.getElementById('thinkingBar');
const statusLabel = document.getElementById('statusLabel');
@@ -1731,24 +1743,13 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
function renderAttachments() {
attachPreview.innerHTML = '';
const paths = [];
if (pendingFiles.length > 0) {
attachPreview.classList.add('visible');
pathDisplay.style.display = 'block';
pendingFiles.forEach((f, idx) => {
const chip = document.createElement('div');
chip.className = 'file-chip';
const sizeStr = f.size ? \`(\${f.size} KB)\` : '';
chip.innerHTML = \`<span>📎</span> \${f.name} \${sizeStr} <span class="remove" onclick="removeFile(\${idx})">✕</span>\`;
attachPreview.appendChild(chip);
paths.push(\`\${f.name} \${sizeStr}\`);
});
pathDisplay.textContent = '📂 ' + paths.join(' | ');
} else {
attachPreview.classList.remove('visible');
pathDisplay.style.display = 'none';
}
if (pendingFiles.length === 0) { attachPreview.classList.remove('visible'); return; }
attachPreview.classList.add('visible');
pendingFiles.forEach((f, i) => {
const chip = document.createElement('div'); chip.className = 'file-chip';
chip.innerHTML = \`<span>📎</span> \${f.name} <span class="remove" onclick="removeFile(\${i})">✕</span>\`;
attachPreview.appendChild(chip);
});
}
window.removeFile = (i) => {
pendingFiles.splice(i, 1);
@@ -1761,15 +1762,9 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
Array.from(files).forEach(file => {
const reader = new FileReader();
const sizeKB = (file.size / 1024).toFixed(1);
reader.onload = () => {
const base64 = reader.result.split(',')[1];
pendingFiles.push({
name: file.name,
type: file.type,
size: sizeKB,
data: base64
});
pendingFiles.push({ name: file.name, type: file.type, data: base64 });
renderAttachments();
setDraftActive(true);
};
@@ -1802,18 +1797,9 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
['dragleave', 'drop'].forEach(eventName => {
document.body.addEventListener(eventName, () => {
document.body.classList.remove('drag-over');
inputBox.classList.remove('is-dragging');
}, false);
});
// 입력 박스 자체의 시각적 피드백
inputBox.addEventListener('dragover', () => {
inputBox.classList.add('is-dragging');
}, false);
inputBox.addEventListener('dragleave', () => {
inputBox.classList.remove('is-dragging');
}, false);
document.body.addEventListener('drop', e => {
const dt = e.dataTransfer;
const files = dt.files;
@@ -1822,6 +1808,12 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
if (files && files.length > 0) {
fileInput.files = files; // Input의 files 속성 업데이트
console.log(\`✅ [DnD] Input 상태 동기화 성공: \${files[0].name}\${files.length - 1}\`);
// ⭐ Roadmap Step 2: 브릿지 전송 (경로 데이터를 확장 프로그램으로 전달)
const paths = Array.from(files).map(f => (f as any).path).filter(p => !!p);
if (paths.length > 0) {
vscode.postMessage({ type: 'filesDropped', paths: paths });
}
}
processFiles(files);