pi-cc-extensions 0.5.4 → 0.5.5

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.
@@ -412,49 +412,84 @@ function collectToolRenderHits(
412
412
  return start + count;
413
413
  }
414
414
 
415
+ function collectToolComponents(component: any, tools: any[], seen = new Set<any>()): void {
416
+ if (!component || typeof component !== "object" || seen.has(component)) return;
417
+ seen.add(component);
418
+ if (isToolExecutionComponent(component)) {
419
+ tools.push(component);
420
+ return;
421
+ }
422
+ if (!Array.isArray(component.children)) return;
423
+ for (const child of component.children) collectToolComponents(child, tools, seen);
424
+ }
425
+
426
+ function fixedEditorLineMatch(rendered: string, visible: string): boolean {
427
+ return rendered === visible
428
+ || (visible.length >= 8 && (rendered.includes(visible) || visible.includes(rendered)));
429
+ }
430
+
431
+ function fixedEditorContextScore(
432
+ renderedLines: string[],
433
+ renderedRow: number,
434
+ visibleLines: string[],
435
+ visibleRow: number,
436
+ ): number {
437
+ let score = renderedLines[renderedRow] === visibleLines[visibleRow] ? 100 : 50;
438
+ for (const direction of [-1, 1]) {
439
+ for (let distance = 1; distance <= 4; distance++) {
440
+ const candidate = renderedLines[renderedRow + direction * distance];
441
+ const visible = visibleLines[visibleRow + direction * distance];
442
+ if (candidate === undefined || visible === undefined || candidate !== visible) break;
443
+ score += 5 - distance;
444
+ }
445
+ }
446
+ return score;
447
+ }
448
+
415
449
  function findToolAtFixedEditorRow(
416
450
  tui: any,
417
- bufferRow: number,
451
+ visibleRow: number,
418
452
  previousLines: string[],
419
453
  width: number,
420
454
  ): ToolRenderHit | null {
421
- if (bufferRow < 0 || bufferRow >= previousLines.length) return null;
422
- const clickedLine = stripTerminalSequences(String(previousLines[bufferRow] ?? ""));
455
+ if (visibleRow < 0 || visibleRow >= previousLines.length) return null;
456
+ const visibleLines = previousLines.map((line) => stripTerminalSequences(String(line)));
457
+ const clickedLine = visibleLines[visibleRow] ?? "";
423
458
  if (!clickedLine) return null;
424
459
 
425
- const hits: ToolRenderHit[] = [];
426
- collectToolRenderHits(tui, 0, width, hits, new WeakMap<object, number>(), new Set<object>());
427
- const clickedOccurrence = previousLines
428
- .slice(0, bufferRow + 1)
429
- .filter((line) => stripTerminalSequences(String(line)) === clickedLine)
430
- .length;
431
- let matchingOccurrence = 0;
432
-
433
- for (const hit of hits) {
434
- const component = hit.component;
460
+ const tools: any[] = [];
461
+ collectToolComponents(tui, tools);
462
+ let best: { hit: ToolRenderHit; score: number } | null = null;
463
+ for (const component of tools) {
435
464
  const expanded = Boolean(component.expanded);
436
465
  if (!expanded && !/(?:expand|\/ click)/i.test(clickedLine)) continue;
437
- const renderedLines = renderComponentTree(component, width);
438
- const matchingLines = renderedLines.filter((line) => {
439
- const rendered = stripTerminalSequences(String(line));
440
- return rendered === clickedLine
441
- || (clickedLine.length >= 8 && (rendered.includes(clickedLine) || clickedLine.includes(rendered)));
442
- }).length;
443
- if (matchingLines === 0) continue;
444
- matchingOccurrence += matchingLines;
445
- if (clickedOccurrence <= matchingOccurrence) return hit;
466
+ const renderedLines = renderComponentTree(component, width)
467
+ .map((line) => stripTerminalSequences(String(line)));
468
+ for (let renderedRow = 0; renderedRow < renderedLines.length; renderedRow++) {
469
+ if (!fixedEditorLineMatch(renderedLines[renderedRow] ?? "", clickedLine)) continue;
470
+ const score = fixedEditorContextScore(renderedLines, renderedRow, visibleLines, visibleRow);
471
+ if (!best || score > best.score) {
472
+ best = {
473
+ hit: { component, start: visibleRow, end: visibleRow + renderedLines.length },
474
+ score,
475
+ };
476
+ }
477
+ }
446
478
  }
447
- return null;
479
+ return best?.hit ?? null;
448
480
  }
449
481
 
450
482
  function findToolAtScreenRow(tui: any, screenRow: number): ToolRenderHit | null {
451
483
  const previousLines = Array.isArray(tui?.previousLines) ? tui.previousLines : [];
452
- const viewportTop = Number.isFinite(tui?.previousViewportTop) ? tui.previousViewportTop : 0;
453
- const bufferRow = viewportTop + screenRow - 1;
454
484
  const width = Math.max(1, Number(tui?.terminal?.columns) || 80);
455
485
  if (isFixedEditorTui(tui)) {
456
- return findToolAtFixedEditorRow(tui, bufferRow, previousLines, width);
486
+ // Zentui replaces Pi's root render with the already-sliced visible
487
+ // transcript. previousViewportTop remains cursor bookkeeping and must not
488
+ // be added to a physical mouse row here.
489
+ return findToolAtFixedEditorRow(tui, screenRow - 1, previousLines, width);
457
490
  }
491
+ const viewportTop = Number.isFinite(tui?.previousViewportTop) ? tui.previousViewportTop : 0;
492
+ const bufferRow = viewportTop + screenRow - 1;
458
493
  if (bufferRow < 0 || bufferRow >= previousLines.length) return null;
459
494
 
460
495
  const hits: ToolRenderHit[] = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cc-extensions",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "Personal extensions for the Pi coding agent",
5
5
  "type": "module",
6
6
  "keywords": [