Intermediate still contains errors, but an attempt to solve filtering as per .gitignore
This commit is contained in:
@@ -3,26 +3,28 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { PrompterSettings } from '../models/settings';
|
||||
import { FileReader } from './fileReader';
|
||||
// Using require for the ignore package due to its module export style
|
||||
let ignore: any;
|
||||
try {
|
||||
ignore = require('ignore');
|
||||
console.log('Successfully loaded ignore package');
|
||||
} catch (error) {
|
||||
console.error('Error loading ignore package:', error);
|
||||
// Fallback implementation if the package fails to load
|
||||
ignore = {
|
||||
default: () => ({
|
||||
add: () => {},
|
||||
ignores: () => false
|
||||
})
|
||||
};
|
||||
}
|
||||
import { TokenEstimator } from './tokenEstimator';
|
||||
|
||||
/**
|
||||
* Utility class for generating prompts from selected files
|
||||
*/
|
||||
// Use require for ignore package with proper fallback
|
||||
let ignoreFunc: () => any;
|
||||
try {
|
||||
const ignoreModule = require('ignore');
|
||||
if (typeof ignoreModule === 'function') {
|
||||
ignoreFunc = ignoreModule;
|
||||
} else if (ignoreModule && typeof ignoreModule.default === 'function') {
|
||||
ignoreFunc = ignoreModule.default;
|
||||
} else {
|
||||
throw new Error('Ignore module is neither a function nor has a default function');
|
||||
}
|
||||
console.log('Successfully loaded ignore function');
|
||||
} catch (error) {
|
||||
console.error('Error loading ignore package:', error);
|
||||
ignoreFunc = () => ({
|
||||
add: () => {},
|
||||
ignores: () => false
|
||||
});
|
||||
}
|
||||
|
||||
export class PromptGenerator {
|
||||
/**
|
||||
* Generate a prompt from the selected files
|
||||
@@ -35,21 +37,76 @@ export class PromptGenerator {
|
||||
throw new Error('No files selected');
|
||||
}
|
||||
|
||||
let totalTokens = 0;
|
||||
const fileContents = new Map<string, { content: string; tokens: number }>();
|
||||
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath || '';
|
||||
|
||||
// Create ignore instance using the resolved function
|
||||
const ig = ignoreFunc();
|
||||
console.log('Created ignore instance');
|
||||
|
||||
// Load .gitignore patterns
|
||||
try {
|
||||
const gitignorePath = path.join(workspaceRoot, '.gitignore');
|
||||
if (fs.existsSync(gitignorePath)) {
|
||||
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
|
||||
ig.add(gitignoreContent);
|
||||
console.log('Successfully loaded .gitignore patterns:', gitignoreContent.split('\n').filter(Boolean));
|
||||
} else {
|
||||
console.log('No .gitignore file found at:', gitignorePath);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading .gitignore:', error);
|
||||
}
|
||||
|
||||
// Process each selected file
|
||||
const fileContents = new Map<string, { content: string; tokens: number }>();
|
||||
const filteredFiles = new Set<string>();
|
||||
let totalTokens = 0;
|
||||
|
||||
// Process and filter files
|
||||
for (const filePath of selectedFiles) {
|
||||
try {
|
||||
const stat = fs.statSync(filePath);
|
||||
if (stat.isDirectory()) {
|
||||
console.log(`Skipping directory: ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error checking file stats for ${filePath}:`, error);
|
||||
continue;
|
||||
}
|
||||
|
||||
const relativePath = this.getRelativePath(filePath, workspaceRoot);
|
||||
const normalizedPath = relativePath.split(path.sep).join('/');
|
||||
|
||||
console.log(`Checking path: ${normalizedPath}`);
|
||||
if (ig.ignores(normalizedPath)) {
|
||||
console.log(`Ignoring file based on patterns: ${normalizedPath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`Processing file: ${normalizedPath}`);
|
||||
filteredFiles.add(filePath);
|
||||
const content = await FileReader.readFileContent(filePath);
|
||||
const tokens = TokenEstimator.estimateTokens(content);
|
||||
totalTokens += tokens;
|
||||
fileContents.set(filePath, { content, tokens });
|
||||
}
|
||||
|
||||
// Always generate XML prompt
|
||||
return this.generateXMLPrompt(fileContents, settings);
|
||||
if (filteredFiles.size === 0) {
|
||||
vscode.window.showWarningMessage('All selected files were filtered out by ignore patterns');
|
||||
throw new Error('All files were filtered out by ignore patterns');
|
||||
}
|
||||
|
||||
// Create filtered contents map
|
||||
const filteredContents = new Map<string, { content: string; tokens: number }>();
|
||||
for (const filePath of filteredFiles) {
|
||||
if (fileContents.has(filePath)) {
|
||||
filteredContents.set(filePath, fileContents.get(filePath)!);
|
||||
}
|
||||
}
|
||||
|
||||
return this.generateXMLPrompt(filteredContents, settings);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a plain text prompt
|
||||
* @param files Map of file paths to content and token counts
|
||||
@@ -64,14 +121,11 @@ export class PromptGenerator {
|
||||
const fileName = path.basename(filePath);
|
||||
totalTokenCount += tokens;
|
||||
|
||||
// Add the file to the prompt
|
||||
promptText += `File: ${fileName}\n`;
|
||||
promptText += `${content}\n`;
|
||||
|
||||
promptText += '\n';
|
||||
}
|
||||
|
||||
// Add token count if enabled
|
||||
if (settings.tokenCalculationEnabled) {
|
||||
promptText += `\nEstimated token count: ${totalTokenCount}`;
|
||||
}
|
||||
@@ -88,13 +142,11 @@ export class PromptGenerator {
|
||||
private static generateXMLPrompt(files: Map<string, { content: string; tokens: number }>, settings: PrompterSettings): string {
|
||||
const xmlParts: string[] = [];
|
||||
|
||||
// Store formatting instructions to add at the end if enabled
|
||||
let formattingInstructions = '';
|
||||
if (settings.includeFormattingInstructions) {
|
||||
try {
|
||||
// Get the extension path
|
||||
const extensionPath = vscode.extensions.getExtension('prompter')?.extensionPath ||
|
||||
path.join(__dirname, '..', '..');
|
||||
path.join(__dirname, '..', '..');
|
||||
const formattingInstructionsPath = path.join(extensionPath, 'resources', 'xml_formatting_instructions.xml');
|
||||
|
||||
if (fs.existsSync(formattingInstructionsPath)) {
|
||||
@@ -107,29 +159,21 @@ export class PromptGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
// Generate file map section if enabled in settings
|
||||
if (settings.includeFileMap) {
|
||||
xmlParts.push('<file_map>');
|
||||
|
||||
// Get the workspace root path
|
||||
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath || '';
|
||||
xmlParts.push(workspaceRoot);
|
||||
|
||||
// Create a tree representation of the files
|
||||
const fileTree = this.generateFileTree(files, workspaceRoot);
|
||||
xmlParts.push(fileTree);
|
||||
xmlParts.push('</file_map>');
|
||||
}
|
||||
|
||||
// Generate file contents section
|
||||
xmlParts.push('<file_contents>');
|
||||
|
||||
// Add each file with its content
|
||||
for (const [filePath, { content, tokens }] of files) {
|
||||
const extension = path.extname(filePath);
|
||||
let language = extension.substring(1); // Remove the dot
|
||||
let language = extension.substring(1);
|
||||
|
||||
// Handle special cases for language detection
|
||||
if (extension === '.js' || extension === '.jsx') {
|
||||
language = 'javascript';
|
||||
} else if (extension === '.ts' || extension === '.tsx') {
|
||||
@@ -146,13 +190,9 @@ export class PromptGenerator {
|
||||
language = 'json';
|
||||
}
|
||||
|
||||
// Use content as is
|
||||
const formattedContent = content;
|
||||
|
||||
// Get the workspace root path if not already defined
|
||||
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath || '';
|
||||
|
||||
// Add file entry
|
||||
xmlParts.push(`File: ${this.getRelativePath(filePath, workspaceRoot)}`);
|
||||
xmlParts.push(`\`\`\`${language}`);
|
||||
xmlParts.push(formattedContent);
|
||||
@@ -161,14 +201,11 @@ export class PromptGenerator {
|
||||
|
||||
xmlParts.push('</file_contents>');
|
||||
|
||||
// Calculate tokens for toast notification but don't include in XML
|
||||
if (settings.tokenCalculationEnabled) {
|
||||
const totalTokens = Array.from(files.values()).reduce((sum, { tokens }) => sum + tokens, 0);
|
||||
// We'll show this in a toast notification but not include it in the XML
|
||||
vscode.window.showInformationMessage(`Total tokens: ${totalTokens}`);
|
||||
}
|
||||
|
||||
// Add formatting instructions at the end if enabled
|
||||
if (settings.includeFormattingInstructions && formattingInstructions) {
|
||||
xmlParts.push(formattingInstructions);
|
||||
}
|
||||
@@ -183,69 +220,43 @@ export class PromptGenerator {
|
||||
* @returns String representation of the file tree
|
||||
*/
|
||||
private static generateFileTree(files: Map<string, any>, rootPath: string): string {
|
||||
// Create a tree representation
|
||||
const treeLines: string[] = [];
|
||||
|
||||
// Create a tree structure
|
||||
interface TreeNode {
|
||||
name: string;
|
||||
isDirectory: boolean;
|
||||
children: Map<string, TreeNode>;
|
||||
}
|
||||
|
||||
// Create root node
|
||||
const root: TreeNode = {
|
||||
name: path.basename(rootPath),
|
||||
isDirectory: true,
|
||||
children: new Map<string, TreeNode>()
|
||||
};
|
||||
|
||||
// Initialize the ignore instance
|
||||
let ig;
|
||||
try {
|
||||
ig = typeof ignore === 'function' ? ignore() : ignore.default();
|
||||
console.log('Successfully initialized ignore instance');
|
||||
} catch (error) {
|
||||
console.error('Error initializing ignore instance:', error);
|
||||
// Fallback implementation
|
||||
ig = {
|
||||
add: () => {},
|
||||
ignores: () => false
|
||||
};
|
||||
}
|
||||
|
||||
// Read .gitignore patterns if available
|
||||
const ig = ignoreFunc();
|
||||
try {
|
||||
const gitignorePath = path.join(rootPath, '.gitignore');
|
||||
if (fs.existsSync(gitignorePath)) {
|
||||
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
|
||||
// Add patterns from .gitignore
|
||||
ig.add(gitignoreContent);
|
||||
// Always include .gitignore itself
|
||||
ig.add('!.gitignore');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error reading .gitignore:', error);
|
||||
}
|
||||
|
||||
// Build the tree structure
|
||||
for (const filePath of files.keys()) {
|
||||
const relativePath = this.getRelativePath(filePath, rootPath);
|
||||
|
||||
// Skip ignored files using the ignore package
|
||||
// Use forward slashes for paths to ensure consistent matching across platforms
|
||||
const normalizedPath = relativePath.split(path.sep).join('/');
|
||||
|
||||
if (ig.ignores(normalizedPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Split the path into parts
|
||||
const parts = relativePath.split('/');
|
||||
|
||||
// Start from the root
|
||||
let currentNode = root;
|
||||
|
||||
// Build the path in the tree
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
const isDirectory = i < parts.length - 1;
|
||||
@@ -257,7 +268,6 @@ export class PromptGenerator {
|
||||
children: new Map<string, TreeNode>()
|
||||
});
|
||||
} else if (isDirectory) {
|
||||
// Ensure it's marked as a directory if we encounter it again
|
||||
currentNode.children.get(part)!.isDirectory = true;
|
||||
}
|
||||
|
||||
@@ -265,15 +275,12 @@ export class PromptGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
// Function to recursively build the tree lines
|
||||
const buildTreeLines = (node: TreeNode, prefix: string = '', isLast: boolean = true, parentPrefix: string = ''): void => {
|
||||
// Skip the root node in the output
|
||||
if (node !== root) {
|
||||
const linePrefix = parentPrefix + (isLast ? '└── ' : '├── ');
|
||||
treeLines.push(`${linePrefix}${node.name}${node.isDirectory ? '' : ''}`);
|
||||
}
|
||||
|
||||
// Sort children: directories first, then files, both alphabetically
|
||||
const sortedChildren = Array.from(node.children.values())
|
||||
.sort((a, b) => {
|
||||
if (a.isDirectory === b.isDirectory) {
|
||||
@@ -282,18 +289,14 @@ export class PromptGenerator {
|
||||
return a.isDirectory ? -1 : 1;
|
||||
});
|
||||
|
||||
// Process children
|
||||
sortedChildren.forEach((child, index) => {
|
||||
const isChildLast = index === sortedChildren.length - 1;
|
||||
const childParentPrefix = node === root ? '' : parentPrefix + (isLast ? ' ' : '│ ');
|
||||
|
||||
buildTreeLines(child, prefix, isChildLast, childParentPrefix);
|
||||
});
|
||||
};
|
||||
|
||||
// Build the tree lines
|
||||
buildTreeLines(root);
|
||||
|
||||
return treeLines.join('\n');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user