pi-cc-extensions 1.0.2 → 1.0.4

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.
@@ -191,7 +191,7 @@ const FULLSCREEN_FOCUS_OUT = "\x1b[O";
191
191
 
192
192
  let pendingCollapsePress: {
193
193
  card: any;
194
- /** 命中的最内层组件:官方工具卡由 MouseRegion 的 click 收起。 */
194
+ /** 命中的最内层组件:卡内工具行命中时用于触发官方 click 抑制窗口。 */
195
195
  component: any;
196
196
  col: number;
197
197
  row: number;
@@ -240,22 +240,17 @@ function rememberCollapsePress(card: any, component: any, packet: SgrMousePacket
240
240
 
241
241
  /**
242
242
  * 松手结算:只有"按下与松开在同一格"的完整单击才收起,拖动选择文本时保持展开。
243
- * 官方工具卡(result 区的 MouseRegion)由 guard 在官方 click 里收起,这里必须跳过,
244
- * 否则先收起会被官方 click 的 setExpanded(!expanded) 翻回来。
243
+ * 官方卡与自有卡统一在这里结算:fullscreen 下结算成功的 release 被 consume,
244
+ * 官方不会再合成 click(合成 click 要求按下/松开坐标完全一致,手抖一格就不发,
245
+ * 还会把刚收起的卡 toggle 回去、或误触卡内其他 MouseRegion)。
245
246
  */
246
- function resolveCollapsePress(
247
- tui: any,
248
- packet: SgrMousePacket,
249
- options: { skipOfficialCards: boolean },
250
- ): any {
247
+ function resolveCollapsePress(tui: any, packet: SgrMousePacket): any {
251
248
  const press = pendingCollapsePress;
252
249
  pendingCollapsePress = null;
253
250
  if (!press || packet.final !== "m") return undefined;
254
251
  if (Math.abs(packet.col - press.col) > COLLAPSE_CLICK_MOVE_TOLERANCE) return undefined;
255
252
  if (Math.abs(packet.row - press.row) > COLLAPSE_CLICK_MOVE_TOLERANCE) return undefined;
256
253
  if (Date.now() - press.at > COLLAPSE_CLICK_MAX_MS) return undefined;
257
- if (options.skipOfficialCards && press.component instanceof ToolExecutionComponent)
258
- return undefined;
259
254
  const card = press.card;
260
255
  if (
261
256
  (card instanceof ToolGroupComponent || isCompactAssistantComponent(card)) &&
@@ -265,7 +260,7 @@ function resolveCollapsePress(
265
260
  suppressOfficialCardClickUntil = Date.now() + SUPPRESS_OFFICIAL_CARD_CLICK_MS;
266
261
  }
267
262
  collapseExpandedCard(tui, card);
268
- // 返回收起的卡:调用方只对 compact 面板清官方选区。
263
+ // 返回收起的卡:fullscreen 调用方据此吞掉这次 release 并清官方按下/选区状态。
269
264
  return card;
270
265
  }
271
266
 
@@ -398,7 +393,7 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
398
393
  if (!target) return false;
399
394
  const component = target.component;
400
395
  // compact 展开面板内命中时 owner 是面板:收起对象与记账对象都归面板,
401
- // 否则 skipOfficialCards 会把内部工具当成官方卡跳过。
396
+ // 面板内工具行命中时 release 统一由扩展结算。
402
397
  const card = target.group ?? target.owner ?? component;
403
398
  const pressComponent = target.owner ?? component;
404
399
  // 回到底部按钮:按组件引用命中,不依赖渲染行缓存。
@@ -474,8 +469,8 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
474
469
  });
475
470
  }
476
471
  }
477
- // 其余放行官方:选区、OSC8 链接与 click 合成都交给 renderer。
478
- // 完整单击的收起:官方卡由 guard 处理,自有卡在松手时结算。
472
+ // 其余放行官方:选区、OSC8 链接与官方按下登记都交给 renderer。
473
+ // 完整单击的收起统一在松手结算(release 被 consume,官方不会再合成 click)。
479
474
  rememberCollapsePress(card, pressComponent, packet);
480
475
  return false;
481
476
  }
@@ -584,6 +579,27 @@ function handleFullscreenToolHover(tui: any, packet: SgrMousePacket): void {
584
579
  applyFullscreenHover(tui, target);
585
580
  }
586
581
 
