@giovannijecha/jecode 0.3.0 → 0.3.2
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/store-lock.js +37 -5
- package/dist/tools/ripgrep.js +14 -3
- package/dist/tui/components/tool.js +3 -49
- package/dist/ui/theme.js +1 -1
- package/package.json +1 -1
package/dist/store-lock.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// from replacing unrelated settings, API keys, or rotated OAuth credentials
|
|
5
5
|
// with snapshots they cached before the other process wrote.
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
|
-
import { mkdir, open, readFile,
|
|
7
|
+
import { mkdir, open, readFile, rmdir, stat, unlink } from "node:fs/promises";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
const WAIT_MS = 50;
|
|
10
10
|
const WAIT_LIMIT_MS = 20_000;
|
|
@@ -59,10 +59,12 @@ async function recoverStale(directory) {
|
|
|
59
59
|
const details = await stat(directory);
|
|
60
60
|
if (Date.now() - details.mtimeMs < STALE_MS)
|
|
61
61
|
return;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
if (await ownerIsAlive(directory))
|
|
63
|
+
return;
|
|
64
|
+
// Remove the owner first, then the now-empty directory. `rmdir` cannot
|
|
65
|
+
// erase a fresh lock acquired after another waiter wins this recovery,
|
|
66
|
+
// whereas renaming the shared path can steal that new lock in an ABA race.
|
|
67
|
+
await removeLock(directory);
|
|
66
68
|
}
|
|
67
69
|
catch (error) {
|
|
68
70
|
const code = error.code;
|
|
@@ -70,6 +72,36 @@ async function recoverStale(directory) {
|
|
|
70
72
|
throw error;
|
|
71
73
|
}
|
|
72
74
|
}
|
|
75
|
+
async function ownerIsAlive(directory) {
|
|
76
|
+
let token;
|
|
77
|
+
try {
|
|
78
|
+
token = await readFile(path.join(directory, "owner"), "utf8");
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
const code = error.code;
|
|
82
|
+
if (code === "ENOENT")
|
|
83
|
+
return false;
|
|
84
|
+
if (code === "EACCES" || code === "EPERM")
|
|
85
|
+
return true;
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
const match = /^([1-9]\d*):/.exec(token.trim());
|
|
89
|
+
if (match === null)
|
|
90
|
+
return false;
|
|
91
|
+
const pid = Number(match[1]);
|
|
92
|
+
if (!Number.isSafeInteger(pid) || pid > 0x7fff_ffff)
|
|
93
|
+
return false;
|
|
94
|
+
try {
|
|
95
|
+
process.kill(pid, 0);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
// ESRCH is the one portable proof that the owner no longer exists.
|
|
100
|
+
// Permission failures and unknown platform errors must not authorize a
|
|
101
|
+
// second writer to enter the same store.
|
|
102
|
+
return error.code !== "ESRCH";
|
|
103
|
+
}
|
|
104
|
+
}
|
|
73
105
|
async function release(directory, token) {
|
|
74
106
|
try {
|
|
75
107
|
const owner = path.join(directory, "owner");
|
package/dist/tools/ripgrep.js
CHANGED
|
@@ -102,6 +102,7 @@ function searchBatch(executable, files, options) {
|
|
|
102
102
|
let buffered = "";
|
|
103
103
|
let stderr = "";
|
|
104
104
|
let invalid = false;
|
|
105
|
+
let overLimit = false;
|
|
105
106
|
let settled = false;
|
|
106
107
|
const onAbort = () => child.kill();
|
|
107
108
|
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
@@ -118,13 +119,23 @@ function searchBatch(executable, files, options) {
|
|
|
118
119
|
resolve(value);
|
|
119
120
|
};
|
|
120
121
|
const consume = (line) => {
|
|
121
|
-
if (line === "" || invalid)
|
|
122
|
+
if (line === "" || invalid || overLimit)
|
|
122
123
|
return;
|
|
123
124
|
try {
|
|
124
125
|
const event = JSON.parse(line);
|
|
125
126
|
const parsed = ripgrepEvent(event);
|
|
126
|
-
if (parsed?.kind === "match")
|
|
127
|
+
if (parsed?.kind === "match") {
|
|
128
|
+
// `rg --max-count` is per file, not global. Once the raw stream
|
|
129
|
+
// exceeds the requested result count, stop the accelerator and let
|
|
130
|
+
// the portable scanner produce the exact bounded answer. Returning
|
|
131
|
+
// early here would lose the later binary-file end marker.
|
|
132
|
+
if (matches.length >= options.limit) {
|
|
133
|
+
overLimit = true;
|
|
134
|
+
child.kill();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
127
137
|
matches.push(parsed.match);
|
|
138
|
+
}
|
|
128
139
|
if (parsed?.kind === "binary")
|
|
129
140
|
binaryPaths.add(parsed.path);
|
|
130
141
|
}
|
|
@@ -166,7 +177,7 @@ function searchBatch(executable, files, options) {
|
|
|
166
177
|
return;
|
|
167
178
|
}
|
|
168
179
|
consume(buffered);
|
|
169
|
-
if (invalid || (code !== 0 && code !== 1) || stderr.trim() !== "") {
|
|
180
|
+
if (invalid || overLimit || (code !== 0 && code !== 1) || stderr.trim() !== "") {
|
|
170
181
|
finish(undefined);
|
|
171
182
|
return;
|
|
172
183
|
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { hasColor, row } from "../../ui/render.js";
|
|
3
3
|
const OUTPUT_ROWS = 8;
|
|
4
4
|
const LIVE_OUTPUT_ROWS = 6;
|
|
5
|
-
const DIFF_ROWS = 12;
|
|
6
5
|
export function renderTool(block, width, pal, context = {}) {
|
|
7
6
|
const shown = visibleDetails(block);
|
|
8
7
|
const right = liveLabel(block, context);
|
|
@@ -31,54 +30,9 @@ function visibleDetails(block) {
|
|
|
31
30
|
: `… ${hidden} earlier lines · ctrl+o expand`;
|
|
32
31
|
return [{ kind: "gap", text: note }, ...all.slice(-limit)];
|
|
33
32
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
function diffWindow(all) {
|
|
38
|
-
if (all.length <= DIFF_ROWS)
|
|
39
|
-
return [...all];
|
|
40
|
-
const important = all.flatMap((detail, index) => detail.kind === "keep" ? [] : [index]);
|
|
41
|
-
const chosen = new Set();
|
|
42
|
-
const budgeted = important.length <= DIFF_ROWS
|
|
43
|
-
? important
|
|
44
|
-
: [
|
|
45
|
-
...important.slice(0, Math.ceil(DIFF_ROWS / 2)),
|
|
46
|
-
...important.slice(-Math.floor(DIFF_ROWS / 2)),
|
|
47
|
-
];
|
|
48
|
-
for (const index of budgeted)
|
|
49
|
-
chosen.add(index);
|
|
50
|
-
for (let radius = 1; chosen.size < DIFF_ROWS && radius < all.length; radius++) {
|
|
51
|
-
for (const index of important) {
|
|
52
|
-
for (const candidate of [index - radius, index + radius]) {
|
|
53
|
-
if (candidate < 0 || candidate >= all.length || chosen.has(candidate))
|
|
54
|
-
continue;
|
|
55
|
-
chosen.add(candidate);
|
|
56
|
-
if (chosen.size >= DIFF_ROWS)
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
if (chosen.size >= DIFF_ROWS)
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
const indexes = [...chosen].sort((left, right) => left - right);
|
|
64
|
-
const out = [];
|
|
65
|
-
let previous = -1;
|
|
66
|
-
for (const index of indexes) {
|
|
67
|
-
if (index > previous + 1) {
|
|
68
|
-
out.push({ kind: "gap", text: `… ${index - previous - 1} lines hidden · ctrl+o expand` });
|
|
69
|
-
}
|
|
70
|
-
const detail = all[index];
|
|
71
|
-
if (detail !== undefined)
|
|
72
|
-
out.push(detail);
|
|
73
|
-
previous = index;
|
|
74
|
-
}
|
|
75
|
-
if (previous < all.length - 1) {
|
|
76
|
-
out.push({
|
|
77
|
-
kind: "gap",
|
|
78
|
-
text: `… ${all.length - previous - 1} lines hidden · ctrl+o expand`,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
return out;
|
|
33
|
+
// The compact transcript is an audit of what changed, not a code excerpt.
|
|
34
|
+
// Context and gap rows remain in semantic state for the explicit full view.
|
|
35
|
+
return all.filter((detail) => detail.kind === "add" || detail.kind === "del");
|
|
82
36
|
}
|
|
83
37
|
function renderDetail(detail, tone, width, pal) {
|
|
84
38
|
const lead = { text: " " };
|
package/dist/ui/theme.js
CHANGED