@zzs-fun/current-question 0.1.0 → 0.1.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # current-question
2
2
 
3
- [English](./README.md) | [简体中文](./README.zh-CN.md)
3
+ [English](./README.md) | [Chinese (Simplified)](./README.zh-CN.md)
4
4
 
5
5
  > A [pi coding agent](https://pi.dev) extension that pins your recent questions above the editor — navigate, expand, copy, and reply with context.
6
6
 
@@ -10,7 +10,7 @@
10
10
 
11
11
  - Shows the last N user questions (default 5; set with `/current-question <number>`)
12
12
  - One line per question; long lines are truncated with `...` (correctly handles double-width CJK characters)
13
- - Title carries the round counter: `❯ 当前问题(N)` where N is the current question round
13
+ - Title carries the round counter: `❯ Current Questions (N)` where N is the current question round
14
14
  - Collapsed by default; `Ctrl+Alt+L` expands the selected entry to show the full question + its answer, `Ctrl+Alt+H` collapses
15
15
  - Collapsed: `Ctrl+Alt+H` hides the widget; hidden: `Ctrl+Alt+L` shows it again
16
16
  - Expanded: `Ctrl+Alt+J/K` scrolls the answer; collapsed: `Ctrl+Alt+J/K` switches entries
@@ -26,7 +26,11 @@
26
26
  ## Install
27
27
 
28
28
  ```bash
29
+ # Install globally
29
30
  pi install npm:@zzs-fun/current-question
31
+
32
+ # Or install for the current project only
33
+ pi install -l npm:@zzs-fun/current-question
30
34
  ```
31
35
 
32
36
  ## Shortcuts
package/README.zh-CN.md CHANGED
@@ -10,7 +10,7 @@ pi coding agent 扩展,在编辑器上方固定显示「最近 n 条用户问
10
10
 
11
11
  - 显示最近 N 条用户问题(默认 5 条,`/current-question <数字>` 设置)
12
12
  - 每条只展示一行,超长自动截断并加 `...`(正确处理中文等双宽字符)
13
- - 标题带「第 N 轮」计数:`❯ 当前问题(N)`,N 为当前提问轮数
13
+ - 标题带「第 N 轮」计数:`❯ Current Questions (N)`,N 为当前提问轮数
14
14
  - 默认收起;选中条可 `Ctrl+Alt+L` 展开看问题全文 + 该轮回答,`Ctrl+Alt+H` 收起
15
15
  - 收起态 `Ctrl+Alt+H` 隐藏当前问题;隐藏态 `Ctrl+Alt+L` 重新显示
16
16
  - 展开态 `Ctrl+Alt+J/K` 滚动回答内容;收起态 `Ctrl+Alt+J/K` 切换问题条目
@@ -26,7 +26,11 @@ pi coding agent 扩展,在编辑器上方固定显示「最近 n 条用户问
26
26
  ## 安装
27
27
 
28
28
  ```bash
29
+ # 全局安装
29
30
  pi install npm:@zzs-fun/current-question
31
+
32
+ # 或仅安装到当前项目
33
+ pi install -l npm:@zzs-fun/current-question
30
34
  ```
31
35
 
32
36
  ## 快捷键
package/index.ts CHANGED
@@ -47,9 +47,9 @@ const MAX_QUESTION_LINES = 6;
47
47
  const WIDGET_HEIGHT_RESERVED = 10;
48
48
 
49
49
  /** 外部编辑器分节标记(行首严格匹配) */
50
- const MARKER_QUESTION = ">> 问题";
51
- const MARKER_ANSWER = ">> 回答";
52
- const MARKER_REPLY = ">> 回复";
50
+ const MARKER_QUESTION = ">> Question";
51
+ const MARKER_ANSWER = ">> Answer";
52
+ const MARKER_REPLY = ">> Reply";
53
53
 
54
54
  /** 缓存的 TUI 实例句柄,供外部编辑器流程暂停/恢复终端 */
55
55
  interface TuiHandle {
@@ -75,7 +75,7 @@ const NVIM_LOCK_SCRIPT = [
75
75
  '" —— 2. 找到最后一个 “>> 回复” 标记行 ——',
76
76
  'let g:pi_ctx_locked_end = 0',
77
77
  'for s:i in range(1, line(\'$\'))',
78
- ' if getline(s:i) =~# \'^>>\\s*回复\\s*$\'',
78
+ ' if getline(s:i) =~# \'^>>\\s*Reply\\s*$\'',
79
79
  ' let g:pi_ctx_locked_end = s:i',
80
80
  ' endif',
81
81
  'endfor',
@@ -89,7 +89,7 @@ const NVIM_LOCK_SCRIPT = [
89
89
  'function! PiCtxGuardInsert() abort',
90
90
  ' if line(\'.\') <= g:pi_ctx_locked_end',
91
91
  ' call feedkeys("\\<Esc>", \'n\')',
92
- ' echohl WarningMsg | echo "历史问答区只读,请在 \'>> 回复\' 下方编辑" | echohl None',
92
+ ' echohl WarningMsg | echo "The question and answer history is read-only. Edit below \'>> Reply\'." | echohl None',
93
93
  ' endif',
94
94
  'endfunction',
95
95
  'autocmd InsertEnter <buffer> call PiCtxGuardInsert()',
@@ -100,7 +100,7 @@ const NVIM_LOCK_SCRIPT = [
100
100
  ' let g:pi_ctx_restoring = 1',
101
101
  ' undo',
102
102
  ' unlet g:pi_ctx_restoring',
103
- ' echohl WarningMsg | echo "已恢复被改动的历史问答区" | echohl None',
103
+ ' echohl WarningMsg | echo "Restored the modified question and answer history." | echohl None',
104
104
  ' endif',
105
105
  'endfunction',
106
106
  'autocmd TextChanged,TextChangedI <buffer> call PiCtxCheckLocked()',
@@ -135,7 +135,7 @@ function extractText(content: unknown): string {
135
135
  }
136
136
  const text = parts.join("").trim();
137
137
  if (text) return text;
138
- return hasImage ? "[图片]" : "";
138
+ return hasImage ? "[Image]" : "";
139
139
  }
140
140
 
141
141
  /**
@@ -220,7 +220,7 @@ function resolveEditorCommand(): { command: string; args: string[] } {
220
220
  * 回复区放当前编辑器文本,前两节作为只读参考。
221
221
  */
222
222
  function buildContextFileContent(q: { text: string; answer: string }, replyText: string): string {
223
- const answer = q.answer?.trim() || "(暂无回答)";
223
+ const answer = q.answer?.trim() || "(No answer yet)";
224
224
  return `${MARKER_QUESTION}\n\n${q.text}\n\n${MARKER_ANSWER}\n\n${answer}\n\n${MARKER_REPLY}\n\n${replyText}\n`;
225
225
  }
226
226
 
@@ -238,7 +238,7 @@ function parseReplyFromContent(content: string): { reply: string; markerFound: b
238
238
  const lines = content.replace(/\r\n/g, "\n").split("\n");
239
239
  let markerIdx = -1;
240
240
  for (let i = lines.length - 1; i >= 0; i--) {
241
- if (/^>>\s*回复\s*$/.test(lines[i]!)) {
241
+ if (/^>>\s*Reply\s*$/.test(lines[i]!)) {
242
242
  markerIdx = i;
243
243
  break;
244
244
  }
@@ -322,17 +322,17 @@ export default function (pi: ExtensionAPI) {
322
322
  const copyQuestionAndAnswer = async (ctx: ExtensionContext) => {
323
323
  const q = getSelectedQuestion();
324
324
  if (!q) {
325
- ctx.ui.notify("暂无历史问题可复制", "warning");
325
+ ctx.ui.notify("No question history to copy", "warning");
326
326
  return;
327
327
  }
328
328
 
329
- const text = `>> 问题\n\n${q.text}\n\n>> 回答\n\n${q.answer || "(暂无回答)"}`;
329
+ const text = `>> Question\n\n${q.text}\n\n>> Answer\n\n${q.answer || "(No answer yet)"}`;
330
330
  try {
331
331
  await copyToClipboard(text);
332
- ctx.ui.notify(`已复制第 ${q.round} 轮问题与回答`, "info");
332
+ ctx.ui.notify(`Copied question and answer from round ${q.round}`, "info");
333
333
  } catch (error) {
334
- console.error("[current-question] 复制到剪贴板失败", error);
335
- ctx.ui.notify(`复制失败:${error instanceof Error ? error.message : String(error)}`, "error");
334
+ console.error("[current-question] Failed to copy to clipboard", error);
335
+ ctx.ui.notify(`Copy failed: ${error instanceof Error ? error.message : String(error)}`, "error");
336
336
  }
337
337
  };
338
338
 
@@ -406,12 +406,12 @@ export default function (pi: ExtensionAPI) {
406
406
  const indentWidth = visibleWidth(indent);
407
407
  const n = recentQuestions.length;
408
408
  const out: string[] = [];
409
- out.push(theme.fg("accent", theme.bold(`❯ 当前问题(${currentRound})`)));
409
+ out.push(theme.fg("accent", theme.bold(`❯ Current Questions (${currentRound})`)));
410
410
 
411
411
  // 顶部指示:窗口上方还有更新的问题
412
412
  const newerCount = n - (windowStart + maxLines);
413
413
  if (newerCount > 0) {
414
- out.push(`${theme.fg("dim", indent)}${theme.fg("dim", `↑ 还有 ${newerCount} 条更新`)}`);
414
+ out.push(`${theme.fg("dim", indent)}${theme.fg("dim", `↑ ${newerCount} newer`)}`);
415
415
  }
416
416
 
417
417
  // 可见窗口 [windowStart, windowStart+maxLines),反转使最新在上
@@ -445,9 +445,9 @@ export default function (pi: ExtensionAPI) {
445
445
  }
446
446
  });
447
447
  if (qTruncated) {
448
- out.push(`${theme.fg("dim", bodyIndent)}${theme.fg("dim", `… 问题共 ${rawQLines.length} 行,已截断前 ${MAX_QUESTION_LINES} 行(Ctrl+Alt+B 复制全文)`)}`);
448
+ out.push(`${theme.fg("dim", bodyIndent)}${theme.fg("dim", `… Question has ${rawQLines.length} lines; showing first ${MAX_QUESTION_LINES} (Ctrl+Alt+B copies all)`)}`);
449
449
  }
450
- out.push(`${theme.fg("dim", bodyIndent)}${theme.fg("accent", "─ 回答 ─")}`);
450
+ out.push(`${theme.fg("dim", bodyIndent)}${theme.fg("accent", "─ Answer ─")}`);
451
451
  if (q.answer) {
452
452
  // 全部分行并缓存,供滚动 handler 做边界判断
453
453
  cachedAnswerLines = wrapText(q.answer, bodyWidth);
@@ -471,7 +471,7 @@ export default function (pi: ExtensionAPI) {
471
471
  const viewEnd = Math.min(total, answerScroll + answerView);
472
472
  // 回答上方滚动提示
473
473
  if (answerScroll > 0) {
474
- out.push(`${theme.fg("dim", bodyIndent)}${theme.fg("dim", `↑ 还有 ${answerScroll} (Ctrl+Alt+K 上滚)`)}`);
474
+ out.push(`${theme.fg("dim", bodyIndent)}${theme.fg("dim", `↑ ${answerScroll} lines above (Ctrl+Alt+K)`)}`);
475
475
  }
476
476
  for (const ln of cachedAnswerLines.slice(answerScroll, viewEnd)) {
477
477
  out.push(`${theme.fg("dim", bodyIndent)}${theme.fg("muted", ln)}`);
@@ -479,10 +479,10 @@ export default function (pi: ExtensionAPI) {
479
479
  // 回答下方滚动提示
480
480
  const remain = total - viewEnd;
481
481
  if (remain > 0) {
482
- out.push(`${theme.fg("dim", bodyIndent)}${theme.fg("dim", `↓ 还有 ${remain} (Ctrl+Alt+J 下滚)`)}`);
482
+ out.push(`${theme.fg("dim", bodyIndent)}${theme.fg("dim", `↓ ${remain} lines below (Ctrl+Alt+J)`)}`);
483
483
  }
484
484
  } else {
485
- out.push(`${theme.fg("dim", bodyIndent)}${theme.fg("dim", "(暂无回答)")}`);
485
+ out.push(`${theme.fg("dim", bodyIndent)}${theme.fg("dim", "(No answer yet)")}`);
486
486
  }
487
487
  }
488
488
  } else {
@@ -496,14 +496,14 @@ export default function (pi: ExtensionAPI) {
496
496
  // 底部指示:窗口下方还有更早的问题
497
497
  const olderCount = windowStart;
498
498
  if (olderCount > 0) {
499
- out.push(`${theme.fg("dim", indent)}${theme.fg("dim", `↓ 还有 ${olderCount} 条更早`)}`);
499
+ out.push(`${theme.fg("dim", indent)}${theme.fg("dim", `↓ ${olderCount} older`)}`);
500
500
  }
501
501
  // 兜底:极端配置或小终端下,若 widget 总行数仍超上限,从底部截断并提示,
502
502
  // 确保不触碰终端顶部引发流式抖动。
503
503
  if (out.length > maxWidgetRows) {
504
504
  const overflow = out.length - maxWidgetRows;
505
505
  out.length = maxWidgetRows;
506
- out.push(`${theme.fg("dim", indent)}${theme.fg("dim", `↓ 因终端高度隐藏 ${overflow} 行`)}`);
506
+ out.push(`${theme.fg("dim", indent)}${theme.fg("dim", `↓ ${overflow} lines hidden due to terminal height`)}`);
507
507
  }
508
508
  cachedOutput = out;
509
509
  return cachedOutput;
@@ -571,7 +571,7 @@ export default function (pi: ExtensionAPI) {
571
571
 
572
572
  // Ctrl+Alt+J:收起态=选更早问题;展开态=回答下滚
573
573
  pi.registerShortcut("ctrl+alt+j", {
574
- description: "当前问题:收起时选更早问题 / 展开时回答下滚",
574
+ description: "Current Questions: select an older question when collapsed / scroll the answer down when expanded",
575
575
  handler: async (ctx) => {
576
576
  if (recentQuestions.length === 0 || !enabled) return;
577
577
  if (expanded) {
@@ -592,7 +592,7 @@ export default function (pi: ExtensionAPI) {
592
592
 
593
593
  // Ctrl+Alt+K:收起态=选更新问题;展开态=回答上滚
594
594
  pi.registerShortcut("ctrl+alt+k", {
595
- description: "当前问题:收起时选更新问题 / 展开时回答上滚",
595
+ description: "Current Questions: select a newer question when collapsed / scroll the answer up when expanded",
596
596
  handler: async (ctx) => {
597
597
  if (recentQuestions.length === 0 || !enabled) return;
598
598
  if (expanded) {
@@ -611,7 +611,7 @@ export default function (pi: ExtensionAPI) {
611
611
 
612
612
  // Ctrl+Alt+C:同时复制当前选中条的完整问题与回答
613
613
  pi.registerShortcut("ctrl+alt+b", {
614
- description: "当前问题:复制选中条完整问题与回答",
614
+ description: "Current Questions: copy the selected question and answer",
615
615
  handler: async (ctx) => {
616
616
  await copyQuestionAndAnswer(ctx);
617
617
  },
@@ -619,7 +619,7 @@ export default function (pi: ExtensionAPI) {
619
619
 
620
620
  // Ctrl+Alt+L:隐藏态=重新显示;显示态=展开当前选中条(重置回答滚动到顶部)
621
621
  pi.registerShortcut("ctrl+alt+l", {
622
- description: "当前问题:隐藏时重新显示 / 显示时展开选中条(显示问题全文 + 回答)",
622
+ description: "Current Questions: show when hidden / expand the selected question and answer when visible",
623
623
  handler: async (ctx) => {
624
624
  if (recentQuestions.length === 0) return;
625
625
  if (!enabled) {
@@ -637,7 +637,7 @@ export default function (pi: ExtensionAPI) {
637
637
 
638
638
  // Ctrl+Alt+H:展开态=收起当前选中条;收起态=隐藏当前问题
639
639
  pi.registerShortcut("ctrl+alt+h", {
640
- description: "当前问题:展开时收起选中条 / 收起时隐藏当前问题",
640
+ description: "Current Questions: collapse the selected item when expanded / hide the widget when collapsed",
641
641
  handler: async (ctx) => {
642
642
  if (expanded) {
643
643
  expanded = false;
@@ -654,20 +654,20 @@ export default function (pi: ExtensionAPI) {
654
654
  // 终端管理复用 pi 内置 openExternalEditor 的方式:tui.stop() 释放终端、异步 spawn 编辑器、
655
655
  // 退出后 tui.start() 恢复。用异步 spawn 而非 spawnSync,避免 Windows 上 console input 抢占。
656
656
  pi.registerShortcut("ctrl+alt+g", {
657
- description: "当前问题:带选中问答上下文打开外部编辑器编辑回复",
657
+ description: "Current Questions: open an external editor with selected question-and-answer context",
658
658
  handler: async (ctx) => {
659
659
  if (!enabled || recentQuestions.length === 0) {
660
- ctx.ui.notify("暂无历史问题,无法带上下文编辑", "warning");
660
+ ctx.ui.notify("No question history available for context-aware editing", "warning");
661
661
  return;
662
662
  }
663
663
  const q = getSelectedQuestion();
664
664
  if (!q) {
665
- ctx.ui.notify("未选中历史问题", "warning");
665
+ ctx.ui.notify("No history item is selected", "warning");
666
666
  return;
667
667
  }
668
668
  const tui = tuiRef;
669
669
  if (!tui?.stop || !tui?.start) {
670
- ctx.ui.notify("当前环境不支持外部编辑器流程", "error");
670
+ ctx.ui.notify("External editor workflow is unavailable in this environment", "error");
671
671
  return;
672
672
  }
673
673
 
@@ -686,7 +686,7 @@ export default function (pi: ExtensionAPI) {
686
686
  try {
687
687
  fs.writeFileSync(tmpFile, fileContent, "utf8");
688
688
  } catch (e) {
689
- ctx.ui.notify(`写临时文件失败:${e instanceof Error ? e.message : String(e)}`, "error");
689
+ ctx.ui.notify(`Failed to write temporary file: ${e instanceof Error ? e.message : String(e)}`, "error");
690
690
  return;
691
691
  }
692
692
 
@@ -711,7 +711,7 @@ export default function (pi: ExtensionAPI) {
711
711
  }
712
712
 
713
713
  // stop 前直接写 stdout 提示(TUI 停止后 notify 不可见)
714
- process.stdout.write(`打开外部编辑器编辑第 ${q.round} 轮问答(${command})…\nPi 将在编辑器退出后恢复。\n`);
714
+ process.stdout.write(`Opening ${command} to edit the reply with context from round ${q.round}…\nPi will resume when the editor exits.\n`);
715
715
 
716
716
  let status: number | null = null;
717
717
  let spawnErr: Error | null = null;
@@ -734,13 +734,13 @@ export default function (pi: ExtensionAPI) {
734
734
  }
735
735
 
736
736
  if (spawnErr) {
737
- ctx.ui.notify(`启动编辑器失败:${spawnErr.message}`, "error");
737
+ ctx.ui.notify(`Failed to launch editor: ${spawnErr.message}`, "error");
738
738
  cleanupTemp();
739
739
  return;
740
740
  }
741
741
  if (status !== 0) {
742
742
  // 非正常退出:保留原输入框内容不变
743
- ctx.ui.notify(`编辑器退出码 ${status},未修改输入框`, "warning");
743
+ ctx.ui.notify(`Editor exited with code ${status}; editor contents were not changed`, "warning");
744
744
  cleanupTemp();
745
745
  return;
746
746
  }
@@ -750,7 +750,7 @@ export default function (pi: ExtensionAPI) {
750
750
  try {
751
751
  newContent = fs.readFileSync(tmpFile, "utf8");
752
752
  } catch (e) {
753
- ctx.ui.notify(`读取临时文件失败:${e instanceof Error ? e.message : String(e)}`, "error");
753
+ ctx.ui.notify(`Failed to read temporary file: ${e instanceof Error ? e.message : String(e)}`, "error");
754
754
  cleanupTemp();
755
755
  return;
756
756
  }
@@ -759,16 +759,16 @@ export default function (pi: ExtensionAPI) {
759
759
  const { reply, markerFound } = parseReplyFromContent(newContent);
760
760
  ctx.ui.setEditorText(reply);
761
761
  if (!markerFound) {
762
- ctx.ui.notify("⚠️ 未找到 '>> 回复' 标记(可能被删除/改写),已将全部内容作为回复", "warning");
762
+ ctx.ui.notify("⚠️ Could not find the '>> Reply' marker; used the entire file as the reply", "warning");
763
763
  } else {
764
- ctx.ui.notify(`已更新输入框(参考第 ${q.round} 轮问答)`, "info");
764
+ ctx.ui.notify(`Updated the editor with context from round ${q.round}`, "info");
765
765
  }
766
766
  },
767
767
  });
768
768
 
769
769
  // /current-question [on|off|toggle|<数字>]
770
770
  pi.registerCommand("current-question", {
771
- description: "当前问题置顶:on/off/toggle 切换开关,或传数字设置显示条数(默认 5",
771
+ description: "Current Questions: use on/off/toggle to change visibility, or a number to set the displayed count (default: 5)",
772
772
  handler: async (args, ctx) => {
773
773
  const arg = args.trim().toLowerCase();
774
774
  if (arg === "on") {
@@ -783,11 +783,11 @@ export default function (pi: ExtensionAPI) {
783
783
  enabled = true;
784
784
  ensureVisible();
785
785
  } else {
786
- ctx.ui.notify("用法: /current-question [on|off|toggle|<数字>]", "warning");
786
+ ctx.ui.notify("Usage: /current-question [on|off|toggle|<number>]", "warning");
787
787
  return;
788
788
  }
789
789
  refresh(ctx);
790
- ctx.ui.notify(`当前问题置顶:${enabled ? "" : ""},显示 ${maxLines} 条`, "info");
790
+ ctx.ui.notify(`Current Questions: ${enabled ? "on" : "off"}; displaying ${maxLines} item(s)`, "info");
791
791
  },
792
792
  });
793
793
  }
package/package.json CHANGED
@@ -1,25 +1,34 @@
1
1
  {
2
2
  "name": "@zzs-fun/current-question",
3
- "version": "0.1.0",
4
- "description": "pi 扩展:在编辑器上方置顶显示最近 N 条用户问题,支持切换/展开/复制/带上下文的外部编辑器回复",
5
- "keywords": ["pi-package"],
3
+ "version": "0.1.2",
4
+ "description": "pi extension that pins your recent questions above the editor — navigate, expand, copy, and reply with context.",
5
+ "keywords": [
6
+ "pi-package"
7
+ ],
6
8
  "author": "jenson",
7
9
  "license": "MIT",
8
10
  "repository": {
9
11
  "type": "git",
10
- "url": "https://github.com/wszzs110/current-question.git"
12
+ "url": "git+https://github.com/wszzs110/current-question.git"
11
13
  },
12
14
  "homepage": "https://github.com/wszzs110/current-question#readme",
13
15
  "bugs": {
14
16
  "url": "https://github.com/wszzs110/current-question/issues"
15
17
  },
16
- "files": ["index.ts", "README.md", "README.zh-CN.md", "LICENSE"],
18
+ "files": [
19
+ "index.ts",
20
+ "README.md",
21
+ "README.zh-CN.md",
22
+ "LICENSE"
23
+ ],
17
24
  "pi": {
18
- "extensions": ["./index.ts"]
25
+ "extensions": [
26
+ "./index.ts"
27
+ ]
19
28
  },
20
29
  "peerDependencies": {
21
30
  "@earendil-works/pi-coding-agent": "*",
22
31
  "@earendil-works/pi-tui": "*",
23
32
  "typebox": "*"
24
33
  }
25
- }
34
+ }