582
+ /**
583
+ * 鼠标包(1-based 屏幕坐标)是否落在某个已渲染 overlay 的矩形内。
584
+ * renderedOverlayLayouts 与官方 dispatchMouseToOverlay 同源:只含上一帧画出的可见 overlay。
585
+ */
586
+ function packetInsideOverlay(tui: any, packet: SgrMousePacket): boolean {
587
+ const layouts = tui?.renderedOverlayLayouts;
588
+ if (!Array.isArray(layouts)) return false;
589
+ const x = packet.col - 1;
590
+ const y = packet.row - 1;
591
+ for (const layout of layouts) {
592
+ if (
593
+ x >= layout.col &&
594
+ x < layout.col + layout.width &&
595
+ y >= layout.row &&
596
+ y < layout.row + layout.height
597
+ )
598
+ return true;
599
+ }
600
+ return false;
601
+ }
602
+
587
603
  /**
588
604
  * 实例级包装 TuiAltScreen.handleViewportInput(惰性 Proxy 安全):
589
605
  * 原型方法取 original(绕开 proxy 函数包装),实例 own property 装 wrapper
@@ -602,24 +618,32 @@ function patchFullscreenViewportInput(tui: any): void {
602
618
  // 官方 viewport 会消费键盘,扩展监听器无法补偿,必须在这里调度。
603
619
  scheduleScrollButtonSync(tui, data);
604
620
  const packets = parseSgrMousePackets(data);
605
- // 官方 fullscreen 会消费全部鼠标;文本预览 overlay 活动时放行给 focused
606
- // custom component,使 [esc] 点击和滚轮可用。
607
- if (packets && tui.hasOverlay?.() && hasActiveTextPreview()) return undefined;
608
- if (packets && !tui.hasOverlay?.()) {
621
+ // overlay(文本预览/context 弹框,都是真 overlay)可见时:落在
622
+ // overlay 矩形内的包经 packetInsideOverlay 放行官方
623
+ // dispatchMouseToOverlay → overlay.handleMouse;overlay 外的包仍
624
+ // 走扩展收起/展开/hover。两条路径各管各的。
625
+ if (packets) {
626
+ // overlay 可见时:落在 overlay 矩形内的包全部放行官方(文本预览的
627
+ // [esc] 点击/滚轮、官方搜索框等),扩展不展开/收起/hover。
628
+ const overlayPackets = tui.hasOverlay?.()
629
+ ? packets.filter((packet) => packetInsideOverlay(tui, packet))
630
+ : undefined;
631
+ const insideOverlay = (packet: SgrMousePacket) => overlayPackets?.includes(packet) === true;
609
632
  for (const packet of packets) {
633
+ if (insideOverlay(packet)) continue;
610
634
  if (isSgrLeftRelease(packet)) {
611
- // 只给 compact 面板清选区:自有面板收起会重排布局,官方选区锚点
612
- // 还停在旧布局上,同一次 release 会结算出多行高亮。
613
- if (
614
- isCompactAssistantComponent(
615
- resolveCollapsePress(tui, packet, { skipOfficialCards: true }),
616
- )
617
- ) {
635
+ // 结算单击收起:成功时吞掉 release,官方不会再对同一次点击合成
636
+ // click(官方 click 要求按下/松开坐标严格一致,还会把刚收起的卡
637
+ // toggle 回去,或误触卡内其他 MouseRegion 区)。
638
+ if (resolveCollapsePress(tui, packet) !== undefined) {
639
+ // 收起会重排布局:官方还留着按下时登记的 press 目标与选区锚点,
640
+ // 发 FOCUS_OUT 一次清干净,避免旧坐标结算出误选/误发 click。
618
641
  try {
619
642
  Reflect.apply(original, this, [FULLSCREEN_FOCUS_OUT]);
620
643
  } catch {
621
644
  /* 老版本没有选区状态 */
622
645
  }
646
+ return { consume: true };
623
647
  }
624
648
  } else if (!isSgrLeftPress(packet) && !isSgrIdleMotion(packet))
625
649
  clearPendingCollapsePressOnMove(packet);
@@ -965,12 +989,15 @@ function handleToolMouseInput(data: string): { consume: true } | undefined {
965
989
  scheduleScrollButtonSync(getToolMouseTui(), data);
966
990
  return undefined;
967
991
  }
992
+ // 文本预览活动时,点击交给预览自己的 handleInput(esc/copy/滚动条),
993
+ // 不被残留的工具 region 吞掉;预览关闭后恢复。
994
+ if (hasActiveTextPreview()) return undefined;
968
995
 
969
996
  let consumed = false;
970
997
  for (const packet of packets) {
971
998
  updateToolSummaryHover(getToolMouseTui(), packet);
972
999
  if (isSgrLeftRelease(packet)) {
973
- resolveCollapsePress(getToolMouseTui(), packet, { skipOfficialCards: false });
1000
+ resolveCollapsePress(getToolMouseTui(), packet);
974
1001
  continue;
975
1002
  }
976
1003
  if (!isSgrLeftPress(packet)) {
@@ -94,13 +94,13 @@ export async function executeWriteWithMetadata(
94
94
  await mkdir(dirname(absolutePath), { recursive: true });
95
95
  throwIfAborted();
96
96
  await writeFile(absolutePath, params.content, "utf8");
97
- throwIfAborted();
97
+ // The file is already changed. A later abort must not report failure.
98
98
  store.set(toolCallId, metadata);
99
99
  return {
100
100
  content: [
101
101
  {
102
102
  type: "text",
103
- text: `Successfully wrote ${params.content.length} bytes to ${params.path}`,
103
+ text: `Successfully wrote ${Buffer.byteLength(params.content, "utf8")} bytes to ${params.path}`,
104
104
  },
105
105
  ],
106
106
  details: undefined,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cc-extensions",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "license": "MIT",
5
5
  "description": "Claude Code-style TUI output with some personal touches and handy utilities.",
6
6
  "type": "module",