@ff-labs/fff-node 0.2.4-nightly.d1d5d86 → 0.2.5-dev.df8b3c4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/ffi.d.ts +15 -15
- package/dist/src/ffi.d.ts.map +1 -1
- package/dist/src/ffi.js +546 -116
- package/dist/src/ffi.js.map +1 -1
- package/dist/src/finder.d.ts +9 -6
- package/dist/src/finder.d.ts.map +1 -1
- package/dist/src/finder.js +25 -41
- package/dist/src/finder.js.map +1 -1
- package/dist/src/types.d.ts +0 -75
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +0 -63
- package/dist/src/types.js.map +1 -1
- package/package.json +10 -10
package/dist/src/ffi.js
CHANGED
|
@@ -35,8 +35,23 @@
|
|
|
35
35
|
*/
|
|
36
36
|
import { close, DataType, isNullPointer, load, open, restorePointer, wrapPointer, } from "ffi-rs";
|
|
37
37
|
import { findBinary } from "./binary.js";
|
|
38
|
-
import { err } from "./types.js";
|
|
38
|
+
import { createGrepCursor, err } from "./types.js";
|
|
39
39
|
const LIBRARY_KEY = "fff_c";
|
|
40
|
+
/** Grep mode constants matching the C API (u8). */
|
|
41
|
+
const GREP_MODE_PLAIN = 0;
|
|
42
|
+
const GREP_MODE_REGEX = 1;
|
|
43
|
+
const GREP_MODE_FUZZY = 2;
|
|
44
|
+
/** Map string mode to u8 */
|
|
45
|
+
function grepModeToU8(mode) {
|
|
46
|
+
switch (mode) {
|
|
47
|
+
case "regex":
|
|
48
|
+
return GREP_MODE_REGEX;
|
|
49
|
+
case "fuzzy":
|
|
50
|
+
return GREP_MODE_FUZZY;
|
|
51
|
+
default:
|
|
52
|
+
return GREP_MODE_PLAIN;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
40
55
|
// Track whether the library is loaded
|
|
41
56
|
let isLoaded = false;
|
|
42
57
|
/**
|
|
@@ -48,9 +63,9 @@ let isLoaded = false;
|
|
|
48
63
|
*/
|
|
49
64
|
const FFF_RESULT_STRUCT = {
|
|
50
65
|
success: DataType.U8,
|
|
51
|
-
data: DataType.External,
|
|
52
66
|
error: DataType.External,
|
|
53
67
|
handle: DataType.External,
|
|
68
|
+
int_value: DataType.I64,
|
|
54
69
|
};
|
|
55
70
|
/**
|
|
56
71
|
* Load the native library using ffi-rs
|
|
@@ -145,60 +160,112 @@ function freeResult(resultPtr) {
|
|
|
145
160
|
}
|
|
146
161
|
}
|
|
147
162
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* Strategy:
|
|
152
|
-
* 1. Call the C function with External retType to get the raw pointer
|
|
153
|
-
* 2. Read struct fields via restorePointer
|
|
154
|
-
* 3. Based on success flag, read the data or error string
|
|
155
|
-
* 4. Call fff_free_result with the original raw pointer to free Rust memory
|
|
156
|
-
* 5. Parse JSON data and convert snake_case to camelCase
|
|
163
|
+
* Read the FffResult envelope from a raw call. Returns the parsed struct + raw pointer.
|
|
164
|
+
* On error, frees the result and returns a Result error.
|
|
157
165
|
*/
|
|
158
|
-
function
|
|
166
|
+
function readResultEnvelope(funcName, paramsType, paramsValue) {
|
|
159
167
|
loadLibrary();
|
|
160
168
|
const { rawPtr, struct: structData } = callRaw(funcName, paramsType, paramsValue);
|
|
161
|
-
|
|
169
|
+
if (structData.success === 0) {
|
|
170
|
+
const errorStr = readCString(structData.error);
|
|
171
|
+
freeResult(rawPtr);
|
|
172
|
+
return err(errorStr || "Unknown error");
|
|
173
|
+
}
|
|
174
|
+
return { rawPtr, struct: structData };
|
|
175
|
+
}
|
|
176
|
+
/** Call a function returning FffResult with void payload. */
|
|
177
|
+
function callVoidResult(funcName, paramsType, paramsValue) {
|
|
178
|
+
const res = readResultEnvelope(funcName, paramsType, paramsValue);
|
|
179
|
+
if ("ok" in res)
|
|
180
|
+
return res;
|
|
181
|
+
freeResult(res.rawPtr);
|
|
182
|
+
return { ok: true, value: undefined };
|
|
183
|
+
}
|
|
184
|
+
/** Call a function returning FffResult with int_value payload. */
|
|
185
|
+
function callIntResult(funcName, paramsType, paramsValue) {
|
|
186
|
+
const res = readResultEnvelope(funcName, paramsType, paramsValue);
|
|
187
|
+
if ("ok" in res)
|
|
188
|
+
return res;
|
|
189
|
+
const value = Number(res.struct.int_value);
|
|
190
|
+
freeResult(res.rawPtr);
|
|
191
|
+
return { ok: true, value };
|
|
192
|
+
}
|
|
193
|
+
/** Call a function returning FffResult with bool in int_value. */
|
|
194
|
+
function callBoolResult(funcName, paramsType, paramsValue) {
|
|
195
|
+
const res = readResultEnvelope(funcName, paramsType, paramsValue);
|
|
196
|
+
if ("ok" in res)
|
|
197
|
+
return res;
|
|
198
|
+
const value = Number(res.struct.int_value) !== 0;
|
|
199
|
+
freeResult(res.rawPtr);
|
|
200
|
+
return { ok: true, value };
|
|
201
|
+
}
|
|
202
|
+
/** Call a function returning FffResult with a C string in handle. */
|
|
203
|
+
function callStringResult(funcName, paramsType, paramsValue) {
|
|
204
|
+
const res = readResultEnvelope(funcName, paramsType, paramsValue);
|
|
205
|
+
if ("ok" in res)
|
|
206
|
+
return res;
|
|
207
|
+
const handlePtr = res.struct.handle;
|
|
208
|
+
freeResult(res.rawPtr);
|
|
209
|
+
if (isNullPointer(handlePtr))
|
|
210
|
+
return { ok: true, value: null };
|
|
211
|
+
const str = readCString(handlePtr);
|
|
212
|
+
freeString(handlePtr);
|
|
213
|
+
return { ok: true, value: str };
|
|
214
|
+
}
|
|
215
|
+
/** Call a function returning FffResult with a JSON string in handle. */
|
|
216
|
+
function callJsonResult(funcName, paramsType, paramsValue) {
|
|
217
|
+
const res = readResultEnvelope(funcName, paramsType, paramsValue);
|
|
218
|
+
if ("ok" in res)
|
|
219
|
+
return res;
|
|
220
|
+
const handlePtr = res.struct.handle;
|
|
221
|
+
freeResult(res.rawPtr);
|
|
222
|
+
if (isNullPointer(handlePtr))
|
|
223
|
+
return { ok: true, value: undefined };
|
|
224
|
+
const jsonStr = readCString(handlePtr);
|
|
225
|
+
freeString(handlePtr);
|
|
226
|
+
if (jsonStr === null || jsonStr === "")
|
|
227
|
+
return { ok: true, value: undefined };
|
|
162
228
|
try {
|
|
163
|
-
|
|
164
|
-
const dataStr = readCString(structData.data);
|
|
165
|
-
if (dataStr === null || dataStr === "") {
|
|
166
|
-
return { ok: true, value: undefined };
|
|
167
|
-
}
|
|
168
|
-
try {
|
|
169
|
-
const parsed = JSON.parse(dataStr);
|
|
170
|
-
const transformed = snakeToCamel(parsed);
|
|
171
|
-
return { ok: true, value: transformed };
|
|
172
|
-
}
|
|
173
|
-
catch {
|
|
174
|
-
// For simple values like "true" or numbers
|
|
175
|
-
return { ok: true, value: dataStr };
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
else {
|
|
179
|
-
const errorStr = readCString(structData.error);
|
|
180
|
-
return err(errorStr || "Unknown error");
|
|
181
|
-
}
|
|
229
|
+
return { ok: true, value: snakeToCamel(JSON.parse(jsonStr)) };
|
|
182
230
|
}
|
|
183
|
-
|
|
184
|
-
|
|
231
|
+
catch {
|
|
232
|
+
return { ok: true, value: jsonStr };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/** Free a C string via fff_free_string. */
|
|
236
|
+
function freeString(ptr) {
|
|
237
|
+
try {
|
|
238
|
+
load({
|
|
239
|
+
library: LIBRARY_KEY,
|
|
240
|
+
funcName: "fff_free_string",
|
|
241
|
+
retType: DataType.Void,
|
|
242
|
+
paramsType: [DataType.External],
|
|
243
|
+
paramsValue: [ptr],
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
// Ignore
|
|
185
248
|
}
|
|
186
249
|
}
|
|
187
250
|
/**
|
|
188
251
|
* Create a new file finder instance.
|
|
189
|
-
*
|
|
190
|
-
* Returns the opaque native handle on success. The handle must be passed to
|
|
191
|
-
* all subsequent FFI calls and freed with `ffiDestroy`.
|
|
192
252
|
*/
|
|
193
|
-
export function ffiCreate(
|
|
253
|
+
export function ffiCreate(basePath, frecencyDbPath, historyDbPath, useUnsafeNoLock, warmupMmapCache, aiMode) {
|
|
194
254
|
loadLibrary();
|
|
195
|
-
const { rawPtr, struct: structData } = callRaw("
|
|
255
|
+
const { rawPtr, struct: structData } = callRaw("fff_create_instance", [
|
|
256
|
+
DataType.String, // base_path
|
|
257
|
+
DataType.String, // frecency_db_path
|
|
258
|
+
DataType.String, // history_db_path
|
|
259
|
+
DataType.Boolean, // use_unsafe_no_lock
|
|
260
|
+
DataType.Boolean, // warmup_mmap_cache
|
|
261
|
+
DataType.Boolean, // ai_mode
|
|
262
|
+
], [basePath, frecencyDbPath, historyDbPath, useUnsafeNoLock, warmupMmapCache, aiMode]);
|
|
196
263
|
const success = structData.success !== 0;
|
|
197
264
|
try {
|
|
198
265
|
if (success) {
|
|
199
266
|
const handle = structData.handle;
|
|
200
267
|
if (isNullPointer(handle)) {
|
|
201
|
-
return err("
|
|
268
|
+
return err("fff_create_instance returned null handle");
|
|
202
269
|
}
|
|
203
270
|
return { ok: true, value: handle };
|
|
204
271
|
}
|
|
@@ -224,17 +291,416 @@ export function ffiDestroy(handle) {
|
|
|
224
291
|
paramsValue: [handle],
|
|
225
292
|
});
|
|
226
293
|
}
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
// Struct type definitions for restorePointer (must match #[repr(C)] layout)
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
const FFF_FILE_ITEM_STRUCT = {
|
|
298
|
+
path: DataType.External,
|
|
299
|
+
relative_path: DataType.External,
|
|
300
|
+
file_name: DataType.External,
|
|
301
|
+
git_status: DataType.External,
|
|
302
|
+
size: DataType.U64,
|
|
303
|
+
modified: DataType.U64,
|
|
304
|
+
access_frecency_score: DataType.I64,
|
|
305
|
+
modification_frecency_score: DataType.I64,
|
|
306
|
+
total_frecency_score: DataType.I64,
|
|
307
|
+
is_binary: DataType.U8,
|
|
308
|
+
};
|
|
309
|
+
const FFF_SCORE_STRUCT = {
|
|
310
|
+
total: DataType.I32,
|
|
311
|
+
base_score: DataType.I32,
|
|
312
|
+
filename_bonus: DataType.I32,
|
|
313
|
+
special_filename_bonus: DataType.I32,
|
|
314
|
+
frecency_boost: DataType.I32,
|
|
315
|
+
distance_penalty: DataType.I32,
|
|
316
|
+
current_file_penalty: DataType.I32,
|
|
317
|
+
combo_match_boost: DataType.I32,
|
|
318
|
+
exact_match: DataType.U8,
|
|
319
|
+
match_type: DataType.External,
|
|
320
|
+
};
|
|
321
|
+
const FFF_SEARCH_RESULT_STRUCT = {
|
|
322
|
+
items: DataType.External,
|
|
323
|
+
scores: DataType.External,
|
|
324
|
+
count: DataType.U32,
|
|
325
|
+
total_matched: DataType.U32,
|
|
326
|
+
total_files: DataType.U32,
|
|
327
|
+
// FffLocation inlined (flattened)
|
|
328
|
+
location_tag: DataType.U8,
|
|
329
|
+
location_line: DataType.I32,
|
|
330
|
+
location_col: DataType.I32,
|
|
331
|
+
location_end_line: DataType.I32,
|
|
332
|
+
location_end_col: DataType.I32,
|
|
333
|
+
};
|
|
334
|
+
// FffGrepMatch (144 bytes) — ordered by alignment: ptrs, u64s, u32s, u16, bools
|
|
335
|
+
const FFF_GREP_MATCH_STRUCT = {
|
|
336
|
+
path: DataType.External,
|
|
337
|
+
relative_path: DataType.External,
|
|
338
|
+
file_name: DataType.External,
|
|
339
|
+
git_status: DataType.External,
|
|
340
|
+
line_content: DataType.External,
|
|
341
|
+
match_ranges: DataType.External,
|
|
342
|
+
context_before: DataType.External,
|
|
343
|
+
context_after: DataType.External,
|
|
344
|
+
size: DataType.U64,
|
|
345
|
+
modified: DataType.U64,
|
|
346
|
+
total_frecency_score: DataType.I64,
|
|
347
|
+
access_frecency_score: DataType.I64,
|
|
348
|
+
modification_frecency_score: DataType.I64,
|
|
349
|
+
line_number: DataType.U64,
|
|
350
|
+
byte_offset: DataType.U64,
|
|
351
|
+
col: DataType.U32,
|
|
352
|
+
match_ranges_count: DataType.U32,
|
|
353
|
+
context_before_count: DataType.U32,
|
|
354
|
+
context_after_count: DataType.U32,
|
|
355
|
+
fuzzy_score: DataType.U32, // actually u16 in C, but ffi-rs doesn't have U16 — reads as u32 with padding
|
|
356
|
+
has_fuzzy_score: DataType.U8,
|
|
357
|
+
is_binary: DataType.U8,
|
|
358
|
+
is_definition: DataType.U8,
|
|
359
|
+
};
|
|
360
|
+
const FFF_GREP_RESULT_STRUCT = {
|
|
361
|
+
items: DataType.External,
|
|
362
|
+
count: DataType.U32,
|
|
363
|
+
total_matched: DataType.U32,
|
|
364
|
+
total_files_searched: DataType.U32,
|
|
365
|
+
total_files: DataType.U32,
|
|
366
|
+
filtered_file_count: DataType.U32,
|
|
367
|
+
next_file_offset: DataType.U32,
|
|
368
|
+
regex_fallback_error: DataType.External,
|
|
369
|
+
};
|
|
370
|
+
const FFF_MATCH_RANGE_STRUCT = {
|
|
371
|
+
start: DataType.U32,
|
|
372
|
+
end: DataType.U32,
|
|
373
|
+
};
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
375
|
+
// Struct reading helpers
|
|
376
|
+
// ---------------------------------------------------------------------------
|
|
377
|
+
function readFileItemFromRaw(raw) {
|
|
378
|
+
return {
|
|
379
|
+
path: readCString(raw.path) ?? "",
|
|
380
|
+
relativePath: readCString(raw.relative_path) ?? "",
|
|
381
|
+
fileName: readCString(raw.file_name) ?? "",
|
|
382
|
+
gitStatus: readCString(raw.git_status) ?? "",
|
|
383
|
+
size: Number(raw.size),
|
|
384
|
+
modified: Number(raw.modified),
|
|
385
|
+
accessFrecencyScore: Number(raw.access_frecency_score),
|
|
386
|
+
modificationFrecencyScore: Number(raw.modification_frecency_score),
|
|
387
|
+
totalFrecencyScore: Number(raw.total_frecency_score),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function readScoreFromRaw(raw) {
|
|
391
|
+
return {
|
|
392
|
+
total: raw.total,
|
|
393
|
+
baseScore: raw.base_score,
|
|
394
|
+
filenameBonus: raw.filename_bonus,
|
|
395
|
+
specialFilenameBonus: raw.special_filename_bonus,
|
|
396
|
+
frecencyBoost: raw.frecency_boost,
|
|
397
|
+
distancePenalty: raw.distance_penalty,
|
|
398
|
+
currentFilePenalty: raw.current_file_penalty,
|
|
399
|
+
comboMatchBoost: raw.combo_match_boost,
|
|
400
|
+
exactMatch: raw.exact_match !== 0,
|
|
401
|
+
matchType: readCString(raw.match_type) ?? "",
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Call an accessor function that returns a pointer to a struct element,
|
|
406
|
+
* then read the struct from that pointer.
|
|
407
|
+
*/
|
|
408
|
+
function callAccessor(funcName, resultPtr, index, structDef) {
|
|
409
|
+
loadLibrary();
|
|
410
|
+
const elemPtr = load({
|
|
411
|
+
library: LIBRARY_KEY,
|
|
412
|
+
funcName,
|
|
413
|
+
retType: DataType.External,
|
|
414
|
+
paramsType: [DataType.External, DataType.U32],
|
|
415
|
+
paramsValue: [resultPtr, index],
|
|
416
|
+
});
|
|
417
|
+
const [raw] = restorePointer({
|
|
418
|
+
retType: [structDef],
|
|
419
|
+
paramsValue: wrapPointer([elemPtr]),
|
|
420
|
+
});
|
|
421
|
+
return raw;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Offset a pointer by `bytes` using the C API helper.
|
|
425
|
+
*/
|
|
426
|
+
function ptrOffset(base, bytes) {
|
|
427
|
+
return load({
|
|
428
|
+
library: LIBRARY_KEY,
|
|
429
|
+
funcName: "fff_ptr_offset",
|
|
430
|
+
retType: DataType.External,
|
|
431
|
+
paramsType: [DataType.External, DataType.U64],
|
|
432
|
+
paramsValue: [base, bytes],
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Read a C string array (char**) of `count` elements.
|
|
437
|
+
*/
|
|
438
|
+
function readCStringArray(ptrArray, count) {
|
|
439
|
+
if (count === 0 || isNullPointer(ptrArray))
|
|
440
|
+
return [];
|
|
441
|
+
const result = [];
|
|
442
|
+
for (let i = 0; i < count; i++) {
|
|
443
|
+
const elemPtr = ptrOffset(ptrArray, i * 8);
|
|
444
|
+
const [charPtr] = restorePointer({
|
|
445
|
+
retType: [DataType.External],
|
|
446
|
+
paramsValue: wrapPointer([elemPtr]),
|
|
447
|
+
});
|
|
448
|
+
result.push(readCString(charPtr) ?? "");
|
|
449
|
+
}
|
|
450
|
+
return result;
|
|
451
|
+
}
|
|
452
|
+
function readGrepMatchFromRaw(raw) {
|
|
453
|
+
// Read match_ranges array via pointer offsets
|
|
454
|
+
const matchRanges = [];
|
|
455
|
+
for (let i = 0; i < raw.match_ranges_count; i++) {
|
|
456
|
+
const rangePtr = ptrOffset(raw.match_ranges, i * 8); // FffMatchRange is 8 bytes
|
|
457
|
+
const [rangeRaw] = restorePointer({
|
|
458
|
+
retType: [FFF_MATCH_RANGE_STRUCT],
|
|
459
|
+
paramsValue: wrapPointer([rangePtr]),
|
|
460
|
+
});
|
|
461
|
+
matchRanges.push([rangeRaw.start, rangeRaw.end]);
|
|
462
|
+
}
|
|
463
|
+
const match = {
|
|
464
|
+
path: readCString(raw.path) ?? "",
|
|
465
|
+
relativePath: readCString(raw.relative_path) ?? "",
|
|
466
|
+
fileName: readCString(raw.file_name) ?? "",
|
|
467
|
+
gitStatus: readCString(raw.git_status) ?? "",
|
|
468
|
+
lineContent: readCString(raw.line_content) ?? "",
|
|
469
|
+
size: Number(raw.size),
|
|
470
|
+
modified: Number(raw.modified),
|
|
471
|
+
totalFrecencyScore: Number(raw.total_frecency_score),
|
|
472
|
+
accessFrecencyScore: Number(raw.access_frecency_score),
|
|
473
|
+
modificationFrecencyScore: Number(raw.modification_frecency_score),
|
|
474
|
+
isBinary: raw.is_binary !== 0,
|
|
475
|
+
lineNumber: Number(raw.line_number),
|
|
476
|
+
col: raw.col,
|
|
477
|
+
byteOffset: Number(raw.byte_offset),
|
|
478
|
+
matchRanges,
|
|
479
|
+
};
|
|
480
|
+
if (raw.has_fuzzy_score !== 0) {
|
|
481
|
+
match.fuzzyScore = raw.fuzzy_score;
|
|
482
|
+
}
|
|
483
|
+
if (raw.context_before_count > 0) {
|
|
484
|
+
match.contextBefore = readCStringArray(raw.context_before, raw.context_before_count);
|
|
485
|
+
}
|
|
486
|
+
if (raw.context_after_count > 0) {
|
|
487
|
+
match.contextAfter = readCStringArray(raw.context_after, raw.context_after_count);
|
|
488
|
+
}
|
|
489
|
+
return match;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Parse an FffGrepResult from `FffResult.handle`, then free native memory.
|
|
493
|
+
*/
|
|
494
|
+
function parseGrepResult(rawPtr) {
|
|
495
|
+
loadLibrary();
|
|
496
|
+
const [envelope] = restorePointer({
|
|
497
|
+
retType: [FFF_RESULT_STRUCT],
|
|
498
|
+
paramsValue: wrapPointer([rawPtr]),
|
|
499
|
+
});
|
|
500
|
+
const success = envelope.success !== 0;
|
|
501
|
+
if (!success) {
|
|
502
|
+
const errorMsg = readCString(envelope.error) || "Unknown error";
|
|
503
|
+
freeResult(rawPtr);
|
|
504
|
+
return err(errorMsg);
|
|
505
|
+
}
|
|
506
|
+
const handlePtr = envelope.handle;
|
|
507
|
+
freeResult(rawPtr);
|
|
508
|
+
if (isNullPointer(handlePtr)) {
|
|
509
|
+
return err("grep returned null result");
|
|
510
|
+
}
|
|
511
|
+
const [gr] = restorePointer({
|
|
512
|
+
retType: [FFF_GREP_RESULT_STRUCT],
|
|
513
|
+
paramsValue: wrapPointer([handlePtr]),
|
|
514
|
+
});
|
|
515
|
+
const count = gr.count;
|
|
516
|
+
const regexFallbackError = readCString(gr.regex_fallback_error) ?? undefined;
|
|
517
|
+
const items = [];
|
|
518
|
+
for (let i = 0; i < count; i++) {
|
|
519
|
+
const rawMatch = callAccessor("fff_grep_result_get_match", handlePtr, i, FFF_GREP_MATCH_STRUCT);
|
|
520
|
+
items.push(readGrepMatchFromRaw(rawMatch));
|
|
521
|
+
}
|
|
522
|
+
// Free native grep result
|
|
523
|
+
load({
|
|
524
|
+
library: LIBRARY_KEY,
|
|
525
|
+
funcName: "fff_free_grep_result",
|
|
526
|
+
retType: DataType.Void,
|
|
527
|
+
paramsType: [DataType.External],
|
|
528
|
+
paramsValue: [handlePtr],
|
|
529
|
+
});
|
|
530
|
+
const grepResult = {
|
|
531
|
+
items,
|
|
532
|
+
totalMatched: gr.total_matched,
|
|
533
|
+
totalFilesSearched: gr.total_files_searched,
|
|
534
|
+
totalFiles: gr.total_files,
|
|
535
|
+
filteredFileCount: gr.filtered_file_count,
|
|
536
|
+
nextCursor: gr.next_file_offset > 0 ? createGrepCursor(gr.next_file_offset) : null,
|
|
537
|
+
};
|
|
538
|
+
if (regexFallbackError) {
|
|
539
|
+
grepResult.regexFallbackError = regexFallbackError;
|
|
540
|
+
}
|
|
541
|
+
return { ok: true, value: grepResult };
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Parse an FffSearchResult from `FffResult.handle`, then free native memory.
|
|
545
|
+
*/
|
|
546
|
+
function parseSearchResult(rawPtr) {
|
|
547
|
+
loadLibrary();
|
|
548
|
+
// Read FffResult envelope
|
|
549
|
+
const [envelope] = restorePointer({
|
|
550
|
+
retType: [FFF_RESULT_STRUCT],
|
|
551
|
+
paramsValue: wrapPointer([rawPtr]),
|
|
552
|
+
});
|
|
553
|
+
const success = envelope.success !== 0;
|
|
554
|
+
if (!success) {
|
|
555
|
+
const errorMsg = readCString(envelope.error) || "Unknown error";
|
|
556
|
+
freeResult(rawPtr);
|
|
557
|
+
return err(errorMsg);
|
|
558
|
+
}
|
|
559
|
+
const handlePtr = envelope.handle;
|
|
560
|
+
// Free the FffResult envelope (does NOT free handle)
|
|
561
|
+
freeResult(rawPtr);
|
|
562
|
+
if (isNullPointer(handlePtr)) {
|
|
563
|
+
return err("fff_search returned null search result");
|
|
564
|
+
}
|
|
565
|
+
// Read FffSearchResult struct
|
|
566
|
+
const [sr] = restorePointer({
|
|
567
|
+
retType: [FFF_SEARCH_RESULT_STRUCT],
|
|
568
|
+
paramsValue: wrapPointer([handlePtr]),
|
|
569
|
+
});
|
|
570
|
+
const count = sr.count;
|
|
571
|
+
// Read location
|
|
572
|
+
let location;
|
|
573
|
+
if (sr.location_tag === 1) {
|
|
574
|
+
location = { type: "line", line: sr.location_line };
|
|
575
|
+
}
|
|
576
|
+
else if (sr.location_tag === 2) {
|
|
577
|
+
location = { type: "position", line: sr.location_line, col: sr.location_col };
|
|
578
|
+
}
|
|
579
|
+
else if (sr.location_tag === 3) {
|
|
580
|
+
location = {
|
|
581
|
+
type: "range",
|
|
582
|
+
start: { line: sr.location_line, col: sr.location_col },
|
|
583
|
+
end: { line: sr.location_end_line, col: sr.location_end_col },
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
// Read items and scores via accessor functions
|
|
587
|
+
const items = [];
|
|
588
|
+
const scores = [];
|
|
589
|
+
for (let i = 0; i < count; i++) {
|
|
590
|
+
const rawItem = callAccessor("fff_search_result_get_item", handlePtr, i, FFF_FILE_ITEM_STRUCT);
|
|
591
|
+
items.push(readFileItemFromRaw(rawItem));
|
|
592
|
+
const rawScore = callAccessor("fff_search_result_get_score", handlePtr, i, FFF_SCORE_STRUCT);
|
|
593
|
+
scores.push(readScoreFromRaw(rawScore));
|
|
594
|
+
}
|
|
595
|
+
// Free native search result
|
|
596
|
+
load({
|
|
597
|
+
library: LIBRARY_KEY,
|
|
598
|
+
funcName: "fff_free_search_result",
|
|
599
|
+
retType: DataType.Void,
|
|
600
|
+
paramsType: [DataType.External],
|
|
601
|
+
paramsValue: [handlePtr],
|
|
602
|
+
});
|
|
603
|
+
const result = { items, scores, totalMatched: sr.total_matched, totalFiles: sr.total_files };
|
|
604
|
+
if (location) {
|
|
605
|
+
result.location = location;
|
|
606
|
+
}
|
|
607
|
+
return { ok: true, value: result };
|
|
608
|
+
}
|
|
227
609
|
/**
|
|
228
610
|
* Perform fuzzy search.
|
|
229
611
|
*/
|
|
230
|
-
export function ffiSearch(handle, query,
|
|
231
|
-
|
|
612
|
+
export function ffiSearch(handle, query, currentFile, maxThreads, pageIndex, pageSize, comboBoostMultiplier, minComboCount) {
|
|
613
|
+
loadLibrary();
|
|
614
|
+
const rawPtr = load({
|
|
615
|
+
library: LIBRARY_KEY,
|
|
616
|
+
funcName: "fff_search",
|
|
617
|
+
retType: DataType.External,
|
|
618
|
+
paramsType: [
|
|
619
|
+
DataType.External, // handle
|
|
620
|
+
DataType.String, // query
|
|
621
|
+
DataType.String, // current_file
|
|
622
|
+
DataType.U32, // max_threads
|
|
623
|
+
DataType.U32, // page_index
|
|
624
|
+
DataType.U32, // page_size
|
|
625
|
+
DataType.I32, // combo_boost_multiplier
|
|
626
|
+
DataType.U32, // min_combo_count
|
|
627
|
+
],
|
|
628
|
+
paramsValue: [handle, query, currentFile, maxThreads, pageIndex, pageSize, comboBoostMultiplier, minComboCount],
|
|
629
|
+
freeResultMemory: false,
|
|
630
|
+
});
|
|
631
|
+
return parseSearchResult(rawPtr);
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Live grep - search file contents.
|
|
635
|
+
*/
|
|
636
|
+
export function ffiLiveGrep(handle, query, mode, maxFileSize, maxMatchesPerFile, smartCase, fileOffset, pageLimit, timeBudgetMs, beforeContext, afterContext, classifyDefinitions) {
|
|
637
|
+
loadLibrary();
|
|
638
|
+
const rawPtr = load({
|
|
639
|
+
library: LIBRARY_KEY,
|
|
640
|
+
funcName: "fff_live_grep",
|
|
641
|
+
retType: DataType.External,
|
|
642
|
+
paramsType: [
|
|
643
|
+
DataType.External, // handle
|
|
644
|
+
DataType.String, // query
|
|
645
|
+
DataType.U8, // mode
|
|
646
|
+
DataType.U64, // max_file_size
|
|
647
|
+
DataType.U32, // max_matches_per_file
|
|
648
|
+
DataType.Boolean, // smart_case
|
|
649
|
+
DataType.U32, // file_offset
|
|
650
|
+
DataType.U32, // page_limit
|
|
651
|
+
DataType.U64, // time_budget_ms
|
|
652
|
+
DataType.U32, // before_context
|
|
653
|
+
DataType.U32, // after_context
|
|
654
|
+
DataType.Boolean, // classify_definitions
|
|
655
|
+
],
|
|
656
|
+
paramsValue: [
|
|
657
|
+
handle, query, grepModeToU8(mode),
|
|
658
|
+
maxFileSize, maxMatchesPerFile, smartCase,
|
|
659
|
+
fileOffset, pageLimit, timeBudgetMs,
|
|
660
|
+
beforeContext, afterContext, classifyDefinitions,
|
|
661
|
+
],
|
|
662
|
+
freeResultMemory: false,
|
|
663
|
+
});
|
|
664
|
+
return parseGrepResult(rawPtr);
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Multi-pattern grep - Aho-Corasick multi-needle search.
|
|
668
|
+
*/
|
|
669
|
+
export function ffiMultiGrep(handle, patternsJoined, constraints, maxFileSize, maxMatchesPerFile, smartCase, fileOffset, pageLimit, timeBudgetMs, beforeContext, afterContext, classifyDefinitions) {
|
|
670
|
+
loadLibrary();
|
|
671
|
+
const rawPtr = load({
|
|
672
|
+
library: LIBRARY_KEY,
|
|
673
|
+
funcName: "fff_multi_grep",
|
|
674
|
+
retType: DataType.External,
|
|
675
|
+
paramsType: [
|
|
676
|
+
DataType.External, // handle
|
|
677
|
+
DataType.String, // patterns_joined
|
|
678
|
+
DataType.String, // constraints
|
|
679
|
+
DataType.U64, // max_file_size
|
|
680
|
+
DataType.U32, // max_matches_per_file
|
|
681
|
+
DataType.Boolean, // smart_case
|
|
682
|
+
DataType.U32, // file_offset
|
|
683
|
+
DataType.U32, // page_limit
|
|
684
|
+
DataType.U64, // time_budget_ms
|
|
685
|
+
DataType.U32, // before_context
|
|
686
|
+
DataType.U32, // after_context
|
|
687
|
+
DataType.Boolean, // classify_definitions
|
|
688
|
+
],
|
|
689
|
+
paramsValue: [
|
|
690
|
+
handle, patternsJoined, constraints,
|
|
691
|
+
maxFileSize, maxMatchesPerFile, smartCase,
|
|
692
|
+
fileOffset, pageLimit, timeBudgetMs,
|
|
693
|
+
beforeContext, afterContext, classifyDefinitions,
|
|
694
|
+
],
|
|
695
|
+
freeResultMemory: false,
|
|
696
|
+
});
|
|
697
|
+
return parseGrepResult(rawPtr);
|
|
232
698
|
}
|
|
233
699
|
/**
|
|
234
700
|
* Trigger file scan.
|
|
235
701
|
*/
|
|
236
702
|
export function ffiScanFiles(handle) {
|
|
237
|
-
return
|
|
703
|
+
return callVoidResult("fff_scan_files", [DataType.External], [handle]);
|
|
238
704
|
}
|
|
239
705
|
/**
|
|
240
706
|
* Check if scanning.
|
|
@@ -249,60 +715,70 @@ export function ffiIsScanning(handle) {
|
|
|
249
715
|
paramsValue: [handle],
|
|
250
716
|
});
|
|
251
717
|
}
|
|
718
|
+
// FffScanProgress struct definition
|
|
719
|
+
const FFF_SCAN_PROGRESS_STRUCT = {
|
|
720
|
+
scanned_files_count: DataType.U64,
|
|
721
|
+
is_scanning: DataType.U8,
|
|
722
|
+
};
|
|
252
723
|
/**
|
|
253
724
|
* Get scan progress.
|
|
254
725
|
*/
|
|
255
726
|
export function ffiGetScanProgress(handle) {
|
|
256
|
-
|
|
727
|
+
loadLibrary();
|
|
728
|
+
const res = readResultEnvelope("fff_get_scan_progress", [DataType.External], [handle]);
|
|
729
|
+
if ("ok" in res)
|
|
730
|
+
return res;
|
|
731
|
+
const handlePtr = res.struct.handle;
|
|
732
|
+
freeResult(res.rawPtr);
|
|
733
|
+
if (isNullPointer(handlePtr))
|
|
734
|
+
return err("scan progress returned null");
|
|
735
|
+
const [sp] = restorePointer({
|
|
736
|
+
retType: [FFF_SCAN_PROGRESS_STRUCT],
|
|
737
|
+
paramsValue: wrapPointer([handlePtr]),
|
|
738
|
+
});
|
|
739
|
+
const result = {
|
|
740
|
+
scannedFilesCount: Number(sp.scanned_files_count),
|
|
741
|
+
isScanning: sp.is_scanning !== 0,
|
|
742
|
+
};
|
|
743
|
+
// Free native scan progress
|
|
744
|
+
load({
|
|
745
|
+
library: LIBRARY_KEY,
|
|
746
|
+
funcName: "fff_free_scan_progress",
|
|
747
|
+
retType: DataType.Void,
|
|
748
|
+
paramsType: [DataType.External],
|
|
749
|
+
paramsValue: [handlePtr],
|
|
750
|
+
});
|
|
751
|
+
return { ok: true, value: result };
|
|
257
752
|
}
|
|
258
753
|
/**
|
|
259
754
|
* Wait for a tree scan to complete.
|
|
260
755
|
*/
|
|
261
756
|
export function ffiWaitForScan(handle, timeoutMs) {
|
|
262
|
-
|
|
263
|
-
if (!result.ok)
|
|
264
|
-
return result;
|
|
265
|
-
return { ok: true, value: result.value === true || result.value === "true" };
|
|
757
|
+
return callBoolResult("fff_wait_for_scan", [DataType.External, DataType.U64], [handle, timeoutMs]);
|
|
266
758
|
}
|
|
267
759
|
/**
|
|
268
760
|
* Restart index in new path.
|
|
269
761
|
*/
|
|
270
762
|
export function ffiRestartIndex(handle, newPath) {
|
|
271
|
-
return
|
|
763
|
+
return callVoidResult("fff_restart_index", [DataType.External, DataType.String], [handle, newPath]);
|
|
272
764
|
}
|
|
273
765
|
/**
|
|
274
766
|
* Refresh git status.
|
|
275
767
|
*/
|
|
276
768
|
export function ffiRefreshGitStatus(handle) {
|
|
277
|
-
|
|
278
|
-
if (!result.ok)
|
|
279
|
-
return result;
|
|
280
|
-
return {
|
|
281
|
-
ok: true,
|
|
282
|
-
value: typeof result.value === "number"
|
|
283
|
-
? result.value
|
|
284
|
-
: parseInt(result.value, 10),
|
|
285
|
-
};
|
|
769
|
+
return callIntResult("fff_refresh_git_status", [DataType.External], [handle]);
|
|
286
770
|
}
|
|
287
771
|
/**
|
|
288
772
|
* Track query completion.
|
|
289
773
|
*/
|
|
290
774
|
export function ffiTrackQuery(handle, query, filePath) {
|
|
291
|
-
|
|
292
|
-
if (!result.ok)
|
|
293
|
-
return result;
|
|
294
|
-
return { ok: true, value: result.value === true || result.value === "true" };
|
|
775
|
+
return callBoolResult("fff_track_query", [DataType.External, DataType.String, DataType.String], [handle, query, filePath]);
|
|
295
776
|
}
|
|
296
777
|
/**
|
|
297
778
|
* Get historical query.
|
|
298
779
|
*/
|
|
299
780
|
export function ffiGetHistoricalQuery(handle, offset) {
|
|
300
|
-
|
|
301
|
-
if (!result.ok)
|
|
302
|
-
return result;
|
|
303
|
-
if (result.value === null || result.value === "null")
|
|
304
|
-
return { ok: true, value: null };
|
|
305
|
-
return result;
|
|
781
|
+
return callStringResult("fff_get_historical_query", [DataType.External, DataType.U64], [handle, offset]);
|
|
306
782
|
}
|
|
307
783
|
/**
|
|
308
784
|
* Health check.
|
|
@@ -312,57 +788,11 @@ export function ffiGetHistoricalQuery(handle, offset) {
|
|
|
312
788
|
* since ffi-rs does not accept `null` for External parameters.
|
|
313
789
|
*/
|
|
314
790
|
export function ffiHealthCheck(handle, testPath) {
|
|
315
|
-
loadLibrary();
|
|
316
791
|
if (handle === null) {
|
|
317
792
|
// Use U64(0) as a null pointer since ffi-rs rejects null for External params
|
|
318
|
-
|
|
319
|
-
library: LIBRARY_KEY,
|
|
320
|
-
funcName: "fff_health_check",
|
|
321
|
-
retType: DataType.External,
|
|
322
|
-
paramsType: [DataType.U64, DataType.String],
|
|
323
|
-
paramsValue: [0, testPath],
|
|
324
|
-
freeResultMemory: false,
|
|
325
|
-
});
|
|
326
|
-
const [structData] = restorePointer({
|
|
327
|
-
retType: [FFF_RESULT_STRUCT],
|
|
328
|
-
paramsValue: wrapPointer([rawPtr]),
|
|
329
|
-
});
|
|
330
|
-
const success = structData.success !== 0;
|
|
331
|
-
try {
|
|
332
|
-
if (success) {
|
|
333
|
-
const dataStr = readCString(structData.data);
|
|
334
|
-
if (dataStr === null || dataStr === "") {
|
|
335
|
-
return { ok: true, value: undefined };
|
|
336
|
-
}
|
|
337
|
-
try {
|
|
338
|
-
return { ok: true, value: snakeToCamel(JSON.parse(dataStr)) };
|
|
339
|
-
}
|
|
340
|
-
catch {
|
|
341
|
-
return { ok: true, value: dataStr };
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
else {
|
|
345
|
-
const errorStr = readCString(structData.error);
|
|
346
|
-
return err(errorStr || "Unknown error");
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
finally {
|
|
350
|
-
freeResult(rawPtr);
|
|
351
|
-
}
|
|
793
|
+
return callJsonResult("fff_health_check", [DataType.U64, DataType.String], [0, testPath]);
|
|
352
794
|
}
|
|
353
|
-
return
|
|
354
|
-
}
|
|
355
|
-
/**
|
|
356
|
-
* Live grep - search file contents.
|
|
357
|
-
*/
|
|
358
|
-
export function ffiLiveGrep(handle, query, optsJson) {
|
|
359
|
-
return callFfiResult("fff_live_grep", [DataType.External, DataType.String, DataType.String], [handle, query, optsJson]);
|
|
360
|
-
}
|
|
361
|
-
/**
|
|
362
|
-
* Multi-pattern grep - Aho-Corasick multi-needle search.
|
|
363
|
-
*/
|
|
364
|
-
export function ffiMultiGrep(handle, optsJson) {
|
|
365
|
-
return callFfiResult("fff_multi_grep", [DataType.External, DataType.String], [handle, optsJson]);
|
|
795
|
+
return callJsonResult("fff_health_check", [DataType.External, DataType.String], [handle, testPath]);
|
|
366
796
|
}
|
|
367
797
|
/**
|
|
368
798
|
* Ensure the library is loaded.
|