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