pi-diff-review 0.1.10 → 0.1.12

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
@@ -20,6 +20,13 @@ Or install directly from GitHub:
20
20
  pi install https://github.com/cmpadden/pi-diff-review
21
21
  ```
22
22
 
23
+ For local development, clone the repository and install from the local path:
24
+
25
+ ```bash
26
+ git clone https://github.com/cmpadden/pi-diff-review
27
+ pi install ./pi-diff-review
28
+ ```
29
+
23
30
  ## Features
24
31
 
25
32
  - `/diff` reviews the current unstaged `git diff`
@@ -39,6 +46,21 @@ pi install https://github.com/cmpadden/pi-diff-review
39
46
  - Comments are cached per session and restored when reopening the same diff
40
47
  - `q` to exit
41
48
 
49
+ ## Development
50
+
51
+ Install [pre-commit](https://pre-commit.com/) and enable the repository hooks to run typechecking, tests, and formatting checks before each commit:
52
+
53
+ ```bash
54
+ pre-commit install
55
+ ```
56
+
57
+ Run the same checks manually with either:
58
+
59
+ ```bash
60
+ pre-commit run --all-files
61
+ npm run precommit
62
+ ```
63
+
42
64
  ## Release
43
65
 
44
66
  See [RELEASE.md](./RELEASE.md).
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "pi-diff-review",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Local diff review TUI extension for pi",
5
5
  "license": "MIT",
6
+ "type": "module",
6
7
  "repository": {
7
8
  "type": "git",
8
9
  "url": "git+https://github.com/cmpadden/pi-diff-review.git"
@@ -26,16 +27,30 @@
26
27
  "LICENSE"
27
28
  ],
28
29
  "scripts": {
29
- "format": "prettier --write ."
30
+ "check": "npm run typecheck && npm test",
31
+ "format": "prettier --write .",
32
+ "precommit": "npm run check && npx prettier --check .",
33
+ "test": "node --import tsx --test test/**/*.test.ts",
34
+ "typecheck": "tsc --noEmit"
30
35
  },
31
36
  "devDependencies": {
32
- "prettier": "^3.5.3"
37
+ "@earendil-works/pi-ai": "^0.74.0",
38
+ "@earendil-works/pi-coding-agent": "^0.74.1",
39
+ "@earendil-works/pi-tui": "^0.74.1",
40
+ "@types/node": "^24.12.4",
41
+ "prettier": "^3.5.3",
42
+ "tsx": "^4.22.1",
43
+ "typescript": "^6.0.3"
33
44
  },
34
45
  "peerDependencies": {
46
+ "@earendil-works/pi-ai": "*",
35
47
  "@earendil-works/pi-coding-agent": "*",
36
48
  "@earendil-works/pi-tui": "*"
37
49
  },
38
50
  "peerDependenciesMeta": {
51
+ "@earendil-works/pi-ai": {
52
+ "optional": true
53
+ },
39
54
  "@earendil-works/pi-coding-agent": {
40
55
  "optional": true
41
56
  },
@@ -46,10 +61,10 @@
46
61
  "pi": {
47
62
  "extensions": [
48
63
  "./extensions"
49
- ]
64
+ ],
65
+ "image": "https://github.com/user-attachments/assets/3fd00163-5d19-489b-94ed-3d4816c6cad3"
50
66
  },
51
67
  "dependencies": {
52
- "@earendil-works/pi-ai": "^0.74.0",
53
68
  "@pierre/diffs": "^1.1.21"
54
69
  }
55
70
  }
@@ -1,3 +1,7 @@
1
+ import {
2
+ getLanguageFromPath,
3
+ highlightCode,
4
+ } from "@earendil-works/pi-coding-agent";
1
5
  import {
2
6
  Editor,
3
7
  matchesKey,
@@ -58,6 +62,7 @@ export class ReviewComponent {
58
62
  private commentLineKeys = new Map<number, string[]>();
59
63
  private commentsRevision = 0;
60
64
  private commentLineKeysRevision = -1;
65
+ private highlightedLineCache = new Map<string, string>();
61
66
 
62
67
  constructor(
63
68
  private tui: ReviewTui,
@@ -389,7 +394,9 @@ export class ReviewComponent {
389
394
  return { diffHeight, commentsHeight };
390
395
  }
391
396
 
392
- invalidate(): void {}
397
+ invalidate(): void {
398
+ this.highlightedLineCache.clear();
399
+ }
393
400
 
394
401
  dispose(): void {
395
402
  this.explanationAbort?.abort();
@@ -728,22 +735,8 @@ export class ReviewComponent {
728
735
  const commentMark = hasComment ? this.theme.fg("warning", "●") : " ";
729
736
  const lineNumber =
730
737
  side === "left" ? line.oldLineNumber : line.newLineNumber;
731
- const raw = `${commentMark} ${lineNumberCell(lineNumber)} ${this.getDisplayText(line)}`;
732
-
733
- let styled: string;
734
- switch (line.kind) {
735
- case "add":
736
- styled = this.theme.fg("toolDiffAdded", raw);
737
- break;
738
- case "remove":
739
- styled = this.theme.fg("toolDiffRemoved", raw);
740
- break;
741
- case "context":
742
- styled = this.theme.fg("toolDiffContext", raw);
743
- break;
744
- default:
745
- styled = this.theme.fg("muted", raw);
746
- }
738
+ const prefix = `${commentMark} ${lineNumberCell(lineNumber)} `;
739
+ let styled = this.renderDiffRowContent(line, prefix);
747
740
 
748
741
  styled = truncateToWidth(styled, width);
749
742
  const selection = this.getSelectionBounds();
@@ -752,7 +745,7 @@ export class ReviewComponent {
752
745
  if (index === this.selected || inSelection) {
753
746
  return this.theme.bg("selectedBg", padToWidth(styled, width));
754
747
  }
755
- return styled;
748
+ return this.applyDiffBackground(line, styled, width);
756
749
  }
757
750
 
758
751
  private ensureScroll(viewportHeight: number): void {
@@ -779,43 +772,75 @@ export class ReviewComponent {
779
772
  : line.text;
780
773
  }
781
774
 
782
- private renderDiffLine(
775
+ private applyDiffBackground(
783
776
  line: ReviewLine,
784
- index: number,
777
+ styled: string,
785
778
  width: number,
786
- selected: boolean,
787
- selection?: SelectionBounds,
788
779
  ): string {
789
- const hasComment = this.getCommentKeysForLine(index).length > 0;
790
- const commentMark = hasComment ? this.theme.fg("warning", "●") : " ";
791
- const numbers = `${lineNumberCell(line.oldLineNumber)} ${lineNumberCell(line.newLineNumber)}`;
792
- const raw = `${commentMark} ${numbers} ${this.getDisplayText(line)}`;
780
+ if (line.kind === "add") {
781
+ return this.theme.bg("toolSuccessBg", padToWidth(styled, width));
782
+ }
783
+ if (line.kind === "remove") {
784
+ return this.theme.bg("toolErrorBg", padToWidth(styled, width));
785
+ }
786
+ return styled;
787
+ }
793
788
 
794
- let styled: string;
789
+ private renderDiffRowContent(line: ReviewLine, prefix: string): string {
795
790
  switch (line.kind) {
796
791
  case "add":
797
- styled = this.theme.fg("toolDiffAdded", raw);
798
- break;
792
+ return `${this.theme.fg("toolDiffAdded", prefix)}${this.getHighlightedDisplayText(line)}`;
799
793
  case "remove":
800
- styled = this.theme.fg("toolDiffRemoved", raw);
801
- break;
794
+ return `${this.theme.fg("toolDiffRemoved", prefix)}${this.getHighlightedDisplayText(line)}`;
802
795
  case "context":
803
- styled = this.theme.fg("toolDiffContext", raw);
804
- break;
796
+ return `${this.theme.fg("toolDiffContext", prefix)}${this.getHighlightedDisplayText(line)}`;
805
797
  case "hunk":
806
- styled = this.theme.fg("accent", raw);
807
- break;
798
+ return this.theme.fg("accent", `${prefix}${this.getDisplayText(line)}`);
808
799
  default:
809
- styled = this.theme.fg("muted", raw);
800
+ return this.theme.fg("muted", `${prefix}${this.getDisplayText(line)}`);
801
+ }
802
+ }
803
+
804
+ private getHighlightedDisplayText(line: ReviewLine): string {
805
+ const code = this.getDisplayText(line);
806
+ if (!code) return code;
807
+
808
+ const lang = line.filePath ? getLanguageFromPath(line.filePath) : undefined;
809
+ const cacheKey = `${line.id}\0${lang ?? ""}\0${code}`;
810
+ const cached = this.highlightedLineCache.get(cacheKey);
811
+ if (cached != null) return cached;
812
+
813
+ let highlighted = code;
814
+ try {
815
+ highlighted = highlightCode(code, lang)[0] ?? code;
816
+ } catch {
817
+ highlighted = code;
810
818
  }
811
819
 
820
+ this.highlightedLineCache.set(cacheKey, highlighted);
821
+ return highlighted;
822
+ }
823
+
824
+ private renderDiffLine(
825
+ line: ReviewLine,
826
+ index: number,
827
+ width: number,
828
+ selected: boolean,
829
+ selection?: SelectionBounds,
830
+ ): string {
831
+ const hasComment = this.getCommentKeysForLine(index).length > 0;
832
+ const commentMark = hasComment ? this.theme.fg("warning", "●") : " ";
833
+ const numbers = `${lineNumberCell(line.oldLineNumber)} ${lineNumberCell(line.newLineNumber)}`;
834
+ const prefix = `${commentMark} ${numbers} `;
835
+ let styled = this.renderDiffRowContent(line, prefix);
836
+
812
837
  styled = truncateToWidth(styled, width);
813
838
  const inSelection =
814
839
  selection != null && index >= selection.start && index <= selection.end;
815
840
  if (selected || inSelection) {
816
841
  return this.theme.bg("selectedBg", padToWidth(styled, width));
817
842
  }
818
- return styled;
843
+ return this.applyDiffBackground(line, styled, width);
819
844
  }
820
845
 
821
846
  private renderRightPane(