Skip to content

Commit f5e04fe

Browse files
committed
feat: simplify file source implementation
1 parent 6355e00 commit f5e04fe

File tree

1 file changed

+42
-68
lines changed

1 file changed

+42
-68
lines changed

builtin/source/file.ts

Lines changed: 42 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import * as fn from "@denops/std/function";
21
import { enumerate } from "@core/iterutil/async/enumerate";
3-
import { join } from "@std/path/join";
2+
import { SEPARATOR } from "@std/path/constants";
43

54
import { 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

1913
export type FileOptions = {
@@ -40,93 +34,73 @@ export type FileOptions = {
4034
export 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+
130104
function isSilence(err: unknown): boolean {
131105
if (err instanceof Deno.errors.NotFound) {
132106
return true;

0 commit comments

Comments
 (0)