@cmmd-center/forge 0.13.6 → 0.13.8

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.
Files changed (78) hide show
  1. package/dist/{BunHttpServer-DKvSpNd1.cjs → BunHttpServer-C7-1JJ7b.cjs} +4 -4
  2. package/dist/{BunHttpServer-CDh4Vioe.mjs → BunHttpServer-CVPGkizx.mjs} +4 -4
  3. package/dist/{BunServices-D6PAheqS.mjs → BunServices-CPrZSIfE.mjs} +2 -2
  4. package/dist/{BunServices-DDh7Whyx.cjs → BunServices-Dl-3897e.cjs} +2 -2
  5. package/dist/{Cache-BVilO50s.mjs → Cache-C9cwdCDt.mjs} +2 -2
  6. package/dist/{Cache-DCF22Dfk.cjs → Cache-Cf3ARNaS.cjs} +2 -2
  7. package/dist/{HttpServer-ClsGgAq1.mjs → HttpServer-COaBeM7-.mjs} +2 -2
  8. package/dist/{HttpServer-D1gSU7NX.cjs → HttpServer-CggZzScj.cjs} +3 -3
  9. package/dist/{NodeHttpServer-Dfc7nSMq.cjs → NodeHttpServer-Bi339XFk.cjs} +5 -5
  10. package/dist/{NodeHttpServer-DT0TF8s4.mjs → NodeHttpServer-UQnvZnmu.mjs} +5 -5
  11. package/dist/{NodePTY-loxi8Vku.mjs → NodePTY-B1u6RHXk.mjs} +2 -2
  12. package/dist/{NodePTY-S4tPEwZc.cjs → NodePTY-UOaRyQsj.cjs} +2 -2
  13. package/dist/{NodeServices-Cb0rXFb2.cjs → NodeServices-CKFIZVu5.cjs} +2 -2
  14. package/dist/{NodeServices-DE8jhdcU.cjs → NodeServices-R0BZsZ72.cjs} +1 -1
  15. package/dist/{NodeServices-BahzLhbo.mjs → NodeServices-z1Ac4lEO.mjs} +2 -2
  16. package/dist/{NodeSqliteClient-DqFjB3u_.mjs → NodeSqliteClient-BrzqdOsF.mjs} +4 -4
  17. package/dist/{NodeSqliteClient-qjBWAOGq.cjs → NodeSqliteClient-CyJvo9y9.cjs} +4 -4
  18. package/dist/{NodeTerminal-rYbifme7.cjs → NodeTerminal-B89MobLp.cjs} +2 -2
  19. package/dist/{NodeTerminal-BPw7bktz.mjs → NodeTerminal-pYVVRTiD.mjs} +2 -2
  20. package/dist/{Path-CGCc_dDx.cjs → Path-BunGrE8F.cjs} +72 -1
  21. package/dist/{Path-C851t6sC.mjs → Path-evOm-H0Q.mjs} +61 -2
  22. package/dist/{SqlError-CzB6p9lL.cjs → SqlError-1wIMjXAf.cjs} +2 -2
  23. package/dist/{SqlError-3aqibZrj.mjs → SqlError-CezTFGvr.mjs} +2 -2
  24. package/dist/{SqliteClient-DTWCGkrj.mjs → SqliteClient-D3Yd3ZHn.mjs} +3 -3
  25. package/dist/{SqliteClient-Cq1peWN1.cjs → SqliteClient-bo7iui1t.cjs} +3 -3
  26. package/dist/bin.cjs +4875 -3961
  27. package/dist/bin.mjs +4422 -3514
  28. package/dist/client/assets/{CanvasPanel-BwSV27Ct.js → CanvasPanel-z0F-jdf1.js} +2 -2
  29. package/dist/client/assets/{CompositeItem-CZk3X-ud.js → CompositeItem-P9UVXCKv.js} +2 -2
  30. package/dist/client/assets/DiffPanel-CBfeDIH3.js +64 -0
  31. package/dist/client/assets/{DiffPanel.logic-D-SDgBF9.js → DiffPanel.logic-CZdlskWf.js} +2 -2
  32. package/dist/client/assets/DiffPanelShell-BnSbIo9A.js +2532 -0
  33. package/dist/client/assets/{DiffWorkerPoolProvider-BO_xUE3R.js → DiffWorkerPoolProvider-DC4SN57K.js} +9 -9
  34. package/dist/client/assets/{PullRequestCodePanel-CW4RK1Oa.js → PullRequestCodePanel-DwjjRPkc.js} +3 -3
  35. package/dist/client/assets/Virtualizer-ClYwoP30.js +2 -0
  36. package/dist/client/assets/{canvasStateStore-5DH2Z-PG.js → canvasStateStore-CdQxd2aO.js} +2 -2
  37. package/dist/client/assets/cmmdAuthRedirect-Bp1uQaQ9.js +2 -0
  38. package/dist/client/assets/index-BE83u7Db.css +1 -0
  39. package/dist/client/assets/index-CZhTqk2p.js +153 -0
  40. package/dist/client/assets/{popover-Dxp_1Sz0.js → popover-CUSlb3eK.js} +2 -2
  41. package/dist/client/assets/src-BL-mOkT1.js +3 -0
  42. package/dist/client/assets/{toggle-B-GE6jq_.js → toggle-BREV66nT.js} +2 -2
  43. package/dist/client/index.html +11 -11
  44. package/dist/forge-build.json +1 -1
  45. package/dist/node_modules/@ff-labs/fff-bin-linux-x64-gnu/libfff_c.so +0 -0
  46. package/dist/node_modules/@ff-labs/fff-bin-linux-x64-gnu/package.json +27 -0
  47. package/dist/node_modules/@ff-labs/fff-node/.bun-tag-689ffe3747e13d19 +0 -0
  48. package/dist/node_modules/@ff-labs/fff-node/README.md +126 -0
  49. package/dist/node_modules/@ff-labs/fff-node/dist/src/binary.d.ts +22 -0
  50. package/dist/node_modules/@ff-labs/fff-node/dist/src/binary.js +144 -0
  51. package/dist/node_modules/@ff-labs/fff-node/dist/src/fff-api.d.ts +581 -0
  52. package/dist/node_modules/@ff-labs/fff-node/dist/src/fff-api.js +24 -0
  53. package/dist/node_modules/@ff-labs/fff-node/dist/src/ffi.d.ts +137 -0
  54. package/dist/node_modules/@ff-labs/fff-node/dist/src/ffi.js +1191 -0
  55. package/dist/node_modules/@ff-labs/fff-node/dist/src/finder.d.ts +353 -0
  56. package/dist/node_modules/@ff-labs/fff-node/dist/src/finder.js +488 -0
  57. package/dist/node_modules/@ff-labs/fff-node/dist/src/index.d.ts +47 -0
  58. package/dist/node_modules/@ff-labs/fff-node/dist/src/index.js +47 -0
  59. package/dist/node_modules/@ff-labs/fff-node/dist/src/platform.d.ts +27 -0
  60. package/dist/node_modules/@ff-labs/fff-node/dist/src/platform.js +120 -0
  61. package/dist/node_modules/@ff-labs/fff-node/package.json +76 -0
  62. package/dist/node_modules/@yuuang/ffi-rs-linux-x64-gnu/README.md +794 -0
  63. package/dist/node_modules/@yuuang/ffi-rs-linux-x64-gnu/ffi-rs.linux-x64-gnu.node +0 -0
  64. package/dist/node_modules/@yuuang/ffi-rs-linux-x64-gnu/package.json +21 -0
  65. package/dist/node_modules/ffi-rs/LICENSE +21 -0
  66. package/dist/node_modules/ffi-rs/README.md +794 -0
  67. package/dist/node_modules/ffi-rs/index.d.ts +222 -0
  68. package/dist/node_modules/ffi-rs/index.js +380 -0
  69. package/dist/node_modules/ffi-rs/package.json +76 -0
  70. package/dist/{open-FIgTDosf.mjs → open-D8mGyWmt.mjs} +3 -3
  71. package/package.json +2 -1
  72. package/dist/client/assets/DiffPanel-ixzJ8GwW.js +0 -64
  73. package/dist/client/assets/DiffPanelShell-Dz5Wvcvi.js +0 -2532
  74. package/dist/client/assets/cmmdAuthRedirect-D68LO-sw.js +0 -2
  75. package/dist/client/assets/diffTargetResolution-WqxwJEax.js +0 -2
  76. package/dist/client/assets/index-DivaIsZ4.css +0 -1
  77. package/dist/client/assets/index-UXWCtM6c.js +0 -153
  78. package/dist/client/assets/src-n5cTLVWV.js +0 -3
