pi-diff-review 0.1.5 → 0.1.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-diff-review",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Local diff review TUI extension for pi",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,6 +38,12 @@ export class ReviewComponent {
38
38
  private layout: ReviewLayout = "side-by-side";
39
39
  private diffRenderMode: DiffRenderMode = "unified";
40
40
  private editor: Editor;
41
+ private splitRows?: SplitDiffRow[];
42
+ private splitRowByLineIndex?: number[];
43
+ private lineIndexById = new Map<string, number>();
44
+ private commentLineKeys = new Map<number, string[]>();
45
+ private commentsRevision = 0;
46
+ private commentLineKeysRevision = -1;
41
47
 
42
48
  constructor(
43
49
  private tui: ReviewTui,
@@ -49,6 +55,7 @@ export class ReviewComponent {
49
55
  ) {
50
56
  const firstCommentable = this.lines.findIndex((line) => line.commentable);
51
57
  this.selected = firstCommentable >= 0 ? firstCommentable : 0;
58
+ this.lines.forEach((line, index) => this.lineIndexById.set(line.id, index));
52
59
 
53
60
  this.editor = new Editor(tui as never, {
54
61
  borderColor: (s) => theme.fg("accent", s),
@@ -71,12 +78,13 @@ export class ReviewComponent {
71
78
  const trimmed = value.trim();
72
79
  const key = this.getSelectionKey(selection.start, selection.end);
73
80
  if (!trimmed) {
74
- this.comments.delete(key);
81
+ if (this.comments.delete(key)) this.markCommentsChanged();
75
82
  } else {
76
83
  this.comments.set(
77
84
  key,
78
85
  this.buildCommentFromSelection(selection, trimmed),
79
86
  );
87
+ this.markCommentsChanged();
80
88
  }
81
89
 
82
90
  this.exitEditMode();
@@ -276,7 +284,7 @@ export class ReviewComponent {
276
284
  }
277
285
 
278
286
  private renderSplitDiffRows(width: number, height: number): string[] {
279
- const rows = this.buildSplitDiffRows();
287
+ const rows = this.getSplitDiffRows();
280
288
  const output: string[] = [];
281
289
  const separatorWidth = 3;
282
290
  const leftWidth = Math.max(10, Math.floor((width - separatorWidth) / 2));
@@ -345,17 +353,25 @@ export class ReviewComponent {
345
353
  invalidate(): void {}
346
354
 
347
355
  private move(delta: number): void {
348
- this.selected = Math.max(
356
+ const next = Math.max(
349
357
  0,
350
358
  Math.min(this.lines.length - 1, this.selected + delta),
351
359
  );
360
+ if (next === this.selected) return;
361
+ this.selected = next;
352
362
  this.tui.requestRender();
353
363
  }
354
364
 
355
365
  private jumpToBoundary(boundary: "start" | "end"): void {
356
- this.selected =
357
- boundary === "start" ? 0 : Math.max(0, this.lines.length - 1);
358
- this.clearSelection();
366
+ const next = boundary === "start" ? 0 : Math.max(0, this.lines.length - 1);
367
+ const hadSelection = this.selectionAnchor != null;
368
+ if (next === this.selected && !hadSelection) return;
369
+ this.selected = next;
370
+ if (hadSelection) {
371
+ this.clearSelection();
372
+ } else {
373
+ this.tui.requestRender();
374
+ }
359
375
  }
360
376
 
361
377
  private toggleLayout(): void {
@@ -383,14 +399,17 @@ export class ReviewComponent {
383
399
  if (this.selectionAnchor == null) {
384
400
  this.selectionAnchor = this.selected;
385
401
  }
386
- this.selected = Math.max(
402
+ const next = Math.max(
387
403
  0,
388
404
  Math.min(this.lines.length - 1, this.selected + delta),
389
405
  );
406
+ if (next === this.selected) return;
407
+ this.selected = next;
390
408
  this.tui.requestRender();
391
409
  }
392
410
 
393
411
  private clearSelection(): void {
412
+ if (this.selectionAnchor == null) return;
394
413
  this.selectionAnchor = undefined;
395
414
  this.tui.requestRender();
396
415
  }
@@ -431,19 +450,35 @@ export class ReviewComponent {
431
450
  }
432
451
 
433
452
  private getCommentKeysForLine(index: number): string[] {
434
- const line = this.lines[index];
435
- if (!line) return [];
436
- return [...this.comments.entries()]
437
- .filter(([, comment]) => {
438
- const start = this.lines.findIndex(
439
- (item) => item.id === comment.startLineId,
440
- );
441
- const end = this.lines.findIndex(
442
- (item) => item.id === comment.endLineId,
443
- );
444
- return start !== -1 && end !== -1 && index >= start && index <= end;
445
- })
446
- .map(([key]) => key);
453
+ this.ensureCommentLineKeys();
454
+ return this.commentLineKeys.get(index) ?? [];
455
+ }
456
+
457
+ private markCommentsChanged(): void {
458
+ this.commentsRevision++;
459
+ }
460
+
461
+ private ensureCommentLineKeys(): void {
462
+ if (this.commentLineKeysRevision === this.commentsRevision) return;
463
+
464
+ this.commentLineKeys = new Map<number, string[]>();
465
+ for (const [key, comment] of this.comments) {
466
+ const start = this.lineIndexById.get(comment.startLineId);
467
+ const end = this.lineIndexById.get(comment.endLineId);
468
+ if (start == null || end == null) continue;
469
+ const from = Math.min(start, end);
470
+ const to = Math.max(start, end);
471
+ for (let index = from; index <= to; index++) {
472
+ const keys = this.commentLineKeys.get(index);
473
+ if (keys) {
474
+ keys.push(key);
475
+ } else {
476
+ this.commentLineKeys.set(index, [key]);
477
+ }
478
+ }
479
+ }
480
+
481
+ this.commentLineKeysRevision = this.commentsRevision;
447
482
  }
448
483
 
449
484
  private buildCommentFromSelection(
@@ -503,7 +538,11 @@ export class ReviewComponent {
503
538
  private deleteComment(): void {
504
539
  const selection = this.getActiveCommentSelection();
505
540
  if (!selection) return;
506
- this.comments.delete(this.getSelectionKey(selection.start, selection.end));
541
+ if (
542
+ this.comments.delete(this.getSelectionKey(selection.start, selection.end))
543
+ ) {
544
+ this.markCommentsChanged();
545
+ }
507
546
  this.tui.requestRender();
508
547
  }
509
548
 
@@ -532,10 +571,24 @@ export class ReviewComponent {
532
571
  this.tui.requestRender(true);
533
572
  }
534
573
 
535
- private buildSplitDiffRows(): SplitDiffRow[] {
574
+ private getSplitDiffRows(): SplitDiffRow[] {
575
+ if (this.splitRows) return this.splitRows;
576
+
536
577
  const rows: SplitDiffRow[] = [];
578
+ const rowByLineIndex: number[] = [];
537
579
  let index = 0;
538
580
 
581
+ const pushRow = (row: SplitDiffRow) => {
582
+ const displayRow = rows.length;
583
+ rows.push(row);
584
+ if (row.kind === "full") {
585
+ rowByLineIndex[row.cell.index] = displayRow;
586
+ } else {
587
+ if (row.left) rowByLineIndex[row.left.index] = displayRow;
588
+ if (row.right) rowByLineIndex[row.right.index] = displayRow;
589
+ }
590
+ };
591
+
539
592
  while (index < this.lines.length) {
540
593
  const line = this.lines[index]!;
541
594
 
@@ -554,7 +607,7 @@ export class ReviewComponent {
554
607
 
555
608
  const count = Math.max(removals.length, additions.length);
556
609
  for (let offset = 0; offset < count; offset++) {
557
- rows.push({
610
+ pushRow({
558
611
  kind: "split",
559
612
  left: removals[offset],
560
613
  right: additions[offset],
@@ -565,32 +618,28 @@ export class ReviewComponent {
565
618
 
566
619
  if (line.kind === "context") {
567
620
  const cell = { line, index };
568
- rows.push({ kind: "split", left: cell, right: cell });
621
+ pushRow({ kind: "split", left: cell, right: cell });
569
622
  } else {
570
- rows.push({ kind: "full", cell: { line, index } });
623
+ pushRow({ kind: "full", cell: { line, index } });
571
624
  }
572
625
  index++;
573
626
  }
574
627
 
628
+ this.splitRows = rows;
629
+ this.splitRowByLineIndex = rowByLineIndex;
575
630
  return rows;
576
631
  }
577
632
 
578
633
  private getSelectedDisplayRow(): number {
579
634
  if (this.diffRenderMode === "unified") return this.selected;
580
- const rows = this.buildSplitDiffRows();
581
- const row = rows.findIndex((item) =>
582
- item.kind === "full"
583
- ? item.cell.index === this.selected
584
- : item.left?.index === this.selected ||
585
- item.right?.index === this.selected,
586
- );
587
- return row === -1 ? 0 : row;
635
+ this.getSplitDiffRows();
636
+ return this.splitRowByLineIndex?.[this.selected] ?? 0;
588
637
  }
589
638
 
590
639
  private getDisplayRowCount(): number {
591
640
  return this.diffRenderMode === "unified"
592
641
  ? this.lines.length
593
- : this.buildSplitDiffRows().length;
642
+ : this.getSplitDiffRows().length;
594
643
  }
595
644
 
596
645
  private renderSplitDiffCell(