diff --git a/CHANGELOG.md b/CHANGELOG.md index 651c17d..76184ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ All notable changes to the "Claude Code" extension will be documented in this fi Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. +## [0.1.6] - 2025-01-09 + +### Added +- **Official Claude Code Extension Integration**: NEW feature to detect and use the official Claude Code extension (anthropic.claude-code) when available + - Automatic detection and smart fallback from official extension to terminal approach + - Zero breaking changes - existing terminal functionality preserved as fallback + - Seamless user experience with no configuration required +- **Enhanced Terminal Connection Strategy**: Intelligent terminal management and connection + - Smart terminal creation and disposal when official extension is detected + - Improved connection reliability with better error handling + - Single terminal approach prevents duplicate terminals + +### Improved +- **Launch Strategy**: Unified launch logic for both auto-start and manual launch scenarios +- **Error Handling**: Enhanced error handling mechanisms throughout the extension +- **Performance**: Better resource management and terminal lifecycle handling + +### Technical +- Added test infrastructure improvements (test dependencies updated) +- Code quality improvements with enhanced linting and compilation + ## [0.1.3] - 2025-05-31 ### Added diff --git a/media/input.js b/media/input.js index 4e61b5a..fd5fc65 100644 --- a/media/input.js +++ b/media/input.js @@ -1002,6 +1002,12 @@ setTimeout(() => { messageInputElement.focus(); }, 50); + + // Also try to scroll into view in case it's not visible + setTimeout(() => { + messageInputElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); + messageInputElement.focus(); + }, 100); } break; diff --git a/package-lock.json b/package-lock.json index f7a4804..b099e29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "claude-code-extension", - "version": "0.1.3", + "version": "0.1.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claude-code-extension", - "version": "0.1.3", + "version": "0.1.5", "license": "MIT", "devDependencies": { "@types/node": "^22.15.18", "@types/vscode": "^1.90.0", "@typescript-eslint/eslint-plugin": "^6.9.0", "@typescript-eslint/parser": "^6.9.0", + "@vscode/test-electron": "^2.5.2", "@vscode/vsce": "^2.24.0", "css-loader": "^6.8.1", "eslint": "^8.52.0", @@ -1862,6 +1863,206 @@ "dev": true, "license": "ISC" }, + "node_modules/@vscode/test-electron": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz", + "integrity": "sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "jszip": "^3.10.1", + "ora": "^8.1.0", + "semver": "^7.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@vscode/test-electron/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@vscode/test-electron/node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vscode/test-electron/node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@vscode/vsce": { "version": "2.32.0", "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.32.0.tgz", @@ -6928,6 +7129,13 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true, + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -7767,6 +7975,52 @@ "npm": ">=6" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/just-diff": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz", @@ -7890,6 +8144,16 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -9973,6 +10237,13 @@ "integrity": "sha512-8EKVBxCRSvLnsX1p2LlSFSH3c2/wuhY9/BXXWu8boL78FbVKqn2L5SpURt1x5iw6Gq8PTqJ7MdPoe5nCtX3I+g==", "dev": true }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -11618,6 +11889,13 @@ "randombytes": "^2.1.0" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", diff --git a/package.json b/package.json index d38b7d4..f21f04f 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "claude-code-extension", "displayName": "Claude Code Assistant for VSCode", "description": "Unofficial integration of Anthropic's Claude Code AI assistant into VSCode", - "version": "0.1.5", + "version": "0.1.6", "publisher": "codeflow-studio", "icon": "resources/claude-icon.png", "repository": { @@ -57,6 +57,26 @@ { "command": "claude-code-extension.toggleMode", "title": "Claude Code: Toggle Mode (Shift+Tab)" + }, + { + "command": "claude-code-extension.focusInput", + "title": "Focus on Claude Code Input" + }, + { + "command": "claude-code-extension.explainFile", + "title": "Explain with Claude Code" + }, + { + "command": "claude-code-extension.explainFolder", + "title": "Explain Folder with Claude Code" + }, + { + "command": "claude-code-extension.explainSelection", + "title": "Explain Selection with Claude Code" + }, + { + "command": "claude-code-extension.explainCurrentFile", + "title": "Explain File with Claude Code" } ], "viewsContainers": { @@ -95,7 +115,29 @@ { "command": "claude-code-extension.addSelectionToInput", "when": "editorHasSelection", - "group": "navigation" + "group": "claude@1" + }, + { + "command": "claude-code-extension.explainSelection", + "when": "editorHasSelection", + "group": "claude@2" + }, + { + "command": "claude-code-extension.explainCurrentFile", + "when": "!editorHasSelection", + "group": "claude@1" + } + ], + "explorer/context": [ + { + "command": "claude-code-extension.explainFile", + "when": "!explorerResourceIsFolder", + "group": "claude@1" + }, + { + "command": "claude-code-extension.explainFolder", + "when": "explorerResourceIsFolder", + "group": "claude@1" } ] }, @@ -109,7 +151,11 @@ }, "claude-code-extension.autoStartCommand": { "type": "string", - "enum": ["claude", "claude -c", "claude -r"], + "enum": [ + "claude", + "claude -c", + "claude -r" + ], "default": "claude", "description": "Command to use when auto-starting Claude Code" } @@ -131,6 +177,7 @@ "@types/vscode": "^1.90.0", "@typescript-eslint/eslint-plugin": "^6.9.0", "@typescript-eslint/parser": "^6.9.0", + "@vscode/test-electron": "^2.5.2", "@vscode/vsce": "^2.24.0", "css-loader": "^6.8.1", "eslint": "^8.52.0", diff --git a/src/extension.ts b/src/extension.ts index de03b64..578b51a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -441,6 +441,255 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(toggleModeCommand); + // Register command to focus Claude Code input + const focusInputCommand = vscode.commands.registerCommand('claude-code-extension.focusInput', async () => { + // Check if provider is initialized + if (!claudeTerminalInputProvider) { + vscode.window.showErrorMessage('Claude terminal input provider not initialized'); + return; + } + + // Focus the input field + await claudeTerminalInputProvider.focusInput(); + }); + + context.subscriptions.push(focusInputCommand); + + // Register command to explain file with Claude Code + const explainFileCommand = vscode.commands.registerCommand('claude-code-extension.explainFile', async (uri: vscode.Uri) => { + if (!claudeTerminalInputProvider) { + vscode.window.showErrorMessage('Claude terminal input provider not initialized'); + return; + } + + try { + // Check if file exists + const fileExists = await vscode.workspace.fs.stat(uri).then(() => true, () => false); + if (!fileExists) { + vscode.window.showWarningMessage(`File does not exist: ${uri.fsPath}`); + return; + } + + // Get workspace-relative path + const workspaceFolders = vscode.workspace.workspaceFolders; + let relativePath = uri.fsPath; + + if (workspaceFolders && workspaceFolders.length > 0) { + const workspaceRoot = workspaceFolders[0].uri.fsPath; + if (relativePath.startsWith(workspaceRoot)) { + relativePath = relativePath.substring(workspaceRoot.length); + if (relativePath.startsWith('/') || relativePath.startsWith('\\')) { + relativePath = relativePath.substring(1); + } + } + } else { + const pathParts = relativePath.split(/[/\\]/); + relativePath = pathParts[pathParts.length - 1]; + } + + // Format message to explain the file + const message = `Explain this file: @${relativePath}`; + + // Ensure terminal is available and send message + const terminalResult = await ensureClaudeTerminal(context); + claudeTerminalInputProvider.updateTerminal(terminalResult.terminal, terminalResult.isExisting); + terminalResult.terminal.show(false); + + if (!terminalResult.isRunningClaude && !isClaudeRunning) { + await claudeTerminalInputProvider.sendToTerminal('claude'); + isClaudeRunning = true; + await new Promise(resolve => setTimeout(resolve, 1000)); + } + + await claudeTerminalInputProvider.sendToTerminal(message); + vscode.commands.executeCommand('claudeCodeInputView.focus'); + } catch (error) { + console.error('Error in explainFile command:', error); + vscode.window.showErrorMessage(`Failed to explain file: ${error}`); + } + }); + + context.subscriptions.push(explainFileCommand); + + // Register command to explain folder with Claude Code + const explainFolderCommand = vscode.commands.registerCommand('claude-code-extension.explainFolder', async (uri: vscode.Uri) => { + if (!claudeTerminalInputProvider) { + vscode.window.showErrorMessage('Claude terminal input provider not initialized'); + return; + } + + try { + // Check if folder exists + const stat = await vscode.workspace.fs.stat(uri); + if (!(stat.type & vscode.FileType.Directory)) { + vscode.window.showWarningMessage(`Path is not a directory: ${uri.fsPath}`); + return; + } + + // Get workspace-relative path + const workspaceFolders = vscode.workspace.workspaceFolders; + let relativePath = uri.fsPath; + + if (workspaceFolders && workspaceFolders.length > 0) { + const workspaceRoot = workspaceFolders[0].uri.fsPath; + if (relativePath.startsWith(workspaceRoot)) { + relativePath = relativePath.substring(workspaceRoot.length); + if (relativePath.startsWith('/') || relativePath.startsWith('\\')) { + relativePath = relativePath.substring(1); + } + } + } else { + const pathParts = relativePath.split(/[/\\]/); + relativePath = pathParts[pathParts.length - 1]; + } + + // Format message to explain the folder + const message = `Explain the structure and purpose of this folder: @${relativePath}`; + + // Ensure terminal is available and send message + const terminalResult = await ensureClaudeTerminal(context); + claudeTerminalInputProvider.updateTerminal(terminalResult.terminal, terminalResult.isExisting); + terminalResult.terminal.show(false); + + if (!terminalResult.isRunningClaude && !isClaudeRunning) { + await claudeTerminalInputProvider.sendToTerminal('claude'); + isClaudeRunning = true; + await new Promise(resolve => setTimeout(resolve, 1000)); + } + + await claudeTerminalInputProvider.sendToTerminal(message); + vscode.commands.executeCommand('claudeCodeInputView.focus'); + } catch (error) { + console.error('Error in explainFolder command:', error); + vscode.window.showErrorMessage(`Failed to explain folder: ${error}`); + } + }); + + context.subscriptions.push(explainFolderCommand); + + // Register command to explain selection with Claude Code + const explainSelectionCommand = vscode.commands.registerCommand('claude-code-extension.explainSelection', async () => { + if (!claudeTerminalInputProvider) { + vscode.window.showErrorMessage('Claude terminal input provider not initialized'); + return; + } + + try { + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showWarningMessage('No active editor found'); + return; + } + + const selection = editor.selection; + const selectedText = editor.document.getText(selection); + + if (!selectedText) { + vscode.window.showWarningMessage('No text selected'); + return; + } + + // Get workspace-relative path + const workspaceFolders = vscode.workspace.workspaceFolders; + let relativePath = editor.document.fileName; + + if (workspaceFolders && workspaceFolders.length > 0) { + const workspaceRoot = workspaceFolders[0].uri.fsPath; + if (relativePath.startsWith(workspaceRoot)) { + relativePath = relativePath.substring(workspaceRoot.length); + if (relativePath.startsWith('/') || relativePath.startsWith('\\')) { + relativePath = relativePath.substring(1); + } + } + } else { + const pathParts = relativePath.split(/[/\\]/); + relativePath = pathParts[pathParts.length - 1]; + } + + // Get line numbers + const startLine = selection.start.line + 1; + const endLine = selection.end.line + 1; + const lineRange = startLine === endLine ? `${startLine}` : `${startLine}-${endLine}`; + + // Format message to explain the selection + const message = `Explain this code: @${relativePath}#L${lineRange}`; + + // Ensure terminal is available and send message + const terminalResult = await ensureClaudeTerminal(context); + claudeTerminalInputProvider.updateTerminal(terminalResult.terminal, terminalResult.isExisting); + terminalResult.terminal.show(false); + + if (!terminalResult.isRunningClaude && !isClaudeRunning) { + await claudeTerminalInputProvider.sendToTerminal('claude'); + isClaudeRunning = true; + await new Promise(resolve => setTimeout(resolve, 1000)); + } + + await claudeTerminalInputProvider.sendToTerminal(message); + vscode.commands.executeCommand('claudeCodeInputView.focus'); + } catch (error) { + console.error('Error in explainSelection command:', error); + vscode.window.showErrorMessage(`Failed to explain selection: ${error}`); + } + }); + + context.subscriptions.push(explainSelectionCommand); + + // Register command to explain current file with Claude Code + const explainCurrentFileCommand = vscode.commands.registerCommand('claude-code-extension.explainCurrentFile', async () => { + if (!claudeTerminalInputProvider) { + vscode.window.showErrorMessage('Claude terminal input provider not initialized'); + return; + } + + try { + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showWarningMessage('No active editor found'); + return; + } + + // Get workspace-relative path + const workspaceFolders = vscode.workspace.workspaceFolders; + let relativePath = editor.document.fileName; + + if (workspaceFolders && workspaceFolders.length > 0) { + const workspaceRoot = workspaceFolders[0].uri.fsPath; + if (relativePath.startsWith(workspaceRoot)) { + relativePath = relativePath.substring(workspaceRoot.length); + if (relativePath.startsWith('/') || relativePath.startsWith('\\')) { + relativePath = relativePath.substring(1); + } + } + } else { + const pathParts = relativePath.split(/[/\\]/); + relativePath = pathParts[pathParts.length - 1]; + } + + // Format message to explain the current file + const message = `Explain this file: @${relativePath}`; + + // Ensure terminal is available and send message + const terminalResult = await ensureClaudeTerminal(context); + claudeTerminalInputProvider.updateTerminal(terminalResult.terminal, terminalResult.isExisting); + terminalResult.terminal.show(false); + + if (!terminalResult.isRunningClaude && !isClaudeRunning) { + await claudeTerminalInputProvider.sendToTerminal('claude'); + isClaudeRunning = true; + await new Promise(resolve => setTimeout(resolve, 1000)); + } + + await claudeTerminalInputProvider.sendToTerminal(message); + vscode.commands.executeCommand('claudeCodeInputView.focus'); + } catch (error) { + console.error('Error in explainCurrentFile command:', error); + vscode.window.showErrorMessage(`Failed to explain current file: ${error}`); + } + }); + + context.subscriptions.push(explainCurrentFileCommand); + // Register Claude Code Action Provider for Quick Fix menu const claudeCodeActionProvider = new ClaudeCodeActionProvider(claudeTerminalInputProvider); context.subscriptions.push( diff --git a/src/ui/claudeTerminalInputProvider.ts b/src/ui/claudeTerminalInputProvider.ts index 5ac1c75..82cfbf9 100644 --- a/src/ui/claudeTerminalInputProvider.ts +++ b/src/ui/claudeTerminalInputProvider.ts @@ -106,6 +106,18 @@ export class ClaudeTerminalInputProvider implements vscode.WebviewViewProvider { }); } } + + public async focusInput() { + // Focus the Claude Code input view first + await vscode.commands.executeCommand('claudeCodeInputView.focus'); + + // Then send a focus message to the webview to focus the input field + if (this._view) { + this._view.webview.postMessage({ + command: "focusInput" + }); + } + } /** * Sends a command to the Claude terminal asynchronously @@ -287,7 +299,7 @@ export class ClaudeTerminalInputProvider implements vscode.WebviewViewProvider { this._terminal?.sendText('\x1b[200~', false); // Send the actual text - this._terminal?.sendText(text, false); + this._terminal?.sendText(text + " ", false); // Send bracketed paste end sequence this._terminal?.sendText('\x1b[201~', false); @@ -295,7 +307,7 @@ export class ClaudeTerminalInputProvider implements vscode.WebviewViewProvider { // Keep bracketed paste mode enabled (Claude Code keeps it on) } else { // Send text to terminal normally - this._terminal?.sendText(text, false); + this._terminal?.sendText(text + " ", false); } // Use the same delay logic for both paste and normal mode