@xuhaojun/githunk 0.3.0 → 0.3.1
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/dist/githunk.js +1234 -367
- package/package.json +6 -6
package/dist/githunk.js
CHANGED
|
@@ -12,7 +12,7 @@ import { Command, CommanderError } from "commander";
|
|
|
12
12
|
// package.json
|
|
13
13
|
var package_default = {
|
|
14
14
|
name: "@xuhaojun/githunk",
|
|
15
|
-
version: "0.3.
|
|
15
|
+
version: "0.3.1",
|
|
16
16
|
description: "A review-first Git TUI combining lazygit's everyday Git workflow with focused hunk review.",
|
|
17
17
|
type: "module",
|
|
18
18
|
bin: {
|
|
@@ -15961,9 +15961,8 @@ function isWorkerAvailable() {
|
|
|
15961
15961
|
}
|
|
15962
15962
|
|
|
15963
15963
|
// src/ui/review-workspace/ReviewWorkspaceApp.tsx
|
|
15964
|
-
import {
|
|
15965
|
-
import {
|
|
15966
|
-
import { useCallback, useEffect as useEffect3, useLayoutEffect as useLayoutEffect2, useMemo as useMemo4, useRef as useRef2, useState as useState3, useSyncExternalStore } from "react";
|
|
15964
|
+
import { useKeyboard as useKeyboard2, useTerminalDimensions } from "@opentui/react";
|
|
15965
|
+
import { useCallback, useEffect as useEffect3, useLayoutEffect as useLayoutEffect3, useMemo as useMemo5, useRef as useRef3, useState as useState4, useSyncExternalStore } from "react";
|
|
15967
15966
|
|
|
15968
15967
|
// src/review/core/selectors.ts
|
|
15969
15968
|
function resolveViewedRecord(file, viewed) {
|
|
@@ -16124,16 +16123,23 @@ function reviewHeaderLines(state, width) {
|
|
|
16124
16123
|
totalDeletions = null;
|
|
16125
16124
|
const additionsText = totalAdditions === null ? "—" : `+${totalAdditions}`;
|
|
16126
16125
|
const deletionsText = totalDeletions === null ? "—" : `−${totalDeletions}`;
|
|
16127
|
-
const projectionLabel = "Aggregate";
|
|
16128
|
-
const
|
|
16129
|
-
const
|
|
16126
|
+
const projectionLabel = state.projection.kind === "since-last-review" ? "Since last review" : state.projection.kind === "commit" ? `Commit ${state.projection.oid.slice(0, 7)}` : "Aggregate";
|
|
16127
|
+
const baseBudget = Math.min(cellWidth(baseLabel), Math.max(1, Math.floor(w / 2)));
|
|
16128
|
+
const headPrefix = w > baseBudget + 3 ? `${truncateCell(headLabel, w - baseBudget - 3)} → ` : "";
|
|
16129
|
+
const baseText = truncateCell(baseLabel, Math.max(0, w - cellWidth(headPrefix)));
|
|
16130
|
+
const suffix = ` · ${commits} commits · ${files} files · ${additionsText} ${deletionsText} [${projectionLabel}]`;
|
|
16131
|
+
const suffixText = truncateCell(suffix, Math.max(0, w - cellWidth(headPrefix) - cellWidth(baseText)));
|
|
16130
16132
|
const reviewedLabel = `Reviewed ${progress.viewed}/${progress.total}`;
|
|
16131
16133
|
const changedPart = progress.changed > 0 ? ` · ${progress.changed} changed` : "";
|
|
16132
16134
|
const pendingPart = progress.pending > 0 ? ` · ${progress.pending} pending` : progress.pending === 0 ? " · 0 pending" : "";
|
|
16133
16135
|
const reviewingPart = progress.reviewing > 0 ? ` · ${progress.reviewing} reviewing` : "";
|
|
16134
16136
|
const line2Raw = `${reviewedLabel}${changedPart}${reviewingPart}${pendingPart}`;
|
|
16135
16137
|
const line2 = truncateCell(line2Raw, w);
|
|
16136
|
-
const line1Spans = [
|
|
16138
|
+
const line1Spans = [
|
|
16139
|
+
{ text: headPrefix, style: "strong" },
|
|
16140
|
+
{ text: baseText, style: "strong", action: "choose-base" },
|
|
16141
|
+
{ text: suffixText, style: "strong" }
|
|
16142
|
+
];
|
|
16137
16143
|
const line2Spans = [{ text: line2, style: "dim" }];
|
|
16138
16144
|
return [line1Spans, line2Spans];
|
|
16139
16145
|
}
|
|
@@ -16368,6 +16374,22 @@ var REVIEW_COMMANDS = [
|
|
|
16368
16374
|
available: always,
|
|
16369
16375
|
hint: "help"
|
|
16370
16376
|
},
|
|
16377
|
+
{
|
|
16378
|
+
id: "review.toggleSinceLastReview",
|
|
16379
|
+
title: "Review only what changed since the last review",
|
|
16380
|
+
keys: ["s"],
|
|
16381
|
+
focus: ["any"],
|
|
16382
|
+
available: (state) => state.projection.kind !== "aggregate" || state.lastSubmission !== null,
|
|
16383
|
+
hint: "since"
|
|
16384
|
+
},
|
|
16385
|
+
{
|
|
16386
|
+
id: "review.chooseBase",
|
|
16387
|
+
title: "Change base branch",
|
|
16388
|
+
keys: ["B"],
|
|
16389
|
+
focus: ["any"],
|
|
16390
|
+
available: always,
|
|
16391
|
+
hint: "base"
|
|
16392
|
+
},
|
|
16371
16393
|
{
|
|
16372
16394
|
id: "review.close",
|
|
16373
16395
|
title: "Close overlay or workspace",
|
|
@@ -16383,7 +16405,7 @@ for (const cmd of REVIEW_COMMANDS) {
|
|
|
16383
16405
|
if (!keyToCommand.has(k))
|
|
16384
16406
|
keyToCommand.set(k, cmd);
|
|
16385
16407
|
const lower = k.toLowerCase();
|
|
16386
|
-
if (lower !== k && !keyToCommand.has(lower))
|
|
16408
|
+
if (k.length > 1 && lower !== k && !keyToCommand.has(lower))
|
|
16387
16409
|
keyToCommand.set(lower, cmd);
|
|
16388
16410
|
}
|
|
16389
16411
|
}
|
|
@@ -16406,7 +16428,8 @@ function reviewHelp(focus, state) {
|
|
|
16406
16428
|
"review.focusDiff": 0,
|
|
16407
16429
|
"review.focusFiles": 1,
|
|
16408
16430
|
"review.toggleFocus": 2,
|
|
16409
|
-
"review.layoutCycle": 3
|
|
16431
|
+
"review.layoutCycle": 3,
|
|
16432
|
+
"review.chooseBase": 4
|
|
16410
16433
|
};
|
|
16411
16434
|
const ordered = [...available].sort((a, b) => (panelPriority[a.id] ?? 99) - (panelPriority[b.id] ?? 99));
|
|
16412
16435
|
const lines = ordered.map((c) => `${c.keys.map((key) => key === "tab" ? "Tab" : key).join("/")} ${c.title}`);
|
|
@@ -16770,7 +16793,7 @@ function hunkHeaderText(file, index) {
|
|
|
16770
16793
|
const hunk = file.metadata.hunks[index];
|
|
16771
16794
|
if (!hunk)
|
|
16772
16795
|
return "@@";
|
|
16773
|
-
return hunk.hunkSpecs ?? `@@ -${hunk.deletionStart},${hunk.deletionCount} +${hunk.additionStart},${hunk.additionCount} @@`;
|
|
16796
|
+
return hunk.hunkSpecs?.replace(/\r?\n$/u, "") ?? `@@ -${hunk.deletionStart},${hunk.deletionCount} +${hunk.additionStart},${hunk.additionCount} @@`;
|
|
16774
16797
|
}
|
|
16775
16798
|
function hunkGapBefore(file, hunkIndex) {
|
|
16776
16799
|
if (hunkIndex <= 0)
|
|
@@ -17377,8 +17400,114 @@ function ReviewDiffSection({
|
|
|
17377
17400
|
});
|
|
17378
17401
|
}
|
|
17379
17402
|
|
|
17403
|
+
// src/ui/review-workspace/components/ReviewStickyHeader.tsx
|
|
17404
|
+
import { StyledText as StyledText8, parseColor as parseColor3 } from "@opentui/core";
|
|
17405
|
+
import { jsx as jsx3 } from "@opentui/react/jsx-runtime";
|
|
17406
|
+
var STICKY_BACKGROUND = "#2b3138";
|
|
17407
|
+
var PATH_FOREGROUND = "#e0e2e4";
|
|
17408
|
+
var HUNK_FOREGROUND = "#7aa6da";
|
|
17409
|
+
var colorCache3 = new Map;
|
|
17410
|
+
function color2(value) {
|
|
17411
|
+
const cached = colorCache3.get(value);
|
|
17412
|
+
if (cached)
|
|
17413
|
+
return cached;
|
|
17414
|
+
const parsed = parseColor3(value);
|
|
17415
|
+
colorCache3.set(value, parsed);
|
|
17416
|
+
return parsed;
|
|
17417
|
+
}
|
|
17418
|
+
function chunk2(text, fg5) {
|
|
17419
|
+
return { __isChunk: true, text, fg: color2(fg5), bg: color2(STICKY_BACKGROUND) };
|
|
17420
|
+
}
|
|
17421
|
+
function fit(text, width) {
|
|
17422
|
+
if (width <= 0)
|
|
17423
|
+
return { text: "", used: 0 };
|
|
17424
|
+
let out = "";
|
|
17425
|
+
let used = 0;
|
|
17426
|
+
for (const character of text) {
|
|
17427
|
+
const characterWidth = cellWidth(character);
|
|
17428
|
+
if (used + characterWidth > width)
|
|
17429
|
+
break;
|
|
17430
|
+
out += character;
|
|
17431
|
+
used += characterWidth;
|
|
17432
|
+
}
|
|
17433
|
+
return { text: out, used };
|
|
17434
|
+
}
|
|
17435
|
+
function stickyHeaderChunks(sticky, width) {
|
|
17436
|
+
if (width <= 0)
|
|
17437
|
+
return [];
|
|
17438
|
+
const chunks = [];
|
|
17439
|
+
const hunkText2 = sticky.hunkText ?? "";
|
|
17440
|
+
const hunkWidth = hunkText2.length === 0 ? 0 : Math.min(width, cellWidth(hunkText2) + 2);
|
|
17441
|
+
const path = fit(sticky.filePath, Math.max(0, width - hunkWidth));
|
|
17442
|
+
if (path.text.length > 0)
|
|
17443
|
+
chunks.push(chunk2(path.text, PATH_FOREGROUND));
|
|
17444
|
+
let used = path.used;
|
|
17445
|
+
if (hunkText2.length > 0 && used < width) {
|
|
17446
|
+
const separator = fit(" ", width - used);
|
|
17447
|
+
if (separator.text.length > 0) {
|
|
17448
|
+
chunks.push(chunk2(separator.text, PATH_FOREGROUND));
|
|
17449
|
+
used += separator.used;
|
|
17450
|
+
}
|
|
17451
|
+
const hunk = fit(hunkText2, width - used);
|
|
17452
|
+
if (hunk.text.length > 0) {
|
|
17453
|
+
chunks.push(chunk2(hunk.text, HUNK_FOREGROUND));
|
|
17454
|
+
used += hunk.used;
|
|
17455
|
+
}
|
|
17456
|
+
}
|
|
17457
|
+
if (used < width)
|
|
17458
|
+
chunks.push(chunk2(" ".repeat(width - used), PATH_FOREGROUND));
|
|
17459
|
+
return chunks;
|
|
17460
|
+
}
|
|
17461
|
+
function ReviewStickyHeader({ sticky, width }) {
|
|
17462
|
+
return /* @__PURE__ */ jsx3("box", {
|
|
17463
|
+
id: "review-sticky-header",
|
|
17464
|
+
style: { width: "100%", height: 1, flexShrink: 0, backgroundColor: STICKY_BACKGROUND },
|
|
17465
|
+
children: /* @__PURE__ */ jsx3("text", {
|
|
17466
|
+
content: new StyledText8([...stickyHeaderChunks(sticky, Math.max(0, width))]),
|
|
17467
|
+
wrapMode: "none",
|
|
17468
|
+
truncate: true
|
|
17469
|
+
})
|
|
17470
|
+
});
|
|
17471
|
+
}
|
|
17472
|
+
|
|
17473
|
+
// src/ui/review-workspace/sticky-header.ts
|
|
17474
|
+
function headerTopOf(sectionOffsets, index) {
|
|
17475
|
+
return (sectionOffsets[index] ?? 0) + (index > 0 ? 1 : 0);
|
|
17476
|
+
}
|
|
17477
|
+
function sectionIndexAt(sectionOffsets, fileCount, top) {
|
|
17478
|
+
let index = 0;
|
|
17479
|
+
while (index < fileCount - 1 && headerTopOf(sectionOffsets, index + 1) < top)
|
|
17480
|
+
index += 1;
|
|
17481
|
+
return index;
|
|
17482
|
+
}
|
|
17483
|
+
function resolveStickyDiffHeader(request) {
|
|
17484
|
+
const { files, state, layout, sectionOffsets, expandedSourceByGap } = request;
|
|
17485
|
+
if (files.length === 0)
|
|
17486
|
+
return;
|
|
17487
|
+
const top = Math.max(0, Math.floor(request.scrollTop));
|
|
17488
|
+
const fileIndex = sectionIndexAt(sectionOffsets, files.length, top);
|
|
17489
|
+
const file = files[fileIndex];
|
|
17490
|
+
if (!file)
|
|
17491
|
+
return;
|
|
17492
|
+
const localRow = top - (sectionOffsets[fileIndex] ?? 0);
|
|
17493
|
+
const showDivider = fileIndex > 0;
|
|
17494
|
+
let hunkIndex = -1;
|
|
17495
|
+
for (let candidate = 0;candidate < file.metadata.hunks.length; candidate += 1) {
|
|
17496
|
+
const headerRow = hunkSectionRowOffset(file, layout, candidate, state, expandedSourceByGap, showDivider);
|
|
17497
|
+
if (headerRow > localRow)
|
|
17498
|
+
break;
|
|
17499
|
+
hunkIndex = candidate;
|
|
17500
|
+
}
|
|
17501
|
+
return {
|
|
17502
|
+
fileKey: file.id,
|
|
17503
|
+
filePath: file.path,
|
|
17504
|
+
hunkIndex,
|
|
17505
|
+
...hunkIndex < 0 ? {} : { hunkText: hunkHeaderText(file, hunkIndex) }
|
|
17506
|
+
};
|
|
17507
|
+
}
|
|
17508
|
+
|
|
17380
17509
|
// src/ui/review-workspace/components/ReviewDiffPane.tsx
|
|
17381
|
-
import { jsx as
|
|
17510
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "@opentui/react/jsx-runtime";
|
|
17382
17511
|
function sectionWindow(files, state, layout, scrollTop, viewportHeight, overscan, expandedSourceByGap) {
|
|
17383
17512
|
const heights = [];
|
|
17384
17513
|
const offsets = [0];
|
|
@@ -17412,6 +17541,7 @@ function ReviewDiffPane({
|
|
|
17412
17541
|
selectedHunkIndex,
|
|
17413
17542
|
showLineNumbers = true,
|
|
17414
17543
|
wrapLines = false,
|
|
17544
|
+
showStickyHeader = true,
|
|
17415
17545
|
overscan = Math.max(10, height * 2),
|
|
17416
17546
|
highlightByFileKey,
|
|
17417
17547
|
expandedSourceByGap,
|
|
@@ -17430,12 +17560,14 @@ function ReviewDiffPane({
|
|
|
17430
17560
|
const ownedScrollRef = useRef(null);
|
|
17431
17561
|
const scrollRef = externalScrollRef ?? ownedScrollRef;
|
|
17432
17562
|
const [scrollTop, setScrollTop] = useState(0);
|
|
17563
|
+
const stickyRows = showStickyHeader && files.length > 0 ? 1 : 0;
|
|
17564
|
+
const viewportHeight = Math.max(1, height - stickyRows);
|
|
17433
17565
|
const previousFileRevealTokenRef = useRef(selectedFileRevealToken);
|
|
17434
17566
|
const previousSelectionRef = useRef(null);
|
|
17435
17567
|
const previousHunkRevealTokenRef = useRef(undefined);
|
|
17436
17568
|
const pendingSelectionRevealRequestRef = useRef(null);
|
|
17437
17569
|
const pendingSelectionRevealTimersRef = useRef([]);
|
|
17438
|
-
const window = useMemo2(() => sectionWindow(files, state, layout, scrollTop,
|
|
17570
|
+
const window = useMemo2(() => sectionWindow(files, state, layout, scrollTop, viewportHeight, overscan, expandedSourceByGap), [expandedSourceByGap, files, layout, overscan, scrollTop, state.expandedGaps, state.feedback, viewportHeight]);
|
|
17439
17571
|
useEffect(() => {
|
|
17440
17572
|
if (!onVisibleFileKeysChange)
|
|
17441
17573
|
return;
|
|
@@ -17474,13 +17606,13 @@ function ReviewDiffPane({
|
|
|
17474
17606
|
if (state.reveal.scrollToFeedback)
|
|
17475
17607
|
return;
|
|
17476
17608
|
const sectionTop = window.offsets[index] ?? 0;
|
|
17477
|
-
const target = Math.min(Math.max(0, sectionTop), Math.max(0, window.total -
|
|
17609
|
+
const target = Math.min(Math.max(0, sectionTop), Math.max(0, window.total - viewportHeight));
|
|
17478
17610
|
const scrollBox = scrollRef.current;
|
|
17479
17611
|
if (scrollBox)
|
|
17480
17612
|
scrollBox.scrollTop = target;
|
|
17481
17613
|
setScrollTop(target);
|
|
17482
17614
|
onViewportChange?.(target);
|
|
17483
|
-
}, [files,
|
|
17615
|
+
}, [files, onViewportChange, scrollRef, selectedFileKey, selectedFileRevealToken, state.reveal.scrollToFeedback, viewportHeight, window]);
|
|
17484
17616
|
useLayoutEffect(() => {
|
|
17485
17617
|
const currentSelection = { fileKey: selectedFileKey, hunkIndex: selectedHunkIndex };
|
|
17486
17618
|
const previousSelection = previousSelectionRef.current;
|
|
@@ -17540,11 +17672,11 @@ function ReviewDiffPane({
|
|
|
17540
17672
|
const scrollBox = scrollRef.current;
|
|
17541
17673
|
if (!scrollBox)
|
|
17542
17674
|
return;
|
|
17543
|
-
const
|
|
17675
|
+
const measuredHeight = Math.max(1, Math.floor(scrollBox.viewport.height || viewportHeight));
|
|
17544
17676
|
const currentTop = Math.max(0, Math.floor(scrollBox.scrollTop));
|
|
17545
|
-
const currentEnd = currentTop +
|
|
17677
|
+
const currentEnd = currentTop + measuredHeight;
|
|
17546
17678
|
if (target < currentTop || target + 1 > currentEnd) {
|
|
17547
|
-
const nextTop = Math.min(Math.max(0, target), Math.max(0, window.total -
|
|
17679
|
+
const nextTop = Math.min(Math.max(0, target), Math.max(0, window.total - measuredHeight));
|
|
17548
17680
|
scrollBox.scrollTop = nextTop;
|
|
17549
17681
|
setScrollTop(nextTop);
|
|
17550
17682
|
onViewportChange?.(nextTop);
|
|
@@ -17563,73 +17695,323 @@ function ReviewDiffPane({
|
|
|
17563
17695
|
}
|
|
17564
17696
|
}, delay));
|
|
17565
17697
|
return clearPendingTimers;
|
|
17566
|
-
}, [expandedSourceByGap, files,
|
|
17698
|
+
}, [expandedSourceByGap, files, layout, onViewportChange, selectedFileKey, selectedFileRevealToken, selectedHunkIndex, selectedHunkRevealToken, state, viewportHeight]);
|
|
17699
|
+
const sticky = useMemo2(() => stickyRows === 0 ? undefined : resolveStickyDiffHeader({
|
|
17700
|
+
files,
|
|
17701
|
+
state,
|
|
17702
|
+
layout,
|
|
17703
|
+
scrollTop,
|
|
17704
|
+
sectionOffsets: window.offsets,
|
|
17705
|
+
...expandedSourceByGap ? { expandedSourceByGap } : {}
|
|
17706
|
+
}), [expandedSourceByGap, files, layout, scrollTop, state, stickyRows, window.offsets]);
|
|
17567
17707
|
const leadingSpacer = window.offsets[window.first] ?? 0;
|
|
17568
17708
|
const trailingSpacer = window.total - (window.offsets[window.last + 1] ?? window.total);
|
|
17569
|
-
return /* @__PURE__ */
|
|
17570
|
-
id: "review-diff-
|
|
17571
|
-
|
|
17572
|
-
|
|
17573
|
-
|
|
17574
|
-
|
|
17575
|
-
|
|
17576
|
-
|
|
17577
|
-
|
|
17578
|
-
|
|
17579
|
-
|
|
17580
|
-
|
|
17581
|
-
|
|
17582
|
-
|
|
17583
|
-
|
|
17584
|
-
|
|
17585
|
-
|
|
17586
|
-
|
|
17587
|
-
|
|
17588
|
-
|
|
17589
|
-
|
|
17590
|
-
|
|
17709
|
+
return /* @__PURE__ */ jsxs3("box", {
|
|
17710
|
+
id: "review-diff-pane",
|
|
17711
|
+
style: { width: "100%", height: "100%", flexDirection: "column" },
|
|
17712
|
+
children: [
|
|
17713
|
+
sticky ? /* @__PURE__ */ jsx4(ReviewStickyHeader, {
|
|
17714
|
+
sticky,
|
|
17715
|
+
width
|
|
17716
|
+
}) : null,
|
|
17717
|
+
/* @__PURE__ */ jsx4("scrollbox", {
|
|
17718
|
+
id: "review-diff-scrollbox",
|
|
17719
|
+
ref: scrollRef,
|
|
17720
|
+
...focused === undefined ? {} : { focused },
|
|
17721
|
+
width: "100%",
|
|
17722
|
+
flexGrow: 1,
|
|
17723
|
+
minHeight: 0,
|
|
17724
|
+
scrollY: true,
|
|
17725
|
+
viewportCulling: true,
|
|
17726
|
+
verticalScrollbarOptions: { visible: false },
|
|
17727
|
+
onMouseScroll: (event) => {
|
|
17728
|
+
const direction = event.scroll?.direction;
|
|
17729
|
+
const delta = Math.max(1, Math.floor(event.scroll?.delta ?? 1));
|
|
17730
|
+
const target = event.currentTarget;
|
|
17731
|
+
const scrollBox = target && typeof target.scrollBy === "function" ? target : scrollRef.current;
|
|
17732
|
+
if (direction === "down")
|
|
17733
|
+
scrollBox?.scrollBy(delta);
|
|
17734
|
+
else if (direction === "up")
|
|
17735
|
+
scrollBox?.scrollBy(-delta);
|
|
17736
|
+
},
|
|
17737
|
+
children: /* @__PURE__ */ jsxs3("box", {
|
|
17738
|
+
id: "review-diff-content",
|
|
17739
|
+
style: { width: "100%", flexDirection: "column" },
|
|
17740
|
+
children: [
|
|
17741
|
+
leadingSpacer > 0 ? /* @__PURE__ */ jsx4("box", {
|
|
17742
|
+
style: { width: "100%", height: leadingSpacer }
|
|
17743
|
+
}, "review-leading-spacer") : null,
|
|
17744
|
+
window.first <= window.last ? files.slice(window.first, window.last + 1).map((file, offset) => {
|
|
17745
|
+
const fileIndex = window.first + offset;
|
|
17746
|
+
const sectionTop = window.offsets[fileIndex] ?? 0;
|
|
17747
|
+
const sectionHeight = window.heights[fileIndex] ?? 0;
|
|
17748
|
+
const rowStart = Math.max(0, Math.floor(scrollTop - sectionTop - overscan));
|
|
17749
|
+
const rowEnd = Math.min(sectionHeight, Math.ceil(scrollTop + viewportHeight + overscan - sectionTop));
|
|
17750
|
+
const highlight = highlightByFileKey?.get(file.id);
|
|
17751
|
+
const select = onSelectFile ? () => onSelectFile(file.id) : undefined;
|
|
17752
|
+
return /* @__PURE__ */ jsx4(ReviewDiffSection, {
|
|
17753
|
+
file,
|
|
17754
|
+
state,
|
|
17755
|
+
layout,
|
|
17756
|
+
width,
|
|
17757
|
+
selectedHunkIndex: file.id === selectedFileKey ? selectedHunkIndex : -1,
|
|
17758
|
+
showDivider: fileIndex > 0,
|
|
17759
|
+
showLineNumbers,
|
|
17760
|
+
wrapLines,
|
|
17761
|
+
rowStart,
|
|
17762
|
+
rowEnd: Math.max(rowStart, rowEnd),
|
|
17763
|
+
...highlight ? { highlight } : {},
|
|
17764
|
+
...expandedSourceByGap ? { expandedSourceByGap } : {},
|
|
17765
|
+
...select ? { onSelect: select } : {},
|
|
17766
|
+
...onSelectFeedback ? { onSelectFeedback } : {},
|
|
17767
|
+
...onSelectDiffAddress ? { onSelectDiffAddress } : {},
|
|
17768
|
+
...selectedFeedbackId !== undefined ? { selectedFeedbackId } : {},
|
|
17769
|
+
...onToggleGap ? { onToggleGap: (gapId) => onToggleGap(file.id, gapId) } : {}
|
|
17770
|
+
}, file.id);
|
|
17771
|
+
}) : null,
|
|
17772
|
+
trailingSpacer > 0 ? /* @__PURE__ */ jsx4("box", {
|
|
17773
|
+
style: { width: "100%", height: trailingSpacer }
|
|
17774
|
+
}, "review-trailing-spacer") : null
|
|
17775
|
+
]
|
|
17776
|
+
})
|
|
17777
|
+
})
|
|
17778
|
+
]
|
|
17779
|
+
});
|
|
17780
|
+
}
|
|
17781
|
+
|
|
17782
|
+
// src/ui/review-workspace/components/ReviewBasePicker.tsx
|
|
17783
|
+
import { useKeyboard } from "@opentui/react";
|
|
17784
|
+
import { useLayoutEffect as useLayoutEffect2, useMemo as useMemo3, useRef as useRef2, useState as useState2 } from "react";
|
|
17785
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "@opentui/react/jsx-runtime";
|
|
17786
|
+
function consume(event) {
|
|
17787
|
+
event.preventDefault();
|
|
17788
|
+
event.stopPropagation();
|
|
17789
|
+
}
|
|
17790
|
+
function ReviewBasePicker({ selection, width, height, active: active2, warning, onChoose, onCancel, onRetry }) {
|
|
17791
|
+
const [query, setQuery] = useState2("");
|
|
17792
|
+
const [selectedIndex, setSelectedIndex] = useState2(0);
|
|
17793
|
+
const inputRef = useRef2(null);
|
|
17794
|
+
const scrollRef = useRef2(null);
|
|
17795
|
+
const busy = selection.loading || selection.selecting;
|
|
17796
|
+
const candidates = useMemo3(() => {
|
|
17797
|
+
const filter = query.trim().toLowerCase();
|
|
17798
|
+
return filter ? selection.candidates.filter((candidate) => candidate.label.toLowerCase().includes(filter) || candidate.ref.toLowerCase().includes(filter)) : selection.candidates;
|
|
17799
|
+
}, [query, selection.candidates]);
|
|
17800
|
+
const index = Math.min(selectedIndex, Math.max(0, candidates.length - 1));
|
|
17801
|
+
const selected = candidates[index];
|
|
17802
|
+
const dialogWidth = Math.max(1, Math.min(90, width - (width > 20 ? 4 : 0)));
|
|
17803
|
+
const dialogHeight = Math.max(1, Math.min(20, height - (height > 10 ? 2 : 0)));
|
|
17804
|
+
const border = dialogWidth >= 4 && dialogHeight >= 5;
|
|
17805
|
+
const showDetail = dialogHeight >= 9;
|
|
17806
|
+
const contentWidth = Math.max(1, dialogWidth - (border ? 2 : 0));
|
|
17807
|
+
const warningHeight = warning && dialogHeight >= 9 ? 1 : 0;
|
|
17808
|
+
const listHeight = Math.max(1, dialogHeight - (border ? 2 : 0) - 3 - (showDetail ? 1 : 0) - warningHeight);
|
|
17809
|
+
const reveal = (next) => {
|
|
17810
|
+
const scroll = scrollRef.current;
|
|
17811
|
+
if (!scroll)
|
|
17812
|
+
return;
|
|
17813
|
+
const viewportHeight = Math.max(1, Math.floor(scroll.viewport.height || listHeight));
|
|
17814
|
+
if (next < scroll.scrollTop)
|
|
17815
|
+
scroll.scrollTop = next;
|
|
17816
|
+
else if (next >= scroll.scrollTop + viewportHeight)
|
|
17817
|
+
scroll.scrollTop = next - viewportHeight + 1;
|
|
17818
|
+
};
|
|
17819
|
+
useLayoutEffect2(() => {
|
|
17820
|
+
reveal(index);
|
|
17821
|
+
}, [index, candidates, listHeight]);
|
|
17822
|
+
useLayoutEffect2(() => {
|
|
17823
|
+
inputRef.current?.blur();
|
|
17824
|
+
}, [active2, busy]);
|
|
17825
|
+
const move = (direction) => {
|
|
17826
|
+
if (busy)
|
|
17827
|
+
return;
|
|
17828
|
+
const next = Math.max(0, Math.min(candidates.length - 1, index + (direction === "down" ? 1 : -1)));
|
|
17829
|
+
setSelectedIndex(next);
|
|
17830
|
+
reveal(next);
|
|
17831
|
+
};
|
|
17832
|
+
const submit = () => {
|
|
17833
|
+
if (busy)
|
|
17834
|
+
return;
|
|
17835
|
+
if (selected)
|
|
17836
|
+
onChoose(selected.ref);
|
|
17837
|
+
else if (selection.error)
|
|
17838
|
+
onRetry();
|
|
17839
|
+
};
|
|
17840
|
+
const handlePickerKey = (event) => {
|
|
17841
|
+
const name = event.name.toLowerCase();
|
|
17842
|
+
if (busy) {
|
|
17843
|
+
consume(event);
|
|
17844
|
+
return true;
|
|
17845
|
+
}
|
|
17846
|
+
if (name === "escape") {
|
|
17847
|
+
consume(event);
|
|
17848
|
+
onCancel();
|
|
17849
|
+
return true;
|
|
17850
|
+
}
|
|
17851
|
+
if (name === "up" || name === "down") {
|
|
17852
|
+
move(name);
|
|
17853
|
+
consume(event);
|
|
17854
|
+
return true;
|
|
17855
|
+
}
|
|
17856
|
+
if (name === "return" || name === "enter") {
|
|
17857
|
+
consume(event);
|
|
17858
|
+
submit();
|
|
17859
|
+
return true;
|
|
17860
|
+
}
|
|
17861
|
+
if (event.ctrl && name === "r") {
|
|
17862
|
+
consume(event);
|
|
17863
|
+
onRetry();
|
|
17864
|
+
return true;
|
|
17865
|
+
}
|
|
17866
|
+
if (name === "tab") {
|
|
17867
|
+
consume(event);
|
|
17868
|
+
return true;
|
|
17869
|
+
}
|
|
17870
|
+
if (name === "backspace") {
|
|
17871
|
+
consume(event);
|
|
17872
|
+
setQuery((previous) => previous.length > 0 ? previous.slice(0, -1) : previous);
|
|
17873
|
+
setSelectedIndex(0);
|
|
17874
|
+
if (scrollRef.current)
|
|
17875
|
+
scrollRef.current.scrollTop = 0;
|
|
17876
|
+
return true;
|
|
17877
|
+
}
|
|
17878
|
+
if (!event.ctrl && !event.meta) {
|
|
17879
|
+
const char = name === "space" ? " " : name;
|
|
17880
|
+
if ([...char].length === 1) {
|
|
17881
|
+
const code = char.codePointAt(0) ?? 0;
|
|
17882
|
+
if (code >= 32 && code !== 127) {
|
|
17883
|
+
consume(event);
|
|
17884
|
+
setQuery((previous) => `${previous}${char}`);
|
|
17885
|
+
setSelectedIndex(0);
|
|
17886
|
+
if (scrollRef.current)
|
|
17887
|
+
scrollRef.current.scrollTop = 0;
|
|
17888
|
+
return true;
|
|
17889
|
+
}
|
|
17890
|
+
}
|
|
17891
|
+
}
|
|
17892
|
+
return false;
|
|
17893
|
+
};
|
|
17894
|
+
useKeyboard((event) => {
|
|
17895
|
+
if (!active2)
|
|
17896
|
+
return;
|
|
17897
|
+
handlePickerKey(event);
|
|
17898
|
+
});
|
|
17899
|
+
const status = selection.loading ? "Loading branches…" : selection.selecting ? "Loading review…" : selection.error ? `Error: ${selection.error}` : selection.candidates.length === 0 ? "No branches available. Ctrl-R retry." : candidates.length === 0 ? "No matching branches." : `${candidates.length} branches · recommendations first`;
|
|
17900
|
+
return /* @__PURE__ */ jsx5("box", {
|
|
17901
|
+
id: "review-base-backdrop",
|
|
17902
|
+
onMouse: consume,
|
|
17903
|
+
style: { position: "absolute", left: 0, top: 0, width, height, zIndex: 100, backgroundColor: "#151515" },
|
|
17904
|
+
children: /* @__PURE__ */ jsxs4("box", {
|
|
17905
|
+
id: "review-base-picker",
|
|
17906
|
+
style: { position: "absolute", left: Math.floor((width - dialogWidth) / 2), top: Math.floor((height - dialogHeight) / 2), width: dialogWidth, height: dialogHeight, border, borderColor: "#b9ca4a", flexDirection: "column", backgroundColor: "#202020", overflow: "hidden" },
|
|
17591
17907
|
children: [
|
|
17592
|
-
|
|
17593
|
-
|
|
17594
|
-
|
|
17595
|
-
|
|
17596
|
-
|
|
17597
|
-
|
|
17598
|
-
|
|
17599
|
-
|
|
17600
|
-
|
|
17601
|
-
|
|
17602
|
-
|
|
17603
|
-
|
|
17604
|
-
|
|
17605
|
-
|
|
17606
|
-
|
|
17607
|
-
|
|
17608
|
-
|
|
17609
|
-
|
|
17610
|
-
|
|
17611
|
-
|
|
17612
|
-
|
|
17613
|
-
|
|
17614
|
-
|
|
17615
|
-
|
|
17616
|
-
|
|
17617
|
-
|
|
17618
|
-
|
|
17619
|
-
|
|
17620
|
-
|
|
17621
|
-
|
|
17908
|
+
/* @__PURE__ */ jsx5("text", {
|
|
17909
|
+
content: "Choose base branch",
|
|
17910
|
+
wrapMode: "none",
|
|
17911
|
+
truncate: true
|
|
17912
|
+
}),
|
|
17913
|
+
/* @__PURE__ */ jsx5("input", {
|
|
17914
|
+
id: "review-base-filter",
|
|
17915
|
+
ref: inputRef,
|
|
17916
|
+
width: contentWidth,
|
|
17917
|
+
value: query,
|
|
17918
|
+
placeholder: "Filter branches…",
|
|
17919
|
+
focused: false,
|
|
17920
|
+
onInput: (value) => {
|
|
17921
|
+
if (busy)
|
|
17922
|
+
return;
|
|
17923
|
+
setQuery(value);
|
|
17924
|
+
setSelectedIndex(0);
|
|
17925
|
+
if (scrollRef.current)
|
|
17926
|
+
scrollRef.current.scrollTop = 0;
|
|
17927
|
+
},
|
|
17928
|
+
onKeyDown: (event) => {
|
|
17929
|
+
if (handlePickerKey(event))
|
|
17930
|
+
return;
|
|
17931
|
+
},
|
|
17932
|
+
onSubmit: () => {
|
|
17933
|
+
submit();
|
|
17934
|
+
}
|
|
17935
|
+
}),
|
|
17936
|
+
/* @__PURE__ */ jsx5("scrollbox", {
|
|
17937
|
+
id: "review-base-list",
|
|
17938
|
+
ref: scrollRef,
|
|
17939
|
+
width: "100%",
|
|
17940
|
+
height: listHeight,
|
|
17941
|
+
flexShrink: 0,
|
|
17942
|
+
scrollY: true,
|
|
17943
|
+
viewportCulling: true,
|
|
17944
|
+
verticalScrollbarOptions: { visible: false },
|
|
17945
|
+
children: /* @__PURE__ */ jsxs4("box", {
|
|
17946
|
+
style: { width: "100%", flexDirection: "column" },
|
|
17947
|
+
children: [
|
|
17948
|
+
candidates.length === 0 ? /* @__PURE__ */ jsx5("text", {
|
|
17949
|
+
content: status,
|
|
17950
|
+
wrapMode: "none",
|
|
17951
|
+
truncate: true
|
|
17952
|
+
}) : null,
|
|
17953
|
+
candidates.map((candidate, candidateIndex) => /* @__PURE__ */ jsx5("box", {
|
|
17954
|
+
id: `review-base-row:${candidate.ref}`,
|
|
17955
|
+
style: { width: "100%", height: 1, flexShrink: 0, backgroundColor: index === candidateIndex ? "#365f8a" : "#202020" },
|
|
17956
|
+
onMouseUp: (event) => {
|
|
17957
|
+
consume(event);
|
|
17958
|
+
if (busy)
|
|
17959
|
+
return;
|
|
17960
|
+
setSelectedIndex(candidateIndex);
|
|
17961
|
+
onChoose(candidate.ref);
|
|
17962
|
+
},
|
|
17963
|
+
children: /* @__PURE__ */ jsx5("text", {
|
|
17964
|
+
content: `${index === candidateIndex ? ">" : " "} ${candidate.label}${candidate.reason ? ` — ${candidate.reason}` : ""}`,
|
|
17965
|
+
selectable: false,
|
|
17966
|
+
wrapMode: "none",
|
|
17967
|
+
truncate: true
|
|
17968
|
+
})
|
|
17969
|
+
}, candidate.ref))
|
|
17970
|
+
]
|
|
17971
|
+
})
|
|
17972
|
+
}),
|
|
17973
|
+
showDetail ? /* @__PURE__ */ jsx5("text", {
|
|
17974
|
+
id: "review-base-status",
|
|
17975
|
+
content: status,
|
|
17976
|
+
fg: selection.error ? "#f0c674" : "#b4b4b4",
|
|
17977
|
+
wrapMode: "none",
|
|
17978
|
+
truncate: true
|
|
17622
17979
|
}) : null,
|
|
17623
|
-
|
|
17624
|
-
|
|
17625
|
-
|
|
17980
|
+
warningHeight > 0 && warning ? /* @__PURE__ */ jsx5("text", {
|
|
17981
|
+
id: "review-base-warning",
|
|
17982
|
+
content: warning,
|
|
17983
|
+
fg: "#f0c674",
|
|
17984
|
+
wrapMode: "none",
|
|
17985
|
+
truncate: true
|
|
17986
|
+
}) : null,
|
|
17987
|
+
/* @__PURE__ */ jsxs4("box", {
|
|
17988
|
+
style: { width: "100%", height: 1, flexShrink: 0, flexDirection: "row" },
|
|
17989
|
+
children: [
|
|
17990
|
+
selection.error || selection.candidates.length === 0 ? /* @__PURE__ */ jsx5("box", {
|
|
17991
|
+
id: "review-base-retry",
|
|
17992
|
+
onMouseUp: (event) => {
|
|
17993
|
+
consume(event);
|
|
17994
|
+
if (!busy)
|
|
17995
|
+
onRetry();
|
|
17996
|
+
},
|
|
17997
|
+
children: /* @__PURE__ */ jsx5("text", {
|
|
17998
|
+
content: "[Ctrl-R retry] "
|
|
17999
|
+
})
|
|
18000
|
+
}) : null,
|
|
18001
|
+
/* @__PURE__ */ jsx5("text", {
|
|
18002
|
+
content: selection.error !== undefined || busy ? status : "↑↓ choose · Enter select · Esc cancel",
|
|
18003
|
+
wrapMode: "none",
|
|
18004
|
+
truncate: true
|
|
18005
|
+
})
|
|
18006
|
+
]
|
|
18007
|
+
})
|
|
17626
18008
|
]
|
|
17627
18009
|
})
|
|
17628
18010
|
});
|
|
17629
18011
|
}
|
|
17630
18012
|
|
|
17631
18013
|
// src/ui/review-workspace/hooks/useReviewHighlights.ts
|
|
17632
|
-
import { useEffect as useEffect2, useMemo as
|
|
18014
|
+
import { useEffect as useEffect2, useMemo as useMemo4, useState as useState3 } from "react";
|
|
17633
18015
|
|
|
17634
18016
|
// src/review/git/highlight/highlight-cache.ts
|
|
17635
18017
|
class HighlightCache {
|
|
@@ -18237,7 +18619,7 @@ function findPatchChunk(metadata, chunks, index) {
|
|
|
18237
18619
|
const byIndex = chunks[index];
|
|
18238
18620
|
if (byIndex)
|
|
18239
18621
|
return byIndex;
|
|
18240
|
-
return chunks.find((
|
|
18622
|
+
return chunks.find((chunk3) => [metadata.name, metadata.prevName].map((v) => normalizeDiffPath2(v)).filter((v) => Boolean(v)).map(stripPrefixes).some((path) => chunk3.includes(`a/${path}`) || chunk3.includes(`b/${path}`) || chunk3.includes(path))) ?? "";
|
|
18241
18623
|
}
|
|
18242
18624
|
function parseHunkHeader(line) {
|
|
18243
18625
|
const m = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
|
|
@@ -18249,8 +18631,8 @@ function parseHunkHeader(line) {
|
|
|
18249
18631
|
const newCount = m[4] !== undefined ? Number.parseInt(m[4], 10) : 1;
|
|
18250
18632
|
return { oldStart, oldCount, newStart, newCount };
|
|
18251
18633
|
}
|
|
18252
|
-
function extractHunksFromChunk(
|
|
18253
|
-
const lines =
|
|
18634
|
+
function extractHunksFromChunk(chunk3) {
|
|
18635
|
+
const lines = chunk3.split(`
|
|
18254
18636
|
`);
|
|
18255
18637
|
const hunks = [];
|
|
18256
18638
|
let currentHunk = null;
|
|
@@ -18400,10 +18782,10 @@ function collectHastHighlightRuns(node, appearance) {
|
|
|
18400
18782
|
}
|
|
18401
18783
|
const props = cur.properties ?? {};
|
|
18402
18784
|
const style = parseStyleValue(props["style"]);
|
|
18403
|
-
const
|
|
18785
|
+
const color3 = style.get("color");
|
|
18404
18786
|
const bg3 = style.get("background-color");
|
|
18405
18787
|
const isWordDiff = bg3 !== undefined && bg3 !== "transparent";
|
|
18406
|
-
const nextFg =
|
|
18788
|
+
const nextFg = color3 ?? fg5;
|
|
18407
18789
|
const nextWordDiff = wordDiff || isWordDiff;
|
|
18408
18790
|
const pushFg = nextFg;
|
|
18409
18791
|
const children = cur.children ?? [];
|
|
@@ -18590,9 +18972,9 @@ function useReviewHighlights(options) {
|
|
|
18590
18972
|
} = options;
|
|
18591
18973
|
const reviewId = options.reviewId ?? state?.document.identity.id ?? "review";
|
|
18592
18974
|
const generationId = options.generationId ?? state?.document.generation.id ?? "generation";
|
|
18593
|
-
const requestedKey =
|
|
18594
|
-
const [highlights, setHighlights] =
|
|
18595
|
-
const [loading, setLoading] =
|
|
18975
|
+
const requestedKey = useMemo4(() => requestedFileKeys?.join("\x00") ?? "", [requestedFileKeys]);
|
|
18976
|
+
const [highlights, setHighlights] = useState3(new Map);
|
|
18977
|
+
const [loading, setLoading] = useState3(false);
|
|
18596
18978
|
useEffect2(() => {
|
|
18597
18979
|
let cancelled = false;
|
|
18598
18980
|
if (!enabled || files.length === 0) {
|
|
@@ -19153,7 +19535,7 @@ function planReviewIntent(state, intent) {
|
|
|
19153
19535
|
}
|
|
19154
19536
|
|
|
19155
19537
|
// src/ui/review-workspace/ReviewWorkspaceApp.tsx
|
|
19156
|
-
import { jsx as
|
|
19538
|
+
import { jsx as jsx6, jsxs as jsxs5, Fragment } from "@opentui/react/jsx-runtime";
|
|
19157
19539
|
var REVIEW_SIDEBAR_DEFAULT_WIDTH = 30;
|
|
19158
19540
|
var REVIEW_SIDEBAR_MIN_WIDTH = 20;
|
|
19159
19541
|
var REVIEW_DIFF_MIN_CONTENT_WIDTH = 40;
|
|
@@ -19168,30 +19550,6 @@ var COLORS2 = {
|
|
|
19168
19550
|
changed: "#f0c674",
|
|
19169
19551
|
feedback: "#c397d8"
|
|
19170
19552
|
};
|
|
19171
|
-
var colorCache3 = new Map;
|
|
19172
|
-
function textChunk(text, style) {
|
|
19173
|
-
let fg5 = colorCache3.get(COLORS2[style]);
|
|
19174
|
-
if (!fg5) {
|
|
19175
|
-
fg5 = parseColor3(COLORS2[style]);
|
|
19176
|
-
colorCache3.set(COLORS2[style], fg5);
|
|
19177
|
-
}
|
|
19178
|
-
return { __isChunk: true, text, fg: fg5 };
|
|
19179
|
-
}
|
|
19180
|
-
function headerText(state, width, error) {
|
|
19181
|
-
const lines = [...reviewHeaderLines(state, width)];
|
|
19182
|
-
if (error)
|
|
19183
|
-
lines.push([{ text: `! ${error.title}: ${error.detail}`, style: "dim" }]);
|
|
19184
|
-
const chunks = [];
|
|
19185
|
-
for (const [index, line] of lines.entries()) {
|
|
19186
|
-
if (index > 0)
|
|
19187
|
-
chunks.push(textChunk(`
|
|
19188
|
-
`, "plain"));
|
|
19189
|
-
for (const span of line) {
|
|
19190
|
-
chunks.push(textChunk(span.text, span.style === "strong" ? "strong" : span.style === "dim" ? "dim" : "plain"));
|
|
19191
|
-
}
|
|
19192
|
-
}
|
|
19193
|
-
return new StyledText8(chunks);
|
|
19194
|
-
}
|
|
19195
19553
|
function fitText(text, width, overflowMarker = ".") {
|
|
19196
19554
|
if (cellWidth(text) <= width)
|
|
19197
19555
|
return text;
|
|
@@ -19216,7 +19574,14 @@ function padText(text, width) {
|
|
|
19216
19574
|
return text;
|
|
19217
19575
|
return text + " ".repeat(width - w);
|
|
19218
19576
|
}
|
|
19219
|
-
|
|
19577
|
+
var PROJECTION_NOTICES = {
|
|
19578
|
+
"no-previous-review": "Since last review needs a finished review to measure from",
|
|
19579
|
+
"history-rewritten": "History was rewritten since the last review — showing the full range",
|
|
19580
|
+
"already-projected": "A projection is already open",
|
|
19581
|
+
stale: "The review moved on while loading — try again",
|
|
19582
|
+
unavailable: "Not available right now"
|
|
19583
|
+
};
|
|
19584
|
+
function reviewFooter(state, layout, focus, notice) {
|
|
19220
19585
|
const selected = state.selection.fileKey ?? "none";
|
|
19221
19586
|
const entryFor = (id) => REVIEW_COMMANDS.find((candidate) => candidate.id === id);
|
|
19222
19587
|
const keyFor = (id) => {
|
|
@@ -19231,6 +19596,7 @@ function reviewFooter(state, layout, focus) {
|
|
|
19231
19596
|
command("review.focusFiles", true),
|
|
19232
19597
|
command("review.toggleFocus", true),
|
|
19233
19598
|
`${command("review.layoutCycle", true)}(${layout})`,
|
|
19599
|
+
command("review.chooseBase"),
|
|
19234
19600
|
pair("review.moveDown", "review.moveUp"),
|
|
19235
19601
|
pair("review.nextHunk", "review.prevHunk"),
|
|
19236
19602
|
pair("review.nextFile", "review.prevFile"),
|
|
@@ -19249,7 +19615,8 @@ function reviewFooter(state, layout, focus) {
|
|
|
19249
19615
|
command("review.help"),
|
|
19250
19616
|
command("review.close")
|
|
19251
19617
|
];
|
|
19252
|
-
|
|
19618
|
+
const trailer = `${focus} — ${selected}`;
|
|
19619
|
+
return notice ? `${notice} | ${hints.join(" | ")} | ${trailer}` : `${hints.join(" | ")} | ${trailer}`;
|
|
19253
19620
|
}
|
|
19254
19621
|
function feedbackDraftText(state) {
|
|
19255
19622
|
const draft = state.draft;
|
|
@@ -19293,7 +19660,7 @@ function isPrintableFilterKey(name) {
|
|
|
19293
19660
|
const codePoint = value.codePointAt(0) ?? 0;
|
|
19294
19661
|
return codePoint >= 32 && codePoint !== 127;
|
|
19295
19662
|
}
|
|
19296
|
-
function
|
|
19663
|
+
function consume2(event) {
|
|
19297
19664
|
try {
|
|
19298
19665
|
event.preventDefault?.();
|
|
19299
19666
|
} catch {}
|
|
@@ -19379,37 +19746,53 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
19379
19746
|
const active2 = session.active;
|
|
19380
19747
|
const onClose = session.onClose;
|
|
19381
19748
|
const finishDialog = session.finishDialog;
|
|
19382
|
-
const [publishedState
|
|
19749
|
+
const [publishedState] = useState4(() => controller.state);
|
|
19750
|
+
useEffect3(() => {
|
|
19751
|
+
let lastSelection = controller.baseSelection;
|
|
19752
|
+
let hadState = controller.state !== undefined;
|
|
19753
|
+
return controller.subscribe(() => {
|
|
19754
|
+
const nextSelection = controller.baseSelection;
|
|
19755
|
+
const hasState = controller.state !== undefined;
|
|
19756
|
+
if (nextSelection !== lastSelection || hasState !== hadState) {
|
|
19757
|
+
lastSelection = nextSelection;
|
|
19758
|
+
hadState = hasState;
|
|
19759
|
+
session.invalidate();
|
|
19760
|
+
}
|
|
19761
|
+
});
|
|
19762
|
+
}, [controller, session]);
|
|
19383
19763
|
const state = controller.state ?? publishedState;
|
|
19384
|
-
const
|
|
19385
|
-
const [
|
|
19386
|
-
const [
|
|
19387
|
-
const [
|
|
19388
|
-
const [
|
|
19389
|
-
const [
|
|
19390
|
-
const [
|
|
19391
|
-
const [
|
|
19392
|
-
const
|
|
19393
|
-
const [
|
|
19394
|
-
const
|
|
19395
|
-
const [
|
|
19396
|
-
const [
|
|
19397
|
-
const [
|
|
19398
|
-
const [
|
|
19399
|
-
const [
|
|
19400
|
-
const [
|
|
19401
|
-
const
|
|
19402
|
-
const
|
|
19403
|
-
const
|
|
19404
|
-
const
|
|
19405
|
-
const
|
|
19406
|
-
const
|
|
19407
|
-
const
|
|
19408
|
-
const
|
|
19409
|
-
const
|
|
19410
|
-
const
|
|
19411
|
-
const
|
|
19412
|
-
const
|
|
19764
|
+
const baseSelection = controller.baseSelection;
|
|
19765
|
+
const [layoutMode, setLayoutMode] = useState4("auto");
|
|
19766
|
+
const [projectionNotice, setProjectionNotice] = useState4(null);
|
|
19767
|
+
const [sidebarWidthPreference, setSidebarWidthPreference] = useState4(REVIEW_SIDEBAR_DEFAULT_WIDTH);
|
|
19768
|
+
const [resizeBarHovered, setResizeBarHovered] = useState4(false);
|
|
19769
|
+
const [resizingSidebar, setResizingSidebar] = useState4(false);
|
|
19770
|
+
const [visibleFileKeys, setVisibleFileKeys] = useState4([]);
|
|
19771
|
+
const [focus, setFocus] = useState4("stream");
|
|
19772
|
+
const [rangeStart, setRangeStart] = useState4(null);
|
|
19773
|
+
const [pendingRangeAnchor, setPendingRangeAnchor] = useState4(null);
|
|
19774
|
+
const localRangeIdentityRef = useRef3(null);
|
|
19775
|
+
const [selectedFeedbackId, setSelectedFeedbackId] = useState4(null);
|
|
19776
|
+
const [editingFeedbackId, setEditingFeedbackId] = useState4(null);
|
|
19777
|
+
const [pendingDeleteFeedbackId, setPendingDeleteFeedbackId] = useState4(null);
|
|
19778
|
+
const [composerFocus, setComposerFocus] = useState4("body");
|
|
19779
|
+
const [composerControlIndex, setComposerControlIndex] = useState4(0);
|
|
19780
|
+
const [reanchorFeedbackId, setReanchorFeedbackId] = useState4(null);
|
|
19781
|
+
const [feedbackMessage, setFeedbackMessage] = useState4(null);
|
|
19782
|
+
const [helpOpen, setHelpOpen] = useState4(false);
|
|
19783
|
+
const diffScrollRef = useRef3(null);
|
|
19784
|
+
const resizingSidebarRef = useRef3(false);
|
|
19785
|
+
const resizeDraggedRef = useRef3(false);
|
|
19786
|
+
const resizeReleaseSuppressionRef = useRef3(false);
|
|
19787
|
+
const resizeReleaseCleanupTokenRef = useRef3(0);
|
|
19788
|
+
const filterInputRef = useRef3(null);
|
|
19789
|
+
const pendingDeleteFeedbackRef = useRef3(null);
|
|
19790
|
+
const composerBodyRef = useRef3(null);
|
|
19791
|
+
const replacementRef = useRef3(null);
|
|
19792
|
+
const finishSummaryRef = useRef3(null);
|
|
19793
|
+
const finishSubmitRef = useRef3(false);
|
|
19794
|
+
const localReviewIdentityRef = useRef3(state?.document.identity.id);
|
|
19795
|
+
const expandedSourceRef = useRef3(new Map);
|
|
19413
19796
|
const dimensions2 = { width: Math.max(1, terminal.width), height: Math.max(1, terminal.height) };
|
|
19414
19797
|
const maxSidebarWidth = Math.max(REVIEW_SIDEBAR_MIN_WIDTH, dimensions2.width - REVIEW_RESIZE_BAR_WIDTH - REVIEW_DIFF_BORDER_WIDTH - REVIEW_DIFF_MIN_CONTENT_WIDTH);
|
|
19415
19798
|
const sidebarWidth = dimensions2.width >= REVIEW_SIDEBAR_VISIBILITY_WIDTH ? Math.min(Math.max(sidebarWidthPreference, REVIEW_SIDEBAR_MIN_WIDTH), maxSidebarWidth) : 0;
|
|
@@ -19420,12 +19803,12 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
19420
19803
|
const resizeBarHeight = Math.max(1, dimensions2.height - 4 - composerHeight);
|
|
19421
19804
|
const sidebarFocused = focus === "sidebar" || focus === "filter";
|
|
19422
19805
|
const diffFocused = focus === "stream";
|
|
19423
|
-
const files =
|
|
19424
|
-
const sidebarEntries =
|
|
19425
|
-
const sidebarFileEntries =
|
|
19426
|
-
const sidebarStatsWidth =
|
|
19806
|
+
const files = useMemo5(() => state ? toHunkReviewFiles(visibleReviewFiles(state)) : [], [state?.document, state?.feedback, state?.filter, state?.viewed]);
|
|
19807
|
+
const sidebarEntries = useMemo5(() => state ? buildReviewSidebarEntries(state) : [], [state]);
|
|
19808
|
+
const sidebarFileEntries = useMemo5(() => sidebarEntries.filter((entry) => entry.kind === "file"), [sidebarEntries]);
|
|
19809
|
+
const sidebarStatsWidth = useMemo5(() => Math.max(0, ...sidebarFileEntries.map((entry) => sidebarEntryStatsWidth(entry))), [sidebarFileEntries]);
|
|
19427
19810
|
const sidebarTextWidth = Math.max(8, sidebarWidth - 2);
|
|
19428
|
-
const expandedSourceByGap =
|
|
19811
|
+
const expandedSourceByGap = useMemo5(() => {
|
|
19429
19812
|
const next = typeof controller.getExpandedSourceByGap === "function" ? controller.getExpandedSourceByGap() : new Map;
|
|
19430
19813
|
const previous = expandedSourceRef.current;
|
|
19431
19814
|
if (previous.size === next.size && [...next].every(([key, lines]) => previous.get(key) === lines))
|
|
@@ -19506,9 +19889,7 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
19506
19889
|
setResizingSidebar(false);
|
|
19507
19890
|
setResizeBarHovered(false);
|
|
19508
19891
|
}, []);
|
|
19509
|
-
|
|
19510
|
-
if (active2)
|
|
19511
|
-
return;
|
|
19892
|
+
const resetLocalReviewUi = useCallback(() => {
|
|
19512
19893
|
setRangeStart(null);
|
|
19513
19894
|
setPendingRangeAnchor(null);
|
|
19514
19895
|
setSelectedFeedbackId(null);
|
|
@@ -19522,8 +19903,33 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
19522
19903
|
setHelpOpen(false);
|
|
19523
19904
|
setFocus("stream");
|
|
19524
19905
|
resetSidebarResize();
|
|
19525
|
-
|
|
19526
|
-
|
|
19906
|
+
setVisibleFileKeys([]);
|
|
19907
|
+
localRangeIdentityRef.current = null;
|
|
19908
|
+
if (diffScrollRef.current)
|
|
19909
|
+
diffScrollRef.current.scrollTop = 0;
|
|
19910
|
+
session.setViewportStart(0);
|
|
19911
|
+
finishDialog.close();
|
|
19912
|
+
}, [finishDialog, resetSidebarResize, session]);
|
|
19913
|
+
useLayoutEffect3(() => {
|
|
19914
|
+
if (!active2)
|
|
19915
|
+
resetLocalReviewUi();
|
|
19916
|
+
}, [active2, resetLocalReviewUi]);
|
|
19917
|
+
useLayoutEffect3(() => {
|
|
19918
|
+
const identity = state?.document.identity.id;
|
|
19919
|
+
if (identity === localReviewIdentityRef.current)
|
|
19920
|
+
return;
|
|
19921
|
+
localReviewIdentityRef.current = identity;
|
|
19922
|
+
resetLocalReviewUi();
|
|
19923
|
+
}, [resetLocalReviewUi, state?.document.identity.id]);
|
|
19924
|
+
useLayoutEffect3(() => {
|
|
19925
|
+
if (!baseSelection)
|
|
19926
|
+
return;
|
|
19927
|
+
filterInputRef.current?.blur();
|
|
19928
|
+
composerBodyRef.current?.blur();
|
|
19929
|
+
replacementRef.current?.blur();
|
|
19930
|
+
resetSidebarResize();
|
|
19931
|
+
}, [baseSelection !== undefined, resetSidebarResize]);
|
|
19932
|
+
useLayoutEffect3(() => {
|
|
19527
19933
|
if (sidebarWidth > 0)
|
|
19528
19934
|
return;
|
|
19529
19935
|
resetSidebarResize();
|
|
@@ -19532,8 +19938,8 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
19532
19938
|
if (sidebarWidth === 0 && focus !== "stream")
|
|
19533
19939
|
setFocus("stream");
|
|
19534
19940
|
}, [focus, sidebarWidth]);
|
|
19535
|
-
|
|
19536
|
-
if (!state?.draft)
|
|
19941
|
+
useLayoutEffect3(() => {
|
|
19942
|
+
if (!state?.draft || baseSelection)
|
|
19537
19943
|
return;
|
|
19538
19944
|
if (composerFocus === "body") {
|
|
19539
19945
|
composerBodyRef.current?.focus();
|
|
@@ -19545,7 +19951,7 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
19545
19951
|
composerBodyRef.current?.blur();
|
|
19546
19952
|
replacementRef.current?.blur();
|
|
19547
19953
|
}
|
|
19548
|
-
}, [composerFocus, state?.draft]);
|
|
19954
|
+
}, [baseSelection !== undefined, composerFocus, state?.draft]);
|
|
19549
19955
|
useEffect3(() => {
|
|
19550
19956
|
const pending = pendingRangeAnchor;
|
|
19551
19957
|
const start = rangeStart;
|
|
@@ -19794,7 +20200,48 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
19794
20200
|
session.invalidate();
|
|
19795
20201
|
} catch {}
|
|
19796
20202
|
}, [controller, rangeStart, session]);
|
|
20203
|
+
const requestBaseSelection = useCallback(() => {
|
|
20204
|
+
if (controller.baseSelection || finishDialog.isOpen() || helpOpen)
|
|
20205
|
+
return;
|
|
20206
|
+
controller.requestBaseSelection();
|
|
20207
|
+
}, [controller, finishDialog, helpOpen]);
|
|
20208
|
+
const cancelBaseSelection = useCallback(() => {
|
|
20209
|
+
if (controller.baseSelection?.loading || controller.baseSelection?.selecting)
|
|
20210
|
+
return;
|
|
20211
|
+
controller.cancelBaseSelection();
|
|
20212
|
+
if (!controller.state)
|
|
20213
|
+
onClose();
|
|
20214
|
+
}, [controller, onClose]);
|
|
20215
|
+
const chooseBase = useCallback((ref) => {
|
|
20216
|
+
controller.chooseBase(ref);
|
|
20217
|
+
}, [controller]);
|
|
20218
|
+
const retryBaseSelection = useCallback(() => {
|
|
20219
|
+
controller.requestBaseSelection();
|
|
20220
|
+
}, [controller]);
|
|
19797
20221
|
const executeCommand = useCallback((commandId, payload) => {
|
|
20222
|
+
if (controller.baseSelection)
|
|
20223
|
+
return false;
|
|
20224
|
+
if (commandId === "review.chooseBase") {
|
|
20225
|
+
requestBaseSelection();
|
|
20226
|
+
return true;
|
|
20227
|
+
}
|
|
20228
|
+
if (commandId === "review.toggleSinceLastReview") {
|
|
20229
|
+
if (controller.exitProjection()) {
|
|
20230
|
+
setProjectionNotice(null);
|
|
20231
|
+
session.invalidate();
|
|
20232
|
+
return true;
|
|
20233
|
+
}
|
|
20234
|
+
setProjectionNotice(null);
|
|
20235
|
+
controller.enterSinceLastReview().then((result) => {
|
|
20236
|
+
if (result.ok) {
|
|
20237
|
+
setProjectionNotice(result.fileCount === 0 ? "Nothing changed since the last review" : null);
|
|
20238
|
+
} else {
|
|
20239
|
+
setProjectionNotice(PROJECTION_NOTICES[result.reason] ?? result.message ?? "Could not open the projection");
|
|
20240
|
+
}
|
|
20241
|
+
session.invalidate();
|
|
20242
|
+
});
|
|
20243
|
+
return true;
|
|
20244
|
+
}
|
|
19798
20245
|
const current = controller.state;
|
|
19799
20246
|
if (commandId === "review.focusDiff") {
|
|
19800
20247
|
setFocus("stream");
|
|
@@ -20037,10 +20484,17 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20037
20484
|
return true;
|
|
20038
20485
|
}
|
|
20039
20486
|
return false;
|
|
20040
|
-
}, [controller, deleteFeedback, diffWidth, editFeedback, finishDialog, focus, onClose, pendingRangeAnchor, rangeStart, reanchorFeedback, selectedFeedbackId, selectFeedback, selectDiffAddress, session, sidebarWidth, toggleGap]);
|
|
20487
|
+
}, [controller, deleteFeedback, diffWidth, editFeedback, finishDialog, focus, onClose, pendingRangeAnchor, rangeStart, reanchorFeedback, requestBaseSelection, selectedFeedbackId, selectFeedback, selectDiffAddress, session, sidebarWidth, toggleGap]);
|
|
20041
20488
|
const handleKey = useCallback((event) => {
|
|
20042
20489
|
if (!active2)
|
|
20043
20490
|
return;
|
|
20491
|
+
if (baseSelection || controller.baseSelection) {
|
|
20492
|
+
if (keyName(event) === "escape") {
|
|
20493
|
+
consume2(event);
|
|
20494
|
+
cancelBaseSelection();
|
|
20495
|
+
}
|
|
20496
|
+
return;
|
|
20497
|
+
}
|
|
20044
20498
|
const name = keyName(event);
|
|
20045
20499
|
const current = controller.state;
|
|
20046
20500
|
if (name === "escape") {
|
|
@@ -20052,25 +20506,25 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20052
20506
|
setComposerFocus("body");
|
|
20053
20507
|
setComposerControlIndex(0);
|
|
20054
20508
|
session.invalidate();
|
|
20055
|
-
|
|
20509
|
+
consume2(event);
|
|
20056
20510
|
return;
|
|
20057
20511
|
}
|
|
20058
20512
|
if (finishDialog.isOpen()) {
|
|
20059
20513
|
finishDialog.close();
|
|
20060
20514
|
session.invalidate();
|
|
20061
|
-
|
|
20515
|
+
consume2(event);
|
|
20062
20516
|
return;
|
|
20063
20517
|
}
|
|
20064
20518
|
if (helpOpen) {
|
|
20065
20519
|
setHelpOpen(false);
|
|
20066
|
-
|
|
20520
|
+
consume2(event);
|
|
20067
20521
|
return;
|
|
20068
20522
|
}
|
|
20069
20523
|
if (pendingDeleteFeedbackId) {
|
|
20070
20524
|
pendingDeleteFeedbackRef.current = null;
|
|
20071
20525
|
setPendingDeleteFeedbackId(null);
|
|
20072
20526
|
session.invalidate();
|
|
20073
|
-
|
|
20527
|
+
consume2(event);
|
|
20074
20528
|
return;
|
|
20075
20529
|
}
|
|
20076
20530
|
if (focus === "filter") {
|
|
@@ -20083,19 +20537,19 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20083
20537
|
filterInputRef.current?.blur();
|
|
20084
20538
|
setFocus("stream");
|
|
20085
20539
|
}
|
|
20086
|
-
|
|
20540
|
+
consume2(event);
|
|
20087
20541
|
return;
|
|
20088
20542
|
}
|
|
20089
20543
|
if (reanchorFeedbackId) {
|
|
20090
20544
|
setReanchorFeedbackId(null);
|
|
20091
20545
|
setFeedbackMessage(null);
|
|
20092
|
-
|
|
20546
|
+
consume2(event);
|
|
20093
20547
|
return;
|
|
20094
20548
|
}
|
|
20095
20549
|
if (rangeStart || pendingRangeAnchor) {
|
|
20096
20550
|
setRangeStart(null);
|
|
20097
20551
|
setPendingRangeAnchor(null);
|
|
20098
|
-
|
|
20552
|
+
consume2(event);
|
|
20099
20553
|
return;
|
|
20100
20554
|
}
|
|
20101
20555
|
}
|
|
@@ -20103,24 +20557,24 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20103
20557
|
if (event.ctrl && (name === "1" || name === "2" || name === "3")) {
|
|
20104
20558
|
finishDialog.setDecision(name === "1" ? "comment" : name === "2" ? "approve" : "request-changes");
|
|
20105
20559
|
session.invalidate();
|
|
20106
|
-
|
|
20560
|
+
consume2(event);
|
|
20107
20561
|
return;
|
|
20108
20562
|
}
|
|
20109
20563
|
if (event.ctrl && name.toLowerCase() === "s" || name === "enter") {
|
|
20110
20564
|
submitFinish();
|
|
20111
|
-
|
|
20565
|
+
consume2(event);
|
|
20112
20566
|
return;
|
|
20113
20567
|
}
|
|
20114
20568
|
return;
|
|
20115
20569
|
}
|
|
20116
20570
|
if (helpOpen) {
|
|
20117
|
-
|
|
20571
|
+
consume2(event);
|
|
20118
20572
|
return;
|
|
20119
20573
|
}
|
|
20120
20574
|
if (current?.draft) {
|
|
20121
20575
|
if (event.ctrl && name.toLowerCase() === "s") {
|
|
20122
20576
|
saveDraft();
|
|
20123
|
-
|
|
20577
|
+
consume2(event);
|
|
20124
20578
|
} else if (name === "tab") {
|
|
20125
20579
|
const hasReplacement = canShowReplacementDraft(current);
|
|
20126
20580
|
const backwards = event.shift === true;
|
|
@@ -20150,14 +20604,14 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20150
20604
|
setComposerControlIndex(0);
|
|
20151
20605
|
}
|
|
20152
20606
|
}
|
|
20153
|
-
|
|
20607
|
+
consume2(event);
|
|
20154
20608
|
}
|
|
20155
20609
|
return;
|
|
20156
20610
|
}
|
|
20157
20611
|
if (focus === "filter" && name === "enter") {
|
|
20158
20612
|
filterInputRef.current?.blur();
|
|
20159
20613
|
setFocus("stream");
|
|
20160
|
-
|
|
20614
|
+
consume2(event);
|
|
20161
20615
|
return;
|
|
20162
20616
|
}
|
|
20163
20617
|
if (focus === "filter" && !event.ctrl && !event.meta && !event.option && isPrintableFilterKey(name))
|
|
@@ -20167,17 +20621,30 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20167
20621
|
if (!command || current && !command.available(current))
|
|
20168
20622
|
return;
|
|
20169
20623
|
if (executeCommand(command.id))
|
|
20170
|
-
|
|
20171
|
-
}, [active2, composerControlIndex, composerFocus, controller, executeCommand, finishDialog, focus, helpOpen, onClose, pendingDeleteFeedbackId, pendingRangeAnchor, rangeStart, reanchorFeedbackId, saveDraft, session, submitFinish]);
|
|
20172
|
-
|
|
20624
|
+
consume2(event);
|
|
20625
|
+
}, [active2, baseSelection, cancelBaseSelection, composerControlIndex, composerFocus, controller, executeCommand, finishDialog, focus, helpOpen, onClose, pendingDeleteFeedbackId, pendingRangeAnchor, rangeStart, reanchorFeedbackId, saveDraft, session, submitFinish]);
|
|
20626
|
+
useKeyboard2(handleKey);
|
|
20627
|
+
const basePicker = baseSelection ? /* @__PURE__ */ jsx6(ReviewBasePicker, {
|
|
20628
|
+
selection: baseSelection,
|
|
20629
|
+
width: dimensions2.width,
|
|
20630
|
+
height: dimensions2.height,
|
|
20631
|
+
active: active2,
|
|
20632
|
+
...controller.error ? { warning: `! ${controller.error.title}: ${controller.error.detail}` } : {},
|
|
20633
|
+
onChoose: chooseBase,
|
|
20634
|
+
onCancel: cancelBaseSelection,
|
|
20635
|
+
onRetry: retryBaseSelection
|
|
20636
|
+
}) : null;
|
|
20173
20637
|
if (!state) {
|
|
20174
|
-
return /* @__PURE__ */
|
|
20638
|
+
return /* @__PURE__ */ jsxs5("box", {
|
|
20175
20639
|
id: "react-review-workspace",
|
|
20176
20640
|
visible: active2,
|
|
20177
|
-
style: { width: "100%", height: "100%" },
|
|
20178
|
-
children:
|
|
20179
|
-
|
|
20180
|
-
|
|
20641
|
+
style: { position: "relative", width: "100%", height: "100%", overflow: "hidden" },
|
|
20642
|
+
children: [
|
|
20643
|
+
/* @__PURE__ */ jsx6("text", {
|
|
20644
|
+
content: "Choose a base branch to start reviewing."
|
|
20645
|
+
}),
|
|
20646
|
+
basePicker
|
|
20647
|
+
]
|
|
20181
20648
|
});
|
|
20182
20649
|
}
|
|
20183
20650
|
function selectFile(fileKey, nextFocus2 = "stream") {
|
|
@@ -20199,28 +20666,55 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20199
20666
|
const suggestionAllowed = state.draft !== null && state.draft.anchor.kind === "range" && state.draft.anchor.side === "new" && state.document.files.some((file) => file.key === state.draft?.anchor.fileKey && file.source !== "binary" && file.source !== "too-large");
|
|
20200
20667
|
const replacementInvalid = suggestionReplacementInvalid(state);
|
|
20201
20668
|
const orphanedFeedback = state.feedback.filter((feedback) => !state.document.files.some((file) => file.key === feedback.anchor.fileKey));
|
|
20202
|
-
return /* @__PURE__ */
|
|
20669
|
+
return /* @__PURE__ */ jsxs5("box", {
|
|
20203
20670
|
id: "react-review-workspace",
|
|
20204
20671
|
visible: active2,
|
|
20205
20672
|
onMouse: handleSidebarResizeMouse,
|
|
20206
20673
|
style: { position: "relative", width: "100%", height: "100%", flexDirection: "column", overflow: "hidden" },
|
|
20207
20674
|
children: [
|
|
20208
|
-
/* @__PURE__ */
|
|
20675
|
+
/* @__PURE__ */ jsxs5("box", {
|
|
20209
20676
|
id: "react-review-header",
|
|
20210
|
-
style: { width: "100%", height: 3, flexShrink: 0 },
|
|
20211
|
-
children:
|
|
20212
|
-
|
|
20213
|
-
|
|
20214
|
-
|
|
20215
|
-
|
|
20677
|
+
style: { width: "100%", height: 3, flexShrink: 0, flexDirection: "column" },
|
|
20678
|
+
children: [
|
|
20679
|
+
reviewHeaderLines(state, dimensions2.width).map((line, lineIndex) => /* @__PURE__ */ jsx6("box", {
|
|
20680
|
+
style: { width: "100%", height: 1, flexShrink: 0, flexDirection: "row" },
|
|
20681
|
+
children: line.map((span, spanIndex) => span.action === "choose-base" ? /* @__PURE__ */ jsx6("box", {
|
|
20682
|
+
id: "review-base-selector",
|
|
20683
|
+
style: { width: cellWidth(span.text), height: 1, flexShrink: 0 },
|
|
20684
|
+
onMouseUp: (event) => {
|
|
20685
|
+
event.preventDefault();
|
|
20686
|
+
event.stopPropagation();
|
|
20687
|
+
requestBaseSelection();
|
|
20688
|
+
},
|
|
20689
|
+
children: /* @__PURE__ */ jsx6("text", {
|
|
20690
|
+
content: span.text,
|
|
20691
|
+
fg: COLORS2.strong,
|
|
20692
|
+
selectable: false,
|
|
20693
|
+
wrapMode: "none",
|
|
20694
|
+
truncate: true
|
|
20695
|
+
})
|
|
20696
|
+
}, spanIndex) : /* @__PURE__ */ jsx6("text", {
|
|
20697
|
+
content: span.text,
|
|
20698
|
+
fg: span.style === "dim" ? COLORS2.dim : COLORS2.strong,
|
|
20699
|
+
wrapMode: "none",
|
|
20700
|
+
truncate: true
|
|
20701
|
+
}, spanIndex))
|
|
20702
|
+
}, lineIndex)),
|
|
20703
|
+
controller.error ? /* @__PURE__ */ jsx6("text", {
|
|
20704
|
+
content: `! ${controller.error.title}: ${controller.error.detail}`,
|
|
20705
|
+
fg: COLORS2.dim,
|
|
20706
|
+
wrapMode: "none",
|
|
20707
|
+
truncate: true
|
|
20708
|
+
}) : null
|
|
20709
|
+
]
|
|
20216
20710
|
}),
|
|
20217
|
-
/* @__PURE__ */
|
|
20711
|
+
/* @__PURE__ */ jsxs5("box", {
|
|
20218
20712
|
id: "react-review-body",
|
|
20219
20713
|
style: { width: "100%", flexGrow: 1, flexDirection: "row", overflow: "hidden" },
|
|
20220
20714
|
children: [
|
|
20221
|
-
sidebarWidth > 0 ? /* @__PURE__ */
|
|
20715
|
+
sidebarWidth > 0 ? /* @__PURE__ */ jsxs5(Fragment, {
|
|
20222
20716
|
children: [
|
|
20223
|
-
/* @__PURE__ */
|
|
20717
|
+
/* @__PURE__ */ jsxs5("box", {
|
|
20224
20718
|
id: "react-review-sidebar",
|
|
20225
20719
|
borderColor: sidebarFocused ? ANSI_GREEN : DEFAULT_FOREGROUND,
|
|
20226
20720
|
title: `[1] Files ${sidebarFileEntries.length}/${state.document.files.length}`,
|
|
@@ -20228,21 +20722,21 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20228
20722
|
style: { width: sidebarWidth, height: "100%", flexShrink: 0, border: true, flexDirection: "column" },
|
|
20229
20723
|
onMouseDown: () => setFocus("sidebar"),
|
|
20230
20724
|
children: [
|
|
20231
|
-
/* @__PURE__ */
|
|
20725
|
+
/* @__PURE__ */ jsxs5("box", {
|
|
20232
20726
|
id: "review-file-filter",
|
|
20233
20727
|
style: { width: "100%", height: 1, flexShrink: 0, flexDirection: "row" },
|
|
20234
20728
|
children: [
|
|
20235
|
-
/* @__PURE__ */
|
|
20729
|
+
/* @__PURE__ */ jsx6("text", {
|
|
20236
20730
|
content: "/ ",
|
|
20237
20731
|
fg: COLORS2.dim
|
|
20238
20732
|
}),
|
|
20239
|
-
/* @__PURE__ */
|
|
20733
|
+
/* @__PURE__ */ jsx6("input", {
|
|
20240
20734
|
id: "review-file-filter-input",
|
|
20241
20735
|
ref: filterInputRef,
|
|
20242
20736
|
width: Math.max(4, sidebarWidth - 4),
|
|
20243
20737
|
value: state.filter.query,
|
|
20244
20738
|
placeholder: "filter files",
|
|
20245
|
-
focused: focus === "filter",
|
|
20739
|
+
focused: focus === "filter" && !baseSelection,
|
|
20246
20740
|
onMouseUp: () => {
|
|
20247
20741
|
if (resizingSidebarRef.current || resizeReleaseSuppressionRef.current)
|
|
20248
20742
|
return;
|
|
@@ -20269,28 +20763,28 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20269
20763
|
filterInputRef.current?.blur();
|
|
20270
20764
|
setFocus("stream");
|
|
20271
20765
|
}
|
|
20272
|
-
|
|
20766
|
+
consume2(event);
|
|
20273
20767
|
}
|
|
20274
20768
|
})
|
|
20275
20769
|
]
|
|
20276
20770
|
}),
|
|
20277
|
-
/* @__PURE__ */
|
|
20771
|
+
/* @__PURE__ */ jsx6("scrollbox", {
|
|
20278
20772
|
id: "react-review-sidebar-scrollbox",
|
|
20279
|
-
focused: focus === "sidebar",
|
|
20773
|
+
focused: focus === "sidebar" && !baseSelection,
|
|
20280
20774
|
width: "100%",
|
|
20281
20775
|
flexGrow: 1,
|
|
20282
20776
|
scrollY: true,
|
|
20283
20777
|
viewportCulling: true,
|
|
20284
20778
|
verticalScrollbarOptions: { visible: false },
|
|
20285
20779
|
onMouseDown: () => setFocus("sidebar"),
|
|
20286
|
-
children: /* @__PURE__ */
|
|
20780
|
+
children: /* @__PURE__ */ jsx6("box", {
|
|
20287
20781
|
style: { width: "100%", flexDirection: "column" },
|
|
20288
20782
|
children: sidebarEntries.map((entry) => {
|
|
20289
20783
|
if (entry.kind === "group") {
|
|
20290
|
-
return /* @__PURE__ */
|
|
20784
|
+
return /* @__PURE__ */ jsx6("box", {
|
|
20291
20785
|
id: `review-file-group:${entry.label}`,
|
|
20292
20786
|
style: { width: "100%", height: 1, backgroundColor: REVIEW_SIDEBAR_THEME.panel, paddingLeft: 1 },
|
|
20293
|
-
children: /* @__PURE__ */
|
|
20787
|
+
children: /* @__PURE__ */ jsx6("text", {
|
|
20294
20788
|
fg: REVIEW_SIDEBAR_THEME.muted,
|
|
20295
20789
|
children: fitText(entry.label, sidebarTextWidth)
|
|
20296
20790
|
})
|
|
@@ -20299,11 +20793,11 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20299
20793
|
const selected = entry.id === state.selection.fileKey;
|
|
20300
20794
|
const rowBackground = selected ? REVIEW_SIDEBAR_THEME.panelAlt : REVIEW_SIDEBAR_THEME.panel;
|
|
20301
20795
|
const stats = sidebarEntryStats(entry);
|
|
20302
|
-
const { icon, color:
|
|
20796
|
+
const { icon, color: color3 } = getFileStateIcon(entry);
|
|
20303
20797
|
const iconWidth = icon ? 2 : 0;
|
|
20304
20798
|
const statsSectionWidth = sidebarStatsWidth > 0 ? sidebarStatsWidth + 1 : 0;
|
|
20305
20799
|
const nameWidth = Math.max(1, sidebarTextWidth - 1 - iconWidth - statsSectionWidth);
|
|
20306
|
-
return /* @__PURE__ */
|
|
20800
|
+
return /* @__PURE__ */ jsxs5("box", {
|
|
20307
20801
|
id: `review-file-row:${entry.id}`,
|
|
20308
20802
|
style: { width: "100%", height: 1, backgroundColor: rowBackground, flexDirection: "row" },
|
|
20309
20803
|
onMouseDown: () => setFocus("sidebar"),
|
|
@@ -20311,30 +20805,30 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20311
20805
|
executeCommand("review.selectFile", { fileKey: entry.id, nextFocus: "sidebar" });
|
|
20312
20806
|
},
|
|
20313
20807
|
children: [
|
|
20314
|
-
/* @__PURE__ */
|
|
20808
|
+
/* @__PURE__ */ jsx6("box", {
|
|
20315
20809
|
style: { width: 1, height: 1, backgroundColor: selected ? REVIEW_SIDEBAR_THEME.accent : rowBackground }
|
|
20316
20810
|
}),
|
|
20317
|
-
/* @__PURE__ */
|
|
20811
|
+
/* @__PURE__ */ jsxs5("box", {
|
|
20318
20812
|
style: { flexGrow: 1, height: 1, paddingLeft: 0, flexDirection: "row", backgroundColor: rowBackground },
|
|
20319
20813
|
children: [
|
|
20320
|
-
icon ? /* @__PURE__ */
|
|
20321
|
-
fg:
|
|
20814
|
+
icon ? /* @__PURE__ */ jsx6("text", {
|
|
20815
|
+
fg: color3,
|
|
20322
20816
|
children: `${icon} `
|
|
20323
20817
|
}) : null,
|
|
20324
|
-
/* @__PURE__ */
|
|
20818
|
+
/* @__PURE__ */ jsx6("text", {
|
|
20325
20819
|
fg: REVIEW_SIDEBAR_THEME.text,
|
|
20326
20820
|
children: padText(fitText(entry.name, nameWidth), nameWidth)
|
|
20327
20821
|
}),
|
|
20328
|
-
statsSectionWidth > 0 ? /* @__PURE__ */
|
|
20822
|
+
statsSectionWidth > 0 ? /* @__PURE__ */ jsx6("box", {
|
|
20329
20823
|
style: { width: statsSectionWidth, height: 1, flexDirection: "row", justifyContent: "flex-end", backgroundColor: rowBackground },
|
|
20330
|
-
children: stats.map((stat3, index) => /* @__PURE__ */
|
|
20824
|
+
children: stats.map((stat3, index) => /* @__PURE__ */ jsxs5("box", {
|
|
20331
20825
|
style: { height: 1, flexDirection: "row", backgroundColor: rowBackground },
|
|
20332
20826
|
children: [
|
|
20333
|
-
index > 0 ? /* @__PURE__ */
|
|
20827
|
+
index > 0 ? /* @__PURE__ */ jsx6("text", {
|
|
20334
20828
|
fg: selected ? REVIEW_SIDEBAR_THEME.text : REVIEW_SIDEBAR_THEME.muted,
|
|
20335
20829
|
children: " "
|
|
20336
20830
|
}) : null,
|
|
20337
|
-
/* @__PURE__ */
|
|
20831
|
+
/* @__PURE__ */ jsx6("text", {
|
|
20338
20832
|
fg: stat3.kind === "agent-comment" ? REVIEW_SIDEBAR_THEME.noteBorder : stat3.kind === "addition" ? REVIEW_SIDEBAR_THEME.badgeAdded : REVIEW_SIDEBAR_THEME.badgeRemoved,
|
|
20339
20833
|
children: stat3.text
|
|
20340
20834
|
})
|
|
@@ -20350,7 +20844,7 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20350
20844
|
})
|
|
20351
20845
|
]
|
|
20352
20846
|
}),
|
|
20353
|
-
/* @__PURE__ */
|
|
20847
|
+
/* @__PURE__ */ jsx6("box", {
|
|
20354
20848
|
id: "review-pane-resize-bar",
|
|
20355
20849
|
style: { width: REVIEW_RESIZE_BAR_WIDTH, height: "100%", flexShrink: 0 },
|
|
20356
20850
|
onMouseOver: () => setResizeBarHovered(true),
|
|
@@ -20383,7 +20877,7 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20383
20877
|
event.preventDefault();
|
|
20384
20878
|
event.stopPropagation();
|
|
20385
20879
|
},
|
|
20386
|
-
children: /* @__PURE__ */
|
|
20880
|
+
children: /* @__PURE__ */ jsx6("text", {
|
|
20387
20881
|
id: "review-pane-resize-bar-glyphs",
|
|
20388
20882
|
selectable: false,
|
|
20389
20883
|
content: splitterGlyphs("vertical", REVIEW_RESIZE_BAR_WIDTH, resizeBarHeight, resizeBarHovered || resizingSidebar),
|
|
@@ -20396,20 +20890,20 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20396
20890
|
})
|
|
20397
20891
|
]
|
|
20398
20892
|
}) : null,
|
|
20399
|
-
/* @__PURE__ */
|
|
20893
|
+
/* @__PURE__ */ jsx6("box", {
|
|
20400
20894
|
id: "react-review-diff",
|
|
20401
20895
|
borderColor: diffFocused ? ANSI_GREEN : DEFAULT_FOREGROUND,
|
|
20402
20896
|
title: `[0] Diff — ${layout}`,
|
|
20403
20897
|
titleColor: diffFocused ? ANSI_GREEN : DEFAULT_FOREGROUND,
|
|
20404
20898
|
style: { width: "100%", height: "100%", flexGrow: 1, border: true, minWidth: 0 },
|
|
20405
20899
|
onMouseDown: () => setFocus("stream"),
|
|
20406
|
-
children: /* @__PURE__ */
|
|
20900
|
+
children: /* @__PURE__ */ jsx6(ReviewDiffPane, {
|
|
20407
20901
|
files,
|
|
20408
20902
|
state,
|
|
20409
20903
|
layout,
|
|
20410
20904
|
width: diffWidth,
|
|
20411
20905
|
height: diffHeight,
|
|
20412
|
-
focused: focus === "stream",
|
|
20906
|
+
focused: focus === "stream" && !baseSelection,
|
|
20413
20907
|
scrollRef: diffScrollRef,
|
|
20414
20908
|
selectedFileKey: state.selection.fileKey,
|
|
20415
20909
|
selectedHunkIndex: state.selection.hunkIndex,
|
|
@@ -20430,14 +20924,14 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20430
20924
|
},
|
|
20431
20925
|
selectedFeedbackId,
|
|
20432
20926
|
onViewportChange: session.setViewportStart
|
|
20433
|
-
})
|
|
20927
|
+
}, state.document.identity.id)
|
|
20434
20928
|
})
|
|
20435
20929
|
]
|
|
20436
20930
|
}),
|
|
20437
|
-
orphanedFeedback.length > 0 ? /* @__PURE__ */
|
|
20931
|
+
orphanedFeedback.length > 0 ? /* @__PURE__ */ jsx6("box", {
|
|
20438
20932
|
id: "review-orphaned-feedback",
|
|
20439
20933
|
style: { position: "absolute", left: 1, bottom: 1, width: Math.max(20, dimensions2.width - 2), height: Math.min(4, orphanedFeedback.length), zIndex: 50, border: true, flexDirection: "column", backgroundColor: "#202020" },
|
|
20440
|
-
children: orphanedFeedback.slice(0, 4).map((feedback) => /* @__PURE__ */
|
|
20934
|
+
children: orphanedFeedback.slice(0, 4).map((feedback) => /* @__PURE__ */ jsxs5("box", {
|
|
20441
20935
|
style: { width: "100%", height: 1, flexDirection: "row" },
|
|
20442
20936
|
onMouseUp: () => {
|
|
20443
20937
|
if (resizingSidebarRef.current || resizeReleaseSuppressionRef.current)
|
|
@@ -20445,32 +20939,32 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20445
20939
|
executeCommand("review.selectFeedback", feedback.id);
|
|
20446
20940
|
},
|
|
20447
20941
|
children: [
|
|
20448
|
-
/* @__PURE__ */
|
|
20942
|
+
/* @__PURE__ */ jsx6("text", {
|
|
20449
20943
|
content: `${feedback.resolution} feedback ${feedback.id} — ${feedback.anchor.kind === "range" ? `${feedback.anchor.side}:${feedback.anchor.startLine === feedback.anchor.endLine ? feedback.anchor.startLine : `${feedback.anchor.startLine}-${feedback.anchor.endLine}`}` : "file"} — [a]nchor`,
|
|
20450
20944
|
wrapMode: "none",
|
|
20451
20945
|
truncate: true
|
|
20452
20946
|
}),
|
|
20453
|
-
/* @__PURE__ */
|
|
20947
|
+
/* @__PURE__ */ jsx6("box", {
|
|
20454
20948
|
id: `review-delete-feedback:${feedback.id}`,
|
|
20455
20949
|
onMouseUp: () => {
|
|
20456
20950
|
if (resizingSidebarRef.current || resizeReleaseSuppressionRef.current)
|
|
20457
20951
|
return;
|
|
20458
20952
|
deleteFeedback(feedback.id);
|
|
20459
20953
|
},
|
|
20460
|
-
children: /* @__PURE__ */
|
|
20954
|
+
children: /* @__PURE__ */ jsx6("text", {
|
|
20461
20955
|
content: pendingDeleteFeedbackId === feedback.id ? "[delete again]" : "[delete]",
|
|
20462
20956
|
wrapMode: "none",
|
|
20463
20957
|
truncate: true
|
|
20464
20958
|
})
|
|
20465
20959
|
}),
|
|
20466
|
-
/* @__PURE__ */
|
|
20960
|
+
/* @__PURE__ */ jsx6("box", {
|
|
20467
20961
|
id: `review-reanchor-feedback:${feedback.id}`,
|
|
20468
20962
|
onMouseUp: () => {
|
|
20469
20963
|
if (resizingSidebarRef.current || resizeReleaseSuppressionRef.current)
|
|
20470
20964
|
return;
|
|
20471
20965
|
reanchorFeedback(feedback.id);
|
|
20472
20966
|
},
|
|
20473
|
-
children: /* @__PURE__ */
|
|
20967
|
+
children: /* @__PURE__ */ jsx6("text", {
|
|
20474
20968
|
content: "[re-anchor]",
|
|
20475
20969
|
wrapMode: "none",
|
|
20476
20970
|
truncate: true
|
|
@@ -20479,29 +20973,29 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20479
20973
|
]
|
|
20480
20974
|
}, feedback.id))
|
|
20481
20975
|
}) : null,
|
|
20482
|
-
feedbackMessage ? /* @__PURE__ */
|
|
20976
|
+
feedbackMessage ? /* @__PURE__ */ jsx6("box", {
|
|
20483
20977
|
id: "review-feedback-message",
|
|
20484
20978
|
style: { position: "absolute", left: 1, bottom: orphanedFeedback.length > 0 ? Math.min(5, orphanedFeedback.length + 1) : 1, width: Math.max(20, dimensions2.width - 2), height: 1, zIndex: 55, backgroundColor: "#202020" },
|
|
20485
|
-
children: /* @__PURE__ */
|
|
20979
|
+
children: /* @__PURE__ */ jsx6("text", {
|
|
20486
20980
|
content: feedbackMessage,
|
|
20487
20981
|
wrapMode: "none",
|
|
20488
20982
|
truncate: true
|
|
20489
20983
|
})
|
|
20490
20984
|
}) : null,
|
|
20491
|
-
state.draft ? /* @__PURE__ */
|
|
20985
|
+
state.draft ? /* @__PURE__ */ jsxs5("box", {
|
|
20492
20986
|
id: "review-feedback-composer",
|
|
20493
20987
|
style: { width: "100%", height: composerHeight, flexShrink: 0, border: true, flexDirection: "column" },
|
|
20494
20988
|
children: [
|
|
20495
|
-
/* @__PURE__ */
|
|
20989
|
+
/* @__PURE__ */ jsx6("text", {
|
|
20496
20990
|
content: feedbackDraftText(state),
|
|
20497
20991
|
wrapMode: "none",
|
|
20498
20992
|
truncate: true
|
|
20499
20993
|
}),
|
|
20500
|
-
/* @__PURE__ */
|
|
20994
|
+
/* @__PURE__ */ jsxs5("box", {
|
|
20501
20995
|
id: "review-feedback-controls",
|
|
20502
20996
|
style: { width: "100%", height: 1, flexDirection: "row" },
|
|
20503
20997
|
children: [
|
|
20504
|
-
/* @__PURE__ */
|
|
20998
|
+
/* @__PURE__ */ jsx6("box", {
|
|
20505
20999
|
id: "review-feedback-kind-note",
|
|
20506
21000
|
style: composerFocus === "controls" && composerControlIndex === 0 ? { backgroundColor: "#365f8a" } : {},
|
|
20507
21001
|
onMouseUp: () => {
|
|
@@ -20517,11 +21011,11 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20517
21011
|
session.invalidate();
|
|
20518
21012
|
} catch {}
|
|
20519
21013
|
},
|
|
20520
|
-
children: /* @__PURE__ */
|
|
21014
|
+
children: /* @__PURE__ */ jsx6("text", {
|
|
20521
21015
|
content: state.draft.kind === "note" ? "[Note]" : " Note "
|
|
20522
21016
|
})
|
|
20523
21017
|
}),
|
|
20524
|
-
/* @__PURE__ */
|
|
21018
|
+
/* @__PURE__ */ jsx6("box", {
|
|
20525
21019
|
id: "review-feedback-kind-suggestion",
|
|
20526
21020
|
style: composerFocus === "controls" && composerControlIndex === 1 ? { backgroundColor: "#365f8a" } : {},
|
|
20527
21021
|
onMouseUp: () => {
|
|
@@ -20538,11 +21032,11 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20538
21032
|
session.invalidate();
|
|
20539
21033
|
} catch {}
|
|
20540
21034
|
},
|
|
20541
|
-
children: /* @__PURE__ */
|
|
21035
|
+
children: /* @__PURE__ */ jsx6("text", {
|
|
20542
21036
|
content: state.draft.kind === "suggestion" ? "[Suggestion]" : " Suggestion "
|
|
20543
21037
|
})
|
|
20544
21038
|
}),
|
|
20545
|
-
/* @__PURE__ */
|
|
21039
|
+
/* @__PURE__ */ jsx6("box", {
|
|
20546
21040
|
id: "review-feedback-severity-comment",
|
|
20547
21041
|
style: composerFocus === "controls" && composerControlIndex === 2 ? { backgroundColor: "#365f8a" } : {},
|
|
20548
21042
|
onMouseUp: () => {
|
|
@@ -20558,11 +21052,11 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20558
21052
|
session.invalidate();
|
|
20559
21053
|
} catch {}
|
|
20560
21054
|
},
|
|
20561
|
-
children: /* @__PURE__ */
|
|
21055
|
+
children: /* @__PURE__ */ jsx6("text", {
|
|
20562
21056
|
content: state.draft.severity === "comment" ? "[Comment]" : " Comment "
|
|
20563
21057
|
})
|
|
20564
21058
|
}),
|
|
20565
|
-
/* @__PURE__ */
|
|
21059
|
+
/* @__PURE__ */ jsx6("box", {
|
|
20566
21060
|
id: "review-feedback-severity-blocking",
|
|
20567
21061
|
style: composerFocus === "controls" && composerControlIndex === 3 ? { backgroundColor: "#365f8a" } : {},
|
|
20568
21062
|
onMouseUp: () => {
|
|
@@ -20578,11 +21072,11 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20578
21072
|
session.invalidate();
|
|
20579
21073
|
} catch {}
|
|
20580
21074
|
},
|
|
20581
|
-
children: /* @__PURE__ */
|
|
21075
|
+
children: /* @__PURE__ */ jsx6("text", {
|
|
20582
21076
|
content: state.draft.severity === "blocking" ? "[Blocking]" : " Blocking "
|
|
20583
21077
|
})
|
|
20584
21078
|
}),
|
|
20585
|
-
/* @__PURE__ */
|
|
21079
|
+
/* @__PURE__ */ jsx6("box", {
|
|
20586
21080
|
id: "review-feedback-save",
|
|
20587
21081
|
style: composerFocus === "controls" && composerControlIndex === 4 ? { backgroundColor: "#365f8a" } : {},
|
|
20588
21082
|
onMouseUp: () => {
|
|
@@ -20592,11 +21086,11 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20592
21086
|
setComposerControlIndex(4);
|
|
20593
21087
|
saveDraft();
|
|
20594
21088
|
},
|
|
20595
|
-
children: /* @__PURE__ */
|
|
21089
|
+
children: /* @__PURE__ */ jsx6("text", {
|
|
20596
21090
|
content: replacementInvalid ? "[Save disabled]" : " [Save] "
|
|
20597
21091
|
})
|
|
20598
21092
|
}),
|
|
20599
|
-
/* @__PURE__ */
|
|
21093
|
+
/* @__PURE__ */ jsx6("box", {
|
|
20600
21094
|
id: "review-feedback-cancel",
|
|
20601
21095
|
style: composerFocus === "controls" && composerControlIndex === 5 ? { backgroundColor: "#365f8a" } : {},
|
|
20602
21096
|
onMouseUp: () => {
|
|
@@ -20614,19 +21108,19 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20614
21108
|
session.invalidate();
|
|
20615
21109
|
} catch {}
|
|
20616
21110
|
},
|
|
20617
|
-
children: /* @__PURE__ */
|
|
21111
|
+
children: /* @__PURE__ */ jsx6("text", {
|
|
20618
21112
|
content: " [Cancel] "
|
|
20619
21113
|
})
|
|
20620
21114
|
})
|
|
20621
21115
|
]
|
|
20622
21116
|
}),
|
|
20623
|
-
/* @__PURE__ */
|
|
21117
|
+
/* @__PURE__ */ jsx6("textarea", {
|
|
20624
21118
|
id: "review-feedback-body",
|
|
20625
21119
|
ref: composerBodyRef,
|
|
20626
21120
|
width: "100%",
|
|
20627
21121
|
height: 2,
|
|
20628
21122
|
initialValue: state.draft.body,
|
|
20629
|
-
focused: composerFocus === "body",
|
|
21123
|
+
focused: composerFocus === "body" && !baseSelection,
|
|
20630
21124
|
keyBindings: [{ name: "escape", action: "submit" }],
|
|
20631
21125
|
onSubmit: () => {
|
|
20632
21126
|
const latest = controller.state;
|
|
@@ -20651,10 +21145,10 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20651
21145
|
setComposerFocus("body");
|
|
20652
21146
|
setComposerControlIndex(0);
|
|
20653
21147
|
session.invalidate();
|
|
20654
|
-
|
|
21148
|
+
consume2(event);
|
|
20655
21149
|
} else if (event.ctrl && name.toLowerCase() === "s" && latest?.draft) {
|
|
20656
21150
|
saveDraft();
|
|
20657
|
-
|
|
21151
|
+
consume2(event);
|
|
20658
21152
|
}
|
|
20659
21153
|
},
|
|
20660
21154
|
onContentChange: () => {
|
|
@@ -20666,16 +21160,16 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20666
21160
|
controller.dispatch(planReviewIntent(latest, { type: "feedback/update-draft", body }));
|
|
20667
21161
|
} catch {}
|
|
20668
21162
|
}
|
|
20669
|
-
}),
|
|
20670
|
-
canShowReplacementDraft(state) ? /* @__PURE__ */
|
|
21163
|
+
}, state.document.identity.id),
|
|
21164
|
+
canShowReplacementDraft(state) ? /* @__PURE__ */ jsxs5(Fragment, {
|
|
20671
21165
|
children: [
|
|
20672
|
-
/* @__PURE__ */
|
|
21166
|
+
/* @__PURE__ */ jsx6("textarea", {
|
|
20673
21167
|
id: "review-feedback-replacement",
|
|
20674
21168
|
ref: replacementRef,
|
|
20675
21169
|
width: "100%",
|
|
20676
21170
|
height: 2,
|
|
20677
21171
|
initialValue: state.draft.replacement ?? "",
|
|
20678
|
-
focused: composerFocus === "replacement",
|
|
21172
|
+
focused: composerFocus === "replacement" && !baseSelection,
|
|
20679
21173
|
wrapMode: "char",
|
|
20680
21174
|
placeholder: "Replacement text",
|
|
20681
21175
|
onContentChange: () => {
|
|
@@ -20688,8 +21182,8 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20688
21182
|
session.invalidate();
|
|
20689
21183
|
} catch {}
|
|
20690
21184
|
}
|
|
20691
|
-
}),
|
|
20692
|
-
replacementInvalid ? /* @__PURE__ */
|
|
21185
|
+
}, state.document.identity.id),
|
|
21186
|
+
replacementInvalid ? /* @__PURE__ */ jsx6("text", {
|
|
20693
21187
|
id: "review-feedback-replacement-error",
|
|
20694
21188
|
content: "Invalid replacement: enter non-whitespace text.",
|
|
20695
21189
|
wrapMode: "none",
|
|
@@ -20699,10 +21193,10 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
20699
21193
|
}) : null
|
|
20700
21194
|
]
|
|
20701
21195
|
}) : null,
|
|
20702
|
-
helpOpen ? /* @__PURE__ */
|
|
21196
|
+
helpOpen ? /* @__PURE__ */ jsx6("box", {
|
|
20703
21197
|
id: "review-help-dialog",
|
|
20704
|
-
style: { position: "absolute", left: Math.max(1, Math.floor(dimensions2.width / 10)), top: 2, width: Math.max(50, Math.floor(dimensions2.width * 4 / 5)), height: Math.min(
|
|
20705
|
-
children: /* @__PURE__ */
|
|
21198
|
+
style: { position: "absolute", left: Math.max(1, Math.floor(dimensions2.width / 10)), top: 2, width: Math.max(50, Math.floor(dimensions2.width * 4 / 5)), height: Math.min(27, Math.max(14, dimensions2.height - 3)), zIndex: 70, border: true, flexDirection: "column", backgroundColor: "#202020" },
|
|
21199
|
+
children: /* @__PURE__ */ jsx6("text", {
|
|
20706
21200
|
content: `Review commands
|
|
20707
21201
|
${reviewHelp(focus, state)}
|
|
20708
21202
|
Esc close this help`,
|
|
@@ -20710,16 +21204,16 @@ Esc close this help`,
|
|
|
20710
21204
|
truncate: true
|
|
20711
21205
|
})
|
|
20712
21206
|
}) : null,
|
|
20713
|
-
finishDialog.isOpen() ? /* @__PURE__ */
|
|
21207
|
+
finishDialog.isOpen() ? /* @__PURE__ */ jsxs5("box", {
|
|
20714
21208
|
id: "review-finish-dialog",
|
|
20715
21209
|
style: { position: "absolute", left: Math.max(1, Math.floor(dimensions2.width / 8)), top: 3, width: Math.max(40, Math.floor(dimensions2.width * 3 / 4)), height: 10, zIndex: 60, border: true, flexDirection: "column", backgroundColor: "#202020" },
|
|
20716
21210
|
children: [
|
|
20717
|
-
/* @__PURE__ */
|
|
21211
|
+
/* @__PURE__ */ jsx6("text", {
|
|
20718
21212
|
content: `Finish review — ${finishDialog.getDecision()}`,
|
|
20719
21213
|
wrapMode: "none",
|
|
20720
21214
|
truncate: true
|
|
20721
21215
|
}),
|
|
20722
|
-
/* @__PURE__ */
|
|
21216
|
+
/* @__PURE__ */ jsx6("textarea", {
|
|
20723
21217
|
id: "review-finish-summary",
|
|
20724
21218
|
ref: finishSummaryRef,
|
|
20725
21219
|
width: "100%",
|
|
@@ -20733,10 +21227,10 @@ Esc close this help`,
|
|
|
20733
21227
|
if (name === "escape") {
|
|
20734
21228
|
finishDialog.close();
|
|
20735
21229
|
session.invalidate();
|
|
20736
|
-
|
|
21230
|
+
consume2(event);
|
|
20737
21231
|
} else if (event.ctrl && name.toLowerCase() === "s") {
|
|
20738
21232
|
submitFinish();
|
|
20739
|
-
|
|
21233
|
+
consume2(event);
|
|
20740
21234
|
}
|
|
20741
21235
|
},
|
|
20742
21236
|
keyBindings: [{ name: "enter", action: "submit" }],
|
|
@@ -20746,15 +21240,15 @@ Esc close this help`,
|
|
|
20746
21240
|
session.invalidate();
|
|
20747
21241
|
}
|
|
20748
21242
|
}),
|
|
20749
|
-
/* @__PURE__ */
|
|
21243
|
+
/* @__PURE__ */ jsx6("text", {
|
|
20750
21244
|
content: finishDialog.getValidationMessage(),
|
|
20751
21245
|
wrapMode: "none",
|
|
20752
21246
|
truncate: true
|
|
20753
21247
|
}),
|
|
20754
|
-
/* @__PURE__ */
|
|
21248
|
+
/* @__PURE__ */ jsxs5("box", {
|
|
20755
21249
|
style: { flexDirection: "row", height: 1 },
|
|
20756
21250
|
children: [
|
|
20757
|
-
/* @__PURE__ */
|
|
21251
|
+
/* @__PURE__ */ jsx6("box", {
|
|
20758
21252
|
id: "review-finish-comment",
|
|
20759
21253
|
onMouseUp: () => {
|
|
20760
21254
|
if (resizingSidebarRef.current || resizeReleaseSuppressionRef.current)
|
|
@@ -20762,11 +21256,11 @@ Esc close this help`,
|
|
|
20762
21256
|
finishDialog.setDecision("comment");
|
|
20763
21257
|
session.invalidate();
|
|
20764
21258
|
},
|
|
20765
|
-
children: /* @__PURE__ */
|
|
21259
|
+
children: /* @__PURE__ */ jsx6("text", {
|
|
20766
21260
|
content: finishDialog.getDecision() === "comment" ? "[Comment]" : " Comment "
|
|
20767
21261
|
})
|
|
20768
21262
|
}),
|
|
20769
|
-
/* @__PURE__ */
|
|
21263
|
+
/* @__PURE__ */ jsx6("box", {
|
|
20770
21264
|
id: "review-finish-approve",
|
|
20771
21265
|
onMouseUp: () => {
|
|
20772
21266
|
if (resizingSidebarRef.current || resizeReleaseSuppressionRef.current)
|
|
@@ -20774,11 +21268,11 @@ Esc close this help`,
|
|
|
20774
21268
|
finishDialog.setDecision("approve");
|
|
20775
21269
|
session.invalidate();
|
|
20776
21270
|
},
|
|
20777
|
-
children: /* @__PURE__ */
|
|
21271
|
+
children: /* @__PURE__ */ jsx6("text", {
|
|
20778
21272
|
content: finishDialog.getDecision() === "approve" ? "[Approve]" : " Approve "
|
|
20779
21273
|
})
|
|
20780
21274
|
}),
|
|
20781
|
-
/* @__PURE__ */
|
|
21275
|
+
/* @__PURE__ */ jsx6("box", {
|
|
20782
21276
|
id: "review-finish-request-changes",
|
|
20783
21277
|
onMouseUp: () => {
|
|
20784
21278
|
if (resizingSidebarRef.current || resizeReleaseSuppressionRef.current)
|
|
@@ -20786,39 +21280,40 @@ Esc close this help`,
|
|
|
20786
21280
|
finishDialog.setDecision("request-changes");
|
|
20787
21281
|
session.invalidate();
|
|
20788
21282
|
},
|
|
20789
|
-
children: /* @__PURE__ */
|
|
21283
|
+
children: /* @__PURE__ */ jsx6("text", {
|
|
20790
21284
|
content: finishDialog.getDecision() === "request-changes" ? "[Request Changes]" : " Request Changes "
|
|
20791
21285
|
})
|
|
20792
21286
|
})
|
|
20793
21287
|
]
|
|
20794
21288
|
}),
|
|
20795
|
-
/* @__PURE__ */
|
|
21289
|
+
/* @__PURE__ */ jsx6("box", {
|
|
20796
21290
|
id: "review-finish-submit",
|
|
20797
21291
|
onMouseUp: () => {
|
|
20798
21292
|
if (resizingSidebarRef.current || resizeReleaseSuppressionRef.current)
|
|
20799
21293
|
return;
|
|
20800
21294
|
submitFinish();
|
|
20801
21295
|
},
|
|
20802
|
-
children: /* @__PURE__ */
|
|
21296
|
+
children: /* @__PURE__ */ jsx6("text", {
|
|
20803
21297
|
content: " Submit "
|
|
20804
21298
|
})
|
|
20805
21299
|
}),
|
|
20806
|
-
/* @__PURE__ */
|
|
21300
|
+
/* @__PURE__ */ jsx6("text", {
|
|
20807
21301
|
content: "Ctrl-1 comment · Ctrl-2 approve · Ctrl-3 request changes · Enter/Ctrl-S submit · Esc cancel",
|
|
20808
21302
|
wrapMode: "none",
|
|
20809
21303
|
truncate: true
|
|
20810
21304
|
})
|
|
20811
21305
|
]
|
|
20812
21306
|
}) : null,
|
|
20813
|
-
/* @__PURE__ */
|
|
21307
|
+
/* @__PURE__ */ jsx6("box", {
|
|
20814
21308
|
id: "react-review-footer",
|
|
20815
21309
|
style: { width: "100%", height: 1, flexShrink: 0 },
|
|
20816
|
-
children: /* @__PURE__ */
|
|
20817
|
-
content: reviewFooter(state, layout, focus),
|
|
21310
|
+
children: /* @__PURE__ */ jsx6("text", {
|
|
21311
|
+
content: reviewFooter(state, layout, focus, projectionNotice),
|
|
20818
21312
|
wrapMode: "none",
|
|
20819
21313
|
truncate: true
|
|
20820
21314
|
})
|
|
20821
|
-
})
|
|
21315
|
+
}),
|
|
21316
|
+
basePicker
|
|
20822
21317
|
]
|
|
20823
21318
|
});
|
|
20824
21319
|
}
|
|
@@ -21144,7 +21639,7 @@ var persistedReviewStateSchema = z.object({
|
|
|
21144
21639
|
lastSubmission: submittedReviewRefSchema.nullable(),
|
|
21145
21640
|
submissionInProgress: submissionInProgressSchema.nullable().optional()
|
|
21146
21641
|
}).strict();
|
|
21147
|
-
var baseByHeadSchema = z.record(z.string(), z.object({ baseRef: z.string().min(1) }).strict()).superRefine((val, ctx) => {
|
|
21642
|
+
var baseByHeadSchema = z.record(z.string(), z.object({ baseRef: z.string().min(1), confirmed: z.boolean().optional() }).strict()).superRefine((val, ctx) => {
|
|
21148
21643
|
for (const key of Object.keys(val)) {
|
|
21149
21644
|
if (!isValidBaseByHeadKey(key)) {
|
|
21150
21645
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `invalid baseByHead key: ${key}`, path: [key] });
|
|
@@ -21298,8 +21793,9 @@ function toDatabase(raw) {
|
|
|
21298
21793
|
for (const [k, v] of Object.entries(raw.reviews))
|
|
21299
21794
|
reviews[k] = toPersistedReviewState(v);
|
|
21300
21795
|
const baseByHead = {};
|
|
21301
|
-
for (const [k, v] of Object.entries(raw.baseByHead))
|
|
21302
|
-
baseByHead[k] = { baseRef: v.baseRef };
|
|
21796
|
+
for (const [k, v] of Object.entries(raw.baseByHead)) {
|
|
21797
|
+
baseByHead[k] = { baseRef: v.baseRef, ...v.confirmed === undefined ? {} : { confirmed: v.confirmed } };
|
|
21798
|
+
}
|
|
21303
21799
|
return { version: 2, baseByHead, reviews };
|
|
21304
21800
|
}
|
|
21305
21801
|
function toIdentity(raw) {
|
|
@@ -21665,7 +22161,7 @@ class ReactReviewSession {
|
|
|
21665
22161
|
}
|
|
21666
22162
|
|
|
21667
22163
|
// src/ui/review-workspace/react-review-host.tsx
|
|
21668
|
-
import { jsx as
|
|
22164
|
+
import { jsx as jsx7 } from "@opentui/react/jsx-runtime";
|
|
21669
22165
|
var rootsByRenderer = new WeakMap;
|
|
21670
22166
|
|
|
21671
22167
|
class ReactReviewHost {
|
|
@@ -21688,7 +22184,7 @@ class ReactReviewHost {
|
|
|
21688
22184
|
if (!existing.mounted) {
|
|
21689
22185
|
existing.mounted = true;
|
|
21690
22186
|
flushSync(() => {
|
|
21691
|
-
this.reactRoot.render(/* @__PURE__ */
|
|
22187
|
+
this.reactRoot.render(/* @__PURE__ */ jsx7(ReviewWorkspaceApp, {
|
|
21692
22188
|
session: this.session
|
|
21693
22189
|
}));
|
|
21694
22190
|
});
|
|
@@ -21700,7 +22196,7 @@ class ReactReviewHost {
|
|
|
21700
22196
|
this.reactRoot = createRoot(renderer);
|
|
21701
22197
|
rootsByRenderer.set(renderer, { root: this.reactRoot, session: this.session, mounted: true });
|
|
21702
22198
|
flushSync(() => {
|
|
21703
|
-
this.reactRoot.render(/* @__PURE__ */
|
|
22199
|
+
this.reactRoot.render(/* @__PURE__ */ jsx7(ReviewWorkspaceApp, {
|
|
21704
22200
|
session: this.session
|
|
21705
22201
|
}));
|
|
21706
22202
|
});
|
|
@@ -22281,6 +22777,24 @@ function reduceReviewState(state, action) {
|
|
|
22281
22777
|
revision: state.revision + 1
|
|
22282
22778
|
};
|
|
22283
22779
|
}
|
|
22780
|
+
case "projection/apply": {
|
|
22781
|
+
const firstFile = action.document.files[0] ?? null;
|
|
22782
|
+
return {
|
|
22783
|
+
...state,
|
|
22784
|
+
document: action.document,
|
|
22785
|
+
projection: action.projection,
|
|
22786
|
+
selection: { fileKey: firstFile?.key ?? null, hunkIndex: 0 },
|
|
22787
|
+
lineSelection: null,
|
|
22788
|
+
expandedGaps: [],
|
|
22789
|
+
reveal: {
|
|
22790
|
+
fileTopToken: state.reveal.fileTopToken + 1,
|
|
22791
|
+
fileTopRequestToken: state.reveal.fileTopRequestToken + 1,
|
|
22792
|
+
hunkToken: state.reveal.hunkToken + 1,
|
|
22793
|
+
scrollToFeedback: false
|
|
22794
|
+
},
|
|
22795
|
+
revision: state.revision + 1
|
|
22796
|
+
};
|
|
22797
|
+
}
|
|
22284
22798
|
case "projection/set": {
|
|
22285
22799
|
if (projectionsEqual(state.projection, action.projection))
|
|
22286
22800
|
return state;
|
|
@@ -22860,65 +23374,97 @@ async function currentBranchRef(runner) {
|
|
|
22860
23374
|
const ref = (await output(runner, ["symbolic-ref", "--quiet", "HEAD"]))?.trim();
|
|
22861
23375
|
return ref?.startsWith("refs/heads/") ? ref : undefined;
|
|
22862
23376
|
}
|
|
22863
|
-
async function
|
|
22864
|
-
const
|
|
22865
|
-
|
|
22866
|
-
|
|
22867
|
-
|
|
22868
|
-
|
|
22869
|
-
|
|
22870
|
-
|
|
22871
|
-
|
|
22872
|
-
|
|
22873
|
-
}
|
|
22874
|
-
|
|
22875
|
-
const
|
|
22876
|
-
const
|
|
22877
|
-
const
|
|
22878
|
-
const
|
|
22879
|
-
|
|
22880
|
-
|
|
22881
|
-
|
|
22882
|
-
|
|
22883
|
-
|
|
22884
|
-
|
|
22885
|
-
|
|
22886
|
-
|
|
22887
|
-
|
|
23377
|
+
async function inferReviewBase(runner, preferredRef) {
|
|
23378
|
+
const [refResult, branchResult, headResult, remoteResult] = await Promise.all([
|
|
23379
|
+
runner.run([
|
|
23380
|
+
"for-each-ref",
|
|
23381
|
+
"--format=%(refname)%09%(objectname)%09%(objecttype)%09%(symref)%09%(upstream:remotename)",
|
|
23382
|
+
"refs/heads",
|
|
23383
|
+
"refs/remotes"
|
|
23384
|
+
], { readOnly: true }),
|
|
23385
|
+
runner.run(["symbolic-ref", "--quiet", "HEAD"], { readOnly: true, acceptedExitCodes: [0, 1] }),
|
|
23386
|
+
runner.run(["rev-parse", "--verify", "--quiet", "HEAD^{commit}"], { readOnly: true, acceptedExitCodes: [0, 1] }),
|
|
23387
|
+
runner.run(["remote"], { readOnly: true })
|
|
23388
|
+
]);
|
|
23389
|
+
const branchRef = branchResult.exitCode === 0 ? branchResult.stdout.trim() : undefined;
|
|
23390
|
+
const headOid = headResult.exitCode === 0 ? headResult.stdout.trim() : undefined;
|
|
23391
|
+
const branchName = branchRef?.startsWith("refs/heads/") ? branchRef.slice("refs/heads/".length) : undefined;
|
|
23392
|
+
const refs = [];
|
|
23393
|
+
for (const line of refResult.stdout.split(/\r?\n/)) {
|
|
23394
|
+
const [ref, oid, objectType, symbolic = "", upstreamRemote2 = ""] = line.split("\t");
|
|
23395
|
+
if (ref === undefined || oid === undefined || objectType !== "commit")
|
|
23396
|
+
continue;
|
|
23397
|
+
refs.push({ ref, oid, symbolic, upstreamRemote: upstreamRemote2 });
|
|
23398
|
+
}
|
|
23399
|
+
const upstreamRemote = refs.find(({ ref }) => ref === branchRef)?.upstreamRemote;
|
|
23400
|
+
const remoteNames = remoteResult.stdout.split(/\r?\n/).filter(Boolean).sort((left2, right2) => right2.length - left2.length || (left2 < right2 ? -1 : left2 > right2 ? 1 : 0));
|
|
23401
|
+
const remoteOrder = (remote) => remote === upstreamRemote ? 0 : remote === "origin" ? 1 : 2;
|
|
23402
|
+
const defaults = refs.filter(({ ref, symbolic }) => ref.startsWith("refs/remotes/") && ref.endsWith("/HEAD") && symbolic !== "").sort((left2, right2) => {
|
|
23403
|
+
const leftRemote = left2.ref.slice("refs/remotes/".length, -"/HEAD".length);
|
|
23404
|
+
const rightRemote = right2.ref.slice("refs/remotes/".length, -"/HEAD".length);
|
|
23405
|
+
return remoteOrder(leftRemote) - remoteOrder(rightRemote) || (left2.ref < right2.ref ? -1 : left2.ref > right2.ref ? 1 : 0);
|
|
22888
23406
|
});
|
|
22889
|
-
|
|
22890
|
-
}
|
|
22891
|
-
|
|
22892
|
-
|
|
22893
|
-
|
|
22894
|
-
|
|
22895
|
-
|
|
22896
|
-
|
|
22897
|
-
|
|
22898
|
-
|
|
22899
|
-
|
|
22900
|
-
|
|
22901
|
-
const
|
|
22902
|
-
|
|
22903
|
-
|
|
22904
|
-
const
|
|
22905
|
-
|
|
22906
|
-
|
|
22907
|
-
|
|
22908
|
-
|
|
22909
|
-
|
|
22910
|
-
const
|
|
22911
|
-
|
|
22912
|
-
|
|
22913
|
-
|
|
22914
|
-
|
|
22915
|
-
|
|
22916
|
-
|
|
22917
|
-
|
|
22918
|
-
|
|
23407
|
+
const defaultRanks = new Map(defaults.map(({ symbolic }, index) => [symbolic, index]));
|
|
23408
|
+
const defaultRemotes = new Map(defaults.map(({ ref, symbolic }) => [symbolic, ref.slice("refs/remotes/".length, -"/HEAD".length)]));
|
|
23409
|
+
const branches = refs.filter(({ ref, symbolic }) => ref !== branchRef && symbolic === "" && !(ref.startsWith("refs/remotes/") && ref.endsWith("/HEAD")));
|
|
23410
|
+
const firstParentDistances = new Map;
|
|
23411
|
+
if (headOid !== undefined && branches.length > 0) {
|
|
23412
|
+
const history = await runner.run(["rev-list", "--first-parent", "--max-count=2049", headOid, "--"], { readOnly: true });
|
|
23413
|
+
let distance = 0;
|
|
23414
|
+
for (const oid of history.stdout.trim().split(/\r?\n/)) {
|
|
23415
|
+
if (oid !== "")
|
|
23416
|
+
firstParentDistances.set(oid, distance++);
|
|
23417
|
+
}
|
|
23418
|
+
}
|
|
23419
|
+
const ranked = branches.map(({ ref, oid }) => {
|
|
23420
|
+
const local = ref.startsWith("refs/heads/");
|
|
23421
|
+
const label = ref.slice(local ? "refs/heads/".length : "refs/remotes/".length);
|
|
23422
|
+
const remote = local ? undefined : remoteNames.find((name) => label.startsWith(`${name}/`));
|
|
23423
|
+
const shortName = local ? label : remote === undefined ? undefined : label.slice(remote.length + 1);
|
|
23424
|
+
const trackingHead = !local && branchName !== undefined && shortName === branchName;
|
|
23425
|
+
const sameHead = headOid !== undefined && oid === headOid;
|
|
23426
|
+
const distance = firstParentDistances.get(oid);
|
|
23427
|
+
const defaultRank = defaultRanks.get(ref);
|
|
23428
|
+
const defaultRemote = defaultRemotes.get(ref);
|
|
23429
|
+
const conventionalRank = shortName === "main" ? 0 : shortName === "master" ? 1 : shortName === "develop" ? 2 : 3;
|
|
23430
|
+
const defaultReason = defaultRemote === undefined ? undefined : `Default branch of ${defaultRemote === upstreamRemote ? "upstream remote " : "remote "}${defaultRemote}`;
|
|
23431
|
+
let priority;
|
|
23432
|
+
let reason;
|
|
23433
|
+
if (ref === preferredRef) {
|
|
23434
|
+
priority = 0;
|
|
23435
|
+
reason = "Previously selected review base";
|
|
23436
|
+
} else if (trackingHead || sameHead) {
|
|
23437
|
+
priority = 5;
|
|
23438
|
+
reason = trackingHead ? "Same-name remote branch for current HEAD" : "Points at current HEAD";
|
|
23439
|
+
} else if (distance !== undefined && distance > 0) {
|
|
23440
|
+
priority = 1;
|
|
23441
|
+
reason = `Branch tip ${distance} first-parent ${distance === 1 ? "commit" : "commits"} behind HEAD`;
|
|
23442
|
+
if (defaultReason !== undefined)
|
|
23443
|
+
reason += `; ${defaultReason}`;
|
|
23444
|
+
} else if (defaultRank !== undefined) {
|
|
23445
|
+
priority = 2;
|
|
23446
|
+
reason = defaultReason;
|
|
23447
|
+
} else if (conventionalRank < 3) {
|
|
23448
|
+
priority = 3;
|
|
23449
|
+
reason = `Conventional ${shortName} base branch`;
|
|
23450
|
+
} else {
|
|
23451
|
+
priority = 4;
|
|
23452
|
+
reason = local ? "Local branch" : "Remote branch";
|
|
22919
23453
|
}
|
|
22920
|
-
|
|
22921
|
-
|
|
23454
|
+
return {
|
|
23455
|
+
candidate: { ref, label, reason },
|
|
23456
|
+
priority,
|
|
23457
|
+
distance: priority === 1 ? distance : 0,
|
|
23458
|
+
defaultRank: priority === 1 || priority === 2 ? defaultRank ?? defaults.length : 0,
|
|
23459
|
+
conventionalRank: priority === 1 || priority === 3 ? conventionalRank : 0
|
|
23460
|
+
};
|
|
23461
|
+
});
|
|
23462
|
+
ranked.sort((left2, right2) => left2.priority - right2.priority || left2.distance - right2.distance || left2.defaultRank - right2.defaultRank || left2.conventionalRank - right2.conventionalRank || (left2.candidate.ref < right2.candidate.ref ? -1 : left2.candidate.ref > right2.candidate.ref ? 1 : 0));
|
|
23463
|
+
return {
|
|
23464
|
+
kind: "choose",
|
|
23465
|
+
candidates: ranked.map(({ candidate }) => candidate),
|
|
23466
|
+
reason: headOid === undefined ? "HEAD has no commit" : branchRef === undefined ? "HEAD is detached; choose a review base" : "Choose a review base; likely branches are listed first"
|
|
23467
|
+
};
|
|
22922
23468
|
}
|
|
22923
23469
|
|
|
22924
23470
|
// src/review/git/load-review-document.ts
|
|
@@ -23105,6 +23651,187 @@ async function loadReviewDocument(runner, baseRef) {
|
|
|
23105
23651
|
return createReviewDocument({ identity, generation, commits: commitsForDoc, files });
|
|
23106
23652
|
}
|
|
23107
23653
|
|
|
23654
|
+
// src/review/git/load-review-projection.ts
|
|
23655
|
+
function normalizePathForJoin2(p) {
|
|
23656
|
+
return p.replace(/[\r\n]+$/u, "");
|
|
23657
|
+
}
|
|
23658
|
+
function kindFromRaw2(rawStatus, isBinary) {
|
|
23659
|
+
if (isBinary)
|
|
23660
|
+
return "binary";
|
|
23661
|
+
const letter = rawStatus[0] ?? "";
|
|
23662
|
+
if (letter === "R")
|
|
23663
|
+
return "renamed";
|
|
23664
|
+
if (letter === "C")
|
|
23665
|
+
return "copied";
|
|
23666
|
+
if (letter === "A")
|
|
23667
|
+
return "added";
|
|
23668
|
+
if (letter === "D")
|
|
23669
|
+
return "deleted";
|
|
23670
|
+
return "modified";
|
|
23671
|
+
}
|
|
23672
|
+
function sourceFromKind2(kind, isBinary) {
|
|
23673
|
+
if (isBinary || kind === "binary")
|
|
23674
|
+
return "binary";
|
|
23675
|
+
return "available";
|
|
23676
|
+
}
|
|
23677
|
+
async function buildFilesForRange(runner, rangeArgs) {
|
|
23678
|
+
const patchResult = await runner.run(["diff", "--no-ext-diff", "--no-color", "--find-renames", "--binary", "--src-prefix=a/", "--dst-prefix=b/", ...rangeArgs, "--"], { readOnly: true });
|
|
23679
|
+
const rawResult = await runner.run(["diff", "--no-ext-diff", "--no-color", "--find-renames", "--raw", "-z", ...rangeArgs, "--"], { readOnly: true });
|
|
23680
|
+
const numstatResult = await runner.run(["diff", "--no-ext-diff", "--no-color", "--find-renames", "--numstat", "-z", ...rangeArgs, "--"], { readOnly: true });
|
|
23681
|
+
const patchText = patchResult.stdout;
|
|
23682
|
+
const rawText = rawResult.stdout;
|
|
23683
|
+
const numstatText = numstatResult.stdout;
|
|
23684
|
+
const parsedPatches = parseReviewPatch(patchText);
|
|
23685
|
+
const rawEntries = parseRawDiffZ(rawText);
|
|
23686
|
+
const numstatEntries = parseNumstatZ(numstatText);
|
|
23687
|
+
const rawByKey = new Map;
|
|
23688
|
+
for (const entry of rawEntries) {
|
|
23689
|
+
const key = `${normalizePathForJoin2(entry.path)}|${entry.previousPath ? normalizePathForJoin2(entry.previousPath) : ""}`;
|
|
23690
|
+
const list = rawByKey.get(key);
|
|
23691
|
+
if (list)
|
|
23692
|
+
list.push(entry);
|
|
23693
|
+
else
|
|
23694
|
+
rawByKey.set(key, [entry]);
|
|
23695
|
+
}
|
|
23696
|
+
const numstatByKey = new Map;
|
|
23697
|
+
for (const entry of numstatEntries) {
|
|
23698
|
+
const key = `${normalizePathForJoin2(entry.path)}|${entry.previousPath ? normalizePathForJoin2(entry.previousPath) : ""}`;
|
|
23699
|
+
const list = numstatByKey.get(key);
|
|
23700
|
+
if (list)
|
|
23701
|
+
list.push(entry);
|
|
23702
|
+
else
|
|
23703
|
+
numstatByKey.set(key, [entry]);
|
|
23704
|
+
}
|
|
23705
|
+
const seenPatchKeys = new Set;
|
|
23706
|
+
for (const pf of parsedPatches) {
|
|
23707
|
+
const key = `${normalizePathForJoin2(pf.path)}|${pf.previousPath ? normalizePathForJoin2(pf.previousPath) : ""}`;
|
|
23708
|
+
if (seenPatchKeys.has(key))
|
|
23709
|
+
throw new Error(`ambiguous patch join: duplicate path ${key}`);
|
|
23710
|
+
seenPatchKeys.add(key);
|
|
23711
|
+
}
|
|
23712
|
+
if (parsedPatches.length === 0) {
|
|
23713
|
+
if (rawEntries.length !== 0)
|
|
23714
|
+
throw new Error(`missing patch for raw entries: ${rawEntries.map((r) => r.path).join(",")}`);
|
|
23715
|
+
if (numstatEntries.length !== 0)
|
|
23716
|
+
throw new Error(`missing patch for numstat entries: ${numstatEntries.map((r) => r.path).join(",")}`);
|
|
23717
|
+
return [];
|
|
23718
|
+
}
|
|
23719
|
+
const files = [];
|
|
23720
|
+
const matchedRawKeys = new Set;
|
|
23721
|
+
const matchedNumstatKeys = new Set;
|
|
23722
|
+
for (const pf of parsedPatches) {
|
|
23723
|
+
const key = `${normalizePathForJoin2(pf.path)}|${pf.previousPath ? normalizePathForJoin2(pf.previousPath) : ""}`;
|
|
23724
|
+
const rawList = rawByKey.get(key);
|
|
23725
|
+
if (!rawList || rawList.length === 0) {
|
|
23726
|
+
throw new Error(`missing raw entry for patch file ${pf.path}${pf.previousPath ? ` (from ${pf.previousPath})` : ""}`);
|
|
23727
|
+
}
|
|
23728
|
+
if (rawList.length > 1)
|
|
23729
|
+
throw new Error(`ambiguous raw join for ${pf.path}`);
|
|
23730
|
+
const raw = rawList[0];
|
|
23731
|
+
matchedRawKeys.add(key);
|
|
23732
|
+
const numstatList = numstatByKey.get(key);
|
|
23733
|
+
let numstat = numstatList?.[0];
|
|
23734
|
+
if (!numstat) {
|
|
23735
|
+
throw new Error(`missing numstat entry for patch file ${pf.path}`);
|
|
23736
|
+
}
|
|
23737
|
+
if (numstatList && numstatList.length > 1)
|
|
23738
|
+
throw new Error(`ambiguous numstat join for ${pf.path}`);
|
|
23739
|
+
matchedNumstatKeys.add(key);
|
|
23740
|
+
const patchPrevNorm = pf.previousPath ? normalizePathForJoin2(pf.previousPath) : undefined;
|
|
23741
|
+
const numstatPrevNorm = numstat.previousPath ? normalizePathForJoin2(numstat.previousPath) : undefined;
|
|
23742
|
+
if (patchPrevNorm !== numstatPrevNorm) {
|
|
23743
|
+
if (patchPrevNorm !== undefined || numstatPrevNorm !== undefined) {
|
|
23744
|
+
throw new Error(`mismatched previousPath for ${pf.path}: patch ${patchPrevNorm} vs numstat ${numstatPrevNorm}`);
|
|
23745
|
+
}
|
|
23746
|
+
}
|
|
23747
|
+
const kind = kindFromRaw2(raw.status, pf.isBinary);
|
|
23748
|
+
const source = sourceFromKind2(kind, pf.isBinary);
|
|
23749
|
+
const normalizedHunkBody = pf.normalizedHunkBody;
|
|
23750
|
+
const contentId = sha256Tuple2([raw.oldBlobOid ?? "", raw.newBlobOid ?? "", raw.oldMode ?? "", raw.newMode ?? "", normalizedHunkBody]);
|
|
23751
|
+
const patchDigest = pf.patchDigest;
|
|
23752
|
+
const stats = { additions: numstat.additions, deletions: numstat.deletions };
|
|
23753
|
+
const hunks = pf.isBinary ? [] : pf.hunks;
|
|
23754
|
+
const file = pf.previousPath ? {
|
|
23755
|
+
key: pf.path,
|
|
23756
|
+
path: pf.path,
|
|
23757
|
+
previousPath: pf.previousPath,
|
|
23758
|
+
kind,
|
|
23759
|
+
oldBlobOid: raw.oldBlobOid,
|
|
23760
|
+
newBlobOid: raw.newBlobOid,
|
|
23761
|
+
oldMode: raw.oldMode,
|
|
23762
|
+
newMode: raw.newMode,
|
|
23763
|
+
contentId,
|
|
23764
|
+
patchDigest,
|
|
23765
|
+
stats,
|
|
23766
|
+
hunks,
|
|
23767
|
+
source
|
|
23768
|
+
} : {
|
|
23769
|
+
key: pf.path,
|
|
23770
|
+
path: pf.path,
|
|
23771
|
+
kind,
|
|
23772
|
+
oldBlobOid: raw.oldBlobOid,
|
|
23773
|
+
newBlobOid: raw.newBlobOid,
|
|
23774
|
+
oldMode: raw.oldMode,
|
|
23775
|
+
newMode: raw.newMode,
|
|
23776
|
+
contentId,
|
|
23777
|
+
patchDigest,
|
|
23778
|
+
stats,
|
|
23779
|
+
hunks,
|
|
23780
|
+
source
|
|
23781
|
+
};
|
|
23782
|
+
files.push(file);
|
|
23783
|
+
}
|
|
23784
|
+
for (const [key, list] of rawByKey) {
|
|
23785
|
+
if (!matchedRawKeys.has(key))
|
|
23786
|
+
throw new Error(`missing patch for raw entries: ${key} (${list.length})`);
|
|
23787
|
+
}
|
|
23788
|
+
for (const [key] of numstatByKey) {
|
|
23789
|
+
if (!matchedNumstatKeys.has(key))
|
|
23790
|
+
throw new Error(`missing patch for numstat entries: ${key}`);
|
|
23791
|
+
}
|
|
23792
|
+
return files;
|
|
23793
|
+
}
|
|
23794
|
+
async function isAncestor(runner, ancestorOid, descendantOid) {
|
|
23795
|
+
if (!ancestorOid || !descendantOid)
|
|
23796
|
+
return false;
|
|
23797
|
+
if (ancestorOid === descendantOid)
|
|
23798
|
+
return true;
|
|
23799
|
+
try {
|
|
23800
|
+
const result = await runner.run(["merge-base", "--is-ancestor", ancestorOid, descendantOid], {
|
|
23801
|
+
readOnly: true,
|
|
23802
|
+
acceptedExitCodes: [0, 1]
|
|
23803
|
+
});
|
|
23804
|
+
return result.exitCode === 0;
|
|
23805
|
+
} catch {
|
|
23806
|
+
return false;
|
|
23807
|
+
}
|
|
23808
|
+
}
|
|
23809
|
+
async function loadSinceLastReviewProjection(runner, aggregateDocument, lastHeadOid) {
|
|
23810
|
+
if (!lastHeadOid || lastHeadOid.trim() === "") {
|
|
23811
|
+
throw new Error("lastHeadOid must be non-empty");
|
|
23812
|
+
}
|
|
23813
|
+
const headOid = aggregateDocument.generation.headOid;
|
|
23814
|
+
const ancestor = await isAncestor(runner, lastHeadOid, headOid);
|
|
23815
|
+
if (!ancestor) {
|
|
23816
|
+
return {
|
|
23817
|
+
kind: "history-rewritten",
|
|
23818
|
+
lastHeadOid,
|
|
23819
|
+
headOid,
|
|
23820
|
+
reason: "history rewritten: last submission head is not an ancestor of current HEAD"
|
|
23821
|
+
};
|
|
23822
|
+
}
|
|
23823
|
+
const range = `${lastHeadOid}..${headOid}`;
|
|
23824
|
+
const files = await buildFilesForRange(runner, [range]);
|
|
23825
|
+
const projection = { kind: "since-last-review", fromHeadOid: lastHeadOid };
|
|
23826
|
+
const doc = {
|
|
23827
|
+
reviewId: aggregateDocument.identity.id,
|
|
23828
|
+
generationId: aggregateDocument.generation.id,
|
|
23829
|
+
projection,
|
|
23830
|
+
files
|
|
23831
|
+
};
|
|
23832
|
+
return { kind: "ok", document: doc };
|
|
23833
|
+
}
|
|
23834
|
+
|
|
23108
23835
|
// src/review/git/load-source-context.ts
|
|
23109
23836
|
var DEFAULT_MAX_BYTES = 1e6;
|
|
23110
23837
|
function isZeroOid(oid) {
|
|
@@ -23691,6 +24418,7 @@ class ReviewWorkspaceController {
|
|
|
23691
24418
|
stateStore;
|
|
23692
24419
|
artifactStore;
|
|
23693
24420
|
loadDocumentImpl;
|
|
24421
|
+
loadSinceLastReviewImpl;
|
|
23694
24422
|
loadSourceContextImpl;
|
|
23695
24423
|
nowImpl;
|
|
23696
24424
|
randomIdImpl;
|
|
@@ -23703,14 +24431,18 @@ class ReviewWorkspaceController {
|
|
|
23703
24431
|
activeReviewId;
|
|
23704
24432
|
activeGenerationId;
|
|
23705
24433
|
baseRef;
|
|
24434
|
+
_baseSelection;
|
|
24435
|
+
baseSelectionRequestId = 0;
|
|
23706
24436
|
sourceContextCache = new Map;
|
|
23707
24437
|
pendingGapRequests = new Map;
|
|
23708
24438
|
gapRequestCounter = 0;
|
|
24439
|
+
aggregateDocument;
|
|
23709
24440
|
constructor(options) {
|
|
23710
24441
|
this.runner = options.runner;
|
|
23711
24442
|
this.stateStore = options.stateStore;
|
|
23712
24443
|
this.artifactStore = options.artifactStore;
|
|
23713
24444
|
this.loadDocumentImpl = options.loadDocument ?? ((baseRef) => loadReviewDocument(options.runner, baseRef));
|
|
24445
|
+
this.loadSinceLastReviewImpl = options.loadSinceLastReview ?? ((aggregate, fromHeadOid) => loadSinceLastReviewProjection(options.runner, aggregate, fromHeadOid));
|
|
23714
24446
|
this.loadSourceContextImpl = options.loadSourceContextImpl;
|
|
23715
24447
|
this.nowImpl = options.now ?? (() => new Date().toISOString());
|
|
23716
24448
|
this.randomIdImpl = options.randomId ?? (() => {
|
|
@@ -23736,6 +24468,78 @@ class ReviewWorkspaceController {
|
|
|
23736
24468
|
get base() {
|
|
23737
24469
|
return this.baseRef;
|
|
23738
24470
|
}
|
|
24471
|
+
get baseSelection() {
|
|
24472
|
+
return this._baseSelection;
|
|
24473
|
+
}
|
|
24474
|
+
async requestBaseSelection() {
|
|
24475
|
+
if (this.destroyed || this._baseSelection?.selecting)
|
|
24476
|
+
return;
|
|
24477
|
+
const token = ++this.baseSelectionRequestId;
|
|
24478
|
+
this.requestId++;
|
|
24479
|
+
this._baseSelection = { candidates: [], loading: true, selecting: false };
|
|
24480
|
+
this.publish();
|
|
24481
|
+
try {
|
|
24482
|
+
const remembered = this.baseRef === undefined ? await this.rememberedBase() : undefined;
|
|
24483
|
+
const inferred = await inferReviewBase(this.runner, this.baseRef ?? remembered?.baseRef);
|
|
24484
|
+
if (this.destroyed || token !== this.baseSelectionRequestId)
|
|
24485
|
+
return;
|
|
24486
|
+
this._baseSelection = { candidates: inferred.candidates, loading: false, selecting: false };
|
|
24487
|
+
} catch (err) {
|
|
24488
|
+
if (this.destroyed || token !== this.baseSelectionRequestId)
|
|
24489
|
+
return;
|
|
24490
|
+
this._baseSelection = {
|
|
24491
|
+
candidates: [],
|
|
24492
|
+
loading: false,
|
|
24493
|
+
selecting: false,
|
|
24494
|
+
error: err instanceof Error ? err.message : String(err)
|
|
24495
|
+
};
|
|
24496
|
+
}
|
|
24497
|
+
this.publish();
|
|
24498
|
+
}
|
|
24499
|
+
cancelBaseSelection() {
|
|
24500
|
+
if (this._baseSelection?.selecting)
|
|
24501
|
+
return;
|
|
24502
|
+
this.baseSelectionRequestId++;
|
|
24503
|
+
this._baseSelection = undefined;
|
|
24504
|
+
this.publish();
|
|
24505
|
+
}
|
|
24506
|
+
async chooseBase(ref) {
|
|
24507
|
+
const picker = this._baseSelection;
|
|
24508
|
+
if (this.destroyed || !picker || picker.loading || picker.selecting || !picker.candidates.some((candidate) => candidate.ref === ref))
|
|
24509
|
+
return false;
|
|
24510
|
+
const token = ++this.baseSelectionRequestId;
|
|
24511
|
+
this.requestId++;
|
|
24512
|
+
this._baseSelection = { candidates: picker.candidates, loading: false, selecting: true };
|
|
24513
|
+
this.publish();
|
|
24514
|
+
return this.reviewOperationQueue.run(async () => {
|
|
24515
|
+
if (this.destroyed || token !== this.baseSelectionRequestId)
|
|
24516
|
+
return false;
|
|
24517
|
+
try {
|
|
24518
|
+
if (this._state !== undefined)
|
|
24519
|
+
await this.persistState();
|
|
24520
|
+
await this.stateStore?.flush();
|
|
24521
|
+
if (this.destroyed || token !== this.baseSelectionRequestId)
|
|
24522
|
+
return false;
|
|
24523
|
+
await this.open(ref);
|
|
24524
|
+
if (this.destroyed || token !== this.baseSelectionRequestId)
|
|
24525
|
+
return false;
|
|
24526
|
+
this._baseSelection = undefined;
|
|
24527
|
+
this.publish();
|
|
24528
|
+
return true;
|
|
24529
|
+
} catch (err) {
|
|
24530
|
+
if (this.destroyed || token !== this.baseSelectionRequestId)
|
|
24531
|
+
return false;
|
|
24532
|
+
this._baseSelection = {
|
|
24533
|
+
candidates: picker.candidates,
|
|
24534
|
+
loading: false,
|
|
24535
|
+
selecting: false,
|
|
24536
|
+
error: err instanceof Error ? err.message : String(err)
|
|
24537
|
+
};
|
|
24538
|
+
this.publish();
|
|
24539
|
+
return false;
|
|
24540
|
+
}
|
|
24541
|
+
});
|
|
24542
|
+
}
|
|
23739
24543
|
clearError() {
|
|
23740
24544
|
if (this._error === undefined)
|
|
23741
24545
|
return;
|
|
@@ -23744,7 +24548,7 @@ class ReviewWorkspaceController {
|
|
|
23744
24548
|
}
|
|
23745
24549
|
get refreshGeneration() {
|
|
23746
24550
|
return async () => {
|
|
23747
|
-
if (this.baseRef === undefined || this.destroyed)
|
|
24551
|
+
if (this.baseRef === undefined || this.destroyed || this._baseSelection !== undefined)
|
|
23748
24552
|
return;
|
|
23749
24553
|
const token = ++this.requestId;
|
|
23750
24554
|
const capturedGeneration = this.activeGenerationId;
|
|
@@ -23788,10 +24592,12 @@ class ReviewWorkspaceController {
|
|
|
23788
24592
|
this.sourceContextCache.clear();
|
|
23789
24593
|
this.pendingGapRequests.clear();
|
|
23790
24594
|
const currentState = this._state;
|
|
23791
|
-
const
|
|
24595
|
+
const reconcileFrom = currentState !== undefined && currentState.projection.kind !== "aggregate" ? { ...currentState, projection: { kind: "aggregate" } } : currentState;
|
|
24596
|
+
const nextState = reconcileFrom === undefined ? createInitialReviewState(doc) : reconcileReviewState(reconcileFrom, doc);
|
|
23792
24597
|
if (!ownsRequest())
|
|
23793
24598
|
return;
|
|
23794
24599
|
this._state = nextState;
|
|
24600
|
+
this.aggregateDocument = undefined;
|
|
23795
24601
|
this.activeReviewId = doc.identity.id;
|
|
23796
24602
|
this.activeGenerationId = doc.generation.id;
|
|
23797
24603
|
this._error = undefined;
|
|
@@ -23812,10 +24618,21 @@ class ReviewWorkspaceController {
|
|
|
23812
24618
|
throw new Error("controller destroyed");
|
|
23813
24619
|
const token = ++this.requestId;
|
|
23814
24620
|
let resolvedBase = baseRef;
|
|
23815
|
-
|
|
24621
|
+
if (resolvedBase !== undefined && this._baseSelection !== undefined && !this._baseSelection.selecting) {
|
|
24622
|
+
this.baseSelectionRequestId++;
|
|
24623
|
+
this._baseSelection = undefined;
|
|
24624
|
+
this.publish();
|
|
24625
|
+
}
|
|
24626
|
+
let corruptError = this._error?.kind === "corrupt-state" || this._error?.kind === "storage" ? this._error : undefined;
|
|
23816
24627
|
if (resolvedBase === undefined) {
|
|
23817
24628
|
try {
|
|
23818
|
-
|
|
24629
|
+
const remembered = await this.rememberedBase();
|
|
24630
|
+
if (remembered?.confirmed === true && await resolveRefOid(this.runner, remembered.baseRef) !== undefined) {
|
|
24631
|
+
resolvedBase = remembered.baseRef;
|
|
24632
|
+
} else {
|
|
24633
|
+
await this.requestBaseSelection();
|
|
24634
|
+
return;
|
|
24635
|
+
}
|
|
23819
24636
|
const warning = this.stateStore?.quarantineWarning;
|
|
23820
24637
|
if (warning) {
|
|
23821
24638
|
const pathMatch = warning.match(/moved to (\S+)/);
|
|
@@ -23831,6 +24648,8 @@ class ReviewWorkspaceController {
|
|
|
23831
24648
|
}
|
|
23832
24649
|
if (this.destroyed || token !== this.requestId)
|
|
23833
24650
|
throw new Error("open cancelled");
|
|
24651
|
+
if (resolvedBase === undefined)
|
|
24652
|
+
throw new Error("base selection required");
|
|
23834
24653
|
let doc;
|
|
23835
24654
|
try {
|
|
23836
24655
|
doc = await this.loadDocumentImpl(resolvedBase);
|
|
@@ -23855,7 +24674,20 @@ class ReviewWorkspaceController {
|
|
|
23855
24674
|
const qPath = pathMatch?.[1] ?? warning;
|
|
23856
24675
|
corruptError = createCorruptStateError(qPath, warning);
|
|
23857
24676
|
}
|
|
23858
|
-
|
|
24677
|
+
let persisted = db.reviews[doc.identity.id];
|
|
24678
|
+
if (persisted === undefined) {
|
|
24679
|
+
const headKey = doc.identity.headRef ?? `detached:${doc.identity.detachedHeadOid}`;
|
|
24680
|
+
const previousBase = db.baseByHead[headKey]?.baseRef;
|
|
24681
|
+
if (previousBase && previousBase !== resolvedBase && !previousBase.startsWith("refs/")) {
|
|
24682
|
+
const previousId = sha256Tuple2(["branch-review-v2", headKey, previousBase]);
|
|
24683
|
+
const previous = db.reviews[previousId];
|
|
24684
|
+
if (previous !== undefined) {
|
|
24685
|
+
const canonical = await this.runner.run(["rev-parse", "--symbolic-full-name", "--verify", "--end-of-options", previousBase], { readOnly: true, acceptedExitCodes: [0, 1, 128] });
|
|
24686
|
+
if (canonical.stdout.trim() === resolvedBase)
|
|
24687
|
+
persisted = previous;
|
|
24688
|
+
}
|
|
24689
|
+
}
|
|
24690
|
+
}
|
|
23859
24691
|
if (persisted !== undefined) {
|
|
23860
24692
|
const initial = createInitialReviewState(doc);
|
|
23861
24693
|
const reconstructed = {
|
|
@@ -23900,7 +24732,7 @@ class ReviewWorkspaceController {
|
|
|
23900
24732
|
}
|
|
23901
24733
|
dispatch(action) {
|
|
23902
24734
|
const current = this._state;
|
|
23903
|
-
if (current === undefined)
|
|
24735
|
+
if (current === undefined || this._baseSelection !== undefined)
|
|
23904
24736
|
return;
|
|
23905
24737
|
const next = reduceReviewState(current, action);
|
|
23906
24738
|
if (next === current)
|
|
@@ -23918,7 +24750,7 @@ class ReviewWorkspaceController {
|
|
|
23918
24750
|
}
|
|
23919
24751
|
dispatchIntent(intent) {
|
|
23920
24752
|
const current = this._state;
|
|
23921
|
-
if (current === undefined)
|
|
24753
|
+
if (current === undefined || this._baseSelection !== undefined)
|
|
23922
24754
|
return false;
|
|
23923
24755
|
try {
|
|
23924
24756
|
const action = planReviewIntent(current, intent);
|
|
@@ -23928,6 +24760,50 @@ class ReviewWorkspaceController {
|
|
|
23928
24760
|
return false;
|
|
23929
24761
|
}
|
|
23930
24762
|
}
|
|
24763
|
+
async enterSinceLastReview() {
|
|
24764
|
+
const current = this._state;
|
|
24765
|
+
if (current === undefined || this._baseSelection !== undefined)
|
|
24766
|
+
return { ok: false, reason: "unavailable" };
|
|
24767
|
+
if (current.projection.kind !== "aggregate")
|
|
24768
|
+
return { ok: false, reason: "already-projected" };
|
|
24769
|
+
const fromHeadOid = current.lastSubmission?.headOid;
|
|
24770
|
+
if (fromHeadOid === undefined || fromHeadOid.trim() === "")
|
|
24771
|
+
return { ok: false, reason: "no-previous-review" };
|
|
24772
|
+
const aggregate = current.document;
|
|
24773
|
+
let result;
|
|
24774
|
+
try {
|
|
24775
|
+
result = await this.loadSinceLastReviewImpl(aggregate, fromHeadOid);
|
|
24776
|
+
} catch (err) {
|
|
24777
|
+
return { ok: false, reason: "load-failed", message: err instanceof Error ? err.message : String(err) };
|
|
24778
|
+
}
|
|
24779
|
+
if (result.kind === "history-rewritten")
|
|
24780
|
+
return { ok: false, reason: "history-rewritten", message: result.reason };
|
|
24781
|
+
const latest = this._state;
|
|
24782
|
+
if (latest === undefined || latest.document !== aggregate || latest.projection.kind !== "aggregate") {
|
|
24783
|
+
return { ok: false, reason: "stale" };
|
|
24784
|
+
}
|
|
24785
|
+
this.aggregateDocument = aggregate;
|
|
24786
|
+
this.dispatch({
|
|
24787
|
+
type: "projection/apply",
|
|
24788
|
+
projection: result.document.projection,
|
|
24789
|
+
document: createReviewDocument({
|
|
24790
|
+
identity: aggregate.identity,
|
|
24791
|
+
generation: aggregate.generation,
|
|
24792
|
+
commits: aggregate.commits,
|
|
24793
|
+
files: result.document.files
|
|
24794
|
+
})
|
|
24795
|
+
});
|
|
24796
|
+
return { ok: true, fileCount: result.document.files.length };
|
|
24797
|
+
}
|
|
24798
|
+
exitProjection() {
|
|
24799
|
+
const current = this._state;
|
|
24800
|
+
const aggregate = this.aggregateDocument;
|
|
24801
|
+
if (current === undefined || aggregate === undefined || current.projection.kind === "aggregate")
|
|
24802
|
+
return false;
|
|
24803
|
+
this.aggregateDocument = undefined;
|
|
24804
|
+
this.dispatch({ type: "projection/apply", projection: { kind: "aggregate" }, document: aggregate });
|
|
24805
|
+
return true;
|
|
24806
|
+
}
|
|
23931
24807
|
async flushDrafts() {
|
|
23932
24808
|
if (!this.stateStore)
|
|
23933
24809
|
return;
|
|
@@ -24176,6 +25052,8 @@ class ReviewWorkspaceController {
|
|
|
24176
25052
|
this.destroyed = true;
|
|
24177
25053
|
this.listeners.clear();
|
|
24178
25054
|
this.requestId++;
|
|
25055
|
+
this.baseSelectionRequestId++;
|
|
25056
|
+
this._baseSelection = undefined;
|
|
24179
25057
|
this.pendingGapRequests.clear();
|
|
24180
25058
|
this.sourceContextCache.clear();
|
|
24181
25059
|
if (this.activeReviewId && this.stateStore) {
|
|
@@ -24208,7 +25086,7 @@ class ReviewWorkspaceController {
|
|
|
24208
25086
|
const submissionInProgress = db.reviews[reviewId]?.submissionInProgress ?? null;
|
|
24209
25087
|
return {
|
|
24210
25088
|
...db,
|
|
24211
|
-
baseByHead: { ...db.baseByHead, [headKey]: { baseRef: snapshot.document.identity.baseRef } },
|
|
25089
|
+
baseByHead: { ...db.baseByHead, [headKey]: { baseRef: snapshot.document.identity.baseRef, confirmed: true } },
|
|
24212
25090
|
reviews: { ...db.reviews, [reviewId]: { ...persisted, submissionInProgress } }
|
|
24213
25091
|
};
|
|
24214
25092
|
});
|
|
@@ -24221,37 +25099,26 @@ class ReviewWorkspaceController {
|
|
|
24221
25099
|
throw err;
|
|
24222
25100
|
}
|
|
24223
25101
|
}
|
|
24224
|
-
async
|
|
25102
|
+
async rememberedBase() {
|
|
24225
25103
|
const headRef = await currentBranchRef(this.runner);
|
|
24226
25104
|
const detachedOid = headRef === undefined ? await resolveRefOid(this.runner, "HEAD") : undefined;
|
|
24227
25105
|
const headKey = headRef ?? (detachedOid ? `detached:${detachedOid}` : undefined);
|
|
24228
|
-
if (headKey
|
|
24229
|
-
|
|
24230
|
-
const db = await this.stateStore.load();
|
|
24231
|
-
const warning = this.stateStore.quarantineWarning;
|
|
24232
|
-
if (warning) {
|
|
24233
|
-
const pathMatch = warning.match(/moved to (\S+)/);
|
|
24234
|
-
const qPath = pathMatch?.[1] ?? warning;
|
|
24235
|
-
this._error = createCorruptStateError(qPath, warning);
|
|
24236
|
-
this.publish();
|
|
24237
|
-
}
|
|
24238
|
-
const remembered = db.baseByHead[headKey]?.baseRef;
|
|
24239
|
-
if (remembered && await resolveRefOid(this.runner, remembered) !== undefined) {
|
|
24240
|
-
return remembered;
|
|
24241
|
-
}
|
|
24242
|
-
} catch {}
|
|
24243
|
-
}
|
|
25106
|
+
if (headKey === undefined || this.stateStore === undefined)
|
|
25107
|
+
return;
|
|
24244
25108
|
try {
|
|
24245
|
-
const
|
|
24246
|
-
|
|
24247
|
-
|
|
24248
|
-
|
|
24249
|
-
|
|
24250
|
-
|
|
25109
|
+
const db = await this.stateStore.load();
|
|
25110
|
+
const warning = this.stateStore.quarantineWarning;
|
|
25111
|
+
if (warning) {
|
|
25112
|
+
const qPath = warning.match(/moved to (\S+)/)?.[1] ?? warning;
|
|
25113
|
+
this._error = createCorruptStateError(qPath, warning);
|
|
25114
|
+
this.publish();
|
|
25115
|
+
}
|
|
25116
|
+
return db.baseByHead[headKey];
|
|
24251
25117
|
} catch (err) {
|
|
24252
|
-
|
|
25118
|
+
this._error = createStorageError(err instanceof Error ? err.message : String(err));
|
|
25119
|
+
this.publish();
|
|
25120
|
+
return;
|
|
24253
25121
|
}
|
|
24254
|
-
return "refs/heads/main";
|
|
24255
25122
|
}
|
|
24256
25123
|
}
|
|
24257
25124
|
|