1- import * as fn from "@denops/std/function" ;
21import { enumerate } from "@core/iterutil/async/enumerate" ;
3- import { join } from "@std/path/join " ;
2+ import { SEPARATOR } from "@std/path/constants " ;
43
54import { defineSource , type Source } from "../../source.ts" ;
65
@@ -9,11 +8,6 @@ type Detail = {
98 * Absolute path of the file.
109 */
1110 path : string ;
12-
13- /**
14- * File information including metadata like size, permissions, etc.
15- */
16- stat : Deno . FileInfo ;
1711} ;
1812
1913export type FileOptions = {
@@ -40,93 +34,73 @@ export type FileOptions = {
4034export function file ( options : Readonly < FileOptions > = { } ) : Source < Detail > {
4135 const { includes, excludes } = options ;
4236 return defineSource ( async function * ( denops , { args } , { signal } ) {
43- const path = await fn . expand ( denops , args [ 0 ] ?? "." ) as string ;
44- signal ?. throwIfAborted ( ) ;
45- const abspath = await fn . fnamemodify ( denops , path , ":p" ) ;
37+ const root = removeTrailingSeparator (
38+ await denops . eval (
39+ "fnamemodify(expand(path), ':p')" ,
40+ { path : args [ 0 ] ?? "." } ,
41+ ) as string ,
42+ ) ;
4643 signal ?. throwIfAborted ( ) ;
4744
45+ const filter = ( path : string ) => {
46+ if ( includes && ! includes . some ( ( p ) => p . test ( path ) ) ) {
47+ return false ;
48+ } else if ( excludes && excludes . some ( ( p ) => p . test ( path ) ) ) {
49+ return false ;
50+ }
51+ return true ;
52+ } ;
53+
4854 // Enumerate files and apply filters
49- for await (
50- const [ id , detail ] of enumerate (
51- collect ( abspath , includes , excludes , signal ) ,
52- )
53- ) {
55+ for await ( const [ id , path ] of enumerate ( walk ( root , filter , signal ) ) ) {
5456 yield {
5557 id,
56- value : detail . path ,
57- detail,
58+ value : path ,
59+ detail : { path } ,
5860 } ;
5961 }
6062 } ) ;
6163}
6264
63- /**
64- * Recursively collects files from a given directory, applying optional filters.
65- *
66- * @param root - The root directory to start collecting files.
67- * @param includes - Patterns to include files.
68- * @param excludes - Patterns to exclude files.
69- * @param signal - Optional signal to handle abort requests.
70- */
71- async function * collect (
65+ async function * walk (
7266 root : string ,
73- includes : RegExp [ ] | undefined ,
74- excludes : RegExp [ ] | undefined ,
67+ filter : ( path : string ) => boolean ,
7568 signal ?: AbortSignal ,
76- ) : AsyncIterableIterator < Detail > {
69+ ) : AsyncIterableIterator < string > {
7770 for await ( const entry of Deno . readDir ( root ) ) {
78- const path = join ( root , entry . name ) ;
79-
80- // Apply include and exclude filters
81- if ( includes && ! includes . some ( ( p ) => p . test ( path ) ) ) {
82- continue ;
83- } else if ( excludes && excludes . some ( ( p ) => p . test ( path ) ) ) {
84- continue ;
85- }
86-
87- let fileInfo : Deno . FileInfo ;
71+ const path = `${ root } ${ SEPARATOR } ${ entry . name } ` ;
72+ // Skip files that do not match the filter
73+ if ( ! filter ( path ) ) continue ;
74+ // Follow symbolic links to recursively yield files
75+ let isDirectory = entry . isDirectory ;
8876 if ( entry . isSymlink ) {
89- // Handle symbolic links by resolving their real path
90- try {
91- const realPath = await Deno . realPath ( path ) ;
92- signal ?. throwIfAborted ( ) ;
93- fileInfo = await Deno . stat ( realPath ) ;
94- signal ?. throwIfAborted ( ) ;
95- } catch ( err ) {
96- if ( isSilence ( err ) ) continue ;
97- throw err ;
98- }
99- } else {
100- // Get file info for regular files and directories
10177 try {
102- fileInfo = await Deno . stat ( path ) ;
78+ const fileInfo = await Deno . stat ( path ) ;
10379 signal ?. throwIfAborted ( ) ;
80+ isDirectory = fileInfo . isDirectory ;
10481 } catch ( err ) {
105- if ( isSilence ( err ) ) continue ;
82+ if ( isSilence ( err ) ) {
83+ continue ;
84+ }
10685 throw err ;
10786 }
10887 }
109-
11088 // Recursively yield files from directories, or yield file details
111- if ( fileInfo . isDirectory ) {
112- yield * collect ( path , includes , excludes , signal ) ;
89+ if ( isDirectory ) {
90+ yield * walk ( path , filter , signal ) ;
11391 } else {
114- yield {
115- path,
116- stat : fileInfo ,
117- } ;
92+ yield path ;
11893 }
11994 }
12095}
12196
122- /**
123- * Determines if an error is silent (non-fatal) and should be ignored.
124- *
125- * This includes errors like file not found or permission denied.
126- *
127- * @param err - The error to check.
128- * @returns Whether the error should be silently ignored.
129- */
97+ function removeTrailingSeparator ( path : string ) : string {
98+ if ( path . endsWith ( SEPARATOR ) ) {
99+ return path . slice ( 0 , path . length - SEPARATOR . length ) ;
100+ }
101+ return path ;
102+ }
103+
130104function isSilence ( err : unknown ) : boolean {
131105 if ( err instanceof Deno . errors . NotFound ) {
132106 return true ;
0 commit comments