@@ -0,0 +1,1191 @@
1
+ /**
2
+ * Node.js FFI bindings for the fff-c native library using ffi-rs
3
+ *
4
+ * This module uses ffi-rs to call into the Rust C library.
5
+ * All functions follow the Result pattern for error handling.
6
+ *
7
+ * The API is instance-based: `ffiCreate` returns an opaque handle that must
8
+ * be passed to all subsequent calls and freed with `ffiDestroy`.
9
+ *
10
+ * ## Memory management
11
+ *
12
+ * Every `fff_*` function returning `*mut FffResult` allocates with Rust's Box.
13
+ * We MUST call `fff_free_result` to properly deallocate (not libc::free).
14
+ *
15
+ * ## FffResult struct reading
16
+ *
17
+ * The FffResult struct layout (#[repr(C)]):
18
+ * offset 0: success (bool, 1 byte + 7 padding)
19
+ * offset 8: data pointer (8 bytes) - *mut c_char (JSON string or null)
20
+ * offset 16: error pointer (8 bytes) - *mut c_char (error message or null)
21
+ * offset 24: handle pointer (8 bytes) - *mut c_void (instance handle or null)
22
+ *
23
+ * ## Two-step approach for reading + freeing
24
+ *
25
+ * ffi-rs auto-dereferences struct retType pointers, losing the original pointer.
26
+ * We solve this by:
27
+ * 1. Calling the C function with `retType: DataType.External` to get the raw pointer
28
+ * 2. Using `restorePointer` to read the struct fields from the raw pointer
29
+ * 3. Calling `fff_free_result` with the original raw pointer
30
+ *
31
+ * ## Null pointer detection
32
+ *
33
+ * `isNullPointer` from ffi-rs correctly detects null C pointers wrapped as
34
+ * V8 External objects. We use this instead of truthy checks.
35
+ */
36
+ import { close, DataType, isNullPointer, load, open, restorePointer, wrapPointer, } from "ffi-rs";
37
+ import { findBinary } from "./binary.js";
38
+ import { createGrepCursor, err } from "./fff-api.js";
39
+ const LIBRARY_KEY = "fff_c";
40
+ const FFF_CREATE_OPTIONS_STRUCT = {
41
+ version: DataType.U32,
42
+ base_path: DataType.String,
43
+ frecency_db_path: DataType.String,
44
+ history_db_path: DataType.String,
45
+ enable_mmap_cache: DataType.U8,
46
+ enable_content_indexing: DataType.U8,
47
+ watch: DataType.U8,
48
+ ai_mode: DataType.U8,
49
+ log_file_path: DataType.String,
50
+ log_level: DataType.String,
51
+ cache_budget_max_files: DataType.U64,
52
+ cache_budget_max_bytes: DataType.U64,
53
+ cache_budget_max_file_size: DataType.U64,
54
+ enable_fs_root_scanning: DataType.U8,
55
+ enable_home_dir_scanning: DataType.U8,
56
+ };
57
+ // ALWAYS KEEP IN SYNC WITH fff.h
58
+ const FFF_CREATE_OPTIONS_VERSION = 1;
59
+ /** Grep mode constants matching the C API (u8). */
60
+ const GREP_MODE_PLAIN = 0;
61
+ const GREP_MODE_REGEX = 1;
62
+ const GREP_MODE_FUZZY = 2;
63
+ /** Map string mode to u8 */
64
+ function grepModeToU8(mode) {
65
+ switch (mode) {
66
+ case "regex":
67
+ return GREP_MODE_REGEX;
68
+ case "fuzzy":
69
+ return GREP_MODE_FUZZY;
70
+ default:
71
+ return GREP_MODE_PLAIN;
72
+ }
73
+ }
74
+ // Track whether the library is loaded
75
+ let isLoaded = false;
76
+ /**
77
+ * Struct type definition for FffResult used with restorePointer.
78
+ *
79
+ * Uses U8 for the bool success field (correct alignment with ffi-rs).
80
+ * Uses External for ALL pointer fields to avoid hangs on null char* pointers
81
+ * (ffi-rs hangs when trying to read DataType.String from null char*).
82
+ */
83
+ const FFF_RESULT_STRUCT = {
84
+ success: DataType.U8,
85
+ error: DataType.External,
86
+ handle: DataType.External,
87
+ int_value: DataType.I64,
88
+ };
89
+ /**
90
+ * Load the native library using ffi-rs
91
+ */
92
+ function loadLibrary() {
93
+ if (isLoaded)
94
+ return;
95
+ const binaryPath = findBinary();
96
+ if (!binaryPath) {
97
+ throw new Error("fff native library not found. Run `npx @ff-labs/fff-node download` or build from source with `cargo build --release -p fff-c`");
98
+ }
99
+ open({ library: LIBRARY_KEY, path: binaryPath });
100
+ isLoaded = true;
101
+ }
102
+ /**
103
+ * Convert snake_case keys to camelCase recursively
104
+ */
105
+ function snakeToCamel(obj) {
106
+ if (obj === null || obj === undefined)
107
+ return obj;
108
+ if (typeof obj !== "object")
109
+ return obj;
110
+ if (Array.isArray(obj))
111
+ return obj.map(snakeToCamel);
112
+ const result = {};
113
+ for (const [key, value] of Object.entries(obj)) {
114
+ const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
115
+ result[camelKey] = snakeToCamel(value);
116
+ }
117
+ return result;
118
+ }
119
+ /**
120
+ * Read a C string (char*) from an ffi-rs External pointer.
121
+ *
122
+ * Uses restorePointer + wrapPointer to dereference the char* and read the
123
+ * null-terminated string. Returns null if the pointer is null.
124
+ */
125
+ function readCString(ptr) {
126
+ if (isNullPointer(ptr))
127
+ return null;
128
+ try {
129
+ const [str] = restorePointer({
130
+ retType: [DataType.String],
131
+ paramsValue: wrapPointer([ptr]),
132
+ });
133
+ return str;
134
+ }
135
+ catch {
136
+ return null;
137
+ }
138
+ }
139
+ /**
140
+ * Call a C function that returns `*mut FffResult` and get both the raw pointer
141
+ * (for freeing) and the parsed struct fields.
142
+ *
143
+ * Step 1: Call function with `DataType.External` retType → raw pointer
144
+ * Step 2: Use `restorePointer` to read struct fields from the raw pointer
145
+ */
146
+ function callRaw(funcName, paramsType, paramsValue) {
147
+ const rawPtr = load({
148
+ library: LIBRARY_KEY,
149
+ funcName,
150
+ retType: DataType.External,
151
+ paramsType,
152
+ paramsValue,
153
+ freeResultMemory: false,
154
+ });
155
+ const [structData] = restorePointer({
156
+ retType: [FFF_RESULT_STRUCT],
157
+ paramsValue: wrapPointer([rawPtr]),
158
+ });
159
+ return { rawPtr, struct: structData };
160
+ }
161
+ /**
162
+ * Free a FffResult pointer by calling fff_free_result.
163
+ *
164
+ * This frees the FffResult struct and its data/error strings using Rust's
165
+ * Box::from_raw and CString::from_raw. The handle field is NOT freed.
166
+ */
167
+ function freeResult(resultPtr) {
168
+ try {
169
+ load({
170
+ library: LIBRARY_KEY,
171
+ funcName: "fff_free_result",
172
+ retType: DataType.Void,
173
+ paramsType: [DataType.External],
174
+ paramsValue: [resultPtr],
175
+ });
176
+ }
177
+ catch {
178
+ // Ignore cleanup errors
179
+ }
180
+ }
181
+ /**
182
+ * Read the FffResult envelope from a raw call. Returns the parsed struct + raw pointer.
183
+ * On error, frees the result and returns a Result error.
184
+ */
185
+ function readResultEnvelope(funcName, paramsType, paramsValue) {
186
+ loadLibrary();
187
+ const { rawPtr, struct: structData } = callRaw(funcName, paramsType, paramsValue);
188
+ if (structData.success === 0) {
189
+ const errorStr = readCString(structData.error);
190
+ freeResult(rawPtr);
191
+ return err(errorStr || "Unknown error");
192
+ }
193
+ return { rawPtr, struct: structData };
194
+ }
195
+ /** Call a function returning FffResult with void payload. */
196
+ function callVoidResult(funcName, paramsType, paramsValue) {
197
+ const res = readResultEnvelope(funcName, paramsType, paramsValue);
198
+ if ("ok" in res)
199
+ return res;
200
+ freeResult(res.rawPtr);
201
+ return { ok: true, value: undefined };
202
+ }
203
+ /** Call a function returning FffResult with int_value payload. */
204
+ function callIntResult(funcName, paramsType, paramsValue) {
205
+ const res = readResultEnvelope(funcName, paramsType, paramsValue);
206
+ if ("ok" in res)
207
+ return res;
208
+ const value = Number(res.struct.int_value);
209
+ freeResult(res.rawPtr);
210
+ return { ok: true, value };
211
+ }
212
+ /** Call a function returning FffResult with bool in int_value. */
213
+ function callBoolResult(funcName, paramsType, paramsValue) {
214
+ const res = readResultEnvelope(funcName, paramsType, paramsValue);
215
+ if ("ok" in res)
216
+ return res;
217
+ const value = Number(res.struct.int_value) !== 0;
218
+ freeResult(res.rawPtr);
219
+ return { ok: true, value };
220
+ }
221
+ /** Call a function returning FffResult with a C string in handle. */
222
+ function callStringResult(funcName, paramsType, paramsValue) {
223
+ const res = readResultEnvelope(funcName, paramsType, paramsValue);
224
+ if ("ok" in res)
225
+ return res;
226
+ const handlePtr = res.struct.handle;
227
+ freeResult(res.rawPtr);
228
+ if (isNullPointer(handlePtr))
229
+ return { ok: true, value: null };
230
+ const str = readCString(handlePtr);
231
+ freeString(handlePtr);
232
+ return { ok: true, value: str };
233
+ }
234
+ /** Call a function returning FffResult with a JSON string in handle. */
235
+ function callJsonResult(funcName, paramsType, paramsValue) {
236
+ const res = readResultEnvelope(funcName, paramsType, paramsValue);
237
+ if ("ok" in res)
238
+ return res;
239
+ const handlePtr = res.struct.handle;
240
+ freeResult(res.rawPtr);
241
+ if (isNullPointer(handlePtr))
242
+ return { ok: true, value: undefined };
243
+ const jsonStr = readCString(handlePtr);
244
+ freeString(handlePtr);
245
+ if (jsonStr === null || jsonStr === "")
246
+ return { ok: true, value: undefined };
247
+ try {
248
+ return { ok: true, value: snakeToCamel(JSON.parse(jsonStr)) };
249
+ }
250
+ catch {
251
+ return { ok: true, value: jsonStr };
252
+ }
253
+ }
254
+ /** Free a C string via fff_free_string. */
255
+ function freeString(ptr) {
256
+ try {
257
+ load({
258
+ library: LIBRARY_KEY,
259
+ funcName: "fff_free_string",
260
+ retType: DataType.Void,
261
+ paramsType: [DataType.External],
262
+ paramsValue: [ptr],
263
+ });
264
+ }
265
+ catch {
266
+ // Ignore
267
+ }
268
+ }
269
+ export function ffiCreate(basePath, frecencyDbPath, historyDbPath, _useUnsafeNoLock, enableMmapCache, enableContentIndexing, watch, aiMode, logFilePath, logLevel, cacheBudgetMaxFiles, cacheBudgetMaxBytes, cacheBudgetMaxFileSize, enableFsRootScanning, enableHomeDirScanning) {
270
+ loadLibrary();
271
+ const optsValue = {
272
+ version: FFF_CREATE_OPTIONS_VERSION,
273
+ base_path: basePath,
274
+ frecency_db_path: frecencyDbPath,
275
+ history_db_path: historyDbPath,
276
+ enable_mmap_cache: enableMmapCache ? 1 : 0,
277
+ enable_content_indexing: enableContentIndexing ? 1 : 0,
278
+ watch: watch ? 1 : 0,
279
+ ai_mode: aiMode ? 1 : 0,
280
+ log_file_path: logFilePath,
281
+ log_level: logLevel,
282
+ cache_budget_max_files: cacheBudgetMaxFiles,
283
+ cache_budget_max_bytes: cacheBudgetMaxBytes,
284
+ cache_budget_max_file_size: cacheBudgetMaxFileSize,
285
+ enable_fs_root_scanning: enableFsRootScanning ? 1 : 0,
286
+ enable_home_dir_scanning: enableHomeDirScanning ? 1 : 0,
287
+ };
288
+ const rawPtr = load({
289
+ library: LIBRARY_KEY,
290
+ funcName: "fff_create_instance_with",
291
+ retType: DataType.External,
292
+ paramsType: [FFF_CREATE_OPTIONS_STRUCT],
293
+ paramsValue: [optsValue],
294
+ freeResultMemory: false,
295
+ });
296
+ const [structData] = restorePointer({
297
+ retType: [FFF_RESULT_STRUCT],
298
+ paramsValue: wrapPointer([rawPtr]),
299
+ });
300
+ const success = structData.success !== 0;
301
+ try {
302
+ if (success) {
303
+ const handle = structData.handle;
304
+ if (isNullPointer(handle)) {
305
+ return err("fff_create_instance_with returned null handle");
306
+ }
307
+ return { ok: true, value: handle };
308
+ }
309
+ else {
310
+ const errorStr = readCString(structData.error);
311
+ return err(errorStr || "Unknown error");
312
+ }
313
+ }
314
+ finally {
315
+ freeResult(rawPtr);
316
+ }
317
+ }
318
+ /**
319
+ * Destroy and clean up an instance.
320
+ */
321
+ export function ffiDestroy(handle) {
322
+ loadLibrary();
323
+ load({
324
+ library: LIBRARY_KEY,
325
+ funcName: "fff_destroy",
326
+ retType: DataType.Void,
327
+ paramsType: [DataType.External],
328
+ paramsValue: [handle],
329
+ });
330
+ }
331
+ // ---------------------------------------------------------------------------
332
+ // Struct type definitions for restorePointer (must match #[repr(C)] layout)
333
+ // ---------------------------------------------------------------------------
334
+ const FFF_FILE_ITEM_STRUCT = {
335
+ relative_path: DataType.External,
336
+ file_name: DataType.External,
337
+ git_status: DataType.External,
338
+ size: DataType.U64,
339
+ modified: DataType.U64,
340
+ access_frecency_score: DataType.I64,
341
+ modification_frecency_score: DataType.I64,
342
+ total_frecency_score: DataType.I64,
343
+ is_binary: DataType.U8,
344
+ };
345
+ const FFF_SCORE_STRUCT = {
346
+ total: DataType.I32,
347
+ base_score: DataType.I32,
348
+ filename_bonus: DataType.I32,
349
+ special_filename_bonus: DataType.I32,
350
+ frecency_boost: DataType.I32,
351
+ distance_penalty: DataType.I32,
352
+ current_file_penalty: DataType.I32,
353
+ combo_match_boost: DataType.I32,
354
+ exact_match: DataType.U8,
355
+ match_type: DataType.External,
356
+ };
357
+ const FFF_SEARCH_RESULT_STRUCT = {
358
+ items: DataType.External,
359
+ scores: DataType.External,
360
+ count: DataType.U32,
361
+ total_matched: DataType.U32,
362
+ total_files: DataType.U32,
363
+ // FffLocation inlined (flattened)
364
+ location_tag: DataType.U8,
365
+ location_line: DataType.I32,
366
+ location_col: DataType.I32,
367
+ location_end_line: DataType.I32,
368
+ location_end_col: DataType.I32,
369
+ };
370
+ // FffDirItem struct (#[repr(C)]): char* (8) + char* (8) + i32 (4) + 4 padding = 24 bytes
371
+ const FFF_DIR_ITEM_STRUCT = {
372
+ relative_path: DataType.External,
373
+ dir_name: DataType.External,
374
+ max_access_frecency: DataType.I32,
375
+ };
376
+ const FFF_DIR_SEARCH_RESULT_STRUCT = {
377
+ items: DataType.External,
378
+ scores: DataType.External,
379
+ count: DataType.U32,
380
+ total_matched: DataType.U32,
381
+ total_dirs: DataType.U32,
382
+ };
383
+ // FffMixedItem struct (#[repr(C)]): u8 (1) + 7 padding + char* (8) + char* (8) + char* (8)
384
+ // + u64 (8) + u64 (8) + i64 (8) + i64 (8) + i64 (8) + bool (1) + 7 padding = 80 bytes
385
+ const FFF_MIXED_ITEM_STRUCT = {
386
+ item_type: DataType.U8,
387
+ relative_path: DataType.External,
388
+ display_name: DataType.External,
389
+ git_status: DataType.External,
390
+ size: DataType.U64,
391
+ modified: DataType.U64,
392
+ access_frecency_score: DataType.I64,
393
+ modification_frecency_score: DataType.I64,
394
+ total_frecency_score: DataType.I64,
395
+ is_binary: DataType.U8,
396
+ };
397
+ const FFF_MIXED_SEARCH_RESULT_STRUCT = {
398
+ items: DataType.External,
399
+ scores: DataType.External,
400
+ count: DataType.U32,
401
+ total_matched: DataType.U32,
402
+ total_files: DataType.U32,
403
+ total_dirs: DataType.U32,
404
+ // FffLocation inlined (flattened)
405
+ location_tag: DataType.U8,
406
+ location_line: DataType.I32,
407
+ location_col: DataType.I32,
408
+ location_end_line: DataType.I32,
409
+ location_end_col: DataType.I32,
410
+ };
411
+ const FFF_GREP_MATCH_STRUCT = {
412
+ relative_path: DataType.External,
413
+ file_name: DataType.External,
414
+ git_status: DataType.External,
415
+ line_content: DataType.External,
416
+ match_ranges: DataType.External,
417
+ context_before: DataType.External,
418
+ context_after: DataType.External,
419
+ size: DataType.U64,
420
+ modified: DataType.U64,
421
+ total_frecency_score: DataType.I64,
422
+ access_frecency_score: DataType.I64,
423
+ modification_frecency_score: DataType.I64,
424
+ line_number: DataType.U64,
425
+ byte_offset: DataType.U64,
426
+ col: DataType.U32,
427
+ match_ranges_count: DataType.U32,
428
+ context_before_count: DataType.U32,
429
+ context_after_count: DataType.U32,
430
+ fuzzy_score: DataType.U32, // actually u16 in C, but ffi-rs doesn't so we read it as u32 with padding
431
+ has_fuzzy_score: DataType.U8,
432
+ is_binary: DataType.U8,
433
+ is_definition: DataType.U8,
434
+ };
435
+ const FFF_GREP_RESULT_STRUCT = {
436
+ items: DataType.External,
437
+ count: DataType.U32,
438
+ total_matched: DataType.U32,
439
+ total_files_searched: DataType.U32,
440
+ total_files: DataType.U32,
441
+ filtered_file_count: DataType.U32,
442
+ next_file_offset: DataType.U32,
443
+ regex_fallback_error: DataType.External,
444
+ };
445
+ const FFF_MATCH_RANGE_STRUCT = {
446
+ start: DataType.U32,
447
+ end: DataType.U32,
448
+ };
449
+ // ---------------------------------------------------------------------------
450
+ // Struct reading helpers
451
+ // ---------------------------------------------------------------------------
452
+ function readFileItemFromRaw(raw) {
453
+ return {
454
+ relativePath: readCString(raw.relative_path) ?? "",
455
+ fileName: readCString(raw.file_name) ?? "",
456
+ gitStatus: readCString(raw.git_status) ?? "",
457
+ size: Number(raw.size),
458
+ modified: Number(raw.modified),
459
+ accessFrecencyScore: Number(raw.access_frecency_score),
460
+ modificationFrecencyScore: Number(raw.modification_frecency_score),
461
+ totalFrecencyScore: Number(raw.total_frecency_score),
462
+ };
463
+ }
464
+ function readScoreFromRaw(raw) {
465
+ return {
466
+ total: raw.total,
467
+ baseScore: raw.base_score,
468
+ filenameBonus: raw.filename_bonus,
469
+ specialFilenameBonus: raw.special_filename_bonus,
470
+ frecencyBoost: raw.frecency_boost,
471
+ distancePenalty: raw.distance_penalty,
472
+ currentFilePenalty: raw.current_file_penalty,
473
+ comboMatchBoost: raw.combo_match_boost,
474
+ exactMatch: raw.exact_match !== 0,
475
+ matchType: readCString(raw.match_type) ?? "",
476
+ };
477
+ }
478
+ function readDirItemFromRaw(raw) {
479
+ return {
480
+ relativePath: readCString(raw.relative_path) ?? "",
481
+ dirName: readCString(raw.dir_name) ?? "",
482
+ maxAccessFrecency: raw.max_access_frecency,
483
+ };
484
+ }
485
+ function readMixedItemFromRaw(raw) {
486
+ if (raw.item_type === 1) {
487
+ // Directory
488
+ return {
489
+ type: "directory",
490
+ item: {
491
+ relativePath: readCString(raw.relative_path) ?? "",
492
+ dirName: readCString(raw.display_name) ?? "",
493
+ maxAccessFrecency: Number(raw.access_frecency_score),
494
+ },
495
+ };
496
+ }
497
+ // File (item_type === 0)
498
+ return {
499
+ type: "file",
500
+ item: {
501
+ relativePath: readCString(raw.relative_path) ?? "",
502
+ fileName: readCString(raw.display_name) ?? "",
503
+ gitStatus: readCString(raw.git_status) ?? "",
504
+ size: Number(raw.size),
505
+ modified: Number(raw.modified),
506
+ accessFrecencyScore: Number(raw.access_frecency_score),
507
+ modificationFrecencyScore: Number(raw.modification_frecency_score),
508
+ totalFrecencyScore: Number(raw.total_frecency_score),
509
+ },
510
+ };
511
+ }
512
+ /**
513
+ * Call an accessor function that returns a pointer to a struct element,
514
+ * then read the struct from that pointer.
515
+ */
516
+ function callAccessor(funcName, resultPtr, index, structDef) {
517
+ loadLibrary();
518
+ const elemPtr = load({
519
+ library: LIBRARY_KEY,
520
+ funcName,
521
+ retType: DataType.External,
522
+ paramsType: [DataType.External, DataType.U32],
523
+ paramsValue: [resultPtr, index],
524
+ });
525
+ const [raw] = restorePointer({
526
+ retType: [structDef],
527
+ paramsValue: wrapPointer([elemPtr]),
528
+ });
529
+ return raw;
530
+ }
531
+ /**
532
+ * Offset a pointer by `bytes` using the C API helper.
533
+ */
534
+ function ptrOffset(base, bytes) {
535
+ return load({
536
+ library: LIBRARY_KEY,
537
+ funcName: "fff_ptr_offset",
538
+ retType: DataType.External,
539
+ paramsType: [DataType.External, DataType.U64],
540
+ paramsValue: [base, bytes],
541
+ });
542
+ }
543
+ /**
544
+ * Read a C string array (char**) of `count` elements.
545
+ */
546
+ function readCStringArray(ptrArray, count) {
547
+ if (count === 0 || isNullPointer(ptrArray))
548
+ return [];
549
+ const result = [];
550
+ for (let i = 0; i < count; i++) {
551
+ const elemPtr = ptrOffset(ptrArray, i * 8);
552
+ const [charPtr] = restorePointer({
553
+ retType: [DataType.External],
554
+ paramsValue: [elemPtr],
555
+ });
556
+ result.push(readCString(charPtr) ?? "");
557
+ }
558
+ return result;
559
+ }
560
+ function readGrepMatchFromRaw(raw) {
561
+ // Read match_ranges array via pointer offsets
562
+ const matchRanges = [];
563
+ for (let i = 0; i < raw.match_ranges_count; i++) {
564
+ const rangePtr = ptrOffset(raw.match_ranges, i * 8); // FffMatchRange is 8 bytes
565
+ const [rangeRaw] = restorePointer({
566
+ retType: [FFF_MATCH_RANGE_STRUCT],
567
+ paramsValue: wrapPointer([rangePtr]),
568
+ });
569
+ matchRanges.push([rangeRaw.start, rangeRaw.end]);
570
+ }
571
+ const match = {
572
+ relativePath: readCString(raw.relative_path) ?? "",
573
+ fileName: readCString(raw.file_name) ?? "",
574
+ gitStatus: readCString(raw.git_status) ?? "",
575
+ lineContent: readCString(raw.line_content) ?? "",
576
+ size: Number(raw.size),
577
+ modified: Number(raw.modified),
578
+ totalFrecencyScore: Number(raw.total_frecency_score),
579
+ accessFrecencyScore: Number(raw.access_frecency_score),
580
+ modificationFrecencyScore: Number(raw.modification_frecency_score),
581
+ isBinary: raw.is_binary !== 0,
582
+ lineNumber: Number(raw.line_number),
583
+ col: raw.col,
584
+ byteOffset: Number(raw.byte_offset),
585
+ matchRanges,
586
+ };
587
+ if (raw.has_fuzzy_score !== 0) {
588
+ match.fuzzyScore = raw.fuzzy_score;
589
+ }
590
+ if (raw.context_before_count > 0) {
591
+ match.contextBefore = readCStringArray(raw.context_before, raw.context_before_count);
592
+ }
593
+ if (raw.context_after_count > 0) {
594
+ match.contextAfter = readCStringArray(raw.context_after, raw.context_after_count);
595
+ }
596
+ if (raw.is_definition !== 0) {
597
+ match.isDefinition = true;
598
+ }
599
+ return match;
600
+ }
601
+ /**
602
+ * Parse an FffGrepResult from `FffResult.handle`, then free native memory.
603
+ */
604
+ function parseGrepResult(rawPtr) {
605
+ loadLibrary();
606
+ const [envelope] = restorePointer({
607
+ retType: [FFF_RESULT_STRUCT],
608
+ paramsValue: wrapPointer([rawPtr]),
609
+ });
610
+ const success = envelope.success !== 0;
611
+ if (!success) {
612
+ const errorMsg = readCString(envelope.error) || "Unknown error";
613
+ freeResult(rawPtr);
614
+ return err(errorMsg);
615
+ }
616
+ const handlePtr = envelope.handle;
617
+ freeResult(rawPtr);
618
+ if (isNullPointer(handlePtr)) {
619
+ return err("grep returned null result");
620
+ }
621
+ const [gr] = restorePointer({
622
+ retType: [FFF_GREP_RESULT_STRUCT],
623
+ paramsValue: wrapPointer([handlePtr]),
624
+ });
625
+ const count = gr.count;
626
+ const regexFallbackError = readCString(gr.regex_fallback_error) ?? undefined;
627
+ const items = [];
628
+ for (let i = 0; i < count; i++) {
629
+ const rawMatch = callAccessor("fff_grep_result_get_match", handlePtr, i, FFF_GREP_MATCH_STRUCT);
630
+ items.push(readGrepMatchFromRaw(rawMatch));
631
+ }
632
+ // Free native grep result
633
+ load({
634
+ library: LIBRARY_KEY,
635
+ funcName: "fff_free_grep_result",
636
+ retType: DataType.Void,
637
+ paramsType: [DataType.External],
638
+ paramsValue: [handlePtr],
639
+ });
640
+ const grepResult = {
641
+ items,
642
+ totalMatched: gr.total_matched,
643
+ totalFilesSearched: gr.total_files_searched,
644
+ totalFiles: gr.total_files,
645
+ filteredFileCount: gr.filtered_file_count,
646
+ nextCursor: gr.next_file_offset > 0 ? createGrepCursor(gr.next_file_offset) : null,
647
+ };
648
+ if (regexFallbackError) {
649
+ grepResult.regexFallbackError = regexFallbackError;
650
+ }
651
+ return { ok: true, value: grepResult };
652
+ }
653
+ /**
654
+ * Parse an FffSearchResult from `FffResult.handle`, then free native memory.
655
+ */
656
+ function parseSearchResult(rawPtr) {
657
+ loadLibrary();
658
+ // Read FffResult envelope
659
+ const [envelope] = restorePointer({
660
+ retType: [FFF_RESULT_STRUCT],
661
+ paramsValue: wrapPointer([rawPtr]),
662
+ });
663
+ const success = envelope.success !== 0;
664
+ if (!success) {
665
+ const errorMsg = readCString(envelope.error) || "Unknown error";
666
+ freeResult(rawPtr);
667
+ return err(errorMsg);
668
+ }
669
+ const handlePtr = envelope.handle;
670
+ // Free the FffResult envelope (does NOT free handle)
671
+ freeResult(rawPtr);
672
+ if (isNullPointer(handlePtr)) {
673
+ return err("fff_search returned null search result");
674
+ }
675
+ // Read FffSearchResult struct
676
+ const [sr] = restorePointer({
677
+ retType: [FFF_SEARCH_RESULT_STRUCT],
678
+ paramsValue: wrapPointer([handlePtr]),
679
+ });
680
+ const count = sr.count;
681
+ // Read location
682
+ let location;
683
+ if (sr.location_tag === 1) {
684
+ location = { type: "line", line: sr.location_line };
685
+ }
686
+ else if (sr.location_tag === 2) {
687
+ location = {
688
+ type: "position",
689
+ line: sr.location_line,
690
+ col: sr.location_col,
691
+ };
692
+ }
693
+ else if (sr.location_tag === 3) {
694
+ location = {
695
+ type: "range",
696
+ start: { line: sr.location_line, col: sr.location_col },
697
+ end: { line: sr.location_end_line, col: sr.location_end_col },
698
+ };
699
+ }
700
+ // Read items and scores via accessor functions
701
+ const items = [];
702
+ const scores = [];
703
+ for (let i = 0; i < count; i++) {
704
+ const rawItem = callAccessor("fff_search_result_get_item", handlePtr, i, FFF_FILE_ITEM_STRUCT);
705
+ items.push(readFileItemFromRaw(rawItem));
706
+ const rawScore = callAccessor("fff_search_result_get_score", handlePtr, i, FFF_SCORE_STRUCT);
707
+ scores.push(readScoreFromRaw(rawScore));
708
+ }
709
+ // Free native search result
710
+ load({
711
+ library: LIBRARY_KEY,
712
+ funcName: "fff_free_search_result",
713
+ retType: DataType.Void,
714
+ paramsType: [DataType.External],
715
+ paramsValue: [handlePtr],
716
+ });
717
+ const result = {
718
+ items,
719
+ scores,
720
+ totalMatched: sr.total_matched,
721
+ totalFiles: sr.total_files,
722
+ };
723
+ if (location) {
724
+ result.location = location;
725
+ }
726
+ return { ok: true, value: result };
727
+ }
728
+ /**
729
+ * Parse an FffDirSearchResult from `FffResult.handle`, then free native memory.
730
+ */
731
+ function parseDirSearchResult(rawPtr) {
732
+ loadLibrary();
733
+ // Read FffResult envelope
734
+ const [envelope] = restorePointer({
735
+ retType: [FFF_RESULT_STRUCT],
736
+ paramsValue: wrapPointer([rawPtr]),
737
+ });
738
+ const success = envelope.success !== 0;
739
+ if (!success) {
740
+ const errorMsg = readCString(envelope.error) || "Unknown error";
741
+ freeResult(rawPtr);
742
+ return err(errorMsg);
743
+ }
744
+ const handlePtr = envelope.handle;
745
+ // Free the FffResult envelope (does NOT free handle)
746
+ freeResult(rawPtr);
747
+ if (isNullPointer(handlePtr)) {
748
+ return err("fff_search_directories returned null search result");
749
+ }
750
+ // Read FffDirSearchResult struct
751
+ const [sr] = restorePointer({
752
+ retType: [FFF_DIR_SEARCH_RESULT_STRUCT],
753
+ paramsValue: wrapPointer([handlePtr]),
754
+ });
755
+ const count = sr.count;
756
+ // Read items and scores via accessor functions
757
+ const items = [];
758
+ const scores = [];
759
+ for (let i = 0; i < count; i++) {
760
+ const rawItem = callAccessor("fff_dir_search_result_get_item", handlePtr, i, FFF_DIR_ITEM_STRUCT);
761
+ items.push(readDirItemFromRaw(rawItem));
762
+ const rawScore = callAccessor("fff_dir_search_result_get_score", handlePtr, i, FFF_SCORE_STRUCT);
763
+ scores.push(readScoreFromRaw(rawScore));
764
+ }
765
+ // Free native dir search result
766
+ load({
767
+ library: LIBRARY_KEY,
768
+ funcName: "fff_free_dir_search_result",
769
+ retType: DataType.Void,
770
+ paramsType: [DataType.External],
771
+ paramsValue: [handlePtr],
772
+ });
773
+ return {
774
+ ok: true,
775
+ value: {
776
+ items,
777
+ scores,
778
+ totalMatched: sr.total_matched,
779
+ totalDirs: sr.total_dirs,
780
+ },
781
+ };
782
+ }
783
+ /**
784
+ * Parse an FffMixedSearchResult from `FffResult.handle`, then free native memory.
785
+ */
786
+ function parseMixedSearchResult(rawPtr) {
787
+ loadLibrary();
788
+ // Read FffResult envelope
789
+ const [envelope] = restorePointer({
790
+ retType: [FFF_RESULT_STRUCT],
791
+ paramsValue: wrapPointer([rawPtr]),
792
+ });
793
+ const success = envelope.success !== 0;
794
+ if (!success) {
795
+ const errorMsg = readCString(envelope.error) || "Unknown error";
796
+ freeResult(rawPtr);
797
+ return err(errorMsg);
798
+ }
799
+ const handlePtr = envelope.handle;
800
+ // Free the FffResult envelope (does NOT free handle)
801
+ freeResult(rawPtr);
802
+ if (isNullPointer(handlePtr)) {
803
+ return err("fff_search_mixed returned null search result");
804
+ }
805
+ // Read FffMixedSearchResult struct
806
+ const [sr] = restorePointer({
807
+ retType: [FFF_MIXED_SEARCH_RESULT_STRUCT],
808
+ paramsValue: wrapPointer([handlePtr]),
809
+ });
810
+ const count = sr.count;
811
+ // Read location
812
+ let location;
813
+ if (sr.location_tag === 1) {
814
+ location = { type: "line", line: sr.location_line };
815
+ }
816
+ else if (sr.location_tag === 2) {
817
+ location = {
818
+ type: "position",
819
+ line: sr.location_line,
820
+ col: sr.location_col,
821
+ };
822
+ }
823
+ else if (sr.location_tag === 3) {
824
+ location = {
825
+ type: "range",
826
+ start: { line: sr.location_line, col: sr.location_col },
827
+ end: { line: sr.location_end_line, col: sr.location_end_col },
828
+ };
829
+ }
830
+ // Read items and scores via accessor functions
831
+ const items = [];
832
+ const scores = [];
833
+ for (let i = 0; i < count; i++) {
834
+ const rawItem = callAccessor("fff_mixed_search_result_get_item", handlePtr, i, FFF_MIXED_ITEM_STRUCT);
835
+ items.push(readMixedItemFromRaw(rawItem));
836
+ const rawScore = callAccessor("fff_mixed_search_result_get_score", handlePtr, i, FFF_SCORE_STRUCT);
837
+ scores.push(readScoreFromRaw(rawScore));
838
+ }
839
+ // Free native mixed search result
840
+ load({
841
+ library: LIBRARY_KEY,
842
+ funcName: "fff_free_mixed_search_result",
843
+ retType: DataType.Void,
844
+ paramsType: [DataType.External],
845
+ paramsValue: [handlePtr],
846
+ });
847
+ const result = {
848
+ items,
849
+ scores,
850
+ totalMatched: sr.total_matched,
851
+ totalFiles: sr.total_files,
852
+ totalDirs: sr.total_dirs,
853
+ };
854
+ if (location) {
855
+ result.location = location;
856
+ }
857
+ return { ok: true, value: result };
858
+ }
859
+ /**
860
+ * Perform fuzzy search.
861
+ */
862
+ export function ffiSearch(handle, query, currentFile, maxThreads, pageIndex, pageSize, comboBoostMultiplier, minComboCount) {
863
+ loadLibrary();
864
+ const rawPtr = load({
865
+ library: LIBRARY_KEY,
866
+ funcName: "fff_search",
867
+ retType: DataType.External,
868
+ paramsType: [
869
+ DataType.External, // handle
870
+ DataType.String, // query
871
+ DataType.String, // current_file
872
+ DataType.U32, // max_threads
873
+ DataType.U32, // page_index
874
+ DataType.U32, // page_size
875
+ DataType.I32, // combo_boost_multiplier
876
+ DataType.U32, // min_combo_count
877
+ ],
878
+ paramsValue: [
879
+ handle,
880
+ query,
881
+ currentFile,
882
+ maxThreads,
883
+ pageIndex,
884
+ pageSize,
885
+ comboBoostMultiplier,
886
+ minComboCount,
887
+ ],
888
+ freeResultMemory: false,
889
+ });
890
+ return parseSearchResult(rawPtr);
891
+ }
892
+ /**
893
+ * Glob-only search. Bypasses the regular query parser, applies the pattern
894
+ * as a single `Constraint::Glob`, ranks by frecency, paginates.
895
+ */
896
+ export function ffiGlob(handle, pattern, currentFile, maxThreads, pageIndex, pageSize) {
897
+ loadLibrary();
898
+ const rawPtr = load({
899
+ library: LIBRARY_KEY,
900
+ funcName: "fff_glob",
901
+ retType: DataType.External,
902
+ paramsType: [
903
+ DataType.External, // handle
904
+ DataType.String, // pattern
905
+ DataType.String, // current_file
906
+ DataType.U32, // max_threads
907
+ DataType.U32, // page_index
908
+ DataType.U32, // page_size
909
+ ],
910
+ paramsValue: [handle, pattern, currentFile, maxThreads, pageIndex, pageSize],
911
+ freeResultMemory: false,
912
+ });
913
+ return parseSearchResult(rawPtr);
914
+ }
915
+ /**
916
+ * Perform fuzzy directory search.
917
+ */
918
+ export function ffiSearchDirectories(handle, query, currentFile, maxThreads, pageIndex, pageSize) {
919
+ loadLibrary();
920
+ const rawPtr = load({
921
+ library: LIBRARY_KEY,
922
+ funcName: "fff_search_directories",
923
+ retType: DataType.External,
924
+ paramsType: [
925
+ DataType.External, // handle
926
+ DataType.String, // query
927
+ DataType.String, // current_file
928
+ DataType.U32, // max_threads
929
+ DataType.U32, // page_index
930
+ DataType.U32, // page_size
931
+ ],
932
+ paramsValue: [handle, query, currentFile ?? "", maxThreads, pageIndex, pageSize],
933
+ freeResultMemory: false,
934
+ });
935
+ return parseDirSearchResult(rawPtr);
936
+ }
937
+ /**
938
+ * Perform mixed (files + directories) fuzzy search.
939
+ */
940
+ export function ffiSearchMixed(handle, query, currentFile, maxThreads, pageIndex, pageSize, comboBoostMultiplier, minComboCount) {
941
+ loadLibrary();
942
+ const rawPtr = load({
943
+ library: LIBRARY_KEY,
944
+ funcName: "fff_search_mixed",
945
+ retType: DataType.External,
946
+ paramsType: [
947
+ DataType.External, // handle
948
+ DataType.String, // query
949
+ DataType.String, // current_file
950
+ DataType.U32, // max_threads
951
+ DataType.U32, // page_index
952
+ DataType.U32, // page_size
953
+ DataType.I32, // combo_boost_multiplier
954
+ DataType.U32, // min_combo_count
955
+ ],
956
+ paramsValue: [
957
+ handle,
958
+ query,
959
+ currentFile,
960
+ maxThreads,
961
+ pageIndex,
962
+ pageSize,
963
+ comboBoostMultiplier,
964
+ minComboCount,
965
+ ],
966
+ freeResultMemory: false,
967
+ });
968
+ return parseMixedSearchResult(rawPtr);
969
+ }
970
+ /**
971
+ * Live grep - search file contents.
972
+ */
973
+ export function ffiLiveGrep(handle, query, mode, maxFileSize, maxMatchesPerFile, smartCase, fileOffset, pageLimit, timeBudgetMs, beforeContext, afterContext, classifyDefinitions) {
974
+ loadLibrary();
975
+ const rawPtr = load({
976
+ library: LIBRARY_KEY,
977
+ funcName: "fff_live_grep",
978
+ retType: DataType.External,
979
+ paramsType: [
980
+ DataType.External, // handle
981
+ DataType.String, // query
982
+ DataType.U8, // mode
983
+ DataType.U64, // max_file_size
984
+ DataType.U32, // max_matches_per_file
985
+ DataType.Boolean, // smart_case
986
+ DataType.U32, // file_offset
987
+ DataType.U32, // page_limit
988
+ DataType.U64, // time_budget_ms
989
+ DataType.U32, // before_context
990
+ DataType.U32, // after_context
991
+ DataType.Boolean, // classify_definitions
992
+ ],
993
+ paramsValue: [
994
+ handle,
995
+ query,
996
+ grepModeToU8(mode),
997
+ maxFileSize,
998
+ maxMatchesPerFile,
999
+ smartCase,
1000
+ fileOffset,
1001
+ pageLimit,
1002
+ timeBudgetMs,
1003
+ beforeContext,
1004
+ afterContext,
1005
+ classifyDefinitions,
1006
+ ],
1007
+ freeResultMemory: false,
1008
+ });
1009
+ return parseGrepResult(rawPtr);
1010
+ }
1011
+ /**
1012
+ * Multi-pattern grep - Aho-Corasick multi-needle search.
1013
+ */
1014
+ export function ffiMultiGrep(handle, patternsJoined, constraints, maxFileSize, maxMatchesPerFile, smartCase, fileOffset, pageLimit, timeBudgetMs, beforeContext, afterContext, classifyDefinitions) {
1015
+ loadLibrary();
1016
+ const rawPtr = load({
1017
+ library: LIBRARY_KEY,
1018
+ funcName: "fff_multi_grep",
1019
+ retType: DataType.External,
1020
+ paramsType: [
1021
+ DataType.External, // handle
1022
+ DataType.String, // patterns_joined
1023
+ DataType.String, // constraints
1024
+ DataType.U64, // max_file_size
1025
+ DataType.U32, // max_matches_per_file
1026
+ DataType.Boolean, // smart_case
1027
+ DataType.U32, // file_offset
1028
+ DataType.U32, // page_limit
1029
+ DataType.U64, // time_budget_ms
1030
+ DataType.U32, // before_context
1031
+ DataType.U32, // after_context
1032
+ DataType.Boolean, // classify_definitions
1033
+ ],
1034
+ paramsValue: [
1035
+ handle,
1036
+ patternsJoined,
1037
+ constraints,
1038
+ maxFileSize,
1039
+ maxMatchesPerFile,
1040
+ smartCase,
1041
+ fileOffset,
1042
+ pageLimit,
1043
+ timeBudgetMs,
1044
+ beforeContext,
1045
+ afterContext,
1046
+ classifyDefinitions,
1047
+ ],
1048
+ freeResultMemory: false,
1049
+ });
1050
+ return parseGrepResult(rawPtr);
1051
+ }
1052
+ /**
1053
+ * Trigger file scan.
1054
+ */
1055
+ export function ffiScanFiles(handle) {
1056
+ return callVoidResult("fff_scan_files", [DataType.External], [handle]);
1057
+ }
1058
+ /**
1059
+ * Check if scanning.
1060
+ */
1061
+ export function ffiIsScanning(handle) {
1062
+ loadLibrary();
1063
+ return load({
1064
+ library: LIBRARY_KEY,
1065
+ funcName: "fff_is_scanning",
1066
+ retType: DataType.Boolean,
1067
+ paramsType: [DataType.External],
1068
+ paramsValue: [handle],
1069
+ });
1070
+ }
1071
+ /**
1072
+ * Get the base path of the file picker.
1073
+ */
1074
+ export function ffiGetBasePath(handle) {
1075
+ return callStringResult("fff_get_base_path", [DataType.External], [handle]);
1076
+ }
1077
+ // FffScanProgress struct definition
1078
+ const FFF_SCAN_PROGRESS_STRUCT = {
1079
+ scanned_files_count: DataType.U64,
1080
+ is_scanning: DataType.U8,
1081
+ is_watcher_ready: DataType.U8,
1082
+ is_warmup_complete: DataType.U8,
1083
+ };
1084
+ /**
1085
+ * Get scan progress.
1086
+ */
1087
+ export function ffiGetScanProgress(handle) {
1088
+ loadLibrary();
1089
+ const res = readResultEnvelope("fff_get_scan_progress", [DataType.External], [handle]);
1090
+ if ("ok" in res)
1091
+ return res;
1092
+ const handlePtr = res.struct.handle;
1093
+ freeResult(res.rawPtr);
1094
+ if (isNullPointer(handlePtr))
1095
+ return err("scan progress returned null");
1096
+ const [sp] = restorePointer({
1097
+ retType: [FFF_SCAN_PROGRESS_STRUCT],
1098
+ paramsValue: wrapPointer([handlePtr]),
1099
+ });
1100
+ const result = {
1101
+ scannedFilesCount: Number(sp.scanned_files_count),
1102
+ isScanning: sp.is_scanning !== 0,
1103
+ isWatcherReady: sp.is_watcher_ready !== 0,
1104
+ isWarmupComplete: sp.is_warmup_complete !== 0,
1105
+ };
1106
+ // Free native scan progress
1107
+ load({
1108
+ library: LIBRARY_KEY,
1109
+ funcName: "fff_free_scan_progress",
1110
+ retType: DataType.Void,
1111
+ paramsType: [DataType.External],
1112
+ paramsValue: [handlePtr],
1113
+ });
1114
+ return { ok: true, value: result };
1115
+ }
1116
+ /**
1117
+ * Wait for a tree scan to complete.
1118
+ */
1119
+ export function ffiWaitForScan(handle, timeoutMs) {
1120
+ return callBoolResult("fff_wait_for_scan", [DataType.External, DataType.U64], [handle, timeoutMs]);
1121
+ }
1122
+ /**
1123
+ * Restart index in new path.
1124
+ */
1125
+ export function ffiRestartIndex(handle, newPath) {
1126
+ return callVoidResult("fff_restart_index", [DataType.External, DataType.String], [handle, newPath]);
1127
+ }
1128
+ /**
1129
+ * Refresh git status.
1130
+ */
1131
+ export function ffiRefreshGitStatus(handle) {
1132
+ return callIntResult("fff_refresh_git_status", [DataType.External], [handle]);
1133
+ }
1134
+ /**
1135
+ * Track query completion.
1136
+ */
1137
+ export function ffiTrackQuery(handle, query, filePath) {
1138
+ return callBoolResult("fff_track_query", [DataType.External, DataType.String, DataType.String], [handle, query, filePath]);
1139
+ }
1140
+ /**
1141
+ * Get historical query.
1142
+ */
1143
+ export function ffiGetHistoricalQuery(handle, offset) {
1144
+ return callStringResult("fff_get_historical_query", [DataType.External, DataType.U64], [handle, offset]);
1145
+ }
1146
+ /**
1147
+ * Health check.
1148
+ *
1149
+ * `handle` can be null for a limited check (version + git only).
1150
+ * When null, we pass DataType.U64 with value 0 as a null pointer workaround
1151
+ * since ffi-rs does not accept `null` for External parameters.
1152
+ */
1153
+ export function ffiHealthCheck(handle, testPath) {
1154
+ if (handle === null) {
1155
+ // Use U64(0) as a null pointer since ffi-rs rejects null for External params
1156
+ return callJsonResult("fff_health_check", [DataType.U64, DataType.String], [0, testPath]);
1157
+ }
1158
+ return callJsonResult("fff_health_check", [DataType.External, DataType.String], [handle, testPath]);
1159
+ }
1160
+ /**
1161
+ * Ensure the library is loaded.
1162
+ *
1163
+ * Loads the native library from the platform-specific npm package
1164
+ * or a local dev build. Throws if the binary is not found.
1165
+ */
1166
+ export function ensureLoaded() {
1167
+ loadLibrary();
1168
+ }
1169
+ /**
1170
+ * Check if the library is available.
1171
+ */
1172
+ export function isAvailable() {
1173
+ try {
1174
+ loadLibrary();
1175
+ return true;
1176
+ }
1177
+ catch {
1178
+ return false;
1179
+ }
1180
+ }
1181
+ /**
1182
+ * Close the library and release ffi-rs resources.
1183
+ * Call this when completely done with the library.
1184
+ */
1185
+ export function closeLibrary() {
1186
+ if (isLoaded) {
1187
+ close(LIBRARY_KEY);
1188
+ isLoaded = false;
1189
+ }
1190
+ }
1191
+ //# sourceMappingURL=ffi.js.map