├── images ├── logo.png └── demo │ ├── code_review.png │ ├── code_review2.png │ ├── hover_error.png │ └── hover_error2.png ├── .gitignore ├── .vscode-test.mjs ├── .vscodeignore ├── jsconfig.json ├── CHANGELOG.md ├── test └── extension.test.js ├── eslint.config.mjs ├── gemini_wrapper.js ├── LICENSE ├── request_processor.js ├── package.json ├── vsc-extension-quickstart.md ├── README.md └── extension.js /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pranay0205/coachlint/HEAD/images/logo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode-test/ 3 | *.vsix 4 | .env 5 | .venv 6 | *.pyc 7 | .vscode -------------------------------------------------------------------------------- /images/demo/code_review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pranay0205/coachlint/HEAD/images/demo/code_review.png -------------------------------------------------------------------------------- /images/demo/code_review2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pranay0205/coachlint/HEAD/images/demo/code_review2.png -------------------------------------------------------------------------------- /images/demo/hover_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pranay0205/coachlint/HEAD/images/demo/hover_error.png -------------------------------------------------------------------------------- /images/demo/hover_error2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pranay0205/coachlint/HEAD/images/demo/hover_error2.png -------------------------------------------------------------------------------- /.vscode-test.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@vscode/test-cli'; 2 | 3 | export default defineConfig({ 4 | files: 'test/**/*.test.js', 5 | }); 6 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | test/** 4 | .gitignore 5 | .yarnrc 6 | vsc-extension-quickstart.md 7 | **/jsconfig.json 8 | **/*.map 9 | **/eslint.config.mjs 10 | **/.vscode-test.* 11 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "Node16", 4 | "target": "ES2022", 5 | "checkJs": false, /* Typecheck .js files. */ 6 | "lib": [ 7 | "ES2022" 8 | ] 9 | }, 10 | "exclude": [ 11 | "node_modules" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "coachlint" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | - Initial release -------------------------------------------------------------------------------- /test/extension.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | const vscode = require('vscode'); 6 | // const myExtension = require('../extension'); 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5)); 13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | 3 | export default [{ 4 | files: ["**/*.js"], 5 | languageOptions: { 6 | globals: { 7 | ...globals.commonjs, 8 | ...globals.node, 9 | ...globals.mocha, 10 | }, 11 | 12 | ecmaVersion: 2022, 13 | sourceType: "module", 14 | }, 15 | 16 | rules: { 17 | "no-const-assign": "warn", 18 | "no-this-before-super": "warn", 19 | "no-undef": "warn", 20 | "no-unreachable": "warn", 21 | "no-unused-vars": "warn", 22 | "constructor-super": "warn", 23 | "valid-typeof": "warn", 24 | }, 25 | }]; -------------------------------------------------------------------------------- /gemini_wrapper.js: -------------------------------------------------------------------------------- 1 | const { GoogleGenerativeAI } = require("@google/generative-ai"); 2 | 3 | async function generate_explanation(prompt, apiKey, model_name = "gemini-2.5-flash-lite") { 4 | try { 5 | if (!apiKey) { 6 | throw new Error("API key is required"); 7 | } 8 | 9 | const genAI = new GoogleGenerativeAI(apiKey); 10 | const modelInstance = genAI.getGenerativeModel({ model: model_name }); 11 | 12 | const result = await modelInstance.generateContent(prompt); 13 | const response = await result.response; 14 | const text = response.text(); 15 | 16 | if (!text) { 17 | return "Unable to generate explanation"; 18 | } 19 | 20 | return text; 21 | } catch (error) { 22 | console.error(`Error generating explanation: ${error}`); 23 | return `Error generating explanation: ${error.message}`; 24 | } 25 | } 26 | 27 | module.exports = { generate_explanation }; 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Pranay Ghuge 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /request_processor.js: -------------------------------------------------------------------------------- 1 | const { generate_explanation } = require("./gemini_wrapper"); 2 | 3 | async function process_hover_function_help(request, apiKey, model_name) { 4 | const prompt = `Explain this function in 2-3 lines: what it does, its parameters, and provide a one-line usage example.\nFunction: ${request.functionName}\nContext: ${request.functionCode}`; 5 | 6 | const ai_explanation = await generate_explanation(prompt, apiKey, model_name); 7 | 8 | return { message: ai_explanation }; 9 | } 10 | 11 | async function process_hover_error_message(request, apiKey, model_name) { 12 | const prompt = `Explain the error in a very short manner should contain only two lines and shortly explain how to resolve it: ${request.errorMessage}\n`; 13 | 14 | const ai_explanation = await generate_explanation(prompt, apiKey, model_name); 15 | 16 | return { message: ai_explanation }; 17 | } 18 | 19 | async function process_code_review_message(request, apiKey, model_name) { 20 | const prompt = `Review this Python code. Give EXACTLY 3 short suggestions only if there are issues otherwise don't include 📍 in your reponse and just say Good Code. Use the following format: 21 | 22 | 📍 Line X: CATEGORY → Brief issue → Why it matters 23 | 24 | Categories: NAMING, PERFORMANCE, DOCS, LOGIC, POTENTIAL BUG, SECURITY, STYLE 25 | 26 | Code: 27 | ${request.code}`; 28 | 29 | const ai_response = await generate_explanation(prompt, apiKey, model_name); 30 | 31 | if (ai_response === null || ai_response === undefined) { 32 | return { message: "Unable to generate code review suggestions" }; 33 | } 34 | 35 | const count = (ai_response.match(/📍/g) || []).length; 36 | let message; 37 | if (count === 0) { 38 | message = "✅ GREAT CODE! No issues found."; 39 | } else { 40 | message = `🔍 ${count} SUGGESTIONS\n\n${ai_response}`; 41 | } 42 | 43 | return { message: message }; 44 | } 45 | 46 | module.exports = { 47 | process_hover_function_help, 48 | process_hover_error_message, 49 | process_code_review_message, 50 | }; 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coachlint", 3 | "displayName": "CoachLint", 4 | "description": "AI-powered coding coach that transforms harsh compiler errors into encouraging, actionable guidance", 5 | "icon": "images/logo.png", 6 | "version": "0.0.3", 7 | "publisher": "PranayGhuge", 8 | "engines": { 9 | "vscode": "^1.102.0" 10 | }, 11 | "categories": [ 12 | "Education", 13 | "Other" 14 | ], 15 | "keywords": [ 16 | "ai", 17 | "error-explanation", 18 | "code-review", 19 | "education", 20 | "gemini", 21 | "programming-coach", 22 | "learning" 23 | ], 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/Pranay0205/coachlint.git" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/Pranay0205/coachlint/issues" 30 | }, 31 | "homepage": "https://github.com/Pranay0205/coachlint#readme", 32 | "author": { 33 | "name": "Pranay Ghuge", 34 | "url": "https://pranayghuge.com" 35 | }, 36 | "license": "MIT", 37 | "activationEvents": [ 38 | "onStartupFinished" 39 | ], 40 | "main": "./extension.js", 41 | "contributes": { 42 | "commands": [ 43 | { 44 | "command": "coachlint.setApiKey", 45 | "title": "Set API Key", 46 | "category": "CoachLint" 47 | }, 48 | { 49 | "command": "coachlint.showApiKeyStatus", 50 | "title": "Show API Key Status", 51 | "category": "CoachLint" 52 | }, 53 | { 54 | "command": "coachlint.clearApiKey", 55 | "title": "Clear API Key", 56 | "category": "CoachLint" 57 | }, 58 | { 59 | "command": "coachlint.reviewCurrentFile", 60 | "title": "Review Current File", 61 | "category": "CoachLint" 62 | } 63 | ], 64 | "configuration": { 65 | "title": "CoachLint", 66 | "properties": { 67 | "coachlint.apiKey": { 68 | "type": "string", 69 | "default": "", 70 | "description": "Gemini API key for CoachLint AI explanations", 71 | "scope": "application" 72 | } 73 | } 74 | } 75 | }, 76 | "scripts": { 77 | "lint": "eslint .", 78 | "pretest": "npm run lint", 79 | "test": "vscode-test", 80 | "vscode:prepublish": "npm run lint", 81 | "package": "vsce package" 82 | }, 83 | "devDependencies": { 84 | "@types/mocha": "^10.0.10", 85 | "@types/node": "20.x", 86 | "@types/vscode": "^1.102.0", 87 | "@vscode/test-cli": "^0.0.11", 88 | "@vscode/test-electron": "^2.5.2", 89 | "eslint": "^9.25.1" 90 | }, 91 | "dependencies": { 92 | "@google/generative-ai": "^0.24.1" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension. 6 | * `package.json` - this is the manifest file in which you declare your extension and command. 7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `extension.js` - this is the main file where you will provide the implementation of your command. 9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 11 | 12 | ## Get up and running straight away 13 | 14 | * Press `F5` to open a new window with your extension loaded. 15 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 16 | * Set breakpoints in your code inside `extension.js` to debug your extension. 17 | * Find output from your extension in the debug console. 18 | 19 | ## Make changes 20 | 21 | * You can relaunch the extension from the debug toolbar after changing code in `extension.js`. 22 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 23 | 24 | ## Explore the API 25 | 26 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. 27 | 28 | ## Run tests 29 | 30 | * Install the [Extension Test Runner](https://marketplace.visualstudio.com/items?itemName=ms-vscode.extension-test-runner) 31 | * Open the Testing view from the activity bar and click the Run Test" button, or use the hotkey `Ctrl/Cmd + ; A` 32 | * See the output of the test result in the Test Results view. 33 | * Make changes to `test/extension.test.js` or create new test files inside the `test` folder. 34 | * The provided test runner will only consider files matching the name pattern `**.test.js`. 35 | * You can create folders inside the `test` folder to structure your tests any way you want. 36 | 37 | ## Go further 38 | 39 | * [Follow UX guidelines](https://code.visualstudio.com/api/ux-guidelines/overview) to create extensions that seamlessly integrate with VS Code's native interface and patterns. 40 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace. 41 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). 42 | * Integrate to the [report issue](https://code.visualstudio.com/api/get-started/wrapping-up#issue-reporting) flow to get issue and feature requests reported by users. 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![CoachLint Logo](images/logo.png) 2 | 3 | # CoachLint - Your AI Programming Coach 4 | 5 | [![Visual Studio Marketplace Version](https://img.shields.io/visual-studio-marketplace/v/PranayGhuge.coachlint?style=for-the-badge&logo=visual-studio-code&logoColor=white&label=VS%20Code%20Marketplace)](https://marketplace.visualstudio.com/items?itemName=PranayGhuge.coachlint) 6 | [![Visual Studio Marketplace Downloads](https://img.shields.io/visual-studio-marketplace/d/PranayGhuge.coachlint?style=for-the-badge&logo=visual-studio-code&logoColor=white)](https://marketplace.visualstudio.com/items?itemName=PranayGhuge.coachlint) 7 | [![Visual Studio Marketplace Rating](https://img.shields.io/visual-studio-marketplace/stars/PranayGhuge.coachlint?style=for-the-badge&logo=visual-studio-code&logoColor=white)](https://marketplace.visualstudio.com/items?itemName=PranayGhuge.coachlint) 8 | 9 | [Boot.dev Hackathon](https://blog.boot.dev/news/hackathon-2025/) 10 | 11 | ![JavaScript](https://img.shields.io/badge/javascript-%23323330.svg?style=for-the-badge&logo=javascript&logoColor=%23F7DF1E) 12 | ![Google Gemini](https://img.shields.io/badge/google%20gemini-8E75B2?style=for-the-badge&logo=google%20gemini&logoColor=white) 13 | ![Visual Studio Code](https://img.shields.io/badge/Visual%20Studio%20Code-0078d7.svg?style=for-the-badge&logo=visual-studio-code&logoColor=white) 14 | 15 | CoachLint is your AI coding coach. It guides you through errors instead of ***just solving them for you.*** 16 | 17 | ## 🚀 Installation 18 | 19 | ### From VS Code Marketplace (Recommended) 20 | 1. Open VS Code 21 | 2. Go to Extensions (`Ctrl+Shift+X`) 22 | 3. Search for "CoachLint" 23 | 4. Click "Install" 24 | 25 | **Or install directly**: [VS Code Marketplace - CoachLint](https://marketplace.visualstudio.com/items?itemName=PranayGhuge.coachlint) 26 | 27 | ### From Command Line 28 | ```bash 29 | code --install-extension PranayGhuge.coachlint 30 | ``` 31 | 32 | ## My Motivation 33 | 34 | Developers are becoming increasingly dependent on quick code fixes and instant code generation. When we use external AI tools, there's no middle ground - the AI just gives you the answer. People copy-paste solutions without understanding the **why** behind them. 35 | 36 | This approach makes us lose the core skill that makes a software engineer valuable: **understanding the problem and solving it systematically**. 37 | 38 | **AI is best for breaking down complex errors into very easy and understandable explanations.** I believe CoachLint will help developers learn more deeply instead of developing shallow programming skills. 39 | 40 | ## ✨ Features 41 | 42 | - **Error Explanations** - Hover over errors for plain English explanations 43 | - **Code Quality Review** - Get improvement suggestions for your Python code 44 | - **Educational Focus** - Learn concepts, not just quick fixes 45 | - **Secure API Key Storage** - Your API key is stored securely in VS Code settings 46 | - **Smart Caching** - Explanations are cached to reduce API calls 47 | 48 | ## ⚙️ Setup 49 | 50 | ### API Key Configuration 51 | 1. Get your Gemini API key from [Google AI Studio](https://makersuite.google.com/app/apikey) 52 | 2. Open Command Palette (`Ctrl+Shift+P`) 53 | 3. Type `CoachLint: Set API Key` 54 | 4. Enter your API key securely 55 | 56 | > API key is stored securely in the VS Code configurations. Remove the key once done, using *CoachLint: Clear API Key* command 57 | 58 | ## 🎯 Usage 59 | 60 | ### Hover Error Explanations 61 | 1. Write Python code with errors 62 | 2. **Hover** over red squiggly lines 63 | 3. Get instant AI explanation 64 | 65 | ### Code Quality Review 66 | 1. Open any Python file 67 | 2. Press `Ctrl+Shift+P` → "CoachLint: Review Current File" 68 | 3. Get suggestions in output panel 69 | 70 | ## 📋 Commands 71 | 72 | - `CoachLint: Set API Key` - Configure API key 73 | - `CoachLint: Review Current File` - Get code suggestions 74 | - `CoachLint: Show API Key Status` - Check configuration 75 | - `CoachLint: Clear API Key` - Remove stored API key 76 | 77 | ## 📸 Demo Screenshots 78 | 79 | ### Hovering over the error 80 | 81 | #### Example 1 82 | ![hover over error sample 1](images/demo/hover_error.png) 83 | 84 | #### Example 2 85 | ![hover over error sample 2](images/demo/hover_error2.png) 86 | 87 | ### Code Review Command 88 | 89 | #### Example 1 90 | ![Code Review Example 1](images/demo/code_review.png) 91 | 92 | #### Example 2 93 | ![Code Review Example 2](images/demo/code_review2.png) 94 | 95 | ## 🔒 Privacy & Security 96 | 97 | - Your API key is stored locally in VS Code settings 98 | - Code is only sent to Google Gemini for processing 99 | - No code or personal data is stored by CoachLint 100 | - You can clear your API key anytime 101 | 102 | ## 🤝 Contributing 103 | 104 | Found a bug or have a feature request? 105 | 106 | - **Issues**: [GitHub Issues](https://github.com/Pranay0205/coachlint/issues) 107 | - **Source Code**: [GitHub Repository](https://github.com/Pranay0205/coachlint) 108 | 109 | ## 📄 License 110 | 111 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 112 | 113 | ## 🎉 Boot.dev Hackathon 114 | 115 | Thank you, [Boot.dev](https://www.boot.dev), for hosting this event and your amazing content! 116 | 117 | --- 118 | 119 | ### Remember: The goal isn't to fix your code faster - it's to make you a better programmer. 120 | 121 | ⭐ **Enjoying CoachLint?** Please rate it on the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=PranayGhuge.coachlint)! -------------------------------------------------------------------------------- /extension.js: -------------------------------------------------------------------------------- 1 | const vscode = require("vscode"); 2 | const { process_hover_error_message, process_code_review_message } = require("./request_processor"); 3 | 4 | function activate(context) { 5 | console.log("CoachLint is running...!"); 6 | 7 | // Cache explanations to avoid re-generating 8 | const explanationCache = new Map(); 9 | let coachLintOutputChannel = vscode.window.createOutputChannel("CoachLint"); 10 | coachLintOutputChannel.appendLine("===================COACHLINT AI COACH======================\n"); 11 | 12 | // Command to set API key 13 | const setApiKeyCommand = vscode.commands.registerCommand("coachlint.setApiKey", async () => { 14 | const apiKey = await vscode.window.showInputBox({ 15 | prompt: "Enter your Gemini API key for CoachLint", 16 | password: true, // Hide the input for security 17 | placeHolder: "your-gemini-api-key-here", 18 | validateInput: (value) => { 19 | if (!value || value.trim().length === 0) { 20 | return "API key cannot be empty"; 21 | } 22 | if (value.length < 10) { 23 | return "API key seems too short"; 24 | } 25 | return null; 26 | }, 27 | }); 28 | 29 | if (apiKey) { 30 | // Store the API key in VS Code settings 31 | await vscode.workspace.getConfiguration("coachlint").update("apiKey", apiKey, vscode.ConfigurationTarget.Global); 32 | 33 | vscode.window.showInformationMessage("✅ API key saved successfully!"); 34 | 35 | // Clear cache when API key changes 36 | explanationCache.clear(); 37 | } 38 | }); 39 | 40 | // Command to show current API key status 41 | const showApiKeyStatusCommand = vscode.commands.registerCommand("coachlint.showApiKeyStatus", async () => { 42 | const apiKey = getApiKey(); 43 | 44 | if (apiKey) { 45 | const maskedKey = apiKey.substring(0, 8) + "..."; 46 | vscode.window.showInformationMessage(`🔑 API key is set: ${maskedKey}`); 47 | } else { 48 | const action = await vscode.window.showWarningMessage( 49 | "No API key set. CoachLint won't work without an API key.", 50 | "Set API Key" 51 | ); 52 | 53 | if (action === "Set API Key") { 54 | vscode.commands.executeCommand("coachlint.setApiKey"); 55 | } 56 | } 57 | }); 58 | 59 | // Command to clear API key 60 | const clearApiKeyCommand = vscode.commands.registerCommand("coachlint.clearApiKey", async () => { 61 | const confirm = await vscode.window.showWarningMessage("Are you sure you want to clear the API key?", "Yes", "No"); 62 | 63 | if (confirm === "Yes") { 64 | await vscode.workspace 65 | .getConfiguration("coachlint") 66 | .update("apiKey", undefined, vscode.ConfigurationTarget.Global); 67 | explanationCache.clear(); 68 | vscode.window.showInformationMessage("🗑️ API key cleared"); 69 | } 70 | }); 71 | 72 | const setModelNameCommand = vscode.commands.registerCommand("coachlint.setModelName", async () => { 73 | const modelName = await vscode.window.showInputBox({ 74 | prompt: "Enter the model name for CoachLint", 75 | placeHolder: "gemini-2.5-flash-lite (Default)", 76 | }); 77 | 78 | if (modelName) { 79 | await vscode.workspace 80 | .getConfiguration("coachlint") 81 | .update("modelName", modelName, vscode.ConfigurationTarget.Global); 82 | vscode.window.showInformationMessage(`✅ Model name set to ${modelName}`); 83 | } 84 | }); 85 | 86 | context.subscriptions.push(setApiKeyCommand, showApiKeyStatusCommand, clearApiKeyCommand, setModelNameCommand); 87 | 88 | // Helper function to get API key 89 | function getApiKey() { 90 | return vscode.workspace.getConfiguration("coachlint").get("apiKey"); 91 | } 92 | 93 | function getModelName() { 94 | return vscode.workspace.getConfiguration("coachlint").get("modelName") || "gemini-2.5-flash-lite"; 95 | } 96 | // On Hover error explanation 97 | const OnErrorHoverExplanation = vscode.languages.registerHoverProvider("*", { 98 | async provideHover(document, position) { 99 | const diagnostics = vscode.languages.getDiagnostics(document.uri); 100 | 101 | const diagnostic = diagnostics.find((d) => d.range.contains(position)); 102 | 103 | if (diagnostic) { 104 | const aiExplanation = await getGeneratedExplanation(diagnostic, document.uri); 105 | 106 | if (aiExplanation) { 107 | const markdown = new vscode.MarkdownString(); 108 | 109 | markdown.appendMarkdown(`**🤖 AI Explanation:**\n\n ${aiExplanation}`); 110 | markdown.appendMarkdown(`\n\n----\n\n**Original Error:** ${diagnostic.message}`); 111 | 112 | return new vscode.Hover(markdown); 113 | } 114 | } 115 | return null; 116 | }, 117 | }); 118 | 119 | async function getGeneratedExplanation(diagnostic, uri) { 120 | const key = `${uri.toString()}-${diagnostic.message}`; 121 | 122 | if (explanationCache.has(key)) { 123 | return explanationCache.get(key); 124 | } 125 | 126 | try { 127 | const errDetails = extractErrorDetails(uri, diagnostic); 128 | 129 | if (errDetails) { 130 | // Get API key from settings 131 | const apiKey = getApiKey(); 132 | 133 | const model_name = getModelName(); 134 | 135 | if (!apiKey) { 136 | vscode.window.showErrorMessage( 137 | 'CoachLint: Please set your Gemini API key first using "CoachLint: Set API Key" command.' 138 | ); 139 | return null; 140 | } 141 | 142 | const response = await process_hover_error_message(errDetails, apiKey, model_name); 143 | 144 | const explanation = response.message; 145 | 146 | explanationCache.set(key, explanation); 147 | 148 | return explanation; 149 | } 150 | } catch (err) { 151 | console.log("Failed to get AI Explanation:", err); 152 | 153 | // Show user-friendly error message 154 | if (err.message.includes("API key") || err.message.includes("authentication")) { 155 | vscode.window.showErrorMessage("CoachLint: Authentication failed. Please check your API key."); 156 | } else { 157 | vscode.window.showErrorMessage("CoachLint: Failed to get AI explanation. Check your connection and API key."); 158 | } 159 | } 160 | 161 | return null; 162 | } 163 | 164 | context.subscriptions.push(OnErrorHoverExplanation); 165 | 166 | function extractErrorDetails(uri, diagnostic) { 167 | const document = vscode.workspace.textDocuments.find((doc) => doc.uri.toString() === uri.toString()); 168 | 169 | if (!document) { 170 | return null; 171 | } 172 | 173 | const errorObj = { 174 | // Error details 175 | errorMessage: diagnostic.message, 176 | errorSource: diagnostic.source, 177 | errorCode: diagnostic.code, 178 | errorSeverity: diagnostic.severity, 179 | fileName: document.fileName, 180 | fileLanguage: document.languageId, 181 | lineNumber: diagnostic.range.start.line + 1, 182 | columnNumber: diagnostic.range.start.character + 1, 183 | 184 | errorLine: getErrorLine(document, diagnostic), 185 | surroundingCode: getSurroundingCode(document, diagnostic), 186 | 187 | timestamp: new Date().toISOString(), 188 | projectRoot: vscode.workspace.workspaceFolders?.[0]?.uri.path, 189 | }; 190 | 191 | return errorObj; 192 | } 193 | 194 | function getErrorLine(document, diagnostic) { 195 | try { 196 | const lineIndex = diagnostic.range.start.line; 197 | const line = document.lineAt(lineIndex); 198 | 199 | return { 200 | number: lineIndex + 1, 201 | text: line.text, 202 | startChar: diagnostic.range.start.character, 203 | endChar: diagnostic.range.end.character, 204 | highlightedText: line.text.substring(diagnostic.range.start.character, diagnostic.range.end.character), 205 | }; 206 | } catch (error) { 207 | console.log(error); 208 | return null; 209 | } 210 | } 211 | 212 | function getSurroundingCode(document, diagnostic, contextLines = 5) { 213 | const errorLine = diagnostic.range.start.line; 214 | const startLine = Math.max(0, errorLine - contextLines); 215 | const endLine = Math.min(document.lineCount - 1, errorLine + contextLines); 216 | 217 | const codeLines = []; 218 | 219 | for (let i = startLine; i <= endLine; i++) { 220 | try { 221 | const line = document.lineAt(i); 222 | codeLines.push({ 223 | number: i + 1, 224 | text: line.text, 225 | isErrorLine: i === errorLine, 226 | prefix: i === errorLine ? ">>> " : " ", 227 | }); 228 | } catch (error) { 229 | console.log(error); 230 | } 231 | } 232 | 233 | return codeLines; 234 | } 235 | 236 | const reviewCodeCommand = vscode.commands.registerCommand("coachlint.reviewCurrentFile", async () => { 237 | const activeEditor = vscode.window.activeTextEditor; 238 | 239 | if (!activeEditor) { 240 | vscode.window.showErrorMessage("No active Python file to review"); 241 | return; 242 | } 243 | 244 | if (activeEditor.document.languageId !== "python") { 245 | vscode.window.showErrorMessage("Code review currently supports Python files only"); 246 | return; 247 | } 248 | 249 | await reviewPythonFile(activeEditor); 250 | }); 251 | 252 | context.subscriptions.push(reviewCodeCommand); 253 | 254 | async function reviewPythonFile(editor) { 255 | const code = editor.document.getText(); 256 | const fileName = editor.document.fileName; 257 | 258 | vscode.window.withProgress( 259 | { 260 | location: vscode.ProgressLocation.Notification, 261 | title: "CoachLint is reviewing your code...", 262 | cancellable: false, 263 | }, 264 | async () => { 265 | const reviewData = { 266 | code: code, 267 | fileName: fileName, 268 | fileLanguage: "python", 269 | timestamp: new Date().toISOString(), 270 | }; 271 | 272 | try { 273 | const apiKey = getApiKey(); 274 | const model_name = getModelName(); 275 | if (!apiKey) { 276 | vscode.window.showErrorMessage( 277 | 'CoachLint: Please set your Gemini API key first using "CoachLint: Set API Key" command.' 278 | ); 279 | return; 280 | } 281 | 282 | const response = await process_code_review_message(reviewData, apiKey, model_name); 283 | 284 | displayCodeReview(response, fileName); 285 | } catch (error) { 286 | console.error("Code review error:", error); 287 | coachLintOutputChannel.appendLine("❌ Failed to get code review"); 288 | coachLintOutputChannel.show(true); 289 | 290 | if (error.message.includes("API key") || error.message.includes("authentication")) { 291 | vscode.window.showErrorMessage("CoachLint: Authentication failed. Please check your API key."); 292 | } else { 293 | vscode.window.showErrorMessage("CoachLint: Failed to get code review. Check your connection and API key."); 294 | } 295 | } 296 | } 297 | ); 298 | } 299 | 300 | function displayCodeReview(reviewResponse, fileName) { 301 | coachLintOutputChannel.clear(); 302 | coachLintOutputChannel.appendLine("═══════════════════════════════════════"); 303 | coachLintOutputChannel.appendLine("🔍 COACHLINT CODE REVIEW"); 304 | coachLintOutputChannel.appendLine("═══════════════════════════════════════"); 305 | coachLintOutputChannel.appendLine(`📁 File: ${fileName}`); 306 | coachLintOutputChannel.appendLine(`⏰ Analyzed: ${new Date().toLocaleString()}\n`); 307 | 308 | coachLintOutputChannel.appendLine(reviewResponse.message); 309 | coachLintOutputChannel.appendLine("\n═══════════════════════════════════════"); 310 | 311 | coachLintOutputChannel.show(true); 312 | } 313 | } 314 | 315 | // This method is called when your extension is deactivated 316 | function deactivate() {} 317 | 318 | module.exports = { 319 | activate, 320 | deactivate, 321 | }; 322 | --------------------------------------------------------------------------------