add support for recursive selection and deselection of files in the tree view
This commit is contained in:
10
.vscode-test.mjs
Normal file
10
.vscode-test.mjs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import * as path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
export default {
|
||||||
|
version: 'stable',
|
||||||
|
extensionDevelopmentPath: __dirname,
|
||||||
|
extensionTestsPath: path.join(__dirname, 'out', 'test')
|
||||||
|
};
|
||||||
@@ -15,19 +15,31 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
|
|
||||||
// Get the workspace folder
|
// Get the workspace folder
|
||||||
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath;
|
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath;
|
||||||
|
console.log('Workspace root:', workspaceRoot);
|
||||||
|
|
||||||
// Create the tree data provider
|
// Create the tree data provider
|
||||||
|
console.log('Creating tree data provider...');
|
||||||
const prompterTreeProvider = new PrompterTreeProvider(workspaceRoot);
|
const prompterTreeProvider = new PrompterTreeProvider(workspaceRoot);
|
||||||
|
console.log('Tree data provider created successfully');
|
||||||
|
|
||||||
// Register the TreeView with checkbox support
|
// Register the TreeView with checkbox support
|
||||||
const treeView = vscode.window.createTreeView('prompterView', {
|
console.log('Registering tree view with ID: prompterView');
|
||||||
treeDataProvider: prompterTreeProvider,
|
let treeView: vscode.TreeView<FileTreeItem | SettingTreeItem>;
|
||||||
showCollapseAll: true,
|
try {
|
||||||
canSelectMany: true
|
treeView = vscode.window.createTreeView('prompterView', {
|
||||||
});
|
treeDataProvider: prompterTreeProvider,
|
||||||
|
showCollapseAll: true,
|
||||||
|
canSelectMany: true
|
||||||
|
});
|
||||||
|
console.log('Tree view registered successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error registering tree view:', error);
|
||||||
|
// Create a fallback empty tree view to prevent further errors
|
||||||
|
treeView = {} as vscode.TreeView<FileTreeItem | SettingTreeItem>;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle checkbox changes
|
// Handle checkbox changes
|
||||||
treeView.onDidChangeCheckboxState(e => {
|
treeView.onDidChangeCheckboxState((e: vscode.TreeCheckboxChangeEvent<FileTreeItem | SettingTreeItem>) => {
|
||||||
console.log('Checkbox state changed');
|
console.log('Checkbox state changed');
|
||||||
for (const [item, state] of e.items) {
|
for (const [item, state] of e.items) {
|
||||||
if (item instanceof FileTreeItem) {
|
if (item instanceof FileTreeItem) {
|
||||||
|
|||||||
@@ -94,16 +94,92 @@ export class PrompterTreeProvider implements vscode.TreeDataProvider<FileTreeIte
|
|||||||
// Add a file to the selection
|
// Add a file to the selection
|
||||||
addToSelection(item: FileTreeItem): void {
|
addToSelection(item: FileTreeItem): void {
|
||||||
if (item.resourceUri) {
|
if (item.resourceUri) {
|
||||||
this.selectedFiles.add(item.resourceUri.fsPath);
|
const filePath = item.resourceUri.fsPath;
|
||||||
console.log(`Added ${item.resourceUri.fsPath} to selection`);
|
|
||||||
|
// Check if it's a directory
|
||||||
|
if (item.collapsibleState === vscode.TreeItemCollapsibleState.Collapsed ||
|
||||||
|
item.collapsibleState === vscode.TreeItemCollapsibleState.Expanded) {
|
||||||
|
// It's a directory, recursively add all files
|
||||||
|
this.addDirectoryToSelection(filePath);
|
||||||
|
} else {
|
||||||
|
// It's a file, just add it
|
||||||
|
this.selectedFiles.add(filePath);
|
||||||
|
console.log(`Added ${filePath} to selection`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recursively add all files in a directory to the selection
|
||||||
|
private async addDirectoryToSelection(dirPath: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Add the directory itself
|
||||||
|
this.selectedFiles.add(dirPath);
|
||||||
|
console.log(`Added directory ${dirPath} to 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.addDirectoryToSelection(filePath);
|
||||||
|
} else {
|
||||||
|
// Add files
|
||||||
|
this.selectedFiles.add(filePath);
|
||||||
|
console.log(`Added ${filePath} to selection (from directory)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error adding directory to selection: ${dirPath}`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove a file from the selection
|
// Remove a file from the selection
|
||||||
removeFromSelection(item: FileTreeItem): void {
|
removeFromSelection(item: FileTreeItem): void {
|
||||||
if (item.resourceUri) {
|
if (item.resourceUri) {
|
||||||
this.selectedFiles.delete(item.resourceUri.fsPath);
|
const filePath = item.resourceUri.fsPath;
|
||||||
console.log(`Removed ${item.resourceUri.fsPath} from selection`);
|
|
||||||
|
// Check if it's a directory
|
||||||
|
if (item.collapsibleState === vscode.TreeItemCollapsibleState.Collapsed ||
|
||||||
|
item.collapsibleState === vscode.TreeItemCollapsibleState.Expanded) {
|
||||||
|
// It's a directory, recursively remove all files
|
||||||
|
this.removeDirectoryFromSelection(filePath);
|
||||||
|
} else {
|
||||||
|
// It's a file, just remove it
|
||||||
|
this.selectedFiles.delete(filePath);
|
||||||
|
console.log(`Removed ${filePath} from selection`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively remove all files in a directory from the selection
|
||||||
|
private async removeDirectoryFromSelection(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.removeDirectoryFromSelection(filePath);
|
||||||
|
} else {
|
||||||
|
// Remove files
|
||||||
|
this.selectedFiles.delete(filePath);
|
||||||
|
console.log(`Removed ${filePath} from selection (from directory)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error removing directory from selection: ${dirPath}`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
251
src/test/fileTreeTest.ts
Normal file
251
src/test/fileTreeTest.ts
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
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();
|
||||||
@@ -3,6 +3,21 @@ import * as fs from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { PrompterSettings } from '../models/settings';
|
import { PrompterSettings } from '../models/settings';
|
||||||
import { FileReader } from './fileReader';
|
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';
|
import { TokenEstimator } from './tokenEstimator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -168,47 +183,117 @@ export class PromptGenerator {
|
|||||||
* @returns String representation of the file tree
|
* @returns String representation of the file tree
|
||||||
*/
|
*/
|
||||||
private static generateFileTree(files: Map<string, any>, rootPath: string): string {
|
private static generateFileTree(files: Map<string, any>, rootPath: string): string {
|
||||||
// Create a simple tree representation
|
// Create a tree representation
|
||||||
const treeLines: string[] = [];
|
const treeLines: string[] = [];
|
||||||
|
|
||||||
// Group files by directory
|
// Create a tree structure
|
||||||
const dirMap = new Map<string, string[]>();
|
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
|
||||||
|
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()) {
|
for (const filePath of files.keys()) {
|
||||||
const relativePath = this.getRelativePath(filePath, rootPath);
|
const relativePath = this.getRelativePath(filePath, rootPath);
|
||||||
const dir = path.dirname(relativePath);
|
|
||||||
|
|
||||||
if (!dirMap.has(dir)) {
|
// Skip ignored files using the ignore package
|
||||||
dirMap.set(dir, []);
|
// Use forward slashes for paths to ensure consistent matching across platforms
|
||||||
|
const normalizedPath = relativePath.split(path.sep).join('/');
|
||||||
|
if (ig.ignores(normalizedPath)) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
dirMap.get(dir)?.push(path.basename(filePath));
|
// Split the path into parts
|
||||||
}
|
const parts = relativePath.split('/');
|
||||||
|
|
||||||
// Sort directories
|
|
||||||
const sortedDirs = Array.from(dirMap.keys()).sort();
|
|
||||||
|
|
||||||
// Build the tree
|
|
||||||
for (let i = 0; i < sortedDirs.length; i++) {
|
|
||||||
const dir = sortedDirs[i];
|
|
||||||
const isLast = i === sortedDirs.length - 1;
|
|
||||||
const prefix = isLast ? '└── ' : '├── ';
|
|
||||||
|
|
||||||
// Skip root directory
|
// Start from the root
|
||||||
if (dir !== '.') {
|
let currentNode = root;
|
||||||
treeLines.push(`${prefix}${dir}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add files
|
// Build the path in the tree
|
||||||
const files = dirMap.get(dir)?.sort() || [];
|
for (let i = 0; i < parts.length; i++) {
|
||||||
for (let j = 0; j < files.length; j++) {
|
const part = parts[i];
|
||||||
const file = files[j];
|
const isDirectory = i < parts.length - 1;
|
||||||
const isLastFile = j === files.length - 1;
|
|
||||||
const filePrefix = dir === '.' ? (isLastFile ? '└── ' : '├── ') : ' ' + (isLastFile ? '└── ' : '├── ');
|
if (!currentNode.children.has(part)) {
|
||||||
treeLines.push(`${filePrefix}${file}`);
|
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');
|
return treeLines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user