feat-filter-ignore #1
@@ -6,5 +6,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
export default {
|
||||
version: 'stable',
|
||||
extensionDevelopmentPath: __dirname,
|
||||
extensionTestsPath: path.join(__dirname, 'out', 'test')
|
||||
extensionTestsPath: path.join(__dirname, 'out', 'test'),
|
||||
testFiles: ['**/**.test.js'],
|
||||
workspaceFolder: __dirname
|
||||
};
|
||||
|
||||
@@ -8,6 +8,8 @@ src/**
|
||||
**/.eslintrc.json
|
||||
**/*.map
|
||||
**/*.ts
|
||||
out/**
|
||||
webpack.config.js
|
||||
.replit
|
||||
.breakpoints
|
||||
.local/**
|
||||
|
||||
@@ -7,3 +7,9 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
|
||||
## [Unreleased]
|
||||
|
||||
- Initial release
|
||||
|
||||
## [0.0.1] - 2025-03-13
|
||||
|
||||
### Added
|
||||
|
||||
- PromptGeneration extension with basic file selection and xml edits option.
|
||||
|
||||
85
README.md
85
README.md
@@ -1,79 +1,48 @@
|
||||
# Prompter
|
||||
|
||||
Code smarter with AI—no more messy copy-pasting. Prompter structures your prompts and applies AI changes seamlessly, streamlining your coding workflow.
|
||||
Enhance your coding with AI—Prompter organizes your prompts and implements AI changes effortlessly, improving your development workflow.
|
||||
|
||||
## Why Prompter?
|
||||
## Why Use Prompter?
|
||||
|
||||
- **Too much bloat in your repo?** Stop zipping everything—send only the files that matter.
|
||||
- **LLM underperforming?** Cut the noise for sharper, more accurate responses.
|
||||
- **Better AI coding?** Select just the right context to optimize results.
|
||||
- **Reduce repository clutter** - Share only essential files instead of everything
|
||||
- **Improve AI responses** - Eliminate irrelevant context for better results
|
||||
- **Optimize coding assistance** - Target exactly what you need for precise AI help
|
||||
|
||||
Prompter empowers you to work efficiently with AI, reducing token waste and improving clarity.
|
||||
Prompter helps you collaborate efficiently with AI, minimizing token usage and enhancing clarity.
|
||||
|
||||
## Features
|
||||
## Key Features
|
||||
|
||||
- **Advanced File Selection & Token Estimation**
|
||||
Precisely filter files and estimate token usage instantly for optimized, cost-effective prompts.
|
||||
- **Smart File Selection & Token Counting**
|
||||
Filter files and track token usage for efficient, economical prompts
|
||||
|
||||
- **Optimized XML Prompt**
|
||||
Structured file trees, CodeMaps, content, and instructions in XML for maximum LLM clarity.
|
||||
- **XML-Based Prompt Structure**
|
||||
Organize file trees, CodeMaps, and instructions in XML for better AI comprehension
|
||||
|
||||
- **Structured XML Diffs**
|
||||
Converts LLM-generated XML edits into precise, reviewable diffs—works at any file size.
|
||||
- **Clean XML Diff Application**
|
||||
Transform AI-generated XML changes into clear, reviewable diffs at any scale
|
||||
|
||||
- **Codemap Extraction**
|
||||
Scans files locally to extract classes, functions, and references, minimizing tokens and hallucinations. Auto-detects referenced types.
|
||||
- **Intelligent Code Scanning**
|
||||
Extract code structure locally to reduce tokens and prevent hallucinations
|
||||
|
||||
- **Mac-Native Performance**
|
||||
Built for macOS with native speed and responsiveness—because performance matters.
|
||||
- **Cross-Platform Support**
|
||||
Designed for any VSCode platform with native performance and responsiveness
|
||||
|
||||
- **Clipboard Integration**
|
||||
Copy structured prompts into any AI chat app—your data stays local, no external API needed.
|
||||
- **Direct Clipboard Support**
|
||||
Copy structured prompts to any AI platform with your data remaining local
|
||||
|
||||
- **Works with Any Model**
|
||||
Compatible with OpenAI, Anthropic, DeepSeek, Gemini, Azure, OpenRouter, and local models—private and offline when you need it.
|
||||
- **Privacy-Focused Design**
|
||||
Process everything locally without sending data to third parties
|
||||
|
||||
- **Privacy First**
|
||||
Local models, offline scanning, and direct clipboard use—no intermediaries required.
|
||||
## Quick Start
|
||||
|
||||
## Installation
|
||||
|
||||
*(Note: Installation steps are assumed based on the VS Code context from other files. Adjust as needed.)*
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
```
|
||||
2. Open the project in VS Code.
|
||||
3. Install dependencies:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
4. Build the extension:
|
||||
```bash
|
||||
npm run compile
|
||||
```
|
||||
5. Press `F5` in VS Code to launch the extension in a development window.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Open your project in VS Code.
|
||||
2. Use the Prompter interface to select files and estimate tokens.
|
||||
3. Generate a structured XML prompt via the clipboard.
|
||||
4. Paste into your preferred AI model (e.g., ChatGPT, Claude, or a local LLM).
|
||||
5. Apply the returned XML diffs directly through Prompter for seamless integration.
|
||||
1. Install the extension in VS Code
|
||||
2. Select relevant files through the Prompter sidebar
|
||||
3. Generate and copy a structured XML prompt
|
||||
4. Paste into your AI tool of choice
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions! To get started:
|
||||
1. Fork the repository.
|
||||
2. Create a feature branch: `git checkout -b my-feature`.
|
||||
3. Commit your changes: `git commit -m "Add my feature"`.
|
||||
4. Push to your branch: `git push origin my-feature`.
|
||||
5. Open a pull request.
|
||||
|
||||
See `vsc-extension-quickstart.md` for development setup and testing details.
|
||||
Contributions welcome!
|
||||
|
||||
---
|
||||
Built with ❤️ by the Prompter team.
|
||||
|
||||
Code smarter with AI—no more messy copy-pasting. Prompter structures your prompts and applies AI changes seamlessly, streamlining your coding workflow.
|
||||
|
||||
19
jest.config.js
Normal file
19
jest.config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} **/
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
transform: {
|
||||
'^.+\.tsx?$': ['ts-jest', {}],
|
||||
},
|
||||
testMatch: ['**/test/**/*.ts'],
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsconfig: 'tsconfig.json',
|
||||
},
|
||||
},
|
||||
// Mock the vscode module since it's not available during testing
|
||||
moduleNameMapper: {
|
||||
'^vscode$': '<rootDir>/src/test/vscode-mock.ts'
|
||||
},
|
||||
};
|
||||
33
package.json
33
package.json
@@ -12,7 +12,7 @@
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [],
|
||||
"main": "./out/extension.js",
|
||||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
@@ -21,7 +21,13 @@
|
||||
},
|
||||
{
|
||||
"command": "prompter.generatePrompt",
|
||||
"title": "Copy"
|
||||
"title": "Copy",
|
||||
"icon": "$(clippy)"
|
||||
},
|
||||
{
|
||||
"command": "prompter.openTreeView",
|
||||
"title": "Show Tree View",
|
||||
"icon": "$(list-tree)"
|
||||
},
|
||||
{
|
||||
"command": "prompter.openSettings",
|
||||
@@ -42,7 +48,8 @@
|
||||
"prompter-sidebar": [
|
||||
{
|
||||
"id": "prompterView",
|
||||
"name": "Prompter"
|
||||
"name": "Prompter",
|
||||
"icon": "resources/icon.svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -54,9 +61,14 @@
|
||||
"when": "view == prompterView"
|
||||
},
|
||||
{
|
||||
"command": "prompter.openSettings",
|
||||
"command": "prompter.openTreeView",
|
||||
"group": "navigation@2",
|
||||
"when": "view == prompterView"
|
||||
},
|
||||
{
|
||||
"command": "prompter.openSettings",
|
||||
"group": "navigation@3",
|
||||
"when": "view == prompterView"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
@@ -68,12 +80,14 @@
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "npm run compile",
|
||||
"vscode:prepublish": "npm run webpack:prod",
|
||||
"webpack:dev": "webpack --mode development",
|
||||
"webpack:prod": "webpack --mode production",
|
||||
"compile": "tsc -p ./",
|
||||
"watch": "tsc -watch -p ./",
|
||||
"watch": "webpack --watch --mode development",
|
||||
"pretest": "npm run compile && npm run lint",
|
||||
"lint": "eslint src",
|
||||
"test": "vscode-test"
|
||||
"test": "node ./src/test/runTest.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^10.0.10",
|
||||
@@ -84,7 +98,10 @@
|
||||
"@vscode/test-cli": "^0.0.10",
|
||||
"@vscode/test-electron": "^2.4.1",
|
||||
"eslint": "^9.21.0",
|
||||
"typescript": "^5.7.3"
|
||||
"ts-loader": "^9.5.2",
|
||||
"typescript": "^5.7.3",
|
||||
"webpack": "^5.98.0",
|
||||
"webpack-cli": "^6.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vscode/vsce": "^3.2.2",
|
||||
|
||||
@@ -16,7 +16,7 @@ Avoid placeholders like `...` or `// existing code here`. Provide complete lines
|
||||
3. **modify** (search/replace) – For partial edits with <search> + <content>.
|
||||
4. **delete** – Remove a file entirely (empty <content>).
|
||||
|
||||
### **Format to Follow for Repo Prompt's Diff Protocol**
|
||||
### **Format to Follow for Diff Protocol**
|
||||
|
||||
<Plan>
|
||||
Describe your approach or reasoning here.
|
||||
@@ -288,7 +288,7 @@ Remove an obsolete file.
|
||||
5. You can always **create** new files and **delete** existing files. Provide full code for create, and empty content for delete. Avoid creating files you know exist already.
|
||||
6. If a file tree is provided, place your files logically within that structure. Respect the user’s relative or absolute paths.
|
||||
7. Wrap your final output in ```XML ... ``` for clarity.
|
||||
8. **Important:** Do not wrap any XML output in CDATA tags (i.e. `<![CDATA[ ... ]]>`). Repo Prompt expects raw XML exactly as shown in the examples.
|
||||
8. **Important:** Do not wrap any XML output in CDATA tags (i.e. `<![CDATA[ ... ]]>`). We xpect raw XML exactly as shown in the examples.
|
||||
9. **IMPORTANT** IF MAKING FILE CHANGES, YOU MUST USE THE AVAILABLE XML FORMATTING CAPABILITIES PROVIDED ABOVE - IT IS THE ONLY WAY FOR YOUR CHANGES TO BE APPLIED.
|
||||
10. The final output must apply cleanly with no leftover syntax errors.
|
||||
</xml_formatting_instructions>
|
||||
@@ -60,26 +60,33 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
// Update formatting instructions button text if that setting changed
|
||||
if (item.settingKey === 'includeFormattingInstructions') {
|
||||
xmlEditsButton.text = prompterTreeProvider.isXmlEditsEnabled() ?
|
||||
"$(check) Formatting Instructions" : "$(diff-added) Formatting Instructions";
|
||||
"$(check) Formatting Instructions" : "$(diff-added) XML Edits";
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Create formatting instructions toggle button
|
||||
const xmlEditsButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
|
||||
xmlEditsButton.text = "$(diff-added) Formatting Instructions";
|
||||
const xmlEditsButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 1);
|
||||
xmlEditsButton.text = "$(diff-added) XML Edits";
|
||||
xmlEditsButton.tooltip = "Toggle formatting instructions mode";
|
||||
xmlEditsButton.command = 'prompter.toggleXmlEdits';
|
||||
xmlEditsButton.show();
|
||||
|
||||
// Create copy button
|
||||
const copyButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right);
|
||||
const copyButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 3);
|
||||
copyButton.text = "$(clippy) Copy";
|
||||
copyButton.tooltip = "Generate and copy prompt";
|
||||
copyButton.command = 'prompter.generatePrompt';
|
||||
copyButton.show();
|
||||
|
||||
// Create settings button
|
||||
const settingsButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 2);
|
||||
settingsButton.text = "$(settings-gear)";
|
||||
settingsButton.tooltip = "Prompter Settings";
|
||||
settingsButton.command = 'prompter.openSettings';
|
||||
settingsButton.show();
|
||||
|
||||
// Register command to toggle file selection
|
||||
let toggleSelectionCommand = vscode.commands.registerCommand('prompter.toggleSelection', (item: FileTreeItem) => {
|
||||
if (item.resourceUri) {
|
||||
@@ -95,13 +102,6 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
"$(check) XML Edits" : "$(diff-added) XML Edits";
|
||||
});
|
||||
|
||||
// Create settings button
|
||||
const settingsButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
|
||||
settingsButton.text = "$(settings-gear)";
|
||||
settingsButton.tooltip = "Prompter Settings";
|
||||
settingsButton.command = 'prompter.openSettings';
|
||||
settingsButton.show();
|
||||
|
||||
// Register command to open settings
|
||||
let openSettingsCommand = vscode.commands.registerCommand('prompter.openSettings', () => {
|
||||
prompterTreeProvider.toggleSettingsView();
|
||||
@@ -113,6 +113,11 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
"Show Files" : "Prompter Settings";
|
||||
});
|
||||
|
||||
// Register command to show the tree view
|
||||
let openTreeViewCommand = vscode.commands.registerCommand('prompter.openTreeView', () => {
|
||||
prompterTreeProvider.showFilesView();
|
||||
});
|
||||
|
||||
// Register command to generate prompt from selected files
|
||||
let generatePromptCommand = vscode.commands.registerCommand('prompter.generatePrompt', async () => {
|
||||
const selectedFiles = prompterTreeProvider.getSelectedFiles();
|
||||
@@ -130,7 +135,14 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
prompterTreeProvider.getSettings()
|
||||
);
|
||||
|
||||
// Copy to clipboard
|
||||
// Check if we got a valid prompt text
|
||||
if (promptText === null) {
|
||||
// Show warning if all files were filtered out
|
||||
vscode.window.showWarningMessage('All selected files were filtered out by ignore patterns');
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy to clipboard only if we have valid content
|
||||
await vscode.env.clipboard.writeText(promptText);
|
||||
vscode.window.showInformationMessage('Prompt copied to clipboard!');
|
||||
} catch (error) {
|
||||
@@ -148,7 +160,8 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
toggleSelectionCommand,
|
||||
toggleXmlEditsCommand,
|
||||
generatePromptCommand,
|
||||
openSettingsCommand
|
||||
openSettingsCommand,
|
||||
openTreeViewCommand
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
// 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 in FileSelectionManager');
|
||||
} catch (error) {
|
||||
console.error('Error loading ignore package in FileSelectionManager:', error);
|
||||
ignoreFunc = () => ({
|
||||
add: () => {},
|
||||
ignores: () => false
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages file selection state for the Prompter extension
|
||||
@@ -11,40 +31,134 @@ export class FileSelectionManager {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Add a file to the selection
|
||||
* Get the path relative to the workspace root
|
||||
* @param filePath Absolute file path
|
||||
* @param rootPath Workspace root path
|
||||
* @returns Relative path
|
||||
*/
|
||||
private getRelativePath(filePath: string, rootPath: string): string {
|
||||
if (filePath.startsWith(rootPath)) {
|
||||
const relativePath = filePath.substring(rootPath.length);
|
||||
return relativePath.startsWith('/') ? relativePath.substring(1) : relativePath;
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a path or any of its parent directories are ignored
|
||||
* @param pathToCheck The path to check
|
||||
* @param ig The ignore instance
|
||||
* @param workspaceRoot The workspace root path
|
||||
* @returns True if the path or any parent is ignored
|
||||
*/
|
||||
private isPathIgnored(pathToCheck: string, ig: any, workspaceRoot: string): boolean {
|
||||
let currentPath = pathToCheck;
|
||||
while (currentPath !== workspaceRoot) {
|
||||
const relativePath = this.getRelativePath(currentPath, workspaceRoot);
|
||||
const normalizedPath = relativePath.split(path.sep).join('/');
|
||||
const isIgnored = ig.ignores(normalizedPath);
|
||||
console.log(`Checking ${normalizedPath}: ignored = ${isIgnored}`);
|
||||
if (isIgnored) {
|
||||
console.log(`Path ${pathToCheck} ignored because parent ${normalizedPath} is ignored`);
|
||||
return true;
|
||||
}
|
||||
currentPath = path.dirname(currentPath);
|
||||
if (currentPath === workspaceRoot) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load ignore patterns from .gitignore files in the given directory and its parent directories
|
||||
* @param directoryPath The directory path to start searching from
|
||||
* @param workspaceRoot The workspace root path
|
||||
* @returns An ignore instance with loaded patterns
|
||||
*/
|
||||
private loadIgnorePatternsFromDirectory(directoryPath: string, workspaceRoot: string): any {
|
||||
const ig = ignoreFunc();
|
||||
let currentDir = directoryPath;
|
||||
|
||||
// Check for .gitignore in the current directory and all parent directories up to workspace root
|
||||
while (currentDir.startsWith(workspaceRoot)) {
|
||||
try {
|
||||
const gitignorePath = path.join(currentDir, '.gitignore');
|
||||
if (fs.existsSync(gitignorePath)) {
|
||||
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
|
||||
ig.add(gitignoreContent);
|
||||
console.log(`Loaded .gitignore patterns from ${gitignorePath}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error loading .gitignore from ${currentDir}:`, error);
|
||||
}
|
||||
|
||||
// Stop if we've reached the workspace root
|
||||
if (currentDir === workspaceRoot) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Move up to parent directory
|
||||
const parentDir = path.dirname(currentDir);
|
||||
if (parentDir === currentDir) { // Avoid infinite loop if we've reached the root
|
||||
break;
|
||||
}
|
||||
currentDir = parentDir;
|
||||
}
|
||||
|
||||
return ig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file to the selection if it's not ignored
|
||||
*/
|
||||
addFile(filePath: string): void {
|
||||
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath || '';
|
||||
const dirPath = path.dirname(filePath);
|
||||
const ig = this.loadIgnorePatternsFromDirectory(dirPath, workspaceRoot);
|
||||
|
||||
if (this.isPathIgnored(filePath, ig, workspaceRoot)) {
|
||||
console.log(`Ignoring file ${filePath} because it or a parent directory is ignored`);
|
||||
return;
|
||||
}
|
||||
this.selectedFiles.add(filePath);
|
||||
console.log(`Added ${filePath} to selection`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a directory and all its contents to the selection
|
||||
* Add a directory and all its non-ignored contents to the selection
|
||||
*/
|
||||
async addDirectory(dirPath: string): Promise<void> {
|
||||
try {
|
||||
// Add the directory itself
|
||||
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath || '';
|
||||
const ig = this.loadIgnorePatternsFromDirectory(dirPath, workspaceRoot);
|
||||
|
||||
const relativeDirPath = this.getRelativePath(dirPath, workspaceRoot);
|
||||
const normalizedDirPath = relativeDirPath.split(path.sep).join('/');
|
||||
if (ig.ignores(normalizedDirPath)) {
|
||||
console.log(`Directory ${dirPath} is ignored, skipping its contents`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedFiles.add(dirPath);
|
||||
console.log(`Added directory ${dirPath} to selection`);
|
||||
|
||||
// Read directory contents
|
||||
try {
|
||||
const files = await vscode.workspace.fs.readDirectory(vscode.Uri.file(dirPath));
|
||||
|
||||
// Process each item
|
||||
for (const [name, type] of files) {
|
||||
const filePath = path.join(dirPath, name);
|
||||
|
||||
const relativeFilePath = this.getRelativePath(filePath, workspaceRoot);
|
||||
const normalizedFilePath = relativeFilePath.split(path.sep).join('/');
|
||||
if (type === vscode.FileType.Directory) {
|
||||
// Recursively process subdirectories
|
||||
await this.addDirectory(filePath);
|
||||
} else {
|
||||
// Add files
|
||||
} else if (!ig.ignores(normalizedFilePath)) {
|
||||
this.selectedFiles.add(filePath);
|
||||
console.log(`Added ${filePath} to selection (from directory)`);
|
||||
console.log(`Added ${filePath} to selection`);
|
||||
} else {
|
||||
console.log(`Ignoring ${filePath} due to ignore patterns`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error adding directory to selection: ${dirPath}`, error);
|
||||
console.error(`Error adding directory ${dirPath}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,22 +175,14 @@ export class FileSelectionManager {
|
||||
*/
|
||||
async removeDirectory(dirPath: string): Promise<void> {
|
||||
try {
|
||||
// Remove the directory itself
|
||||
this.selectedFiles.delete(dirPath);
|
||||
console.log(`Removed directory ${dirPath} from selection`);
|
||||
|
||||
// Read directory contents
|
||||
const files = await vscode.workspace.fs.readDirectory(vscode.Uri.file(dirPath));
|
||||
|
||||
// Process each item
|
||||
for (const [name, type] of files) {
|
||||
const filePath = path.join(dirPath, name);
|
||||
|
||||
if (type === vscode.FileType.Directory) {
|
||||
// Recursively process subdirectories
|
||||
await this.removeDirectory(filePath);
|
||||
} else {
|
||||
// Remove files
|
||||
this.selectedFiles.delete(filePath);
|
||||
console.log(`Removed ${filePath} from selection (from directory)`);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,16 @@ export class PrompterTreeProvider implements vscode.TreeDataProvider<FileTreeIte
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
showSettingsView(): void {
|
||||
this.showingSettings = true;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
showFilesView(): void {
|
||||
this.showingSettings = false;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
getTreeItem(element: FileTreeItem | SettingTreeItem): vscode.TreeItem {
|
||||
// Return the element as is if it's a SettingTreeItem
|
||||
if (element instanceof SettingTreeItem) {
|
||||
|
||||
@@ -1,251 +0,0 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
// Using require for the ignore package due to its module export style
|
||||
const ignoreLib = require('ignore');
|
||||
|
||||
// A simplified version of our tree structure for testing
|
||||
interface TreeNode {
|
||||
name: string;
|
||||
isDirectory: boolean;
|
||||
children: Map<string, TreeNode>;
|
||||
}
|
||||
|
||||
// Test implementation of the file tree generation logic
|
||||
function generateFileTree(files: string[], rootPath: string): string {
|
||||
// Create a tree representation
|
||||
const treeLines: string[] = [];
|
||||
|
||||
// Create root node
|
||||
const root: TreeNode = {
|
||||
name: path.basename(rootPath),
|
||||
isDirectory: true,
|
||||
children: new Map<string, TreeNode>()
|
||||
};
|
||||
|
||||
// Initialize the ignore instance
|
||||
const ig = ignoreLib();
|
||||
|
||||
// Read .gitignore patterns if available
|
||||
try {
|
||||
const gitignorePath = path.join(rootPath, '.gitignore');
|
||||
if (fs.existsSync(gitignorePath)) {
|
||||
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
|
||||
console.log('Using .gitignore content:');
|
||||
console.log(gitignoreContent);
|
||||
|
||||
// Add patterns from .gitignore
|
||||
ig.add(gitignoreContent);
|
||||
// Always include .gitignore itself
|
||||
ig.add('!.gitignore');
|
||||
|
||||
// Debug what's being ignored
|
||||
console.log('\nIgnore patterns loaded. Testing patterns:');
|
||||
['file1.txt', 'file2.log', 'node_modules/package.json', 'src/index.ts', 'temp/temp.txt'].forEach(testPath => {
|
||||
console.log(`${testPath}: ${ig.ignores(testPath) ? 'IGNORED' : 'included'}`);
|
||||
});
|
||||
console.log();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error reading .gitignore:', error);
|
||||
}
|
||||
|
||||
// Build the tree structure
|
||||
for (const filePath of files) {
|
||||
const relativePath = getRelativePath(filePath, rootPath);
|
||||
|
||||
// Skip ignored files using the ignore package
|
||||
// Use forward slashes for paths to ensure consistent matching
|
||||
const normalizedPath = relativePath.split(path.sep).join('/');
|
||||
if (ig.ignores(normalizedPath)) {
|
||||
console.log(`Ignoring: ${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];
|
||||
if (!part) { continue; } // Skip empty parts
|
||||
|
||||
const isDirectory = i < parts.length - 1;
|
||||
|
||||
if (!currentNode.children.has(part)) {
|
||||
currentNode.children.set(part, {
|
||||
name: part,
|
||||
isDirectory,
|
||||
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;
|
||||
}
|
||||
|
||||
currentNode = currentNode.children.get(part)!;
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
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');
|
||||
}
|
||||
|
||||
// Helper function to get relative path
|
||||
function getRelativePath(filePath: string, rootPath: string): string {
|
||||
if (filePath.startsWith(rootPath)) {
|
||||
const relativePath = filePath.substring(rootPath.length);
|
||||
return relativePath.startsWith('/') ? relativePath.substring(1) : relativePath;
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
// Run tests
|
||||
function runTests() {
|
||||
console.log('Running file tree generation tests...');
|
||||
|
||||
// Create a temporary directory for our tests
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'prompter-test-'));
|
||||
console.log(`Created temp directory: ${tempDir}`);
|
||||
|
||||
try {
|
||||
// Test 1: Basic file structure
|
||||
console.log('\nTest 1: Basic file structure');
|
||||
const files = [
|
||||
path.join(tempDir, 'file1.txt'),
|
||||
path.join(tempDir, 'folder1/file2.txt'),
|
||||
path.join(tempDir, 'folder1/subfolder/file3.txt'),
|
||||
path.join(tempDir, 'folder2/file4.txt')
|
||||
];
|
||||
|
||||
// Create the directories and files
|
||||
files.forEach(file => {
|
||||
const dir = path.dirname(file);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(file, 'test content');
|
||||
});
|
||||
|
||||
const result1 = generateFileTree(files, tempDir);
|
||||
console.log(result1);
|
||||
|
||||
// Test 2: With .gitignore
|
||||
console.log('\nTest 2: With .gitignore');
|
||||
// Create a .gitignore file with more explicit patterns
|
||||
const gitignorePath = path.join(tempDir, '.gitignore');
|
||||
fs.writeFileSync(gitignorePath, '# Ignore log files\n*.log\n\n# Ignore node_modules directory\nnode_modules/\n\n# Ignore temp directory\ntemp/\n');
|
||||
console.log('Created .gitignore with content:');
|
||||
console.log(fs.readFileSync(gitignorePath, 'utf8'));
|
||||
|
||||
// Create test files for .gitignore testing
|
||||
const files2 = [
|
||||
path.join(tempDir, '.gitignore'),
|
||||
path.join(tempDir, 'file1.txt'),
|
||||
path.join(tempDir, 'file2.log'), // Should be ignored
|
||||
path.join(tempDir, 'node_modules/package.json'), // Should be ignored
|
||||
path.join(tempDir, 'src/index.ts'),
|
||||
path.join(tempDir, 'temp/temp.txt') // Should be ignored
|
||||
];
|
||||
|
||||
console.log('Test files created:');
|
||||
files2.forEach(f => console.log(` - ${getRelativePath(f, tempDir)}`));
|
||||
|
||||
// Create the directories and files
|
||||
files2.forEach(file => {
|
||||
const dir = path.dirname(file);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
// Don't overwrite the .gitignore file
|
||||
if (path.basename(file) !== '.gitignore') {
|
||||
fs.writeFileSync(file, 'test content');
|
||||
}
|
||||
});
|
||||
|
||||
const result2 = generateFileTree(files2, tempDir);
|
||||
console.log(result2);
|
||||
|
||||
// Test 3: Empty directories
|
||||
console.log('\nTest 3: Empty directories');
|
||||
const emptyDir = path.join(tempDir, 'emptyDir');
|
||||
if (!fs.existsSync(emptyDir)) {
|
||||
fs.mkdirSync(emptyDir, { recursive: true });
|
||||
}
|
||||
|
||||
const files3 = [
|
||||
path.join(tempDir, 'file1.txt'),
|
||||
path.join(tempDir, 'emptyDir') // Empty directory
|
||||
];
|
||||
|
||||
const result3 = generateFileTree(files3, tempDir);
|
||||
console.log(result3);
|
||||
|
||||
// Test 4: Sorting
|
||||
console.log('\nTest 4: Sorting (directories first, then files)');
|
||||
const files4 = [
|
||||
path.join(tempDir, 'z_file.txt'),
|
||||
path.join(tempDir, 'a_file.txt'),
|
||||
path.join(tempDir, 'z_folder/file.txt'),
|
||||
path.join(tempDir, 'a_folder/file.txt')
|
||||
];
|
||||
|
||||
// Create the directories and files
|
||||
files4.forEach(file => {
|
||||
const dir = path.dirname(file);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(file, 'test content');
|
||||
});
|
||||
|
||||
const result4 = generateFileTree(files4, tempDir);
|
||||
console.log(result4);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Test error:', error);
|
||||
} finally {
|
||||
// Clean up
|
||||
console.log('\nCleaning up...');
|
||||
try {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
console.log(`Removed temp directory: ${tempDir}`);
|
||||
} catch (cleanupError) {
|
||||
console.error('Error during cleanup:', cleanupError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run the tests
|
||||
runTests();
|
||||
33
src/test/index.ts
Normal file
33
src/test/index.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import Mocha from 'mocha';
|
||||
|
||||
export function run(): Promise<void> {
|
||||
// Create the mocha test
|
||||
const mocha = new Mocha({
|
||||
ui: 'tdd',
|
||||
color: true
|
||||
});
|
||||
|
||||
const testsRoot = path.resolve(__dirname, '..');
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
// Simple approach - add extension.test.js directly
|
||||
// This will find our basic test file
|
||||
mocha.addFile(path.resolve(testsRoot, 'test/extension.test.js'));
|
||||
|
||||
// Run the mocha test
|
||||
mocha.run((failures: number) => {
|
||||
if (failures > 0) {
|
||||
reject(new Error(`${failures} tests failed.`));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
59
src/test/runTest.mjs
Normal file
59
src/test/runTest.mjs
Normal file
@@ -0,0 +1,59 @@
|
||||
import { runTests } from '@vscode/test-electron';
|
||||
import * as path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// Create a temporary test workspace
|
||||
// Using the system temp directory instead of external volume
|
||||
const tmpDir = os.tmpdir();
|
||||
const testWorkspaceDir = path.join(tmpDir, `vscode-test-workspace-${Math.random().toString(36).substring(2)}`);
|
||||
const userDataDir = path.join(tmpDir, `vscode-test-user-data-${Math.random().toString(36).substring(2)}`);
|
||||
|
||||
// Ensure the test workspace directory exists
|
||||
if (!fs.existsSync(testWorkspaceDir)) {
|
||||
fs.mkdirSync(testWorkspaceDir, { recursive: true });
|
||||
}
|
||||
|
||||
// The folder containing the Extension Manifest package.json
|
||||
// Passed to `--extensionDevelopmentPath`
|
||||
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
|
||||
|
||||
// The path to the extension test script
|
||||
// Passed to --extensionTestsPath
|
||||
const extensionTestsPath = path.resolve(__dirname, '../../out/test');
|
||||
|
||||
console.log('Running tests with the following configuration:');
|
||||
console.log(`Extension Development Path: ${extensionDevelopmentPath}`);
|
||||
console.log(`Extension Tests Path: ${extensionTestsPath}`);
|
||||
console.log(`Workspace Dir: ${testWorkspaceDir}`);
|
||||
console.log(`User Data Dir: ${userDataDir}`);
|
||||
|
||||
// Download VS Code, unzip it and run the integration test
|
||||
await runTests({
|
||||
version: '1.98.0', // Specify the exact version your extension is built for
|
||||
extensionDevelopmentPath,
|
||||
extensionTestsPath,
|
||||
launchArgs: [
|
||||
testWorkspaceDir,
|
||||
'--disable-extensions',
|
||||
`--user-data-dir=${userDataDir}`,
|
||||
'--skip-getting-started',
|
||||
'--skip-release-notes',
|
||||
'--disable-telemetry',
|
||||
'--disable-updates',
|
||||
'--disable-crash-reporter',
|
||||
'--disable-workspace-trust'
|
||||
]
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Failed to run tests', err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -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
|
||||
@@ -30,54 +32,81 @@ export class PromptGenerator {
|
||||
* @param settings Settings to apply when generating the prompt
|
||||
* @returns The generated prompt text
|
||||
*/
|
||||
static async generatePrompt(selectedFiles: Set<string>, settings: PrompterSettings): Promise<string> {
|
||||
static async generatePrompt(selectedFiles: Set<string>, settings: PrompterSettings): Promise<string | null> {
|
||||
if (selectedFiles.size === 0) {
|
||||
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 || '';
|
||||
|
||||
// Process each selected file
|
||||
// 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);
|
||||
}
|
||||
|
||||
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) {
|
||||
// Return null to signal that no files were available after filtering
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a plain text prompt
|
||||
* @param files Map of file paths to content and token counts
|
||||
* @param settings Settings to apply
|
||||
* @returns Plain text prompt
|
||||
*/
|
||||
private static generatePlainPrompt(files: Map<string, { content: string; tokens: number }>, settings: PrompterSettings): string {
|
||||
let promptText = '';
|
||||
let totalTokenCount = 0;
|
||||
|
||||
for (const [filePath, { content, tokens }] of files) {
|
||||
const fileName = path.basename(filePath);
|
||||
totalTokenCount += tokens;
|
||||
|
||||
// Add the file to the prompt
|
||||
promptText += `File: ${fileName}\n`;
|
||||
promptText += `${content}\n`;
|
||||
|
||||
promptText += '\n';
|
||||
// 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)!);
|
||||
}
|
||||
}
|
||||
|
||||
// Add token count if enabled
|
||||
if (settings.tokenCalculationEnabled) {
|
||||
promptText += `\nEstimated token count: ${totalTokenCount}`;
|
||||
return this.generateXMLPrompt(filteredContents, settings);
|
||||
}
|
||||
|
||||
return promptText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an XML formatted prompt following the new schema format
|
||||
@@ -88,11 +117,9 @@ 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, '..', '..');
|
||||
const formattingInstructionsPath = path.join(extensionPath, 'resources', 'xml_formatting_instructions.xml');
|
||||
@@ -107,29 +134,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 +165,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 +176,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 +195,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 +243,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 +250,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 +264,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');
|
||||
}
|
||||
|
||||
|
||||
18
tsconfig.webpack.json
Normal file
18
tsconfig.webpack.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "ES2020",
|
||||
"outDir": "dist",
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"src/test/**",
|
||||
"**/*.test.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
||||
46
webpack.config.js
Normal file
46
webpack.config.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
/**
|
||||
* @type {import('webpack').Configuration}
|
||||
*/
|
||||
const config = {
|
||||
target: 'node',
|
||||
mode: 'none', // Set to 'production' for minified output
|
||||
|
||||
entry: {
|
||||
main: './src/extension.ts'
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'extension.js',
|
||||
libraryTarget: 'commonjs2',
|
||||
devtoolModuleFilenameTemplate: '../[resource-path]'
|
||||
},
|
||||
devtool: 'source-map',
|
||||
externals: {
|
||||
vscode: 'commonjs vscode' // The vscode module is created on-the-fly and must be excluded
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js']
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
exclude: /node_modules|src\/test/,
|
||||
use: [
|
||||
{
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
transpileOnly: true,
|
||||
configFile: path.resolve(__dirname, './tsconfig.webpack.json')
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
Reference in New Issue
Block a user