From 19e6da3956dcc3afa1ebf323419afcb8fcc4fae1 Mon Sep 17 00:00:00 2001 From: "codegen-sh[bot]" <131295404+codegen-sh[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 05:31:17 +0000 Subject: [PATCH 1/4] Implement VSCode Extension for Codegen --- codegen-vscode-extension/README.md | 78 +++++ .../media/codegen-icon.svg | 5 + codegen-vscode-extension/media/main.css | 237 +++++++++++++++ codegen-vscode-extension/media/main.js | 170 +++++++++++ codegen-vscode-extension/package.json | 132 ++++++++ .../src/api/codegenAPI.ts | 136 +++++++++ codegen-vscode-extension/src/commands.ts | 287 ++++++++++++++++++ codegen-vscode-extension/src/extension.ts | 36 +++ .../src/providers/chatViewProvider.ts | 154 ++++++++++ codegen-vscode-extension/src/utils.ts | 102 +++++++ codegen-vscode-extension/tsconfig.json | 15 + codegen-vscode-extension/webpack.config.js | 34 +++ 12 files changed, 1386 insertions(+) create mode 100644 codegen-vscode-extension/README.md create mode 100644 codegen-vscode-extension/media/codegen-icon.svg create mode 100644 codegen-vscode-extension/media/main.css create mode 100644 codegen-vscode-extension/media/main.js create mode 100644 codegen-vscode-extension/package.json create mode 100644 codegen-vscode-extension/src/api/codegenAPI.ts create mode 100644 codegen-vscode-extension/src/commands.ts create mode 100644 codegen-vscode-extension/src/extension.ts create mode 100644 codegen-vscode-extension/src/providers/chatViewProvider.ts create mode 100644 codegen-vscode-extension/src/utils.ts create mode 100644 codegen-vscode-extension/tsconfig.json create mode 100644 codegen-vscode-extension/webpack.config.js diff --git a/codegen-vscode-extension/README.md b/codegen-vscode-extension/README.md new file mode 100644 index 000000000..666ea4750 --- /dev/null +++ b/codegen-vscode-extension/README.md @@ -0,0 +1,78 @@ +# Codegen VSCode Extension + +A VSCode extension for Codegen - AI-powered code generation and assistance. + +## Features + +- **Ask Questions**: Get answers to your programming questions directly in VSCode +- **Generate Code**: Generate code snippets based on your descriptions +- **Explain Code**: Get explanations for selected code +- **Improve Code**: Get suggestions to improve your code +- **Fix Code**: Get help fixing bugs in your code + +## Requirements + +- VSCode 1.78.0 or higher +- A Codegen API key (get one at [codegen.sh](https://codegen.sh)) + +## Installation + +1. Install the extension from the VSCode Marketplace +2. Set your Codegen API key in the extension settings +3. Start using Codegen in your development workflow! + +## Extension Settings + +This extension contributes the following settings: + +* `codegen.apiKey`: Your Codegen API key +* `codegen.endpoint`: The endpoint for the Codegen API (defaults to https://api.codegen.sh) + +## Commands + +The extension provides the following commands: + +* `codegen.askQuestion`: Ask Codegen a question +* `codegen.generateCode`: Generate code with Codegen +* `codegen.explainCode`: Explain selected code +* `codegen.improveCode`: Improve selected code +* `codegen.fixCode`: Fix selected code + +## Usage + +### Ask a Question + +1. Open the command palette (`Ctrl+Shift+P` or `Cmd+Shift+P` on macOS) +2. Type "Ask Codegen a Question" and press Enter +3. Enter your question and press Enter +4. View the response in the Codegen Chat view + +### Generate Code + +1. Open the command palette +2. Type "Generate Code with Codegen" and press Enter +3. Describe the code you want to generate +4. The generated code will be inserted at your cursor position + +### Explain Code + +1. Select the code you want to explain +2. Right-click and select "Explain Selected Code" from the context menu +3. View the explanation in the Codegen Chat view + +### Improve Code + +1. Select the code you want to improve +2. Right-click and select "Improve Selected Code" from the context menu +3. Review the suggested improvements and apply them if desired + +### Fix Code + +1. Select the code you want to fix +2. Right-click and select "Fix Selected Code" from the context menu +3. Optionally provide the error message you're getting +4. Review the suggested fixes and apply them if desired + +## License + +MIT diff --git a/codegen-vscode-extension/media/codegen-icon.svg b/codegen-vscode-extension/media/codegen-icon.svg new file mode 100644 index 000000000..53aa599b8 --- /dev/null +++ b/codegen-vscode-extension/media/codegen-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/codegen-vscode-extension/media/main.css b/codegen-vscode-extension/media/main.css new file mode 100644 index 000000000..fcb28f268 --- /dev/null +++ b/codegen-vscode-extension/media/main.css @@ -0,0 +1,237 @@ +:root { + --container-padding: 10px; + --input-padding-vertical: 6px; + --input-padding-horizontal: 8px; + --input-margin-vertical: 4px; + --input-margin-horizontal: 0; +} + +body { + padding: 0; + margin: 0; + color: var(--vscode-foreground); + font-size: var(--vscode-font-size); + font-weight: var(--vscode-font-weight); + font-family: var(--vscode-font-family); + background-color: var(--vscode-editor-background); +} + +#chat-container { + display: flex; + flex-direction: column; + height: 100vh; + padding: var(--container-padding); + box-sizing: border-box; +} + +#messages { + flex: 1; + overflow-y: auto; + margin-bottom: 10px; + padding-right: 8px; +} + +.message { + margin-bottom: 10px; + padding: 8px 12px; + border-radius: 6px; + max-width: 90%; + word-wrap: break-word; +} + +.user-message { + background-color: var(--vscode-button-background); + color: var(--vscode-button-foreground); + align-self: flex-end; + margin-left: auto; +} + +.assistant-message { + background-color: var(--vscode-editor-inactiveSelectionBackground); + color: var(--vscode-editor-foreground); + align-self: flex-start; +} + +.message-content { + margin-bottom: 5px; +} + +.code-block { + background-color: var(--vscode-editor-background); + border: 1px solid var(--vscode-editor-lineHighlightBorder); + border-radius: 3px; + padding: 8px; + margin-top: 5px; + font-family: var(--vscode-editor-font-family); + font-size: var(--vscode-editor-font-size); + white-space: pre-wrap; + overflow-x: auto; +} + +.code-actions { + display: flex; + justify-content: flex-end; + margin-top: 5px; +} + +.code-action-button { + background-color: var(--vscode-button-secondaryBackground); + color: var(--vscode-button-secondaryForeground); + border: none; + padding: 4px 8px; + border-radius: 2px; + cursor: pointer; + font-size: 12px; + margin-left: 5px; +} + +.code-action-button:hover { + background-color: var(--vscode-button-secondaryHoverBackground); +} + +.timestamp { + font-size: 10px; + color: var(--vscode-descriptionForeground); + text-align: right; + margin-top: 2px; +} + +#input-container { + display: flex; + padding: 8px 0; +} + +#message-input { + flex: 1; + height: 60px; + padding: var(--input-padding-vertical) var(--input-padding-horizontal); + border: 1px solid var(--vscode-input-border); + background-color: var(--vscode-input-background); + color: var(--vscode-input-foreground); + resize: none; + font-family: var(--vscode-font-family); + font-size: var(--vscode-font-size); + border-radius: 2px; +} + +#message-input:focus { + outline: 1px solid var(--vscode-focusBorder); +} + +#send-button { + margin-left: 8px; + padding: 0 14px; + background-color: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: none; + cursor: pointer; + border-radius: 2px; +} + +#send-button:hover { + background-color: var(--vscode-button-hoverBackground); +} + +#loading { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.3); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; +} + +.hidden { + display: none !important; +} + +.spinner { + width: 40px; + height: 40px; + border: 4px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + border-top-color: var(--vscode-button-background); + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* Markdown styling */ +.markdown h1, .markdown h2, .markdown h3, .markdown h4, .markdown h5, .markdown h6 { + margin-top: 16px; + margin-bottom: 8px; + font-weight: 600; +} + +.markdown p { + margin-top: 0; + margin-bottom: 8px; +} + +.markdown ul, .markdown ol { + padding-left: 20px; + margin-top: 0; + margin-bottom: 8px; +} + +.markdown code { + font-family: var(--vscode-editor-font-family); + background-color: var(--vscode-textCodeBlock-background); + padding: 2px 4px; + border-radius: 3px; +} + +.markdown pre { + background-color: var(--vscode-textCodeBlock-background); + padding: 8px; + border-radius: 3px; + overflow-x: auto; + margin-top: 8px; + margin-bottom: 8px; +} + +.markdown pre code { + background-color: transparent; + padding: 0; +} + +.markdown a { + color: var(--vscode-textLink-foreground); + text-decoration: none; +} + +.markdown a:hover { + text-decoration: underline; +} + +.markdown blockquote { + border-left: 3px solid var(--vscode-textBlockQuote-border); + padding-left: 8px; + margin-left: 0; + margin-right: 0; + color: var(--vscode-textBlockQuote-foreground); +} + +.markdown table { + border-collapse: collapse; + width: 100%; + margin-bottom: 16px; +} + +.markdown th, .markdown td { + border: 1px solid var(--vscode-editor-lineHighlightBorder); + padding: 6px 13px; +} + +.markdown th { + background-color: var(--vscode-editor-inactiveSelectionBackground); + font-weight: 600; +} diff --git a/codegen-vscode-extension/media/main.js b/codegen-vscode-extension/media/main.js new file mode 100644 index 000000000..76183cebd --- /dev/null +++ b/codegen-vscode-extension/media/main.js @@ -0,0 +1,170 @@ +(function() { + // Get VS Code API + const vscode = acquireVsCodeApi(); + + // DOM elements + const messagesContainer = document.getElementById('messages'); + const messageInput = document.getElementById('message-input'); + const sendButton = document.getElementById('send-button'); + const loadingElement = document.getElementById('loading'); + + // Store messages + let messages = []; + + // Initialize + window.addEventListener('message', event => { + const message = event.data; + + switch (message.command) { + case 'updateMessages': + messages = message.messages; + renderMessages(); + break; + case 'showLoading': + toggleLoading(message.value); + break; + } + }); + + // Send message when button is clicked + sendButton.addEventListener('click', () => { + sendMessage(); + }); + + // Send message when Enter is pressed (without Shift) + messageInput.addEventListener('keydown', (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + sendMessage(); + } + }); + + // Send message to extension + function sendMessage() { + const text = messageInput.value.trim(); + if (text) { + vscode.postMessage({ + command: 'sendMessage', + text: text + }); + messageInput.value = ''; + } + } + + // Render all messages + function renderMessages() { + messagesContainer.innerHTML = ''; + + messages.forEach(message => { + const messageElement = document.createElement('div'); + messageElement.className = `message ${message.role}-message`; + + // Message content + const contentElement = document.createElement('div'); + contentElement.className = 'message-content markdown'; + contentElement.innerHTML = formatMarkdown(message.content); + messageElement.appendChild(contentElement); + + // Code block (if any) + if (message.code) { + const codeElement = document.createElement('div'); + codeElement.className = 'code-block'; + codeElement.textContent = message.code; + messageElement.appendChild(codeElement); + + // Code actions + const actionsElement = document.createElement('div'); + actionsElement.className = 'code-actions'; + + // Insert code button + const insertButton = document.createElement('button'); + insertButton.className = 'code-action-button'; + insertButton.textContent = 'Insert Code'; + insertButton.addEventListener('click', () => { + vscode.postMessage({ + command: 'insertCode', + code: message.code + }); + }); + actionsElement.appendChild(insertButton); + + // Copy code button + const copyButton = document.createElement('button'); + copyButton.className = 'code-action-button'; + copyButton.textContent = 'Copy'; + copyButton.addEventListener('click', () => { + navigator.clipboard.writeText(message.code); + copyButton.textContent = 'Copied!'; + setTimeout(() => { + copyButton.textContent = 'Copy'; + }, 2000); + }); + actionsElement.appendChild(copyButton); + + messageElement.appendChild(actionsElement); + } + + // Timestamp + const timestampElement = document.createElement('div'); + timestampElement.className = 'timestamp'; + timestampElement.textContent = formatTimestamp(message.timestamp); + messageElement.appendChild(timestampElement); + + messagesContainer.appendChild(messageElement); + }); + + // Scroll to bottom + messagesContainer.scrollTop = messagesContainer.scrollHeight; + } + + // Format timestamp + function formatTimestamp(timestamp) { + const date = new Date(timestamp); + return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + } + + // Toggle loading indicator + function toggleLoading(show) { + if (show) { + loadingElement.classList.remove('hidden'); + } else { + loadingElement.classList.add('hidden'); + } + } + + // Format markdown to HTML + function formatMarkdown(text) { + // This is a very simple markdown parser + // In a real extension, you might want to use a library like marked.js + + // Code blocks + text = text.replace(/```(\w*)\n([\s\S]*?)\n```/g, '
$2
'); + + // Inline code + text = text.replace(/`([^`]+)`/g, '$1'); + + // Headers + text = text.replace(/^### (.*$)/gm, '

$1

'); + text = text.replace(/^## (.*$)/gm, '

$1

'); + text = text.replace(/^# (.*$)/gm, '

$1

'); + + // Bold + text = text.replace(/\*\*(.*?)\*\*/g, '$1'); + + // Italic + text = text.replace(/\*(.*?)\*/g, '$1'); + + // Lists + text = text.replace(/^\s*\*\s(.*$)/gm, '
  • $1
  • '); + text = text.replace(/(
  • .*<\/li>)/g, ''); + + // Links + text = text.replace(/\[(.*?)\]\((.*?)\)/g, '$1'); + + // Paragraphs + text = text.replace(/\n\n/g, '

    '); + text = '

    ' + text + '

    '; + + return text; + } +})(); diff --git a/codegen-vscode-extension/package.json b/codegen-vscode-extension/package.json new file mode 100644 index 000000000..f57e7e552 --- /dev/null +++ b/codegen-vscode-extension/package.json @@ -0,0 +1,132 @@ +{ + "name": "codegen-vscode-extension", + "displayName": "Codegen", + "description": "VSCode extension for Codegen - AI-powered code generation and assistance", + "version": "0.1.0", + "engines": { + "vscode": "^1.78.0" + }, + "categories": [ + "Programming Languages", + "Machine Learning", + "Other" + ], + "activationEvents": [ + "onStartupFinished" + ], + "main": "./dist/extension.js", + "contributes": { + "commands": [ + { + "command": "codegen.askQuestion", + "title": "Ask Codegen a Question", + "category": "Codegen" + }, + { + "command": "codegen.generateCode", + "title": "Generate Code with Codegen", + "category": "Codegen" + }, + { + "command": "codegen.explainCode", + "title": "Explain Selected Code", + "category": "Codegen" + }, + { + "command": "codegen.improveCode", + "title": "Improve Selected Code", + "category": "Codegen" + }, + { + "command": "codegen.fixCode", + "title": "Fix Selected Code", + "category": "Codegen" + } + ], + "viewsContainers": { + "activitybar": [ + { + "id": "codegen-sidebar", + "title": "Codegen", + "icon": "media/codegen-icon.svg" + } + ] + }, + "views": { + "codegen-sidebar": [ + { + "type": "webview", + "id": "codegen.chatView", + "name": "Codegen Chat" + } + ] + }, + "configuration": { + "title": "Codegen", + "properties": { + "codegen.apiKey": { + "type": "string", + "default": "", + "description": "API key for Codegen services" + }, + "codegen.endpoint": { + "type": "string", + "default": "https://api.codegen.sh", + "description": "Endpoint for Codegen API" + } + } + }, + "menus": { + "editor/context": [ + { + "command": "codegen.explainCode", + "when": "editorHasSelection", + "group": "codegen" + }, + { + "command": "codegen.improveCode", + "when": "editorHasSelection", + "group": "codegen" + }, + { + "command": "codegen.fixCode", + "when": "editorHasSelection", + "group": "codegen" + }, + { + "command": "codegen.generateCode", + "group": "codegen" + } + ] + } + }, + "scripts": { + "vscode:prepublish": "npm run package", + "compile": "webpack", + "watch": "webpack --watch", + "package": "webpack --mode production --devtool source-map --config ./webpack.config.js", + "compile-tests": "tsc -p . --outDir out", + "watch-tests": "tsc -p . -w --outDir out", + "lint": "eslint src --ext ts", + "test": "node ./out/test/runTest.js" + }, + "devDependencies": { + "@types/glob": "^8.1.0", + "@types/mocha": "^10.0.1", + "@types/node": "16.x", + "@types/vscode": "^1.78.0", + "@typescript-eslint/eslint-plugin": "^5.59.1", + "@typescript-eslint/parser": "^5.59.1", + "@vscode/test-electron": "^2.3.0", + "eslint": "^8.39.0", + "glob": "^8.1.0", + "mocha": "^10.2.0", + "ts-loader": "^9.4.2", + "typescript": "^5.0.4", + "webpack": "^5.81.0", + "webpack-cli": "^5.0.2" + }, + "dependencies": { + "axios": "^1.4.0" + } +} diff --git a/codegen-vscode-extension/src/api/codegenAPI.ts b/codegen-vscode-extension/src/api/codegenAPI.ts new file mode 100644 index 000000000..9a98c6185 --- /dev/null +++ b/codegen-vscode-extension/src/api/codegenAPI.ts @@ -0,0 +1,136 @@ +import axios, { AxiosInstance } from 'axios'; +import * as vscode from 'vscode'; + +export interface CodegenResponse { + text: string; + code?: string; + language?: string; +} + +export class CodegenAPI { + private client: AxiosInstance; + private apiKey: string = ''; + private endpoint: string = ''; + + constructor() { + this.loadConfiguration(); + + this.client = axios.create({ + baseURL: this.endpoint, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.apiKey}` + } + }); + + // Listen for configuration changes + vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('codegen.apiKey') || e.affectsConfiguration('codegen.endpoint')) { + this.loadConfiguration(); + this.updateClient(); + } + }); + } + + private loadConfiguration() { + const config = vscode.workspace.getConfiguration('codegen'); + this.apiKey = config.get('apiKey') || ''; + this.endpoint = config.get('endpoint') || 'https://api.codegen.sh'; + } + + private updateClient() { + this.client = axios.create({ + baseURL: this.endpoint, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.apiKey}` + } + }); + } + + public async validateApiKey(): Promise { + if (!this.apiKey) { + return false; + } + + try { + // Make a simple request to validate the API key + await this.client.get('/api/validate'); + return true; + } catch (error) { + console.error('API key validation failed:', error); + return false; + } + } + + public async askQuestion(question: string, context?: string): Promise { + try { + const response = await this.client.post('/api/ask', { + question, + context + }); + + return response.data; + } catch (error) { + console.error('Error asking question:', error); + throw new Error('Failed to get response from Codegen API'); + } + } + + public async generateCode(prompt: string, language?: string): Promise { + try { + const response = await this.client.post('/api/generate', { + prompt, + language + }); + + return response.data; + } catch (error) { + console.error('Error generating code:', error); + throw new Error('Failed to generate code from Codegen API'); + } + } + + public async explainCode(code: string, language?: string): Promise { + try { + const response = await this.client.post('/api/explain', { + code, + language + }); + + return response.data; + } catch (error) { + console.error('Error explaining code:', error); + throw new Error('Failed to explain code from Codegen API'); + } + } + + public async improveCode(code: string, language?: string): Promise { + try { + const response = await this.client.post('/api/improve', { + code, + language + }); + + return response.data; + } catch (error) { + console.error('Error improving code:', error); + throw new Error('Failed to improve code from Codegen API'); + } + } + + public async fixCode(code: string, error?: string, language?: string): Promise { + try { + const response = await this.client.post('/api/fix', { + code, + error, + language + }); + + return response.data; + } catch (error) { + console.error('Error fixing code:', error); + throw new Error('Failed to fix code from Codegen API'); + } + } +} diff --git a/codegen-vscode-extension/src/commands.ts b/codegen-vscode-extension/src/commands.ts new file mode 100644 index 000000000..7a7c6682a --- /dev/null +++ b/codegen-vscode-extension/src/commands.ts @@ -0,0 +1,287 @@ +import * as vscode from 'vscode'; +import { CodegenAPI } from './api/codegenAPI'; +import { ChatViewProvider } from './providers/chatViewProvider'; +import { getSelectedText, getDocumentLanguage, insertText } from './utils'; + +export function registerCommands( + context: vscode.ExtensionContext, + api: CodegenAPI, + chatViewProvider: ChatViewProvider +) { + // Ask a question + context.subscriptions.push( + vscode.commands.registerCommand('codegen.askQuestion', async () => { + const question = await vscode.window.showInputBox({ + prompt: 'What would you like to ask Codegen?', + placeHolder: 'Ask a programming question...' + }); + + if (!question) { + return; + } + + try { + vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: 'Asking Codegen...', + cancellable: false + }, async (progress) => { + progress.report({ increment: 0 }); + + const response = await api.askQuestion(question); + + progress.report({ increment: 100 }); + + // Send to chat view + chatViewProvider.addMessage('user', question); + chatViewProvider.addMessage('assistant', response.text); + + // Show the chat view + vscode.commands.executeCommand('codegen.chatView.focus'); + + return response; + }); + } catch (error) { + vscode.window.showErrorMessage(`Error: ${error.message}`); + } + }) + ); + + // Generate code + context.subscriptions.push( + vscode.commands.registerCommand('codegen.generateCode', async () => { + const prompt = await vscode.window.showInputBox({ + prompt: 'Describe the code you want to generate', + placeHolder: 'Generate a function that...' + }); + + if (!prompt) { + return; + } + + const editor = vscode.window.activeTextEditor; + const language = editor ? getDocumentLanguage(editor.document) : undefined; + + try { + vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: 'Generating code...', + cancellable: false + }, async (progress) => { + progress.report({ increment: 0 }); + + const response = await api.generateCode(prompt, language); + + progress.report({ increment: 100 }); + + if (editor && response.code) { + // Insert the generated code at the cursor position + insertText(editor, response.code); + } + + // Send to chat view + chatViewProvider.addMessage('user', `Generate code: ${prompt}`); + chatViewProvider.addMessage('assistant', response.text, response.code); + + return response; + }); + } catch (error) { + vscode.window.showErrorMessage(`Error: ${error.message}`); + } + }) + ); + + // Explain code + context.subscriptions.push( + vscode.commands.registerCommand('codegen.explainCode', async () => { + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showErrorMessage('No active editor'); + return; + } + + const selectedText = getSelectedText(editor); + if (!selectedText) { + vscode.window.showErrorMessage('No code selected'); + return; + } + + const language = getDocumentLanguage(editor.document); + + try { + vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: 'Explaining code...', + cancellable: false + }, async (progress) => { + progress.report({ increment: 0 }); + + const response = await api.explainCode(selectedText, language); + + progress.report({ increment: 100 }); + + // Send to chat view + chatViewProvider.addMessage('user', `Explain this code:\n\`\`\`${language}\n${selectedText}\n\`\`\``); + chatViewProvider.addMessage('assistant', response.text); + + // Show the chat view + vscode.commands.executeCommand('codegen.chatView.focus'); + + return response; + }); + } catch (error) { + vscode.window.showErrorMessage(`Error: ${error.message}`); + } + }) + ); + + // Improve code + context.subscriptions.push( + vscode.commands.registerCommand('codegen.improveCode', async () => { + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showErrorMessage('No active editor'); + return; + } + + const selectedText = getSelectedText(editor); + if (!selectedText) { + vscode.window.showErrorMessage('No code selected'); + return; + } + + const language = getDocumentLanguage(editor.document); + + try { + vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: 'Improving code...', + cancellable: false + }, async (progress) => { + progress.report({ increment: 0 }); + + const response = await api.improveCode(selectedText, language); + + progress.report({ increment: 100 }); + + if (response.code) { + // Show diff and ask if user wants to apply changes + const document = editor.document; + const selection = editor.selection; + + const diffEditor = await vscode.diff.createTextDocumentAndEditorEdit( + document, + selection, + response.code + ); + + // Add buttons to apply or discard changes + const applyChanges = 'Apply Changes'; + const discardChanges = 'Discard'; + + const choice = await vscode.window.showInformationMessage( + 'Review the suggested improvements', + applyChanges, + discardChanges + ); + + if (choice === applyChanges) { + // Replace the selected text with the improved code + editor.edit(editBuilder => { + editBuilder.replace(selection, response.code || ''); + }); + } + } + + // Send to chat view + chatViewProvider.addMessage('user', `Improve this code:\n\`\`\`${language}\n${selectedText}\n\`\`\``); + chatViewProvider.addMessage('assistant', response.text, response.code); + + return response; + }); + } catch (error) { + vscode.window.showErrorMessage(`Error: ${error.message}`); + } + }) + ); + + // Fix code + context.subscriptions.push( + vscode.commands.registerCommand('codegen.fixCode', async () => { + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showErrorMessage('No active editor'); + return; + } + + const selectedText = getSelectedText(editor); + if (!selectedText) { + vscode.window.showErrorMessage('No code selected'); + return; + } + + const language = getDocumentLanguage(editor.document); + + // Ask for error message + const errorMessage = await vscode.window.showInputBox({ + prompt: 'What error are you getting? (optional)', + placeHolder: 'Error message or description of the issue' + }); + + try { + vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: 'Fixing code...', + cancellable: false + }, async (progress) => { + progress.report({ increment: 0 }); + + const response = await api.fixCode(selectedText, errorMessage, language); + + progress.report({ increment: 100 }); + + if (response.code) { + // Show diff and ask if user wants to apply changes + const document = editor.document; + const selection = editor.selection; + + const diffEditor = await vscode.diff.createTextDocumentAndEditorEdit( + document, + selection, + response.code + ); + + // Add buttons to apply or discard changes + const applyChanges = 'Apply Changes'; + const discardChanges = 'Discard'; + + const choice = await vscode.window.showInformationMessage( + 'Review the suggested fixes', + applyChanges, + discardChanges + ); + + if (choice === applyChanges) { + // Replace the selected text with the fixed code + editor.edit(editBuilder => { + editBuilder.replace(selection, response.code || ''); + }); + } + } + + // Send to chat view + const userMessage = errorMessage + ? `Fix this code (error: ${errorMessage}):\n\`\`\`${language}\n${selectedText}\n\`\`\`` + : `Fix this code:\n\`\`\`${language}\n${selectedText}\n\`\`\``; + + chatViewProvider.addMessage('user', userMessage); + chatViewProvider.addMessage('assistant', response.text, response.code); + + return response; + }); + } catch (error) { + vscode.window.showErrorMessage(`Error: ${error.message}`); + } + }) + ); +} diff --git a/codegen-vscode-extension/src/extension.ts b/codegen-vscode-extension/src/extension.ts new file mode 100644 index 000000000..101ad8344 --- /dev/null +++ b/codegen-vscode-extension/src/extension.ts @@ -0,0 +1,36 @@ +import * as vscode from 'vscode'; +import { ChatViewProvider } from './providers/chatViewProvider'; +import { CodegenAPI } from './api/codegenAPI'; +import { registerCommands } from './commands'; + +export function activate(context: vscode.ExtensionContext) { + console.log('Codegen extension is now active!'); + + // Initialize the API client + const api = new CodegenAPI(); + + // Register the chat view provider + const chatViewProvider = new ChatViewProvider(context.extensionUri, api); + context.subscriptions.push( + vscode.window.registerWebviewViewProvider( + 'codegen.chatView', + chatViewProvider, + { webviewOptions: { retainContextWhenHidden: true } } + ) + ); + + // Register commands + registerCommands(context, api, chatViewProvider); + + // Status bar item + const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100); + statusBarItem.text = '$(sparkle) Codegen'; + statusBarItem.tooltip = 'Codegen AI Assistant'; + statusBarItem.command = 'codegen.askQuestion'; + statusBarItem.show(); + context.subscriptions.push(statusBarItem); +} + +export function deactivate() { + // Clean up resources +} diff --git a/codegen-vscode-extension/src/providers/chatViewProvider.ts b/codegen-vscode-extension/src/providers/chatViewProvider.ts new file mode 100644 index 000000000..3ad0e5f91 --- /dev/null +++ b/codegen-vscode-extension/src/providers/chatViewProvider.ts @@ -0,0 +1,154 @@ +import * as vscode from 'vscode'; +import { CodegenAPI } from '../api/codegenAPI'; + +interface ChatMessage { + role: 'user' | 'assistant'; + content: string; + code?: string; + timestamp: number; +} + +export class ChatViewProvider implements vscode.WebviewViewProvider { + public static readonly viewType = 'codegen.chatView'; + private _view?: vscode.WebviewView; + private _messages: ChatMessage[] = []; + + constructor( + private readonly _extensionUri: vscode.Uri, + private readonly _api: CodegenAPI + ) {} + + public resolveWebviewView( + webviewView: vscode.WebviewView, + context: vscode.WebviewViewResolveContext, + _token: vscode.CancellationToken + ) { + this._view = webviewView; + + webviewView.webview.options = { + enableScripts: true, + localResourceRoots: [this._extensionUri] + }; + + webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); + + // Handle messages from the webview + webviewView.webview.onDidReceiveMessage(async (message) => { + switch (message.command) { + case 'sendMessage': + await this._handleUserMessage(message.text); + break; + case 'clearChat': + this._messages = []; + this._updateWebview(); + break; + case 'insertCode': + this._insertCodeToEditor(message.code); + break; + } + }); + + // Update the webview with existing messages + this._updateWebview(); + } + + public addMessage(role: 'user' | 'assistant', content: string, code?: string) { + this._messages.push({ + role, + content, + code, + timestamp: Date.now() + }); + + this._updateWebview(); + } + + private async _handleUserMessage(text: string) { + // Add user message to chat + this.addMessage('user', text); + + try { + // Show loading indicator + this._view?.webview.postMessage({ command: 'showLoading', value: true }); + + // Get response from API + const response = await this._api.askQuestion(text); + + // Add assistant message to chat + this.addMessage('assistant', response.text, response.code); + + // Hide loading indicator + this._view?.webview.postMessage({ command: 'showLoading', value: false }); + } catch (error) { + // Hide loading indicator + this._view?.webview.postMessage({ command: 'showLoading', value: false }); + + // Show error message + this.addMessage('assistant', `Error: ${error.message}`); + } + } + + private _updateWebview() { + if (this._view) { + this._view.webview.postMessage({ + command: 'updateMessages', + messages: this._messages + }); + } + } + + private _insertCodeToEditor(code: string) { + const editor = vscode.window.activeTextEditor; + if (editor) { + editor.edit(editBuilder => { + editBuilder.insert(editor.selection.active, code); + }); + } + } + + private _getHtmlForWebview(webview: vscode.Webview) { + // Create URIs for scripts and styles + const scriptUri = webview.asWebviewUri( + vscode.Uri.joinPath(this._extensionUri, 'media', 'main.js') + ); + const styleUri = webview.asWebviewUri( + vscode.Uri.joinPath(this._extensionUri, 'media', 'main.css') + ); + + // Use a nonce to allow only specific scripts to be run + const nonce = this._getNonce(); + + return ` + + + + + + + Codegen Chat + + +
    +
    +
    + + +
    + +
    + + + `; + } + + private _getNonce() { + let text = ''; + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; + } +} diff --git a/codegen-vscode-extension/src/utils.ts b/codegen-vscode-extension/src/utils.ts new file mode 100644 index 000000000..f802e69b0 --- /dev/null +++ b/codegen-vscode-extension/src/utils.ts @@ -0,0 +1,102 @@ +import * as vscode from 'vscode'; + +/** + * Get the selected text from the editor + */ +export function getSelectedText(editor: vscode.TextEditor): string { + const selection = editor.selection; + if (selection.isEmpty) { + return ''; + } + return editor.document.getText(selection); +} + +/** + * Get the language of the document + */ +export function getDocumentLanguage(document: vscode.TextDocument): string { + return document.languageId; +} + +/** + * Insert text at the current cursor position + */ +export function insertText(editor: vscode.TextEditor, text: string): void { + const selection = editor.selection; + editor.edit(editBuilder => { + editBuilder.insert(selection.active, text); + }); +} + +/** + * Get the current file path + */ +export function getCurrentFilePath(): string | undefined { + const editor = vscode.window.activeTextEditor; + if (!editor) { + return undefined; + } + return editor.document.uri.fsPath; +} + +/** + * Get the current workspace folder + */ +export function getCurrentWorkspaceFolder(): vscode.WorkspaceFolder | undefined { + const editor = vscode.window.activeTextEditor; + if (!editor) { + return undefined; + } + return vscode.workspace.getWorkspaceFolder(editor.document.uri); +} + +/** + * Get the current project name + */ +export function getCurrentProjectName(): string | undefined { + const workspaceFolder = getCurrentWorkspaceFolder(); + if (!workspaceFolder) { + return undefined; + } + return workspaceFolder.name; +} + +/** + * Get the current file name + */ +export function getCurrentFileName(): string | undefined { + const editor = vscode.window.activeTextEditor; + if (!editor) { + return undefined; + } + return editor.document.fileName.split(/[\\/]/).pop(); +} + +/** + * Get the current line of code + */ +export function getCurrentLine(editor: vscode.TextEditor): string { + const position = editor.selection.active; + const line = editor.document.lineAt(position.line); + return line.text; +} + +/** + * Get the current function or class + * This is a simple implementation and might not work for all languages + */ +export function getCurrentFunction(editor: vscode.TextEditor): string | undefined { + const document = editor.document; + const position = editor.selection.active; + + // Simple implementation - search backwards for function or class definition + for (let i = position.line; i >= 0; i--) { + const line = document.lineAt(i).text.trim(); + if (line.startsWith('function ') || line.startsWith('class ') || + line.startsWith('def ') || line.match(/^[a-zA-Z0-9_]+\s*\([^)]*\)\s*{/)) { + return line; + } + } + + return undefined; +} diff --git a/codegen-vscode-extension/tsconfig.json b/codegen-vscode-extension/tsconfig.json new file mode 100644 index 000000000..09724c930 --- /dev/null +++ b/codegen-vscode-extension/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES2020", + "outDir": "out", + "lib": ["ES2020", "DOM"], + "sourceMap": true, + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "exclude": ["node_modules", ".vscode-test"] +} diff --git a/codegen-vscode-extension/webpack.config.js b/codegen-vscode-extension/webpack.config.js new file mode 100644 index 000000000..d2df6e2d3 --- /dev/null +++ b/codegen-vscode-extension/webpack.config.js @@ -0,0 +1,34 @@ +const path = require('path'); + +const config = { + target: 'node', + entry: './src/extension.ts', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'extension.js', + libraryTarget: 'commonjs2', + devtoolModuleFilenameTemplate: '../[resource-path]' + }, + devtool: 'source-map', + externals: { + vscode: 'commonjs vscode' + }, + resolve: { + extensions: ['.ts', '.js'] + }, + module: { + rules: [ + { + test: /\.ts$/, + exclude: /node_modules/, + use: [ + { + loader: 'ts-loader' + } + ] + } + ] + } +}; + +module.exports = config; From 5f8d8113cee27626d0912928aa25123f8a0e2d49 Mon Sep 17 00:00:00 2001 From: "codegen-sh[bot]" <131295404+codegen-sh[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 05:32:18 +0000 Subject: [PATCH 2/4] Automated pre-commit update --- codegen-vscode-extension/README.md | 44 +- codegen-vscode-extension/media/main.css | 283 ++++---- codegen-vscode-extension/media/main.js | 341 +++++----- codegen-vscode-extension/package.json | 254 ++++---- .../src/api/codegenAPI.ts | 279 ++++---- codegen-vscode-extension/src/commands.ts | 607 ++++++++++-------- codegen-vscode-extension/src/extension.ts | 57 +- .../src/providers/chatViewProvider.ts | 257 ++++---- codegen-vscode-extension/src/utils.ts | 106 +-- codegen-vscode-extension/tsconfig.json | 26 +- codegen-vscode-extension/webpack.config.js | 58 +- 11 files changed, 1196 insertions(+), 1116 deletions(-) diff --git a/codegen-vscode-extension/README.md b/codegen-vscode-extension/README.md index 666ea4750..f213f58c4 100644 --- a/codegen-vscode-extension/README.md +++ b/codegen-vscode-extension/README.md @@ -18,60 +18,60 @@ A VSCode extension for Codegen - AI-powered code generation and assistance. ## Installation 1. Install the extension from the VSCode Marketplace -2. Set your Codegen API key in the extension settings -3. Start using Codegen in your development workflow! +1. Set your Codegen API key in the extension settings +1. Start using Codegen in your development workflow! ## Extension Settings This extension contributes the following settings: -* `codegen.apiKey`: Your Codegen API key -* `codegen.endpoint`: The endpoint for the Codegen API (defaults to https://api.codegen.sh) +- `codegen.apiKey`: Your Codegen API key +- `codegen.endpoint`: The endpoint for the Codegen API (defaults to https://api.codegen.sh) ## Commands The extension provides the following commands: -* `codegen.askQuestion`: Ask Codegen a question -* `codegen.generateCode`: Generate code with Codegen -* `codegen.explainCode`: Explain selected code -* `codegen.improveCode`: Improve selected code -* `codegen.fixCode`: Fix selected code +- `codegen.askQuestion`: Ask Codegen a question +- `codegen.generateCode`: Generate code with Codegen +- `codegen.explainCode`: Explain selected code +- `codegen.improveCode`: Improve selected code +- `codegen.fixCode`: Fix selected code ## Usage ### Ask a Question 1. Open the command palette (`Ctrl+Shift+P` or `Cmd+Shift+P` on macOS) -2. Type "Ask Codegen a Question" and press Enter -3. Enter your question and press Enter -4. View the response in the Codegen Chat view +1. Type "Ask Codegen a Question" and press Enter +1. Enter your question and press Enter +1. View the response in the Codegen Chat view ### Generate Code 1. Open the command palette -2. Type "Generate Code with Codegen" and press Enter -3. Describe the code you want to generate -4. The generated code will be inserted at your cursor position +1. Type "Generate Code with Codegen" and press Enter +1. Describe the code you want to generate +1. The generated code will be inserted at your cursor position ### Explain Code 1. Select the code you want to explain -2. Right-click and select "Explain Selected Code" from the context menu -3. View the explanation in the Codegen Chat view +1. Right-click and select "Explain Selected Code" from the context menu +1. View the explanation in the Codegen Chat view ### Improve Code 1. Select the code you want to improve -2. Right-click and select "Improve Selected Code" from the context menu -3. Review the suggested improvements and apply them if desired +1. Right-click and select "Improve Selected Code" from the context menu +1. Review the suggested improvements and apply them if desired ### Fix Code 1. Select the code you want to fix -2. Right-click and select "Fix Selected Code" from the context menu -3. Optionally provide the error message you're getting -4. Review the suggested fixes and apply them if desired +1. Right-click and select "Fix Selected Code" from the context menu +1. Optionally provide the error message you're getting +1. Review the suggested fixes and apply them if desired ## License diff --git a/codegen-vscode-extension/media/main.css b/codegen-vscode-extension/media/main.css index fcb28f268..010ce8d54 100644 --- a/codegen-vscode-extension/media/main.css +++ b/codegen-vscode-extension/media/main.css @@ -1,237 +1,244 @@ :root { - --container-padding: 10px; - --input-padding-vertical: 6px; - --input-padding-horizontal: 8px; - --input-margin-vertical: 4px; - --input-margin-horizontal: 0; + --container-padding: 10px; + --input-padding-vertical: 6px; + --input-padding-horizontal: 8px; + --input-margin-vertical: 4px; + --input-margin-horizontal: 0; } body { - padding: 0; - margin: 0; - color: var(--vscode-foreground); - font-size: var(--vscode-font-size); - font-weight: var(--vscode-font-weight); - font-family: var(--vscode-font-family); - background-color: var(--vscode-editor-background); + padding: 0; + margin: 0; + color: var(--vscode-foreground); + font-size: var(--vscode-font-size); + font-weight: var(--vscode-font-weight); + font-family: var(--vscode-font-family); + background-color: var(--vscode-editor-background); } #chat-container { - display: flex; - flex-direction: column; - height: 100vh; - padding: var(--container-padding); - box-sizing: border-box; + display: flex; + flex-direction: column; + height: 100vh; + padding: var(--container-padding); + box-sizing: border-box; } #messages { - flex: 1; - overflow-y: auto; - margin-bottom: 10px; - padding-right: 8px; + flex: 1; + overflow-y: auto; + margin-bottom: 10px; + padding-right: 8px; } .message { - margin-bottom: 10px; - padding: 8px 12px; - border-radius: 6px; - max-width: 90%; - word-wrap: break-word; + margin-bottom: 10px; + padding: 8px 12px; + border-radius: 6px; + max-width: 90%; + word-wrap: break-word; } .user-message { - background-color: var(--vscode-button-background); - color: var(--vscode-button-foreground); - align-self: flex-end; - margin-left: auto; + background-color: var(--vscode-button-background); + color: var(--vscode-button-foreground); + align-self: flex-end; + margin-left: auto; } .assistant-message { - background-color: var(--vscode-editor-inactiveSelectionBackground); - color: var(--vscode-editor-foreground); - align-self: flex-start; + background-color: var(--vscode-editor-inactiveSelectionBackground); + color: var(--vscode-editor-foreground); + align-self: flex-start; } .message-content { - margin-bottom: 5px; + margin-bottom: 5px; } .code-block { - background-color: var(--vscode-editor-background); - border: 1px solid var(--vscode-editor-lineHighlightBorder); - border-radius: 3px; - padding: 8px; - margin-top: 5px; - font-family: var(--vscode-editor-font-family); - font-size: var(--vscode-editor-font-size); - white-space: pre-wrap; - overflow-x: auto; + background-color: var(--vscode-editor-background); + border: 1px solid var(--vscode-editor-lineHighlightBorder); + border-radius: 3px; + padding: 8px; + margin-top: 5px; + font-family: var(--vscode-editor-font-family); + font-size: var(--vscode-editor-font-size); + white-space: pre-wrap; + overflow-x: auto; } .code-actions { - display: flex; - justify-content: flex-end; - margin-top: 5px; + display: flex; + justify-content: flex-end; + margin-top: 5px; } .code-action-button { - background-color: var(--vscode-button-secondaryBackground); - color: var(--vscode-button-secondaryForeground); - border: none; - padding: 4px 8px; - border-radius: 2px; - cursor: pointer; - font-size: 12px; - margin-left: 5px; + background-color: var(--vscode-button-secondaryBackground); + color: var(--vscode-button-secondaryForeground); + border: none; + padding: 4px 8px; + border-radius: 2px; + cursor: pointer; + font-size: 12px; + margin-left: 5px; } .code-action-button:hover { - background-color: var(--vscode-button-secondaryHoverBackground); + background-color: var(--vscode-button-secondaryHoverBackground); } .timestamp { - font-size: 10px; - color: var(--vscode-descriptionForeground); - text-align: right; - margin-top: 2px; + font-size: 10px; + color: var(--vscode-descriptionForeground); + text-align: right; + margin-top: 2px; } #input-container { - display: flex; - padding: 8px 0; + display: flex; + padding: 8px 0; } #message-input { - flex: 1; - height: 60px; - padding: var(--input-padding-vertical) var(--input-padding-horizontal); - border: 1px solid var(--vscode-input-border); - background-color: var(--vscode-input-background); - color: var(--vscode-input-foreground); - resize: none; - font-family: var(--vscode-font-family); - font-size: var(--vscode-font-size); - border-radius: 2px; + flex: 1; + height: 60px; + padding: var(--input-padding-vertical) var(--input-padding-horizontal); + border: 1px solid var(--vscode-input-border); + background-color: var(--vscode-input-background); + color: var(--vscode-input-foreground); + resize: none; + font-family: var(--vscode-font-family); + font-size: var(--vscode-font-size); + border-radius: 2px; } #message-input:focus { - outline: 1px solid var(--vscode-focusBorder); + outline: 1px solid var(--vscode-focusBorder); } #send-button { - margin-left: 8px; - padding: 0 14px; - background-color: var(--vscode-button-background); - color: var(--vscode-button-foreground); - border: none; - cursor: pointer; - border-radius: 2px; + margin-left: 8px; + padding: 0 14px; + background-color: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: none; + cursor: pointer; + border-radius: 2px; } #send-button:hover { - background-color: var(--vscode-button-hoverBackground); + background-color: var(--vscode-button-hoverBackground); } #loading { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.3); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.3); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; } .hidden { - display: none !important; + display: none !important; } .spinner { - width: 40px; - height: 40px; - border: 4px solid rgba(255, 255, 255, 0.3); - border-radius: 50%; - border-top-color: var(--vscode-button-background); - animation: spin 1s ease-in-out infinite; + width: 40px; + height: 40px; + border: 4px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + border-top-color: var(--vscode-button-background); + animation: spin 1s ease-in-out infinite; } @keyframes spin { - to { - transform: rotate(360deg); - } + to { + transform: rotate(360deg); + } } /* Markdown styling */ -.markdown h1, .markdown h2, .markdown h3, .markdown h4, .markdown h5, .markdown h6 { - margin-top: 16px; - margin-bottom: 8px; - font-weight: 600; +.markdown h1, +.markdown h2, +.markdown h3, +.markdown h4, +.markdown h5, +.markdown h6 { + margin-top: 16px; + margin-bottom: 8px; + font-weight: 600; } .markdown p { - margin-top: 0; - margin-bottom: 8px; + margin-top: 0; + margin-bottom: 8px; } -.markdown ul, .markdown ol { - padding-left: 20px; - margin-top: 0; - margin-bottom: 8px; +.markdown ul, +.markdown ol { + padding-left: 20px; + margin-top: 0; + margin-bottom: 8px; } .markdown code { - font-family: var(--vscode-editor-font-family); - background-color: var(--vscode-textCodeBlock-background); - padding: 2px 4px; - border-radius: 3px; + font-family: var(--vscode-editor-font-family); + background-color: var(--vscode-textCodeBlock-background); + padding: 2px 4px; + border-radius: 3px; } .markdown pre { - background-color: var(--vscode-textCodeBlock-background); - padding: 8px; - border-radius: 3px; - overflow-x: auto; - margin-top: 8px; - margin-bottom: 8px; + background-color: var(--vscode-textCodeBlock-background); + padding: 8px; + border-radius: 3px; + overflow-x: auto; + margin-top: 8px; + margin-bottom: 8px; } .markdown pre code { - background-color: transparent; - padding: 0; + background-color: transparent; + padding: 0; } .markdown a { - color: var(--vscode-textLink-foreground); - text-decoration: none; + color: var(--vscode-textLink-foreground); + text-decoration: none; } .markdown a:hover { - text-decoration: underline; + text-decoration: underline; } .markdown blockquote { - border-left: 3px solid var(--vscode-textBlockQuote-border); - padding-left: 8px; - margin-left: 0; - margin-right: 0; - color: var(--vscode-textBlockQuote-foreground); + border-left: 3px solid var(--vscode-textBlockQuote-border); + padding-left: 8px; + margin-left: 0; + margin-right: 0; + color: var(--vscode-textBlockQuote-foreground); } .markdown table { - border-collapse: collapse; - width: 100%; - margin-bottom: 16px; + border-collapse: collapse; + width: 100%; + margin-bottom: 16px; } -.markdown th, .markdown td { - border: 1px solid var(--vscode-editor-lineHighlightBorder); - padding: 6px 13px; +.markdown th, +.markdown td { + border: 1px solid var(--vscode-editor-lineHighlightBorder); + padding: 6px 13px; } .markdown th { - background-color: var(--vscode-editor-inactiveSelectionBackground); - font-weight: 600; + background-color: var(--vscode-editor-inactiveSelectionBackground); + font-weight: 600; } diff --git a/codegen-vscode-extension/media/main.js b/codegen-vscode-extension/media/main.js index 76183cebd..b0479a700 100644 --- a/codegen-vscode-extension/media/main.js +++ b/codegen-vscode-extension/media/main.js @@ -1,170 +1,173 @@ -(function() { - // Get VS Code API - const vscode = acquireVsCodeApi(); - - // DOM elements - const messagesContainer = document.getElementById('messages'); - const messageInput = document.getElementById('message-input'); - const sendButton = document.getElementById('send-button'); - const loadingElement = document.getElementById('loading'); - - // Store messages - let messages = []; - - // Initialize - window.addEventListener('message', event => { - const message = event.data; - - switch (message.command) { - case 'updateMessages': - messages = message.messages; - renderMessages(); - break; - case 'showLoading': - toggleLoading(message.value); - break; - } - }); - - // Send message when button is clicked - sendButton.addEventListener('click', () => { - sendMessage(); - }); - - // Send message when Enter is pressed (without Shift) - messageInput.addEventListener('keydown', (e) => { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault(); - sendMessage(); - } - }); - - // Send message to extension - function sendMessage() { - const text = messageInput.value.trim(); - if (text) { - vscode.postMessage({ - command: 'sendMessage', - text: text - }); - messageInput.value = ''; - } - } - - // Render all messages - function renderMessages() { - messagesContainer.innerHTML = ''; - - messages.forEach(message => { - const messageElement = document.createElement('div'); - messageElement.className = `message ${message.role}-message`; - - // Message content - const contentElement = document.createElement('div'); - contentElement.className = 'message-content markdown'; - contentElement.innerHTML = formatMarkdown(message.content); - messageElement.appendChild(contentElement); - - // Code block (if any) - if (message.code) { - const codeElement = document.createElement('div'); - codeElement.className = 'code-block'; - codeElement.textContent = message.code; - messageElement.appendChild(codeElement); - - // Code actions - const actionsElement = document.createElement('div'); - actionsElement.className = 'code-actions'; - - // Insert code button - const insertButton = document.createElement('button'); - insertButton.className = 'code-action-button'; - insertButton.textContent = 'Insert Code'; - insertButton.addEventListener('click', () => { - vscode.postMessage({ - command: 'insertCode', - code: message.code - }); - }); - actionsElement.appendChild(insertButton); - - // Copy code button - const copyButton = document.createElement('button'); - copyButton.className = 'code-action-button'; - copyButton.textContent = 'Copy'; - copyButton.addEventListener('click', () => { - navigator.clipboard.writeText(message.code); - copyButton.textContent = 'Copied!'; - setTimeout(() => { - copyButton.textContent = 'Copy'; - }, 2000); - }); - actionsElement.appendChild(copyButton); - - messageElement.appendChild(actionsElement); - } - - // Timestamp - const timestampElement = document.createElement('div'); - timestampElement.className = 'timestamp'; - timestampElement.textContent = formatTimestamp(message.timestamp); - messageElement.appendChild(timestampElement); - - messagesContainer.appendChild(messageElement); - }); - - // Scroll to bottom - messagesContainer.scrollTop = messagesContainer.scrollHeight; - } - - // Format timestamp - function formatTimestamp(timestamp) { - const date = new Date(timestamp); - return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - } - - // Toggle loading indicator - function toggleLoading(show) { - if (show) { - loadingElement.classList.remove('hidden'); - } else { - loadingElement.classList.add('hidden'); - } - } - - // Format markdown to HTML - function formatMarkdown(text) { - // This is a very simple markdown parser - // In a real extension, you might want to use a library like marked.js - - // Code blocks - text = text.replace(/```(\w*)\n([\s\S]*?)\n```/g, '
    $2
    '); - - // Inline code - text = text.replace(/`([^`]+)`/g, '$1'); - - // Headers - text = text.replace(/^### (.*$)/gm, '

    $1

    '); - text = text.replace(/^## (.*$)/gm, '

    $1

    '); - text = text.replace(/^# (.*$)/gm, '

    $1

    '); - - // Bold - text = text.replace(/\*\*(.*?)\*\*/g, '$1'); - - // Italic - text = text.replace(/\*(.*?)\*/g, '$1'); - - // Lists - text = text.replace(/^\s*\*\s(.*$)/gm, '
  • $1
  • '); - text = text.replace(/(
  • .*<\/li>)/g, ''); - - // Links - text = text.replace(/\[(.*?)\]\((.*?)\)/g, '$1'); - - // Paragraphs - text = text.replace(/\n\n/g, '

    '); - text = '

    ' + text + '

    '; - - return text; - } +(() => { + // Get VS Code API + const vscode = acquireVsCodeApi(); + + // DOM elements + const messagesContainer = document.getElementById("messages"); + const messageInput = document.getElementById("message-input"); + const sendButton = document.getElementById("send-button"); + const loadingElement = document.getElementById("loading"); + + // Store messages + let messages = []; + + // Initialize + window.addEventListener("message", (event) => { + const message = event.data; + + switch (message.command) { + case "updateMessages": + messages = message.messages; + renderMessages(); + break; + case "showLoading": + toggleLoading(message.value); + break; + } + }); + + // Send message when button is clicked + sendButton.addEventListener("click", () => { + sendMessage(); + }); + + // Send message when Enter is pressed (without Shift) + messageInput.addEventListener("keydown", (e) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + sendMessage(); + } + }); + + // Send message to extension + function sendMessage() { + const text = messageInput.value.trim(); + if (text) { + vscode.postMessage({ + command: "sendMessage", + text: text, + }); + messageInput.value = ""; + } + } + + // Render all messages + function renderMessages() { + messagesContainer.innerHTML = ""; + + messages.forEach((message) => { + const messageElement = document.createElement("div"); + messageElement.className = `message ${message.role}-message`; + + // Message content + const contentElement = document.createElement("div"); + contentElement.className = "message-content markdown"; + contentElement.innerHTML = formatMarkdown(message.content); + messageElement.appendChild(contentElement); + + // Code block (if any) + if (message.code) { + const codeElement = document.createElement("div"); + codeElement.className = "code-block"; + codeElement.textContent = message.code; + messageElement.appendChild(codeElement); + + // Code actions + const actionsElement = document.createElement("div"); + actionsElement.className = "code-actions"; + + // Insert code button + const insertButton = document.createElement("button"); + insertButton.className = "code-action-button"; + insertButton.textContent = "Insert Code"; + insertButton.addEventListener("click", () => { + vscode.postMessage({ + command: "insertCode", + code: message.code, + }); + }); + actionsElement.appendChild(insertButton); + + // Copy code button + const copyButton = document.createElement("button"); + copyButton.className = "code-action-button"; + copyButton.textContent = "Copy"; + copyButton.addEventListener("click", () => { + navigator.clipboard.writeText(message.code); + copyButton.textContent = "Copied!"; + setTimeout(() => { + copyButton.textContent = "Copy"; + }, 2000); + }); + actionsElement.appendChild(copyButton); + + messageElement.appendChild(actionsElement); + } + + // Timestamp + const timestampElement = document.createElement("div"); + timestampElement.className = "timestamp"; + timestampElement.textContent = formatTimestamp(message.timestamp); + messageElement.appendChild(timestampElement); + + messagesContainer.appendChild(messageElement); + }); + + // Scroll to bottom + messagesContainer.scrollTop = messagesContainer.scrollHeight; + } + + // Format timestamp + function formatTimestamp(timestamp) { + const date = new Date(timestamp); + return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); + } + + // Toggle loading indicator + function toggleLoading(show) { + if (show) { + loadingElement.classList.remove("hidden"); + } else { + loadingElement.classList.add("hidden"); + } + } + + // Format markdown to HTML + function formatMarkdown(text) { + // This is a very simple markdown parser + // In a real extension, you might want to use a library like marked.js + + // Code blocks + text = text.replace( + /```(\w*)\n([\s\S]*?)\n```/g, + "
    $2
    ", + ); + + // Inline code + text = text.replace(/`([^`]+)`/g, "$1"); + + // Headers + text = text.replace(/^### (.*$)/gm, "

    $1

    "); + text = text.replace(/^## (.*$)/gm, "

    $1

    "); + text = text.replace(/^# (.*$)/gm, "

    $1

    "); + + // Bold + text = text.replace(/\*\*(.*?)\*\*/g, "$1"); + + // Italic + text = text.replace(/\*(.*?)\*/g, "$1"); + + // Lists + text = text.replace(/^\s*\*\s(.*$)/gm, "
  • $1
  • "); + text = text.replace(/(
  • .*<\/li>)/g, ""); + + // Links + text = text.replace(/\[(.*?)\]\((.*?)\)/g, '$1'); + + // Paragraphs + text = text.replace(/\n\n/g, "

    "); + text = "

    " + text + "

    "; + + return text; + } })(); diff --git a/codegen-vscode-extension/package.json b/codegen-vscode-extension/package.json index f57e7e552..18475fd69 100644 --- a/codegen-vscode-extension/package.json +++ b/codegen-vscode-extension/package.json @@ -1,132 +1,126 @@ { - "name": "codegen-vscode-extension", - "displayName": "Codegen", - "description": "VSCode extension for Codegen - AI-powered code generation and assistance", - "version": "0.1.0", - "engines": { - "vscode": "^1.78.0" - }, - "categories": [ - "Programming Languages", - "Machine Learning", - "Other" - ], - "activationEvents": [ - "onStartupFinished" - ], - "main": "./dist/extension.js", - "contributes": { - "commands": [ - { - "command": "codegen.askQuestion", - "title": "Ask Codegen a Question", - "category": "Codegen" - }, - { - "command": "codegen.generateCode", - "title": "Generate Code with Codegen", - "category": "Codegen" - }, - { - "command": "codegen.explainCode", - "title": "Explain Selected Code", - "category": "Codegen" - }, - { - "command": "codegen.improveCode", - "title": "Improve Selected Code", - "category": "Codegen" - }, - { - "command": "codegen.fixCode", - "title": "Fix Selected Code", - "category": "Codegen" - } - ], - "viewsContainers": { - "activitybar": [ - { - "id": "codegen-sidebar", - "title": "Codegen", - "icon": "media/codegen-icon.svg" - } - ] - }, - "views": { - "codegen-sidebar": [ - { - "type": "webview", - "id": "codegen.chatView", - "name": "Codegen Chat" - } - ] - }, - "configuration": { - "title": "Codegen", - "properties": { - "codegen.apiKey": { - "type": "string", - "default": "", - "description": "API key for Codegen services" - }, - "codegen.endpoint": { - "type": "string", - "default": "https://api.codegen.sh", - "description": "Endpoint for Codegen API" - } - } - }, - "menus": { - "editor/context": [ - { - "command": "codegen.explainCode", - "when": "editorHasSelection", - "group": "codegen" - }, - { - "command": "codegen.improveCode", - "when": "editorHasSelection", - "group": "codegen" - }, - { - "command": "codegen.fixCode", - "when": "editorHasSelection", - "group": "codegen" - }, - { - "command": "codegen.generateCode", - "group": "codegen" - } - ] - } - }, - "scripts": { - "vscode:prepublish": "npm run package", - "compile": "webpack", - "watch": "webpack --watch", - "package": "webpack --mode production --devtool source-map --config ./webpack.config.js", - "compile-tests": "tsc -p . --outDir out", - "watch-tests": "tsc -p . -w --outDir out", - "lint": "eslint src --ext ts", - "test": "node ./out/test/runTest.js" - }, - "devDependencies": { - "@types/glob": "^8.1.0", - "@types/mocha": "^10.0.1", - "@types/node": "16.x", - "@types/vscode": "^1.78.0", - "@typescript-eslint/eslint-plugin": "^5.59.1", - "@typescript-eslint/parser": "^5.59.1", - "@vscode/test-electron": "^2.3.0", - "eslint": "^8.39.0", - "glob": "^8.1.0", - "mocha": "^10.2.0", - "ts-loader": "^9.4.2", - "typescript": "^5.0.4", - "webpack": "^5.81.0", - "webpack-cli": "^5.0.2" - }, - "dependencies": { - "axios": "^1.4.0" - } + "name": "codegen-vscode-extension", + "displayName": "Codegen", + "description": "VSCode extension for Codegen - AI-powered code generation and assistance", + "version": "0.1.0", + "engines": { + "vscode": "^1.78.0" + }, + "categories": ["Programming Languages", "Machine Learning", "Other"], + "activationEvents": ["onStartupFinished"], + "main": "./dist/extension.js", + "contributes": { + "commands": [ + { + "command": "codegen.askQuestion", + "title": "Ask Codegen a Question", + "category": "Codegen" + }, + { + "command": "codegen.generateCode", + "title": "Generate Code with Codegen", + "category": "Codegen" + }, + { + "command": "codegen.explainCode", + "title": "Explain Selected Code", + "category": "Codegen" + }, + { + "command": "codegen.improveCode", + "title": "Improve Selected Code", + "category": "Codegen" + }, + { + "command": "codegen.fixCode", + "title": "Fix Selected Code", + "category": "Codegen" + } + ], + "viewsContainers": { + "activitybar": [ + { + "id": "codegen-sidebar", + "title": "Codegen", + "icon": "media/codegen-icon.svg" + } + ] + }, + "views": { + "codegen-sidebar": [ + { + "type": "webview", + "id": "codegen.chatView", + "name": "Codegen Chat" + } + ] + }, + "configuration": { + "title": "Codegen", + "properties": { + "codegen.apiKey": { + "type": "string", + "default": "", + "description": "API key for Codegen services" + }, + "codegen.endpoint": { + "type": "string", + "default": "https://api.codegen.sh", + "description": "Endpoint for Codegen API" + } + } + }, + "menus": { + "editor/context": [ + { + "command": "codegen.explainCode", + "when": "editorHasSelection", + "group": "codegen" + }, + { + "command": "codegen.improveCode", + "when": "editorHasSelection", + "group": "codegen" + }, + { + "command": "codegen.fixCode", + "when": "editorHasSelection", + "group": "codegen" + }, + { + "command": "codegen.generateCode", + "group": "codegen" + } + ] + } + }, + "scripts": { + "vscode:prepublish": "npm run package", + "compile": "webpack", + "watch": "webpack --watch", + "package": "webpack --mode production --devtool source-map --config ./webpack.config.js", + "compile-tests": "tsc -p . --outDir out", + "watch-tests": "tsc -p . -w --outDir out", + "lint": "eslint src --ext ts", + "test": "node ./out/test/runTest.js" + }, + "devDependencies": { + "@types/glob": "^8.1.0", + "@types/mocha": "^10.0.1", + "@types/node": "16.x", + "@types/vscode": "^1.78.0", + "@typescript-eslint/eslint-plugin": "^5.59.1", + "@typescript-eslint/parser": "^5.59.1", + "@vscode/test-electron": "^2.3.0", + "eslint": "^8.39.0", + "glob": "^8.1.0", + "mocha": "^10.2.0", + "ts-loader": "^9.4.2", + "typescript": "^5.0.4", + "webpack": "^5.81.0", + "webpack-cli": "^5.0.2" + }, + "dependencies": { + "axios": "^1.4.0" + } } diff --git a/codegen-vscode-extension/src/api/codegenAPI.ts b/codegen-vscode-extension/src/api/codegenAPI.ts index 9a98c6185..f201c20e0 100644 --- a/codegen-vscode-extension/src/api/codegenAPI.ts +++ b/codegen-vscode-extension/src/api/codegenAPI.ts @@ -1,136 +1,155 @@ -import axios, { AxiosInstance } from 'axios'; -import * as vscode from 'vscode'; +import axios, { type AxiosInstance } from "axios"; +import * as vscode from "vscode"; export interface CodegenResponse { - text: string; - code?: string; - language?: string; + text: string; + code?: string; + language?: string; } export class CodegenAPI { - private client: AxiosInstance; - private apiKey: string = ''; - private endpoint: string = ''; - - constructor() { - this.loadConfiguration(); - - this.client = axios.create({ - baseURL: this.endpoint, - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.apiKey}` - } - }); - - // Listen for configuration changes - vscode.workspace.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('codegen.apiKey') || e.affectsConfiguration('codegen.endpoint')) { - this.loadConfiguration(); - this.updateClient(); - } - }); - } - - private loadConfiguration() { - const config = vscode.workspace.getConfiguration('codegen'); - this.apiKey = config.get('apiKey') || ''; - this.endpoint = config.get('endpoint') || 'https://api.codegen.sh'; - } - - private updateClient() { - this.client = axios.create({ - baseURL: this.endpoint, - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.apiKey}` - } - }); - } - - public async validateApiKey(): Promise { - if (!this.apiKey) { - return false; - } - - try { - // Make a simple request to validate the API key - await this.client.get('/api/validate'); - return true; - } catch (error) { - console.error('API key validation failed:', error); - return false; - } - } - - public async askQuestion(question: string, context?: string): Promise { - try { - const response = await this.client.post('/api/ask', { - question, - context - }); - - return response.data; - } catch (error) { - console.error('Error asking question:', error); - throw new Error('Failed to get response from Codegen API'); - } - } - - public async generateCode(prompt: string, language?: string): Promise { - try { - const response = await this.client.post('/api/generate', { - prompt, - language - }); - - return response.data; - } catch (error) { - console.error('Error generating code:', error); - throw new Error('Failed to generate code from Codegen API'); - } - } - - public async explainCode(code: string, language?: string): Promise { - try { - const response = await this.client.post('/api/explain', { - code, - language - }); - - return response.data; - } catch (error) { - console.error('Error explaining code:', error); - throw new Error('Failed to explain code from Codegen API'); - } - } - - public async improveCode(code: string, language?: string): Promise { - try { - const response = await this.client.post('/api/improve', { - code, - language - }); - - return response.data; - } catch (error) { - console.error('Error improving code:', error); - throw new Error('Failed to improve code from Codegen API'); - } - } - - public async fixCode(code: string, error?: string, language?: string): Promise { - try { - const response = await this.client.post('/api/fix', { - code, - error, - language - }); - - return response.data; - } catch (error) { - console.error('Error fixing code:', error); - throw new Error('Failed to fix code from Codegen API'); - } - } + private client: AxiosInstance; + private apiKey = ""; + private endpoint = ""; + + constructor() { + this.loadConfiguration(); + + this.client = axios.create({ + baseURL: this.endpoint, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.apiKey}`, + }, + }); + + // Listen for configuration changes + vscode.workspace.onDidChangeConfiguration((e) => { + if ( + e.affectsConfiguration("codegen.apiKey") || + e.affectsConfiguration("codegen.endpoint") + ) { + this.loadConfiguration(); + this.updateClient(); + } + }); + } + + private loadConfiguration() { + const config = vscode.workspace.getConfiguration("codegen"); + this.apiKey = config.get("apiKey") || ""; + this.endpoint = config.get("endpoint") || "https://api.codegen.sh"; + } + + private updateClient() { + this.client = axios.create({ + baseURL: this.endpoint, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.apiKey}`, + }, + }); + } + + public async validateApiKey(): Promise { + if (!this.apiKey) { + return false; + } + + try { + // Make a simple request to validate the API key + await this.client.get("/api/validate"); + return true; + } catch (error) { + console.error("API key validation failed:", error); + return false; + } + } + + public async askQuestion( + question: string, + context?: string, + ): Promise { + try { + const response = await this.client.post("/api/ask", { + question, + context, + }); + + return response.data; + } catch (error) { + console.error("Error asking question:", error); + throw new Error("Failed to get response from Codegen API"); + } + } + + public async generateCode( + prompt: string, + language?: string, + ): Promise { + try { + const response = await this.client.post("/api/generate", { + prompt, + language, + }); + + return response.data; + } catch (error) { + console.error("Error generating code:", error); + throw new Error("Failed to generate code from Codegen API"); + } + } + + public async explainCode( + code: string, + language?: string, + ): Promise { + try { + const response = await this.client.post("/api/explain", { + code, + language, + }); + + return response.data; + } catch (error) { + console.error("Error explaining code:", error); + throw new Error("Failed to explain code from Codegen API"); + } + } + + public async improveCode( + code: string, + language?: string, + ): Promise { + try { + const response = await this.client.post("/api/improve", { + code, + language, + }); + + return response.data; + } catch (error) { + console.error("Error improving code:", error); + throw new Error("Failed to improve code from Codegen API"); + } + } + + public async fixCode( + code: string, + error?: string, + language?: string, + ): Promise { + try { + const response = await this.client.post("/api/fix", { + code, + error, + language, + }); + + return response.data; + } catch (error) { + console.error("Error fixing code:", error); + throw new Error("Failed to fix code from Codegen API"); + } + } } diff --git a/codegen-vscode-extension/src/commands.ts b/codegen-vscode-extension/src/commands.ts index 7a7c6682a..e605d4e0e 100644 --- a/codegen-vscode-extension/src/commands.ts +++ b/codegen-vscode-extension/src/commands.ts @@ -1,287 +1,328 @@ -import * as vscode from 'vscode'; -import { CodegenAPI } from './api/codegenAPI'; -import { ChatViewProvider } from './providers/chatViewProvider'; -import { getSelectedText, getDocumentLanguage, insertText } from './utils'; +import * as vscode from "vscode"; +import type { CodegenAPI } from "./api/codegenAPI"; +import type { ChatViewProvider } from "./providers/chatViewProvider"; +import { getDocumentLanguage, getSelectedText, insertText } from "./utils"; export function registerCommands( - context: vscode.ExtensionContext, - api: CodegenAPI, - chatViewProvider: ChatViewProvider + context: vscode.ExtensionContext, + api: CodegenAPI, + chatViewProvider: ChatViewProvider, ) { - // Ask a question - context.subscriptions.push( - vscode.commands.registerCommand('codegen.askQuestion', async () => { - const question = await vscode.window.showInputBox({ - prompt: 'What would you like to ask Codegen?', - placeHolder: 'Ask a programming question...' - }); - - if (!question) { - return; - } - - try { - vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - title: 'Asking Codegen...', - cancellable: false - }, async (progress) => { - progress.report({ increment: 0 }); - - const response = await api.askQuestion(question); - - progress.report({ increment: 100 }); - - // Send to chat view - chatViewProvider.addMessage('user', question); - chatViewProvider.addMessage('assistant', response.text); - - // Show the chat view - vscode.commands.executeCommand('codegen.chatView.focus'); - - return response; - }); - } catch (error) { - vscode.window.showErrorMessage(`Error: ${error.message}`); - } - }) - ); - - // Generate code - context.subscriptions.push( - vscode.commands.registerCommand('codegen.generateCode', async () => { - const prompt = await vscode.window.showInputBox({ - prompt: 'Describe the code you want to generate', - placeHolder: 'Generate a function that...' - }); - - if (!prompt) { - return; - } - - const editor = vscode.window.activeTextEditor; - const language = editor ? getDocumentLanguage(editor.document) : undefined; - - try { - vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - title: 'Generating code...', - cancellable: false - }, async (progress) => { - progress.report({ increment: 0 }); - - const response = await api.generateCode(prompt, language); - - progress.report({ increment: 100 }); - - if (editor && response.code) { - // Insert the generated code at the cursor position - insertText(editor, response.code); - } - - // Send to chat view - chatViewProvider.addMessage('user', `Generate code: ${prompt}`); - chatViewProvider.addMessage('assistant', response.text, response.code); - - return response; - }); - } catch (error) { - vscode.window.showErrorMessage(`Error: ${error.message}`); - } - }) - ); - - // Explain code - context.subscriptions.push( - vscode.commands.registerCommand('codegen.explainCode', async () => { - const editor = vscode.window.activeTextEditor; - if (!editor) { - vscode.window.showErrorMessage('No active editor'); - return; - } - - const selectedText = getSelectedText(editor); - if (!selectedText) { - vscode.window.showErrorMessage('No code selected'); - return; - } - - const language = getDocumentLanguage(editor.document); - - try { - vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - title: 'Explaining code...', - cancellable: false - }, async (progress) => { - progress.report({ increment: 0 }); - - const response = await api.explainCode(selectedText, language); - - progress.report({ increment: 100 }); - - // Send to chat view - chatViewProvider.addMessage('user', `Explain this code:\n\`\`\`${language}\n${selectedText}\n\`\`\``); - chatViewProvider.addMessage('assistant', response.text); - - // Show the chat view - vscode.commands.executeCommand('codegen.chatView.focus'); - - return response; - }); - } catch (error) { - vscode.window.showErrorMessage(`Error: ${error.message}`); - } - }) - ); - - // Improve code - context.subscriptions.push( - vscode.commands.registerCommand('codegen.improveCode', async () => { - const editor = vscode.window.activeTextEditor; - if (!editor) { - vscode.window.showErrorMessage('No active editor'); - return; - } - - const selectedText = getSelectedText(editor); - if (!selectedText) { - vscode.window.showErrorMessage('No code selected'); - return; - } - - const language = getDocumentLanguage(editor.document); - - try { - vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - title: 'Improving code...', - cancellable: false - }, async (progress) => { - progress.report({ increment: 0 }); - - const response = await api.improveCode(selectedText, language); - - progress.report({ increment: 100 }); - - if (response.code) { - // Show diff and ask if user wants to apply changes - const document = editor.document; - const selection = editor.selection; - - const diffEditor = await vscode.diff.createTextDocumentAndEditorEdit( - document, - selection, - response.code - ); - - // Add buttons to apply or discard changes - const applyChanges = 'Apply Changes'; - const discardChanges = 'Discard'; - - const choice = await vscode.window.showInformationMessage( - 'Review the suggested improvements', - applyChanges, - discardChanges - ); - - if (choice === applyChanges) { - // Replace the selected text with the improved code - editor.edit(editBuilder => { - editBuilder.replace(selection, response.code || ''); - }); - } - } - - // Send to chat view - chatViewProvider.addMessage('user', `Improve this code:\n\`\`\`${language}\n${selectedText}\n\`\`\``); - chatViewProvider.addMessage('assistant', response.text, response.code); - - return response; - }); - } catch (error) { - vscode.window.showErrorMessage(`Error: ${error.message}`); - } - }) - ); - - // Fix code - context.subscriptions.push( - vscode.commands.registerCommand('codegen.fixCode', async () => { - const editor = vscode.window.activeTextEditor; - if (!editor) { - vscode.window.showErrorMessage('No active editor'); - return; - } - - const selectedText = getSelectedText(editor); - if (!selectedText) { - vscode.window.showErrorMessage('No code selected'); - return; - } - - const language = getDocumentLanguage(editor.document); - - // Ask for error message - const errorMessage = await vscode.window.showInputBox({ - prompt: 'What error are you getting? (optional)', - placeHolder: 'Error message or description of the issue' - }); - - try { - vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - title: 'Fixing code...', - cancellable: false - }, async (progress) => { - progress.report({ increment: 0 }); - - const response = await api.fixCode(selectedText, errorMessage, language); - - progress.report({ increment: 100 }); - - if (response.code) { - // Show diff and ask if user wants to apply changes - const document = editor.document; - const selection = editor.selection; - - const diffEditor = await vscode.diff.createTextDocumentAndEditorEdit( - document, - selection, - response.code - ); - - // Add buttons to apply or discard changes - const applyChanges = 'Apply Changes'; - const discardChanges = 'Discard'; - - const choice = await vscode.window.showInformationMessage( - 'Review the suggested fixes', - applyChanges, - discardChanges - ); - - if (choice === applyChanges) { - // Replace the selected text with the fixed code - editor.edit(editBuilder => { - editBuilder.replace(selection, response.code || ''); - }); - } - } - - // Send to chat view - const userMessage = errorMessage - ? `Fix this code (error: ${errorMessage}):\n\`\`\`${language}\n${selectedText}\n\`\`\`` - : `Fix this code:\n\`\`\`${language}\n${selectedText}\n\`\`\``; - - chatViewProvider.addMessage('user', userMessage); - chatViewProvider.addMessage('assistant', response.text, response.code); - - return response; - }); - } catch (error) { - vscode.window.showErrorMessage(`Error: ${error.message}`); - } - }) - ); + // Ask a question + context.subscriptions.push( + vscode.commands.registerCommand("codegen.askQuestion", async () => { + const question = await vscode.window.showInputBox({ + prompt: "What would you like to ask Codegen?", + placeHolder: "Ask a programming question...", + }); + + if (!question) { + return; + } + + try { + vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: "Asking Codegen...", + cancellable: false, + }, + async (progress) => { + progress.report({ increment: 0 }); + + const response = await api.askQuestion(question); + + progress.report({ increment: 100 }); + + // Send to chat view + chatViewProvider.addMessage("user", question); + chatViewProvider.addMessage("assistant", response.text); + + // Show the chat view + vscode.commands.executeCommand("codegen.chatView.focus"); + + return response; + }, + ); + } catch (error) { + vscode.window.showErrorMessage(`Error: ${error.message}`); + } + }), + ); + + // Generate code + context.subscriptions.push( + vscode.commands.registerCommand("codegen.generateCode", async () => { + const prompt = await vscode.window.showInputBox({ + prompt: "Describe the code you want to generate", + placeHolder: "Generate a function that...", + }); + + if (!prompt) { + return; + } + + const editor = vscode.window.activeTextEditor; + const language = editor + ? getDocumentLanguage(editor.document) + : undefined; + + try { + vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: "Generating code...", + cancellable: false, + }, + async (progress) => { + progress.report({ increment: 0 }); + + const response = await api.generateCode(prompt, language); + + progress.report({ increment: 100 }); + + if (editor && response.code) { + // Insert the generated code at the cursor position + insertText(editor, response.code); + } + + // Send to chat view + chatViewProvider.addMessage("user", `Generate code: ${prompt}`); + chatViewProvider.addMessage( + "assistant", + response.text, + response.code, + ); + + return response; + }, + ); + } catch (error) { + vscode.window.showErrorMessage(`Error: ${error.message}`); + } + }), + ); + + // Explain code + context.subscriptions.push( + vscode.commands.registerCommand("codegen.explainCode", async () => { + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showErrorMessage("No active editor"); + return; + } + + const selectedText = getSelectedText(editor); + if (!selectedText) { + vscode.window.showErrorMessage("No code selected"); + return; + } + + const language = getDocumentLanguage(editor.document); + + try { + vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: "Explaining code...", + cancellable: false, + }, + async (progress) => { + progress.report({ increment: 0 }); + + const response = await api.explainCode(selectedText, language); + + progress.report({ increment: 100 }); + + // Send to chat view + chatViewProvider.addMessage( + "user", + `Explain this code:\n\`\`\`${language}\n${selectedText}\n\`\`\``, + ); + chatViewProvider.addMessage("assistant", response.text); + + // Show the chat view + vscode.commands.executeCommand("codegen.chatView.focus"); + + return response; + }, + ); + } catch (error) { + vscode.window.showErrorMessage(`Error: ${error.message}`); + } + }), + ); + + // Improve code + context.subscriptions.push( + vscode.commands.registerCommand("codegen.improveCode", async () => { + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showErrorMessage("No active editor"); + return; + } + + const selectedText = getSelectedText(editor); + if (!selectedText) { + vscode.window.showErrorMessage("No code selected"); + return; + } + + const language = getDocumentLanguage(editor.document); + + try { + vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: "Improving code...", + cancellable: false, + }, + async (progress) => { + progress.report({ increment: 0 }); + + const response = await api.improveCode(selectedText, language); + + progress.report({ increment: 100 }); + + if (response.code) { + // Show diff and ask if user wants to apply changes + const document = editor.document; + const selection = editor.selection; + + const diffEditor = + await vscode.diff.createTextDocumentAndEditorEdit( + document, + selection, + response.code, + ); + + // Add buttons to apply or discard changes + const applyChanges = "Apply Changes"; + const discardChanges = "Discard"; + + const choice = await vscode.window.showInformationMessage( + "Review the suggested improvements", + applyChanges, + discardChanges, + ); + + if (choice === applyChanges) { + // Replace the selected text with the improved code + editor.edit((editBuilder) => { + editBuilder.replace(selection, response.code || ""); + }); + } + } + + // Send to chat view + chatViewProvider.addMessage( + "user", + `Improve this code:\n\`\`\`${language}\n${selectedText}\n\`\`\``, + ); + chatViewProvider.addMessage( + "assistant", + response.text, + response.code, + ); + + return response; + }, + ); + } catch (error) { + vscode.window.showErrorMessage(`Error: ${error.message}`); + } + }), + ); + + // Fix code + context.subscriptions.push( + vscode.commands.registerCommand("codegen.fixCode", async () => { + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showErrorMessage("No active editor"); + return; + } + + const selectedText = getSelectedText(editor); + if (!selectedText) { + vscode.window.showErrorMessage("No code selected"); + return; + } + + const language = getDocumentLanguage(editor.document); + + // Ask for error message + const errorMessage = await vscode.window.showInputBox({ + prompt: "What error are you getting? (optional)", + placeHolder: "Error message or description of the issue", + }); + + try { + vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: "Fixing code...", + cancellable: false, + }, + async (progress) => { + progress.report({ increment: 0 }); + + const response = await api.fixCode( + selectedText, + errorMessage, + language, + ); + + progress.report({ increment: 100 }); + + if (response.code) { + // Show diff and ask if user wants to apply changes + const document = editor.document; + const selection = editor.selection; + + const diffEditor = + await vscode.diff.createTextDocumentAndEditorEdit( + document, + selection, + response.code, + ); + + // Add buttons to apply or discard changes + const applyChanges = "Apply Changes"; + const discardChanges = "Discard"; + + const choice = await vscode.window.showInformationMessage( + "Review the suggested fixes", + applyChanges, + discardChanges, + ); + + if (choice === applyChanges) { + // Replace the selected text with the fixed code + editor.edit((editBuilder) => { + editBuilder.replace(selection, response.code || ""); + }); + } + } + + // Send to chat view + const userMessage = errorMessage + ? `Fix this code (error: ${errorMessage}):\n\`\`\`${language}\n${selectedText}\n\`\`\`` + : `Fix this code:\n\`\`\`${language}\n${selectedText}\n\`\`\``; + + chatViewProvider.addMessage("user", userMessage); + chatViewProvider.addMessage( + "assistant", + response.text, + response.code, + ); + + return response; + }, + ); + } catch (error) { + vscode.window.showErrorMessage(`Error: ${error.message}`); + } + }), + ); } diff --git a/codegen-vscode-extension/src/extension.ts b/codegen-vscode-extension/src/extension.ts index 101ad8344..fa01b5d29 100644 --- a/codegen-vscode-extension/src/extension.ts +++ b/codegen-vscode-extension/src/extension.ts @@ -1,36 +1,39 @@ -import * as vscode from 'vscode'; -import { ChatViewProvider } from './providers/chatViewProvider'; -import { CodegenAPI } from './api/codegenAPI'; -import { registerCommands } from './commands'; +import * as vscode from "vscode"; +import { CodegenAPI } from "./api/codegenAPI"; +import { registerCommands } from "./commands"; +import { ChatViewProvider } from "./providers/chatViewProvider"; export function activate(context: vscode.ExtensionContext) { - console.log('Codegen extension is now active!'); + console.log("Codegen extension is now active!"); - // Initialize the API client - const api = new CodegenAPI(); - - // Register the chat view provider - const chatViewProvider = new ChatViewProvider(context.extensionUri, api); - context.subscriptions.push( - vscode.window.registerWebviewViewProvider( - 'codegen.chatView', - chatViewProvider, - { webviewOptions: { retainContextWhenHidden: true } } - ) - ); + // Initialize the API client + const api = new CodegenAPI(); - // Register commands - registerCommands(context, api, chatViewProvider); + // Register the chat view provider + const chatViewProvider = new ChatViewProvider(context.extensionUri, api); + context.subscriptions.push( + vscode.window.registerWebviewViewProvider( + "codegen.chatView", + chatViewProvider, + { webviewOptions: { retainContextWhenHidden: true } }, + ), + ); - // Status bar item - const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100); - statusBarItem.text = '$(sparkle) Codegen'; - statusBarItem.tooltip = 'Codegen AI Assistant'; - statusBarItem.command = 'codegen.askQuestion'; - statusBarItem.show(); - context.subscriptions.push(statusBarItem); + // Register commands + registerCommands(context, api, chatViewProvider); + + // Status bar item + const statusBarItem = vscode.window.createStatusBarItem( + vscode.StatusBarAlignment.Right, + 100, + ); + statusBarItem.text = "$(sparkle) Codegen"; + statusBarItem.tooltip = "Codegen AI Assistant"; + statusBarItem.command = "codegen.askQuestion"; + statusBarItem.show(); + context.subscriptions.push(statusBarItem); } export function deactivate() { - // Clean up resources + // Clean up resources } diff --git a/codegen-vscode-extension/src/providers/chatViewProvider.ts b/codegen-vscode-extension/src/providers/chatViewProvider.ts index 3ad0e5f91..d10774f39 100644 --- a/codegen-vscode-extension/src/providers/chatViewProvider.ts +++ b/codegen-vscode-extension/src/providers/chatViewProvider.ts @@ -1,124 +1,128 @@ -import * as vscode from 'vscode'; -import { CodegenAPI } from '../api/codegenAPI'; +import * as vscode from "vscode"; +import type { CodegenAPI } from "../api/codegenAPI"; interface ChatMessage { - role: 'user' | 'assistant'; - content: string; - code?: string; - timestamp: number; + role: "user" | "assistant"; + content: string; + code?: string; + timestamp: number; } export class ChatViewProvider implements vscode.WebviewViewProvider { - public static readonly viewType = 'codegen.chatView'; - private _view?: vscode.WebviewView; - private _messages: ChatMessage[] = []; - - constructor( - private readonly _extensionUri: vscode.Uri, - private readonly _api: CodegenAPI - ) {} - - public resolveWebviewView( - webviewView: vscode.WebviewView, - context: vscode.WebviewViewResolveContext, - _token: vscode.CancellationToken - ) { - this._view = webviewView; - - webviewView.webview.options = { - enableScripts: true, - localResourceRoots: [this._extensionUri] - }; - - webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); - - // Handle messages from the webview - webviewView.webview.onDidReceiveMessage(async (message) => { - switch (message.command) { - case 'sendMessage': - await this._handleUserMessage(message.text); - break; - case 'clearChat': - this._messages = []; - this._updateWebview(); - break; - case 'insertCode': - this._insertCodeToEditor(message.code); - break; - } - }); - - // Update the webview with existing messages - this._updateWebview(); - } - - public addMessage(role: 'user' | 'assistant', content: string, code?: string) { - this._messages.push({ - role, - content, - code, - timestamp: Date.now() - }); - - this._updateWebview(); - } - - private async _handleUserMessage(text: string) { - // Add user message to chat - this.addMessage('user', text); - - try { - // Show loading indicator - this._view?.webview.postMessage({ command: 'showLoading', value: true }); - - // Get response from API - const response = await this._api.askQuestion(text); - - // Add assistant message to chat - this.addMessage('assistant', response.text, response.code); - - // Hide loading indicator - this._view?.webview.postMessage({ command: 'showLoading', value: false }); - } catch (error) { - // Hide loading indicator - this._view?.webview.postMessage({ command: 'showLoading', value: false }); - - // Show error message - this.addMessage('assistant', `Error: ${error.message}`); - } - } - - private _updateWebview() { - if (this._view) { - this._view.webview.postMessage({ - command: 'updateMessages', - messages: this._messages - }); - } - } - - private _insertCodeToEditor(code: string) { - const editor = vscode.window.activeTextEditor; - if (editor) { - editor.edit(editBuilder => { - editBuilder.insert(editor.selection.active, code); - }); - } - } - - private _getHtmlForWebview(webview: vscode.Webview) { - // Create URIs for scripts and styles - const scriptUri = webview.asWebviewUri( - vscode.Uri.joinPath(this._extensionUri, 'media', 'main.js') - ); - const styleUri = webview.asWebviewUri( - vscode.Uri.joinPath(this._extensionUri, 'media', 'main.css') - ); - - // Use a nonce to allow only specific scripts to be run - const nonce = this._getNonce(); - - return ` + public static readonly viewType = "codegen.chatView"; + private _view?: vscode.WebviewView; + private _messages: ChatMessage[] = []; + + constructor( + private readonly _extensionUri: vscode.Uri, + private readonly _api: CodegenAPI, + ) {} + + public resolveWebviewView( + webviewView: vscode.WebviewView, + context: vscode.WebviewViewResolveContext, + _token: vscode.CancellationToken, + ) { + this._view = webviewView; + + webviewView.webview.options = { + enableScripts: true, + localResourceRoots: [this._extensionUri], + }; + + webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); + + // Handle messages from the webview + webviewView.webview.onDidReceiveMessage(async (message) => { + switch (message.command) { + case "sendMessage": + await this._handleUserMessage(message.text); + break; + case "clearChat": + this._messages = []; + this._updateWebview(); + break; + case "insertCode": + this._insertCodeToEditor(message.code); + break; + } + }); + + // Update the webview with existing messages + this._updateWebview(); + } + + public addMessage( + role: "user" | "assistant", + content: string, + code?: string, + ) { + this._messages.push({ + role, + content, + code, + timestamp: Date.now(), + }); + + this._updateWebview(); + } + + private async _handleUserMessage(text: string) { + // Add user message to chat + this.addMessage("user", text); + + try { + // Show loading indicator + this._view?.webview.postMessage({ command: "showLoading", value: true }); + + // Get response from API + const response = await this._api.askQuestion(text); + + // Add assistant message to chat + this.addMessage("assistant", response.text, response.code); + + // Hide loading indicator + this._view?.webview.postMessage({ command: "showLoading", value: false }); + } catch (error) { + // Hide loading indicator + this._view?.webview.postMessage({ command: "showLoading", value: false }); + + // Show error message + this.addMessage("assistant", `Error: ${error.message}`); + } + } + + private _updateWebview() { + if (this._view) { + this._view.webview.postMessage({ + command: "updateMessages", + messages: this._messages, + }); + } + } + + private _insertCodeToEditor(code: string) { + const editor = vscode.window.activeTextEditor; + if (editor) { + editor.edit((editBuilder) => { + editBuilder.insert(editor.selection.active, code); + }); + } + } + + private _getHtmlForWebview(webview: vscode.Webview) { + // Create URIs for scripts and styles + const scriptUri = webview.asWebviewUri( + vscode.Uri.joinPath(this._extensionUri, "media", "main.js"), + ); + const styleUri = webview.asWebviewUri( + vscode.Uri.joinPath(this._extensionUri, "media", "main.css"), + ); + + // Use a nonce to allow only specific scripts to be run + const nonce = this._getNonce(); + + return ` @@ -141,14 +145,15 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { `; - } - - private _getNonce() { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 32; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; - } + } + + private _getNonce() { + let text = ""; + const possible = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; + } } diff --git a/codegen-vscode-extension/src/utils.ts b/codegen-vscode-extension/src/utils.ts index f802e69b0..f0478eba0 100644 --- a/codegen-vscode-extension/src/utils.ts +++ b/codegen-vscode-extension/src/utils.ts @@ -1,102 +1,110 @@ -import * as vscode from 'vscode'; +import * as vscode from "vscode"; /** * Get the selected text from the editor */ export function getSelectedText(editor: vscode.TextEditor): string { - const selection = editor.selection; - if (selection.isEmpty) { - return ''; - } - return editor.document.getText(selection); + const selection = editor.selection; + if (selection.isEmpty) { + return ""; + } + return editor.document.getText(selection); } /** * Get the language of the document */ export function getDocumentLanguage(document: vscode.TextDocument): string { - return document.languageId; + return document.languageId; } /** * Insert text at the current cursor position */ export function insertText(editor: vscode.TextEditor, text: string): void { - const selection = editor.selection; - editor.edit(editBuilder => { - editBuilder.insert(selection.active, text); - }); + const selection = editor.selection; + editor.edit((editBuilder) => { + editBuilder.insert(selection.active, text); + }); } /** * Get the current file path */ export function getCurrentFilePath(): string | undefined { - const editor = vscode.window.activeTextEditor; - if (!editor) { - return undefined; - } - return editor.document.uri.fsPath; + const editor = vscode.window.activeTextEditor; + if (!editor) { + return undefined; + } + return editor.document.uri.fsPath; } /** * Get the current workspace folder */ -export function getCurrentWorkspaceFolder(): vscode.WorkspaceFolder | undefined { - const editor = vscode.window.activeTextEditor; - if (!editor) { - return undefined; - } - return vscode.workspace.getWorkspaceFolder(editor.document.uri); +export function getCurrentWorkspaceFolder(): + | vscode.WorkspaceFolder + | undefined { + const editor = vscode.window.activeTextEditor; + if (!editor) { + return undefined; + } + return vscode.workspace.getWorkspaceFolder(editor.document.uri); } /** * Get the current project name */ export function getCurrentProjectName(): string | undefined { - const workspaceFolder = getCurrentWorkspaceFolder(); - if (!workspaceFolder) { - return undefined; - } - return workspaceFolder.name; + const workspaceFolder = getCurrentWorkspaceFolder(); + if (!workspaceFolder) { + return undefined; + } + return workspaceFolder.name; } /** * Get the current file name */ export function getCurrentFileName(): string | undefined { - const editor = vscode.window.activeTextEditor; - if (!editor) { - return undefined; - } - return editor.document.fileName.split(/[\\/]/).pop(); + const editor = vscode.window.activeTextEditor; + if (!editor) { + return undefined; + } + return editor.document.fileName.split(/[\\/]/).pop(); } /** * Get the current line of code */ export function getCurrentLine(editor: vscode.TextEditor): string { - const position = editor.selection.active; - const line = editor.document.lineAt(position.line); - return line.text; + const position = editor.selection.active; + const line = editor.document.lineAt(position.line); + return line.text; } /** * Get the current function or class * This is a simple implementation and might not work for all languages */ -export function getCurrentFunction(editor: vscode.TextEditor): string | undefined { - const document = editor.document; - const position = editor.selection.active; - - // Simple implementation - search backwards for function or class definition - for (let i = position.line; i >= 0; i--) { - const line = document.lineAt(i).text.trim(); - if (line.startsWith('function ') || line.startsWith('class ') || - line.startsWith('def ') || line.match(/^[a-zA-Z0-9_]+\s*\([^)]*\)\s*{/)) { - return line; - } - } - - return undefined; +export function getCurrentFunction( + editor: vscode.TextEditor, +): string | undefined { + const document = editor.document; + const position = editor.selection.active; + + // Simple implementation - search backwards for function or class definition + for (let i = position.line; i >= 0; i--) { + const line = document.lineAt(i).text.trim(); + if ( + line.startsWith("function ") || + line.startsWith("class ") || + line.startsWith("def ") || + line.match(/^[a-zA-Z0-9_]+\s*\([^)]*\)\s*{/) + ) { + return line; + } + } + + return undefined; } diff --git a/codegen-vscode-extension/tsconfig.json b/codegen-vscode-extension/tsconfig.json index 09724c930..0749ae80f 100644 --- a/codegen-vscode-extension/tsconfig.json +++ b/codegen-vscode-extension/tsconfig.json @@ -1,15 +1,15 @@ { - "compilerOptions": { - "module": "commonjs", - "target": "ES2020", - "outDir": "out", - "lib": ["ES2020", "DOM"], - "sourceMap": true, - "rootDir": "src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true - }, - "exclude": ["node_modules", ".vscode-test"] + "compilerOptions": { + "module": "commonjs", + "target": "ES2020", + "outDir": "out", + "lib": ["ES2020", "DOM"], + "sourceMap": true, + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "exclude": ["node_modules", ".vscode-test"] } diff --git a/codegen-vscode-extension/webpack.config.js b/codegen-vscode-extension/webpack.config.js index d2df6e2d3..8fbf295ab 100644 --- a/codegen-vscode-extension/webpack.config.js +++ b/codegen-vscode-extension/webpack.config.js @@ -1,34 +1,34 @@ -const path = require('path'); +const path = require("path"); const config = { - target: 'node', - entry: './src/extension.ts', - output: { - path: path.resolve(__dirname, 'dist'), - filename: 'extension.js', - libraryTarget: 'commonjs2', - devtoolModuleFilenameTemplate: '../[resource-path]' - }, - devtool: 'source-map', - externals: { - vscode: 'commonjs vscode' - }, - resolve: { - extensions: ['.ts', '.js'] - }, - module: { - rules: [ - { - test: /\.ts$/, - exclude: /node_modules/, - use: [ - { - loader: 'ts-loader' - } - ] - } - ] - } + target: "node", + entry: "./src/extension.ts", + output: { + path: path.resolve(__dirname, "dist"), + filename: "extension.js", + libraryTarget: "commonjs2", + devtoolModuleFilenameTemplate: "../[resource-path]", + }, + devtool: "source-map", + externals: { + vscode: "commonjs vscode", + }, + resolve: { + extensions: [".ts", ".js"], + }, + module: { + rules: [ + { + test: /\.ts$/, + exclude: /node_modules/, + use: [ + { + loader: "ts-loader", + }, + ], + }, + ], + }, }; module.exports = config; From 5dfcbcc505ea51051d1c48bbec99c8d4fc6bb51f Mon Sep 17 00:00:00 2001 From: "codegen-sh[bot]" <131295404+codegen-sh[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 19:48:41 +0000 Subject: [PATCH 3/4] Add auto-expand functionality for chat messages --- codegen-vscode-extension/media/main.css | 45 +++++++++++++++++++++ codegen-vscode-extension/media/main.js | 52 +++++++++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/codegen-vscode-extension/media/main.css b/codegen-vscode-extension/media/main.css index 010ce8d54..86a842023 100644 --- a/codegen-vscode-extension/media/main.css +++ b/codegen-vscode-extension/media/main.css @@ -4,6 +4,7 @@ --input-padding-horizontal: 8px; --input-margin-vertical: 4px; --input-margin-horizontal: 0; + --message-max-height: 150px; /* Added for collapsed messages */ } body { @@ -37,6 +38,8 @@ body { border-radius: 6px; max-width: 90%; word-wrap: break-word; + cursor: pointer; /* Added to indicate clickable */ + transition: max-height 0.3s ease; /* Added for smooth transition */ } .user-message { @@ -54,6 +57,7 @@ body { .message-content { margin-bottom: 5px; + overflow: hidden; /* Added for collapsed state */ } .code-block { @@ -242,3 +246,44 @@ body { background-color: var(--vscode-editor-inactiveSelectionBackground); font-weight: 600; } + +/* Added for collapsed messages */ +.message-content.collapsed { + max-height: var(--message-max-height); + overflow: hidden; + position: relative; +} + +/* Added gradient fade for collapsed messages */ +.message-content.collapsed::after { + content: ""; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 40px; + background: linear-gradient( + to bottom, + transparent, + var(--vscode-editor-inactiveSelectionBackground) + ); + pointer-events: none; +} + +/* Adjust gradient color for user messages */ +.user-message .message-content.collapsed::after { + background: linear-gradient( + to bottom, + transparent, + var(--vscode-button-background) + ); +} + +/* Added expand indicator */ +.expand-indicator { + text-align: center; + font-size: 12px; + color: var(--vscode-descriptionForeground); + margin-top: 4px; + user-select: none; +} diff --git a/codegen-vscode-extension/media/main.js b/codegen-vscode-extension/media/main.js index b0479a700..abc1efee4 100644 --- a/codegen-vscode-extension/media/main.js +++ b/codegen-vscode-extension/media/main.js @@ -51,6 +51,30 @@ } } + // Check if content needs to be collapsed + function shouldCollapseContent(contentElement) { + // Get the computed max-height value from CSS variable + const maxHeight = parseInt( + getComputedStyle(document.documentElement).getPropertyValue('--message-max-height') + ); + + // If the content is taller than the max height, it should be collapsed + return contentElement.scrollHeight > maxHeight; + } + + // Toggle message expansion + function toggleMessageExpansion(contentElement, indicatorElement) { + if (contentElement.classList.contains("collapsed")) { + // Expand + contentElement.classList.remove("collapsed"); + indicatorElement.textContent = "Click to collapse"; + } else { + // Collapse + contentElement.classList.add("collapsed"); + indicatorElement.textContent = "Click to expand"; + } + } + // Render all messages function renderMessages() { messagesContainer.innerHTML = ""; @@ -65,6 +89,34 @@ contentElement.innerHTML = formatMarkdown(message.content); messageElement.appendChild(contentElement); + // Add expand indicator element + const expandIndicator = document.createElement("div"); + expandIndicator.className = "expand-indicator"; + messageElement.appendChild(expandIndicator); + + // Check if content should be collapsed (after adding to DOM to get accurate height) + setTimeout(() => { + if (shouldCollapseContent(contentElement)) { + contentElement.classList.add("collapsed"); + expandIndicator.textContent = "Click to expand"; + } else { + expandIndicator.style.display = "none"; // Hide indicator if not needed + } + }, 0); + + // Add click event to toggle expansion + messageElement.addEventListener("click", (e) => { + // Don't toggle if clicking on code action buttons + if (e.target.closest(".code-action-button")) { + return; + } + + // Toggle expansion + if (shouldCollapseContent(contentElement)) { + toggleMessageExpansion(contentElement, expandIndicator); + } + }); + // Code block (if any) if (message.code) { const codeElement = document.createElement("div"); From d37a4854ce4d2054094613f9795b3877f62b999d Mon Sep 17 00:00:00 2001 From: "codegen-sh[bot]" <131295404+codegen-sh[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 19:49:42 +0000 Subject: [PATCH 4/4] Automated pre-commit update --- codegen-vscode-extension/media/main.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/codegen-vscode-extension/media/main.js b/codegen-vscode-extension/media/main.js index abc1efee4..fa098d2c7 100644 --- a/codegen-vscode-extension/media/main.js +++ b/codegen-vscode-extension/media/main.js @@ -54,10 +54,12 @@ // Check if content needs to be collapsed function shouldCollapseContent(contentElement) { // Get the computed max-height value from CSS variable - const maxHeight = parseInt( - getComputedStyle(document.documentElement).getPropertyValue('--message-max-height') + const maxHeight = Number.parseInt( + getComputedStyle(document.documentElement).getPropertyValue( + "--message-max-height", + ), ); - + // If the content is taller than the max height, it should be collapsed return contentElement.scrollHeight > maxHeight; } @@ -110,7 +112,7 @@ if (e.target.closest(".code-action-button")) { return; } - + // Toggle expansion if (shouldCollapseContent(contentElement)) { toggleMessageExpansion(contentElement, expandIndicator);