@ff-labs/fff-node 0.10.4-nightly.e2cad2f → 0.10.5-dev.774d6bc
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/README.md +2 -2
- package/dist/binary.d.ts.map +1 -0
- package/dist/fff-api.d.ts.map +1 -0
- package/dist/ffi.d.ts.map +1 -0
- package/dist/finder.d.ts.map +1 -0
- package/dist/index.cjs +1533 -0
- package/dist/index.cjs.map +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1504 -0
- package/dist/index.js.map +14 -0
- package/dist/platform.d.ts.map +1 -0
- package/package.json +25 -18
- package/dist/src/binary.d.ts.map +0 -1
- package/dist/src/binary.js +0 -135
- package/dist/src/binary.js.map +0 -1
- package/dist/src/fff-api.d.ts.map +0 -1
- package/dist/src/fff-api.js +0 -24
- package/dist/src/fff-api.js.map +0 -1
- package/dist/src/ffi.d.ts.map +0 -1
- package/dist/src/ffi.js +0 -1365
- package/dist/src/ffi.js.map +0 -1
- package/dist/src/finder.d.ts.map +0 -1
- package/dist/src/finder.js +0 -528
- package/dist/src/finder.js.map +0 -1
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -47
- package/dist/src/index.js.map +0 -1
- package/dist/src/platform.d.ts.map +0 -1
- package/dist/src/platform.js +0 -124
- package/dist/src/platform.js.map +0 -1
- /package/dist/{src/binary.d.ts → binary.d.ts} +0 -0
- /package/dist/{src/fff-api.d.ts → fff-api.d.ts} +0 -0
- /package/dist/{src/ffi.d.ts → ffi.d.ts} +0 -0
- /package/dist/{src/finder.d.ts → finder.d.ts} +0 -0
- /package/dist/{src/index.d.ts → index.d.ts} +0 -0
- /package/dist/{src/platform.d.ts → platform.d.ts} +0 -0
package/dist/src/ffi.js
DELETED
|
@@ -1,1365 +0,0 @@
|
|
|
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, createPointer, DataType, freePointer, funcConstructor, isNullPointer, load, open, PointerType, restorePointer, unwrapPointer, 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
|
-
follow_symlinks: DataType.U8,
|
|
57
|
-
};
|
|
58
|
-
// ALWAYS KEEP IN SYNC WITH fff.h
|
|
59
|
-
const FFF_CREATE_OPTIONS_VERSION = 2;
|
|
60
|
-
/** Grep mode constants matching the C API (u8). */
|
|
61
|
-
const GREP_MODE_PLAIN = 0;
|
|
62
|
-
const GREP_MODE_REGEX = 1;
|
|
63
|
-
const GREP_MODE_FUZZY = 2;
|
|
64
|
-
/** Map string mode to u8 */
|
|
65
|
-
function grepModeToU8(mode) {
|
|
66
|
-
switch (mode) {
|
|
67
|
-
case "regex":
|
|
68
|
-
return GREP_MODE_REGEX;
|
|
69
|
-
case "fuzzy":
|
|
70
|
-
return GREP_MODE_FUZZY;
|
|
71
|
-
default:
|
|
72
|
-
return GREP_MODE_PLAIN;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
// Track whether the library is loaded
|
|
76
|
-
let isLoaded = false;
|
|
77
|
-
/**
|
|
78
|
-
* Struct type definition for FffResult used with restorePointer.
|
|
79
|
-
*
|
|
80
|
-
* Uses U8 for the bool success field (correct alignment with ffi-rs).
|
|
81
|
-
* Uses External for ALL pointer fields to avoid hangs on null char* pointers
|
|
82
|
-
* (ffi-rs hangs when trying to read DataType.String from null char*).
|
|
83
|
-
*/
|
|
84
|
-
const FFF_RESULT_STRUCT = {
|
|
85
|
-
success: DataType.U8,
|
|
86
|
-
error: DataType.External,
|
|
87
|
-
handle: DataType.External,
|
|
88
|
-
int_value: DataType.I64,
|
|
89
|
-
};
|
|
90
|
-
/**
|
|
91
|
-
* Load the native library using ffi-rs
|
|
92
|
-
*/
|
|
93
|
-
function loadLibrary() {
|
|
94
|
-
if (isLoaded)
|
|
95
|
-
return;
|
|
96
|
-
const binaryPath = findBinary();
|
|
97
|
-
if (!binaryPath) {
|
|
98
|
-
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`");
|
|
99
|
-
}
|
|
100
|
-
open({ library: LIBRARY_KEY, path: binaryPath });
|
|
101
|
-
isLoaded = true;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Convert snake_case keys to camelCase recursively
|
|
105
|
-
*/
|
|
106
|
-
function snakeToCamel(obj) {
|
|
107
|
-
if (obj === null || obj === undefined)
|
|
108
|
-
return obj;
|
|
109
|
-
if (typeof obj !== "object")
|
|
110
|
-
return obj;
|
|
111
|
-
if (Array.isArray(obj))
|
|
112
|
-
return obj.map(snakeToCamel);
|
|
113
|
-
const result = {};
|
|
114
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
115
|
-
const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
116
|
-
result[camelKey] = snakeToCamel(value);
|
|
117
|
-
}
|
|
118
|
-
return result;
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Read a C string (char*) from an ffi-rs External pointer.
|
|
122
|
-
*
|
|
123
|
-
* Uses restorePointer + wrapPointer to dereference the char* and read the
|
|
124
|
-
* null-terminated string. Returns null if the pointer is null.
|
|
125
|
-
*/
|
|
126
|
-
function readCString(ptr) {
|
|
127
|
-
if (isNullPointer(ptr))
|
|
128
|
-
return null;
|
|
129
|
-
try {
|
|
130
|
-
const [str] = restorePointer({
|
|
131
|
-
retType: [DataType.String],
|
|
132
|
-
paramsValue: wrapPointer([ptr]),
|
|
133
|
-
});
|
|
134
|
-
return str;
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Call a C function that returns `*mut FffResult` and get both the raw pointer
|
|
142
|
-
* (for freeing) and the parsed struct fields.
|
|
143
|
-
*
|
|
144
|
-
* Step 1: Call function with `DataType.External` retType → raw pointer
|
|
145
|
-
* Step 2: Use `restorePointer` to read struct fields from the raw pointer
|
|
146
|
-
*/
|
|
147
|
-
function callRaw(funcName, paramsType, paramsValue) {
|
|
148
|
-
const rawPtr = load({
|
|
149
|
-
library: LIBRARY_KEY,
|
|
150
|
-
funcName,
|
|
151
|
-
retType: DataType.External,
|
|
152
|
-
paramsType,
|
|
153
|
-
paramsValue,
|
|
154
|
-
freeResultMemory: false,
|
|
155
|
-
});
|
|
156
|
-
const [structData] = restorePointer({
|
|
157
|
-
retType: [FFF_RESULT_STRUCT],
|
|
158
|
-
paramsValue: wrapPointer([rawPtr]),
|
|
159
|
-
});
|
|
160
|
-
return { rawPtr, struct: structData };
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Free a FffResult pointer by calling fff_free_result.
|
|
164
|
-
*
|
|
165
|
-
* This frees the FffResult struct and its data/error strings using Rust's
|
|
166
|
-
* Box::from_raw and CString::from_raw. The handle field is NOT freed.
|
|
167
|
-
*/
|
|
168
|
-
function freeResult(resultPtr) {
|
|
169
|
-
try {
|
|
170
|
-
load({
|
|
171
|
-
library: LIBRARY_KEY,
|
|
172
|
-
funcName: "fff_free_result",
|
|
173
|
-
retType: DataType.Void,
|
|
174
|
-
paramsType: [DataType.External],
|
|
175
|
-
paramsValue: [resultPtr],
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
// Ignore cleanup errors
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Read the FffResult envelope from a raw call. Returns the parsed struct + raw pointer.
|
|
184
|
-
* On error, frees the result and returns a Result error.
|
|
185
|
-
*/
|
|
186
|
-
function readResultEnvelope(funcName, paramsType, paramsValue) {
|
|
187
|
-
loadLibrary();
|
|
188
|
-
const { rawPtr, struct: structData } = callRaw(funcName, paramsType, paramsValue);
|
|
189
|
-
if (structData.success === 0) {
|
|
190
|
-
const errorStr = readCString(structData.error);
|
|
191
|
-
freeResult(rawPtr);
|
|
192
|
-
return err(errorStr || "Unknown error");
|
|
193
|
-
}
|
|
194
|
-
return { rawPtr, struct: structData };
|
|
195
|
-
}
|
|
196
|
-
/** Call a function returning FffResult with void payload. */
|
|
197
|
-
function callVoidResult(funcName, paramsType, paramsValue) {
|
|
198
|
-
const res = readResultEnvelope(funcName, paramsType, paramsValue);
|
|
199
|
-
if ("ok" in res)
|
|
200
|
-
return res;
|
|
201
|
-
freeResult(res.rawPtr);
|
|
202
|
-
return { ok: true, value: undefined };
|
|
203
|
-
}
|
|
204
|
-
/** Call a function returning FffResult with int_value payload. */
|
|
205
|
-
function callIntResult(funcName, paramsType, paramsValue) {
|
|
206
|
-
const res = readResultEnvelope(funcName, paramsType, paramsValue);
|
|
207
|
-
if ("ok" in res)
|
|
208
|
-
return res;
|
|
209
|
-
const value = Number(res.struct.int_value);
|
|
210
|
-
freeResult(res.rawPtr);
|
|
211
|
-
return { ok: true, value };
|
|
212
|
-
}
|
|
213
|
-
/** Call a function returning FffResult with bool in int_value. */
|
|
214
|
-
function callBoolResult(funcName, paramsType, paramsValue) {
|
|
215
|
-
const res = readResultEnvelope(funcName, paramsType, paramsValue);
|
|
216
|
-
if ("ok" in res)
|
|
217
|
-
return res;
|
|
218
|
-
const value = Number(res.struct.int_value) !== 0;
|
|
219
|
-
freeResult(res.rawPtr);
|
|
220
|
-
return { ok: true, value };
|
|
221
|
-
}
|
|
222
|
-
/** Call a function returning FffResult with a C string in handle. */
|
|
223
|
-
function callStringResult(funcName, paramsType, paramsValue) {
|
|
224
|
-
const res = readResultEnvelope(funcName, paramsType, paramsValue);
|
|
225
|
-
if ("ok" in res)
|
|
226
|
-
return res;
|
|
227
|
-
const handlePtr = res.struct.handle;
|
|
228
|
-
freeResult(res.rawPtr);
|
|
229
|
-
if (isNullPointer(handlePtr))
|
|
230
|
-
return { ok: true, value: null };
|
|
231
|
-
const str = readCString(handlePtr);
|
|
232
|
-
freeString(handlePtr);
|
|
233
|
-
return { ok: true, value: str };
|
|
234
|
-
}
|
|
235
|
-
/** Call a function returning FffResult with a JSON string in handle. */
|
|
236
|
-
function callJsonResult(funcName, paramsType, paramsValue) {
|
|
237
|
-
const res = readResultEnvelope(funcName, paramsType, paramsValue);
|
|
238
|
-
if ("ok" in res)
|
|
239
|
-
return res;
|
|
240
|
-
const handlePtr = res.struct.handle;
|
|
241
|
-
freeResult(res.rawPtr);
|
|
242
|
-
if (isNullPointer(handlePtr))
|
|
243
|
-
return { ok: true, value: undefined };
|
|
244
|
-
const jsonStr = readCString(handlePtr);
|
|
245
|
-
freeString(handlePtr);
|
|
246
|
-
if (jsonStr === null || jsonStr === "")
|
|
247
|
-
return { ok: true, value: undefined };
|
|
248
|
-
try {
|
|
249
|
-
return { ok: true, value: snakeToCamel(JSON.parse(jsonStr)) };
|
|
250
|
-
}
|
|
251
|
-
catch {
|
|
252
|
-
return { ok: true, value: jsonStr };
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
/** Free a C string via fff_free_string. */
|
|
256
|
-
function freeString(ptr) {
|
|
257
|
-
try {
|
|
258
|
-
load({
|
|
259
|
-
library: LIBRARY_KEY,
|
|
260
|
-
funcName: "fff_free_string",
|
|
261
|
-
retType: DataType.Void,
|
|
262
|
-
paramsType: [DataType.External],
|
|
263
|
-
paramsValue: [ptr],
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
catch {
|
|
267
|
-
// Ignore
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
export function ffiCreate(basePath, frecencyDbPath, historyDbPath, _useUnsafeNoLock, enableMmapCache, enableContentIndexing, watch, aiMode, logFilePath, logLevel, cacheBudgetMaxFiles, cacheBudgetMaxBytes, cacheBudgetMaxFileSize, enableFsRootScanning, enableHomeDirScanning, followSymlinks) {
|
|
271
|
-
loadLibrary();
|
|
272
|
-
const optsValue = {
|
|
273
|
-
version: FFF_CREATE_OPTIONS_VERSION,
|
|
274
|
-
base_path: basePath,
|
|
275
|
-
frecency_db_path: frecencyDbPath,
|
|
276
|
-
history_db_path: historyDbPath,
|
|
277
|
-
enable_mmap_cache: enableMmapCache ? 1 : 0,
|
|
278
|
-
enable_content_indexing: enableContentIndexing ? 1 : 0,
|
|
279
|
-
watch: watch ? 1 : 0,
|
|
280
|
-
ai_mode: aiMode ? 1 : 0,
|
|
281
|
-
log_file_path: logFilePath,
|
|
282
|
-
log_level: logLevel,
|
|
283
|
-
cache_budget_max_files: cacheBudgetMaxFiles,
|
|
284
|
-
cache_budget_max_bytes: cacheBudgetMaxBytes,
|
|
285
|
-
cache_budget_max_file_size: cacheBudgetMaxFileSize,
|
|
286
|
-
enable_fs_root_scanning: enableFsRootScanning ? 1 : 0,
|
|
287
|
-
enable_home_dir_scanning: enableHomeDirScanning ? 1 : 0,
|
|
288
|
-
follow_symlinks: followSymlinks ? 1 : 0,
|
|
289
|
-
};
|
|
290
|
-
const rawPtr = load({
|
|
291
|
-
library: LIBRARY_KEY,
|
|
292
|
-
funcName: "fff_create_instance_with",
|
|
293
|
-
retType: DataType.External,
|
|
294
|
-
paramsType: [FFF_CREATE_OPTIONS_STRUCT],
|
|
295
|
-
paramsValue: [optsValue],
|
|
296
|
-
freeResultMemory: false,
|
|
297
|
-
});
|
|
298
|
-
const [structData] = restorePointer({
|
|
299
|
-
retType: [FFF_RESULT_STRUCT],
|
|
300
|
-
paramsValue: wrapPointer([rawPtr]),
|
|
301
|
-
});
|
|
302
|
-
const success = structData.success !== 0;
|
|
303
|
-
try {
|
|
304
|
-
if (success) {
|
|
305
|
-
const handle = structData.handle;
|
|
306
|
-
if (isNullPointer(handle)) {
|
|
307
|
-
return err("fff_create_instance_with returned null handle");
|
|
308
|
-
}
|
|
309
|
-
return { ok: true, value: handle };
|
|
310
|
-
}
|
|
311
|
-
else {
|
|
312
|
-
const errorStr = readCString(structData.error);
|
|
313
|
-
return err(errorStr || "Unknown error");
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
finally {
|
|
317
|
-
freeResult(rawPtr);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
/**
|
|
321
|
-
* Destroy and clean up an instance.
|
|
322
|
-
*/
|
|
323
|
-
export function ffiDestroy(handle) {
|
|
324
|
-
loadLibrary();
|
|
325
|
-
load({
|
|
326
|
-
library: LIBRARY_KEY,
|
|
327
|
-
funcName: "fff_destroy",
|
|
328
|
-
retType: DataType.Void,
|
|
329
|
-
paramsType: [DataType.External],
|
|
330
|
-
paramsValue: [handle],
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
// ---------------------------------------------------------------------------
|
|
334
|
-
// Struct type definitions for restorePointer (must match #[repr(C)] layout)
|
|
335
|
-
// ---------------------------------------------------------------------------
|
|
336
|
-
const FFF_FILE_ITEM_STRUCT = {
|
|
337
|
-
relative_path: DataType.External,
|
|
338
|
-
file_name: DataType.External,
|
|
339
|
-
git_status: DataType.External,
|
|
340
|
-
size: DataType.U64,
|
|
341
|
-
modified: DataType.U64,
|
|
342
|
-
access_frecency_score: DataType.I64,
|
|
343
|
-
modification_frecency_score: DataType.I64,
|
|
344
|
-
total_frecency_score: DataType.I64,
|
|
345
|
-
is_binary: DataType.U8,
|
|
346
|
-
};
|
|
347
|
-
const FFF_SCORE_STRUCT = {
|
|
348
|
-
total: DataType.I32,
|
|
349
|
-
base_score: DataType.I32,
|
|
350
|
-
filename_bonus: DataType.I32,
|
|
351
|
-
special_filename_bonus: DataType.I32,
|
|
352
|
-
frecency_boost: DataType.I32,
|
|
353
|
-
distance_penalty: DataType.I32,
|
|
354
|
-
current_file_penalty: DataType.I32,
|
|
355
|
-
combo_match_boost: DataType.I32,
|
|
356
|
-
exact_match: DataType.U8,
|
|
357
|
-
match_type: DataType.External,
|
|
358
|
-
};
|
|
359
|
-
const FFF_SEARCH_RESULT_STRUCT = {
|
|
360
|
-
items: DataType.External,
|
|
361
|
-
scores: DataType.External,
|
|
362
|
-
count: DataType.U32,
|
|
363
|
-
total_matched: DataType.U32,
|
|
364
|
-
total_files: DataType.U32,
|
|
365
|
-
// FffLocation inlined (flattened)
|
|
366
|
-
location_tag: DataType.U8,
|
|
367
|
-
location_line: DataType.I32,
|
|
368
|
-
location_col: DataType.I32,
|
|
369
|
-
location_end_line: DataType.I32,
|
|
370
|
-
location_end_col: DataType.I32,
|
|
371
|
-
};
|
|
372
|
-
// FffDirItem struct (#[repr(C)]): char* (8) + char* (8) + i32 (4) + 4 padding = 24 bytes
|
|
373
|
-
const FFF_DIR_ITEM_STRUCT = {
|
|
374
|
-
relative_path: DataType.External,
|
|
375
|
-
dir_name: DataType.External,
|
|
376
|
-
max_access_frecency: DataType.I32,
|
|
377
|
-
};
|
|
378
|
-
const FFF_DIR_SEARCH_RESULT_STRUCT = {
|
|
379
|
-
items: DataType.External,
|
|
380
|
-
scores: DataType.External,
|
|
381
|
-
count: DataType.U32,
|
|
382
|
-
total_matched: DataType.U32,
|
|
383
|
-
total_dirs: DataType.U32,
|
|
384
|
-
};
|
|
385
|
-
// FffMixedItem struct (#[repr(C)]): u8 (1) + 7 padding + char* (8) + char* (8) + char* (8)
|
|
386
|
-
// + u64 (8) + u64 (8) + i64 (8) + i64 (8) + i64 (8) + bool (1) + 7 padding = 80 bytes
|
|
387
|
-
const FFF_MIXED_ITEM_STRUCT = {
|
|
388
|
-
item_type: DataType.U8,
|
|
389
|
-
relative_path: DataType.External,
|
|
390
|
-
display_name: DataType.External,
|
|
391
|
-
git_status: DataType.External,
|
|
392
|
-
size: DataType.U64,
|
|
393
|
-
modified: DataType.U64,
|
|
394
|
-
access_frecency_score: DataType.I64,
|
|
395
|
-
modification_frecency_score: DataType.I64,
|
|
396
|
-
total_frecency_score: DataType.I64,
|
|
397
|
-
is_binary: DataType.U8,
|
|
398
|
-
};
|
|
399
|
-
const FFF_MIXED_SEARCH_RESULT_STRUCT = {
|
|
400
|
-
items: DataType.External,
|
|
401
|
-
scores: DataType.External,
|
|
402
|
-
count: DataType.U32,
|
|
403
|
-
total_matched: DataType.U32,
|
|
404
|
-
total_files: DataType.U32,
|
|
405
|
-
total_dirs: DataType.U32,
|
|
406
|
-
// FffLocation inlined (flattened)
|
|
407
|
-
location_tag: DataType.U8,
|
|
408
|
-
location_line: DataType.I32,
|
|
409
|
-
location_col: DataType.I32,
|
|
410
|
-
location_end_line: DataType.I32,
|
|
411
|
-
location_end_col: DataType.I32,
|
|
412
|
-
};
|
|
413
|
-
const FFF_GREP_MATCH_STRUCT = {
|
|
414
|
-
relative_path: DataType.External,
|
|
415
|
-
file_name: DataType.External,
|
|
416
|
-
git_status: DataType.External,
|
|
417
|
-
line_content: DataType.External,
|
|
418
|
-
match_ranges: DataType.External,
|
|
419
|
-
context_before: DataType.External,
|
|
420
|
-
context_after: DataType.External,
|
|
421
|
-
size: DataType.U64,
|
|
422
|
-
modified: DataType.U64,
|
|
423
|
-
total_frecency_score: DataType.I64,
|
|
424
|
-
access_frecency_score: DataType.I64,
|
|
425
|
-
modification_frecency_score: DataType.I64,
|
|
426
|
-
line_number: DataType.U64,
|
|
427
|
-
byte_offset: DataType.U64,
|
|
428
|
-
col: DataType.U32,
|
|
429
|
-
match_ranges_count: DataType.U32,
|
|
430
|
-
context_before_count: DataType.U32,
|
|
431
|
-
context_after_count: DataType.U32,
|
|
432
|
-
fuzzy_score: DataType.U32, // actually u16 in C, but ffi-rs doesn't so we read it as u32 with padding
|
|
433
|
-
has_fuzzy_score: DataType.U8,
|
|
434
|
-
is_binary: DataType.U8,
|
|
435
|
-
is_definition: DataType.U8,
|
|
436
|
-
};
|
|
437
|
-
const FFF_GREP_RESULT_STRUCT = {
|
|
438
|
-
items: DataType.External,
|
|
439
|
-
count: DataType.U32,
|
|
440
|
-
total_matched: DataType.U32,
|
|
441
|
-
total_files_searched: DataType.U32,
|
|
442
|
-
total_files: DataType.U32,
|
|
443
|
-
filtered_file_count: DataType.U32,
|
|
444
|
-
next_file_offset: DataType.U32,
|
|
445
|
-
regex_fallback_error: DataType.External,
|
|
446
|
-
};
|
|
447
|
-
const FFF_MATCH_RANGE_STRUCT = {
|
|
448
|
-
start: DataType.U32,
|
|
449
|
-
end: DataType.U32,
|
|
450
|
-
};
|
|
451
|
-
// ---------------------------------------------------------------------------
|
|
452
|
-
// Struct reading helpers
|
|
453
|
-
// ---------------------------------------------------------------------------
|
|
454
|
-
function readFileItemFromRaw(raw) {
|
|
455
|
-
return {
|
|
456
|
-
relativePath: readCString(raw.relative_path) ?? "",
|
|
457
|
-
fileName: readCString(raw.file_name) ?? "",
|
|
458
|
-
gitStatus: readCString(raw.git_status) ?? "",
|
|
459
|
-
size: Number(raw.size),
|
|
460
|
-
modified: Number(raw.modified),
|
|
461
|
-
accessFrecencyScore: Number(raw.access_frecency_score),
|
|
462
|
-
modificationFrecencyScore: Number(raw.modification_frecency_score),
|
|
463
|
-
totalFrecencyScore: Number(raw.total_frecency_score),
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
function readScoreFromRaw(raw) {
|
|
467
|
-
return {
|
|
468
|
-
total: raw.total,
|
|
469
|
-
baseScore: raw.base_score,
|
|
470
|
-
filenameBonus: raw.filename_bonus,
|
|
471
|
-
specialFilenameBonus: raw.special_filename_bonus,
|
|
472
|
-
frecencyBoost: raw.frecency_boost,
|
|
473
|
-
distancePenalty: raw.distance_penalty,
|
|
474
|
-
currentFilePenalty: raw.current_file_penalty,
|
|
475
|
-
comboMatchBoost: raw.combo_match_boost,
|
|
476
|
-
exactMatch: raw.exact_match !== 0,
|
|
477
|
-
matchType: readCString(raw.match_type) ?? "",
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
|
-
function readDirItemFromRaw(raw) {
|
|
481
|
-
return {
|
|
482
|
-
relativePath: readCString(raw.relative_path) ?? "",
|
|
483
|
-
dirName: readCString(raw.dir_name) ?? "",
|
|
484
|
-
maxAccessFrecency: raw.max_access_frecency,
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
function readMixedItemFromRaw(raw) {
|
|
488
|
-
if (raw.item_type === 1) {
|
|
489
|
-
// Directory
|
|
490
|
-
return {
|
|
491
|
-
type: "directory",
|
|
492
|
-
item: {
|
|
493
|
-
relativePath: readCString(raw.relative_path) ?? "",
|
|
494
|
-
dirName: readCString(raw.display_name) ?? "",
|
|
495
|
-
maxAccessFrecency: Number(raw.access_frecency_score),
|
|
496
|
-
},
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
// File (item_type === 0)
|
|
500
|
-
return {
|
|
501
|
-
type: "file",
|
|
502
|
-
item: {
|
|
503
|
-
relativePath: readCString(raw.relative_path) ?? "",
|
|
504
|
-
fileName: readCString(raw.display_name) ?? "",
|
|
505
|
-
gitStatus: readCString(raw.git_status) ?? "",
|
|
506
|
-
size: Number(raw.size),
|
|
507
|
-
modified: Number(raw.modified),
|
|
508
|
-
accessFrecencyScore: Number(raw.access_frecency_score),
|
|
509
|
-
modificationFrecencyScore: Number(raw.modification_frecency_score),
|
|
510
|
-
totalFrecencyScore: Number(raw.total_frecency_score),
|
|
511
|
-
},
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
/**
|
|
515
|
-
* Call an accessor function that returns a pointer to a struct element,
|
|
516
|
-
* then read the struct from that pointer.
|
|
517
|
-
*/
|
|
518
|
-
function callAccessor(funcName, resultPtr, index, structDef) {
|
|
519
|
-
loadLibrary();
|
|
520
|
-
const elemPtr = load({
|
|
521
|
-
library: LIBRARY_KEY,
|
|
522
|
-
funcName,
|
|
523
|
-
retType: DataType.External,
|
|
524
|
-
paramsType: [DataType.External, DataType.U32],
|
|
525
|
-
paramsValue: [resultPtr, index],
|
|
526
|
-
});
|
|
527
|
-
const [raw] = restorePointer({
|
|
528
|
-
retType: [structDef],
|
|
529
|
-
paramsValue: wrapPointer([elemPtr]),
|
|
530
|
-
});
|
|
531
|
-
return raw;
|
|
532
|
-
}
|
|
533
|
-
/**
|
|
534
|
-
* Offset a pointer by `bytes` using the C API helper.
|
|
535
|
-
*/
|
|
536
|
-
function ptrOffset(base, bytes) {
|
|
537
|
-
return load({
|
|
538
|
-
library: LIBRARY_KEY,
|
|
539
|
-
funcName: "fff_ptr_offset",
|
|
540
|
-
retType: DataType.External,
|
|
541
|
-
paramsType: [DataType.External, DataType.U64],
|
|
542
|
-
paramsValue: [base, bytes],
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
/**
|
|
546
|
-
* Read a C string array (char**) of `count` elements.
|
|
547
|
-
*/
|
|
548
|
-
function readCStringArray(ptrArray, count) {
|
|
549
|
-
if (count === 0 || isNullPointer(ptrArray))
|
|
550
|
-
return [];
|
|
551
|
-
const result = [];
|
|
552
|
-
for (let i = 0; i < count; i++) {
|
|
553
|
-
const elemPtr = ptrOffset(ptrArray, i * 8);
|
|
554
|
-
const [charPtr] = restorePointer({
|
|
555
|
-
retType: [DataType.External],
|
|
556
|
-
paramsValue: [elemPtr],
|
|
557
|
-
});
|
|
558
|
-
result.push(readCString(charPtr) ?? "");
|
|
559
|
-
}
|
|
560
|
-
return result;
|
|
561
|
-
}
|
|
562
|
-
function readGrepMatchFromRaw(raw) {
|
|
563
|
-
// Read match_ranges array via pointer offsets
|
|
564
|
-
const matchRanges = [];
|
|
565
|
-
for (let i = 0; i < raw.match_ranges_count; i++) {
|
|
566
|
-
const rangePtr = ptrOffset(raw.match_ranges, i * 8); // FffMatchRange is 8 bytes
|
|
567
|
-
const [rangeRaw] = restorePointer({
|
|
568
|
-
retType: [FFF_MATCH_RANGE_STRUCT],
|
|
569
|
-
paramsValue: wrapPointer([rangePtr]),
|
|
570
|
-
});
|
|
571
|
-
matchRanges.push([rangeRaw.start, rangeRaw.end]);
|
|
572
|
-
}
|
|
573
|
-
const match = {
|
|
574
|
-
relativePath: readCString(raw.relative_path) ?? "",
|
|
575
|
-
fileName: readCString(raw.file_name) ?? "",
|
|
576
|
-
gitStatus: readCString(raw.git_status) ?? "",
|
|
577
|
-
lineContent: readCString(raw.line_content) ?? "",
|
|
578
|
-
size: Number(raw.size),
|
|
579
|
-
modified: Number(raw.modified),
|
|
580
|
-
totalFrecencyScore: Number(raw.total_frecency_score),
|
|
581
|
-
accessFrecencyScore: Number(raw.access_frecency_score),
|
|
582
|
-
modificationFrecencyScore: Number(raw.modification_frecency_score),
|
|
583
|
-
isBinary: raw.is_binary !== 0,
|
|
584
|
-
lineNumber: Number(raw.line_number),
|
|
585
|
-
col: raw.col,
|
|
586
|
-
byteOffset: Number(raw.byte_offset),
|
|
587
|
-
matchRanges,
|
|
588
|
-
};
|
|
589
|
-
if (raw.has_fuzzy_score !== 0) {
|
|
590
|
-
match.fuzzyScore = raw.fuzzy_score;
|
|
591
|
-
}
|
|
592
|
-
if (raw.context_before_count > 0) {
|
|
593
|
-
match.contextBefore = readCStringArray(raw.context_before, raw.context_before_count);
|
|
594
|
-
}
|
|
595
|
-
if (raw.context_after_count > 0) {
|
|
596
|
-
match.contextAfter = readCStringArray(raw.context_after, raw.context_after_count);
|
|
597
|
-
}
|
|
598
|
-
if (raw.is_definition !== 0) {
|
|
599
|
-
match.isDefinition = true;
|
|
600
|
-
}
|
|
601
|
-
return match;
|
|
602
|
-
}
|
|
603
|
-
/**
|
|
604
|
-
* Parse an FffGrepResult from `FffResult.handle`, then free native memory.
|
|
605
|
-
*/
|
|
606
|
-
function parseGrepResult(rawPtr) {
|
|
607
|
-
loadLibrary();
|
|
608
|
-
const [envelope] = restorePointer({
|
|
609
|
-
retType: [FFF_RESULT_STRUCT],
|
|
610
|
-
paramsValue: wrapPointer([rawPtr]),
|
|
611
|
-
});
|
|
612
|
-
const success = envelope.success !== 0;
|
|
613
|
-
if (!success) {
|
|
614
|
-
const errorMsg = readCString(envelope.error) || "Unknown error";
|
|
615
|
-
freeResult(rawPtr);
|
|
616
|
-
return err(errorMsg);
|
|
617
|
-
}
|
|
618
|
-
const handlePtr = envelope.handle;
|
|
619
|
-
freeResult(rawPtr);
|
|
620
|
-
if (isNullPointer(handlePtr)) {
|
|
621
|
-
return err("grep returned null result");
|
|
622
|
-
}
|
|
623
|
-
const [gr] = restorePointer({
|
|
624
|
-
retType: [FFF_GREP_RESULT_STRUCT],
|
|
625
|
-
paramsValue: wrapPointer([handlePtr]),
|
|
626
|
-
});
|
|
627
|
-
const count = gr.count;
|
|
628
|
-
const regexFallbackError = readCString(gr.regex_fallback_error) ?? undefined;
|
|
629
|
-
const items = [];
|
|
630
|
-
for (let i = 0; i < count; i++) {
|
|
631
|
-
const rawMatch = callAccessor("fff_grep_result_get_match", handlePtr, i, FFF_GREP_MATCH_STRUCT);
|
|
632
|
-
items.push(readGrepMatchFromRaw(rawMatch));
|
|
633
|
-
}
|
|
634
|
-
// Free native grep result
|
|
635
|
-
load({
|
|
636
|
-
library: LIBRARY_KEY,
|
|
637
|
-
funcName: "fff_free_grep_result",
|
|
638
|
-
retType: DataType.Void,
|
|
639
|
-
paramsType: [DataType.External],
|
|
640
|
-
paramsValue: [handlePtr],
|
|
641
|
-
});
|
|
642
|
-
const grepResult = {
|
|
643
|
-
items,
|
|
644
|
-
totalMatched: gr.total_matched,
|
|
645
|
-
totalFilesSearched: gr.total_files_searched,
|
|
646
|
-
totalFiles: gr.total_files,
|
|
647
|
-
filteredFileCount: gr.filtered_file_count,
|
|
648
|
-
nextCursor: gr.next_file_offset > 0 ? createGrepCursor(gr.next_file_offset) : null,
|
|
649
|
-
};
|
|
650
|
-
if (regexFallbackError) {
|
|
651
|
-
grepResult.regexFallbackError = regexFallbackError;
|
|
652
|
-
}
|
|
653
|
-
return { ok: true, value: grepResult };
|
|
654
|
-
}
|
|
655
|
-
/**
|
|
656
|
-
* Parse an FffSearchResult from `FffResult.handle`, then free native memory.
|
|
657
|
-
*/
|
|
658
|
-
function parseSearchResult(rawPtr) {
|
|
659
|
-
loadLibrary();
|
|
660
|
-
// Read FffResult envelope
|
|
661
|
-
const [envelope] = restorePointer({
|
|
662
|
-
retType: [FFF_RESULT_STRUCT],
|
|
663
|
-
paramsValue: wrapPointer([rawPtr]),
|
|
664
|
-
});
|
|
665
|
-
const success = envelope.success !== 0;
|
|
666
|
-
if (!success) {
|
|
667
|
-
const errorMsg = readCString(envelope.error) || "Unknown error";
|
|
668
|
-
freeResult(rawPtr);
|
|
669
|
-
return err(errorMsg);
|
|
670
|
-
}
|
|
671
|
-
const handlePtr = envelope.handle;
|
|
672
|
-
// Free the FffResult envelope (does NOT free handle)
|
|
673
|
-
freeResult(rawPtr);
|
|
674
|
-
if (isNullPointer(handlePtr)) {
|
|
675
|
-
return err("fff_search returned null search result");
|
|
676
|
-
}
|
|
677
|
-
// Read FffSearchResult struct
|
|
678
|
-
const [sr] = restorePointer({
|
|
679
|
-
retType: [FFF_SEARCH_RESULT_STRUCT],
|
|
680
|
-
paramsValue: wrapPointer([handlePtr]),
|
|
681
|
-
});
|
|
682
|
-
const count = sr.count;
|
|
683
|
-
// Read location
|
|
684
|
-
let location;
|
|
685
|
-
if (sr.location_tag === 1) {
|
|
686
|
-
location = { type: "line", line: sr.location_line };
|
|
687
|
-
}
|
|
688
|
-
else if (sr.location_tag === 2) {
|
|
689
|
-
location = {
|
|
690
|
-
type: "position",
|
|
691
|
-
line: sr.location_line,
|
|
692
|
-
col: sr.location_col,
|
|
693
|
-
};
|
|
694
|
-
}
|
|
695
|
-
else if (sr.location_tag === 3) {
|
|
696
|
-
location = {
|
|
697
|
-
type: "range",
|
|
698
|
-
start: { line: sr.location_line, col: sr.location_col },
|
|
699
|
-
end: { line: sr.location_end_line, col: sr.location_end_col },
|
|
700
|
-
};
|
|
701
|
-
}
|
|
702
|
-
// Read items and scores via accessor functions
|
|
703
|
-
const items = [];
|
|
704
|
-
const scores = [];
|
|
705
|
-
for (let i = 0; i < count; i++) {
|
|
706
|
-
const rawItem = callAccessor("fff_search_result_get_item", handlePtr, i, FFF_FILE_ITEM_STRUCT);
|
|
707
|
-
items.push(readFileItemFromRaw(rawItem));
|
|
708
|
-
const rawScore = callAccessor("fff_search_result_get_score", handlePtr, i, FFF_SCORE_STRUCT);
|
|
709
|
-
scores.push(readScoreFromRaw(rawScore));
|
|
710
|
-
}
|
|
711
|
-
// Free native search result
|
|
712
|
-
load({
|
|
713
|
-
library: LIBRARY_KEY,
|
|
714
|
-
funcName: "fff_free_search_result",
|
|
715
|
-
retType: DataType.Void,
|
|
716
|
-
paramsType: [DataType.External],
|
|
717
|
-
paramsValue: [handlePtr],
|
|
718
|
-
});
|
|
719
|
-
const result = {
|
|
720
|
-
items,
|
|
721
|
-
scores,
|
|
722
|
-
totalMatched: sr.total_matched,
|
|
723
|
-
totalFiles: sr.total_files,
|
|
724
|
-
};
|
|
725
|
-
if (location) {
|
|
726
|
-
result.location = location;
|
|
727
|
-
}
|
|
728
|
-
return { ok: true, value: result };
|
|
729
|
-
}
|
|
730
|
-
/**
|
|
731
|
-
* Parse an FffDirSearchResult from `FffResult.handle`, then free native memory.
|
|
732
|
-
*/
|
|
733
|
-
function parseDirSearchResult(rawPtr) {
|
|
734
|
-
loadLibrary();
|
|
735
|
-
// Read FffResult envelope
|
|
736
|
-
const [envelope] = restorePointer({
|
|
737
|
-
retType: [FFF_RESULT_STRUCT],
|
|
738
|
-
paramsValue: wrapPointer([rawPtr]),
|
|
739
|
-
});
|
|
740
|
-
const success = envelope.success !== 0;
|
|
741
|
-
if (!success) {
|
|
742
|
-
const errorMsg = readCString(envelope.error) || "Unknown error";
|
|
743
|
-
freeResult(rawPtr);
|
|
744
|
-
return err(errorMsg);
|
|
745
|
-
}
|
|
746
|
-
const handlePtr = envelope.handle;
|
|
747
|
-
// Free the FffResult envelope (does NOT free handle)
|
|
748
|
-
freeResult(rawPtr);
|
|
749
|
-
if (isNullPointer(handlePtr)) {
|
|
750
|
-
return err("fff_search_directories returned null search result");
|
|
751
|
-
}
|
|
752
|
-
// Read FffDirSearchResult struct
|
|
753
|
-
const [sr] = restorePointer({
|
|
754
|
-
retType: [FFF_DIR_SEARCH_RESULT_STRUCT],
|
|
755
|
-
paramsValue: wrapPointer([handlePtr]),
|
|
756
|
-
});
|
|
757
|
-
const count = sr.count;
|
|
758
|
-
// Read items and scores via accessor functions
|
|
759
|
-
const items = [];
|
|
760
|
-
const scores = [];
|
|
761
|
-
for (let i = 0; i < count; i++) {
|
|
762
|
-
const rawItem = callAccessor("fff_dir_search_result_get_item", handlePtr, i, FFF_DIR_ITEM_STRUCT);
|
|
763
|
-
items.push(readDirItemFromRaw(rawItem));
|
|
764
|
-
const rawScore = callAccessor("fff_dir_search_result_get_score", handlePtr, i, FFF_SCORE_STRUCT);
|
|
765
|
-
scores.push(readScoreFromRaw(rawScore));
|
|
766
|
-
}
|
|
767
|
-
// Free native dir search result
|
|
768
|
-
load({
|
|
769
|
-
library: LIBRARY_KEY,
|
|
770
|
-
funcName: "fff_free_dir_search_result",
|
|
771
|
-
retType: DataType.Void,
|
|
772
|
-
paramsType: [DataType.External],
|
|
773
|
-
paramsValue: [handlePtr],
|
|
774
|
-
});
|
|
775
|
-
return {
|
|
776
|
-
ok: true,
|
|
777
|
-
value: {
|
|
778
|
-
items,
|
|
779
|
-
scores,
|
|
780
|
-
totalMatched: sr.total_matched,
|
|
781
|
-
totalDirs: sr.total_dirs,
|
|
782
|
-
},
|
|
783
|
-
};
|
|
784
|
-
}
|
|
785
|
-
/**
|
|
786
|
-
* Parse an FffMixedSearchResult from `FffResult.handle`, then free native memory.
|
|
787
|
-
*/
|
|
788
|
-
function parseMixedSearchResult(rawPtr) {
|
|
789
|
-
loadLibrary();
|
|
790
|
-
// Read FffResult envelope
|
|
791
|
-
const [envelope] = restorePointer({
|
|
792
|
-
retType: [FFF_RESULT_STRUCT],
|
|
793
|
-
paramsValue: wrapPointer([rawPtr]),
|
|
794
|
-
});
|
|
795
|
-
const success = envelope.success !== 0;
|
|
796
|
-
if (!success) {
|
|
797
|
-
const errorMsg = readCString(envelope.error) || "Unknown error";
|
|
798
|
-
freeResult(rawPtr);
|
|
799
|
-
return err(errorMsg);
|
|
800
|
-
}
|
|
801
|
-
const handlePtr = envelope.handle;
|
|
802
|
-
// Free the FffResult envelope (does NOT free handle)
|
|
803
|
-
freeResult(rawPtr);
|
|
804
|
-
if (isNullPointer(handlePtr)) {
|
|
805
|
-
return err("fff_search_mixed returned null search result");
|
|
806
|
-
}
|
|
807
|
-
// Read FffMixedSearchResult struct
|
|
808
|
-
const [sr] = restorePointer({
|
|
809
|
-
retType: [FFF_MIXED_SEARCH_RESULT_STRUCT],
|
|
810
|
-
paramsValue: wrapPointer([handlePtr]),
|
|
811
|
-
});
|
|
812
|
-
const count = sr.count;
|
|
813
|
-
// Read location
|
|
814
|
-
let location;
|
|
815
|
-
if (sr.location_tag === 1) {
|
|
816
|
-
location = { type: "line", line: sr.location_line };
|
|
817
|
-
}
|
|
818
|
-
else if (sr.location_tag === 2) {
|
|
819
|
-
location = {
|
|
820
|
-
type: "position",
|
|
821
|
-
line: sr.location_line,
|
|
822
|
-
col: sr.location_col,
|
|
823
|
-
};
|
|
824
|
-
}
|
|
825
|
-
else if (sr.location_tag === 3) {
|
|
826
|
-
location = {
|
|
827
|
-
type: "range",
|
|
828
|
-
start: { line: sr.location_line, col: sr.location_col },
|
|
829
|
-
end: { line: sr.location_end_line, col: sr.location_end_col },
|
|
830
|
-
};
|
|
831
|
-
}
|
|
832
|
-
// Read items and scores via accessor functions
|
|
833
|
-
const items = [];
|
|
834
|
-
const scores = [];
|
|
835
|
-
for (let i = 0; i < count; i++) {
|
|
836
|
-
const rawItem = callAccessor("fff_mixed_search_result_get_item", handlePtr, i, FFF_MIXED_ITEM_STRUCT);
|
|
837
|
-
items.push(readMixedItemFromRaw(rawItem));
|
|
838
|
-
const rawScore = callAccessor("fff_mixed_search_result_get_score", handlePtr, i, FFF_SCORE_STRUCT);
|
|
839
|
-
scores.push(readScoreFromRaw(rawScore));
|
|
840
|
-
}
|
|
841
|
-
// Free native mixed search result
|
|
842
|
-
load({
|
|
843
|
-
library: LIBRARY_KEY,
|
|
844
|
-
funcName: "fff_free_mixed_search_result",
|
|
845
|
-
retType: DataType.Void,
|
|
846
|
-
paramsType: [DataType.External],
|
|
847
|
-
paramsValue: [handlePtr],
|
|
848
|
-
});
|
|
849
|
-
const result = {
|
|
850
|
-
items,
|
|
851
|
-
scores,
|
|
852
|
-
totalMatched: sr.total_matched,
|
|
853
|
-
totalFiles: sr.total_files,
|
|
854
|
-
totalDirs: sr.total_dirs,
|
|
855
|
-
};
|
|
856
|
-
if (location) {
|
|
857
|
-
result.location = location;
|
|
858
|
-
}
|
|
859
|
-
return { ok: true, value: result };
|
|
860
|
-
}
|
|
861
|
-
/**
|
|
862
|
-
* Perform fuzzy search.
|
|
863
|
-
*/
|
|
864
|
-
export function ffiSearch(handle, query, currentFile, maxThreads, pageIndex, pageSize, comboBoostMultiplier, minComboCount) {
|
|
865
|
-
loadLibrary();
|
|
866
|
-
const rawPtr = load({
|
|
867
|
-
library: LIBRARY_KEY,
|
|
868
|
-
funcName: "fff_search",
|
|
869
|
-
retType: DataType.External,
|
|
870
|
-
paramsType: [
|
|
871
|
-
DataType.External, // handle
|
|
872
|
-
DataType.String, // query
|
|
873
|
-
DataType.String, // current_file
|
|
874
|
-
DataType.U32, // max_threads
|
|
875
|
-
DataType.U32, // page_index
|
|
876
|
-
DataType.U32, // page_size
|
|
877
|
-
DataType.I32, // combo_boost_multiplier
|
|
878
|
-
DataType.U32, // min_combo_count
|
|
879
|
-
],
|
|
880
|
-
paramsValue: [
|
|
881
|
-
handle,
|
|
882
|
-
query,
|
|
883
|
-
currentFile,
|
|
884
|
-
maxThreads,
|
|
885
|
-
pageIndex,
|
|
886
|
-
pageSize,
|
|
887
|
-
comboBoostMultiplier,
|
|
888
|
-
minComboCount,
|
|
889
|
-
],
|
|
890
|
-
freeResultMemory: false,
|
|
891
|
-
});
|
|
892
|
-
return parseSearchResult(rawPtr);
|
|
893
|
-
}
|
|
894
|
-
/**
|
|
895
|
-
* Glob-only search. Bypasses the regular query parser, applies the pattern
|
|
896
|
-
* as a single `Constraint::Glob`, ranks by frecency, paginates.
|
|
897
|
-
*/
|
|
898
|
-
export function ffiGlob(handle, pattern, currentFile, maxThreads, pageIndex, pageSize) {
|
|
899
|
-
loadLibrary();
|
|
900
|
-
const rawPtr = load({
|
|
901
|
-
library: LIBRARY_KEY,
|
|
902
|
-
funcName: "fff_glob",
|
|
903
|
-
retType: DataType.External,
|
|
904
|
-
paramsType: [
|
|
905
|
-
DataType.External, // handle
|
|
906
|
-
DataType.String, // pattern
|
|
907
|
-
DataType.String, // current_file
|
|
908
|
-
DataType.U32, // max_threads
|
|
909
|
-
DataType.U32, // page_index
|
|
910
|
-
DataType.U32, // page_size
|
|
911
|
-
],
|
|
912
|
-
paramsValue: [handle, pattern, currentFile, maxThreads, pageIndex, pageSize],
|
|
913
|
-
freeResultMemory: false,
|
|
914
|
-
});
|
|
915
|
-
return parseSearchResult(rawPtr);
|
|
916
|
-
}
|
|
917
|
-
/**
|
|
918
|
-
* Perform fuzzy directory search.
|
|
919
|
-
*/
|
|
920
|
-
export function ffiSearchDirectories(handle, query, currentFile, maxThreads, pageIndex, pageSize) {
|
|
921
|
-
loadLibrary();
|
|
922
|
-
const rawPtr = load({
|
|
923
|
-
library: LIBRARY_KEY,
|
|
924
|
-
funcName: "fff_search_directories",
|
|
925
|
-
retType: DataType.External,
|
|
926
|
-
paramsType: [
|
|
927
|
-
DataType.External, // handle
|
|
928
|
-
DataType.String, // query
|
|
929
|
-
DataType.String, // current_file
|
|
930
|
-
DataType.U32, // max_threads
|
|
931
|
-
DataType.U32, // page_index
|
|
932
|
-
DataType.U32, // page_size
|
|
933
|
-
],
|
|
934
|
-
paramsValue: [handle, query, currentFile ?? "", maxThreads, pageIndex, pageSize],
|
|
935
|
-
freeResultMemory: false,
|
|
936
|
-
});
|
|
937
|
-
return parseDirSearchResult(rawPtr);
|
|
938
|
-
}
|
|
939
|
-
/**
|
|
940
|
-
* Perform mixed (files + directories) fuzzy search.
|
|
941
|
-
*/
|
|
942
|
-
export function ffiSearchMixed(handle, query, currentFile, maxThreads, pageIndex, pageSize, comboBoostMultiplier, minComboCount) {
|
|
943
|
-
loadLibrary();
|
|
944
|
-
const rawPtr = load({
|
|
945
|
-
library: LIBRARY_KEY,
|
|
946
|
-
funcName: "fff_search_mixed",
|
|
947
|
-
retType: DataType.External,
|
|
948
|
-
paramsType: [
|
|
949
|
-
DataType.External, // handle
|
|
950
|
-
DataType.String, // query
|
|
951
|
-
DataType.String, // current_file
|
|
952
|
-
DataType.U32, // max_threads
|
|
953
|
-
DataType.U32, // page_index
|
|
954
|
-
DataType.U32, // page_size
|
|
955
|
-
DataType.I32, // combo_boost_multiplier
|
|
956
|
-
DataType.U32, // min_combo_count
|
|
957
|
-
],
|
|
958
|
-
paramsValue: [
|
|
959
|
-
handle,
|
|
960
|
-
query,
|
|
961
|
-
currentFile,
|
|
962
|
-
maxThreads,
|
|
963
|
-
pageIndex,
|
|
964
|
-
pageSize,
|
|
965
|
-
comboBoostMultiplier,
|
|
966
|
-
minComboCount,
|
|
967
|
-
],
|
|
968
|
-
freeResultMemory: false,
|
|
969
|
-
});
|
|
970
|
-
return parseMixedSearchResult(rawPtr);
|
|
971
|
-
}
|
|
972
|
-
/**
|
|
973
|
-
* Live grep - search file contents.
|
|
974
|
-
*/
|
|
975
|
-
export function ffiLiveGrep(handle, query, mode, maxFileSize, maxMatchesPerFile, smartCase, fileOffset, pageLimit, timeBudgetMs, beforeContext, afterContext, classifyDefinitions) {
|
|
976
|
-
loadLibrary();
|
|
977
|
-
const rawPtr = load({
|
|
978
|
-
library: LIBRARY_KEY,
|
|
979
|
-
funcName: "fff_live_grep",
|
|
980
|
-
retType: DataType.External,
|
|
981
|
-
paramsType: [
|
|
982
|
-
DataType.External, // handle
|
|
983
|
-
DataType.String, // query
|
|
984
|
-
DataType.U8, // mode
|
|
985
|
-
DataType.U64, // max_file_size
|
|
986
|
-
DataType.U32, // max_matches_per_file
|
|
987
|
-
DataType.Boolean, // smart_case
|
|
988
|
-
DataType.U32, // file_offset
|
|
989
|
-
DataType.U32, // page_limit
|
|
990
|
-
DataType.U64, // time_budget_ms
|
|
991
|
-
DataType.U32, // before_context
|
|
992
|
-
DataType.U32, // after_context
|
|
993
|
-
DataType.Boolean, // classify_definitions
|
|
994
|
-
],
|
|
995
|
-
paramsValue: [
|
|
996
|
-
handle,
|
|
997
|
-
query,
|
|
998
|
-
grepModeToU8(mode),
|
|
999
|
-
maxFileSize,
|
|
1000
|
-
maxMatchesPerFile,
|
|
1001
|
-
smartCase,
|
|
1002
|
-
fileOffset,
|
|
1003
|
-
pageLimit,
|
|
1004
|
-
timeBudgetMs,
|
|
1005
|
-
beforeContext,
|
|
1006
|
-
afterContext,
|
|
1007
|
-
classifyDefinitions,
|
|
1008
|
-
],
|
|
1009
|
-
freeResultMemory: false,
|
|
1010
|
-
});
|
|
1011
|
-
return parseGrepResult(rawPtr);
|
|
1012
|
-
}
|
|
1013
|
-
/**
|
|
1014
|
-
* Multi-pattern grep - Aho-Corasick multi-needle search.
|
|
1015
|
-
*/
|
|
1016
|
-
export function ffiMultiGrep(handle, patternsJoined, constraints, maxFileSize, maxMatchesPerFile, smartCase, fileOffset, pageLimit, timeBudgetMs, beforeContext, afterContext, classifyDefinitions) {
|
|
1017
|
-
loadLibrary();
|
|
1018
|
-
const rawPtr = load({
|
|
1019
|
-
library: LIBRARY_KEY,
|
|
1020
|
-
funcName: "fff_multi_grep",
|
|
1021
|
-
retType: DataType.External,
|
|
1022
|
-
paramsType: [
|
|
1023
|
-
DataType.External, // handle
|
|
1024
|
-
DataType.String, // patterns_joined
|
|
1025
|
-
DataType.String, // constraints
|
|
1026
|
-
DataType.U64, // max_file_size
|
|
1027
|
-
DataType.U32, // max_matches_per_file
|
|
1028
|
-
DataType.Boolean, // smart_case
|
|
1029
|
-
DataType.U32, // file_offset
|
|
1030
|
-
DataType.U32, // page_limit
|
|
1031
|
-
DataType.U64, // time_budget_ms
|
|
1032
|
-
DataType.U32, // before_context
|
|
1033
|
-
DataType.U32, // after_context
|
|
1034
|
-
DataType.Boolean, // classify_definitions
|
|
1035
|
-
],
|
|
1036
|
-
paramsValue: [
|
|
1037
|
-
handle,
|
|
1038
|
-
patternsJoined,
|
|
1039
|
-
constraints,
|
|
1040
|
-
maxFileSize,
|
|
1041
|
-
maxMatchesPerFile,
|
|
1042
|
-
smartCase,
|
|
1043
|
-
fileOffset,
|
|
1044
|
-
pageLimit,
|
|
1045
|
-
timeBudgetMs,
|
|
1046
|
-
beforeContext,
|
|
1047
|
-
afterContext,
|
|
1048
|
-
classifyDefinitions,
|
|
1049
|
-
],
|
|
1050
|
-
freeResultMemory: false,
|
|
1051
|
-
});
|
|
1052
|
-
return parseGrepResult(rawPtr);
|
|
1053
|
-
}
|
|
1054
|
-
/**
|
|
1055
|
-
* Trigger file scan.
|
|
1056
|
-
*/
|
|
1057
|
-
export function ffiScanFiles(handle) {
|
|
1058
|
-
return callVoidResult("fff_scan_files", [DataType.External], [handle]);
|
|
1059
|
-
}
|
|
1060
|
-
/**
|
|
1061
|
-
* Check if scanning.
|
|
1062
|
-
*/
|
|
1063
|
-
export function ffiIsScanning(handle) {
|
|
1064
|
-
loadLibrary();
|
|
1065
|
-
return load({
|
|
1066
|
-
library: LIBRARY_KEY,
|
|
1067
|
-
funcName: "fff_is_scanning",
|
|
1068
|
-
retType: DataType.Boolean,
|
|
1069
|
-
paramsType: [DataType.External],
|
|
1070
|
-
paramsValue: [handle],
|
|
1071
|
-
});
|
|
1072
|
-
}
|
|
1073
|
-
/**
|
|
1074
|
-
* Get the base path of the file picker.
|
|
1075
|
-
*/
|
|
1076
|
-
export function ffiGetBasePath(handle) {
|
|
1077
|
-
return callStringResult("fff_get_base_path", [DataType.External], [handle]);
|
|
1078
|
-
}
|
|
1079
|
-
// FffScanProgress struct definition
|
|
1080
|
-
const FFF_SCAN_PROGRESS_STRUCT = {
|
|
1081
|
-
scanned_files_count: DataType.U64,
|
|
1082
|
-
is_scanning: DataType.U8,
|
|
1083
|
-
is_watcher_ready: DataType.U8,
|
|
1084
|
-
is_warmup_complete: DataType.U8,
|
|
1085
|
-
};
|
|
1086
|
-
/**
|
|
1087
|
-
* Get scan progress.
|
|
1088
|
-
*/
|
|
1089
|
-
export function ffiGetScanProgress(handle) {
|
|
1090
|
-
loadLibrary();
|
|
1091
|
-
const res = readResultEnvelope("fff_get_scan_progress", [DataType.External], [handle]);
|
|
1092
|
-
if ("ok" in res)
|
|
1093
|
-
return res;
|
|
1094
|
-
const handlePtr = res.struct.handle;
|
|
1095
|
-
freeResult(res.rawPtr);
|
|
1096
|
-
if (isNullPointer(handlePtr))
|
|
1097
|
-
return err("scan progress returned null");
|
|
1098
|
-
const [sp] = restorePointer({
|
|
1099
|
-
retType: [FFF_SCAN_PROGRESS_STRUCT],
|
|
1100
|
-
paramsValue: wrapPointer([handlePtr]),
|
|
1101
|
-
});
|
|
1102
|
-
const result = {
|
|
1103
|
-
scannedFilesCount: Number(sp.scanned_files_count),
|
|
1104
|
-
isScanning: sp.is_scanning !== 0,
|
|
1105
|
-
isWatcherReady: sp.is_watcher_ready !== 0,
|
|
1106
|
-
isWarmupComplete: sp.is_warmup_complete !== 0,
|
|
1107
|
-
};
|
|
1108
|
-
// Free native scan progress
|
|
1109
|
-
load({
|
|
1110
|
-
library: LIBRARY_KEY,
|
|
1111
|
-
funcName: "fff_free_scan_progress",
|
|
1112
|
-
retType: DataType.Void,
|
|
1113
|
-
paramsType: [DataType.External],
|
|
1114
|
-
paramsValue: [handlePtr],
|
|
1115
|
-
});
|
|
1116
|
-
return { ok: true, value: result };
|
|
1117
|
-
}
|
|
1118
|
-
/**
|
|
1119
|
-
* Wait for a tree scan to complete.
|
|
1120
|
-
*/
|
|
1121
|
-
export function ffiWaitForScan(handle, timeoutMs) {
|
|
1122
|
-
return callBoolResult("fff_wait_for_scan", [DataType.External, DataType.U64], [handle, timeoutMs]);
|
|
1123
|
-
}
|
|
1124
|
-
/**
|
|
1125
|
-
* Restart index in new path.
|
|
1126
|
-
*/
|
|
1127
|
-
export function ffiRestartIndex(handle, newPath) {
|
|
1128
|
-
return callVoidResult("fff_restart_index", [DataType.External, DataType.String], [handle, newPath]);
|
|
1129
|
-
}
|
|
1130
|
-
/**
|
|
1131
|
-
* Refresh git status.
|
|
1132
|
-
*/
|
|
1133
|
-
export function ffiRefreshGitStatus(handle) {
|
|
1134
|
-
return callIntResult("fff_refresh_git_status", [DataType.External], [handle]);
|
|
1135
|
-
}
|
|
1136
|
-
/**
|
|
1137
|
-
* Track query completion.
|
|
1138
|
-
*/
|
|
1139
|
-
export function ffiTrackQuery(handle, query, filePath) {
|
|
1140
|
-
return callBoolResult("fff_track_query", [DataType.External, DataType.String, DataType.String], [handle, query, filePath]);
|
|
1141
|
-
}
|
|
1142
|
-
/**
|
|
1143
|
-
* Get historical query.
|
|
1144
|
-
*/
|
|
1145
|
-
export function ffiGetHistoricalQuery(handle, offset) {
|
|
1146
|
-
return callStringResult("fff_get_historical_query", [DataType.External, DataType.U64], [handle, offset]);
|
|
1147
|
-
}
|
|
1148
|
-
// ALWAYS KEEP IN SYNC WITH fff.h
|
|
1149
|
-
//
|
|
1150
|
-
// Note: node uses `fff_watch_args` (flattened options) because ffi-rs cannot
|
|
1151
|
-
// marshal a `*const *const c_char` field inside a struct param — StringArray
|
|
1152
|
-
// is only supported as a top-level parameter.
|
|
1153
|
-
//
|
|
1154
|
-
// Batch contents are read through the C accessors (fff_watch_events_count /
|
|
1155
|
-
// fff_watch_events_get_path / fff_watch_events_get_kind), so no struct
|
|
1156
|
-
// layout knowledge lives on this side.
|
|
1157
|
-
/** Map the C kind byte to the public WatchEventKind. */
|
|
1158
|
-
function watchKindFromU8(kind) {
|
|
1159
|
-
switch (kind) {
|
|
1160
|
-
case 0:
|
|
1161
|
-
return "created";
|
|
1162
|
-
case 1:
|
|
1163
|
-
return "modified";
|
|
1164
|
-
case 2:
|
|
1165
|
-
return "removed";
|
|
1166
|
-
default:
|
|
1167
|
-
return "rescan";
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
/** Trampoline signature: (watch id, batch address, user_data — unused). */
|
|
1171
|
-
const WATCH_TRAMPOLINE_TYPE = funcConstructor({
|
|
1172
|
-
paramsType: [DataType.U64, DataType.U64, DataType.U64],
|
|
1173
|
-
retType: DataType.Void,
|
|
1174
|
-
});
|
|
1175
|
-
/** JS handlers keyed by process-unique native watch id. */
|
|
1176
|
-
const watchHandlers = new Map();
|
|
1177
|
-
/** Instances (by handle identity) that ever created a watch subscription. */
|
|
1178
|
-
const watchInstances = new Set();
|
|
1179
|
-
/** Lazily created process-wide trampoline (createPointer result). */
|
|
1180
|
-
let watchTrampoline = null;
|
|
1181
|
-
/** Convert a raw u64 address delivered through the trampoline to a JsExternal. */
|
|
1182
|
-
function addressToExternal(address) {
|
|
1183
|
-
return load({
|
|
1184
|
-
library: LIBRARY_KEY,
|
|
1185
|
-
funcName: "fff_ptr_offset",
|
|
1186
|
-
retType: DataType.External,
|
|
1187
|
-
paramsType: [DataType.U64, DataType.U64],
|
|
1188
|
-
paramsValue: [address, 0],
|
|
1189
|
-
});
|
|
1190
|
-
}
|
|
1191
|
-
/** Parse an FffWatchEventBatch at `address` and free the native memory. */
|
|
1192
|
-
function consumeWatchBatch(address) {
|
|
1193
|
-
const batchPtr = addressToExternal(address);
|
|
1194
|
-
const count = load({
|
|
1195
|
-
library: LIBRARY_KEY,
|
|
1196
|
-
funcName: "fff_watch_events_count",
|
|
1197
|
-
retType: DataType.U32,
|
|
1198
|
-
paramsType: [DataType.External],
|
|
1199
|
-
paramsValue: [batchPtr],
|
|
1200
|
-
});
|
|
1201
|
-
const events = [];
|
|
1202
|
-
for (let i = 0; i < count; i++) {
|
|
1203
|
-
const path = load({
|
|
1204
|
-
library: LIBRARY_KEY,
|
|
1205
|
-
funcName: "fff_watch_events_get_path",
|
|
1206
|
-
retType: DataType.External,
|
|
1207
|
-
paramsType: [DataType.External, DataType.U32],
|
|
1208
|
-
paramsValue: [batchPtr, i],
|
|
1209
|
-
});
|
|
1210
|
-
const kind = load({
|
|
1211
|
-
library: LIBRARY_KEY,
|
|
1212
|
-
funcName: "fff_watch_events_get_kind",
|
|
1213
|
-
retType: DataType.U8,
|
|
1214
|
-
paramsType: [DataType.External, DataType.U32],
|
|
1215
|
-
paramsValue: [batchPtr, i],
|
|
1216
|
-
});
|
|
1217
|
-
events.push({
|
|
1218
|
-
path: readCString(path) ?? "",
|
|
1219
|
-
kind: watchKindFromU8(kind),
|
|
1220
|
-
});
|
|
1221
|
-
}
|
|
1222
|
-
load({
|
|
1223
|
-
library: LIBRARY_KEY,
|
|
1224
|
-
funcName: "fff_free_watch_events",
|
|
1225
|
-
retType: DataType.Void,
|
|
1226
|
-
paramsType: [DataType.U64],
|
|
1227
|
-
paramsValue: [address],
|
|
1228
|
-
});
|
|
1229
|
-
return events;
|
|
1230
|
-
}
|
|
1231
|
-
/**
|
|
1232
|
-
* The single native->JS entry point for all watch subscriptions. Runs on
|
|
1233
|
-
* the JS thread (threadsafe_function delivery); the batch is owned by us
|
|
1234
|
-
* and freed inside `consumeWatchBatch`. Unknown watch ids (unsubscribe
|
|
1235
|
-
* races) are benign: the batch is freed and dropped.
|
|
1236
|
-
*/
|
|
1237
|
-
function watchTrampolineImpl(watchId, batchAddress, _userData) {
|
|
1238
|
-
const events = consumeWatchBatch(batchAddress);
|
|
1239
|
-
const handler = watchHandlers.get(Number(watchId));
|
|
1240
|
-
if (handler === undefined || events.length === 0)
|
|
1241
|
-
return;
|
|
1242
|
-
try {
|
|
1243
|
-
handler(events);
|
|
1244
|
-
}
|
|
1245
|
-
catch {
|
|
1246
|
-
// User callback errors must not propagate into the FFI layer
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
function ensureWatchTrampoline() {
|
|
1250
|
-
if (watchTrampoline === null) {
|
|
1251
|
-
watchTrampoline = createPointer({
|
|
1252
|
-
paramsType: [WATCH_TRAMPOLINE_TYPE],
|
|
1253
|
-
paramsValue: [watchTrampolineImpl],
|
|
1254
|
-
});
|
|
1255
|
-
}
|
|
1256
|
-
return unwrapPointer(watchTrampoline)[0];
|
|
1257
|
-
}
|
|
1258
|
-
// fff watcher uses a single cross-boundary FFI callback to deliver all events which we then manually
|
|
1259
|
-
// mapping to the user's javascript functions
|
|
1260
|
-
function ensureWatchCallbackRegistered(handle) {
|
|
1261
|
-
if (watchInstances.has(handle))
|
|
1262
|
-
return { ok: true, value: undefined };
|
|
1263
|
-
const trampoline = ensureWatchTrampoline();
|
|
1264
|
-
const registered = callVoidResult("fff_set_watch_callback", [DataType.External, DataType.External, DataType.U64], [handle, trampoline, 0]);
|
|
1265
|
-
if (registered.ok)
|
|
1266
|
-
watchInstances.add(handle);
|
|
1267
|
-
return registered;
|
|
1268
|
-
}
|
|
1269
|
-
function releaseWatchTrampolineIfIdle() {
|
|
1270
|
-
if (watchHandlers.size > 0 || watchInstances.size > 0 || watchTrampoline === null)
|
|
1271
|
-
return;
|
|
1272
|
-
freePointer({
|
|
1273
|
-
paramsType: [WATCH_TRAMPOLINE_TYPE],
|
|
1274
|
-
paramsValue: watchTrampoline,
|
|
1275
|
-
pointerType: PointerType.RsPointer,
|
|
1276
|
-
});
|
|
1277
|
-
watchTrampoline = null;
|
|
1278
|
-
}
|
|
1279
|
-
/**
|
|
1280
|
-
* Create a push-mode watch subscription. `callback` receives a normalized
|
|
1281
|
-
* batch of up to 128 events, delivered on the JS event loop.
|
|
1282
|
-
*
|
|
1283
|
-
* Returns the native watch id to pass to `ffiUnwatch`.
|
|
1284
|
-
*/
|
|
1285
|
-
export function ffiWatch(handle, pattern, ignore, callback) {
|
|
1286
|
-
loadLibrary();
|
|
1287
|
-
const registered = ensureWatchCallbackRegistered(handle);
|
|
1288
|
-
if (!registered.ok)
|
|
1289
|
-
return registered;
|
|
1290
|
-
const created = callIntResult("fff_watch_args", [DataType.External, DataType.String, DataType.StringArray, DataType.U32], [handle, pattern, ignore, ignore.length]);
|
|
1291
|
-
if (!created.ok)
|
|
1292
|
-
return created;
|
|
1293
|
-
// No startup race: threadsafe delivery lands on the JS event loop, so this
|
|
1294
|
-
// synchronous set always precedes the first routing lookup for this id.
|
|
1295
|
-
watchHandlers.set(created.value, callback);
|
|
1296
|
-
return created;
|
|
1297
|
-
}
|
|
1298
|
-
/**
|
|
1299
|
-
* Remove a watch subscription. Drops the JS handler synchronously — once
|
|
1300
|
-
* this returns the callback can never run again (a late native tail batch
|
|
1301
|
-
* misses the map lookup and is dropped).
|
|
1302
|
-
*/
|
|
1303
|
-
export function ffiUnwatch(handle, watchId) {
|
|
1304
|
-
const result = callBoolResult("fff_unwatch", [DataType.External, DataType.U64], [handle, watchId]);
|
|
1305
|
-
watchHandlers.delete(watchId);
|
|
1306
|
-
return result;
|
|
1307
|
-
}
|
|
1308
|
-
/**
|
|
1309
|
-
* Post-`ffiDestroy` cleanup for an instance's watch state: drops any
|
|
1310
|
-
* handlers that were never explicitly unwatched and releases the process
|
|
1311
|
-
* trampoline when this was the last watching instance.
|
|
1312
|
-
*/
|
|
1313
|
-
export function ffiWatchCleanupAfterDestroy(handle, watchIds) {
|
|
1314
|
-
for (const id of watchIds) {
|
|
1315
|
-
watchHandlers.delete(id);
|
|
1316
|
-
}
|
|
1317
|
-
watchInstances.delete(handle);
|
|
1318
|
-
releaseWatchTrampolineIfIdle();
|
|
1319
|
-
}
|
|
1320
|
-
/**
|
|
1321
|
-
* Health check.
|
|
1322
|
-
*
|
|
1323
|
-
* `handle` can be null for a limited check (version + git only).
|
|
1324
|
-
* When null, we pass DataType.U64 with value 0 as a null pointer workaround
|
|
1325
|
-
* since ffi-rs does not accept `null` for External parameters.
|
|
1326
|
-
*/
|
|
1327
|
-
export function ffiHealthCheck(handle, testPath) {
|
|
1328
|
-
if (handle === null) {
|
|
1329
|
-
// Use U64(0) as a null pointer since ffi-rs rejects null for External params
|
|
1330
|
-
return callJsonResult("fff_health_check", [DataType.U64, DataType.String], [0, testPath]);
|
|
1331
|
-
}
|
|
1332
|
-
return callJsonResult("fff_health_check", [DataType.External, DataType.String], [handle, testPath]);
|
|
1333
|
-
}
|
|
1334
|
-
/**
|
|
1335
|
-
* Ensure the library is loaded.
|
|
1336
|
-
*
|
|
1337
|
-
* Loads the native library from the platform-specific npm package
|
|
1338
|
-
* or a local dev build. Throws if the binary is not found.
|
|
1339
|
-
*/
|
|
1340
|
-
export function ensureLoaded() {
|
|
1341
|
-
loadLibrary();
|
|
1342
|
-
}
|
|
1343
|
-
/**
|
|
1344
|
-
* Check if the library is available.
|
|
1345
|
-
*/
|
|
1346
|
-
export function isAvailable() {
|
|
1347
|
-
try {
|
|
1348
|
-
loadLibrary();
|
|
1349
|
-
return true;
|
|
1350
|
-
}
|
|
1351
|
-
catch {
|
|
1352
|
-
return false;
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
/**
|
|
1356
|
-
* Close the library and release ffi-rs resources.
|
|
1357
|
-
* Call this when completely done with the library.
|
|
1358
|
-
*/
|
|
1359
|
-
export function closeLibrary() {
|
|
1360
|
-
if (isLoaded) {
|
|
1361
|
-
close(LIBRARY_KEY);
|
|
1362
|
-
isLoaded = false;
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
//# sourceMappingURL=ffi.js.map
|