Skip to content

Commit 43d0c11

Browse files
committed
fix: update code examples in statusline documentation and improve module provider abort handling
1 parent 637e26d commit 43d0c11

4 files changed

Lines changed: 66 additions & 23 deletions

File tree

docs/statusline.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Deep Code CLI 支持通过插件向终端底部状态栏注入自定义信息(
66

77
`~/.deepcode/settings.json`(或项目级 `.deepcode/settings.json`)中添加 `statusline` 字段:
88

9-
```jsonc
9+
```json
1010
{
1111
"statusline": {
1212
"enabled": true,
@@ -100,7 +100,7 @@ export default function tokensProvider({ projectRoot, session }) {
100100
101101
## 安全限制
102102
103-
- **module provider 路径必须位于项目根目录或用户家目录之下**,绝对路径在这两个范围之外会被拒绝加载(防止从任意位置执行代码)。
103+
- **module provider 路径必须位于项目根目录或用户home目录之下**,绝对路径在这两个范围之外会被拒绝加载(防止从任意位置执行代码)。
104104
- 单个 segment 文本被自动:
105105
- 取第一个非空行
106106
- 去除 ANSI 转义序列

docs/statusline_en.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Deep Code CLI lets you inject custom information into the status line at the bot
66

77
Add a `statusline` field to `~/.deepcode/settings.json` (or the project-level `.deepcode/settings.json`):
88

9-
```jsonc
9+
```json
1010
{
1111
"statusline": {
1212
"enabled": true,

packages/cli/src/tests/statusline.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,42 @@ test("loadModuleProvider succeeds for a well-formed module", async () => {
171171
}
172172
});
173173

174+
test("loadModuleProvider removes abort listener after successful fetch", async () => {
175+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "deepcode-statusline-"));
176+
const modPath = path.join(dir, "cleanup.mjs");
177+
fs.writeFileSync(modPath, "export default () => 'ok';", "utf8");
178+
try {
179+
const provider = await loadModuleProvider(modPath, undefined, "cleanup", 10_000);
180+
assert.ok(provider);
181+
182+
const ac = new AbortController();
183+
const signal = ac.signal;
184+
const originalAdd = signal.addEventListener;
185+
const originalRemove = signal.removeEventListener;
186+
let abortListenerAdds = 0;
187+
let abortListenerRemoves = 0;
188+
signal.addEventListener = function (this: AbortSignal, ...args: Parameters<AbortSignal["addEventListener"]>) {
189+
if (args[0] === "abort") {
190+
abortListenerAdds += 1;
191+
}
192+
return originalAdd.apply(this, args);
193+
} as AbortSignal["addEventListener"];
194+
signal.removeEventListener = function (this: AbortSignal, ...args: Parameters<AbortSignal["removeEventListener"]>) {
195+
if (args[0] === "abort") {
196+
abortListenerRemoves += 1;
197+
}
198+
return originalRemove.apply(this, args);
199+
} as AbortSignal["removeEventListener"];
200+
201+
const result = await provider!.fetch({ projectRoot: dir, signal });
202+
assert.equal(result, "ok");
203+
assert.equal(abortListenerAdds, 1);
204+
assert.equal(abortListenerRemoves, 1);
205+
} finally {
206+
fs.rmSync(dir, { recursive: true, force: true });
207+
}
208+
});
209+
174210
test("resolveSettingsSources lets project-level providers override user-level by id", () => {
175211
const resolved = resolveSettingsSources(
176212
{

packages/cli/src/ui/statusline/module-provider.ts

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -63,26 +63,33 @@ export async function loadModuleProvider(
6363
if (ctx.signal.aborted) {
6464
return "";
6565
}
66-
const result = await Promise.race([
67-
Promise.resolve().then(() =>
68-
providerFn({
69-
projectRoot: ctx.projectRoot,
70-
session: ctx.getSessionInfo ? ctx.getSessionInfo() : null,
71-
})
72-
),
73-
new Promise<string>((_, reject) => {
74-
const timer = setTimeout(() => reject(new Error("timeout")), timeout);
75-
ctx.signal.addEventListener(
76-
"abort",
77-
() => {
78-
clearTimeout(timer);
79-
reject(new Error("aborted"));
80-
},
81-
{ once: true }
82-
);
83-
}),
84-
]);
85-
return typeof result === "string" ? result : "";
66+
let timer: ReturnType<typeof setTimeout> | null = null;
67+
let onAbort: (() => void) | null = null;
68+
const timeoutPromise = new Promise<never>((_, reject) => {
69+
timer = setTimeout(() => reject(new Error("timeout")), timeout);
70+
onAbort = () => reject(new Error("aborted"));
71+
ctx.signal.addEventListener("abort", onAbort, { once: true });
72+
});
73+
74+
try {
75+
const result = await Promise.race([
76+
Promise.resolve().then(() =>
77+
providerFn({
78+
projectRoot: ctx.projectRoot,
79+
session: ctx.getSessionInfo ? ctx.getSessionInfo() : null,
80+
})
81+
),
82+
timeoutPromise,
83+
]);
84+
return typeof result === "string" ? result : "";
85+
} finally {
86+
if (timer) {
87+
clearTimeout(timer);
88+
}
89+
if (onAbort) {
90+
ctx.signal.removeEventListener("abort", onAbort);
91+
}
92+
}
8693
},
8794
};
8895
} catch {

0 commit comments

Comments
 (0)