diffwiki-ripgrep 0.14.0 → 0.15.0-rc.202608040914.07e70ce
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/bin.js +129 -60
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/bin.ts
|
|
4
|
-
import { definePlugin } from "diffwiki-core/plugin";
|
|
4
|
+
import { definePlugin, ensureTool } from "diffwiki-core/plugin";
|
|
5
5
|
|
|
6
6
|
// src/ripgrep.ts
|
|
7
7
|
import { spawn } from "child_process";
|
|
@@ -22,6 +22,22 @@ var spawnRg = (args) => new Promise((resolve, reject) => {
|
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
});
|
|
25
|
+
var spawnFzf = (args, input) => new Promise((resolve, reject) => {
|
|
26
|
+
const chunks = [];
|
|
27
|
+
const proc = spawn("fzf", args, { stdio: ["pipe", "pipe", "ignore"] });
|
|
28
|
+
proc.stdout.on("data", (chunk) => chunks.push(chunk));
|
|
29
|
+
proc.on("error", reject);
|
|
30
|
+
proc.on("close", (code) => {
|
|
31
|
+
if (code === 0 || code === 1) {
|
|
32
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
33
|
+
} else {
|
|
34
|
+
reject(new Error(`fzf exited with code ${code}`));
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
proc.stdin.on("error", () => {
|
|
38
|
+
});
|
|
39
|
+
proc.stdin.end(input);
|
|
40
|
+
});
|
|
25
41
|
var readFileLines = (absPath) => new Promise((resolve, reject) => {
|
|
26
42
|
const lines = [];
|
|
27
43
|
const stream = createReadStream(absPath, { encoding: "utf8" });
|
|
@@ -37,26 +53,61 @@ var readFileLines = (absPath) => new Promise((resolve, reject) => {
|
|
|
37
53
|
rl.on("error", reject);
|
|
38
54
|
stream.on("error", reject);
|
|
39
55
|
});
|
|
40
|
-
async function
|
|
41
|
-
if (
|
|
56
|
+
async function toolOnPath(name, probe) {
|
|
57
|
+
if (probe) {
|
|
42
58
|
try {
|
|
43
|
-
await
|
|
59
|
+
await probe();
|
|
44
60
|
return true;
|
|
45
61
|
} catch {
|
|
46
62
|
return false;
|
|
47
63
|
}
|
|
48
64
|
}
|
|
49
|
-
return commandExists(
|
|
65
|
+
return commandExists(name);
|
|
66
|
+
}
|
|
67
|
+
async function rgOnPath(spawnFn = spawnRg) {
|
|
68
|
+
return toolOnPath("rg", spawnFn === spawnRg ? null : () => spawnFn(["--version"]));
|
|
69
|
+
}
|
|
70
|
+
async function fzfOnPath(fzfFn = spawnFzf) {
|
|
71
|
+
return toolOnPath("fzf", fzfFn === spawnFzf ? null : () => fzfFn(["--version"], ""));
|
|
50
72
|
}
|
|
51
|
-
|
|
73
|
+
function tokenize(query) {
|
|
74
|
+
return query.toLowerCase().match(/[\p{L}\p{N}_]+/gu) ?? [];
|
|
75
|
+
}
|
|
76
|
+
function escapeRegex(token) {
|
|
77
|
+
return token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
78
|
+
}
|
|
79
|
+
async function runRg(spawnFn, query, roots, opts) {
|
|
80
|
+
const tokens = tokenize(query);
|
|
81
|
+
if (tokens.length === 0 || roots.length === 0) return "";
|
|
82
|
+
const pattern = tokens.map(escapeRegex).join("|");
|
|
52
83
|
const limit = opts.limit > 0 ? opts.limit : 20;
|
|
53
|
-
const args = [
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
84
|
+
const args = [
|
|
85
|
+
"--line-number",
|
|
86
|
+
"--no-heading",
|
|
87
|
+
"--with-filename",
|
|
88
|
+
"--color",
|
|
89
|
+
"never",
|
|
90
|
+
"--smart-case",
|
|
91
|
+
"--max-count",
|
|
92
|
+
String(limit),
|
|
93
|
+
"--glob",
|
|
94
|
+
"*.{md,mdx}",
|
|
95
|
+
"-e",
|
|
96
|
+
pattern,
|
|
97
|
+
"--",
|
|
98
|
+
...roots
|
|
99
|
+
];
|
|
58
100
|
return spawnFn(args);
|
|
59
101
|
}
|
|
102
|
+
async function fuzzyRank(fzfFn, candidates, query) {
|
|
103
|
+
if (candidates.trim().length === 0) return "";
|
|
104
|
+
return fzfFn(["--filter", query], candidates);
|
|
105
|
+
}
|
|
106
|
+
function parseRgLine(line) {
|
|
107
|
+
const m = /^(.*?):(\d+):(.*)$/.exec(line);
|
|
108
|
+
if (!m) return null;
|
|
109
|
+
return { path: m[1], lineNumber: Number(m[2]), text: m[3] };
|
|
110
|
+
}
|
|
60
111
|
async function cheapTitle(absPath, readFn = readFileLines) {
|
|
61
112
|
let lines;
|
|
62
113
|
try {
|
|
@@ -80,7 +131,9 @@ async function cheapTitle(absPath, readFn = readFileLines) {
|
|
|
80
131
|
}
|
|
81
132
|
|
|
82
133
|
// src/bin.ts
|
|
83
|
-
var VERSION = "0.
|
|
134
|
+
var VERSION = "0.4.0";
|
|
135
|
+
var RG_INSTALL = "https://github.com/BurntSushi/ripgrep";
|
|
136
|
+
var FZF_INSTALL = "https://github.com/junegunn/fzf";
|
|
84
137
|
function asCollections(raw) {
|
|
85
138
|
if (!Array.isArray(raw)) return [];
|
|
86
139
|
return raw.filter(
|
|
@@ -100,27 +153,19 @@ function matchCollection(filePath, collections) {
|
|
|
100
153
|
}
|
|
101
154
|
return null;
|
|
102
155
|
}
|
|
103
|
-
function parseRgOutput(stdout) {
|
|
104
|
-
return stdout.split("\n").filter((line) => line.trim().length > 0).flatMap((line) => {
|
|
105
|
-
try {
|
|
106
|
-
const obj = JSON.parse(line);
|
|
107
|
-
return obj.type === "match" ? [obj] : [];
|
|
108
|
-
} catch {
|
|
109
|
-
return [];
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
156
|
function makePluginDefinition(deps = {}) {
|
|
114
157
|
const spawnFn = deps.spawnFn ?? spawnRg;
|
|
158
|
+
const fzfFn = deps.fzfFn ?? spawnFzf;
|
|
115
159
|
const readFn = deps.readFn ?? readFileLines;
|
|
160
|
+
const installFn = deps.installFn ?? ensureTool;
|
|
161
|
+
const RG_SPEC = { command: "rg", mise: "ripgrep", brew: "ripgrep", apt: "ripgrep", manual: RG_INSTALL };
|
|
162
|
+
const FZF_SPEC = { command: "fzf", mise: "fzf", brew: "fzf", apt: "fzf", manual: FZF_INSTALL };
|
|
116
163
|
const handlers = {
|
|
117
164
|
async search(params) {
|
|
118
165
|
const term = params.term ?? "";
|
|
119
166
|
const collections = asCollections(params.collections);
|
|
120
167
|
const collectionFilter = typeof params.collection === "string" && params.collection.length > 0 ? params.collection : null;
|
|
121
|
-
const type = params.type ?? "text";
|
|
122
168
|
const limit = params.limit && params.limit > 0 ? params.limit : 20;
|
|
123
|
-
const fixedStrings = type !== "regex";
|
|
124
169
|
let roots;
|
|
125
170
|
let activeCollections;
|
|
126
171
|
if (collectionFilter !== null) {
|
|
@@ -131,22 +176,35 @@ function makePluginDefinition(deps = {}) {
|
|
|
131
176
|
roots = collections.map((c) => c.path);
|
|
132
177
|
activeCollections = collections;
|
|
133
178
|
}
|
|
134
|
-
if (roots.length === 0 || term.length === 0) {
|
|
179
|
+
if (roots.length === 0 || term.trim().length === 0) {
|
|
135
180
|
return { hits: [] };
|
|
136
181
|
}
|
|
137
|
-
const
|
|
138
|
-
|
|
182
|
+
const candidates = await runRg(spawnFn, term, roots, { limit });
|
|
183
|
+
if (candidates.trim().length === 0) {
|
|
184
|
+
return { hits: [] };
|
|
185
|
+
}
|
|
186
|
+
let ranked = candidates;
|
|
187
|
+
let fuzzyRanked = false;
|
|
188
|
+
try {
|
|
189
|
+
const fzfOut = await fuzzyRank(fzfFn, candidates, term);
|
|
190
|
+
if (fzfOut.trim().length > 0) {
|
|
191
|
+
ranked = fzfOut;
|
|
192
|
+
fuzzyRanked = true;
|
|
193
|
+
}
|
|
194
|
+
} catch {
|
|
195
|
+
}
|
|
139
196
|
const seen = /* @__PURE__ */ new Set();
|
|
140
197
|
const hits = [];
|
|
141
|
-
for (const
|
|
198
|
+
for (const line of ranked.split("\n")) {
|
|
142
199
|
if (hits.length >= limit) break;
|
|
143
|
-
const
|
|
144
|
-
if (
|
|
145
|
-
seen.
|
|
146
|
-
|
|
200
|
+
const parsed = parseRgLine(line);
|
|
201
|
+
if (!parsed) continue;
|
|
202
|
+
if (seen.has(parsed.path)) continue;
|
|
203
|
+
seen.add(parsed.path);
|
|
204
|
+
const matched = matchCollection(parsed.path, activeCollections);
|
|
147
205
|
if (!matched) continue;
|
|
148
|
-
const title = await cheapTitle(
|
|
149
|
-
const snippet =
|
|
206
|
+
const title = await cheapTitle(parsed.path, readFn);
|
|
207
|
+
const snippet = parsed.text.trim();
|
|
150
208
|
hits.push({
|
|
151
209
|
collection: matched.collection,
|
|
152
210
|
relPath: matched.relPath,
|
|
@@ -154,36 +212,53 @@ function makePluginDefinition(deps = {}) {
|
|
|
154
212
|
snippet: snippet.length > 0 ? snippet : void 0
|
|
155
213
|
});
|
|
156
214
|
}
|
|
215
|
+
if (fuzzyRanked && hits.length > 0) {
|
|
216
|
+
const total = hits.length;
|
|
217
|
+
hits.forEach((hit, i) => {
|
|
218
|
+
hit.score = Number(((total - i) / total).toFixed(3));
|
|
219
|
+
});
|
|
220
|
+
}
|
|
157
221
|
return { hits };
|
|
158
222
|
},
|
|
159
223
|
async index() {
|
|
160
224
|
return { indexed: 0 };
|
|
161
225
|
},
|
|
162
226
|
async setup(_params, log) {
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
{
|
|
170
|
-
step: "verify-rg",
|
|
171
|
-
ok: false,
|
|
172
|
-
message: "rg not on PATH \u2014 install ripgrep: https://github.com/BurntSushi/ripgrep"
|
|
173
|
-
}
|
|
174
|
-
]
|
|
175
|
-
};
|
|
227
|
+
const steps = [];
|
|
228
|
+
const rg = await installFn(RG_SPEC, { install: true, log });
|
|
229
|
+
if (!rg.present) {
|
|
230
|
+
log("error", `ripgrep (rg) unavailable \u2014 ${rg.message}`);
|
|
231
|
+
steps.push({ step: "verify-rg", ok: false, message: rg.message });
|
|
232
|
+
return { ready: false, steps };
|
|
176
233
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
234
|
+
steps.push({ step: "verify-rg", ok: true, message: rg.installed ? rg.message : void 0 });
|
|
235
|
+
const fzf = await installFn(FZF_SPEC, { install: true, log });
|
|
236
|
+
if (fzf.present) {
|
|
237
|
+
steps.push({ step: "verify-fzf", ok: true, message: fzf.installed ? fzf.message : void 0 });
|
|
238
|
+
} else {
|
|
239
|
+
log("warn", `fzf unavailable \u2014 fuzzy ranking disabled (plain ripgrep results). ${fzf.message}`);
|
|
240
|
+
steps.push({ step: "verify-fzf", ok: false, message: fzf.message });
|
|
241
|
+
}
|
|
242
|
+
return { ready: true, steps };
|
|
181
243
|
},
|
|
182
244
|
async onEvent() {
|
|
183
245
|
return { accepted: true };
|
|
184
246
|
},
|
|
185
247
|
async health() {
|
|
186
248
|
const rgPresent = await rgOnPath(spawnFn);
|
|
249
|
+
const fzfPresent = rgPresent ? await fzfOnPath(fzfFn) : false;
|
|
250
|
+
const diagnostics = [];
|
|
251
|
+
if (rgPresent) {
|
|
252
|
+
diagnostics.push({ level: "ok", message: "ripgrep ready" });
|
|
253
|
+
diagnostics.push(
|
|
254
|
+
fzfPresent ? { level: "ok", message: "fzf ready \u2014 fuzzy ranking enabled" } : {
|
|
255
|
+
level: "warn",
|
|
256
|
+
message: `fzf not found \u2014 using plain ripgrep (no fuzzy ranking); install fzf: ${FZF_INSTALL}`
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
} else {
|
|
260
|
+
diagnostics.push({ level: "error", message: `ripgrep (rg) not found \u2014 install it: ${RG_INSTALL}` });
|
|
261
|
+
}
|
|
187
262
|
return {
|
|
188
263
|
ready: rgPresent,
|
|
189
264
|
readiness: {
|
|
@@ -194,12 +269,7 @@ function makePluginDefinition(deps = {}) {
|
|
|
194
269
|
embeddingsPending: 0,
|
|
195
270
|
firstSearchCost: "instant"
|
|
196
271
|
},
|
|
197
|
-
diagnostics
|
|
198
|
-
{
|
|
199
|
-
level: "error",
|
|
200
|
-
message: "ripgrep (rg) not found \u2014 install it: https://github.com/BurntSushi/ripgrep"
|
|
201
|
-
}
|
|
202
|
-
]
|
|
272
|
+
diagnostics
|
|
203
273
|
};
|
|
204
274
|
}
|
|
205
275
|
};
|
|
@@ -208,7 +278,7 @@ function makePluginDefinition(deps = {}) {
|
|
|
208
278
|
kind: "search",
|
|
209
279
|
version: VERSION,
|
|
210
280
|
capabilities: {
|
|
211
|
-
searchTypes: ["
|
|
281
|
+
searchTypes: ["fuzzy"],
|
|
212
282
|
collectionFilter: true,
|
|
213
283
|
events: false
|
|
214
284
|
},
|
|
@@ -224,6 +294,5 @@ createPlugin().run();
|
|
|
224
294
|
export {
|
|
225
295
|
createPlugin,
|
|
226
296
|
makePluginDefinition,
|
|
227
|
-
matchCollection
|
|
228
|
-
parseRgOutput
|
|
297
|
+
matchCollection
|
|
229
298
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "diffwiki-ripgrep",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0-rc.202608040914.07e70ce",
|
|
4
4
|
"description": "diffwiki search plugin driving ripgrep (rg) — instant literal/regex search, no index",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"diffwiki-core": "0.
|
|
23
|
+
"diffwiki-core": "0.15.0-rc.202608040914.07e70ce"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/node": "^22.0.0"
|