pi-diff-review 0.1.14 → 0.1.15
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 +1 -0
- package/package.json +1 -1
- package/src/review-component.ts +135 -3
package/README.md
CHANGED
|
@@ -68,6 +68,7 @@ Review a branch or commit range by passing any `git diff` arguments after `/diff
|
|
|
68
68
|
- `t` toggles inline comments/explanations
|
|
69
69
|
- `v` toggles the diff between unified and side-by-side split rendering
|
|
70
70
|
- `?` toggles an AI-generated explanation for the current hunk
|
|
71
|
+
- `/` searches diff lines; `n/N` moves between matches while a search is active
|
|
71
72
|
- `J/K` to extend a highlighted selection into a comment range
|
|
72
73
|
- `esc` clears the active selection, or exits review when no selection is active
|
|
73
74
|
- `n/p` to jump hunks
|
package/package.json
CHANGED
package/src/review-component.ts
CHANGED
|
@@ -54,6 +54,10 @@ export class ReviewComponent {
|
|
|
54
54
|
private navigation: ReviewNavigationState;
|
|
55
55
|
private editMode = false;
|
|
56
56
|
private editingCommentKey?: string;
|
|
57
|
+
private searchMode = false;
|
|
58
|
+
private searchQuery = "";
|
|
59
|
+
private draftSearchQuery = "";
|
|
60
|
+
private searchMessage = "";
|
|
57
61
|
|
|
58
62
|
private inlineAnnotationsVisible = true;
|
|
59
63
|
private visibleExplanationKeys = new Set<string>();
|
|
@@ -179,8 +183,15 @@ export class ReviewComponent {
|
|
|
179
183
|
return;
|
|
180
184
|
}
|
|
181
185
|
|
|
186
|
+
if (this.searchMode) {
|
|
187
|
+
this.handleSearchInput(data);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
182
191
|
if (matchesKey(data, "escape")) {
|
|
183
|
-
if (this.
|
|
192
|
+
if (this.searchQuery) {
|
|
193
|
+
this.clearSearch();
|
|
194
|
+
} else if (this.hasSelection()) {
|
|
184
195
|
this.clearSelection();
|
|
185
196
|
} else {
|
|
186
197
|
this.done({ action: "cancel" });
|
|
@@ -203,6 +214,10 @@ export class ReviewComponent {
|
|
|
203
214
|
this.toggleExplanationPane();
|
|
204
215
|
return;
|
|
205
216
|
}
|
|
217
|
+
if (data === "/") {
|
|
218
|
+
this.startSearchMode();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
206
221
|
if (matchesKey(data, "ctrl+d")) {
|
|
207
222
|
this.move(this.getPageMoveAmount());
|
|
208
223
|
return;
|
|
@@ -236,7 +251,15 @@ export class ReviewComponent {
|
|
|
236
251
|
return;
|
|
237
252
|
}
|
|
238
253
|
if (data === "n") {
|
|
239
|
-
this.
|
|
254
|
+
if (this.searchQuery) {
|
|
255
|
+
this.jumpSearch(1);
|
|
256
|
+
} else {
|
|
257
|
+
this.jumpHunk(1);
|
|
258
|
+
}
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (data === "N" && this.searchQuery) {
|
|
262
|
+
this.jumpSearch(-1);
|
|
240
263
|
return;
|
|
241
264
|
}
|
|
242
265
|
if (data === "p") {
|
|
@@ -277,7 +300,7 @@ export class ReviewComponent {
|
|
|
277
300
|
? `${this.title} • ${this.lines.length} lines • ${this.comments.size} comments • editing inline comment • Enter save • Esc/Ctrl+C cancel`
|
|
278
301
|
: this.hasSelection()
|
|
279
302
|
? `${this.title} • ${this.lines.length} lines • ${this.comments.size} comments • J/K extend • Esc clear selection • c comment range • C overall comment • Enter submit`
|
|
280
|
-
: `${this.title} • ${this.lines.length} lines • ${this.comments.size} comments • ${this.getPositionText(selectedLine)} • inline ${this.inlineAnnotationsVisible ? "shown" : "hidden"} • j/k move • g/G top/bottom • ctrl-u/d page • t annotations • v unified/split • ? explain • J/K extend • c comment • C overall • x delete • n/p hunk • Enter submit • q quit`,
|
|
303
|
+
: `${this.title} • ${this.lines.length} lines • ${this.comments.size} comments • ${this.getPositionText(selectedLine)} • inline ${this.inlineAnnotationsVisible ? "shown" : "hidden"} • j/k move • g/G top/bottom • ctrl-u/d page • / search • t annotations • v unified/split • ? explain • J/K extend • c comment • C overall • x delete • ${this.searchQuery ? "n/N search" : "n/p hunk"} • Enter submit • q quit`,
|
|
281
304
|
),
|
|
282
305
|
width,
|
|
283
306
|
),
|
|
@@ -846,6 +869,13 @@ export class ReviewComponent {
|
|
|
846
869
|
}
|
|
847
870
|
|
|
848
871
|
private getFooterText(selectedLine?: ReviewLine): string {
|
|
872
|
+
if (this.searchMode) {
|
|
873
|
+
return `Search: /${this.draftSearchQuery} • Enter jump • Esc cancel`;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const searchStatus = this.getSearchStatusText();
|
|
877
|
+
if (searchStatus) return searchStatus;
|
|
878
|
+
|
|
849
879
|
const selection = this.getSelectionBounds();
|
|
850
880
|
if (selection) {
|
|
851
881
|
const count = selection.end - selection.start + 1;
|
|
@@ -856,6 +886,21 @@ export class ReviewComponent {
|
|
|
856
886
|
return `Selected: ${selectedLine ? formatLocation(selectedLine) : "(no selection)"}`;
|
|
857
887
|
}
|
|
858
888
|
|
|
889
|
+
private getSearchStatusText(): string {
|
|
890
|
+
if (this.searchMessage) return this.searchMessage;
|
|
891
|
+
if (!this.searchQuery) return "";
|
|
892
|
+
|
|
893
|
+
const matches = this.getSearchMatchIndexes(this.searchQuery);
|
|
894
|
+
if (matches.length === 0) return `No matches for /${this.searchQuery}`;
|
|
895
|
+
|
|
896
|
+
const current = matches.findIndex((index) => index === this.selected);
|
|
897
|
+
const position =
|
|
898
|
+
current >= 0
|
|
899
|
+
? `${current + 1}/${matches.length}`
|
|
900
|
+
: `${matches.length} matches`;
|
|
901
|
+
return `Search /${this.searchQuery} • ${position} • n next • N previous • Esc clear search`;
|
|
902
|
+
}
|
|
903
|
+
|
|
859
904
|
private jumpHunk(direction: 1 | -1): void {
|
|
860
905
|
let index = this.selected + direction;
|
|
861
906
|
while (index >= 0 && index < this.lines.length) {
|
|
@@ -919,6 +964,93 @@ export class ReviewComponent {
|
|
|
919
964
|
this.tui.requestRender(true);
|
|
920
965
|
}
|
|
921
966
|
|
|
967
|
+
private startSearchMode(): void {
|
|
968
|
+
this.searchMode = true;
|
|
969
|
+
this.draftSearchQuery = this.searchQuery;
|
|
970
|
+
this.searchMessage = "";
|
|
971
|
+
this.tui.requestRender();
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
private handleSearchInput(data: string): void {
|
|
975
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
976
|
+
this.searchMode = false;
|
|
977
|
+
this.draftSearchQuery = "";
|
|
978
|
+
this.tui.requestRender();
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
if (matchesKey(data, "enter")) {
|
|
983
|
+
const query = this.draftSearchQuery.trim();
|
|
984
|
+
this.searchMode = false;
|
|
985
|
+
this.draftSearchQuery = "";
|
|
986
|
+
this.searchQuery = query;
|
|
987
|
+
this.searchMessage = "";
|
|
988
|
+
if (query) {
|
|
989
|
+
this.jumpSearch(1);
|
|
990
|
+
} else {
|
|
991
|
+
this.tui.requestRender();
|
|
992
|
+
}
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
if (matchesKey(data, "ctrl+u")) {
|
|
997
|
+
this.draftSearchQuery = "";
|
|
998
|
+
this.tui.requestRender();
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
if (matchesKey(data, "backspace") || data === "\u007f" || data === "\b") {
|
|
1003
|
+
this.draftSearchQuery = this.draftSearchQuery.slice(0, -1);
|
|
1004
|
+
this.tui.requestRender();
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if (data.length === 1 && data >= " " && data !== "\u007f") {
|
|
1009
|
+
this.draftSearchQuery += data;
|
|
1010
|
+
this.tui.requestRender();
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
private clearSearch(): void {
|
|
1015
|
+
this.searchMode = false;
|
|
1016
|
+
this.searchQuery = "";
|
|
1017
|
+
this.draftSearchQuery = "";
|
|
1018
|
+
this.searchMessage = "";
|
|
1019
|
+
this.tui.requestRender();
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
private jumpSearch(direction: 1 | -1): void {
|
|
1023
|
+
const query = this.searchQuery.trim();
|
|
1024
|
+
if (!query) return;
|
|
1025
|
+
|
|
1026
|
+
const matches = this.getSearchMatchIndexes(query);
|
|
1027
|
+
if (matches.length === 0) {
|
|
1028
|
+
this.searchMessage = `No matches for /${query}`;
|
|
1029
|
+
this.tui.requestRender();
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
this.searchMessage = "";
|
|
1034
|
+
const current =
|
|
1035
|
+
direction === 1
|
|
1036
|
+
? matches.find((index) => index > this.selected)
|
|
1037
|
+
: [...matches].reverse().find((index) => index < this.selected);
|
|
1038
|
+
this.selected =
|
|
1039
|
+
current ?? (direction === 1 ? matches[0]! : matches[matches.length - 1]!);
|
|
1040
|
+
this.tui.requestRender();
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
private getSearchMatchIndexes(query: string): number[] {
|
|
1044
|
+
const needle = query.toLocaleLowerCase();
|
|
1045
|
+
if (!needle) return [];
|
|
1046
|
+
|
|
1047
|
+
const matches: number[] = [];
|
|
1048
|
+
this.lines.forEach((line, index) => {
|
|
1049
|
+
if (line.text.toLocaleLowerCase().includes(needle)) matches.push(index);
|
|
1050
|
+
});
|
|
1051
|
+
return matches;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
922
1054
|
private getSplitDiffRows(): SplitDiffRow[] {
|
|
923
1055
|
if (this.splitRows) return this.splitRows;
|
|
924
1056
|
|