@ff-labs/fff-node 0.10.4-nightly.dd87489 → 0.10.5-dev.4d662ef

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1971 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ FileFinder: () => FileFinder,
24
+ binaryExists: () => binaryExists,
25
+ closeLibrary: () => closeLibrary,
26
+ err: () => err,
27
+ findBinary: () => findBinary,
28
+ getLibExtension: () => getLibExtension,
29
+ getLibFilename: () => getLibFilename,
30
+ getNpmPackageName: () => getNpmPackageName,
31
+ getTriple: () => getTriple,
32
+ ok: () => ok
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+
36
+ // src/binary.ts
37
+ var import_node_fs = require("fs");
38
+ var import_node_module = require("module");
39
+ var import_node_path = require("path");
40
+ var import_node_url = require("url");
41
+
42
+ // src/platform.ts
43
+ var import_node_child_process = require("child_process");
44
+ function getTriple() {
45
+ const platform = process.platform;
46
+ const arch = process.arch;
47
+ let osName;
48
+ if (platform === "darwin") {
49
+ osName = "apple-darwin";
50
+ } else if (platform === "android") {
51
+ osName = "linux-android";
52
+ } else if (platform === "linux") {
53
+ osName = detectLinuxLibc();
54
+ } else if (platform === "win32") {
55
+ osName = "pc-windows-msvc";
56
+ } else {
57
+ throw new Error(`Unsupported platform: ${platform}`);
58
+ }
59
+ const archName = normalizeArch(arch);
60
+ return `${archName}-${osName}`;
61
+ }
62
+ function detectLinuxLibc() {
63
+ let output = "";
64
+ try {
65
+ output = (0, import_node_child_process.execSync)("ldd --version 2>&1", {
66
+ encoding: "utf-8",
67
+ timeout: 5e3
68
+ });
69
+ } catch (e) {
70
+ const err2 = e;
71
+ output = String(err2?.stdout ?? "") + String(err2?.stderr ?? "");
72
+ }
73
+ if (output.toLowerCase().includes("musl")) {
74
+ return "unknown-linux-musl";
75
+ }
76
+ return "unknown-linux-gnu";
77
+ }
78
+ function normalizeArch(arch) {
79
+ switch (arch) {
80
+ case "x64":
81
+ case "amd64":
82
+ return "x86_64";
83
+ case "arm64":
84
+ return "aarch64";
85
+ case "arm":
86
+ return "arm";
87
+ default:
88
+ throw new Error(`Unsupported architecture: ${arch}`);
89
+ }
90
+ }
91
+ function getLibExtension() {
92
+ switch (process.platform) {
93
+ case "darwin":
94
+ return "dylib";
95
+ case "win32":
96
+ return "dll";
97
+ default:
98
+ return "so";
99
+ }
100
+ }
101
+ function getLibPrefix() {
102
+ return process.platform === "win32" ? "" : "lib";
103
+ }
104
+ function getLibFilename() {
105
+ const prefix = getLibPrefix();
106
+ const ext = getLibExtension();
107
+ return `${prefix}fff_c.${ext}`;
108
+ }
109
+ var TRIPLE_TO_NPM_PACKAGE = {
110
+ "aarch64-apple-darwin": "@ff-labs/fff-bin-darwin-arm64",
111
+ "x86_64-apple-darwin": "@ff-labs/fff-bin-darwin-x64",
112
+ "x86_64-unknown-linux-gnu": "@ff-labs/fff-bin-linux-x64-gnu",
113
+ "aarch64-unknown-linux-gnu": "@ff-labs/fff-bin-linux-arm64-gnu",
114
+ "x86_64-unknown-linux-musl": "@ff-labs/fff-bin-linux-x64-musl",
115
+ "aarch64-unknown-linux-musl": "@ff-labs/fff-bin-linux-arm64-musl",
116
+ "x86_64-pc-windows-msvc": "@ff-labs/fff-bin-win32-x64",
117
+ "aarch64-pc-windows-msvc": "@ff-labs/fff-bin-win32-arm64",
118
+ "aarch64-linux-android": "@ff-labs/fff-bin-android-arm64"
119
+ };
120
+ function getNpmPackageName() {
121
+ const triple = getTriple();
122
+ const packageName = TRIPLE_TO_NPM_PACKAGE[triple];
123
+ if (!packageName) {
124
+ throw new Error(`No npm package available for platform: ${triple}`);
125
+ }
126
+ return packageName;
127
+ }
128
+
129
+ // src/binary.ts
130
+ var import_meta = {};
131
+ function getCurrentDir() {
132
+ if (typeof __dirname !== "undefined") return __dirname;
133
+ const url = import_meta.url;
134
+ if (url.startsWith("file://")) {
135
+ return (0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(url));
136
+ }
137
+ return (0, import_node_path.dirname)(url);
138
+ }
139
+ function getPackageDir() {
140
+ const currentDir = getCurrentDir();
141
+ let dir = currentDir;
142
+ for (let i = 0; i < 5; i++) {
143
+ if ((0, import_node_fs.existsSync)((0, import_node_path.join)(dir, "package.json"))) {
144
+ try {
145
+ const pkg = JSON.parse((0, import_node_fs.readFileSync)((0, import_node_path.join)(dir, "package.json"), "utf-8"));
146
+ if (pkg.name === "@ff-labs/fff-node") {
147
+ return dir;
148
+ }
149
+ } catch {
150
+ }
151
+ }
152
+ dir = (0, import_node_path.dirname)(dir);
153
+ }
154
+ return (0, import_node_path.dirname)(currentDir);
155
+ }
156
+ function binaryExists() {
157
+ return findBinary() !== null;
158
+ }
159
+ function resolveFromNpmPackage() {
160
+ const packageName = getNpmPackageName();
161
+ try {
162
+ const require2 = (0, import_node_module.createRequire)((0, import_node_path.join)(getPackageDir(), "package.json"));
163
+ const packageJsonPath = require2.resolve(`${packageName}/package.json`);
164
+ const packageDir = (0, import_node_path.dirname)(packageJsonPath);
165
+ const binaryPath = (0, import_node_path.join)(packageDir, getLibFilename());
166
+ if ((0, import_node_fs.existsSync)(binaryPath)) {
167
+ return binaryPath;
168
+ }
169
+ } catch {
170
+ }
171
+ return null;
172
+ }
173
+ function getDevBinaryPath() {
174
+ const packageDir = getPackageDir();
175
+ const workspaceRoot = (0, import_node_path.join)(packageDir, "..", "..");
176
+ const possiblePaths = [
177
+ (0, import_node_path.join)(workspaceRoot, "target", "release", getLibFilename()),
178
+ (0, import_node_path.join)(workspaceRoot, "target", "debug", getLibFilename())
179
+ ];
180
+ for (const path of possiblePaths) {
181
+ if ((0, import_node_fs.existsSync)(path)) {
182
+ return path;
183
+ }
184
+ }
185
+ return null;
186
+ }
187
+ function isDevWorkspace() {
188
+ const packageDir = getPackageDir();
189
+ const workspaceRoot = (0, import_node_path.join)(packageDir, "..", "..");
190
+ return (0, import_node_fs.existsSync)((0, import_node_path.join)(workspaceRoot, "Cargo.toml"));
191
+ }
192
+ function findBinary() {
193
+ if (isDevWorkspace()) {
194
+ const binPath = (0, import_node_path.join)(getPackageDir(), "bin", getLibFilename());
195
+ if ((0, import_node_fs.existsSync)(binPath)) return binPath;
196
+ const devPath = getDevBinaryPath();
197
+ if (devPath) return devPath;
198
+ const npmPath2 = resolveFromNpmPackage();
199
+ if (npmPath2) return npmPath2;
200
+ return null;
201
+ }
202
+ const npmPath = resolveFromNpmPackage();
203
+ if (npmPath) return npmPath;
204
+ return getDevBinaryPath();
205
+ }
206
+
207
+ // src/ffi.ts
208
+ var import_ffi_rs = require("ffi-rs");
209
+
210
+ // src/fff-api.ts
211
+ function ok(value) {
212
+ return { ok: true, value };
213
+ }
214
+ function err(error) {
215
+ return { ok: false, error };
216
+ }
217
+ function createGrepCursor(offset) {
218
+ return { __brand: "GrepCursor", _offset: offset };
219
+ }
220
+
221
+ // src/ffi.ts
222
+ var LIBRARY_KEY = "fff_c";
223
+ var FFF_CREATE_OPTIONS_STRUCT = {
224
+ version: import_ffi_rs.DataType.U32,
225
+ base_path: import_ffi_rs.DataType.String,
226
+ frecency_db_path: import_ffi_rs.DataType.String,
227
+ history_db_path: import_ffi_rs.DataType.String,
228
+ enable_mmap_cache: import_ffi_rs.DataType.U8,
229
+ enable_content_indexing: import_ffi_rs.DataType.U8,
230
+ watch: import_ffi_rs.DataType.U8,
231
+ ai_mode: import_ffi_rs.DataType.U8,
232
+ log_file_path: import_ffi_rs.DataType.String,
233
+ log_level: import_ffi_rs.DataType.String,
234
+ cache_budget_max_files: import_ffi_rs.DataType.U64,
235
+ cache_budget_max_bytes: import_ffi_rs.DataType.U64,
236
+ cache_budget_max_file_size: import_ffi_rs.DataType.U64,
237
+ enable_fs_root_scanning: import_ffi_rs.DataType.U8,
238
+ enable_home_dir_scanning: import_ffi_rs.DataType.U8,
239
+ follow_symlinks: import_ffi_rs.DataType.U8
240
+ };
241
+ var FFF_CREATE_OPTIONS_VERSION = 2;
242
+ var GREP_MODE_PLAIN = 0;
243
+ var GREP_MODE_REGEX = 1;
244
+ var GREP_MODE_FUZZY = 2;
245
+ function grepModeToU8(mode) {
246
+ switch (mode) {
247
+ case "regex":
248
+ return GREP_MODE_REGEX;
249
+ case "fuzzy":
250
+ return GREP_MODE_FUZZY;
251
+ default:
252
+ return GREP_MODE_PLAIN;
253
+ }
254
+ }
255
+ var isLoaded = false;
256
+ var FFF_RESULT_STRUCT = {
257
+ success: import_ffi_rs.DataType.U8,
258
+ error: import_ffi_rs.DataType.External,
259
+ handle: import_ffi_rs.DataType.External,
260
+ int_value: import_ffi_rs.DataType.I64
261
+ };
262
+ function loadLibrary() {
263
+ if (isLoaded) return;
264
+ const binaryPath = findBinary();
265
+ if (!binaryPath) {
266
+ throw new Error(
267
+ "fff native library not found. Run `npx @ff-labs/fff-node download` or build from source with `cargo build --release -p fff-c`"
268
+ );
269
+ }
270
+ (0, import_ffi_rs.open)({ library: LIBRARY_KEY, path: binaryPath });
271
+ isLoaded = true;
272
+ }
273
+ function snakeToCamel(obj) {
274
+ if (obj === null || obj === void 0) return obj;
275
+ if (typeof obj !== "object") return obj;
276
+ if (Array.isArray(obj)) return obj.map(snakeToCamel);
277
+ const result = {};
278
+ for (const [key, value] of Object.entries(obj)) {
279
+ const camelKey = key.replace(
280
+ /_([a-z])/g,
281
+ (_, letter) => letter.toUpperCase()
282
+ );
283
+ result[camelKey] = snakeToCamel(value);
284
+ }
285
+ return result;
286
+ }
287
+ function readCString(ptr) {
288
+ if ((0, import_ffi_rs.isNullPointer)(ptr)) return null;
289
+ try {
290
+ const [str] = (0, import_ffi_rs.restorePointer)({
291
+ retType: [import_ffi_rs.DataType.String],
292
+ paramsValue: (0, import_ffi_rs.wrapPointer)([ptr])
293
+ });
294
+ return str;
295
+ } catch {
296
+ return null;
297
+ }
298
+ }
299
+ function callRaw(funcName, paramsType, paramsValue) {
300
+ const rawPtr = (0, import_ffi_rs.load)({
301
+ library: LIBRARY_KEY,
302
+ funcName,
303
+ retType: import_ffi_rs.DataType.External,
304
+ paramsType,
305
+ paramsValue,
306
+ freeResultMemory: false
307
+ });
308
+ const [structData] = (0, import_ffi_rs.restorePointer)({
309
+ retType: [FFF_RESULT_STRUCT],
310
+ paramsValue: (0, import_ffi_rs.wrapPointer)([rawPtr])
311
+ });
312
+ return { rawPtr, struct: structData };
313
+ }
314
+ function freeResult(resultPtr) {
315
+ try {
316
+ (0, import_ffi_rs.load)({
317
+ library: LIBRARY_KEY,
318
+ funcName: "fff_free_result",
319
+ retType: import_ffi_rs.DataType.Void,
320
+ paramsType: [import_ffi_rs.DataType.External],
321
+ paramsValue: [resultPtr]
322
+ });
323
+ } catch {
324
+ }
325
+ }
326
+ function readResultEnvelope(funcName, paramsType, paramsValue) {
327
+ loadLibrary();
328
+ const { rawPtr, struct: structData } = callRaw(funcName, paramsType, paramsValue);
329
+ if (structData.success === 0) {
330
+ const errorStr = readCString(structData.error);
331
+ freeResult(rawPtr);
332
+ return err(errorStr || "Unknown error");
333
+ }
334
+ return { rawPtr, struct: structData };
335
+ }
336
+ function callVoidResult(funcName, paramsType, paramsValue) {
337
+ const res = readResultEnvelope(funcName, paramsType, paramsValue);
338
+ if ("ok" in res) return res;
339
+ freeResult(res.rawPtr);
340
+ return { ok: true, value: void 0 };
341
+ }
342
+ function callIntResult(funcName, paramsType, paramsValue) {
343
+ const res = readResultEnvelope(funcName, paramsType, paramsValue);
344
+ if ("ok" in res) return res;
345
+ const value = Number(res.struct.int_value);
346
+ freeResult(res.rawPtr);
347
+ return { ok: true, value };
348
+ }
349
+ function callBoolResult(funcName, paramsType, paramsValue) {
350
+ const res = readResultEnvelope(funcName, paramsType, paramsValue);
351
+ if ("ok" in res) return res;
352
+ const value = Number(res.struct.int_value) !== 0;
353
+ freeResult(res.rawPtr);
354
+ return { ok: true, value };
355
+ }
356
+ function callStringResult(funcName, paramsType, paramsValue) {
357
+ const res = readResultEnvelope(funcName, paramsType, paramsValue);
358
+ if ("ok" in res) return res;
359
+ const handlePtr = res.struct.handle;
360
+ freeResult(res.rawPtr);
361
+ if ((0, import_ffi_rs.isNullPointer)(handlePtr)) return { ok: true, value: null };
362
+ const str = readCString(handlePtr);
363
+ freeString(handlePtr);
364
+ return { ok: true, value: str };
365
+ }
366
+ function callJsonResult(funcName, paramsType, paramsValue) {
367
+ const res = readResultEnvelope(funcName, paramsType, paramsValue);
368
+ if ("ok" in res) return res;
369
+ const handlePtr = res.struct.handle;
370
+ freeResult(res.rawPtr);
371
+ if ((0, import_ffi_rs.isNullPointer)(handlePtr)) return { ok: true, value: void 0 };
372
+ const jsonStr = readCString(handlePtr);
373
+ freeString(handlePtr);
374
+ if (jsonStr === null || jsonStr === "") return { ok: true, value: void 0 };
375
+ try {
376
+ return { ok: true, value: snakeToCamel(JSON.parse(jsonStr)) };
377
+ } catch {
378
+ return { ok: true, value: jsonStr };
379
+ }
380
+ }
381
+ function freeString(ptr) {
382
+ try {
383
+ (0, import_ffi_rs.load)({
384
+ library: LIBRARY_KEY,
385
+ funcName: "fff_free_string",
386
+ retType: import_ffi_rs.DataType.Void,
387
+ paramsType: [import_ffi_rs.DataType.External],
388
+ paramsValue: [ptr]
389
+ });
390
+ } catch {
391
+ }
392
+ }
393
+ function ffiCreate(basePath, frecencyDbPath, historyDbPath, _useUnsafeNoLock, enableMmapCache, enableContentIndexing, watch, aiMode, logFilePath, logLevel, cacheBudgetMaxFiles, cacheBudgetMaxBytes, cacheBudgetMaxFileSize, enableFsRootScanning, enableHomeDirScanning, followSymlinks) {
394
+ loadLibrary();
395
+ const optsValue = {
396
+ version: FFF_CREATE_OPTIONS_VERSION,
397
+ base_path: basePath,
398
+ frecency_db_path: frecencyDbPath,
399
+ history_db_path: historyDbPath,
400
+ enable_mmap_cache: enableMmapCache ? 1 : 0,
401
+ enable_content_indexing: enableContentIndexing ? 1 : 0,
402
+ watch: watch ? 1 : 0,
403
+ ai_mode: aiMode ? 1 : 0,
404
+ log_file_path: logFilePath,
405
+ log_level: logLevel,
406
+ cache_budget_max_files: cacheBudgetMaxFiles,
407
+ cache_budget_max_bytes: cacheBudgetMaxBytes,
408
+ cache_budget_max_file_size: cacheBudgetMaxFileSize,
409
+ enable_fs_root_scanning: enableFsRootScanning ? 1 : 0,
410
+ enable_home_dir_scanning: enableHomeDirScanning ? 1 : 0,
411
+ follow_symlinks: followSymlinks ? 1 : 0
412
+ };
413
+ const rawPtr = (0, import_ffi_rs.load)({
414
+ library: LIBRARY_KEY,
415
+ funcName: "fff_create_instance_with",
416
+ retType: import_ffi_rs.DataType.External,
417
+ paramsType: [FFF_CREATE_OPTIONS_STRUCT],
418
+ paramsValue: [optsValue],
419
+ freeResultMemory: false
420
+ });
421
+ const [structData] = (0, import_ffi_rs.restorePointer)({
422
+ retType: [FFF_RESULT_STRUCT],
423
+ paramsValue: (0, import_ffi_rs.wrapPointer)([rawPtr])
424
+ });
425
+ const success = structData.success !== 0;
426
+ try {
427
+ if (success) {
428
+ const handle = structData.handle;
429
+ if ((0, import_ffi_rs.isNullPointer)(handle)) {
430
+ return err("fff_create_instance_with returned null handle");
431
+ }
432
+ return { ok: true, value: handle };
433
+ } else {
434
+ const errorStr = readCString(structData.error);
435
+ return err(errorStr || "Unknown error");
436
+ }
437
+ } finally {
438
+ freeResult(rawPtr);
439
+ }
440
+ }
441
+ function ffiDestroy(handle) {
442
+ loadLibrary();
443
+ (0, import_ffi_rs.load)({
444
+ library: LIBRARY_KEY,
445
+ funcName: "fff_destroy",
446
+ retType: import_ffi_rs.DataType.Void,
447
+ paramsType: [import_ffi_rs.DataType.External],
448
+ paramsValue: [handle]
449
+ });
450
+ }
451
+ var FFF_FILE_ITEM_STRUCT = {
452
+ relative_path: import_ffi_rs.DataType.External,
453
+ file_name: import_ffi_rs.DataType.External,
454
+ git_status: import_ffi_rs.DataType.External,
455
+ size: import_ffi_rs.DataType.U64,
456
+ modified: import_ffi_rs.DataType.U64,
457
+ access_frecency_score: import_ffi_rs.DataType.I64,
458
+ modification_frecency_score: import_ffi_rs.DataType.I64,
459
+ total_frecency_score: import_ffi_rs.DataType.I64,
460
+ is_binary: import_ffi_rs.DataType.U8
461
+ };
462
+ var FFF_SCORE_STRUCT = {
463
+ total: import_ffi_rs.DataType.I32,
464
+ base_score: import_ffi_rs.DataType.I32,
465
+ filename_bonus: import_ffi_rs.DataType.I32,
466
+ special_filename_bonus: import_ffi_rs.DataType.I32,
467
+ frecency_boost: import_ffi_rs.DataType.I32,
468
+ distance_penalty: import_ffi_rs.DataType.I32,
469
+ current_file_penalty: import_ffi_rs.DataType.I32,
470
+ combo_match_boost: import_ffi_rs.DataType.I32,
471
+ exact_match: import_ffi_rs.DataType.U8,
472
+ match_type: import_ffi_rs.DataType.External
473
+ };
474
+ var FFF_SEARCH_RESULT_STRUCT = {
475
+ items: import_ffi_rs.DataType.External,
476
+ scores: import_ffi_rs.DataType.External,
477
+ count: import_ffi_rs.DataType.U32,
478
+ total_matched: import_ffi_rs.DataType.U32,
479
+ total_files: import_ffi_rs.DataType.U32,
480
+ // FffLocation inlined (flattened)
481
+ location_tag: import_ffi_rs.DataType.U8,
482
+ location_line: import_ffi_rs.DataType.I32,
483
+ location_col: import_ffi_rs.DataType.I32,
484
+ location_end_line: import_ffi_rs.DataType.I32,
485
+ location_end_col: import_ffi_rs.DataType.I32
486
+ };
487
+ var FFF_DIR_ITEM_STRUCT = {
488
+ relative_path: import_ffi_rs.DataType.External,
489
+ dir_name: import_ffi_rs.DataType.External,
490
+ max_access_frecency: import_ffi_rs.DataType.I32
491
+ };
492
+ var FFF_DIR_SEARCH_RESULT_STRUCT = {
493
+ items: import_ffi_rs.DataType.External,
494
+ scores: import_ffi_rs.DataType.External,
495
+ count: import_ffi_rs.DataType.U32,
496
+ total_matched: import_ffi_rs.DataType.U32,
497
+ total_dirs: import_ffi_rs.DataType.U32
498
+ };
499
+ var FFF_MIXED_ITEM_STRUCT = {
500
+ item_type: import_ffi_rs.DataType.U8,
501
+ relative_path: import_ffi_rs.DataType.External,
502
+ display_name: import_ffi_rs.DataType.External,
503
+ git_status: import_ffi_rs.DataType.External,
504
+ size: import_ffi_rs.DataType.U64,
505
+ modified: import_ffi_rs.DataType.U64,
506
+ access_frecency_score: import_ffi_rs.DataType.I64,
507
+ modification_frecency_score: import_ffi_rs.DataType.I64,
508
+ total_frecency_score: import_ffi_rs.DataType.I64,
509
+ is_binary: import_ffi_rs.DataType.U8
510
+ };
511
+ var FFF_MIXED_SEARCH_RESULT_STRUCT = {
512
+ items: import_ffi_rs.DataType.External,
513
+ scores: import_ffi_rs.DataType.External,
514
+ count: import_ffi_rs.DataType.U32,
515
+ total_matched: import_ffi_rs.DataType.U32,
516
+ total_files: import_ffi_rs.DataType.U32,
517
+ total_dirs: import_ffi_rs.DataType.U32,
518
+ // FffLocation inlined (flattened)
519
+ location_tag: import_ffi_rs.DataType.U8,
520
+ location_line: import_ffi_rs.DataType.I32,
521
+ location_col: import_ffi_rs.DataType.I32,
522
+ location_end_line: import_ffi_rs.DataType.I32,
523
+ location_end_col: import_ffi_rs.DataType.I32
524
+ };
525
+ var FFF_GREP_MATCH_STRUCT = {
526
+ relative_path: import_ffi_rs.DataType.External,
527
+ file_name: import_ffi_rs.DataType.External,
528
+ git_status: import_ffi_rs.DataType.External,
529
+ line_content: import_ffi_rs.DataType.External,
530
+ match_ranges: import_ffi_rs.DataType.External,
531
+ context_before: import_ffi_rs.DataType.External,
532
+ context_after: import_ffi_rs.DataType.External,
533
+ size: import_ffi_rs.DataType.U64,
534
+ modified: import_ffi_rs.DataType.U64,
535
+ total_frecency_score: import_ffi_rs.DataType.I64,
536
+ access_frecency_score: import_ffi_rs.DataType.I64,
537
+ modification_frecency_score: import_ffi_rs.DataType.I64,
538
+ line_number: import_ffi_rs.DataType.U64,
539
+ byte_offset: import_ffi_rs.DataType.U64,
540
+ col: import_ffi_rs.DataType.U32,
541
+ match_ranges_count: import_ffi_rs.DataType.U32,
542
+ context_before_count: import_ffi_rs.DataType.U32,
543
+ context_after_count: import_ffi_rs.DataType.U32,
544
+ fuzzy_score: import_ffi_rs.DataType.U32,
545
+ // actually u16 in C, but ffi-rs doesn't so we read it as u32 with padding
546
+ has_fuzzy_score: import_ffi_rs.DataType.U8,
547
+ is_binary: import_ffi_rs.DataType.U8,
548
+ is_definition: import_ffi_rs.DataType.U8
549
+ };
550
+ var FFF_GREP_RESULT_STRUCT = {
551
+ items: import_ffi_rs.DataType.External,
552
+ count: import_ffi_rs.DataType.U32,
553
+ total_matched: import_ffi_rs.DataType.U32,
554
+ total_files_searched: import_ffi_rs.DataType.U32,
555
+ total_files: import_ffi_rs.DataType.U32,
556
+ filtered_file_count: import_ffi_rs.DataType.U32,
557
+ next_file_offset: import_ffi_rs.DataType.U32,
558
+ regex_fallback_error: import_ffi_rs.DataType.External
559
+ };
560
+ var FFF_MATCH_RANGE_STRUCT = {
561
+ start: import_ffi_rs.DataType.U32,
562
+ end: import_ffi_rs.DataType.U32
563
+ };
564
+ function readFileItemFromRaw(raw) {
565
+ return {
566
+ relativePath: readCString(raw.relative_path) ?? "",
567
+ fileName: readCString(raw.file_name) ?? "",
568
+ gitStatus: readCString(raw.git_status) ?? "",
569
+ size: Number(raw.size),
570
+ modified: Number(raw.modified),
571
+ accessFrecencyScore: Number(raw.access_frecency_score),
572
+ modificationFrecencyScore: Number(raw.modification_frecency_score),
573
+ totalFrecencyScore: Number(raw.total_frecency_score)
574
+ };
575
+ }
576
+ function readScoreFromRaw(raw) {
577
+ return {
578
+ total: raw.total,
579
+ baseScore: raw.base_score,
580
+ filenameBonus: raw.filename_bonus,
581
+ specialFilenameBonus: raw.special_filename_bonus,
582
+ frecencyBoost: raw.frecency_boost,
583
+ distancePenalty: raw.distance_penalty,
584
+ currentFilePenalty: raw.current_file_penalty,
585
+ comboMatchBoost: raw.combo_match_boost,
586
+ exactMatch: raw.exact_match !== 0,
587
+ matchType: readCString(raw.match_type) ?? ""
588
+ };
589
+ }
590
+ function readDirItemFromRaw(raw) {
591
+ return {
592
+ relativePath: readCString(raw.relative_path) ?? "",
593
+ dirName: readCString(raw.dir_name) ?? "",
594
+ maxAccessFrecency: raw.max_access_frecency
595
+ };
596
+ }
597
+ function readMixedItemFromRaw(raw) {
598
+ if (raw.item_type === 1) {
599
+ return {
600
+ type: "directory",
601
+ item: {
602
+ relativePath: readCString(raw.relative_path) ?? "",
603
+ dirName: readCString(raw.display_name) ?? "",
604
+ maxAccessFrecency: Number(raw.access_frecency_score)
605
+ }
606
+ };
607
+ }
608
+ return {
609
+ type: "file",
610
+ item: {
611
+ relativePath: readCString(raw.relative_path) ?? "",
612
+ fileName: readCString(raw.display_name) ?? "",
613
+ gitStatus: readCString(raw.git_status) ?? "",
614
+ size: Number(raw.size),
615
+ modified: Number(raw.modified),
616
+ accessFrecencyScore: Number(raw.access_frecency_score),
617
+ modificationFrecencyScore: Number(raw.modification_frecency_score),
618
+ totalFrecencyScore: Number(raw.total_frecency_score)
619
+ }
620
+ };
621
+ }
622
+ function callAccessor(funcName, resultPtr, index, structDef) {
623
+ loadLibrary();
624
+ const elemPtr = (0, import_ffi_rs.load)({
625
+ library: LIBRARY_KEY,
626
+ funcName,
627
+ retType: import_ffi_rs.DataType.External,
628
+ paramsType: [import_ffi_rs.DataType.External, import_ffi_rs.DataType.U32],
629
+ paramsValue: [resultPtr, index]
630
+ });
631
+ const [raw] = (0, import_ffi_rs.restorePointer)({
632
+ retType: [structDef],
633
+ paramsValue: (0, import_ffi_rs.wrapPointer)([elemPtr])
634
+ });
635
+ return raw;
636
+ }
637
+ function ptrOffset(base, bytes) {
638
+ return (0, import_ffi_rs.load)({
639
+ library: LIBRARY_KEY,
640
+ funcName: "fff_ptr_offset",
641
+ retType: import_ffi_rs.DataType.External,
642
+ paramsType: [import_ffi_rs.DataType.External, import_ffi_rs.DataType.U64],
643
+ paramsValue: [base, bytes]
644
+ });
645
+ }
646
+ function readCStringArray(ptrArray, count) {
647
+ if (count === 0 || (0, import_ffi_rs.isNullPointer)(ptrArray)) return [];
648
+ const result = [];
649
+ for (let i = 0; i < count; i++) {
650
+ const elemPtr = ptrOffset(ptrArray, i * 8);
651
+ const [charPtr] = (0, import_ffi_rs.restorePointer)({
652
+ retType: [import_ffi_rs.DataType.External],
653
+ paramsValue: [elemPtr]
654
+ });
655
+ result.push(readCString(charPtr) ?? "");
656
+ }
657
+ return result;
658
+ }
659
+ function readGrepMatchFromRaw(raw) {
660
+ const matchRanges = [];
661
+ for (let i = 0; i < raw.match_ranges_count; i++) {
662
+ const rangePtr = ptrOffset(raw.match_ranges, i * 8);
663
+ const [rangeRaw] = (0, import_ffi_rs.restorePointer)({
664
+ retType: [FFF_MATCH_RANGE_STRUCT],
665
+ paramsValue: (0, import_ffi_rs.wrapPointer)([rangePtr])
666
+ });
667
+ matchRanges.push([rangeRaw.start, rangeRaw.end]);
668
+ }
669
+ const match = {
670
+ relativePath: readCString(raw.relative_path) ?? "",
671
+ fileName: readCString(raw.file_name) ?? "",
672
+ gitStatus: readCString(raw.git_status) ?? "",
673
+ lineContent: readCString(raw.line_content) ?? "",
674
+ size: Number(raw.size),
675
+ modified: Number(raw.modified),
676
+ totalFrecencyScore: Number(raw.total_frecency_score),
677
+ accessFrecencyScore: Number(raw.access_frecency_score),
678
+ modificationFrecencyScore: Number(raw.modification_frecency_score),
679
+ isBinary: raw.is_binary !== 0,
680
+ lineNumber: Number(raw.line_number),
681
+ col: raw.col,
682
+ byteOffset: Number(raw.byte_offset),
683
+ matchRanges
684
+ };
685
+ if (raw.has_fuzzy_score !== 0) {
686
+ match.fuzzyScore = raw.fuzzy_score;
687
+ }
688
+ if (raw.context_before_count > 0) {
689
+ match.contextBefore = readCStringArray(raw.context_before, raw.context_before_count);
690
+ }
691
+ if (raw.context_after_count > 0) {
692
+ match.contextAfter = readCStringArray(raw.context_after, raw.context_after_count);
693
+ }
694
+ if (raw.is_definition !== 0) {
695
+ match.isDefinition = true;
696
+ }
697
+ return match;
698
+ }
699
+ function parseGrepResult(rawPtr) {
700
+ loadLibrary();
701
+ const [envelope] = (0, import_ffi_rs.restorePointer)({
702
+ retType: [FFF_RESULT_STRUCT],
703
+ paramsValue: (0, import_ffi_rs.wrapPointer)([rawPtr])
704
+ });
705
+ const success = envelope.success !== 0;
706
+ if (!success) {
707
+ const errorMsg = readCString(envelope.error) || "Unknown error";
708
+ freeResult(rawPtr);
709
+ return err(errorMsg);
710
+ }
711
+ const handlePtr = envelope.handle;
712
+ freeResult(rawPtr);
713
+ if ((0, import_ffi_rs.isNullPointer)(handlePtr)) {
714
+ return err("grep returned null result");
715
+ }
716
+ const [gr] = (0, import_ffi_rs.restorePointer)({
717
+ retType: [FFF_GREP_RESULT_STRUCT],
718
+ paramsValue: (0, import_ffi_rs.wrapPointer)([handlePtr])
719
+ });
720
+ const count = gr.count;
721
+ const regexFallbackError = readCString(gr.regex_fallback_error) ?? void 0;
722
+ const items = [];
723
+ for (let i = 0; i < count; i++) {
724
+ const rawMatch = callAccessor(
725
+ "fff_grep_result_get_match",
726
+ handlePtr,
727
+ i,
728
+ FFF_GREP_MATCH_STRUCT
729
+ );
730
+ items.push(readGrepMatchFromRaw(rawMatch));
731
+ }
732
+ (0, import_ffi_rs.load)({
733
+ library: LIBRARY_KEY,
734
+ funcName: "fff_free_grep_result",
735
+ retType: import_ffi_rs.DataType.Void,
736
+ paramsType: [import_ffi_rs.DataType.External],
737
+ paramsValue: [handlePtr]
738
+ });
739
+ const grepResult = {
740
+ items,
741
+ totalMatched: gr.total_matched,
742
+ totalFilesSearched: gr.total_files_searched,
743
+ totalFiles: gr.total_files,
744
+ filteredFileCount: gr.filtered_file_count,
745
+ nextCursor: gr.next_file_offset > 0 ? createGrepCursor(gr.next_file_offset) : null
746
+ };
747
+ if (regexFallbackError) {
748
+ grepResult.regexFallbackError = regexFallbackError;
749
+ }
750
+ return { ok: true, value: grepResult };
751
+ }
752
+ function parseSearchResult(rawPtr) {
753
+ loadLibrary();
754
+ const [envelope] = (0, import_ffi_rs.restorePointer)({
755
+ retType: [FFF_RESULT_STRUCT],
756
+ paramsValue: (0, import_ffi_rs.wrapPointer)([rawPtr])
757
+ });
758
+ const success = envelope.success !== 0;
759
+ if (!success) {
760
+ const errorMsg = readCString(envelope.error) || "Unknown error";
761
+ freeResult(rawPtr);
762
+ return err(errorMsg);
763
+ }
764
+ const handlePtr = envelope.handle;
765
+ freeResult(rawPtr);
766
+ if ((0, import_ffi_rs.isNullPointer)(handlePtr)) {
767
+ return err("fff_search returned null search result");
768
+ }
769
+ const [sr] = (0, import_ffi_rs.restorePointer)({
770
+ retType: [FFF_SEARCH_RESULT_STRUCT],
771
+ paramsValue: (0, import_ffi_rs.wrapPointer)([handlePtr])
772
+ });
773
+ const count = sr.count;
774
+ let location;
775
+ if (sr.location_tag === 1) {
776
+ location = { type: "line", line: sr.location_line };
777
+ } else if (sr.location_tag === 2) {
778
+ location = {
779
+ type: "position",
780
+ line: sr.location_line,
781
+ col: sr.location_col
782
+ };
783
+ } else if (sr.location_tag === 3) {
784
+ location = {
785
+ type: "range",
786
+ start: { line: sr.location_line, col: sr.location_col },
787
+ end: { line: sr.location_end_line, col: sr.location_end_col }
788
+ };
789
+ }
790
+ const items = [];
791
+ const scores = [];
792
+ for (let i = 0; i < count; i++) {
793
+ const rawItem = callAccessor(
794
+ "fff_search_result_get_item",
795
+ handlePtr,
796
+ i,
797
+ FFF_FILE_ITEM_STRUCT
798
+ );
799
+ items.push(readFileItemFromRaw(rawItem));
800
+ const rawScore = callAccessor(
801
+ "fff_search_result_get_score",
802
+ handlePtr,
803
+ i,
804
+ FFF_SCORE_STRUCT
805
+ );
806
+ scores.push(readScoreFromRaw(rawScore));
807
+ }
808
+ (0, import_ffi_rs.load)({
809
+ library: LIBRARY_KEY,
810
+ funcName: "fff_free_search_result",
811
+ retType: import_ffi_rs.DataType.Void,
812
+ paramsType: [import_ffi_rs.DataType.External],
813
+ paramsValue: [handlePtr]
814
+ });
815
+ const result = {
816
+ items,
817
+ scores,
818
+ totalMatched: sr.total_matched,
819
+ totalFiles: sr.total_files
820
+ };
821
+ if (location) {
822
+ result.location = location;
823
+ }
824
+ return { ok: true, value: result };
825
+ }
826
+ function parseDirSearchResult(rawPtr) {
827
+ loadLibrary();
828
+ const [envelope] = (0, import_ffi_rs.restorePointer)({
829
+ retType: [FFF_RESULT_STRUCT],
830
+ paramsValue: (0, import_ffi_rs.wrapPointer)([rawPtr])
831
+ });
832
+ const success = envelope.success !== 0;
833
+ if (!success) {
834
+ const errorMsg = readCString(envelope.error) || "Unknown error";
835
+ freeResult(rawPtr);
836
+ return err(errorMsg);
837
+ }
838
+ const handlePtr = envelope.handle;
839
+ freeResult(rawPtr);
840
+ if ((0, import_ffi_rs.isNullPointer)(handlePtr)) {
841
+ return err("fff_search_directories returned null search result");
842
+ }
843
+ const [sr] = (0, import_ffi_rs.restorePointer)({
844
+ retType: [FFF_DIR_SEARCH_RESULT_STRUCT],
845
+ paramsValue: (0, import_ffi_rs.wrapPointer)([handlePtr])
846
+ });
847
+ const count = sr.count;
848
+ const items = [];
849
+ const scores = [];
850
+ for (let i = 0; i < count; i++) {
851
+ const rawItem = callAccessor(
852
+ "fff_dir_search_result_get_item",
853
+ handlePtr,
854
+ i,
855
+ FFF_DIR_ITEM_STRUCT
856
+ );
857
+ items.push(readDirItemFromRaw(rawItem));
858
+ const rawScore = callAccessor(
859
+ "fff_dir_search_result_get_score",
860
+ handlePtr,
861
+ i,
862
+ FFF_SCORE_STRUCT
863
+ );
864
+ scores.push(readScoreFromRaw(rawScore));
865
+ }
866
+ (0, import_ffi_rs.load)({
867
+ library: LIBRARY_KEY,
868
+ funcName: "fff_free_dir_search_result",
869
+ retType: import_ffi_rs.DataType.Void,
870
+ paramsType: [import_ffi_rs.DataType.External],
871
+ paramsValue: [handlePtr]
872
+ });
873
+ return {
874
+ ok: true,
875
+ value: {
876
+ items,
877
+ scores,
878
+ totalMatched: sr.total_matched,
879
+ totalDirs: sr.total_dirs
880
+ }
881
+ };
882
+ }
883
+ function parseMixedSearchResult(rawPtr) {
884
+ loadLibrary();
885
+ const [envelope] = (0, import_ffi_rs.restorePointer)({
886
+ retType: [FFF_RESULT_STRUCT],
887
+ paramsValue: (0, import_ffi_rs.wrapPointer)([rawPtr])
888
+ });
889
+ const success = envelope.success !== 0;
890
+ if (!success) {
891
+ const errorMsg = readCString(envelope.error) || "Unknown error";
892
+ freeResult(rawPtr);
893
+ return err(errorMsg);
894
+ }
895
+ const handlePtr = envelope.handle;
896
+ freeResult(rawPtr);
897
+ if ((0, import_ffi_rs.isNullPointer)(handlePtr)) {
898
+ return err("fff_search_mixed returned null search result");
899
+ }
900
+ const [sr] = (0, import_ffi_rs.restorePointer)({
901
+ retType: [FFF_MIXED_SEARCH_RESULT_STRUCT],
902
+ paramsValue: (0, import_ffi_rs.wrapPointer)([handlePtr])
903
+ });
904
+ const count = sr.count;
905
+ let location;
906
+ if (sr.location_tag === 1) {
907
+ location = { type: "line", line: sr.location_line };
908
+ } else if (sr.location_tag === 2) {
909
+ location = {
910
+ type: "position",
911
+ line: sr.location_line,
912
+ col: sr.location_col
913
+ };
914
+ } else if (sr.location_tag === 3) {
915
+ location = {
916
+ type: "range",
917
+ start: { line: sr.location_line, col: sr.location_col },
918
+ end: { line: sr.location_end_line, col: sr.location_end_col }
919
+ };
920
+ }
921
+ const items = [];
922
+ const scores = [];
923
+ for (let i = 0; i < count; i++) {
924
+ const rawItem = callAccessor(
925
+ "fff_mixed_search_result_get_item",
926
+ handlePtr,
927
+ i,
928
+ FFF_MIXED_ITEM_STRUCT
929
+ );
930
+ items.push(readMixedItemFromRaw(rawItem));
931
+ const rawScore = callAccessor(
932
+ "fff_mixed_search_result_get_score",
933
+ handlePtr,
934
+ i,
935
+ FFF_SCORE_STRUCT
936
+ );
937
+ scores.push(readScoreFromRaw(rawScore));
938
+ }
939
+ (0, import_ffi_rs.load)({
940
+ library: LIBRARY_KEY,
941
+ funcName: "fff_free_mixed_search_result",
942
+ retType: import_ffi_rs.DataType.Void,
943
+ paramsType: [import_ffi_rs.DataType.External],
944
+ paramsValue: [handlePtr]
945
+ });
946
+ const result = {
947
+ items,
948
+ scores,
949
+ totalMatched: sr.total_matched,
950
+ totalFiles: sr.total_files,
951
+ totalDirs: sr.total_dirs
952
+ };
953
+ if (location) {
954
+ result.location = location;
955
+ }
956
+ return { ok: true, value: result };
957
+ }
958
+ function ffiSearch(handle, query, currentFile, maxThreads, pageIndex, pageSize, comboBoostMultiplier, minComboCount) {
959
+ loadLibrary();
960
+ const rawPtr = (0, import_ffi_rs.load)({
961
+ library: LIBRARY_KEY,
962
+ funcName: "fff_search",
963
+ retType: import_ffi_rs.DataType.External,
964
+ paramsType: [
965
+ import_ffi_rs.DataType.External,
966
+ // handle
967
+ import_ffi_rs.DataType.String,
968
+ // query
969
+ import_ffi_rs.DataType.String,
970
+ // current_file
971
+ import_ffi_rs.DataType.U32,
972
+ // max_threads
973
+ import_ffi_rs.DataType.U32,
974
+ // page_index
975
+ import_ffi_rs.DataType.U32,
976
+ // page_size
977
+ import_ffi_rs.DataType.I32,
978
+ // combo_boost_multiplier
979
+ import_ffi_rs.DataType.U32
980
+ // min_combo_count
981
+ ],
982
+ paramsValue: [
983
+ handle,
984
+ query,
985
+ currentFile,
986
+ maxThreads,
987
+ pageIndex,
988
+ pageSize,
989
+ comboBoostMultiplier,
990
+ minComboCount
991
+ ],
992
+ freeResultMemory: false
993
+ });
994
+ return parseSearchResult(rawPtr);
995
+ }
996
+ function ffiGlob(handle, pattern, currentFile, maxThreads, pageIndex, pageSize) {
997
+ loadLibrary();
998
+ const rawPtr = (0, import_ffi_rs.load)({
999
+ library: LIBRARY_KEY,
1000
+ funcName: "fff_glob",
1001
+ retType: import_ffi_rs.DataType.External,
1002
+ paramsType: [
1003
+ import_ffi_rs.DataType.External,
1004
+ // handle
1005
+ import_ffi_rs.DataType.String,
1006
+ // pattern
1007
+ import_ffi_rs.DataType.String,
1008
+ // current_file
1009
+ import_ffi_rs.DataType.U32,
1010
+ // max_threads
1011
+ import_ffi_rs.DataType.U32,
1012
+ // page_index
1013
+ import_ffi_rs.DataType.U32
1014
+ // page_size
1015
+ ],
1016
+ paramsValue: [handle, pattern, currentFile, maxThreads, pageIndex, pageSize],
1017
+ freeResultMemory: false
1018
+ });
1019
+ return parseSearchResult(rawPtr);
1020
+ }
1021
+ function ffiSearchDirectories(handle, query, currentFile, maxThreads, pageIndex, pageSize) {
1022
+ loadLibrary();
1023
+ const rawPtr = (0, import_ffi_rs.load)({
1024
+ library: LIBRARY_KEY,
1025
+ funcName: "fff_search_directories",
1026
+ retType: import_ffi_rs.DataType.External,
1027
+ paramsType: [
1028
+ import_ffi_rs.DataType.External,
1029
+ // handle
1030
+ import_ffi_rs.DataType.String,
1031
+ // query
1032
+ import_ffi_rs.DataType.String,
1033
+ // current_file
1034
+ import_ffi_rs.DataType.U32,
1035
+ // max_threads
1036
+ import_ffi_rs.DataType.U32,
1037
+ // page_index
1038
+ import_ffi_rs.DataType.U32
1039
+ // page_size
1040
+ ],
1041
+ paramsValue: [handle, query, currentFile ?? "", maxThreads, pageIndex, pageSize],
1042
+ freeResultMemory: false
1043
+ });
1044
+ return parseDirSearchResult(rawPtr);
1045
+ }
1046
+ function ffiSearchMixed(handle, query, currentFile, maxThreads, pageIndex, pageSize, comboBoostMultiplier, minComboCount) {
1047
+ loadLibrary();
1048
+ const rawPtr = (0, import_ffi_rs.load)({
1049
+ library: LIBRARY_KEY,
1050
+ funcName: "fff_search_mixed",
1051
+ retType: import_ffi_rs.DataType.External,
1052
+ paramsType: [
1053
+ import_ffi_rs.DataType.External,
1054
+ // handle
1055
+ import_ffi_rs.DataType.String,
1056
+ // query
1057
+ import_ffi_rs.DataType.String,
1058
+ // current_file
1059
+ import_ffi_rs.DataType.U32,
1060
+ // max_threads
1061
+ import_ffi_rs.DataType.U32,
1062
+ // page_index
1063
+ import_ffi_rs.DataType.U32,
1064
+ // page_size
1065
+ import_ffi_rs.DataType.I32,
1066
+ // combo_boost_multiplier
1067
+ import_ffi_rs.DataType.U32
1068
+ // min_combo_count
1069
+ ],
1070
+ paramsValue: [
1071
+ handle,
1072
+ query,
1073
+ currentFile,
1074
+ maxThreads,
1075
+ pageIndex,
1076
+ pageSize,
1077
+ comboBoostMultiplier,
1078
+ minComboCount
1079
+ ],
1080
+ freeResultMemory: false
1081
+ });
1082
+ return parseMixedSearchResult(rawPtr);
1083
+ }
1084
+ function ffiLiveGrep(handle, query, mode, maxFileSize, maxMatchesPerFile, smartCase, fileOffset, pageLimit, timeBudgetMs, beforeContext, afterContext, classifyDefinitions) {
1085
+ loadLibrary();
1086
+ const rawPtr = (0, import_ffi_rs.load)({
1087
+ library: LIBRARY_KEY,
1088
+ funcName: "fff_live_grep",
1089
+ retType: import_ffi_rs.DataType.External,
1090
+ paramsType: [
1091
+ import_ffi_rs.DataType.External,
1092
+ // handle
1093
+ import_ffi_rs.DataType.String,
1094
+ // query
1095
+ import_ffi_rs.DataType.U8,
1096
+ // mode
1097
+ import_ffi_rs.DataType.U64,
1098
+ // max_file_size
1099
+ import_ffi_rs.DataType.U32,
1100
+ // max_matches_per_file
1101
+ import_ffi_rs.DataType.Boolean,
1102
+ // smart_case
1103
+ import_ffi_rs.DataType.U32,
1104
+ // file_offset
1105
+ import_ffi_rs.DataType.U32,
1106
+ // page_limit
1107
+ import_ffi_rs.DataType.U64,
1108
+ // time_budget_ms
1109
+ import_ffi_rs.DataType.U32,
1110
+ // before_context
1111
+ import_ffi_rs.DataType.U32,
1112
+ // after_context
1113
+ import_ffi_rs.DataType.Boolean
1114
+ // classify_definitions
1115
+ ],
1116
+ paramsValue: [
1117
+ handle,
1118
+ query,
1119
+ grepModeToU8(mode),
1120
+ maxFileSize,
1121
+ maxMatchesPerFile,
1122
+ smartCase,
1123
+ fileOffset,
1124
+ pageLimit,
1125
+ timeBudgetMs,
1126
+ beforeContext,
1127
+ afterContext,
1128
+ classifyDefinitions
1129
+ ],
1130
+ freeResultMemory: false
1131
+ });
1132
+ return parseGrepResult(rawPtr);
1133
+ }
1134
+ function ffiMultiGrep(handle, patternsJoined, constraints, maxFileSize, maxMatchesPerFile, smartCase, fileOffset, pageLimit, timeBudgetMs, beforeContext, afterContext, classifyDefinitions) {
1135
+ loadLibrary();
1136
+ const rawPtr = (0, import_ffi_rs.load)({
1137
+ library: LIBRARY_KEY,
1138
+ funcName: "fff_multi_grep",
1139
+ retType: import_ffi_rs.DataType.External,
1140
+ paramsType: [
1141
+ import_ffi_rs.DataType.External,
1142
+ // handle
1143
+ import_ffi_rs.DataType.String,
1144
+ // patterns_joined
1145
+ import_ffi_rs.DataType.String,
1146
+ // constraints
1147
+ import_ffi_rs.DataType.U64,
1148
+ // max_file_size
1149
+ import_ffi_rs.DataType.U32,
1150
+ // max_matches_per_file
1151
+ import_ffi_rs.DataType.Boolean,
1152
+ // smart_case
1153
+ import_ffi_rs.DataType.U32,
1154
+ // file_offset
1155
+ import_ffi_rs.DataType.U32,
1156
+ // page_limit
1157
+ import_ffi_rs.DataType.U64,
1158
+ // time_budget_ms
1159
+ import_ffi_rs.DataType.U32,
1160
+ // before_context
1161
+ import_ffi_rs.DataType.U32,
1162
+ // after_context
1163
+ import_ffi_rs.DataType.Boolean
1164
+ // classify_definitions
1165
+ ],
1166
+ paramsValue: [
1167
+ handle,
1168
+ patternsJoined,
1169
+ constraints,
1170
+ maxFileSize,
1171
+ maxMatchesPerFile,
1172
+ smartCase,
1173
+ fileOffset,
1174
+ pageLimit,
1175
+ timeBudgetMs,
1176
+ beforeContext,
1177
+ afterContext,
1178
+ classifyDefinitions
1179
+ ],
1180
+ freeResultMemory: false
1181
+ });
1182
+ return parseGrepResult(rawPtr);
1183
+ }
1184
+ function ffiScanFiles(handle) {
1185
+ return callVoidResult("fff_scan_files", [import_ffi_rs.DataType.External], [handle]);
1186
+ }
1187
+ function ffiIsScanning(handle) {
1188
+ loadLibrary();
1189
+ return (0, import_ffi_rs.load)({
1190
+ library: LIBRARY_KEY,
1191
+ funcName: "fff_is_scanning",
1192
+ retType: import_ffi_rs.DataType.Boolean,
1193
+ paramsType: [import_ffi_rs.DataType.External],
1194
+ paramsValue: [handle]
1195
+ });
1196
+ }
1197
+ function ffiGetBasePath(handle) {
1198
+ return callStringResult("fff_get_base_path", [import_ffi_rs.DataType.External], [handle]);
1199
+ }
1200
+ var FFF_SCAN_PROGRESS_STRUCT = {
1201
+ scanned_files_count: import_ffi_rs.DataType.U64,
1202
+ is_scanning: import_ffi_rs.DataType.U8,
1203
+ is_watcher_ready: import_ffi_rs.DataType.U8,
1204
+ is_warmup_complete: import_ffi_rs.DataType.U8
1205
+ };
1206
+ function ffiGetScanProgress(handle) {
1207
+ loadLibrary();
1208
+ const res = readResultEnvelope("fff_get_scan_progress", [import_ffi_rs.DataType.External], [handle]);
1209
+ if ("ok" in res) return res;
1210
+ const handlePtr = res.struct.handle;
1211
+ freeResult(res.rawPtr);
1212
+ if ((0, import_ffi_rs.isNullPointer)(handlePtr)) return err("scan progress returned null");
1213
+ const [sp] = (0, import_ffi_rs.restorePointer)({
1214
+ retType: [FFF_SCAN_PROGRESS_STRUCT],
1215
+ paramsValue: (0, import_ffi_rs.wrapPointer)([handlePtr])
1216
+ });
1217
+ const result = {
1218
+ scannedFilesCount: Number(sp.scanned_files_count),
1219
+ isScanning: sp.is_scanning !== 0,
1220
+ isWatcherReady: sp.is_watcher_ready !== 0,
1221
+ isWarmupComplete: sp.is_warmup_complete !== 0
1222
+ };
1223
+ (0, import_ffi_rs.load)({
1224
+ library: LIBRARY_KEY,
1225
+ funcName: "fff_free_scan_progress",
1226
+ retType: import_ffi_rs.DataType.Void,
1227
+ paramsType: [import_ffi_rs.DataType.External],
1228
+ paramsValue: [handlePtr]
1229
+ });
1230
+ return { ok: true, value: result };
1231
+ }
1232
+ function ffiWaitForScan(handle, timeoutMs) {
1233
+ return callBoolResult(
1234
+ "fff_wait_for_scan",
1235
+ [import_ffi_rs.DataType.External, import_ffi_rs.DataType.U64],
1236
+ [handle, timeoutMs]
1237
+ );
1238
+ }
1239
+ function ffiRestartIndex(handle, newPath) {
1240
+ return callVoidResult(
1241
+ "fff_restart_index",
1242
+ [import_ffi_rs.DataType.External, import_ffi_rs.DataType.String],
1243
+ [handle, newPath]
1244
+ );
1245
+ }
1246
+ function ffiRefreshGitStatus(handle) {
1247
+ return callIntResult("fff_refresh_git_status", [import_ffi_rs.DataType.External], [handle]);
1248
+ }
1249
+ function ffiTrackQuery(handle, query, filePath) {
1250
+ return callBoolResult(
1251
+ "fff_track_query",
1252
+ [import_ffi_rs.DataType.External, import_ffi_rs.DataType.String, import_ffi_rs.DataType.String],
1253
+ [handle, query, filePath]
1254
+ );
1255
+ }
1256
+ function ffiGetHistoricalQuery(handle, offset) {
1257
+ return callStringResult(
1258
+ "fff_get_historical_query",
1259
+ [import_ffi_rs.DataType.External, import_ffi_rs.DataType.U64],
1260
+ [handle, offset]
1261
+ );
1262
+ }
1263
+ function watchKindFromU8(kind) {
1264
+ switch (kind) {
1265
+ case 0:
1266
+ return "created";
1267
+ case 1:
1268
+ return "modified";
1269
+ case 2:
1270
+ return "removed";
1271
+ default:
1272
+ return "rescan";
1273
+ }
1274
+ }
1275
+ var WATCH_TRAMPOLINE_TYPE = (0, import_ffi_rs.funcConstructor)({
1276
+ paramsType: [import_ffi_rs.DataType.U64, import_ffi_rs.DataType.U64, import_ffi_rs.DataType.U64],
1277
+ retType: import_ffi_rs.DataType.Void
1278
+ });
1279
+ var watchHandlers = /* @__PURE__ */ new Map();
1280
+ var watchInstances = /* @__PURE__ */ new Set();
1281
+ var watchTrampoline = null;
1282
+ function addressToExternal(address) {
1283
+ return (0, import_ffi_rs.load)({
1284
+ library: LIBRARY_KEY,
1285
+ funcName: "fff_ptr_offset",
1286
+ retType: import_ffi_rs.DataType.External,
1287
+ paramsType: [import_ffi_rs.DataType.U64, import_ffi_rs.DataType.U64],
1288
+ paramsValue: [address, 0]
1289
+ });
1290
+ }
1291
+ function consumeWatchBatch(address) {
1292
+ const batchPtr = addressToExternal(address);
1293
+ const count = (0, import_ffi_rs.load)({
1294
+ library: LIBRARY_KEY,
1295
+ funcName: "fff_watch_events_count",
1296
+ retType: import_ffi_rs.DataType.U32,
1297
+ paramsType: [import_ffi_rs.DataType.External],
1298
+ paramsValue: [batchPtr]
1299
+ });
1300
+ const events = [];
1301
+ for (let i = 0; i < count; i++) {
1302
+ const path = (0, import_ffi_rs.load)({
1303
+ library: LIBRARY_KEY,
1304
+ funcName: "fff_watch_events_get_path",
1305
+ retType: import_ffi_rs.DataType.External,
1306
+ paramsType: [import_ffi_rs.DataType.External, import_ffi_rs.DataType.U32],
1307
+ paramsValue: [batchPtr, i]
1308
+ });
1309
+ const kind = (0, import_ffi_rs.load)({
1310
+ library: LIBRARY_KEY,
1311
+ funcName: "fff_watch_events_get_kind",
1312
+ retType: import_ffi_rs.DataType.U8,
1313
+ paramsType: [import_ffi_rs.DataType.External, import_ffi_rs.DataType.U32],
1314
+ paramsValue: [batchPtr, i]
1315
+ });
1316
+ events.push({
1317
+ path: readCString(path) ?? "",
1318
+ kind: watchKindFromU8(kind)
1319
+ });
1320
+ }
1321
+ (0, import_ffi_rs.load)({
1322
+ library: LIBRARY_KEY,
1323
+ funcName: "fff_free_watch_events",
1324
+ retType: import_ffi_rs.DataType.Void,
1325
+ paramsType: [import_ffi_rs.DataType.U64],
1326
+ paramsValue: [address]
1327
+ });
1328
+ return events;
1329
+ }
1330
+ function watchTrampolineImpl(watchId, batchAddress, _userData) {
1331
+ const events = consumeWatchBatch(batchAddress);
1332
+ const handler = watchHandlers.get(Number(watchId));
1333
+ if (handler === void 0 || events.length === 0) return;
1334
+ try {
1335
+ handler(events);
1336
+ } catch {
1337
+ }
1338
+ }
1339
+ function ensureWatchTrampoline() {
1340
+ if (watchTrampoline === null) {
1341
+ watchTrampoline = (0, import_ffi_rs.createPointer)({
1342
+ paramsType: [WATCH_TRAMPOLINE_TYPE],
1343
+ paramsValue: [watchTrampolineImpl]
1344
+ });
1345
+ }
1346
+ return (0, import_ffi_rs.unwrapPointer)(watchTrampoline)[0];
1347
+ }
1348
+ function ensureWatchCallbackRegistered(handle) {
1349
+ if (watchInstances.has(handle)) return { ok: true, value: void 0 };
1350
+ const trampoline = ensureWatchTrampoline();
1351
+ const registered = callVoidResult(
1352
+ "fff_set_watch_callback",
1353
+ [import_ffi_rs.DataType.External, import_ffi_rs.DataType.External, import_ffi_rs.DataType.U64],
1354
+ [handle, trampoline, 0]
1355
+ );
1356
+ if (registered.ok) watchInstances.add(handle);
1357
+ return registered;
1358
+ }
1359
+ function releaseWatchTrampolineIfIdle() {
1360
+ if (watchHandlers.size > 0 || watchInstances.size > 0 || watchTrampoline === null)
1361
+ return;
1362
+ (0, import_ffi_rs.freePointer)({
1363
+ paramsType: [WATCH_TRAMPOLINE_TYPE],
1364
+ paramsValue: watchTrampoline,
1365
+ pointerType: import_ffi_rs.PointerType.RsPointer
1366
+ });
1367
+ watchTrampoline = null;
1368
+ }
1369
+ function ffiWatch(handle, pattern, ignore, callback) {
1370
+ loadLibrary();
1371
+ const registered = ensureWatchCallbackRegistered(handle);
1372
+ if (!registered.ok) return registered;
1373
+ const created = callIntResult(
1374
+ "fff_watch_args",
1375
+ [import_ffi_rs.DataType.External, import_ffi_rs.DataType.String, import_ffi_rs.DataType.StringArray, import_ffi_rs.DataType.U32],
1376
+ [handle, pattern, ignore, ignore.length]
1377
+ );
1378
+ if (!created.ok) return created;
1379
+ watchHandlers.set(created.value, callback);
1380
+ return created;
1381
+ }
1382
+ function ffiUnwatch(handle, watchId) {
1383
+ const result = callBoolResult(
1384
+ "fff_unwatch",
1385
+ [import_ffi_rs.DataType.External, import_ffi_rs.DataType.U64],
1386
+ [handle, watchId]
1387
+ );
1388
+ watchHandlers.delete(watchId);
1389
+ return result;
1390
+ }
1391
+ function ffiWatchCleanupAfterDestroy(handle, watchIds) {
1392
+ for (const id of watchIds) {
1393
+ watchHandlers.delete(id);
1394
+ }
1395
+ watchInstances.delete(handle);
1396
+ releaseWatchTrampolineIfIdle();
1397
+ }
1398
+ function ffiHealthCheck(handle, testPath) {
1399
+ if (handle === null) {
1400
+ return callJsonResult(
1401
+ "fff_health_check",
1402
+ [import_ffi_rs.DataType.U64, import_ffi_rs.DataType.String],
1403
+ [0, testPath]
1404
+ );
1405
+ }
1406
+ return callJsonResult(
1407
+ "fff_health_check",
1408
+ [import_ffi_rs.DataType.External, import_ffi_rs.DataType.String],
1409
+ [handle, testPath]
1410
+ );
1411
+ }
1412
+ function ensureLoaded() {
1413
+ loadLibrary();
1414
+ }
1415
+ function isAvailable() {
1416
+ try {
1417
+ loadLibrary();
1418
+ return true;
1419
+ } catch {
1420
+ return false;
1421
+ }
1422
+ }
1423
+ function closeLibrary() {
1424
+ if (isLoaded) {
1425
+ (0, import_ffi_rs.close)(LIBRARY_KEY);
1426
+ isLoaded = false;
1427
+ }
1428
+ }
1429
+
1430
+ // src/finder.ts
1431
+ var FileFinder = class _FileFinder {
1432
+ handle;
1433
+ /** Native ids of this instance's active watch subscriptions. */
1434
+ watchers = /* @__PURE__ */ new Set();
1435
+ constructor(handle) {
1436
+ this.handle = handle;
1437
+ }
1438
+ /**
1439
+ * Create a new file finder instance.
1440
+ *
1441
+ * @param options - Initialization options
1442
+ * @returns Result containing the new FileFinder instance or an error
1443
+ *
1444
+ * @example
1445
+ * ```typescript
1446
+ * // Basic initialization
1447
+ * const finder = FileFinder.create({ basePath: "/path/to/project" });
1448
+ *
1449
+ * // With custom database paths
1450
+ * const finder = FileFinder.create({
1451
+ * basePath: "/path/to/project",
1452
+ * frecencyDbPath: "/custom/frecency.mdb",
1453
+ * historyDbPath: "/custom/history.mdb",
1454
+ * });
1455
+ * ```
1456
+ */
1457
+ static create(options) {
1458
+ const result = ffiCreate(
1459
+ options.basePath,
1460
+ options.frecencyDbPath ?? "",
1461
+ options.historyDbPath ?? "",
1462
+ options.useUnsafeNoLock ?? false,
1463
+ !(options.disableMmapCache ?? false),
1464
+ !(options.disableContentIndexing ?? options.disableMmapCache ?? false),
1465
+ !(options.disableWatch ?? false),
1466
+ options.aiMode ?? false,
1467
+ options.logFilePath ?? "",
1468
+ options.logLevel ?? "",
1469
+ options.cacheBudgetMaxFiles ?? 0,
1470
+ options.cacheBudgetMaxBytes ?? 0,
1471
+ options.cacheBudgetMaxFileSize ?? 0,
1472
+ options.enableFsRootScanning ?? false,
1473
+ options.enableHomeDirScanning ?? false,
1474
+ options.followSymlinks ?? false
1475
+ );
1476
+ if (!result.ok) {
1477
+ return result;
1478
+ }
1479
+ return { ok: true, value: new _FileFinder(result.value) };
1480
+ }
1481
+ /**
1482
+ * Destroy and clean up all resources.
1483
+ *
1484
+ * Call this when you're done using the file finder to free memory
1485
+ * and stop background file watching. After calling this, the instance
1486
+ * must not be used again.
1487
+ */
1488
+ destroy() {
1489
+ if (this.handle !== null) {
1490
+ const handle = this.handle;
1491
+ ffiDestroy(handle);
1492
+ this.handle = null;
1493
+ ffiWatchCleanupAfterDestroy(handle, this.watchers);
1494
+ this.watchers.clear();
1495
+ }
1496
+ }
1497
+ /**
1498
+ * Check if this instance has been destroyed.
1499
+ */
1500
+ get isDestroyed() {
1501
+ return this.handle === null;
1502
+ }
1503
+ /**
1504
+ * Guard that returns an error if the instance has been destroyed.
1505
+ */
1506
+ ensureAlive() {
1507
+ if (this.handle === null) {
1508
+ return err("FileFinder instance has been destroyed.");
1509
+ }
1510
+ return { ok: true, value: this.handle };
1511
+ }
1512
+ /**
1513
+ * Search for files matching the query.
1514
+ *
1515
+ * The query supports fuzzy matching and special syntax:
1516
+ * - `foo bar` - Match files containing "foo" and "bar"
1517
+ * - `src/` - Match files in src directory
1518
+ * - `file.ts:42` - Match file.ts with line 42
1519
+ * - `file.ts:42:10` - Match file.ts with line 42, column 10
1520
+ *
1521
+ * @param query - Search query string
1522
+ * @param options - Search options
1523
+ * @returns Search results with matched files and scores
1524
+ *
1525
+ * @example
1526
+ * ```typescript
1527
+ * const result = finder.search("main.ts", { pageSize: 10 });
1528
+ * if (result.ok) {
1529
+ * console.log(`Found ${result.value.totalMatched} files`);
1530
+ * for (const item of result.value.items) {
1531
+ * console.log(item.relativePath);
1532
+ * }
1533
+ * }
1534
+ * ```
1535
+ */
1536
+ fileSearch(query, options) {
1537
+ const guard = this.ensureAlive();
1538
+ if (!guard.ok) return guard;
1539
+ return ffiSearch(
1540
+ guard.value,
1541
+ query,
1542
+ options?.currentFile ?? "",
1543
+ options?.maxThreads ?? 0,
1544
+ options?.pageIndex ?? 0,
1545
+ options?.pageSize ?? 0,
1546
+ options?.comboBoostMultiplier ?? 0,
1547
+ options?.minComboCount ?? 0
1548
+ );
1549
+ }
1550
+ /**
1551
+ * Filters files using glob wildcard expression.
1552
+ *
1553
+ * The pattern is applied as a single pass SIMD optimized prefiltering
1554
+ * without any fuzzy matching involved. Faster and 100% compatible to npm `glob`.
1555
+ *
1556
+ * @param pattern - Glob pattern (required, non-empty)
1557
+ * @param options - Glob search options (pagination, max threads, current file)
1558
+ * @returns Search results with files matching the glob
1559
+ *
1560
+ * @example
1561
+ * ```typescript
1562
+ * const result = finder.glob("**\/*.rs", { pageSize: 100 });
1563
+ * if (result.ok) {
1564
+ * for (const item of result.value.items) {
1565
+ * console.log(item.relativePath);
1566
+ * }
1567
+ * }
1568
+ * ```
1569
+ */
1570
+ glob(pattern, options) {
1571
+ const guard = this.ensureAlive();
1572
+ if (!guard.ok) return guard;
1573
+ return ffiGlob(
1574
+ guard.value,
1575
+ pattern,
1576
+ options?.currentFile ?? "",
1577
+ options?.maxThreads ?? 0,
1578
+ options?.pageIndex ?? 0,
1579
+ options?.pageSize ?? 0
1580
+ );
1581
+ }
1582
+ /**
1583
+ * Search for directories matching the query.
1584
+ *
1585
+ * @param query - Search query string
1586
+ * @param options - Directory search options
1587
+ * @returns Search results with matched directories and scores
1588
+ *
1589
+ * @example
1590
+ * ```typescript
1591
+ * const result = finder.directorySearch("src/comp", { pageSize: 10 });
1592
+ * if (result.ok) {
1593
+ * console.log(`Found ${result.value.totalMatched} directories`);
1594
+ * for (const item of result.value.items) {
1595
+ * console.log(item.relativePath);
1596
+ * }
1597
+ * }
1598
+ * ```
1599
+ */
1600
+ directorySearch(query, options) {
1601
+ const guard = this.ensureAlive();
1602
+ if (!guard.ok) return guard;
1603
+ return ffiSearchDirectories(
1604
+ guard.value,
1605
+ query,
1606
+ options?.currentFile ?? null,
1607
+ options?.maxThreads ?? 0,
1608
+ options?.pageIndex ?? 0,
1609
+ options?.pageSize ?? 0
1610
+ );
1611
+ }
1612
+ /**
1613
+ * Search for files and directories matching the query (mixed results).
1614
+ *
1615
+ * Results are interleaved by total score in descending order, mixing
1616
+ * both file and directory items.
1617
+ *
1618
+ * @param query - Search query string
1619
+ * @param options - Search options
1620
+ * @returns Mixed search results with files and directories interleaved by score
1621
+ *
1622
+ * @example
1623
+ * ```typescript
1624
+ * const result = finder.mixedSearch("main", { pageSize: 20 });
1625
+ * if (result.ok) {
1626
+ * for (const entry of result.value.items) {
1627
+ * if (entry.type === "file") {
1628
+ * console.log(`File: ${entry.item.relativePath}`);
1629
+ * } else {
1630
+ * console.log(`Dir: ${entry.item.relativePath}`);
1631
+ * }
1632
+ * }
1633
+ * }
1634
+ * ```
1635
+ */
1636
+ mixedSearch(query, options) {
1637
+ const guard = this.ensureAlive();
1638
+ if (!guard.ok) return guard;
1639
+ return ffiSearchMixed(
1640
+ guard.value,
1641
+ query,
1642
+ options?.currentFile ?? "",
1643
+ options?.maxThreads ?? 0,
1644
+ options?.pageIndex ?? 0,
1645
+ options?.pageSize ?? 0,
1646
+ options?.comboBoostMultiplier ?? 0,
1647
+ options?.minComboCount ?? 0
1648
+ );
1649
+ }
1650
+ /**
1651
+ * Search file contents (live grep).
1652
+ *
1653
+ * Searches through the contents of indexed files using the specified mode:
1654
+ * - `"plain"` (default): SIMD-accelerated literal text matching
1655
+ * - `"regex"`: Regular expression matching
1656
+ * - `"fuzzy"`: Smith-Waterman fuzzy matching per line
1657
+ *
1658
+ * Supports pagination for large result sets. The result includes a `nextCursor`
1659
+ * that can be passed back to fetch the next page.
1660
+ *
1661
+ * The query also supports constraint syntax:
1662
+ * - `*.ts pattern` - Only search in TypeScript files
1663
+ * - `src/ pattern` - Only search in the src directory
1664
+ *
1665
+ * @param query - Search query string
1666
+ * @param options - Grep options (mode, pagination, limits)
1667
+ * @returns Grep results with matched lines and file metadata
1668
+ *
1669
+ * @example
1670
+ * ```typescript
1671
+ * // First page
1672
+ * const result = finder.grep("TODO", { mode: "plain" });
1673
+ * if (result.ok) {
1674
+ * for (const match of result.value.items) {
1675
+ * console.log(`${match.relativePath}:${match.lineNumber}: ${match.lineContent}`);
1676
+ * }
1677
+ * // Fetch next page
1678
+ * if (result.value.nextCursor) {
1679
+ * const page2 = finder.grep("TODO", {
1680
+ * cursor: result.value.nextCursor,
1681
+ * });
1682
+ * }
1683
+ * }
1684
+ * ```
1685
+ */
1686
+ grep(query, options) {
1687
+ const guard = this.ensureAlive();
1688
+ if (!guard.ok) return guard;
1689
+ return ffiLiveGrep(
1690
+ guard.value,
1691
+ query,
1692
+ options?.mode ?? "plain",
1693
+ options?.maxFileSize ?? 0,
1694
+ options?.maxMatchesPerFile ?? 0,
1695
+ options?.smartCase ?? true,
1696
+ options?.cursor?._offset ?? 0,
1697
+ options?.pageSize ?? 0,
1698
+ options?.timeBudgetMs ?? 0,
1699
+ options?.beforeContext ?? 0,
1700
+ options?.afterContext ?? 0,
1701
+ options?.classifyDefinitions ?? false
1702
+ );
1703
+ }
1704
+ /**
1705
+ * Multi-pattern OR search using Aho-Corasick.
1706
+ *
1707
+ * Searches for lines matching ANY of the provided patterns using
1708
+ * SIMD-accelerated multi-needle matching. Faster than regex alternation
1709
+ * for literal text searches.
1710
+ *
1711
+ * Supports pagination. The result includes a `nextCursor` that can be
1712
+ * passed back to fetch the next page.
1713
+ *
1714
+ * @param options - Multi-grep options including patterns and optional constraints
1715
+ * @returns Grep results with matched lines and file metadata
1716
+ *
1717
+ * @example
1718
+ * ```typescript
1719
+ * const result = finder.multiGrep({
1720
+ * patterns: ["VideoFrame", "video_frame", "PreloadedImage"],
1721
+ * });
1722
+ * if (result.ok) {
1723
+ * for (const match of result.value.items) {
1724
+ * console.log(`${match.relativePath}:${match.lineNumber}: ${match.lineContent}`);
1725
+ * }
1726
+ * }
1727
+ * ```
1728
+ */
1729
+ multiGrep(options) {
1730
+ const guard = this.ensureAlive();
1731
+ if (!guard.ok) return guard;
1732
+ if (!options.patterns || options.patterns.length === 0) {
1733
+ return err("patterns array must have at least 1 element");
1734
+ }
1735
+ return ffiMultiGrep(
1736
+ guard.value,
1737
+ options.patterns.join("\n"),
1738
+ options.constraints ?? "",
1739
+ options.maxFileSize ?? 0,
1740
+ options.maxMatchesPerFile ?? 0,
1741
+ options.smartCase ?? true,
1742
+ options.cursor?._offset ?? 0,
1743
+ options.pageSize ?? 0,
1744
+ options.timeBudgetMs ?? 0,
1745
+ options.beforeContext ?? 0,
1746
+ options.afterContext ?? 0,
1747
+ options.classifyDefinitions ?? false
1748
+ );
1749
+ }
1750
+ /**
1751
+ * Trigger a rescan of the indexed directory.
1752
+ *
1753
+ * This is useful after major file system changes that the
1754
+ * background watcher might have missed.
1755
+ */
1756
+ scanFiles() {
1757
+ const guard = this.ensureAlive();
1758
+ if (!guard.ok) return guard;
1759
+ return ffiScanFiles(guard.value);
1760
+ }
1761
+ /**
1762
+ * Check if a scan is currently in progress.
1763
+ */
1764
+ isScanning() {
1765
+ if (this.handle === null) return false;
1766
+ return ffiIsScanning(this.handle);
1767
+ }
1768
+ /**
1769
+ * Get the base path of the file picker (the root directory being indexed).
1770
+ */
1771
+ getBasePath() {
1772
+ const guard = this.ensureAlive();
1773
+ if (!guard.ok) return guard;
1774
+ return ffiGetBasePath(guard.value);
1775
+ }
1776
+ /**
1777
+ * Get the current scan progress.
1778
+ */
1779
+ getScanProgress() {
1780
+ const guard = this.ensureAlive();
1781
+ if (!guard.ok) return guard;
1782
+ return ffiGetScanProgress(guard.value);
1783
+ }
1784
+ /**
1785
+ * Wait for the initial file scan to complete.
1786
+ * Non-blocking: polls `isScanning` and yields to the event loop between checks.
1787
+ *
1788
+ * @param timeoutMs - Maximum time to wait in milliseconds (default: 5000)
1789
+ * @returns true if scan completed, false if timed out
1790
+ *
1791
+ * @example
1792
+ * ```typescript
1793
+ * const finder = FileFinder.create({ basePath: "/path/to/project" });
1794
+ * if (finder.ok) {
1795
+ * const completed = await finder.value.waitForScan(10000);
1796
+ * if (!completed.ok || !completed.value) {
1797
+ * console.warn("Scan did not complete in time");
1798
+ * }
1799
+ * }
1800
+ * ```
1801
+ */
1802
+ async waitForScan(timeoutMs = 5e3) {
1803
+ const guard = this.ensureAlive();
1804
+ if (!guard.ok) return guard;
1805
+ const deadline = Date.now() + timeoutMs;
1806
+ while (this.isScanning()) {
1807
+ if (Date.now() >= deadline) {
1808
+ return { ok: true, value: false };
1809
+ }
1810
+ await new Promise((resolve) => setTimeout(resolve, 50));
1811
+ }
1812
+ return { ok: true, value: true };
1813
+ }
1814
+ /**
1815
+ * Wait for the initial file scan to complete, blocking the calling thread.
1816
+ *
1817
+ * Backed by the native `fff_wait_for_scan` call. Prefer {@link waitForScan}
1818
+ * unless you specifically need synchronous blocking behaviour.
1819
+ *
1820
+ * @param timeoutMs - Maximum time to wait in milliseconds (default: 5000)
1821
+ * @returns true if scan completed, false if timed out
1822
+ */
1823
+ waitForScanBlocking(timeoutMs = 5e3) {
1824
+ const guard = this.ensureAlive();
1825
+ if (!guard.ok) return guard;
1826
+ return ffiWaitForScan(guard.value, timeoutMs);
1827
+ }
1828
+ /**
1829
+ * Wait until the index is fully ready: the scan has finished and the warmup
1830
+ * (content indexing / bigram) phase has completed.
1831
+ *
1832
+ * Non-blocking: polls `getScanProgress` and yields to the event loop.
1833
+ *
1834
+ * @param timeoutMs - Maximum time to wait in milliseconds (default: 5000)
1835
+ * @returns true if the index became ready, false if timed out
1836
+ */
1837
+ async waitForIndexReady(timeoutMs = 5e3) {
1838
+ const guard = this.ensureAlive();
1839
+ if (!guard.ok) return guard;
1840
+ const deadline = Date.now() + timeoutMs;
1841
+ while (true) {
1842
+ const progress = this.getScanProgress();
1843
+ if (!progress.ok) return progress;
1844
+ if (!progress.value.isScanning && progress.value.isWarmupComplete) {
1845
+ return { ok: true, value: true };
1846
+ }
1847
+ if (Date.now() >= deadline) {
1848
+ return { ok: true, value: false };
1849
+ }
1850
+ await new Promise((resolve) => setTimeout(resolve, 50));
1851
+ }
1852
+ }
1853
+ /**
1854
+ * Change the indexed directory to a new path.
1855
+ *
1856
+ * This stops the current file watcher and starts indexing the new directory.
1857
+ *
1858
+ * @param newPath - New directory path to index
1859
+ */
1860
+ reindex(newPath) {
1861
+ const guard = this.ensureAlive();
1862
+ if (!guard.ok) return guard;
1863
+ return ffiRestartIndex(guard.value, newPath);
1864
+ }
1865
+ /**
1866
+ * Refresh the git status cache.
1867
+ *
1868
+ * @returns Number of files with updated git status
1869
+ */
1870
+ refreshGitStatus() {
1871
+ const guard = this.ensureAlive();
1872
+ if (!guard.ok) return guard;
1873
+ return ffiRefreshGitStatus(guard.value);
1874
+ }
1875
+ /**
1876
+ * Track query completion for smart suggestions.
1877
+ *
1878
+ * Call this when a user selects a file from search results.
1879
+ * This helps improve future search rankings for similar queries.
1880
+ *
1881
+ * @param query - The search query that was used
1882
+ * @param selectedFilePath - The file path that was selected
1883
+ */
1884
+ trackQuery(query, selectedFilePath) {
1885
+ const guard = this.ensureAlive();
1886
+ if (!guard.ok) return guard;
1887
+ return ffiTrackQuery(guard.value, query, selectedFilePath);
1888
+ }
1889
+ /**
1890
+ * Get a historical query by offset.
1891
+ *
1892
+ * @param offset - Offset from most recent (0 = most recent)
1893
+ * @returns The historical query string, or null if not found
1894
+ */
1895
+ getHistoricalQuery(offset) {
1896
+ const guard = this.ensureAlive();
1897
+ if (!guard.ok) return guard;
1898
+ return ffiGetHistoricalQuery(guard.value, offset);
1899
+ }
1900
+ watch(patternOrCallback, callbackOrOptions, maybeOptions) {
1901
+ const noPattern = typeof patternOrCallback === "function";
1902
+ const pattern = noPattern ? "" : patternOrCallback;
1903
+ const callback = noPattern ? patternOrCallback : callbackOrOptions;
1904
+ const options = noPattern ? callbackOrOptions : maybeOptions;
1905
+ if (typeof callback !== "function") {
1906
+ return err("watch callback must be a function");
1907
+ }
1908
+ const guard = this.ensureAlive();
1909
+ if (!guard.ok) return guard;
1910
+ const created = ffiWatch(guard.value, pattern, options?.ignore ?? [], callback);
1911
+ if (!created.ok) return created;
1912
+ const watchId = created.value;
1913
+ this.watchers.add(watchId);
1914
+ return {
1915
+ ok: true,
1916
+ value: () => {
1917
+ if (!this.watchers.delete(watchId)) return;
1918
+ if (this.handle !== null) ffiUnwatch(this.handle, watchId);
1919
+ }
1920
+ };
1921
+ }
1922
+ /**
1923
+ * Get health check information.
1924
+ *
1925
+ * Useful for debugging and verifying the file finder is working correctly.
1926
+ *
1927
+ * @param testPath - Optional path to test git repository detection
1928
+ */
1929
+ healthCheck(testPath) {
1930
+ return ffiHealthCheck(this.handle, testPath || "");
1931
+ }
1932
+ /**
1933
+ * Check if the native library is available.
1934
+ */
1935
+ static isAvailable() {
1936
+ return isAvailable();
1937
+ }
1938
+ /**
1939
+ * Ensure the native library is loaded.
1940
+ *
1941
+ * Loads the native library from the platform-specific npm package
1942
+ * or a local dev build. Throws if the binary is not found.
1943
+ */
1944
+ static ensureLoaded() {
1945
+ ensureLoaded();
1946
+ }
1947
+ /**
1948
+ * Get a health check without requiring an instance.
1949
+ *
1950
+ * Returns limited info (version + git only, no picker/frecency/query data).
1951
+ *
1952
+ * @param testPath - Optional path to test git repository detection
1953
+ */
1954
+ static healthCheckStatic(testPath) {
1955
+ return ffiHealthCheck(null, testPath || "");
1956
+ }
1957
+ };
1958
+ // Annotate the CommonJS export names for ESM import in node:
1959
+ 0 && (module.exports = {
1960
+ FileFinder,
1961
+ binaryExists,
1962
+ closeLibrary,
1963
+ err,
1964
+ findBinary,
1965
+ getLibExtension,
1966
+ getLibFilename,
1967
+ getNpmPackageName,
1968
+ getTriple,
1969
+ ok
1970
+ });
1971
+ //# sourceMappingURL=index.cjs.map