From 09a6a8a2f176a018b6704da57ea1663cd9ed6390 Mon Sep 17 00:00:00 2001 From: "R.Sinthujan" Date: Sat, 12 Jul 2025 12:36:55 +0530 Subject: [PATCH 1/7] import module error resolved --- collectors/importDefinitionAnalyzer.js | 325 +++++++++++++++++++------ commands/commands.js | 74 +++--- extension.js | 114 ++++----- indexers/codeStructureIndex.js | 82 ++----- indexers/codebaseIndexer.js | 3 +- indexers/vectorIndex.js | 16 +- package.json | 24 +- 7 files changed, 392 insertions(+), 246 deletions(-) diff --git a/collectors/importDefinitionAnalyzer.js b/collectors/importDefinitionAnalyzer.js index fd0c290..5f958fd 100644 --- a/collectors/importDefinitionAnalyzer.js +++ b/collectors/importDefinitionAnalyzer.js @@ -1,11 +1,15 @@ const vscode = require('vscode'); +const fs = require('fs').promises; +const path = require('path'); +const { glob } = require('glob'); + /** - * A Perl Import Analyzer that uses LanceDB to directly retrieve - * package and subroutine definitions + * A Perl Import Analyzer that uses direct file path resolution + * to retrieve package and subroutine definitions */ class PerlImportDefAnalyzer { - constructor(vectorIndex) { - this.vectorIndex = vectorIndex; + constructor() { + // No dependency on vectorIndex anymore } /** @@ -14,105 +18,269 @@ class PerlImportDefAnalyzer { * @returns {Object} - Map of module names to their imported symbols */ static extractImports(fileText) { - // Keep existing implementation const imports = {}; - // Regex to match use/require statements - const regex = /^\s*(?:use|require)\s+([A-Za-z0-9:]+)(?:\s+qw\(\s*([^)]+)\s*\)|\s*['"]([^'"]+)['"]|\s+(.+?)(?:;|$))?/gm; + // Updated regex to handle both module names and quoted file paths + const regex = /^\s*(?:use|require)\s+(?:([A-Za-z0-9:]+)|["']([^"']+)["'])(?:\s+qw\(\s*([^)]+)\s*\)|\s*["']([^"']+)["']|\s+(.+?)(?:;|$))?/gm; let match; while ((match = regex.exec(fileText))) { - const moduleName = match[1]; + const moduleName = match[1] || match[2]; // Either bareword module or quoted filename let symbols = []; - // Handle various symbol formats - if (match[2]) { // qw(...) format - symbols = match[2].split(/\s+/).filter(Boolean); - } else if (match[3]) { // quoted string format - symbols = [match[3]]; - } else if (match[4]) { // any other format - symbols = [match[4].trim()]; + // Handle various symbol formats - adjust indexes for the capture groups + if (match[3]) { // qw(...) format + symbols = match[3].split(/\s+/).filter(Boolean); + } else if (match[4]) { // quoted string format + symbols = [match[4]]; + } else if (match[5]) { // any other format + symbols = [match[5].trim()]; } imports[moduleName] = symbols; + // Add import type (module or file) for proper handling later + imports[moduleName].importType = match[1] ? 'module' : 'file'; } return imports; } +/** + * Find a file directly by its path or name + * @param {string} filePath - File name or path (e.g., "functions.pl") + * @returns {Promise} - File definition or null if not found + */ + async findFileByPath(filePath) { + const workspaceFolders = vscode.workspace.workspaceFolders; + if (!workspaceFolders || !workspaceFolders.length) { + return null; + } + + const rootPath = workspaceFolders[0].uri.fsPath; + const currentFilePath = vscode.window.activeTextEditor?.document.uri.fsPath; + const currentDir = currentFilePath ? path.dirname(currentFilePath) : rootPath; + + try { + // Try to find the file in these locations: + const searchPaths = [ + // 1. Directly in the same directory as the current file + path.join(currentDir, filePath), + // 2. In the workspace root + path.join(rootPath, filePath), + // 3. Search in workspace recursively + ...(await glob(`**/${filePath}`, { + cwd: rootPath, + ignore: ['**/node_modules/**', '**/blib/**'] + })).map(f => path.join(rootPath, f)) + ]; + + // Try each path + for (const fullPath of searchPaths) { + try { + const content = await fs.readFile(fullPath, 'utf8'); + console.log(`[PerlImportAnalyzer] Found file for ${filePath} at: ${fullPath}`); + return { + filepath: fullPath, + content: content, + type: 'file' + }; + } catch (err) { + // File not found at this path + console.error(`[PerlImportAnalyzer] Error finding file by path: ${err.message}`); + } + } + + console.log(`[PerlImportAnalyzer] No file found for: ${filePath}`); + } catch (err) { + console.error(`[PerlImportAnalyzer] Error finding file by path: ${err.message}`); + } + + return null; + } + + /** - * Get definitions for all imports in the current file using direct LanceDB queries - * @param {vscode.TextDocument} document - The current document - * @returns {Promise} - Map of module/symbol names to their definitions + * Find a module file directly by converting the module name to a file path + * @param {string} moduleName - Module name (e.g., "reader::import") + * @returns {Promise} - Module definition or null if not found */ - async getImportDefinitionsFromLanceDB(document) { - console.log('[PerlImportAnalyzer] Analyzing imports using LanceDB...'); + async findModuleByPath(moduleName) { + // Get workspace folders + const workspaceFolders = vscode.workspace.workspaceFolders; + if (!workspaceFolders || !workspaceFolders.length) { + return null; + } + + const rootPath = workspaceFolders[0].uri.fsPath; - if (!this.vectorIndex) { - console.warn('[PerlImportAnalyzer] Vector index not available, falling back to definition provider'); - return PerlImportAnalyzer.getImportDefinitions(document); + // Convert module name to relative file path (reader::import -> reader/import.pm) + const relativePath = `${moduleName.replace(/::/g, '/')}.pm`; + + try { + // Find all matching files + const files = await glob(`**/${relativePath}`, { + cwd: rootPath, + ignore: ['**/node_modules/**', '**/blib/**'] + }); + + if (files.length > 0) { + const fullPath = path.join(rootPath, files[0]); + const content = await fs.readFile(fullPath, 'utf8'); + console.log(`[PerlImportAnalyzer] Found module file for ${moduleName} at: ${fullPath}`); + return { + filepath: fullPath, + content: content, + type: 'module' + }; + } else { + console.log(`[PerlImportAnalyzer] No file found for module: ${moduleName}`); + } + } catch (err) { + console.error(`[PerlImportAnalyzer] Error finding module by path: ${err.message}`); } + return null; + } + + /** + * Find a symbol in a module file + * @param {string} moduleDef - The module definition + * @param {string} symbol - Symbol name to find + * @returns {Object|null} - Symbol definition or null if not found + */ + async findSymbolInFile(moduleDef, symbol) { + if (!moduleDef || !moduleDef.content) { + return null; + } + + try { + const content = moduleDef.content; + const lines = content.split('\n'); + let symbolLine = -1; + let symbolContent = ''; + + // Match different forms of subroutine declarations + const subRegexPatterns = [ + new RegExp(`sub\\s+${symbol}\\s*\\{`, 'i'), // sub name { + new RegExp(`sub\\s+${symbol}\\s*\\(.*?\\)\\s*\\{`, 'i'), // sub name(...) { + new RegExp(`\\*${symbol}\\s*=\\s*sub\\s*\\{`, 'i'), // *name = sub { + new RegExp(`my\\s+\\$${symbol}\\s*=\\s*sub\\s*\\{`, 'i') // my $name = sub { + ]; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + + // Check each pattern for a match + for (const pattern of subRegexPatterns) { + if (pattern.test(line)) { + symbolLine = i; + + // Capture the function content + let bracketCount = 0; + let startLine = i; + + // Count opening and closing brackets to find the end + for (let j = i; j < lines.length; j++) { + const openCount = (lines[j].match(/\{/g) || []).length; + const closeCount = (lines[j].match(/\}/g) || []).length; + + bracketCount += openCount - closeCount; + + if (bracketCount === 0 && j > i) { + // Found the end of the function + symbolContent = lines.slice(startLine, j + 1).join('\n'); + break; + } + } + + break; + } + } + + if (symbolLine !== -1) { + break; + } + } + + if (symbolLine !== -1) { + return { + filepath: moduleDef.filepath, + content: symbolContent || `sub ${symbol} { ... }`, + line: symbolLine, + type: 'subroutine' + }; + } + } catch (err) { + console.error(`[PerlImportAnalyzer] Error finding symbol: ${err.message}`); + } + + return null; + } + /** + * Get definitions for all imports in the current file using file path approach + * @param {vscode.TextDocument} document - The current document + * @returns {Promise} - Map of module/symbol names to their definitions + */ + async getImportDefinitions(document) { + console.log('[PerlImportAnalyzer] Analyzing imports using file path lookup...'); + const fileText = document.getText(); const imports = PerlImportDefAnalyzer.extractImports(fileText); const results = {}; - console.log(`[PerlImportAnalyzer] Found ${Object.keys(imports).length} imported modules`); + console.log(`[PerlImportAnalyzer] Found ${Object.keys(imports).length} imported modules/files`); - // Process each module - for (const moduleName of Object.keys(imports)) { - console.log(`[PerlImportAnalyzer] Processing module: ${moduleName}`); + // Process each module or file + for (const importName of Object.keys(imports)) { + console.log(`[PerlImportAnalyzer] Processing import: ${importName}`); - try { - // Direct query for exact module name - const moduleMatches = await this.vectorIndex.table - .query() - .where(`type = 'package' AND title = '${moduleName}'`) - .limit(1) - .toArray(); - - console.log(`[PerlImportAnalyzer] Found ${moduleMatches.length} exact package match(es) for ${moduleName}`); + let moduleDef = null; + const importType = imports[importName].importType || 'module'; + + // Use the appropriate finder based on import type + if (importType === 'module') { + // Find module using path lookup + moduleDef = await this.findModuleByPath(importName); + } else { + // Find direct file import + moduleDef = await this.findFileByPath(importName); + } + + + // Process symbols for this import + const symbols = imports[importName].filter(item => typeof item === 'string'); + if (moduleDef) { + if(symbols.length == 0 ){ + results[importName] = [moduleDef]; + } + } else { + results[importName] = []; + console.log(`[PerlImportAnalyzer] Import not found: ${importName}`); + } + + for (const symbol of symbols) { + // Skip invalid symbols + if (symbol.includes('/') || symbol.includes('$') || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(symbol)) { + console.log(`[PerlImportAnalyzer] Skipping invalid symbol: ${symbol}`); + continue; + } - // Format results - results[moduleName] = moduleMatches.map(match => ({ - filepath: match.path, - content: match.content - })); + console.log(`[PerlImportAnalyzer] Processing symbol: ${symbol}`); - // Process symbols for this module - const symbols = imports[moduleName]; - for (const symbol of symbols) { - // Skip invalid symbols - if (symbol.includes('/') || symbol.includes('$') || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(symbol)) { - console.log(`[PerlImportAnalyzer] Skipping invalid symbol: ${symbol}`); - continue; - } - - const fullName = `${moduleName}::${symbol}`; - console.log(`[PerlImportAnalyzer] Processing symbol: ${symbol}`); + // Use the file/module we found to locate the symbol + if (moduleDef) { + const symbolDef = await this.findSymbolInFile(moduleDef, symbol); + const fullName = `${importName}::${symbol}`; - try { - // Direct query for exact subroutine name - const subMatches = await this.vectorIndex.table - .query() - .where(`type = 'subroutine' AND title = '${fullName}'`) - .limit(1) - .toArray(); - - console.log(`[PerlImportAnalyzer] Found ${subMatches.length} exact subroutine match(es) for ${fullName}`); - - // Format results - results[fullName] = subMatches.map(match => ({ - filepath: match.path, - content: match.content - })); - } catch (err) { - console.error(`Error processing symbol ${fullName}: ${err.message}`); + if (symbolDef) { + results[fullName] = [symbolDef]; + console.log(`[PerlImportAnalyzer] Found symbol ${symbol} in ${moduleDef.filepath}`); + } else { results[fullName] = []; + console.log(`[PerlImportAnalyzer] Symbol not found: ${symbol} in import ${importName}`); } + } else { + results[`${importName}::${symbol}`] = []; } - } catch (err) { - console.error(`Error processing module ${moduleName}: ${err.message}`); - results[moduleName] = []; } } @@ -121,11 +289,10 @@ class PerlImportDefAnalyzer { } /** - * Command handler to analyze imports in the current file using LanceDB - * @param {Object} vectorIndex - Instance of PerlVectorIndex + * Command handler to analyze imports in the current file using file path approach * @returns {Promise} Import definitions or null if error */ -async function analyzeImportsForCurrentFileWithLanceDB(vectorIndex) { +async function analyzeImportsForCurrentFile() { try { const editor = vscode.window.activeTextEditor; if (!editor) { @@ -142,10 +309,10 @@ async function analyzeImportsForCurrentFileWithLanceDB(vectorIndex) { return null; } - console.log(`[PerlImportAnalyzer] Analyzing file with LanceDB: ${filepath}`); + console.log(`[PerlImportAnalyzer] Analyzing file: ${filepath}`); - const analyzer = new PerlImportDefAnalyzer(vectorIndex); - const results = await analyzer.getImportDefinitionsFromLanceDB(document); + const analyzer = new PerlImportDefAnalyzer(); + const results = await analyzer.getImportDefinitions(document); const totalEntries = Object.keys(results).length; console.log(`[PerlImportAnalyzer] Analysis complete: ${totalEntries} entries found`); @@ -166,5 +333,5 @@ async function analyzeImportsForCurrentFileWithLanceDB(vectorIndex) { module.exports = { PerlImportDefAnalyzer, - analyzeImportsForCurrentFileWithLanceDB + analyzeImportsForCurrentFile } \ No newline at end of file diff --git a/commands/commands.js b/commands/commands.js index b04c153..8754e6e 100644 --- a/commands/commands.js +++ b/commands/commands.js @@ -1,11 +1,11 @@ const vscode = require('vscode'); -const { analyzeImportsForCurrentFileWithLanceDB } = require('../collectors/importDefinitionAnalyzer'); +const { analyzeImportsForCurrentFile } = require('../collectors/importDefinitionAnalyzer'); const { PerlRepositoryMapProvider } = require('../collectors/repoMapProvider'); const ContextCollector = require('../collectors/contextCollector'); const DefinitionCollector = require('../collectors/definitionCollector'); -const { PerlImportDefAnalyzer } = require('../collectors/importDefinitionAnalyzer'); const PerlImportAnalyzer = require('../collectors/perlImportAnalyzer') const { getParser } = require('../parsers/treeSitter'); +const { debugModuleNames } = require('../collectors/importDefinitionAnalyzer'); /** * Registers all commands for the extension. @@ -204,33 +204,6 @@ function registerCommands(context, { config, logError, logInfo, initializeCodeba } }) ); - - // Command: Get imports - context.subscriptions.push( - vscode.commands.registerCommand('perlcodegeneration.getImports', async () => { - const indexer = getCodebaseIndexer(); - if (!indexer) { - vscode.window.showInformationMessage('Codebase indexer not initialized. Run "Index Perl Codebase" first.'); - return; - } - - try { - const results = await analyzeImportsForCurrentFileWithLanceDB(indexer.vectorIndex); - - if (results) { - const out = vscode.window.createOutputChannel('Perl Imports (LanceDB)'); - out.clear(); - out.appendLine('\n## Imports\n' + JSON.stringify(results, null, 2)); - out.show(); - } else { - vscode.window.showInformationMessage('No import information found'); - } - } catch (error) { - logError('Error analyzing imports:', error); - vscode.window.showErrorMessage(`Failed to analyze imports: ${error.message}`); - } - }) - ); // New Command: Index Perl Codebase (on-demand) context.subscriptions.push( @@ -262,5 +235,48 @@ function registerCommands(context, { config, logError, logInfo, initializeCodeba } }) ); + + // Command: Debug Module Names + context.subscriptions.push( + vscode.commands.registerCommand('perlcodegeneration.debugModuleNames', async () => { + const indexer = getCodebaseIndexer(); + if (!indexer) { + vscode.window.showInformationMessage('Codebase indexer not initialized. Run "Index Perl Codebase" first.'); + return; + } + + try { + await debugModuleNames(indexer.vectorIndex); + vscode.window.showInformationMessage('Module names debug complete. Check Output console for results.'); + } catch (error) { + logError('Error debugging module names:', error); + vscode.window.showErrorMessage(`Failed to debug module names: ${error.message}`); + } + }) + ); + + // Command to get imports (with option for memory-based index) + context.subscriptions.push( + vscode.commands.registerCommand('perlcodegeneration.getImports', async () => { + try { + let results; + // Use memory-based approach + results = await analyzeImportsForCurrentFile(); + + if (results) { + const out = vscode.window.createOutputChannel('Perl Imports'); + out.clear(); + out.appendLine(`\n## Imports (using Memory Index)\n` + + JSON.stringify(results, null, 2)); + out.show(); + } else { + vscode.window.showInformationMessage('No import information found'); + } + } catch (error) { + logError('Error analyzing imports:', error); + vscode.window.showErrorMessage(`Failed to analyze imports: ${error.message}`); + } + }) + ); } module.exports = { registerCommands }; \ No newline at end of file diff --git a/extension.js b/extension.js index ed32074..09195be 100644 --- a/extension.js +++ b/extension.js @@ -3,7 +3,7 @@ const api = require('./api/api'); const { initTreeSitter, getParser } = require('./parsers/treeSitter'); const ContextCollector = require('./collectors/contextCollector'); const DefinitionCollector = require('./collectors/definitionCollector'); -const { PerlImportDefAnalyzer, analyzeImportsForCurrentFileWithLanceDB} = require('./collectors/importDefinitionAnalyzer') +const { PerlImportDefAnalyzer } = require('./collectors/importDefinitionAnalyzer') const PerlImportAnalyzer = require('./collectors/perlImportAnalyzer') const debounce = require('./utils/debounce'); const { PerlCodebaseIndexer } = require('./indexers/codebaseIndexer'); @@ -15,8 +15,8 @@ const { registerCommands } = require('./commands/commands') */ const config = { // Default values, will be overridden by user settings - debounceTime: 500, - relevantCodeCount: 5, + relevantCodeCount: 2, + useMemoryIndex:true, indexOnStartup: true, contextWindowSize: 15, // lines around cursor to consider for context }; @@ -24,7 +24,7 @@ const config = { // Global state let codebaseIndexer = null; let outputChannel = null; - +let debounceTime = 2000; /** * Fetches code suggestion based on a comment * @param {string} comment - The user's comment @@ -51,67 +51,65 @@ async function fetchCode(comment, doc, pos) { * @param {vscode.Position} pos - Current cursor position * @returns {Promise} Context object with code information */ -async function generateContextForComments(comment, doc, pos) { - try { - // Only initialize analyzer if codebaseIndexer exists - const analyzer = codebaseIndexer ? - new PerlImportDefAnalyzer(codebaseIndexer.vectorIndex) : null; - - // Basic context that doesn't require the indexer - const codeCtx = ContextCollector.getCodeAround(doc, pos); - const block = await ContextCollector.getCurrentBlock(doc, pos); - const imports = PerlImportAnalyzer.extractImports(doc.getText()); - const used = PerlImportAnalyzer.findUsedSymbols(codeCtx.textAroundCursor, imports); - - const varDefs = await DefinitionCollector.findVariableDefinitions( - doc, - new vscode.Range(new vscode.Position(0, 0), doc.lineAt(doc.lineCount - 1).range.end) - ); + async function generateContextForComments(comment, doc, pos) { + try { + // Create analyzer - either memory-based or LanceDB-based depending on config + let analyzer = new PerlImportDefAnalyzer(); + // Basic context that doesn't require the indexer + const codeCtx = ContextCollector.getCodeAround(doc, pos); + const block = await ContextCollector.getCurrentBlock(doc, pos); + const imports = PerlImportAnalyzer.extractImports(doc.getText()); + const used = PerlImportAnalyzer.findUsedSymbols(codeCtx.textAroundCursor, imports); + + const varDefs = await DefinitionCollector.findVariableDefinitions( + doc, + new vscode.Range(new vscode.Position(0, 0), doc.lineAt(doc.lineCount - 1).range.end) + ); - // Context payload with mandatory fields - const ctxPayload = { - codePrefix: codeCtx.fullPrefix, - codeSuffix: codeCtx.fullSuffix, - currentBlock: block, - imports: imports, - usedModules: used, - variableDefinitions: varDefs, - fileName: doc.fileName, - }; + // Context payload with mandatory fields + const ctxPayload = { + codePrefix: codeCtx.fullPrefix, + codeSuffix: codeCtx.fullSuffix, + currentBlock: block, + imports: imports, + usedModules: used, + variableDefinitions: varDefs, + fileName: doc.fileName, + }; - // Optional context that requires the indexer - if (codebaseIndexer) { - try { - ctxPayload.projectStructure = await PerlRepositoryMapProvider.generateTreeMap(); - - if (analyzer) { - ctxPayload.importDefinitions = await analyzer.getImportDefinitionsFromLanceDB(doc); + // Get import definitions using the chosen analyzer + if (analyzer) { + try { + ctxPayload.projectStructure = await PerlRepositoryMapProvider.generateTreeMap(); + + ctxPayload.importDefinitions = await analyzer.getImportDefinitions(doc); + + // Get relevant code (works with either approach) + if (codebaseIndexer) { + ctxPayload.relatedCodeStructures = await codebaseIndexer.findRelevantCode( + comment, + config.relevantCodeCount + ); + } + } catch (indexerErr) { + logError('Error getting advanced context:', indexerErr); + // Continue with basic context if advanced context fails } - - ctxPayload.relatedCodeStructures = await codebaseIndexer.findRelevantCode( - comment, - config.relevantCodeCount - ); - } catch (indexerErr) { - logError('Error getting advanced context:', indexerErr); - // Continue with basic context if advanced context fails } - } - logDebug('Context generated:', ctxPayload); - return ctxPayload; - } catch (err) { - logError('Error generating context:', err); - throw new Error(`Failed to generate context: ${err.message}`); + logDebug('Context generated:', ctxPayload); + return ctxPayload; + } catch (err) { + logError('Error generating context:', err); + throw new Error(`Failed to generate context: ${err.message}`); + } } -} /** * Loads extension configuration from settings */ function loadConfiguration() { const settings = vscode.workspace.getConfiguration('perlCodeGeneration'); - config.debounceTime = settings.get('debounceTime', config.debounceTime); config.relevantCodeCount = settings.get('relevantCodeCount', config.relevantCodeCount); config.indexOnStartup = settings.get('indexOnStartup', config.indexOnStartup); config.contextWindowSize = settings.get('contextWindowSize', config.contextWindowSize); @@ -226,10 +224,10 @@ async function activate(context) { logInfo("Codebase indexer initialized successfully"); } }); - }, 500); + }, 300); // Create debounced version of fetchCode - const debouncedFetch = debounce(fetchCode, config.debounceTime); + const debouncedFetch = debounce(fetchCode, debounceTime); // Register configuration change listener context.subscriptions.push( @@ -248,7 +246,7 @@ async function activate(context) { if (!line.trim().startsWith('#')) return { items: [] }; const comment = line.replace(/^(\s*#\s?)/, '').trim(); - if (!comment) return { items: [] }; + if (!comment || comment.length < 3) return { items: [] }; const suggestion = await debouncedFetch(comment, doc, pos); @@ -298,8 +296,4 @@ function deactivate() { } } -function getCodebaseIndexer() { - return codebaseIndexer; -} - module.exports = { activate, deactivate }; \ No newline at end of file diff --git a/indexers/codeStructureIndex.js b/indexers/codeStructureIndex.js index ecfc816..e214979 100644 --- a/indexers/codeStructureIndex.js +++ b/indexers/codeStructureIndex.js @@ -7,7 +7,7 @@ parser = getParser() */ class PerlCodeStructureIndex { constructor() { - this.structures = new Map(); // filepath -> structures + this.structures = new Map(); // filepath -> structures } /** @@ -57,7 +57,7 @@ class PerlCodeStructureIndex { _extractStructuresFromTree(node, contents, structures, parentPackage = '') { // Extract package declarations if (node.type === 'package_statement') { - // 1) extract the package name as before + // extract the package name const packageNameNode = node.child(1); if (!packageNameNode) return; const packageName = contents.slice( @@ -65,42 +65,6 @@ class PerlCodeStructureIndex { packageNameNode.endIndex ); parentPackage = packageName; - - // 2) compute pkgStart - const pkgStart = node.startIndex; - - // 3) find the root (source_file) - let root = node; - while (root.parent) root = root.parent; - - // 4) get all package statements in order - const pkgs = root.namedChildren - .filter(n => n.type === 'package_statement') - .sort((a, b) => a.startIndex - b.startIndex); - - // 5) find this node's index - const myIndex = pkgs.findIndex(n => n === node); - - // 6) determine package end (next package or EOF) - let pkgEnd = contents.length; - if (myIndex >= 0 && myIndex < pkgs.length - 1) { - pkgEnd = pkgs[myIndex + 1].startIndex; - } - - // 7) Extract the full package content - const packageText = contents.substring(pkgStart, pkgEnd); - - console.log(`Package ${packageName} content length: ${packageText.length}`); - console.log(`Package content starts with: ${packageText.substring(0, 50)}...`); - - // 8) push to structures - structures.push({ - name: packageName, - type: 'package', - content: packageText, // Use packageText instead of contents - range: { start: pkgStart, end: pkgEnd }, - line: node.startPosition.row - }); } // Extract subroutine declarations @@ -125,17 +89,17 @@ class PerlCodeStructureIndex { } } - /** + /* * Extract code structures using regex patterns * @private */ _extractStructuresWithRegex(contents, structures) { - // Extract package declarations + // Track package declarations without adding them as structures let currentPackage = ''; const packageRegex = /^\s*package\s+([A-Za-z0-9:]+)\s*;/gm; let match; - // Collect all package declarations first + // Create a map of package ranges to determine current package context const packageRanges = []; while ((match = packageRegex.exec(contents)) !== null) { packageRanges.push({ @@ -144,35 +108,29 @@ class PerlCodeStructureIndex { }); } - // Calculate package content ranges + // Process package ranges to define boundaries (but don't add to structures) for (let i = 0; i < packageRanges.length; i++) { - const pkg = packageRanges[i]; - currentPackage = pkg.name; - - // End is either the next package or EOF - const endPos = (i < packageRanges.length - 1) + packageRanges[i].end = (i < packageRanges.length - 1) ? packageRanges[i + 1].start : contents.length; - - const packageContent = contents.substring(pkg.start, endPos); - - console.log(`Regex: Package ${currentPackage} content length: ${packageContent.length}`); - - structures.push({ - name: currentPackage, - type: 'package', - content: packageContent, - range: { start: pkg.start, end: endPos }, - line: this._getLineNumber(contents, pkg.start) - }); } - - // Extract subroutines + + // Extract subroutines with proper package qualification const subRegex = /^\s*sub\s+([A-Za-z0-9_]+)(?:\s*\{|\s*$|\s*\([^)]*\)\s*\{)/gm; while ((match = subRegex.exec(contents)) !== null) { - // Find matching closing brace const subName = match[1]; const startPos = match.index; + + // Determine which package this sub belongs to + currentPackage = ''; + for (const pkg of packageRanges) { + if (startPos >= pkg.start && startPos < pkg.end) { + currentPackage = pkg.name; + break; + } + } + + // Find matching closing brace let braceCount = 0; let inComment = false; let endPos = startPos; diff --git a/indexers/codebaseIndexer.js b/indexers/codebaseIndexer.js index b1c2dd8..8fb8df5 100644 --- a/indexers/codebaseIndexer.js +++ b/indexers/codebaseIndexer.js @@ -17,8 +17,7 @@ class PerlCodebaseIndexer { this.workspace = workspace; this.fileWatcher = fileWatcher; this.structureIndex = new PerlCodeStructureIndex(); - this.vectorIndex = new PerlVectorIndex(this.structureIndex); - + this.vectorIndex = new PerlVectorIndex(this.structureIndex, workspace); // Create output channel for logging this.outputChannel = vscode.window.createOutputChannel('Perl Indexer'); diff --git a/indexers/vectorIndex.js b/indexers/vectorIndex.js index bde392b..a4709d0 100644 --- a/indexers/vectorIndex.js +++ b/indexers/vectorIndex.js @@ -9,11 +9,19 @@ const { MiniLmEmbeddingProvider } = require('../embeddings/miniLmEmbeddings'); class PerlVectorIndex { /** * @param {Object} structureIndex - Code structure index + * @param {vscode.WorkspaceFolder} workspace - The workspace this index belongs to */ - constructor(structureIndex) { + constructor(structureIndex, workspace) { this.structureIndex = structureIndex; this.embedProvider = new MiniLmEmbeddingProvider(); - this.dbPath = path.join(os.homedir(), '.vscode', 'perl-extension-db'); + + // Generate project-specific folder name (sanitized workspace path) + const workspaceName = workspace ? + workspace.uri.fsPath.replace(/[\/\\:]/g, '-').replace(/^-+/, '') : + 'default'; + + // Create a project-specific database path + this.dbPath = path.join(os.homedir(), '.vscode', 'perl-extension-db', workspaceName); this.tableName = 'perl_code_embeddings'; this.db = null; this.table = null; @@ -117,7 +125,7 @@ class PerlVectorIndex { path: r.path, cacheKey: r.cacheKey, "content":r.content, - "title":r.title, + "title":r.title.toLowerCase(), "samplevector":r.vector.slice(0, 5), "type":r.type })), @@ -192,7 +200,7 @@ class PerlVectorIndex { path: file.path, cacheKey: file.cacheKey, content: structure.content, - title: structure.name, + title: (structure.name).toLowerCase(), vector: vector, type: structure.type }); diff --git a/package.json b/package.json index 10ef184..e2db353 100644 --- a/package.json +++ b/package.json @@ -29,26 +29,25 @@ "command": "perlcodegeneration.findRelevantCode", "title": "Perl: Find Relevant Code" }, - { - "command": "perlcodegeneration.getImports", - "title": "Perl: Analyze Imports" - }, { "command": "perlcodegeneration.indexCodebase", "title": "Perl: Index Codebase" + }, + { + "command": "perlcodegeneration.debugModuleNames", + "title": "Perl: debugName" + }, + { + "command": "perlcodegeneration.getImports", + "title": "Perl: debugImports" } ], "configuration": { "title": "Perl Code Generation", "properties": { - "perlCodeGeneration.debounceTime": { - "type": "number", - "default": 500, - "description": "Time in milliseconds to wait before generating code after typing stops" - }, "perlCodeGeneration.relevantCodeCount": { "type": "number", - "default": 5, + "default": 3, "description": "Number of relevant code examples to retrieve" }, "perlCodeGeneration.indexOnStartup": { @@ -60,6 +59,11 @@ "type": "number", "default": 15, "description": "Number of lines to consider around cursor for context" + }, + "perlCodeGeneration.useMemoryIndex": { + "type": "boolean", + "default": true, + "description": "Use in-memory index instead of LanceDB for module resolution" } } } From d7c57a8cb8599a976c2cfc3a7bb2b399fc71372b Mon Sep 17 00:00:00 2001 From: Dilshan304 Date: Fri, 18 Jul 2025 14:03:44 +0530 Subject: [PATCH 2/7] WIP: Local extension changes before pulling dev updates --- extension.js | 124 ++++++++++--- package-lock.json | 428 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 28 ++- sidebarprovider.js | 70 ++++++++ 4 files changed, 622 insertions(+), 28 deletions(-) create mode 100644 sidebarprovider.js diff --git a/extension.js b/extension.js index ed32074..c3eb465 100644 --- a/extension.js +++ b/extension.js @@ -9,16 +9,15 @@ const debounce = require('./utils/debounce'); const { PerlCodebaseIndexer } = require('./indexers/codebaseIndexer'); const { PerlRepositoryMapProvider } = require('./collectors/repoMapProvider'); const { registerCommands } = require('./commands/commands') - +const { AlternativeSuggestionsProvider } = require('./sidebarProvider'); /** * Global extension configuration */ const config = { - // Default values, will be overridden by user settings - debounceTime: 500, + debounceTime: 500, relevantCodeCount: 5, indexOnStartup: true, - contextWindowSize: 15, // lines around cursor to consider for context + contextWindowSize: 15, }; // Global state @@ -68,7 +67,6 @@ async function generateContextForComments(comment, doc, pos) { new vscode.Range(new vscode.Position(0, 0), doc.lineAt(doc.lineCount - 1).range.end) ); - // Context payload with mandatory fields const ctxPayload = { codePrefix: codeCtx.fullPrefix, codeSuffix: codeCtx.fullSuffix, @@ -94,7 +92,6 @@ async function generateContextForComments(comment, doc, pos) { ); } catch (indexerErr) { logError('Error getting advanced context:', indexerErr); - // Continue with basic context if advanced context fails } } @@ -131,16 +128,14 @@ async function initializeCodebaseIndexer() { try { const fileWatcher = vscode.workspace.createFileSystemWatcher( '**/*.{pl,pm,t}', - false, // Don't ignore creates - false, // Don't ignore changes - false // Don't ignore deletions + false, + false, + false ); - // Create the indexer codebaseIndexer = new PerlCodebaseIndexer(workspaceFolders[0], fileWatcher); if (config.indexOnStartup) { - // Start indexing with progress indicator await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: "Indexing Perl codebase", @@ -206,10 +201,8 @@ function logError(message, error) { async function activate(context) { logInfo("Extension activated"); - // Load configuration loadConfiguration(); - // Initialize Tree-sitter try { await initTreeSitter(); const parser = getParser(); @@ -219,7 +212,6 @@ async function activate(context) { vscode.window.showWarningMessage("Perl parser initialization failed. Some features may not work correctly."); } - // Initialize codebase indexer with delay to not block extension activation setTimeout(() => { initializeCodebaseIndexer().then(success => { if (success) { @@ -228,10 +220,8 @@ async function activate(context) { }); }, 500); - // Create debounced version of fetchCode const debouncedFetch = debounce(fetchCode, config.debounceTime); - // Register configuration change listener context.subscriptions.push( vscode.workspace.onDidChangeConfiguration(e => { if (e.affectsConfiguration('perlCodeGeneration')) { @@ -241,7 +231,6 @@ async function activate(context) { }) ); - // Register inline completion provider const inlineCompletionProvider = { async provideInlineCompletionItems(doc, pos) { const line = doc.lineAt(pos).text; @@ -253,9 +242,7 @@ async function activate(context) { const suggestion = await debouncedFetch(comment, doc, pos); if (!suggestion) return { items: [] }; - // Remove markdown formatting const cleanCode = suggestion.replace(/```[\w]*\n|\n```/g, ''); - // Use the clean code console.log(cleanCode); const insertPos = new vscode.Position(pos.line, line.length); return { @@ -273,12 +260,93 @@ async function activate(context) { inlineCompletionProvider ) ); + const treeProvider = new AlternativeSuggestionsProvider(); + const treeView = vscode.window.createTreeView('perlCodeGen.alternativeSuggestions', { + treeDataProvider: treeProvider + }); + context.subscriptions.push(treeView); - // Register commands registerCommands(context, { config, logError, logInfo, initializeCodebaseIndexer, getCodebaseIndexer: () => codebaseIndexer}) logInfo("Extension setup complete"); + + const processSelectionForSidebar = debounce(async (event) => { + const selection = event.selections[0]; + const doc = event.textEditor.document; + + // Handle non-Perl files first + if (doc.languageId !== 'perl') { + logInfo(`Skipping suggestion for non-Perl file: ${doc.languageId}`); + // Display an error message if it's not a Perl file + treeProvider.refresh([`Error: Only Perl code suggestions are supported. Current file is a '${doc.languageId}' file.`]); + return; + } + + if (!selection || selection.isEmpty) { + treeProvider.refresh([]); // Clear sidebar if selection is empty in a Perl file + return; + } + + const selectedText = doc.getText(selection); + + if (selectedText.trim()) { + try { + logInfo("Sending request to backend for alternative suggestions..."); + + const response = await api.post('/altCode/', { code: selectedText }); + + const alternatives = response.data.alternatives || []; + + const suggestionsForSidebar = alternatives.map(item => item.code); + + if (suggestionsForSidebar.length === 0) { + suggestionsForSidebar.push("No specific code suggestions received from AI, or response format was unexpected."); + } + + treeProvider.refresh(suggestionsForSidebar); + logInfo("Sidebar refreshed with backend suggestions."); + + } catch (err) { + logError('Failed to fetch alternative suggestions from backend', err); + + let userFacingErrorMessage = "An unexpected error occurred. Please check the Debug Console for details."; + + if (err.code === 'ECONNREFUSED') { + userFacingErrorMessage = "Error: Backend server is not running. Please start your FastAPI backend."; + } else if (err.response && err.response.data && err.response.data.alternatives && err.response.data.alternatives.length > 0) { + userFacingErrorMessage = `Backend Error: ${err.response.data.alternatives[0].code}`; + } else if (err.message) { + userFacingErrorMessage = `Error fetching suggestions: ${err.message}`; + } + + treeProvider.refresh([userFacingErrorMessage]); + } + } else { + treeProvider.refresh([]); + } + }, config.debounceTime); + context.subscriptions.push( + vscode.window.onDidChangeTextEditorSelection(processSelectionForSidebar) + ); + + context.subscriptions.push( + vscode.commands.registerCommand('perlcodegeneration.showSuggestions', () => { + const dummySuggestions = ['Suggested fix 1', 'Suggested snippet 2']; + treeProvider.refresh(dummySuggestions); // use the same instance! + }) + ); + + context.subscriptions.push( + vscode.commands.registerCommand('perlCodeGen.copySuggestion', async (codeToCopy) => { + await vscode.env.clipboard.writeText(codeToCopy); + vscode.window.showInformationMessage('Suggestion copied to clipboard!'); + }) + ); + + } +exports.activate = activate; + /** * Extension deactivation handler @@ -297,9 +365,23 @@ function deactivate() { outputChannel = null; } } +exports.deactivate = deactivate; function getCodebaseIndexer() { return codebaseIndexer; } -module.exports = { activate, deactivate }; \ No newline at end of file + +function escapeHtml(str) { + return str.replace(/[&<>"']/g, tag => ({ + '&': '&', '<': '<', '>': '>', + '"': '"', "'": ''' + }[tag])); +} + + +module.exports = { + activate, + deactivate, + getCodebaseIndexer +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index da3b7a6..f2a32fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@ganezdragon/tree-sitter-perl": "^1.1.1", "@lancedb/lancedb": "^0.19.0", "@xenova/transformers": "^2.17.2", - "axios": "^1.8.4", + "axios": "^1.10.0", "glob": "^11.0.2", "tree-sitter": "^0.21.0", "vscode-uri": "^3.1.0" @@ -22,7 +22,8 @@ "@types/vscode": "^1.99.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", - "eslint": "^9.23.0" + "eslint": "^9.23.0", + "vscode": "^1.1.37" }, "engines": { "vscode": "^1.99.0" @@ -585,6 +586,16 @@ "tslib": "^2.8.0" } }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/@types/command-line-args": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/command-line-args/-/command-line-args-5.2.3.tgz", @@ -883,9 +894,10 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", - "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -1072,6 +1084,13 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/c8": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", @@ -1395,6 +1414,13 @@ "node": ">=12.17" } }, + "node_modules/commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1593,6 +1619,23 @@ "node": ">= 0.4" } }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2131,6 +2174,16 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.x" + } + }, "node_modules/guid-typescript": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz", @@ -2696,11 +2749,32 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, + "node_modules/mkdirp/node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", + "dev": true, + "license": "MIT" + }, "node_modules/mocha": { "version": "10.8.2", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", @@ -3572,6 +3646,27 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/stdin-discarder": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", @@ -3952,11 +4047,334 @@ "node": ">=10.12.0" } }, + "node_modules/vscode": { + "version": "1.1.37", + "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.37.tgz", + "integrity": "sha512-vJNj6IlN7IJPdMavlQa1KoFB3Ihn06q1AiN3ZFI/HfzPNzbKZWPPuiU+XkpNOfGU5k15m4r80nxNPlM7wcc0wg==", + "deprecated": "This package is deprecated in favor of @types/vscode and vscode-test. For more information please read: https://code.visualstudio.com/updates/v1_36#_splitting-vscode-package-into-typesvscode-and-vscodetest", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^7.1.2", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "mocha": "^5.2.0", + "semver": "^5.4.1", + "source-map-support": "^0.5.0", + "vscode-test": "^0.4.1" + }, + "bin": { + "vscode-install": "bin/install" + }, + "engines": { + "node": ">=8.9.3" + } + }, + "node_modules/vscode-test": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-0.4.3.tgz", + "integrity": "sha512-EkMGqBSefZH2MgW65nY05rdRSko15uvzq4VAPM5jVmwYuFQKE7eikKXNJDRxL+OITXHB6pI+a3XqqD32Y3KC5w==", + "deprecated": "This package has been renamed to @vscode/test-electron, please update to the new name", + "dev": true, + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1" + }, + "engines": { + "node": ">=8.9.3" + } + }, + "node_modules/vscode-test/node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/vscode-test/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/vscode-test/node_modules/http-proxy-agent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "4", + "debug": "3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/vscode-test/node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/vscode-test/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, "node_modules/vscode-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==" }, + "node_modules/vscode/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/vscode/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/vscode/node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/vscode/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/vscode/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/vscode/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/vscode/node_modules/he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/vscode/node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/vscode/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/vscode/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/vscode/node_modules/mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/vscode/node_modules/mocha/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/vscode/node_modules/mocha/node_modules/glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/vscode/node_modules/mocha/node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/vscode/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/vscode/node_modules/supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 10ef184..f9a1cdc 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,10 @@ { "command": "perlcodegeneration.indexCodebase", "title": "Perl: Index Codebase" + }, + { + "command": "perlcodegeneration.showSuggestions", + "title": "Perl: Show Suggestions" } ], "configuration": { @@ -62,6 +66,24 @@ "description": "Number of lines to consider around cursor for context" } } + }, + "viewsContainers": { + "activitybar": [ + { + "id": "perlCodeGen-alternativeSuggestionsContainer", + "title": "Perl Suggestions", + "icon": "$(lightbulb)" + } + ] + }, + "views": { + "perlCodeGen-alternativeSuggestionsContainer": [ + { + "id": "perlCodeGen.alternativeSuggestions", + "name": "", + "icon": "$(lightbulb)" + } + ] } }, "scripts": { @@ -75,15 +97,17 @@ "@types/vscode": "^1.99.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", - "eslint": "^9.23.0" + "eslint": "^9.23.0", + "vscode": "^1.1.37" }, "dependencies": { "@ganezdragon/tree-sitter-perl": "^1.1.1", "@lancedb/lancedb": "^0.19.0", "@xenova/transformers": "^2.17.2", - "axios": "^1.8.4", + "axios": "^1.10.0", "glob": "^11.0.2", "tree-sitter": "^0.21.0", "vscode-uri": "^3.1.0" } } + diff --git a/sidebarprovider.js b/sidebarprovider.js new file mode 100644 index 0000000..d010828 --- /dev/null +++ b/sidebarprovider.js @@ -0,0 +1,70 @@ +const vscode = require('vscode'); + +class AlternativeSuggestionsProvider { + constructor() { + this._onDidChangeTreeData = new vscode.EventEmitter(); + this.onDidChangeTreeData = this._onDidChangeTreeData.event; + this.suggestions = []; + } + + /** + * Refreshes the tree view with new suggestions. + * @param {string[]} suggestions - An array of code suggestion strings. + */ + refresh(suggestions) { + this.suggestions = suggestions; + this._onDidChangeTreeData.fire(); + } + + /** + * Returns the TreeItem for the given element. + * @param {vscode.TreeItem} element - The element for which to return the TreeItem. + * @returns {vscode.TreeItem} + */ + getTreeItem(element) { + // In this setup, getTreeItem is called for the already constructed TreeItems from getChildren, + // so we just return the element itself. + return element; + } + + /** + * Returns the children of the given element or root if no element is passed. + * @param {vscode.TreeItem} element - The parent element (undefined for root). + * @returns {vscode.ProviderResult} + */ + getChildren(element) { + if (element) { + return Promise.resolve([]); + } else { + if (this.suggestions.length === 1 && this.suggestions[0].startsWith("Error:")) { + const errorMessage = this.suggestions[0]; + const item = new vscode.TreeItem("Error occurred!", vscode.TreeItemCollapsibleState.None); + item.tooltip = new vscode.MarkdownString(`**Error Details:**\n\n\`\`\`\n${errorMessage}\n\`\`\``); + item.command = undefined; + return Promise.resolve([item]); + } + + // Return the top-level suggestions (for actual code suggestions) + return Promise.resolve( + this.suggestions.map((suggestion, index) => { + const label = `Suggestion ${index + 1}`; + const description = suggestion.split('\n')[0].trim(); + + const item = new vscode.TreeItem(label, vscode.TreeItemCollapsibleState.None); + item.description = description; + item.tooltip = new vscode.MarkdownString(`\`\`\`perl\n${suggestion}\n\`\`\``); + + item.command = { + command: 'perlCodeGen.copySuggestion', + title: 'Copy Suggestion', + arguments: [suggestion] + }; + + return item; + }) + ); + } + } +} + +module.exports = { AlternativeSuggestionsProvider }; \ No newline at end of file From f5cfa710e800129b2f146af13dffa18beee0f11b Mon Sep 17 00:00:00 2001 From: "R.Sinthujan" Date: Fri, 18 Jul 2025 17:51:16 +0530 Subject: [PATCH 3/7] minor issues resolved --- api/api.js | 4 +- extension.js | 198 +++++++++++++++++++++++++++++++++++++++---- utils/checkErrors.js | 19 +++++ 3 files changed, 204 insertions(+), 17 deletions(-) create mode 100644 utils/checkErrors.js diff --git a/api/api.js b/api/api.js index c4c5000..7a792d2 100644 --- a/api/api.js +++ b/api/api.js @@ -1,4 +1,6 @@ const axios = require("axios"); module.exports = axios.create({ baseURL: "http://127.0.0.1:8000", -}); \ No newline at end of file +}); + + diff --git a/extension.js b/extension.js index 09195be..9b81f7f 100644 --- a/extension.js +++ b/extension.js @@ -9,6 +9,7 @@ const debounce = require('./utils/debounce'); const { PerlCodebaseIndexer } = require('./indexers/codebaseIndexer'); const { PerlRepositoryMapProvider } = require('./collectors/repoMapProvider'); const { registerCommands } = require('./commands/commands') +const checkCodeForErrors = require('./utils/checkErrors') /** * Global extension configuration @@ -16,34 +17,152 @@ const { registerCommands } = require('./commands/commands') const config = { // Default values, will be overridden by user settings relevantCodeCount: 2, + // NEW: Debounce time for error checking (in milliseconds) + errorCheckDebounceTime: 2000, // 2 seconds, you can set this to 10000 for 10 seconds useMemoryIndex:true, indexOnStartup: true, contextWindowSize: 15, // lines around cursor to consider for context }; -// Global state -let codebaseIndexer = null; -let outputChannel = null; -let debounceTime = 2000; +// Global state for tracking in-flight requests +const inFlightRequests = new Map(); + + /** - * Fetches code suggestion based on a comment + * Creates a unique key for a request based on comment and context * @param {string} comment - The user's comment * @param {vscode.TextDocument} doc - Current document * @param {vscode.Position} pos - Current cursor position - * @returns {Promise} Generated code suggestion + * @returns {string} Unique key for the request */ -async function fetchCode(comment, doc, pos) { - try { - const ctx = await generateContextForComments(comment, doc, pos); - const response = await api.post('/commentCode/', { message: comment, context: ctx }); - return response.data.code; - } catch (err) { - logError(`Error fetching suggestion: ${err.message}`, err); - vscode.window.showErrorMessage(`Failed to generate code: ${err.message}`); - return null; +function createRequestKey(comment, doc, pos) { + // Create a unique key that represents this specific request + return `${doc.fileName}:${pos.line}:${pos.character}:${comment.trim()}`; +} + +// Global state +let codebaseIndexer = null; +let outputChannel = null; +let debounceTime = 3000; +// NEW: Diagnostic collection for displaying errors +let errorDiagnostics = null; +// /** +// * Fetches code suggestion based on a comment +// * @param {string} comment - The user's comment +// * @param {vscode.TextDocument} doc - Current document +// * @param {vscode.Position} pos - Current cursor position +// * @returns {Promise} Generated code suggestion +// */ +// async function fetchCode(comment, doc, pos) { +// try { +// const ctx = await generateContextForComments(comment, doc, pos); +// const response = await api.post('/commentCode/', { message: comment, context: ctx }); +// return response.data.code; +// } catch (err) { +// logError(`Error fetching suggestion: ${err.message}`, err); +// vscode.window.showErrorMessage(`Failed to generate code: ${err.message}`); +// return null; +// } +// } + +async function fetchCodeWithDeduplication(comment, doc, pos) { + const requestKey = createRequestKey(comment, doc, pos); + + // Check if we already have a request in progress for this exact same input + if (inFlightRequests.has(requestKey)) { + logDebug(`Returning existing promise for request: ${requestKey}`); + return inFlightRequests.get(requestKey); } + + // Create new promise for this request + const requestPromise = (async () => { + try { + logDebug(`Starting new request: ${requestKey}`); + const ctx = await generateContextForComments(comment, doc, pos); + const response = await api.post('/commentCode/', { message: comment, context: ctx }); + logDebug(`Request completed: ${requestKey}`); + return response.data.code; + } catch (err) { + logError(`Error fetching suggestion for ${requestKey}: ${err.message}`, err); + vscode.window.showErrorMessage(`Failed to generate code: ${err.message}`); + return null; + } finally { + // Always clean up the request from the map when it's done + inFlightRequests.delete(requestKey); + logDebug(`Cleaned up request: ${requestKey}`); + } + })(); + + // Store the promise in our map + inFlightRequests.set(requestKey, requestPromise); + + return requestPromise; +} + + +/** + * NEW: Analyzes the entire document for errors and updates diagnostics + * @param {vscode.TextDocument} doc - The document to check + */ +async function updateErrorDiagnostics(doc) { + if (doc.languageId !== 'perl') { + return; // Only check Perl files + } + + logInfo(`Running error check for: ${doc.fileName}`); + try { + const code = doc.getText(); + const lines = code.split('\n'); + + // Create array of lines with their line numbers, including empty lines + // Create formatted string with padded line numbers + const totalLines = lines.length; + const padding = totalLines.toString().length; + const linesWithNumbers = lines + .map((text, index) => { + const lineNumber = (index + 1).toString().padStart(padding, ' '); + return `${lineNumber}: ${text}`; + }) + .join('\n'); + + + + // Send code with line numbers to backend + const response = await checkCodeForErrors(linesWithNumbers); + const errors = response.data.errors; // Assuming the errors are in response.data.errors + + if (!Array.isArray(errors)) { + logError("Received invalid error format from API.", errors); + return; + } + + const diagnostics = errors.map(error => { + // VS Code lines are 0-indexed, API might return 1-indexed + const line = Math.max(0, error.line - 1); + const startChar = error.start || 0; + const endChar = error.end || Math.max(startChar + 1, lines[line]?.length || 0); + + const range = new vscode.Range( + new vscode.Position(line, startChar), + new vscode.Position(line, endChar) + ); + + const diagnostic = new vscode.Diagnostic(range, error.message, vscode.DiagnosticSeverity.Error); + diagnostic.source = 'Perl AI Assistant'; + return diagnostic; + }); + + errorDiagnostics.set(doc.uri, diagnostics); + logInfo(`Found ${diagnostics.length} errors.`); + + } catch (err) { + logInfo(`Failed to check for errors: ${err.message}`, err); + // Do not show an error message to the user to avoid being disruptive + } } + + /** * Collects and generates context for AI code generation * @param {string} comment - The user's comment @@ -226,8 +345,55 @@ async function activate(context) { }); }, 300); + // Create debounced version of fetchCode - const debouncedFetch = debounce(fetchCode, debounceTime); + const debouncedFetch = debounce(fetchCodeWithDeduplication, debounceTime); + + // NEW: Create a debounced version of the error checker + const debouncedErrorCheck = debounce( + (doc) => updateErrorDiagnostics(doc), + config.errorCheckDebounceTime + ); + logInfo("Step 4: Debounced functions created."); + + // NEW: Initialize Diagnostics Collection + logInfo("Step 5: Initializing diagnostics collection..."); + errorDiagnostics = vscode.languages.createDiagnosticCollection("perl-ai-errors"); + context.subscriptions.push(errorDiagnostics); + logInfo("Step 5: Diagnostics collection initialized."); + + // NEW: Register event listener for when a text document is changed + logInfo("Step 6: Registering event listeners..."); + context.subscriptions.push( + vscode.workspace.onDidChangeTextDocument(event => { + if (vscode.window.activeTextEditor && event.document === vscode.window.activeTextEditor.document) { + debouncedErrorCheck(event.document); + } + }) + ); + + // NEW: Register event listener for when the active editor changes + context.subscriptions.push( + vscode.window.onDidChangeActiveTextEditor(editor => { + if (editor) { + // Trigger an immediate check when switching to a new file + debouncedErrorCheck(editor.document); + } + }) + ); + logInfo("Step 6: Event listeners registered."); + + // NEW: Clear diagnostics when a document is closed + context.subscriptions.push( + vscode.workspace.onDidCloseTextDocument(doc => errorDiagnostics.delete(doc.uri)) + ); + + // Initial check for the currently active file, if any + logInfo("Step 7: Performing initial check for active editor..."); + if (vscode.window.activeTextEditor) { + debouncedErrorCheck(vscode.window.activeTextEditor.document); + } + logInfo("Step 7: Initial check complete."); // Register configuration change listener context.subscriptions.push( diff --git a/utils/checkErrors.js b/utils/checkErrors.js new file mode 100644 index 0000000..7b22103 --- /dev/null +++ b/utils/checkErrors.js @@ -0,0 +1,19 @@ +const api = require("../api/api") + +/** + * Sends Perl code to the backend to be checked for errors. + * This function uses the pre-configured 'api' instance. + * @param {string} code - The Perl code string to analyze. + * @returns {Promise} The full response from the API. The actual data + * will be in the .data property of the response, + * which is expected to contain an 'errors' array. + */ +const checkCodeForErrors = (code) => { + // 2. Use the 'api' instance to make the POST request. + // The endpoint '/checkErrors/' will be appended to the baseURL. + return api.post('/checkErrors/', { code }); +}; + +// 3. Export both the 'api' instance for general use and the specific +// 'checkCodeForErrors' function for its dedicated task. +module.exports =checkCodeForErrors; \ No newline at end of file From 9d40ce5de8d46d12f78703d4665fd9536df64ad1 Mon Sep 17 00:00:00 2001 From: "R.Sinthujan" Date: Fri, 18 Jul 2025 19:21:23 +0530 Subject: [PATCH 4/7] minor issues resolved --- extension.js | 246 +++++++++++++++++++++++-------------------- utils/checkErrors.js | 16 +-- 2 files changed, 141 insertions(+), 121 deletions(-) diff --git a/extension.js b/extension.js index 8f0cc0a..657912d 100644 --- a/extension.js +++ b/extension.js @@ -44,8 +44,11 @@ function createRequestKey(comment, doc, pos) { let codebaseIndexer = null; let outputChannel = null; let debounceTime = 3000; +let errorCheckDebounceTime = 2000; // NEW: Diagnostic collection for displaying errors let errorDiagnostics = null; +let errorCheckAbortController = new AbortController(); + // /** // * Fetches code suggestion based on a comment // * @param {string} comment - The user's comment @@ -101,68 +104,69 @@ async function fetchCodeWithDeduplication(comment, doc, pos) { /** - * NEW: Analyzes the entire document for errors and updates diagnostics - * @param {vscode.TextDocument} doc - The document to check + * Analyzes the document for errors and underlines the entire line. + * @param {vscode.TextDocument} doc - The document to check. */ async function updateErrorDiagnostics(doc) { - if (doc.languageId !== 'perl') { - return; // Only check Perl files - } + if (doc.languageId !== 'perl') return; - logInfo(`Running error check for: ${doc.fileName}`); - try { - const code = doc.getText(); - const lines = code.split('\n'); - - // Create array of lines with their line numbers, including empty lines - // Create formatted string with padded line numbers - const totalLines = lines.length; - const padding = totalLines.toString().length; - const linesWithNumbers = lines - .map((text, index) => { - const lineNumber = (index + 1).toString().padStart(padding, ' '); - return `${lineNumber}: ${text}`; - }) - .join('\n'); - + // Cancel any previous, still-running check to prevent "ghost errors" + errorCheckAbortController.abort(); + errorCheckAbortController = new AbortController(); + const signal = errorCheckAbortController.signal; - - // Send code with line numbers to backend - const response = await checkCodeForErrors(linesWithNumbers); - const errors = response.data.errors; // Assuming the errors are in response.data.errors + logInfo(`Running error check for: ${doc.fileName}`); + try { + const code = doc.getText(); + const response = await checkCodeForErrors(code, signal); + const errors = response.data.errors; - if (!Array.isArray(errors)) { - logError("Received invalid error format from API.", errors); - return; - } + if (!Array.isArray(errors)) { + logError("Received invalid error format from API.", errors); + return; + } - const diagnostics = errors.map(error => { - // VS Code lines are 0-indexed, API might return 1-indexed - const line = Math.max(0, error.line - 1); - const startChar = error.start || 0; - const endChar = error.end || Math.max(startChar + 1, lines[line]?.length || 0); - - const range = new vscode.Range( - new vscode.Position(line, startChar), - new vscode.Position(line, endChar) - ); - - const diagnostic = new vscode.Diagnostic(range, error.message, vscode.DiagnosticSeverity.Error); - diagnostic.source = 'Perl AI Assistant'; - return diagnostic; - }); + // --- NEW LOGIC: Underline the entire line --- + const diagnostics = errors.map(error => { + // The API now returns only line and message. + const { line: errorLine, message } = error; + + // Convert 1-based line from AI to 0-based for VS Code. + const lineIndex = Math.max(0, errorLine - 1); + + if (lineIndex >= doc.lineCount) { + logError(`API returned invalid line number: ${errorLine}`); + return null; // Skip this error if the line doesn't exist + } - errorDiagnostics.set(doc.uri, diagnostics); - logInfo(`Found ${diagnostics.length} errors.`); + const lineText = doc.lineAt(lineIndex); + // Create a range that covers the entire line, from the first character to the last. + const range = new vscode.Range( + new vscode.Position(lineIndex, 0), + new vscode.Position(lineIndex, lineText.text.length) + ); + + const diagnostic = new vscode.Diagnostic(range, message, vscode.DiagnosticSeverity.Error); + diagnostic.source = 'Perl AI Assistant'; + return diagnostic; + }).filter(diag => diag !== null); // Filter out any null diagnostics - } catch (err) { - logInfo(`Failed to check for errors: ${err.message}`, err); - // Do not show an error message to the user to avoid being disruptive + errorDiagnostics.set(doc.uri, diagnostics); + logInfo(`Found ${diagnostics.length} errors.`); + + } catch (err) { + // If the error was due to cancellation, it's expected, so we just log it quietly. + if (err.name === 'CanceledError' || err.name === 'AbortError') { + logInfo('Error check was cancelled because a new one was started.'); + } else { + logInfo(`Failed to check for errors: ${err.message}`); } + } } + /** * Collects and generates context for AI code generation * @param {string} comment - The user's comment @@ -344,21 +348,14 @@ async function activate(context) { // Create debounced version of fetchCode const debouncedFetch = debounce(fetchCodeWithDeduplication, debounceTime); - // NEW: Create a debounced version of the error checker const debouncedErrorCheck = debounce( (doc) => updateErrorDiagnostics(doc), - config.errorCheckDebounceTime + errorCheckDebounceTime ); - logInfo("Step 4: Debounced functions created."); - // NEW: Initialize Diagnostics Collection - logInfo("Step 5: Initializing diagnostics collection..."); errorDiagnostics = vscode.languages.createDiagnosticCollection("perl-ai-errors"); context.subscriptions.push(errorDiagnostics); - logInfo("Step 5: Diagnostics collection initialized."); - // NEW: Register event listener for when a text document is changed - logInfo("Step 6: Registering event listeners..."); context.subscriptions.push( vscode.workspace.onDidChangeTextDocument(event => { if (vscode.window.activeTextEditor && event.document === vscode.window.activeTextEditor.document) { @@ -367,38 +364,32 @@ async function activate(context) { }) ); - // NEW: Register event listener for when the active editor changes context.subscriptions.push( vscode.window.onDidChangeActiveTextEditor(editor => { if (editor) { - // Trigger an immediate check when switching to a new file debouncedErrorCheck(editor.document); } }) ); - logInfo("Step 6: Event listeners registered."); - // NEW: Clear diagnostics when a document is closed context.subscriptions.push( vscode.workspace.onDidCloseTextDocument(doc => errorDiagnostics.delete(doc.uri)) ); - // Initial check for the currently active file, if any - logInfo("Step 7: Performing initial check for active editor..."); if (vscode.window.activeTextEditor) { debouncedErrorCheck(vscode.window.activeTextEditor.document); } - logInfo("Step 7: Initial check complete."); context.subscriptions.push( vscode.workspace.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('perlCodeGeneration')) { + if (e.affectsConfiguration('perlCodeGeneration')) { // FIX: Corrected typo 'perlCodegeneration' loadConfiguration(); logInfo("Configuration updated", config); } }) ); + const inlineCompletionProvider = { async provideInlineCompletionItems(doc, pos) { const line = doc.lineAt(pos).text; @@ -438,61 +429,90 @@ async function activate(context) { logInfo("Extension setup complete"); - const processSelectionForSidebar = debounce(async (event) => { - const selection = event.selections[0]; - const doc = event.textEditor.document; - - // Handle non-Perl files first - if (doc.languageId !== 'perl') { - logInfo(`Skipping suggestion for non-Perl file: ${doc.languageId}`); - // Display an error message if it's not a Perl file - treeProvider.refresh([`Error: Only Perl code suggestions are supported. Current file is a '${doc.languageId}' file.`]); - return; - } - - if (!selection || selection.isEmpty) { - treeProvider.refresh([]); // Clear sidebar if selection is empty in a Perl file - return; - } - - const selectedText = doc.getText(selection); +// Add these variables at the top with other global state +let lastSelectedText = ''; +let lastSuggestions = []; + +// Replace the processSelectionForSidebar function with this improved version +const processSelectionForSidebar = debounce(async (event) => { + const selection = event.selections[0]; + const doc = event.textEditor.document; + + // Enhanced Perl file detection - check both languageId and file extension + const isPerlFile = doc.languageId === 'perl' || + doc.fileName.endsWith('.pl') || + doc.fileName.endsWith('.pm') || + doc.fileName.endsWith('.t'); + + // Handle non-Perl files first + if (!isPerlFile) { + logInfo(`Skipping suggestion for non-Perl file: ${doc.languageId}, filename: ${doc.fileName}`); + treeProvider.refresh([`Error: Only Perl code suggestions are supported. Current file is a '${doc.languageId}' file (${doc.fileName}).`]); + return; + } + + if (!selection || selection.isEmpty) { + // Don't clear immediately - only clear if we had no previous selection + if (lastSelectedText === '') { + treeProvider.refresh([]); + } + return; + } - if (selectedText.trim()) { - try { - logInfo("Sending request to backend for alternative suggestions..."); - - const response = await api.post('/altCode/', { code: selectedText }); - - const alternatives = response.data.alternatives || []; - - const suggestionsForSidebar = alternatives.map(item => item.code); + const selectedText = doc.getText(selection); + + // If the selected text is the same as last time, don't make a new request + if (selectedText.trim() === lastSelectedText.trim() && lastSuggestions.length > 0) { + logInfo("Using cached suggestions for same selection"); + treeProvider.refresh(lastSuggestions); + return; + } - if (suggestionsForSidebar.length === 0) { - suggestionsForSidebar.push("No specific code suggestions received from AI, or response format was unexpected."); - } + if (selectedText.trim()) { + try { + logInfo("Sending request to backend for alternative suggestions..."); + + const response = await api.post('/altCode/', { code: selectedText }); + + const alternatives = response.data.alternatives || []; + + const suggestionsForSidebar = alternatives.map(item => item.code); - treeProvider.refresh(suggestionsForSidebar); - logInfo("Sidebar refreshed with backend suggestions."); + if (suggestionsForSidebar.length === 0) { + suggestionsForSidebar.push("No specific code suggestions received from AI, or response format was unexpected."); + } - } catch (err) { - logError('Failed to fetch alternative suggestions from backend', err); - - let userFacingErrorMessage = "An unexpected error occurred. Please check the Debug Console for details."; - - if (err.code === 'ECONNREFUSED') { - userFacingErrorMessage = "Error: Backend server is not running. Please start your FastAPI backend."; - } else if (err.response && err.response.data && err.response.data.alternatives && err.response.data.alternatives.length > 0) { - userFacingErrorMessage = `Backend Error: ${err.response.data.alternatives[0].code}`; - } else if (err.message) { - userFacingErrorMessage = `Error fetching suggestions: ${err.message}`; - } + // Cache the results + lastSelectedText = selectedText.trim(); + lastSuggestions = suggestionsForSidebar; + + treeProvider.refresh(suggestionsForSidebar); + logInfo("Sidebar refreshed with backend suggestions."); - treeProvider.refresh([userFacingErrorMessage]); + } catch (err) { + logError('Failed to fetch alternative suggestions from backend', err); + + let userFacingErrorMessage = "An unexpected error occurred. Please check the Debug Console for details."; + + if (err.code === 'ECONNREFUSED') { + userFacingErrorMessage = "Error: Backend server is not running. Please start your FastAPI backend."; + } else if (err.response && err.response.data && err.response.data.alternatives && err.response.data.alternatives.length > 0) { + userFacingErrorMessage = `Backend Error: ${err.response.data.alternatives[0].code}`; + } else if (err.message) { + userFacingErrorMessage = `Error fetching suggestions: ${err.message}`; } - } else { - treeProvider.refresh([]); + + treeProvider.refresh([userFacingErrorMessage]); } - }, config.debounceTime); + } else { + // Only clear if we're moving away from a selection + if (lastSelectedText !== '') { + lastSelectedText = ''; + lastSuggestions = []; + treeProvider.refresh([]); + } + } +}, debounceTime); context.subscriptions.push( vscode.window.onDidChangeTextEditorSelection(processSelectionForSidebar) ); diff --git a/utils/checkErrors.js b/utils/checkErrors.js index 7b22103..c987bd1 100644 --- a/utils/checkErrors.js +++ b/utils/checkErrors.js @@ -2,18 +2,18 @@ const api = require("../api/api") /** * Sends Perl code to the backend to be checked for errors. - * This function uses the pre-configured 'api' instance. + * This function now accepts an AbortSignal to allow for request cancellation. * @param {string} code - The Perl code string to analyze. - * @returns {Promise} The full response from the API. The actual data - * will be in the .data property of the response, - * which is expected to contain an 'errors' array. + * @param {AbortSignal} signal - The signal to cancel the request. + * @returns {Promise} The full response from the API. */ -const checkCodeForErrors = (code) => { - // 2. Use the 'api' instance to make the POST request. - // The endpoint '/checkErrors/' will be appended to the baseURL. - return api.post('/checkErrors/', { code }); +const checkCodeForErrors = (code, signal) => { + // Use the correct endpoint and pass the signal. + // If the signal is aborted elsewhere, Axios will cancel this request. + return api.post('/checkErrors/', { code }, { signal }); }; + // 3. Export both the 'api' instance for general use and the specific // 'checkCodeForErrors' function for its dedicated task. module.exports =checkCodeForErrors; \ No newline at end of file From fefe141a8132d4e027c35acec56b28d59358c226 Mon Sep 17 00:00:00 2001 From: "R.Sinthujan" Date: Mon, 21 Jul 2025 21:02:54 +0530 Subject: [PATCH 5/7] error handling improved --- extension.js | 187 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 165 insertions(+), 22 deletions(-) diff --git a/extension.js b/extension.js index 657912d..b6ddd3c 100644 --- a/extension.js +++ b/extension.js @@ -49,6 +49,29 @@ let errorCheckDebounceTime = 2000; let errorDiagnostics = null; let errorCheckAbortController = new AbortController(); + +/** + * Helper function to find the location of a code chunk in the document. + * @param {vscode.TextDocument} doc The document to search in. + * @param {string} chunk The code chunk to find. + * @returns {vscode.Range | null} The range of the chunk, or null if not found. + */ +function findChunkLocation(doc, chunk) { + const fullText = doc.getText(); + const normalizedText = fullText.replace(/\r\n/g, "\n"); + const startIndex = normalizedText.indexOf(chunk); + + if (startIndex === -1) { + return null; + } + + const startPos = doc.positionAt(startIndex); + const endPos = doc.positionAt(startIndex + chunk.length); + + return new vscode.Range(startPos, endPos); +} + + // /** // * Fetches code suggestion based on a comment // * @param {string} comment - The user's comment @@ -102,15 +125,40 @@ async function fetchCodeWithDeduplication(comment, doc, pos) { return requestPromise; } +// async function fetchCodeCompletion(doc, pos) { +// try { + +// // Get the current line up to cursor position +// const line = doc.lineAt(pos); +// const currentLineText = line.text.substring(0, pos.character); + +// const ctx = await generateContextForComments(currentLineText, doc, pos); + +// const response = await api.post('/commentCode/complete/', { +// codePrefix: ctx.codePrefix +"\n"+currentLineText, +// codeSuffix: ctx.codeSuffix, +// imports: ctx.imports, +// usedModules: ctx.usedModules, +// variableDefinitions: ctx.variableDefinitions, +// importDefinitions: ctx.importDefinitions, +// relatedCodeStructures: ctx.relatedCodeStructures, +// currentBlock: ctx.currentBlock +// }); + +// return response.data.code; +// } catch (err) { +// logError(`Error fetching completion: ${err.message}`, err); +// return null; +// } +// } /** - * Analyzes the document for errors and underlines the entire line. + * Analyzes the document for errors by finding the code chunk and underlining it. * @param {vscode.TextDocument} doc - The document to check. */ async function updateErrorDiagnostics(doc) { if (doc.languageId !== 'perl') return; - // Cancel any previous, still-running check to prevent "ghost errors" errorCheckAbortController.abort(); errorCheckAbortController = new AbortController(); const signal = errorCheckAbortController.signal; @@ -126,40 +174,37 @@ async function updateErrorDiagnostics(doc) { return; } - // --- NEW LOGIC: Underline the entire line --- + logInfo(`Extension Info: Received ${errors.length} errors from the backend.`); + const diagnostics = errors.map(error => { - // The API now returns only line and message. - const { line: errorLine, message } = error; + // Assuming the API returns a 'code_chunk' and a 'message' + const { code_chunk, message } = error; - // Convert 1-based line from AI to 0-based for VS Code. - const lineIndex = Math.max(0, errorLine - 1); - - if (lineIndex >= doc.lineCount) { - logError(`API returned invalid line number: ${errorLine}`); - return null; // Skip this error if the line doesn't exist + if (!code_chunk) { + logError("API response did not contain a 'code_chunk'.", error); + return null; } - const lineText = doc.lineAt(lineIndex); - // Create a range that covers the entire line, from the first character to the last. - const range = new vscode.Range( - new vscode.Position(lineIndex, 0), - new vscode.Position(lineIndex, lineText.text.length) - ); + const range = findChunkLocation(doc, code_chunk); + + if (!range) { + logError(`Could not find the code chunk in the document., { chunk: code_chunk }`); + return null; + } const diagnostic = new vscode.Diagnostic(range, message, vscode.DiagnosticSeverity.Error); diagnostic.source = 'Perl AI Assistant'; return diagnostic; - }).filter(diag => diag !== null); // Filter out any null diagnostics + }).filter(diag => diag !== null); errorDiagnostics.set(doc.uri, diagnostics); - logInfo(`Found ${diagnostics.length} errors.`); + logInfo(`Displaying ${diagnostics.length} valid errors in the editor.`); } catch (err) { - // If the error was due to cancellation, it's expected, so we just log it quietly. if (err.name === 'CanceledError' || err.name === 'AbortError') { logInfo('Error check was cancelled because a new one was started.'); } else { - logInfo(`Failed to check for errors: ${err.message}`); + logInfo(`Failed to check for errors: ${err.message}, err`); } } } @@ -167,6 +212,7 @@ async function updateErrorDiagnostics(doc) { + /** * Collects and generates context for AI code generation * @param {string} comment - The user's comment @@ -380,6 +426,8 @@ async function activate(context) { debouncedErrorCheck(vscode.window.activeTextEditor.document); } + + context.subscriptions.push( vscode.workspace.onDidChangeConfiguration(e => { if (e.affectsConfiguration('perlCodeGeneration')) { // FIX: Corrected typo 'perlCodegeneration' @@ -412,6 +460,88 @@ async function activate(context) { }; } }; + +// const codeCompletionProvider = { +// async provideCompletionItems(doc, pos, token, context) { +// // Only provide completions for Perl files +// if (doc.languageId !== 'perl' && +// !doc.fileName.endsWith('.pl') && +// !doc.fileName.endsWith('.pm') && +// !doc.fileName.endsWith('.t')) { +// return { items: [] }; +// } + +// const line = doc.lineAt(pos); +// const currentLineText = line.text.substring(0, pos.character); +// const fullLineText = line.text; + +// // Skip if line is empty, comment, or already complete +// if (!currentLineText.trim() || +// currentLineText.trim().startsWith('#') || +// this.isLineComplete(fullLineText)) { +// return { items: [] }; +// } + +// // Only trigger on meaningful patterns +// if (!this.shouldTriggerCompletion(currentLineText, context)) { +// return { items: [] }; +// } + +// try { +// const completion = await fetchCodeCompletion(doc, pos); + +// if (!completion) { +// return { items: [] }; +// } + +// // Create completion item +// const item = new vscode.CompletionItem( +// completion, +// vscode.CompletionItemKind.Text +// ); + +// item.insertText = completion; +// item.detail = 'Perl Code Completion'; +// item.documentation = 'Generated by Perl AI Assistant'; + +// // Set sort text to prioritize this completion +// item.sortText = '0000'; + +// return { items: [item] }; + +// } catch (err) { +// logError('Error in code completion:', err); +// return { items: [] }; +// } +// }, + +// shouldTriggerCompletion(text, context) { +// // Method/property access +// if (text.match(/[\$@%]\w+\.|::|->$/)) return true; + +// // Variable declarations +// if (text.match(/\b(my|our|local)\s+[\$@%]\w*$/)) return true; + +// // Control structures +// if (text.match(/\b(if|while|for|foreach|unless|until)\s*\(?\s*$/)) return true; + +// // Function calls +// if (text.match(/\w+\s*\($/)) return true; + +// // Hash/array access +// if (text.match(/[\$@%]\w+[\[\{]$/)) return true; + +// // Only trigger after typing at least 2 characters +// return text.trim().length >= 2; +// }, + +// isLineComplete(line) { +// // Check if line ends with complete statement +// return line.trim().endsWith(';') || +// line.trim().endsWith('}') || +// line.trim().endsWith('{'); +// } +// }; context.subscriptions.push( vscode.languages.registerInlineCompletionItemProvider( @@ -419,6 +549,19 @@ async function activate(context) { inlineCompletionProvider ) ); + + // // Register the code completion provider + // context.subscriptions.push( + // vscode.languages.registerCompletionItemProvider( + // { pattern: '**/*.{pl,pm,t}' }, + // codeCompletionProvider, + // // Trigger characters - these will activate the completion + // '.', ':', '$', '@', '%', '(', '{', '[', ' ', '-', '>' + // ) + // ); + + + const treeProvider = new AlternativeSuggestionsProvider(); const treeView = vscode.window.createTreeView('perlCodeGen.alternativeSuggestions', { treeDataProvider: treeProvider @@ -446,7 +589,7 @@ const processSelectionForSidebar = debounce(async (event) => { // Handle non-Perl files first if (!isPerlFile) { - logInfo(`Skipping suggestion for non-Perl file: ${doc.languageId}, filename: ${doc.fileName}`); + // logInfo(`Skipping suggestion for non-Perl file: ${doc.languageId}, filename: ${doc.fileName}`); treeProvider.refresh([`Error: Only Perl code suggestions are supported. Current file is a '${doc.languageId}' file (${doc.fileName}).`]); return; } From 0efbd4d4936f11c53ec5797829b799eeedeca828 Mon Sep 17 00:00:00 2001 From: "R.Sinthujan" Date: Tue, 9 Sep 2025 21:53:38 +0530 Subject: [PATCH 6/7] Update README.md to enhance documentation and clarify features --- README.md | 297 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 238 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 23673aa..43d3fb4 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,244 @@ -# Perl Code Generator README +# Perl Programming Assistant - VS Code Extension + +A powerful VS Code extension that provides AI-powered assistance for Perl development, featuring intelligent code suggestions, error detection, and context-aware recommendations. + +## ๐Ÿš€ Features + +### ๐Ÿค– AI-Powered Code Generation +- **Comment-to-Code**: Write comments starting with `#` and get intelligent code suggestions +- **Context-Aware Suggestions**: Leverages your project structure, imports, and variable definitions for accurate code generation +- **Smart Caching**: Prevents duplicate requests for the same comments, improving performance + +### ๐Ÿ“‹ Alternative Code Suggestions +- **Sidebar Integration**: Select any code block to see alternative implementations in the sidebar +- **Copy to Clipboard**: One-click copying of suggested code alternatives +- **Real-time Updates**: Suggestions update automatically as you select different code blocks + +### ๐Ÿ” Intelligent Error Detection +- **Real-time Analysis**: Automatic error detection as you type with configurable debounce timing +- **Visual Indicators**: Errors are highlighted directly in your code with detailed descriptions +- **AI-Powered**: Uses advanced AI to detect logical and syntactical issues beyond traditional linting + +### ๐Ÿ“š Advanced Code Understanding +- **Project Structure Analysis**: Automatically indexes your Perl codebase for better context +- **Import Resolution**: Tracks and resolves module imports and dependencies +- **Variable Definition Tracking**: Understands variable scope and definitions across files +- **Symbol Usage Analysis**: Identifies how symbols are used throughout your project + +## ๐Ÿ“ฆ Installation + +1. **Install the Extension** + ```bash + # Clone the repository + git clone + cd git_check/VsCodeExtension + + # Install dependencies + npm install + ``` + +2. **Package the Extension** + ```bash + # Install vsce if you haven't already + npm install -g vsce + + # Package the extension + vsce package + ``` + +3. **Install in VS Code** + - Open VS Code + - Go to Extensions (Ctrl+Shift+X) + - Click the "..." menu and select "Install from VSIX..." + - Select the generated `.vsix` file + +## ๐Ÿ› ๏ธ Prerequisites + +### Backend Server +This extension requires the Programming Assistant AI Bot backend server to be running: + +```bash +# Start the backend server (typically on port 8000) +cd ../backend +python -m uvicorn main:app --reload +``` + +### Supported File Types +- `.pl` - Perl scripts +- `.pm` - Perl modules +- `.t` - Perl test files + +## โš™๏ธ Configuration + +Access settings through `File > Preferences > Settings` and search for "Perl Code Generation": + +### Available Settings + +| Setting | Default | Description | +|---------|---------|-------------| +| `perlCodeGeneration.relevantCodeCount` | `3` | Number of relevant code examples to retrieve for context | +| `perlCodeGeneration.indexOnStartup` | `true` | Automatically index the Perl codebase when extension activates | +| `perlCodeGeneration.contextWindowSize` | `15` | Number of lines to consider around cursor for context | +| `perlCodeGeneration.useMemoryIndex` | `true` | Use in-memory index instead of LanceDB for module resolution | + +### Example Configuration +```json +{ + "perlCodeGeneration.relevantCodeCount": 5, + "perlCodeGeneration.indexOnStartup": true, + "perlCodeGeneration.contextWindowSize": 20, + "perlCodeGeneration.useMemoryIndex": true +} +``` + +## ๐ŸŽฏ Usage + +### Comment-to-Code Generation + +1. **Write a Comment**: Start any line with `#` followed by your description + ```perl + # Create a subroutine that validates email addresses + ``` + +2. **Get Suggestions**: The extension will automatically generate code based on your comment + ```perl + # Create a subroutine that validates email addresses + sub validate_email { + my ($email) = @_; + return $email =~ /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + } + ``` + +### Alternative Code Suggestions + +1. **Select Code**: Highlight any block of Perl code +2. **View Sidebar**: Check the "Perl Code Suggestions" panel in the sidebar +3. **Copy Alternatives**: Click on any suggestion to copy it to your clipboard + +### Error Detection + +- **Automatic Detection**: Errors are automatically detected as you type +- **Visual Feedback**: Problematic code is underlined with error descriptions +- **Configurable Timing**: Adjust detection sensitivity in settings + +## ๐Ÿ—๏ธ Project Structure + +``` +VsCodeExtension/ +โ”œโ”€โ”€ extension.js # Main extension entry point +โ”œโ”€โ”€ package.json # Extension manifest and configuration +โ”œโ”€โ”€ api/ +โ”‚ โ””โ”€โ”€ api.js # Backend API communication +โ”œโ”€โ”€ collectors/ # Context and code analysis +โ”‚ โ”œโ”€โ”€ contextCollector.js +โ”‚ โ”œโ”€โ”€ definitionCollector.js +โ”‚ โ”œโ”€โ”€ importDefinitionAnalyzer.js +โ”‚ โ”œโ”€โ”€ perlImportAnalyzer.js +โ”‚ โ””โ”€โ”€ repoMapProvider.js +โ”œโ”€โ”€ commands/ +โ”‚ โ””โ”€โ”€ commands.js # VS Code command implementations +โ”œโ”€โ”€ indexers/ # Codebase indexing and search +โ”‚ โ”œโ”€โ”€ codebaseIndexer.js +โ”‚ โ”œโ”€โ”€ codeStructureIndex.js +โ”‚ โ””โ”€โ”€ vectorIndex.js +โ”œโ”€โ”€ parsers/ +โ”‚ โ””โ”€โ”€ treeSitter.js # Tree-sitter parser integration +โ”œโ”€โ”€ utils/ +โ”‚ โ”œโ”€โ”€ checkErrors.js # Error detection utilities +โ”‚ โ””โ”€โ”€ debounce.js # Performance optimization +โ””โ”€โ”€ test/ + โ””โ”€โ”€ extension.test.js # Unit tests +``` + +## ๐Ÿ”ง Available Commands + +Access commands through the Command Palette (Ctrl+Shift+P): + +- **`Perl: Index Codebase`** - Manually trigger codebase indexing +- **`Perl: Show Suggestions`** - Display code suggestions panel +- **`Perl: debugName`** - Debug module name resolution +- **`Perl: debugImports`** - Debug import analysis + +## ๐Ÿšฆ Status Indicators + +- **Indexing Progress**: Progress notifications during codebase analysis +- **Error Count**: Status bar indicator showing detected errors +- **Backend Connection**: Visual feedback for backend server connectivity + +## ๐Ÿ” Troubleshooting + +### Common Issues + +1. **Backend Connection Failed** + ``` + Error: Backend server is not running + ``` + **Solution**: Ensure the backend server is running on `http://localhost:8000` + +2. **No Code Suggestions** + ``` + No specific code suggestions received from AI + ``` + **Solution**: Check that your selection contains valid Perl code and the backend is responding + +3. **Indexing Failed** + ``` + Failed to initialize indexer + ``` + **Solution**: Ensure you have a workspace folder open with Perl files + +### Debug Information + +Enable debug logging by checking the "Perl Code Generation" output channel: +- `View > Output > Perl Code Generation` + +## ๐Ÿงช Development + +### Running Tests +```bash +npm test +``` + +### Development Setup +```bash +# Install dependencies +npm install + +# Run in development mode +# Press F5 in VS Code to launch Extension Development Host +``` -This is the README for your extension "Perl Code Generator". After writing up a brief description, we recommend including the following sections. +### Building +```bash +# Lint code +npm run lint + +# Package extension +vsce package +``` + +## ๐Ÿ“‹ Requirements + +- **VS Code**: Version 1.60.0 or higher +- **Node.js**: Version 14.0 or higher +- **Backend Server**: Programming Assistant AI Bot backend running on port 8000 +- **Perl**: For testing and validation (optional) + +## ๐Ÿค Contributing + +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'Add some amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request -## Features +## ๐Ÿ“„ License -Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file. +This project is part of the Programming Assistant AI Bot system. Please refer to the main project license for usage terms. -For example if there is an image subfolder under your extension project workspace: +## ๐Ÿ”— Related Projects -\!\[feature X\]\(images/feature-x.png\) +- **Frontend Interface**: `../frontend` - Web-based chat interface +- **Backend API**: `../backend` - AI processing server +- **Documentation**: Full project documentation -> Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow. - -## Requirements - -If you have any requirements or dependencies, add a section describing those and how to install and configure them. - -## Extension Settings - -Include if your extension adds any VS Code settings through the `contributes.configuration` extension point. - -For example: - -This extension contributes the following settings: - -* `myExtension.enable`: Enable/disable this extension. -* `myExtension.thing`: Set to `blah` to do something. - -## Known Issues - -Calling out known issues can help limit users opening duplicate issues against your extension. - -## Release Notes - -Users appreciate release notes as you update your extension. - -### 1.0.0 - -Initial release of ... - -### 1.0.1 - -Fixed issue #. - -### 1.1.0 - -Added features X, Y, and Z. - ---- - -## Working with Markdown - -You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts: - -* Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux) -* Toggle preview (`Shift+Cmd+V` on macOS or `Shift+Ctrl+V` on Windows and Linux) -* Press `Ctrl+Space` (Windows, Linux, macOS) to see a list of Markdown snippets - -## For more information - -* [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown) -* [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/) - -**Enjoy!** From 6dceb7a80163fa12b12f5efc9f742aaf4dac9007 Mon Sep 17 00:00:00 2001 From: "R.Sinthujan" Date: Tue, 9 Sep 2025 22:03:58 +0530 Subject: [PATCH 7/7] Add LanceDB integration for vector-based semantic search and enhance documentation --- README.md | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 43d3fb4..880f57e 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,12 @@ A powerful VS Code extension that provides AI-powered assistance for Perl develo - **Variable Definition Tracking**: Understands variable scope and definitions across files - **Symbol Usage Analysis**: Identifies how symbols are used throughout your project +### ๐Ÿ” Vector-Based Semantic Search +- **LanceDB Integration**: Uses LanceDB for high-performance vector storage and semantic search +- **Embedding-Based Matching**: Leverages MiniLM embeddings to find semantically similar code +- **Intelligent Code Retrieval**: Finds relevant code examples based on context and meaning, not just keywords +- **Persistent Index**: Maintains a persistent vector database for fast startup and consistent performance + ## ๐Ÿ“ฆ Installation 1. **Install the Extension** @@ -81,16 +87,27 @@ Access settings through `File > Preferences > Settings` and search for "Perl Cod | `perlCodeGeneration.contextWindowSize` | `15` | Number of lines to consider around cursor for context | | `perlCodeGeneration.useMemoryIndex` | `true` | Use in-memory index instead of LanceDB for module resolution | +### LanceDB Configuration + +The extension uses LanceDB for vector-based semantic search to provide more intelligent code suggestions: + +- **Database Location**: Stored in your system's temp directory under `perl-code-gen-lancedb` +- **Vector Dimensions**: Uses 384-dimensional vectors (MiniLM-L6-v2 embeddings) +- **Table Schema**: Stores code content, file paths, titles, and vector embeddings +- **Automatic Indexing**: Builds vector index during workspace initialization + ### Example Configuration ```json { "perlCodeGeneration.relevantCodeCount": 5, "perlCodeGeneration.indexOnStartup": true, "perlCodeGeneration.contextWindowSize": 20, - "perlCodeGeneration.useMemoryIndex": true + "perlCodeGeneration.useMemoryIndex": false } ``` +**Note**: Set `useMemoryIndex` to `false` to enable LanceDB vector search for enhanced semantic code matching. + ## ๐ŸŽฏ Usage ### Comment-to-Code Generation @@ -127,6 +144,7 @@ Access settings through `File > Preferences > Settings` and search for "Perl Cod VsCodeExtension/ โ”œโ”€โ”€ extension.js # Main extension entry point โ”œโ”€โ”€ package.json # Extension manifest and configuration +โ”œโ”€โ”€ sidebarprovider.js # Sidebar suggestions provider โ”œโ”€โ”€ api/ โ”‚ โ””โ”€โ”€ api.js # Backend API communication โ”œโ”€โ”€ collectors/ # Context and code analysis @@ -137,10 +155,12 @@ VsCodeExtension/ โ”‚ โ””โ”€โ”€ repoMapProvider.js โ”œโ”€โ”€ commands/ โ”‚ โ””โ”€โ”€ commands.js # VS Code command implementations +โ”œโ”€โ”€ embeddings/ +โ”‚ โ””โ”€โ”€ miniLmEmbeddings.js # MiniLM embedding generation โ”œโ”€โ”€ indexers/ # Codebase indexing and search โ”‚ โ”œโ”€โ”€ codebaseIndexer.js โ”‚ โ”œโ”€โ”€ codeStructureIndex.js -โ”‚ โ””โ”€โ”€ vectorIndex.js +โ”‚ โ””โ”€โ”€ vectorIndex.js # LanceDB vector operations โ”œโ”€โ”€ parsers/ โ”‚ โ””โ”€โ”€ treeSitter.js # Tree-sitter parser integration โ”œโ”€โ”€ utils/ @@ -187,6 +207,12 @@ Access commands through the Command Palette (Ctrl+Shift+P): ``` **Solution**: Ensure you have a workspace folder open with Perl files +4. **LanceDB Connection Issues** + ``` + Error initializing LanceDB + ``` + **Solution**: Check write permissions to temp directory and ensure sufficient disk space for vector database + ### Debug Information Enable debug logging by checking the "Perl Code Generation" output channel: @@ -222,6 +248,7 @@ vsce package - **VS Code**: Version 1.60.0 or higher - **Node.js**: Version 14.0 or higher - **Backend Server**: Programming Assistant AI Bot backend running on port 8000 +- **LanceDB**: For vector-based semantic search (automatically installed with extension) - **Perl**: For testing and validation (optional) ## ๐Ÿค Contributing