@@ -32,33 +32,105 @@ export async function listFiles(dirPath: string, recursive: boolean, limit: numb
3232 // Get ripgrep path
3333 const rgPath = await getRipgrepPath ( )
3434
35- // Get files using ripgrep
36- const files = await listFilesWithRipgrep ( rgPath , dirPath , recursive , limit )
35+ if ( ! recursive ) {
36+ // For non-recursive, use the existing approach
37+ const files = await listFilesWithRipgrep ( rgPath , dirPath , false , limit )
38+ const ignoreInstance = await createIgnoreInstance ( dirPath )
39+ const directories = await listFilteredDirectories ( dirPath , false , ignoreInstance )
40+ return formatAndCombineResults ( files , directories , limit )
41+ }
3742
38- // Get directories with proper filtering using ignore library
43+ // For recursive mode, use the original approach but ensure first-level directories are included
44+ const files = await listFilesWithRipgrep ( rgPath , dirPath , true , limit )
3945 const ignoreInstance = await createIgnoreInstance ( dirPath )
40- const directories = await listFilteredDirectories ( dirPath , recursive , ignoreInstance )
46+ const directories = await listFilteredDirectories ( dirPath , true , ignoreInstance )
47+
48+ // Combine and check if we hit the limit
49+ const [ results , limitReached ] = formatAndCombineResults ( files , directories , limit )
50+
51+ // If we hit the limit, ensure all first-level directories are included
52+ if ( limitReached ) {
53+ const firstLevelDirs = await getFirstLevelDirectories ( dirPath , ignoreInstance )
54+ return ensureFirstLevelDirectoriesIncluded ( results , firstLevelDirs , limit )
55+ }
4156
42- let allFiles = files
43- let allDirectories = directories
44- const limitReached = files . length + directories . length >= limit
57+ return [ results , limitReached ]
58+ }
4559
46- // If limit is reached in recursive mode, fetch one more layer non-recursively and merge results
47- if ( recursive && limitReached ) {
48- // Get files and directories in one layer (non-recursive)
49- const filesNonRecursive = await listFilesWithRipgrep ( rgPath , dirPath , false , limit )
50- const directoriesNonRecursive = await listFilteredDirectories ( dirPath , false , [ ] )
60+ /**
61+ * Get only the first-level directories in a path
62+ */
63+ async function getFirstLevelDirectories ( dirPath : string , ignoreInstance : ReturnType < typeof ignore > ) : Promise < string [ ] > {
64+ const absolutePath = path . resolve ( dirPath )
65+ const directories : string [ ] = [ ]
5166
52- // Merge recursive and non-recursive results, deduplicate
53- const filesSet = new Set ( [ ...files , ...filesNonRecursive ] )
54- const directoriesSet = new Set ( [ ...directories , ...directoriesNonRecursive ] )
67+ try {
68+ const entries = await fs . promises . readdir ( absolutePath , { withFileTypes : true } )
5569
56- allFiles = Array . from ( filesSet )
57- allDirectories = Array . from ( directoriesSet )
70+ for ( const entry of entries ) {
71+ if ( entry . isDirectory ( ) && ! entry . isSymbolicLink ( ) ) {
72+ const fullDirPath = path . join ( absolutePath , entry . name )
73+ if ( shouldIncludeDirectory ( entry . name , fullDirPath , dirPath , ignoreInstance ) ) {
74+ const formattedPath = fullDirPath . endsWith ( "/" ) ? fullDirPath : `${ fullDirPath } /`
75+ directories . push ( formattedPath )
76+ }
77+ }
78+ }
79+ } catch ( err ) {
80+ console . warn ( `Could not read directory ${ absolutePath } : ${ err } ` )
5881 }
5982
60- // Combine and format the results
61- return formatAndCombineResults ( allFiles , allDirectories , limit )
83+ return directories
84+ }
85+
86+ /**
87+ * Ensure all first-level directories are included in the results
88+ */
89+ function ensureFirstLevelDirectoriesIncluded (
90+ results : string [ ] ,
91+ firstLevelDirs : string [ ] ,
92+ limit : number ,
93+ ) : [ string [ ] , boolean ] {
94+ // Create a set of existing paths for quick lookup
95+ const existingPaths = new Set ( results )
96+
97+ // Find missing first-level directories
98+ const missingDirs = firstLevelDirs . filter ( ( dir ) => ! existingPaths . has ( dir ) )
99+
100+ if ( missingDirs . length === 0 ) {
101+ // All first-level directories are already included
102+ return [ results , true ]
103+ }
104+
105+ // We need to make room for the missing directories
106+ // Remove items from the end (which are likely deeper in the tree)
107+ const itemsToRemove = Math . min ( missingDirs . length , results . length )
108+ const adjustedResults = results . slice ( 0 , results . length - itemsToRemove )
109+
110+ // Add the missing directories at the beginning (after any existing first-level dirs)
111+ // First, separate existing results into first-level and others
112+ const resultPaths = adjustedResults . map ( ( r ) => path . resolve ( r ) )
113+ const basePath = path . resolve ( firstLevelDirs [ 0 ] ) . split ( path . sep ) . slice ( 0 , - 1 ) . join ( path . sep )
114+
115+ const firstLevelResults : string [ ] = [ ]
116+ const otherResults : string [ ] = [ ]
117+
118+ for ( let i = 0 ; i < adjustedResults . length ; i ++ ) {
119+ const resolvedPath = resultPaths [ i ]
120+ const relativePath = path . relative ( basePath , resolvedPath )
121+ const depth = relativePath . split ( path . sep ) . length
122+
123+ if ( depth === 1 ) {
124+ firstLevelResults . push ( adjustedResults [ i ] )
125+ } else {
126+ otherResults . push ( adjustedResults [ i ] )
127+ }
128+ }
129+
130+ // Combine: existing first-level dirs + missing first-level dirs + other results
131+ const finalResults = [ ...firstLevelResults , ...missingDirs , ...otherResults ] . slice ( 0 , limit )
132+
133+ return [ finalResults , true ]
62134}
63135
64136/**
0 commit comments