@kitlangton/ghui 0.1.21 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -1
- package/src/App.tsx +566 -723
- package/src/appCommands.ts +54 -12
- package/src/domain.ts +2 -0
- package/src/index.tsx +2 -2
- package/src/keyboard/createKeymap.ts +25 -0
- package/src/keyboard/useAppCommandRegistry.ts +30 -0
- package/src/keyboard/useScopedBindings.ts +66 -0
- package/src/services/GitHubService.ts +1 -0
- package/src/ui/CommandPalette.tsx +15 -5
- package/src/ui/DetailsPane.tsx +48 -22
- package/src/ui/FooterHints.tsx +9 -18
- package/src/ui/PullRequestDiffPane.tsx +15 -10
- package/src/ui/PullRequestList.tsx +6 -2
- package/src/ui/diff.ts +6 -0
package/src/App.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DiffRenderable, PasteEvent, ScrollBoxRenderable } from "@opentui/core"
|
|
2
2
|
import { RegistryContext, useAtom, useAtomRefresh, useAtomSet, useAtomValue } from "@effect/atom-react"
|
|
3
|
-
import {
|
|
3
|
+
import { useAppCommandRegistry } from "./keyboard/useAppCommandRegistry.js"
|
|
4
|
+
import { scrollBindings, useScopedBindings, type ScopedBindingAction } from "./keyboard/useScopedBindings.js"
|
|
4
5
|
import { useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/react"
|
|
5
6
|
import { Cause, Effect, Layer, Schedule } from "effect"
|
|
6
7
|
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
|
|
@@ -25,8 +26,8 @@ import { GitHubService } from "./services/GitHubService.js"
|
|
|
25
26
|
import { loadStoredThemeId, saveStoredThemeId } from "./themeStore.js"
|
|
26
27
|
import { colors, filterThemeDefinitions, mixHex, setActiveTheme, themeDefinitions, type ThemeId } from "./ui/colors.js"
|
|
27
28
|
import { backspace as editorBackspace, deleteForward as editorDeleteForward, deleteToLineEnd, deleteToLineStart, deleteWordBackward, deleteWordForward, insertText, moveLeft as editorMoveLeft, moveLineEnd, moveLineStart, moveRight as editorMoveRight, moveVertically, moveWordBackward, moveWordForward, type CommentEditorValue } from "./ui/commentEditor.js"
|
|
28
|
-
import { buildStackedDiffFiles, diffCommentLocationKey,
|
|
29
|
-
import { DETAIL_BODY_SCROLL_LIMIT, DetailBody, DetailHeader, DetailPlaceholder, DetailsPane, getDetailHeaderHeight, getDetailJunctionRows,
|
|
29
|
+
import { buildStackedDiffFiles, diffCommentAnchorLabel, diffCommentLineLabel, diffCommentLocationKey, diffCommentSideLabel, getStackedDiffCommentAnchors, PullRequestDiffState, pullRequestDiffKey, safeDiffFileIndex, scrollTopForVisibleLine, splitPatchFiles, stackedDiffFileAtLine, type DiffCommentAnchor, type DiffCommentKind, type DiffView, type DiffWrapMode, type StackedDiffCommentAnchor } from "./ui/diff.js"
|
|
30
|
+
import { DETAIL_BODY_SCROLL_LIMIT, DetailBody, DetailHeader, DetailPlaceholder, DetailsPane, getDetailHeaderHeight, getDetailJunctionRows, getScrollableDetailBodyHeight, LoadingPane, type DetailPlaceholderContent } from "./ui/DetailsPane.js"
|
|
30
31
|
import { FooterHints, initialRetryProgress, RetryProgress } from "./ui/FooterHints.js"
|
|
31
32
|
import { Divider, fitCell, PlainLine, SeparatorColumn } from "./ui/primitives.js"
|
|
32
33
|
import { CommandPalette } from "./ui/CommandPalette.js"
|
|
@@ -96,6 +97,11 @@ interface AppliedDiffLineColorState {
|
|
|
96
97
|
readonly entries: readonly AppliedDiffLineColor[]
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
interface DiffCommentRangeSelection {
|
|
101
|
+
readonly start: StackedDiffCommentAnchor
|
|
102
|
+
readonly end: StackedDiffCommentAnchor
|
|
103
|
+
}
|
|
104
|
+
|
|
99
105
|
interface DetailHydration {
|
|
100
106
|
readonly token: symbol
|
|
101
107
|
notifyError: boolean
|
|
@@ -114,6 +120,23 @@ const DETAIL_PREFETCH_BEHIND = 1
|
|
|
114
120
|
const DETAIL_PREFETCH_AHEAD = 3
|
|
115
121
|
const DETAIL_PREFETCH_CONCURRENCY = 3
|
|
116
122
|
const DETAIL_PREFETCH_DELAY_MS = 120
|
|
123
|
+
const MAX_KEYMAP_COUNT_PREFIX = 99
|
|
124
|
+
|
|
125
|
+
const countSequence = (count: number, key: string) => `${String(count).split("").join(" ")} ${key}`
|
|
126
|
+
|
|
127
|
+
const countedVerticalBindings = (
|
|
128
|
+
moveUp: (count: number) => void,
|
|
129
|
+
moveDown: (count: number) => void,
|
|
130
|
+
): Record<string, ScopedBindingAction> => {
|
|
131
|
+
const bindings: Record<string, ScopedBindingAction> = {}
|
|
132
|
+
for (let count = 1; count <= MAX_KEYMAP_COUNT_PREFIX; count++) {
|
|
133
|
+
bindings[countSequence(count, "k")] = () => moveUp(count)
|
|
134
|
+
bindings[countSequence(count, "up")] = () => moveUp(count)
|
|
135
|
+
bindings[countSequence(count, "j")] = () => moveDown(count)
|
|
136
|
+
bindings[countSequence(count, "down")] = () => moveDown(count)
|
|
137
|
+
}
|
|
138
|
+
return bindings
|
|
139
|
+
}
|
|
117
140
|
|
|
118
141
|
const appendPullRequestPage = (existing: readonly PullRequestItem[], incoming: readonly PullRequestItem[]) => {
|
|
119
142
|
const seen = new Set(existing.map((pullRequest) => pullRequest.url))
|
|
@@ -179,7 +202,6 @@ const noticeAtom = Atom.make<string | null>(null)
|
|
|
179
202
|
const filterQueryAtom = Atom.make("")
|
|
180
203
|
const filterDraftAtom = Atom.make("")
|
|
181
204
|
const filterModeAtom = Atom.make(false)
|
|
182
|
-
const pendingGAtom = Atom.make(false)
|
|
183
205
|
const detailFullViewAtom = Atom.make(false)
|
|
184
206
|
const detailScrollOffsetAtom = Atom.make(0)
|
|
185
207
|
const diffFullViewAtom = Atom.make(false)
|
|
@@ -187,8 +209,8 @@ const diffFileIndexAtom = Atom.make(0)
|
|
|
187
209
|
const diffScrollTopAtom = Atom.make(0)
|
|
188
210
|
const diffRenderViewAtom = Atom.make<DiffView>("split")
|
|
189
211
|
const diffWrapModeAtom = Atom.make<DiffWrapMode>("none")
|
|
190
|
-
const diffCommentModeAtom = Atom.make(false)
|
|
191
212
|
const diffCommentAnchorIndexAtom = Atom.make(0)
|
|
213
|
+
const diffCommentRangeStartIndexAtom = Atom.make<number | null>(null)
|
|
192
214
|
const diffCommentThreadsAtom = Atom.make<Record<string, readonly PullRequestReviewComment[]>>({}).pipe(Atom.keepAlive)
|
|
193
215
|
const diffCommentsLoadedAtom = Atom.make<Record<string, "loading" | "ready">>({}).pipe(Atom.keepAlive)
|
|
194
216
|
const pullRequestDiffCacheAtom = Atom.make<Record<string, PullRequestDiffState>>({}).pipe(Atom.keepAlive)
|
|
@@ -381,12 +403,13 @@ const pullRequestDiffAtomKey = pullRequestRevisionAtomKey
|
|
|
381
403
|
const parsePullRequestDetailAtomKey = (key: string) => parsePullRequestRevisionAtomKey(key, "detail")
|
|
382
404
|
const parsePullRequestDiffAtomKey = (key: string) => parsePullRequestRevisionAtomKey(key, "diff")
|
|
383
405
|
|
|
384
|
-
const isShiftG = (key: { readonly name: string; readonly shift?: boolean }) => key.name === "G" || key.name === "g" && key.shift
|
|
385
406
|
|
|
386
|
-
|
|
407
|
+
|
|
408
|
+
const diffCommentThreadMapKey = (diffKey: string, location: Pick<PullRequestReviewComment, "path" | "side" | "line">) =>
|
|
409
|
+
`${diffKey}:${diffCommentLocationKey(location)}`
|
|
387
410
|
|
|
388
411
|
const diffCommentThreadKey = (pullRequest: PullRequestItem, comment: Pick<PullRequestReviewComment, "path" | "side" | "line">) =>
|
|
389
|
-
|
|
412
|
+
diffCommentThreadMapKey(pullRequestDiffKey(pullRequest), comment)
|
|
390
413
|
|
|
391
414
|
const groupDiffCommentThreads = (pullRequest: PullRequestItem, comments: readonly PullRequestReviewComment[]) => {
|
|
392
415
|
const threads: Record<string, PullRequestReviewComment[]> = {}
|
|
@@ -411,13 +434,43 @@ const originalDiffLineColor = (anchor: DiffCommentAnchor): DiffLineColorConfig =
|
|
|
411
434
|
return { gutter: colors.diff.lineNumberBg, content: colors.diff.contextBg }
|
|
412
435
|
}
|
|
413
436
|
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
437
|
+
const selectedDiffCommentAccentByKind = {
|
|
438
|
+
addition: () => colors.status.passing,
|
|
439
|
+
deletion: () => colors.status.failing,
|
|
440
|
+
context: () => colors.muted,
|
|
441
|
+
} satisfies Record<DiffCommentKind, () => string>
|
|
442
|
+
|
|
443
|
+
const selectedDiffCommentAccent = (kind: DiffCommentKind) => selectedDiffCommentAccentByKind[kind]()
|
|
444
|
+
|
|
445
|
+
const mixDiffLineContentColor = (base: string, accent: string, amount: number) =>
|
|
446
|
+
mixHex(base === "transparent" ? colors.background : base, accent, amount)
|
|
447
|
+
|
|
448
|
+
const diffCommentLineColor = (anchor: DiffCommentAnchor, kind: "selected" | "range" | "thread"): DiffLineColorConfig => {
|
|
449
|
+
const original = originalDiffLineColor(anchor)
|
|
450
|
+
const accent = kind === "thread" ? colors.status.pending : selectedDiffCommentAccent(anchor.kind)
|
|
451
|
+
if (kind === "thread") return { ...original, gutter: mixHex(original.gutter, accent, 0.45) }
|
|
452
|
+
return {
|
|
453
|
+
gutter: mixHex(original.gutter, accent, kind === "selected" ? 0.68 : 0.42),
|
|
454
|
+
content: mixDiffLineContentColor(original.content, accent, kind === "selected" ? 0.2 : 0.1),
|
|
455
|
+
}
|
|
419
456
|
}
|
|
420
457
|
|
|
458
|
+
const sameDiffCommentTarget = (left: DiffCommentAnchor, right: DiffCommentAnchor) =>
|
|
459
|
+
left.path === right.path && left.side === right.side
|
|
460
|
+
|
|
461
|
+
const diffCommentRangeSelection = (start: StackedDiffCommentAnchor | null, end: StackedDiffCommentAnchor | null): DiffCommentRangeSelection | null => {
|
|
462
|
+
if (!start || !end || !sameDiffCommentTarget(start, end)) return null
|
|
463
|
+
return start.line <= end.line ? { start, end } : { start: end, end: start }
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const diffCommentRangeContains = (range: DiffCommentRangeSelection, anchor: StackedDiffCommentAnchor) =>
|
|
467
|
+
sameDiffCommentTarget(range.start, anchor) && anchor.line >= range.start.line && anchor.line <= range.end.line
|
|
468
|
+
|
|
469
|
+
const diffCommentRangeLabel = (range: DiffCommentRangeSelection) =>
|
|
470
|
+
range.start.line === range.end.line
|
|
471
|
+
? diffCommentAnchorLabel(range.end)
|
|
472
|
+
: `${diffCommentSideLabel(range.end)} ${diffCommentLineLabel(range.start)}-${diffCommentLineLabel(range.end)}`
|
|
473
|
+
|
|
421
474
|
const diffSideTargets = (diff: DiffRenderable, anchor: DiffCommentAnchor, view: DiffView) => {
|
|
422
475
|
const withSides = diff as unknown as DiffRenderableRuntimeSides
|
|
423
476
|
if (view === "split") {
|
|
@@ -488,7 +541,6 @@ export const App = () => {
|
|
|
488
541
|
const [filterQuery, setFilterQuery] = useAtom(filterQueryAtom)
|
|
489
542
|
const [filterDraft, setFilterDraft] = useAtom(filterDraftAtom)
|
|
490
543
|
const [filterMode, setFilterMode] = useAtom(filterModeAtom)
|
|
491
|
-
const [pendingG, setPendingG] = useAtom(pendingGAtom)
|
|
492
544
|
const [detailFullView, setDetailFullView] = useAtom(detailFullViewAtom)
|
|
493
545
|
const setDetailScrollOffset = useAtomSet(detailScrollOffsetAtom)
|
|
494
546
|
const [diffFullView, setDiffFullView] = useAtom(diffFullViewAtom)
|
|
@@ -496,8 +548,8 @@ export const App = () => {
|
|
|
496
548
|
const [diffScrollTop, setDiffScrollTop] = useAtom(diffScrollTopAtom)
|
|
497
549
|
const [diffRenderView, setDiffRenderView] = useAtom(diffRenderViewAtom)
|
|
498
550
|
const [diffWrapMode, setDiffWrapMode] = useAtom(diffWrapModeAtom)
|
|
499
|
-
const [diffCommentMode, setDiffCommentMode] = useAtom(diffCommentModeAtom)
|
|
500
551
|
const [diffCommentAnchorIndex, setDiffCommentAnchorIndex] = useAtom(diffCommentAnchorIndexAtom)
|
|
552
|
+
const [diffCommentRangeStartIndex, setDiffCommentRangeStartIndex] = useAtom(diffCommentRangeStartIndexAtom)
|
|
501
553
|
const [diffCommentThreads, setDiffCommentThreads] = useAtom(diffCommentThreadsAtom)
|
|
502
554
|
const setDiffCommentsLoaded = useAtomSet(diffCommentsLoadedAtom)
|
|
503
555
|
const setPullRequestDiffCache = useAtomSet(pullRequestDiffCacheAtom)
|
|
@@ -579,7 +631,6 @@ export const App = () => {
|
|
|
579
631
|
const wideDetailLines = Math.max(8, terminalHeight - 8)
|
|
580
632
|
const wideBodyHeight = Math.max(8, terminalHeight - 4)
|
|
581
633
|
const noticeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
582
|
-
const pendingGTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
583
634
|
const diffPrefetchTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
584
635
|
const detailPrefetchTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
585
636
|
const detailHydrationRef = useRef(new Map<string, DetailHydration>())
|
|
@@ -620,9 +671,6 @@ export const App = () => {
|
|
|
620
671
|
if (noticeTimeoutRef.current !== null) {
|
|
621
672
|
clearTimeout(noticeTimeoutRef.current)
|
|
622
673
|
}
|
|
623
|
-
if (pendingGTimeoutRef.current !== null) {
|
|
624
|
-
clearTimeout(pendingGTimeoutRef.current)
|
|
625
|
-
}
|
|
626
674
|
if (diffPrefetchTimeoutRef.current !== null) {
|
|
627
675
|
clearTimeout(diffPrefetchTimeoutRef.current)
|
|
628
676
|
}
|
|
@@ -670,14 +718,39 @@ export const App = () => {
|
|
|
670
718
|
() => diffFullView ? getStackedDiffCommentAnchors(stackedDiffFiles, effectiveDiffRenderView, diffWrapMode, contentWidth) : [],
|
|
671
719
|
[diffFullView, stackedDiffFiles, effectiveDiffRenderView, diffWrapMode, contentWidth],
|
|
672
720
|
)
|
|
673
|
-
const
|
|
674
|
-
const
|
|
721
|
+
const selectedDiffCommentAnchorIndex = Math.max(0, Math.min(diffCommentAnchorIndex, diffCommentAnchors.length - 1))
|
|
722
|
+
const selectedDiffCommentAnchor = diffCommentAnchors[selectedDiffCommentAnchorIndex] ?? null
|
|
723
|
+
const diffCommentRangeStartAnchor = diffCommentRangeStartIndex === null
|
|
724
|
+
? null
|
|
725
|
+
: diffCommentAnchors[Math.max(0, Math.min(diffCommentRangeStartIndex, diffCommentAnchors.length - 1))] ?? null
|
|
726
|
+
const selectedDiffCommentRange = useMemo(
|
|
727
|
+
() => diffCommentRangeSelection(diffCommentRangeStartAnchor, selectedDiffCommentAnchor),
|
|
728
|
+
[diffCommentRangeStartAnchor, selectedDiffCommentAnchor],
|
|
729
|
+
)
|
|
730
|
+
const selectedDiffCommentRangeAnchors = useMemo(
|
|
731
|
+
() => selectedDiffCommentRange
|
|
732
|
+
? diffCommentAnchors.filter((anchor) => diffCommentRangeContains(selectedDiffCommentRange, anchor))
|
|
733
|
+
: [],
|
|
734
|
+
[diffCommentAnchors, selectedDiffCommentRange],
|
|
735
|
+
)
|
|
736
|
+
const diffCommentRangeActive = selectedDiffCommentRange !== null
|
|
737
|
+
const selectedDiffCommentLabel = selectedDiffCommentRange
|
|
738
|
+
? diffCommentRangeLabel(selectedDiffCommentRange)
|
|
739
|
+
: selectedDiffCommentAnchor ? diffCommentAnchorLabel(selectedDiffCommentAnchor) : null
|
|
740
|
+
const selectedDiffCommentThreadKey = selectedDiffKey && selectedDiffCommentAnchor ? diffCommentThreadMapKey(selectedDiffKey, selectedDiffCommentAnchor) : null
|
|
675
741
|
const selectedDiffCommentThread = selectedDiffCommentThreadKey ? diffCommentThreads[selectedDiffCommentThreadKey] ?? [] : []
|
|
676
742
|
const diffLineColorContextKey = selectedDiffKey ? `${selectedDiffKey}:${effectiveDiffRenderView}:${diffWrapMode}` : null
|
|
677
|
-
const
|
|
678
|
-
()
|
|
679
|
-
|
|
680
|
-
|
|
743
|
+
const diffCommentThreadAnchors = useMemo(() => {
|
|
744
|
+
if (!selectedDiffKey) return [] as readonly StackedDiffCommentAnchor[]
|
|
745
|
+
const seen = new Set<string>()
|
|
746
|
+
return diffCommentAnchors.filter((anchor) => {
|
|
747
|
+
const key = diffCommentLocationKey(anchor)
|
|
748
|
+
if (seen.has(key)) return false
|
|
749
|
+
if ((diffCommentThreads[diffCommentThreadMapKey(selectedDiffKey, anchor)]?.length ?? 0) === 0) return false
|
|
750
|
+
seen.add(key)
|
|
751
|
+
return true
|
|
752
|
+
})
|
|
753
|
+
}, [diffCommentAnchors, diffCommentThreads, selectedDiffKey])
|
|
681
754
|
const groupStarts = useAtomValue(groupStartsAtom)
|
|
682
755
|
const getCurrentGroupIndex = (current: number) => {
|
|
683
756
|
if (groupStarts.length === 0) return 0
|
|
@@ -736,7 +809,7 @@ export const App = () => {
|
|
|
736
809
|
setLoadingMoreKey(null)
|
|
737
810
|
setDetailFullView(false)
|
|
738
811
|
setDiffFullView(false)
|
|
739
|
-
|
|
812
|
+
setDiffCommentRangeStartIndex(null)
|
|
740
813
|
setFilterDraft(filterQuery)
|
|
741
814
|
setNotice(null)
|
|
742
815
|
setRefreshCompletionMessage(null)
|
|
@@ -933,6 +1006,7 @@ export const App = () => {
|
|
|
933
1006
|
setDiffFileIndex(0)
|
|
934
1007
|
setDiffScrollTop(0)
|
|
935
1008
|
setDiffCommentAnchorIndex(0)
|
|
1009
|
+
setDiffCommentRangeStartIndex(null)
|
|
936
1010
|
detailPreviewScrollRef.current?.scrollTo({ x: 0, y: 0 })
|
|
937
1011
|
}, [selectedIndex])
|
|
938
1012
|
|
|
@@ -941,12 +1015,16 @@ export const App = () => {
|
|
|
941
1015
|
if (diffCommentAnchors.length === 0) return 0
|
|
942
1016
|
return Math.max(0, Math.min(current, diffCommentAnchors.length - 1))
|
|
943
1017
|
})
|
|
1018
|
+
setDiffCommentRangeStartIndex((current) => {
|
|
1019
|
+
if (current === null || diffCommentAnchors.length === 0) return null
|
|
1020
|
+
return Math.max(0, Math.min(current, diffCommentAnchors.length - 1))
|
|
1021
|
+
})
|
|
944
1022
|
}, [diffCommentAnchors.length])
|
|
945
1023
|
|
|
946
1024
|
useEffect(() => {
|
|
947
|
-
if (!
|
|
1025
|
+
if (!diffFullView || !selectedDiffCommentAnchor) return
|
|
948
1026
|
setDiffFileIndex((current) => current === selectedDiffCommentAnchor.fileIndex ? current : selectedDiffCommentAnchor.fileIndex)
|
|
949
|
-
}, [
|
|
1027
|
+
}, [diffFullView, selectedDiffCommentAnchor?.fileIndex])
|
|
950
1028
|
|
|
951
1029
|
useEffect(() => {
|
|
952
1030
|
const previous = diffCommentLineColorsRef.current
|
|
@@ -959,27 +1037,28 @@ export const App = () => {
|
|
|
959
1037
|
|
|
960
1038
|
const nextEntries: AppliedDiffLineColor[] = []
|
|
961
1039
|
const appliedKeys = new Set<string>()
|
|
962
|
-
const applyLineColor = (anchor: StackedDiffCommentAnchor,
|
|
1040
|
+
const applyLineColor = (anchor: StackedDiffCommentAnchor, color: DiffLineColorConfig, override = false) => {
|
|
963
1041
|
const key = `${effectiveDiffRenderView}:${anchor.side}:${anchor.renderLine}`
|
|
964
1042
|
if (appliedKeys.has(key) && !override) return
|
|
965
1043
|
appliedKeys.add(key)
|
|
966
1044
|
const entry = { anchor, view: effectiveDiffRenderView } satisfies AppliedDiffLineColor
|
|
967
1045
|
const diff = diffRenderableRefs.current.get(anchor.fileIndex)
|
|
968
|
-
if (diff) setDiffCommentLineColor(diff, entry,
|
|
1046
|
+
if (diff) setDiffCommentLineColor(diff, entry, color)
|
|
969
1047
|
if (!nextEntries.some((existing) => existing.view === entry.view && existing.anchor.side === anchor.side && existing.anchor.renderLine === anchor.renderLine)) {
|
|
970
1048
|
nextEntries.push(entry)
|
|
971
1049
|
}
|
|
972
1050
|
}
|
|
973
1051
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1052
|
+
for (const anchor of diffCommentThreadAnchors) {
|
|
1053
|
+
applyLineColor(anchor, diffCommentLineColor(anchor, "thread"))
|
|
1054
|
+
}
|
|
1055
|
+
if (selectedDiffCommentRangeAnchors.length > 0) {
|
|
1056
|
+
for (const anchor of selectedDiffCommentRangeAnchors) {
|
|
1057
|
+
applyLineColor(anchor, diffCommentLineColor(anchor, "range"), true)
|
|
979
1058
|
}
|
|
980
1059
|
}
|
|
981
|
-
if (
|
|
982
|
-
applyLineColor(selectedDiffCommentAnchor,
|
|
1060
|
+
if (selectedDiffCommentAnchor) {
|
|
1061
|
+
applyLineColor(selectedDiffCommentAnchor, diffCommentLineColor(selectedDiffCommentAnchor, "selected"), true)
|
|
983
1062
|
if (suppressNextDiffCommentScrollRef.current) {
|
|
984
1063
|
suppressNextDiffCommentScrollRef.current = false
|
|
985
1064
|
} else {
|
|
@@ -989,10 +1068,10 @@ export const App = () => {
|
|
|
989
1068
|
suppressNextDiffCommentScrollRef.current = false
|
|
990
1069
|
}
|
|
991
1070
|
diffCommentLineColorsRef.current = { contextKey: diffLineColorContextKey, entries: nextEntries }
|
|
992
|
-
}, [
|
|
1071
|
+
}, [selectedDiffCommentAnchor?.renderLine, selectedDiffCommentAnchor?.localRenderLine, selectedDiffCommentAnchor?.side, selectedDiffCommentAnchor?.fileIndex, selectedDiffCommentRangeAnchors, diffLineColorContextKey, effectiveDiffRenderView, diffCommentThreadAnchors])
|
|
993
1072
|
const isHydratingPullRequestDetails = pullRequestStatus === "ready" && selectedPullRequest?.state === "open" && !selectedPullRequest.detailLoaded
|
|
994
1073
|
const isRefreshingPullRequests = pullRequestResult.waiting && pullRequestLoad !== null
|
|
995
|
-
const hasActiveLoadingIndicator = pullRequestResult.waiting || isHydratingPullRequestDetails || labelModal.loading || closeModal.running || mergeModal.loading || mergeModal.running || selectedDiffState?._tag === "Loading"
|
|
1074
|
+
const hasActiveLoadingIndicator = pullRequestResult.waiting || isHydratingPullRequestDetails || isLoadingMorePullRequests || labelModal.loading || closeModal.running || mergeModal.loading || mergeModal.running || selectedDiffState?._tag === "Loading"
|
|
996
1075
|
const loadingIndicator = LOADING_FRAMES[loadingFrame % LOADING_FRAMES.length]!
|
|
997
1076
|
|
|
998
1077
|
useEffect(() => {
|
|
@@ -1135,9 +1214,10 @@ export const App = () => {
|
|
|
1135
1214
|
diffCommentLineColorsRef.current = { contextKey: null, entries: [] }
|
|
1136
1215
|
setDiffFullView(true)
|
|
1137
1216
|
setDetailFullView(false)
|
|
1138
|
-
setDiffCommentMode(false)
|
|
1139
1217
|
setDiffFileIndex(0)
|
|
1140
1218
|
setDiffScrollTop(0)
|
|
1219
|
+
setDiffCommentAnchorIndex(0)
|
|
1220
|
+
setDiffCommentRangeStartIndex(null)
|
|
1141
1221
|
setDiffRenderView(contentWidth >= 100 ? "split" : "unified")
|
|
1142
1222
|
diffScrollRef.current?.scrollTo({ x: 0, y: 0 })
|
|
1143
1223
|
loadPullRequestDiff(selectedPullRequest, { includeComments: true })
|
|
@@ -1162,49 +1242,9 @@ export const App = () => {
|
|
|
1162
1242
|
setDiffFileIndex((current) => current === nextIndex ? current : nextIndex)
|
|
1163
1243
|
}
|
|
1164
1244
|
|
|
1165
|
-
const scrollDiffBy = (y: number) => {
|
|
1166
|
-
diffScrollRef.current?.scrollBy({ x: 0, y })
|
|
1167
|
-
syncDiffScrollState()
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
const scrollDiffTo = (y: number) => {
|
|
1171
|
-
diffScrollRef.current?.scrollTo({ x: 0, y })
|
|
1172
|
-
syncDiffScrollState()
|
|
1173
|
-
}
|
|
1174
1245
|
const scrollDetailPreviewBy = (y: number) => detailPreviewScrollRef.current?.scrollBy({ x: 0, y })
|
|
1175
1246
|
const scrollDetailPreviewTo = (y: number) => detailPreviewScrollRef.current?.scrollTo({ x: 0, y })
|
|
1176
1247
|
|
|
1177
|
-
const clearPendingGTimeout = () => {
|
|
1178
|
-
if (pendingGTimeoutRef.current !== null) {
|
|
1179
|
-
clearTimeout(pendingGTimeoutRef.current)
|
|
1180
|
-
pendingGTimeoutRef.current = null
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
const handleVimGoto = (key: { readonly name: string; readonly shift?: boolean }, gotoStart: () => void, gotoEnd: () => void): boolean => {
|
|
1185
|
-
if (isShiftG(key)) {
|
|
1186
|
-
gotoEnd()
|
|
1187
|
-
setPendingG(false)
|
|
1188
|
-
clearPendingGTimeout()
|
|
1189
|
-
return true
|
|
1190
|
-
}
|
|
1191
|
-
if (key.name === "g") {
|
|
1192
|
-
if (pendingG) {
|
|
1193
|
-
gotoStart()
|
|
1194
|
-
setPendingG(false)
|
|
1195
|
-
clearPendingGTimeout()
|
|
1196
|
-
} else {
|
|
1197
|
-
setPendingG(true)
|
|
1198
|
-
pendingGTimeoutRef.current = setTimeout(() => {
|
|
1199
|
-
setPendingG(false)
|
|
1200
|
-
pendingGTimeoutRef.current = null
|
|
1201
|
-
}, 500)
|
|
1202
|
-
}
|
|
1203
|
-
return true
|
|
1204
|
-
}
|
|
1205
|
-
return false
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
1248
|
const ensureDiffLineVisible = (line: number) => {
|
|
1209
1249
|
const scroll = diffScrollRef.current
|
|
1210
1250
|
if (!scroll) return
|
|
@@ -1226,37 +1266,72 @@ export const App = () => {
|
|
|
1226
1266
|
if (readyDiffFiles.length === 0) return
|
|
1227
1267
|
const nextIndex = safeDiffFileIndex(readyDiffFiles, diffFileIndex + delta)
|
|
1228
1268
|
setDiffFileIndex(nextIndex)
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
}
|
|
1269
|
+
setDiffCommentRangeStartIndex(null)
|
|
1270
|
+
const nextAnchor = diffCommentAnchors.find((anchor) => anchor.fileIndex === nextIndex && anchor.side === selectedDiffCommentAnchor?.side)
|
|
1271
|
+
?? diffCommentAnchors.find((anchor) => anchor.fileIndex === nextIndex)
|
|
1272
|
+
if (nextAnchor) setDiffCommentAnchorIndex(diffCommentAnchors.indexOf(nextAnchor))
|
|
1234
1273
|
scrollToDiffFile(nextIndex)
|
|
1235
1274
|
}
|
|
1236
1275
|
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
setDiffCommentAnchorIndex(nearestDiffCommentAnchorIndex(diffCommentAnchors, scrollTop + DIFF_STICKY_HEADER_LINES))
|
|
1241
|
-
setDiffCommentMode(true)
|
|
1242
|
-
}
|
|
1276
|
+
const navigableDiffCommentAnchors = () => diffCommentRangeStartAnchor
|
|
1277
|
+
? diffCommentAnchors.filter((anchor) => sameDiffCommentTarget(anchor, diffCommentRangeStartAnchor))
|
|
1278
|
+
: diffCommentAnchors
|
|
1243
1279
|
|
|
1244
|
-
const moveDiffCommentAnchor = (delta: number) => {
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
const
|
|
1248
|
-
const
|
|
1280
|
+
const moveDiffCommentAnchor = (delta: number, options: { readonly preserveViewportRow?: boolean } = {}) => {
|
|
1281
|
+
const anchors = navigableDiffCommentAnchors()
|
|
1282
|
+
if (anchors.length === 0) return
|
|
1283
|
+
const rows = [...new Set(anchors.map((anchor) => anchor.renderLine))].sort((left, right) => left - right)
|
|
1284
|
+
const currentAnchor = selectedDiffCommentAnchor && anchors.includes(selectedDiffCommentAnchor) ? selectedDiffCommentAnchor : anchors[0]
|
|
1285
|
+
const currentRowIndex = Math.max(0, currentAnchor ? rows.indexOf(currentAnchor.renderLine) : 0)
|
|
1286
|
+
const nextRow = rows[Math.max(0, Math.min(rows.length - 1, currentRowIndex + delta))]
|
|
1249
1287
|
if (nextRow === undefined) return
|
|
1250
|
-
const nextAnchor =
|
|
1251
|
-
??
|
|
1288
|
+
const nextAnchor = anchors.find((anchor) => anchor.renderLine === nextRow && anchor.side === currentAnchor?.side)
|
|
1289
|
+
?? anchors.find((anchor) => anchor.renderLine === nextRow)
|
|
1252
1290
|
if (!nextAnchor) return
|
|
1291
|
+
if (options.preserveViewportRow) {
|
|
1292
|
+
const scroll = diffScrollRef.current
|
|
1293
|
+
if (scroll && currentAnchor) {
|
|
1294
|
+
const maxScreenOffset = Math.max(DIFF_STICKY_HEADER_LINES, scroll.viewport.height - 2)
|
|
1295
|
+
const screenOffset = Math.max(DIFF_STICKY_HEADER_LINES, Math.min(maxScreenOffset, currentAnchor.renderLine - scroll.scrollTop))
|
|
1296
|
+
const maxScrollTop = Math.max(0, scroll.scrollHeight - scroll.viewport.height)
|
|
1297
|
+
const nextTop = Math.max(0, Math.min(maxScrollTop, nextAnchor.renderLine - screenOffset))
|
|
1298
|
+
suppressNextDiffCommentScrollRef.current = true
|
|
1299
|
+
scroll.scrollTo({ x: 0, y: nextTop })
|
|
1300
|
+
syncDiffScrollState()
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1253
1303
|
setDiffCommentAnchorIndex(diffCommentAnchors.indexOf(nextAnchor))
|
|
1254
1304
|
}
|
|
1255
1305
|
|
|
1306
|
+
const moveDiffCommentToBoundary = (boundary: "first" | "last") => {
|
|
1307
|
+
const anchors = navigableDiffCommentAnchors()
|
|
1308
|
+
const nextAnchor = boundary === "first" ? anchors[0] : anchors[anchors.length - 1]
|
|
1309
|
+
if (!nextAnchor) return
|
|
1310
|
+
setDiffCommentAnchorIndex(diffCommentAnchors.indexOf(nextAnchor))
|
|
1311
|
+
setDiffFileIndex(nextAnchor.fileIndex)
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
const alignSelectedDiffCommentAnchor = (position: "top" | "center" | "bottom") => {
|
|
1315
|
+
if (!selectedDiffCommentAnchor) return
|
|
1316
|
+
const scroll = diffScrollRef.current
|
|
1317
|
+
if (!scroll) return
|
|
1318
|
+
const viewportHeight = Math.max(1, scroll.viewport.height)
|
|
1319
|
+
const offset = position === "top"
|
|
1320
|
+
? DIFF_STICKY_HEADER_LINES
|
|
1321
|
+
: position === "center"
|
|
1322
|
+
? Math.max(DIFF_STICKY_HEADER_LINES, Math.floor(viewportHeight / 2))
|
|
1323
|
+
: Math.max(DIFF_STICKY_HEADER_LINES, viewportHeight - 2)
|
|
1324
|
+
const maxScrollTop = Math.max(0, scroll.scrollHeight - viewportHeight)
|
|
1325
|
+
const nextTop = Math.max(0, Math.min(maxScrollTop, selectedDiffCommentAnchor.renderLine - offset))
|
|
1326
|
+
scroll.scrollTo({ x: 0, y: nextTop })
|
|
1327
|
+
syncDiffScrollState()
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1256
1330
|
const selectDiffCommentSide = (side: DiffCommentSide) => {
|
|
1257
1331
|
if (!selectedDiffCommentAnchor) return
|
|
1258
1332
|
const nextAnchor = diffCommentAnchors.find((anchor) => anchor.renderLine === selectedDiffCommentAnchor.renderLine && anchor.side === side)
|
|
1259
1333
|
if (!nextAnchor) return
|
|
1334
|
+
setDiffCommentRangeStartIndex(null)
|
|
1260
1335
|
setDiffCommentAnchorIndex(diffCommentAnchors.indexOf(nextAnchor))
|
|
1261
1336
|
}
|
|
1262
1337
|
|
|
@@ -1265,9 +1340,11 @@ export const App = () => {
|
|
|
1265
1340
|
const nextAnchor = (side ? lineAnchors.find((anchor) => anchor.side === side) : undefined) ?? lineAnchors[0]
|
|
1266
1341
|
if (!nextAnchor) return
|
|
1267
1342
|
suppressNextDiffCommentScrollRef.current = true
|
|
1343
|
+
if (diffCommentRangeStartAnchor && !sameDiffCommentTarget(diffCommentRangeStartAnchor, nextAnchor)) {
|
|
1344
|
+
setDiffCommentRangeStartIndex(null)
|
|
1345
|
+
}
|
|
1268
1346
|
setDiffCommentAnchorIndex(diffCommentAnchors.indexOf(nextAnchor))
|
|
1269
1347
|
setDiffFileIndex(nextAnchor.fileIndex)
|
|
1270
|
-
setDiffCommentMode(true)
|
|
1271
1348
|
}
|
|
1272
1349
|
|
|
1273
1350
|
const editComment = (transform: (state: CommentEditorValue) => CommentEditorValue) => {
|
|
@@ -1288,6 +1365,39 @@ export const App = () => {
|
|
|
1288
1365
|
setCommentThreadModal({ scrollOffset: 0 })
|
|
1289
1366
|
}
|
|
1290
1367
|
|
|
1368
|
+
const openSelectedDiffComment = () => {
|
|
1369
|
+
if (diffCommentRangeActive) {
|
|
1370
|
+
openDiffCommentModal()
|
|
1371
|
+
return
|
|
1372
|
+
}
|
|
1373
|
+
if (selectedDiffCommentThread.length > 0) openDiffCommentThreadModal()
|
|
1374
|
+
else openDiffCommentModal()
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
const toggleDiffCommentRange = () => {
|
|
1378
|
+
if (!selectedDiffCommentAnchor) return
|
|
1379
|
+
setDiffCommentRangeStartIndex((current) => current === null ? selectedDiffCommentAnchorIndex : null)
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
const moveDiffCommentThread = (delta: 1 | -1) => {
|
|
1383
|
+
if (diffCommentThreadAnchors.length === 0) {
|
|
1384
|
+
flashNotice("No diff comments")
|
|
1385
|
+
return
|
|
1386
|
+
}
|
|
1387
|
+
const currentIndex = selectedDiffCommentAnchor
|
|
1388
|
+
? diffCommentThreadAnchors.findIndex((anchor) => diffCommentLocationKey(anchor) === diffCommentLocationKey(selectedDiffCommentAnchor))
|
|
1389
|
+
: -1
|
|
1390
|
+
const nextAnchor = currentIndex >= 0
|
|
1391
|
+
? diffCommentThreadAnchors[(currentIndex + delta + diffCommentThreadAnchors.length) % diffCommentThreadAnchors.length]
|
|
1392
|
+
: delta > 0
|
|
1393
|
+
? diffCommentThreadAnchors.find((anchor) => !selectedDiffCommentAnchor || anchor.renderLine > selectedDiffCommentAnchor.renderLine) ?? diffCommentThreadAnchors[0]
|
|
1394
|
+
: [...diffCommentThreadAnchors].reverse().find((anchor) => !selectedDiffCommentAnchor || anchor.renderLine < selectedDiffCommentAnchor.renderLine) ?? diffCommentThreadAnchors[diffCommentThreadAnchors.length - 1]
|
|
1395
|
+
if (!nextAnchor) return
|
|
1396
|
+
setDiffCommentRangeStartIndex(null)
|
|
1397
|
+
setDiffCommentAnchorIndex(diffCommentAnchors.indexOf(nextAnchor))
|
|
1398
|
+
setDiffFileIndex(nextAnchor.fileIndex)
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1291
1401
|
const submitDiffComment = () => {
|
|
1292
1402
|
if (!selectedPullRequest || !selectedDiffCommentAnchor) return
|
|
1293
1403
|
const body = commentModal.body.trim()
|
|
@@ -1296,8 +1406,9 @@ export const App = () => {
|
|
|
1296
1406
|
return
|
|
1297
1407
|
}
|
|
1298
1408
|
|
|
1299
|
-
const
|
|
1300
|
-
const target = selectedDiffCommentAnchor
|
|
1409
|
+
const targetRange = selectedDiffCommentRange
|
|
1410
|
+
const target = targetRange?.end ?? selectedDiffCommentAnchor
|
|
1411
|
+
const threadKey = selectedDiffKey ? diffCommentThreadMapKey(selectedDiffKey, target) : null
|
|
1301
1412
|
const optimisticComment = {
|
|
1302
1413
|
id: `local:${Date.now()}`,
|
|
1303
1414
|
path: target.path,
|
|
@@ -1308,6 +1419,9 @@ export const App = () => {
|
|
|
1308
1419
|
createdAt: new Date(),
|
|
1309
1420
|
url: null,
|
|
1310
1421
|
} satisfies PullRequestReviewComment
|
|
1422
|
+
const rangeInput = targetRange && targetRange.start.line !== targetRange.end.line
|
|
1423
|
+
? { startLine: targetRange.start.line, startSide: targetRange.start.side }
|
|
1424
|
+
: {}
|
|
1311
1425
|
const input = {
|
|
1312
1426
|
repository: selectedPullRequest.repository,
|
|
1313
1427
|
number: selectedPullRequest.number,
|
|
@@ -1316,6 +1430,7 @@ export const App = () => {
|
|
|
1316
1430
|
line: target.line,
|
|
1317
1431
|
side: target.side,
|
|
1318
1432
|
body,
|
|
1433
|
+
...rangeInput,
|
|
1319
1434
|
} satisfies CreatePullRequestCommentInput
|
|
1320
1435
|
|
|
1321
1436
|
if (threadKey) {
|
|
@@ -1325,6 +1440,7 @@ export const App = () => {
|
|
|
1325
1440
|
}))
|
|
1326
1441
|
}
|
|
1327
1442
|
closeActiveModal()
|
|
1443
|
+
setDiffCommentRangeStartIndex(null)
|
|
1328
1444
|
flashNotice(`Commenting on ${target.path}:${target.line}`)
|
|
1329
1445
|
void createPullRequestComment(input).then((comment) => {
|
|
1330
1446
|
if (threadKey) {
|
|
@@ -1687,8 +1803,10 @@ export const App = () => {
|
|
|
1687
1803
|
diffWrapMode,
|
|
1688
1804
|
readyDiffFileCount: readyDiffFiles.length,
|
|
1689
1805
|
diffFileIndex,
|
|
1690
|
-
|
|
1691
|
-
selectedDiffCommentAnchorLabel:
|
|
1806
|
+
diffRangeActive: diffCommentRangeActive,
|
|
1807
|
+
selectedDiffCommentAnchorLabel: selectedDiffCommentLabel,
|
|
1808
|
+
selectedDiffCommentThreadCount: selectedDiffCommentThread.length,
|
|
1809
|
+
hasDiffCommentThreads: diffCommentThreadAnchors.length > 0,
|
|
1692
1810
|
actions: {
|
|
1693
1811
|
openCommandPalette,
|
|
1694
1812
|
refreshPullRequests,
|
|
@@ -1716,7 +1834,7 @@ export const App = () => {
|
|
|
1716
1834
|
openDiffView,
|
|
1717
1835
|
closeDiffView: () => {
|
|
1718
1836
|
setDiffFullView(false)
|
|
1719
|
-
|
|
1837
|
+
setDiffCommentRangeStartIndex(null)
|
|
1720
1838
|
},
|
|
1721
1839
|
reloadDiff: () => {
|
|
1722
1840
|
if (!selectedPullRequest) return
|
|
@@ -1726,10 +1844,9 @@ export const App = () => {
|
|
|
1726
1844
|
toggleDiffRenderView: () => setDiffRenderView((current) => current === "unified" ? "split" : "unified"),
|
|
1727
1845
|
toggleDiffWrapMode: () => setDiffWrapMode((current) => current === "none" ? "word" : "none"),
|
|
1728
1846
|
jumpDiffFile,
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
},
|
|
1847
|
+
openSelectedDiffComment,
|
|
1848
|
+
toggleDiffCommentRange,
|
|
1849
|
+
moveDiffCommentThread,
|
|
1733
1850
|
openDiffCommentModal,
|
|
1734
1851
|
togglePullRequestDraftStatus: toggleSelectedPullRequestDraftStatus,
|
|
1735
1852
|
openLabelModal,
|
|
@@ -1755,6 +1872,9 @@ export const App = () => {
|
|
|
1755
1872
|
const command = appCommands.find((entry) => entry.id === id)
|
|
1756
1873
|
return command ? runCommand(command, options) : false
|
|
1757
1874
|
}
|
|
1875
|
+
const runCommandByIdRef = useRef(runCommandById)
|
|
1876
|
+
runCommandByIdRef.current = runCommandById
|
|
1877
|
+
useAppCommandRegistry(appCommands, runCommandByIdRef)
|
|
1758
1878
|
const dynamicPaletteCommands: readonly AppCommand[] = (() => {
|
|
1759
1879
|
if (!commandPaletteActive) return []
|
|
1760
1880
|
const repository = parseRepositoryInput(commandPalette.query)
|
|
@@ -1769,287 +1889,337 @@ export const App = () => {
|
|
|
1769
1889
|
})()
|
|
1770
1890
|
// Dynamic commands always pin to the top of the palette; they came directly from the
|
|
1771
1891
|
// user's typed input so they shouldn't be filtered by fuzzy score against themselves.
|
|
1892
|
+
const staticPaletteCommands = commandPaletteActive
|
|
1893
|
+
? filterCommands(appCommands.filter((command) => command.id !== "command.open" && commandEnabled(command)), commandPalette.query)
|
|
1894
|
+
: []
|
|
1772
1895
|
const commandPaletteCommands = commandPaletteActive
|
|
1773
1896
|
? [
|
|
1774
1897
|
...dynamicPaletteCommands,
|
|
1775
|
-
...
|
|
1898
|
+
...(commandPalette.query.trim().length > 0 ? staticPaletteCommands : sortCommandsByScope(staticPaletteCommands)),
|
|
1776
1899
|
]
|
|
1777
1900
|
: []
|
|
1778
1901
|
const selectedCommandIndex = clampCommandIndex(commandPalette.selectedIndex, commandPaletteCommands)
|
|
1779
1902
|
const selectedCommand = commandPaletteCommands[selectedCommandIndex] ?? null
|
|
1780
1903
|
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
// so these don't dispatch on top of modal-specific handlers below.
|
|
1784
|
-
const globalKeymapActiveRef = useRef(false)
|
|
1785
|
-
globalKeymapActiveRef.current = !commandPaletteActive
|
|
1786
|
-
&& !openRepositoryModalActive
|
|
1787
|
-
&& !labelModalActive
|
|
1788
|
-
&& !commentModalActive
|
|
1789
|
-
&& !commentThreadModalActive
|
|
1790
|
-
&& !closeModalActive
|
|
1791
|
-
&& !mergeModalActive
|
|
1792
|
-
&& !themeModalActive
|
|
1904
|
+
const noModalActive = activeModal._tag === "None"
|
|
1905
|
+
const globalLayerActive = noModalActive
|
|
1793
1906
|
&& !diffFullView
|
|
1794
1907
|
&& !detailFullView
|
|
1795
1908
|
&& !filterMode
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
],
|
|
1815
|
-
}), [])
|
|
1816
|
-
// Always-on bindings — work even while modals are open.
|
|
1817
|
-
useBindings(() => ({
|
|
1818
|
-
bindings: [
|
|
1819
|
-
{ key: "ctrl+p", cmd: () => runCommandByIdRef.current("command.open") },
|
|
1820
|
-
{ key: "meta+k", cmd: () => runCommandByIdRef.current("command.open") },
|
|
1821
|
-
],
|
|
1822
|
-
}), [])
|
|
1823
|
-
|
|
1824
|
-
// CloseModal: escape closes, enter confirms.
|
|
1825
|
-
const closeModalActiveRef = useRef(false)
|
|
1826
|
-
closeModalActiveRef.current = closeModalActive
|
|
1827
|
-
const closeActiveModalRef = useRef(closeActiveModal)
|
|
1828
|
-
closeActiveModalRef.current = closeActiveModal
|
|
1829
|
-
const confirmClosePullRequestRef = useRef(confirmClosePullRequest)
|
|
1830
|
-
confirmClosePullRequestRef.current = confirmClosePullRequest
|
|
1831
|
-
useBindings(() => ({
|
|
1832
|
-
enabled: () => closeModalActiveRef.current,
|
|
1833
|
-
bindings: [
|
|
1834
|
-
{ key: "escape", cmd: () => closeActiveModalRef.current() },
|
|
1835
|
-
{ key: "return", cmd: () => confirmClosePullRequestRef.current() },
|
|
1836
|
-
],
|
|
1837
|
-
}), [])
|
|
1838
|
-
|
|
1839
|
-
// MergeModal: escape, enter (when options>0), up/down/j/k navigation.
|
|
1840
|
-
const mergeModalActiveRef = useRef(false)
|
|
1841
|
-
mergeModalActiveRef.current = mergeModalActive
|
|
1842
|
-
const mergeModalContextRef = useRef({ availableCount: 0, confirm: confirmMergeAction, setMergeModal })
|
|
1843
|
-
mergeModalContextRef.current = {
|
|
1844
|
-
availableCount: availableMergeActions(mergeModal.info).length,
|
|
1845
|
-
confirm: confirmMergeAction,
|
|
1846
|
-
setMergeModal,
|
|
1847
|
-
}
|
|
1848
|
-
const moveMergeSelection = (delta: -1 | 1) => mergeModalContextRef.current.setMergeModal((current) => {
|
|
1849
|
-
const max = Math.max(0, mergeModalContextRef.current.availableCount - 1)
|
|
1909
|
+
useScopedBindings({
|
|
1910
|
+
when: true,
|
|
1911
|
+
bindings: {
|
|
1912
|
+
"ctrl+p": "command.open",
|
|
1913
|
+
"meta+k": "command.open",
|
|
1914
|
+
},
|
|
1915
|
+
})
|
|
1916
|
+
|
|
1917
|
+
useScopedBindings({
|
|
1918
|
+
when: closeModalActive,
|
|
1919
|
+
bindings: {
|
|
1920
|
+
escape: closeActiveModal,
|
|
1921
|
+
return: confirmClosePullRequest,
|
|
1922
|
+
},
|
|
1923
|
+
})
|
|
1924
|
+
|
|
1925
|
+
const moveMergeSelection = (delta: -1 | 1) => setMergeModal((current) => {
|
|
1926
|
+
const max = Math.max(0, availableMergeActions(mergeModal.info).length - 1)
|
|
1850
1927
|
return { ...current, selectedIndex: Math.max(0, Math.min(max, current.selectedIndex + delta)) }
|
|
1851
1928
|
})
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
bindings:
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
if (
|
|
1858
|
-
}
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
})
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
const commentThreadModalActiveRef = useRef(false)
|
|
1868
|
-
commentThreadModalActiveRef.current = commentThreadModalActive
|
|
1869
|
-
const commentThreadCtxRef = useRef({ openDiffCommentModal, setCommentThreadModal, halfPage })
|
|
1870
|
-
commentThreadCtxRef.current = { openDiffCommentModal, setCommentThreadModal, halfPage }
|
|
1871
|
-
const scrollCommentThread = (delta: number) => commentThreadCtxRef.current.setCommentThreadModal((current) => ({
|
|
1929
|
+
useScopedBindings({
|
|
1930
|
+
when: mergeModalActive,
|
|
1931
|
+
bindings: {
|
|
1932
|
+
escape: closeActiveModal,
|
|
1933
|
+
return: () => {
|
|
1934
|
+
if (availableMergeActions(mergeModal.info).length > 0) confirmMergeAction()
|
|
1935
|
+
},
|
|
1936
|
+
up: () => moveMergeSelection(-1),
|
|
1937
|
+
k: () => moveMergeSelection(-1),
|
|
1938
|
+
down: () => moveMergeSelection(1),
|
|
1939
|
+
j: () => moveMergeSelection(1),
|
|
1940
|
+
},
|
|
1941
|
+
})
|
|
1942
|
+
|
|
1943
|
+
const scrollCommentThread = (delta: number) => setCommentThreadModal((current) => ({
|
|
1872
1944
|
...current,
|
|
1873
1945
|
scrollOffset: Math.max(0, current.scrollOffset + delta),
|
|
1874
1946
|
}))
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
bindings:
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
})
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
labelModalActiveRef.current = labelModalActive
|
|
1897
|
-
const labelModalCtxRef = useRef({ toggleLabelAtIndex, setLabelModal, filteredCount: 0 })
|
|
1898
|
-
labelModalCtxRef.current = {
|
|
1899
|
-
toggleLabelAtIndex,
|
|
1900
|
-
setLabelModal,
|
|
1901
|
-
filteredCount: filterLabels(labelModal.availableLabels, labelModal.query).length,
|
|
1902
|
-
}
|
|
1903
|
-
const moveLabelSelection = (delta: -1 | 1) => labelModalCtxRef.current.setLabelModal((current) => {
|
|
1904
|
-
const max = Math.max(0, labelModalCtxRef.current.filteredCount - 1)
|
|
1947
|
+
useScopedBindings({
|
|
1948
|
+
when: commentThreadModalActive,
|
|
1949
|
+
bindings: {
|
|
1950
|
+
escape: closeActiveModal,
|
|
1951
|
+
return: openDiffCommentModal,
|
|
1952
|
+
a: openDiffCommentModal,
|
|
1953
|
+
c: openDiffCommentModal,
|
|
1954
|
+
up: () => scrollCommentThread(-1),
|
|
1955
|
+
k: () => scrollCommentThread(-1),
|
|
1956
|
+
down: () => scrollCommentThread(1),
|
|
1957
|
+
j: () => scrollCommentThread(1),
|
|
1958
|
+
pageup: () => scrollCommentThread(-halfPage),
|
|
1959
|
+
"ctrl+u": () => scrollCommentThread(-halfPage),
|
|
1960
|
+
pagedown: () => scrollCommentThread(halfPage),
|
|
1961
|
+
"ctrl+d": () => scrollCommentThread(halfPage),
|
|
1962
|
+
"ctrl+v": () => scrollCommentThread(halfPage),
|
|
1963
|
+
},
|
|
1964
|
+
})
|
|
1965
|
+
|
|
1966
|
+
const moveLabelSelection = (delta: -1 | 1) => setLabelModal((current) => {
|
|
1967
|
+
const max = Math.max(0, filterLabels(labelModal.availableLabels, labelModal.query).length - 1)
|
|
1905
1968
|
return { ...current, selectedIndex: Math.max(0, Math.min(max, current.selectedIndex + delta)) }
|
|
1906
1969
|
})
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
bindings:
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
}), [])
|
|
1918
|
-
|
|
1919
|
-
// ThemeModal: nav + filter-mode toggle. j/k only navigate when not in filter mode
|
|
1920
|
-
// (so users can type those letters into the query).
|
|
1921
|
-
const themeModalActiveRef = useRef(false)
|
|
1922
|
-
themeModalActiveRef.current = themeModalActive
|
|
1923
|
-
const themeModalCtxRef = useRef({
|
|
1924
|
-
filterMode: false,
|
|
1925
|
-
hasResults: true,
|
|
1926
|
-
closeThemeModal,
|
|
1927
|
-
updateThemeQuery,
|
|
1928
|
-
moveThemeSelection,
|
|
1970
|
+
useScopedBindings({
|
|
1971
|
+
when: labelModalActive,
|
|
1972
|
+
bindings: {
|
|
1973
|
+
escape: closeActiveModal,
|
|
1974
|
+
return: toggleLabelAtIndex,
|
|
1975
|
+
up: () => moveLabelSelection(-1),
|
|
1976
|
+
k: () => moveLabelSelection(-1),
|
|
1977
|
+
down: () => moveLabelSelection(1),
|
|
1978
|
+
j: () => moveLabelSelection(1),
|
|
1979
|
+
},
|
|
1929
1980
|
})
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
} },
|
|
1949
|
-
{ key: "up", cmd: () => themeModalCtxRef.current.moveThemeSelection(-1) },
|
|
1950
|
-
{ key: "down", cmd: () => themeModalCtxRef.current.moveThemeSelection(1) },
|
|
1951
|
-
{ key: "k", cmd: () => { if (!themeModalCtxRef.current.filterMode) themeModalCtxRef.current.moveThemeSelection(-1) } },
|
|
1952
|
-
{ key: "j", cmd: () => { if (!themeModalCtxRef.current.filterMode) themeModalCtxRef.current.moveThemeSelection(1) } },
|
|
1953
|
-
],
|
|
1954
|
-
}), [])
|
|
1955
|
-
|
|
1956
|
-
// OpenRepositoryModal: escape closes, return submits.
|
|
1957
|
-
const openRepositoryModalActiveRef = useRef(false)
|
|
1958
|
-
openRepositoryModalActiveRef.current = openRepositoryModalActive
|
|
1959
|
-
const openRepositoryFromInputRef = useRef(openRepositoryFromInput)
|
|
1960
|
-
openRepositoryFromInputRef.current = openRepositoryFromInput
|
|
1961
|
-
useBindings(() => ({
|
|
1962
|
-
enabled: () => openRepositoryModalActiveRef.current,
|
|
1963
|
-
bindings: [
|
|
1964
|
-
{ key: "escape", cmd: () => closeActiveModalRef.current() },
|
|
1965
|
-
{ key: "return", cmd: () => openRepositoryFromInputRef.current() },
|
|
1966
|
-
],
|
|
1967
|
-
}), [])
|
|
1968
|
-
|
|
1969
|
-
// CommentModal: full text editor — escape, submit, all the cursor/edit bindings.
|
|
1970
|
-
const commentModalActiveRef = useRef(false)
|
|
1971
|
-
commentModalActiveRef.current = commentModalActive
|
|
1972
|
-
const commentModalCtxRef = useRef({ submitDiffComment, editComment })
|
|
1973
|
-
commentModalCtxRef.current = { submitDiffComment, editComment }
|
|
1974
|
-
const editComm = (transform: Parameters<typeof editComment>[0]) => commentModalCtxRef.current.editComment(transform)
|
|
1975
|
-
useBindings(() => ({
|
|
1976
|
-
enabled: () => commentModalActiveRef.current,
|
|
1977
|
-
bindings: [
|
|
1978
|
-
{ key: "escape", cmd: () => closeActiveModalRef.current() },
|
|
1979
|
-
{ key: "ctrl+s", cmd: () => commentModalCtxRef.current.submitDiffComment() },
|
|
1980
|
-
{ key: "ctrl+a", cmd: () => editComm(moveLineStart) },
|
|
1981
|
-
{ key: "ctrl+e", cmd: () => editComm(moveLineEnd) },
|
|
1982
|
-
{ key: "ctrl+b", cmd: () => editComm(editorMoveLeft) },
|
|
1983
|
-
{ key: "ctrl+f", cmd: () => editComm(editorMoveRight) },
|
|
1984
|
-
{ key: "ctrl+w", cmd: () => editComm(deleteWordBackward) },
|
|
1985
|
-
{ key: "ctrl+u", cmd: () => editComm(deleteToLineStart) },
|
|
1986
|
-
{ key: "ctrl+k", cmd: () => editComm(deleteToLineEnd) },
|
|
1987
|
-
{ key: "ctrl+d", cmd: () => editComm(editorDeleteForward) },
|
|
1988
|
-
{ key: "meta+b", cmd: () => editComm(moveWordBackward) },
|
|
1989
|
-
{ key: "meta+left", cmd: () => editComm(moveWordBackward) },
|
|
1990
|
-
{ key: "meta+f", cmd: () => editComm(moveWordForward) },
|
|
1991
|
-
{ key: "meta+right", cmd: () => editComm(moveWordForward) },
|
|
1992
|
-
{ key: "meta+backspace", cmd: () => editComm(deleteWordBackward) },
|
|
1993
|
-
{ key: "meta+delete", cmd: () => editComm(deleteWordForward) },
|
|
1994
|
-
{ key: "backspace", cmd: () => editComm(editorBackspace) },
|
|
1995
|
-
{ key: "delete", cmd: () => editComm(editorDeleteForward) },
|
|
1996
|
-
{ key: "left", cmd: () => editComm(editorMoveLeft) },
|
|
1997
|
-
{ key: "right", cmd: () => editComm(editorMoveRight) },
|
|
1998
|
-
{ key: "up", cmd: () => editComm((state) => moveVertically(state, -1)) },
|
|
1999
|
-
{ key: "down", cmd: () => editComm((state) => moveVertically(state, 1)) },
|
|
2000
|
-
{ key: "home", cmd: () => editComm(moveLineStart) },
|
|
2001
|
-
{ key: "end", cmd: () => editComm(moveLineEnd) },
|
|
2002
|
-
{ key: "shift+return", cmd: () => editComm((state) => insertText(state, "\n")) },
|
|
2003
|
-
{ key: "return", cmd: () => commentModalCtxRef.current.submitDiffComment() },
|
|
2004
|
-
],
|
|
2005
|
-
}), [])
|
|
2006
|
-
|
|
2007
|
-
// CommandPalette: escape closes, return runs, up/k & down/j navigate.
|
|
2008
|
-
const commandPaletteActiveRef = useRef(false)
|
|
2009
|
-
commandPaletteActiveRef.current = commandPaletteActive
|
|
2010
|
-
const commandPaletteCtxRef = useRef({
|
|
2011
|
-
runSelected: () => {},
|
|
2012
|
-
setCommandPalette,
|
|
2013
|
-
paletteCommands: commandPaletteCommands,
|
|
1981
|
+
|
|
1982
|
+
useScopedBindings({
|
|
1983
|
+
when: themeModalActive,
|
|
1984
|
+
bindings: {
|
|
1985
|
+
escape: () => {
|
|
1986
|
+
if (themeModal.filterMode) updateThemeQuery("", { filterMode: false })
|
|
1987
|
+
else closeThemeModal(false)
|
|
1988
|
+
},
|
|
1989
|
+
"/": () => updateThemeQuery("", { filterMode: true }),
|
|
1990
|
+
return: () => {
|
|
1991
|
+
if (themeModal.filterMode && filterThemeDefinitions(themeModal.query).length === 0) return
|
|
1992
|
+
closeThemeModal(true)
|
|
1993
|
+
},
|
|
1994
|
+
up: () => moveThemeSelection(-1),
|
|
1995
|
+
down: () => moveThemeSelection(1),
|
|
1996
|
+
k: () => { if (!themeModal.filterMode) moveThemeSelection(-1) },
|
|
1997
|
+
j: () => { if (!themeModal.filterMode) moveThemeSelection(1) },
|
|
1998
|
+
},
|
|
2014
1999
|
})
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2000
|
+
|
|
2001
|
+
useScopedBindings({
|
|
2002
|
+
when: openRepositoryModalActive,
|
|
2003
|
+
bindings: {
|
|
2004
|
+
escape: closeActiveModal,
|
|
2005
|
+
return: openRepositoryFromInput,
|
|
2006
|
+
},
|
|
2007
|
+
})
|
|
2008
|
+
|
|
2009
|
+
useScopedBindings({
|
|
2010
|
+
when: commentModalActive,
|
|
2011
|
+
bindings: {
|
|
2012
|
+
escape: closeActiveModal,
|
|
2013
|
+
"ctrl+s": submitDiffComment,
|
|
2014
|
+
"ctrl+a": () => editComment(moveLineStart),
|
|
2015
|
+
"ctrl+e": () => editComment(moveLineEnd),
|
|
2016
|
+
"ctrl+b": () => editComment(editorMoveLeft),
|
|
2017
|
+
"ctrl+f": () => editComment(editorMoveRight),
|
|
2018
|
+
"ctrl+w": () => editComment(deleteWordBackward),
|
|
2019
|
+
"ctrl+u": () => editComment(deleteToLineStart),
|
|
2020
|
+
"ctrl+k": () => editComment(deleteToLineEnd),
|
|
2021
|
+
"ctrl+d": () => editComment(editorDeleteForward),
|
|
2022
|
+
"meta+b": () => editComment(moveWordBackward),
|
|
2023
|
+
"meta+left": () => editComment(moveWordBackward),
|
|
2024
|
+
"meta+f": () => editComment(moveWordForward),
|
|
2025
|
+
"meta+right": () => editComment(moveWordForward),
|
|
2026
|
+
"meta+backspace": () => editComment(deleteWordBackward),
|
|
2027
|
+
"meta+delete": () => editComment(deleteWordForward),
|
|
2028
|
+
backspace: () => editComment(editorBackspace),
|
|
2029
|
+
delete: () => editComment(editorDeleteForward),
|
|
2030
|
+
left: () => editComment(editorMoveLeft),
|
|
2031
|
+
right: () => editComment(editorMoveRight),
|
|
2032
|
+
up: () => editComment((state) => moveVertically(state, -1)),
|
|
2033
|
+
down: () => editComment((state) => moveVertically(state, 1)),
|
|
2034
|
+
home: () => editComment(moveLineStart),
|
|
2035
|
+
end: () => editComment(moveLineEnd),
|
|
2036
|
+
"shift+return": () => editComment((state) => insertText(state, "\n")),
|
|
2037
|
+
return: submitDiffComment,
|
|
2038
|
+
},
|
|
2039
|
+
})
|
|
2040
|
+
|
|
2041
|
+
const moveCommandPaletteSelection = (delta: -1 | 1) => setCommandPalette((current) => {
|
|
2042
|
+
const selectedIndex = clampCommandIndex(current.selectedIndex + delta, commandPaletteCommands)
|
|
2022
2043
|
return selectedIndex === current.selectedIndex ? current : { ...current, selectedIndex }
|
|
2023
2044
|
})
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
bindings:
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
})
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2045
|
+
useScopedBindings({
|
|
2046
|
+
when: commandPaletteActive,
|
|
2047
|
+
bindings: {
|
|
2048
|
+
escape: closeActiveModal,
|
|
2049
|
+
"ctrl+c": closeActiveModal,
|
|
2050
|
+
return: () => { if (selectedCommand) runCommand(selectedCommand, { notifyDisabled: true, closePalette: true }) },
|
|
2051
|
+
up: () => moveCommandPaletteSelection(-1),
|
|
2052
|
+
down: () => moveCommandPaletteSelection(1),
|
|
2053
|
+
},
|
|
2054
|
+
})
|
|
2055
|
+
|
|
2056
|
+
useScopedBindings({
|
|
2057
|
+
when: filterMode,
|
|
2058
|
+
bindings: {
|
|
2059
|
+
escape: () => { setFilterDraft(filterQuery); setFilterMode(false) },
|
|
2060
|
+
return: () => { setFilterQuery(filterDraft); setFilterMode(false) },
|
|
2061
|
+
},
|
|
2062
|
+
})
|
|
2063
|
+
|
|
2064
|
+
useScopedBindings({
|
|
2065
|
+
when: diffFullView && noModalActive,
|
|
2066
|
+
bindings: {
|
|
2067
|
+
escape: () => {
|
|
2068
|
+
if (diffCommentRangeActive) setDiffCommentRangeStartIndex(null)
|
|
2069
|
+
else runCommandById("diff.close")
|
|
2070
|
+
},
|
|
2071
|
+
return: openSelectedDiffComment,
|
|
2072
|
+
a: "diff.add-comment",
|
|
2073
|
+
v: "diff.toggle-range",
|
|
2074
|
+
"shift+v": "diff.toggle-view",
|
|
2075
|
+
w: "diff.toggle-wrap",
|
|
2076
|
+
r: "diff.reload",
|
|
2077
|
+
n: "diff.next-thread",
|
|
2078
|
+
p: "diff.previous-thread",
|
|
2079
|
+
pageup: () => moveDiffCommentAnchor(-halfPage, { preserveViewportRow: true }),
|
|
2080
|
+
"ctrl+u": () => moveDiffCommentAnchor(-halfPage, { preserveViewportRow: true }),
|
|
2081
|
+
pagedown: () => moveDiffCommentAnchor(halfPage, { preserveViewportRow: true }),
|
|
2082
|
+
"ctrl+d": () => moveDiffCommentAnchor(halfPage, { preserveViewportRow: true }),
|
|
2083
|
+
"ctrl+v": () => moveDiffCommentAnchor(halfPage, { preserveViewportRow: true }),
|
|
2084
|
+
"shift+up": () => moveDiffCommentAnchor(-8),
|
|
2085
|
+
"shift+k": () => moveDiffCommentAnchor(-8),
|
|
2086
|
+
"meta+up": () => moveDiffCommentAnchor(-8),
|
|
2087
|
+
"meta+k": () => moveDiffCommentAnchor(-8),
|
|
2088
|
+
"shift+down": () => moveDiffCommentAnchor(8),
|
|
2089
|
+
"shift+j": () => moveDiffCommentAnchor(8),
|
|
2090
|
+
"meta+down": () => moveDiffCommentAnchor(8),
|
|
2091
|
+
"meta+j": () => moveDiffCommentAnchor(8),
|
|
2092
|
+
...countedVerticalBindings(
|
|
2093
|
+
(count) => moveDiffCommentAnchor(-count),
|
|
2094
|
+
(count) => moveDiffCommentAnchor(count),
|
|
2095
|
+
),
|
|
2096
|
+
up: () => moveDiffCommentAnchor(-1),
|
|
2097
|
+
k: () => moveDiffCommentAnchor(-1),
|
|
2098
|
+
down: () => moveDiffCommentAnchor(1),
|
|
2099
|
+
j: () => moveDiffCommentAnchor(1),
|
|
2100
|
+
left: () => selectDiffCommentSide("LEFT"),
|
|
2101
|
+
h: () => selectDiffCommentSide("LEFT"),
|
|
2102
|
+
right: () => selectDiffCommentSide("RIGHT"),
|
|
2103
|
+
l: () => selectDiffCommentSide("RIGHT"),
|
|
2104
|
+
"]": "diff.next-file",
|
|
2105
|
+
"[": "diff.previous-file",
|
|
2106
|
+
"g g": () => moveDiffCommentToBoundary("first"),
|
|
2107
|
+
"shift+g": () => moveDiffCommentToBoundary("last"),
|
|
2108
|
+
"z z": () => alignSelectedDiffCommentAnchor("center"),
|
|
2109
|
+
"z t": () => alignSelectedDiffCommentAnchor("top"),
|
|
2110
|
+
"z b": () => alignSelectedDiffCommentAnchor("bottom"),
|
|
2111
|
+
o: "pull.open-browser",
|
|
2112
|
+
},
|
|
2113
|
+
})
|
|
2114
|
+
|
|
2115
|
+
const scrollDetailFullViewBy = (delta: number) => {
|
|
2116
|
+
detailScrollRef.current?.scrollBy({ x: 0, y: delta })
|
|
2117
|
+
setDetailScrollOffset((current) => Math.max(0, current + delta))
|
|
2118
|
+
}
|
|
2119
|
+
const scrollDetailFullViewTo = (y: number) => {
|
|
2120
|
+
detailScrollRef.current?.scrollTo({ x: 0, y })
|
|
2121
|
+
setDetailScrollOffset(y)
|
|
2122
|
+
}
|
|
2123
|
+
useScopedBindings({
|
|
2124
|
+
when: detailFullView && noModalActive,
|
|
2125
|
+
bindings: {
|
|
2126
|
+
...scrollBindings(scrollDetailFullViewBy, halfPage, scrollDetailFullViewTo),
|
|
2127
|
+
escape: "detail.close",
|
|
2128
|
+
return: "detail.close",
|
|
2129
|
+
t: "theme.open",
|
|
2130
|
+
d: "diff.open",
|
|
2131
|
+
x: "pull.close",
|
|
2132
|
+
l: "pull.labels",
|
|
2133
|
+
m: "pull.merge",
|
|
2134
|
+
"shift+m": "pull.merge",
|
|
2135
|
+
s: "pull.toggle-draft",
|
|
2136
|
+
"shift+s": "pull.toggle-draft",
|
|
2137
|
+
r: "pull.refresh",
|
|
2138
|
+
o: "pull.open-browser",
|
|
2139
|
+
y: "pull.copy-metadata",
|
|
2140
|
+
},
|
|
2141
|
+
})
|
|
2142
|
+
|
|
2143
|
+
const moveSelectedToPreviousGroup = () => setSelectedIndex((current) => {
|
|
2144
|
+
if (visiblePullRequests.length === 0 || groupStarts.length === 0) return 0
|
|
2145
|
+
const currentGroup = getCurrentGroupIndex(current)
|
|
2146
|
+
if (currentGroup <= 0) return groupStarts[groupStarts.length - 1]!
|
|
2147
|
+
return groupStarts[currentGroup - 1]!
|
|
2148
|
+
})
|
|
2149
|
+
const moveSelectedToNextGroup = () => setSelectedIndex((current) => {
|
|
2150
|
+
if (visiblePullRequests.length === 0 || groupStarts.length === 0) return 0
|
|
2151
|
+
const currentGroup = getCurrentGroupIndex(current)
|
|
2152
|
+
if (currentGroup >= groupStarts.length - 1) return groupStarts[0]!
|
|
2153
|
+
return groupStarts[currentGroup + 1]!
|
|
2154
|
+
})
|
|
2155
|
+
const stepSelected = (delta: number) => setSelectedIndex((current) => {
|
|
2156
|
+
if (visiblePullRequests.length === 0) return 0
|
|
2157
|
+
return Math.max(0, Math.min(visiblePullRequests.length - 1, current + delta))
|
|
2158
|
+
})
|
|
2159
|
+
const stepSelectedDown = (count = 1) => {
|
|
2160
|
+
if (visiblePullRequests.length === 0) return
|
|
2161
|
+
if (selectedIndex + count >= visiblePullRequests.length && hasMorePullRequests) {
|
|
2162
|
+
loadMorePullRequests()
|
|
2163
|
+
}
|
|
2164
|
+
stepSelected(count)
|
|
2165
|
+
}
|
|
2166
|
+
const stepSelectedUp = (count = 1) => stepSelected(-count)
|
|
2167
|
+
const stepSelectedDownWithLoadMore = () => {
|
|
2168
|
+
if (visiblePullRequests.length > 0 && selectedIndex >= visiblePullRequests.length - 1 && hasMorePullRequests) {
|
|
2169
|
+
loadMorePullRequests()
|
|
2170
|
+
return
|
|
2171
|
+
}
|
|
2172
|
+
setSelectedIndex((current) => {
|
|
2173
|
+
if (visiblePullRequests.length === 0) return 0
|
|
2174
|
+
return current >= visiblePullRequests.length - 1 ? 0 : current + 1
|
|
2175
|
+
})
|
|
2176
|
+
}
|
|
2177
|
+
const stepSelectedUpWrap = () => setSelectedIndex((current) => {
|
|
2178
|
+
if (visiblePullRequests.length === 0) return 0
|
|
2179
|
+
return current <= 0 ? visiblePullRequests.length - 1 : current - 1
|
|
2180
|
+
})
|
|
2181
|
+
useScopedBindings({
|
|
2182
|
+
when: globalLayerActive,
|
|
2183
|
+
bindings: {
|
|
2184
|
+
"/": "filter.open",
|
|
2185
|
+
r: "pull.refresh",
|
|
2186
|
+
t: "theme.open",
|
|
2187
|
+
d: "diff.open",
|
|
2188
|
+
l: "pull.labels",
|
|
2189
|
+
m: "pull.merge",
|
|
2190
|
+
"shift+m": "pull.merge",
|
|
2191
|
+
x: "pull.close",
|
|
2192
|
+
o: "pull.open-browser",
|
|
2193
|
+
s: "pull.toggle-draft",
|
|
2194
|
+
"shift+s": "pull.toggle-draft",
|
|
2195
|
+
y: "pull.copy-metadata",
|
|
2196
|
+
return: "detail.open",
|
|
2197
|
+
tab: () => switchQueueMode(1),
|
|
2198
|
+
"shift+tab": () => switchQueueMode(-1),
|
|
2199
|
+
escape: () => { if (filterQuery.length > 0) runCommandByIdRef.current("filter.clear") },
|
|
2200
|
+
home: () => { if (isWideLayout && selectedPullRequest) scrollDetailPreviewTo(0) },
|
|
2201
|
+
end: () => { if (isWideLayout && selectedPullRequest) scrollDetailPreviewTo(Number.MAX_SAFE_INTEGER) },
|
|
2202
|
+
pageup: () => { if (isWideLayout && selectedPullRequest) scrollDetailPreviewBy(-halfPage) },
|
|
2203
|
+
pagedown: () => { if (isWideLayout && selectedPullRequest) scrollDetailPreviewBy(halfPage) },
|
|
2204
|
+
"[": moveSelectedToPreviousGroup,
|
|
2205
|
+
"meta+up": moveSelectedToPreviousGroup,
|
|
2206
|
+
"meta+k": moveSelectedToPreviousGroup,
|
|
2207
|
+
"shift+k": moveSelectedToPreviousGroup,
|
|
2208
|
+
"]": moveSelectedToNextGroup,
|
|
2209
|
+
"meta+down": moveSelectedToNextGroup,
|
|
2210
|
+
"meta+j": moveSelectedToNextGroup,
|
|
2211
|
+
"shift+j": moveSelectedToNextGroup,
|
|
2212
|
+
"ctrl+u": () => stepSelected(-halfPage),
|
|
2213
|
+
"ctrl+d": () => stepSelected(halfPage),
|
|
2214
|
+
...countedVerticalBindings(stepSelectedUp, stepSelectedDown),
|
|
2215
|
+
up: stepSelectedUpWrap,
|
|
2216
|
+
k: stepSelectedUpWrap,
|
|
2217
|
+
down: stepSelectedDownWithLoadMore,
|
|
2218
|
+
j: stepSelectedDownWithLoadMore,
|
|
2219
|
+
"g g": () => setSelectedIndex(0),
|
|
2220
|
+
"shift+g": () => setSelectedIndex(visiblePullRequests.length === 0 ? 0 : visiblePullRequests.length - 1),
|
|
2221
|
+
},
|
|
2222
|
+
})
|
|
2053
2223
|
|
|
2054
2224
|
useKeyboard((key) => {
|
|
2055
2225
|
if (commandPaletteActive) {
|
|
@@ -2113,340 +2283,19 @@ export const App = () => {
|
|
|
2113
2283
|
return
|
|
2114
2284
|
}
|
|
2115
2285
|
|
|
2116
|
-
if (diffFullView) {
|
|
2117
|
-
if (diffCommentMode) {
|
|
2118
|
-
if (key.name === "escape") {
|
|
2119
|
-
setDiffCommentMode(false)
|
|
2120
|
-
return
|
|
2121
|
-
}
|
|
2122
|
-
if (key.name === "c") {
|
|
2123
|
-
runCommandById("diff.comment-mode")
|
|
2124
|
-
return
|
|
2125
|
-
}
|
|
2126
|
-
if (key.name === "return" || key.name === "enter") {
|
|
2127
|
-
if (selectedDiffCommentThread.length > 0) openDiffCommentThreadModal()
|
|
2128
|
-
else openDiffCommentModal()
|
|
2129
|
-
return
|
|
2130
|
-
}
|
|
2131
|
-
if (key.name === "a") {
|
|
2132
|
-
runCommandById("diff.add-comment")
|
|
2133
|
-
return
|
|
2134
|
-
}
|
|
2135
|
-
if (key.name === "pageup" || key.ctrl && key.name === "u") {
|
|
2136
|
-
moveDiffCommentAnchor(-halfPage)
|
|
2137
|
-
return
|
|
2138
|
-
}
|
|
2139
|
-
if (key.name === "pagedown" || key.ctrl && (key.name === "d" || key.name === "v")) {
|
|
2140
|
-
moveDiffCommentAnchor(halfPage)
|
|
2141
|
-
return
|
|
2142
|
-
}
|
|
2143
|
-
if ((key.shift || key.option || key.meta) && (key.name === "up" || key.name === "k") || key.name === "K") {
|
|
2144
|
-
moveDiffCommentAnchor(-8)
|
|
2145
|
-
return
|
|
2146
|
-
}
|
|
2147
|
-
if ((key.shift || key.option || key.meta) && (key.name === "down" || key.name === "j") || key.name === "J") {
|
|
2148
|
-
moveDiffCommentAnchor(8)
|
|
2149
|
-
return
|
|
2150
|
-
}
|
|
2151
|
-
if (key.name === "up" || key.name === "k") {
|
|
2152
|
-
moveDiffCommentAnchor(-1)
|
|
2153
|
-
return
|
|
2154
|
-
}
|
|
2155
|
-
if (key.name === "down" || key.name === "j") {
|
|
2156
|
-
moveDiffCommentAnchor(1)
|
|
2157
|
-
return
|
|
2158
|
-
}
|
|
2159
|
-
if (key.name === "left" || key.name === "h") {
|
|
2160
|
-
selectDiffCommentSide("LEFT")
|
|
2161
|
-
return
|
|
2162
|
-
}
|
|
2163
|
-
if (key.name === "right" || key.name === "l") {
|
|
2164
|
-
selectDiffCommentSide("RIGHT")
|
|
2165
|
-
return
|
|
2166
|
-
}
|
|
2167
|
-
if (key.name === "]" && selectedDiffState?._tag === "Ready") {
|
|
2168
|
-
runCommandById("diff.next-file")
|
|
2169
|
-
return
|
|
2170
|
-
}
|
|
2171
|
-
if (key.name === "[" && selectedDiffState?._tag === "Ready") {
|
|
2172
|
-
runCommandById("diff.previous-file")
|
|
2173
|
-
return
|
|
2174
|
-
}
|
|
2175
|
-
return
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
|
-
if (key.name === "escape" || key.name === "return" || key.name === "enter") {
|
|
2179
|
-
runCommandById("diff.close")
|
|
2180
|
-
return
|
|
2181
|
-
}
|
|
2182
|
-
if (key.name === "c" && selectedDiffState?._tag === "Ready") {
|
|
2183
|
-
runCommandById("diff.comment-mode")
|
|
2184
|
-
return
|
|
2185
|
-
}
|
|
2186
|
-
if (key.name === "home") {
|
|
2187
|
-
scrollDiffTo(0)
|
|
2188
|
-
return
|
|
2189
|
-
}
|
|
2190
|
-
if (key.name === "end") {
|
|
2191
|
-
scrollDiffTo(Number.MAX_SAFE_INTEGER)
|
|
2192
|
-
return
|
|
2193
|
-
}
|
|
2194
|
-
if (key.name === "pageup") {
|
|
2195
|
-
scrollDiffBy(-halfPage)
|
|
2196
|
-
return
|
|
2197
|
-
}
|
|
2198
|
-
if (key.name === "pagedown") {
|
|
2199
|
-
scrollDiffBy(halfPage)
|
|
2200
|
-
return
|
|
2201
|
-
}
|
|
2202
|
-
if (handleVimGoto(key, () => scrollDiffTo(0), () => scrollDiffTo(Number.MAX_SAFE_INTEGER))) return
|
|
2203
|
-
if (key.name === "up" || key.name === "k") {
|
|
2204
|
-
scrollDiffBy(-1)
|
|
2205
|
-
return
|
|
2206
|
-
}
|
|
2207
|
-
if (key.name === "down" || key.name === "j") {
|
|
2208
|
-
scrollDiffBy(1)
|
|
2209
|
-
return
|
|
2210
|
-
}
|
|
2211
|
-
if (key.ctrl && key.name === "u") {
|
|
2212
|
-
scrollDiffBy(-halfPage)
|
|
2213
|
-
return
|
|
2214
|
-
}
|
|
2215
|
-
if (key.ctrl && (key.name === "d" || key.name === "v")) {
|
|
2216
|
-
scrollDiffBy(halfPage)
|
|
2217
|
-
return
|
|
2218
|
-
}
|
|
2219
|
-
if (key.name === "v") {
|
|
2220
|
-
runCommandById("diff.toggle-view")
|
|
2221
|
-
return
|
|
2222
|
-
}
|
|
2223
|
-
if (key.name === "w") {
|
|
2224
|
-
runCommandById("diff.toggle-wrap")
|
|
2225
|
-
return
|
|
2226
|
-
}
|
|
2227
|
-
if (key.name === "r" && selectedPullRequest) {
|
|
2228
|
-
runCommandById("diff.reload")
|
|
2229
|
-
return
|
|
2230
|
-
}
|
|
2231
|
-
if ((key.name === "]" || key.name === "right" || key.name === "l") && selectedDiffState?._tag === "Ready") {
|
|
2232
|
-
runCommandById("diff.next-file")
|
|
2233
|
-
return
|
|
2234
|
-
}
|
|
2235
|
-
if ((key.name === "[" || key.name === "left" || key.name === "h") && selectedDiffState?._tag === "Ready") {
|
|
2236
|
-
runCommandById("diff.previous-file")
|
|
2237
|
-
return
|
|
2238
|
-
}
|
|
2239
|
-
if (key.name === "o" && selectedPullRequest) {
|
|
2240
|
-
runCommandById("pull.open-browser")
|
|
2241
|
-
return
|
|
2242
|
-
}
|
|
2243
|
-
return
|
|
2244
|
-
}
|
|
2245
|
-
|
|
2246
|
-
if (detailFullView) {
|
|
2247
|
-
const plainKey = !key.ctrl && !key.meta && !key.option
|
|
2248
|
-
if (key.name === "escape" || (key.name === "return" || key.name === "enter")) {
|
|
2249
|
-
runCommandById("detail.close")
|
|
2250
|
-
return
|
|
2251
|
-
}
|
|
2252
|
-
if (isThemeKey(key)) {
|
|
2253
|
-
runCommandById("theme.open")
|
|
2254
|
-
return
|
|
2255
|
-
}
|
|
2256
|
-
if (plainKey && key.name === "d" && selectedPullRequest) {
|
|
2257
|
-
runCommandById("diff.open")
|
|
2258
|
-
return
|
|
2259
|
-
}
|
|
2260
|
-
if (plainKey && key.name === "x" && selectedPullRequest?.state === "open") {
|
|
2261
|
-
runCommandById("pull.close")
|
|
2262
|
-
return
|
|
2263
|
-
}
|
|
2264
|
-
if (plainKey && key.name === "l" && selectedPullRequest) {
|
|
2265
|
-
runCommandById("pull.labels")
|
|
2266
|
-
return
|
|
2267
|
-
}
|
|
2268
|
-
if (plainKey && (key.name === "m" || key.name === "M") && selectedPullRequest) {
|
|
2269
|
-
runCommandById("pull.merge")
|
|
2270
|
-
return
|
|
2271
|
-
}
|
|
2272
|
-
if (plainKey && (key.name === "s" || key.name === "S") && selectedPullRequest) {
|
|
2273
|
-
runCommandById("pull.toggle-draft")
|
|
2274
|
-
return
|
|
2275
|
-
}
|
|
2276
|
-
if (plainKey && key.name === "r") {
|
|
2277
|
-
runCommandById("pull.refresh")
|
|
2278
|
-
return
|
|
2279
|
-
}
|
|
2280
|
-
if (key.name === "home") {
|
|
2281
|
-
detailScrollRef.current?.scrollTo({ x: 0, y: 0 })
|
|
2282
|
-
setDetailScrollOffset(0)
|
|
2283
|
-
return
|
|
2284
|
-
}
|
|
2285
|
-
if (key.name === "end") {
|
|
2286
|
-
detailScrollRef.current?.scrollTo({ x: 0, y: Number.MAX_SAFE_INTEGER })
|
|
2287
|
-
setDetailScrollOffset(Number.MAX_SAFE_INTEGER)
|
|
2288
|
-
return
|
|
2289
|
-
}
|
|
2290
|
-
if (key.name === "pageup") {
|
|
2291
|
-
detailScrollRef.current?.scrollBy({ x: 0, y: -halfPage })
|
|
2292
|
-
setDetailScrollOffset((current) => Math.max(0, current - halfPage))
|
|
2293
|
-
return
|
|
2294
|
-
}
|
|
2295
|
-
if (key.name === "pagedown") {
|
|
2296
|
-
detailScrollRef.current?.scrollBy({ x: 0, y: halfPage })
|
|
2297
|
-
setDetailScrollOffset((current) => current + halfPage)
|
|
2298
|
-
return
|
|
2299
|
-
}
|
|
2300
|
-
if (handleVimGoto(key,
|
|
2301
|
-
() => { detailScrollRef.current?.scrollTo({ x: 0, y: 0 }); setDetailScrollOffset(0) },
|
|
2302
|
-
() => { detailScrollRef.current?.scrollTo({ x: 0, y: Number.MAX_SAFE_INTEGER }); setDetailScrollOffset(Number.MAX_SAFE_INTEGER) },
|
|
2303
|
-
)) return
|
|
2304
|
-
if (key.name === "up" || key.name === "k") {
|
|
2305
|
-
detailScrollRef.current?.scrollBy({ x: 0, y: -1 })
|
|
2306
|
-
setDetailScrollOffset((current) => Math.max(0, current - 1))
|
|
2307
|
-
return
|
|
2308
|
-
}
|
|
2309
|
-
if (key.name === "down" || key.name === "j") {
|
|
2310
|
-
detailScrollRef.current?.scrollBy({ x: 0, y: 1 })
|
|
2311
|
-
setDetailScrollOffset((current) => current + 1)
|
|
2312
|
-
return
|
|
2313
|
-
}
|
|
2314
|
-
if (key.ctrl && key.name === "u") {
|
|
2315
|
-
detailScrollRef.current?.scrollBy({ x: 0, y: -halfPage })
|
|
2316
|
-
setDetailScrollOffset((current) => Math.max(0, current - halfPage))
|
|
2317
|
-
return
|
|
2318
|
-
}
|
|
2319
|
-
if (key.ctrl && (key.name === "d" || key.name === "v")) {
|
|
2320
|
-
detailScrollRef.current?.scrollBy({ x: 0, y: halfPage })
|
|
2321
|
-
setDetailScrollOffset((current) => current + halfPage)
|
|
2322
|
-
return
|
|
2323
|
-
}
|
|
2324
|
-
if (plainKey && key.name === "o" && selectedPullRequest) {
|
|
2325
|
-
runCommandById("pull.open-browser")
|
|
2326
|
-
return
|
|
2327
|
-
}
|
|
2328
|
-
if (plainKey && key.name === "y" && selectedPullRequest) {
|
|
2329
|
-
runCommandById("pull.copy-metadata")
|
|
2330
|
-
return
|
|
2331
|
-
}
|
|
2332
|
-
return
|
|
2333
|
-
}
|
|
2334
|
-
|
|
2335
2286
|
if (filterMode) {
|
|
2336
2287
|
if (isSingleLineInputKey(key)) {
|
|
2337
2288
|
setFilterDraft((current) => editSingleLineInput(current, key) ?? current)
|
|
2338
2289
|
}
|
|
2339
|
-
return
|
|
2340
2290
|
}
|
|
2341
|
-
|
|
2342
|
-
if (key.name === "tab") {
|
|
2343
|
-
switchQueueMode(key.shift ? -1 : 1)
|
|
2344
|
-
return
|
|
2345
|
-
}
|
|
2346
|
-
|
|
2347
|
-
if (key.name === "escape" && filterQuery.length > 0) {
|
|
2348
|
-
runCommandById("filter.clear")
|
|
2349
|
-
return
|
|
2350
|
-
}
|
|
2351
|
-
if (isWideLayout && selectedPullRequest && !detailFullView && !diffFullView) {
|
|
2352
|
-
if (key.name === "home") {
|
|
2353
|
-
scrollDetailPreviewTo(0)
|
|
2354
|
-
return
|
|
2355
|
-
}
|
|
2356
|
-
if (key.name === "end") {
|
|
2357
|
-
scrollDetailPreviewTo(Number.MAX_SAFE_INTEGER)
|
|
2358
|
-
return
|
|
2359
|
-
}
|
|
2360
|
-
if (key.name === "pageup") {
|
|
2361
|
-
scrollDetailPreviewBy(-halfPage)
|
|
2362
|
-
return
|
|
2363
|
-
}
|
|
2364
|
-
if (key.name === "pagedown") {
|
|
2365
|
-
scrollDetailPreviewBy(halfPage)
|
|
2366
|
-
return
|
|
2367
|
-
}
|
|
2368
|
-
}
|
|
2369
|
-
if (
|
|
2370
|
-
key.name === "[" ||
|
|
2371
|
-
((key.option || key.meta) && (key.name === "up" || key.name === "k")) ||
|
|
2372
|
-
(key.shift && key.name === "k") ||
|
|
2373
|
-
key.name === "K"
|
|
2374
|
-
) {
|
|
2375
|
-
setSelectedIndex((current) => {
|
|
2376
|
-
if (visiblePullRequests.length === 0 || groupStarts.length === 0) return 0
|
|
2377
|
-
const currentGroup = getCurrentGroupIndex(current)
|
|
2378
|
-
if (currentGroup <= 0) return groupStarts[groupStarts.length - 1]!
|
|
2379
|
-
return groupStarts[currentGroup - 1]!
|
|
2380
|
-
})
|
|
2381
|
-
return
|
|
2382
|
-
}
|
|
2383
|
-
if (
|
|
2384
|
-
key.name === "]" ||
|
|
2385
|
-
((key.option || key.meta) && (key.name === "down" || key.name === "j")) ||
|
|
2386
|
-
(key.shift && key.name === "j") ||
|
|
2387
|
-
key.name === "J"
|
|
2388
|
-
) {
|
|
2389
|
-
setSelectedIndex((current) => {
|
|
2390
|
-
if (visiblePullRequests.length === 0 || groupStarts.length === 0) return 0
|
|
2391
|
-
const currentGroup = getCurrentGroupIndex(current)
|
|
2392
|
-
if (currentGroup >= groupStarts.length - 1) return groupStarts[0]!
|
|
2393
|
-
return groupStarts[currentGroup + 1]!
|
|
2394
|
-
})
|
|
2395
|
-
return
|
|
2396
|
-
}
|
|
2397
|
-
if (key.ctrl && key.name === "u") {
|
|
2398
|
-
setSelectedIndex((current) => {
|
|
2399
|
-
if (visiblePullRequests.length === 0) return 0
|
|
2400
|
-
return Math.max(0, current - halfPage)
|
|
2401
|
-
})
|
|
2402
|
-
return
|
|
2403
|
-
}
|
|
2404
|
-
if (key.ctrl && key.name === "d") {
|
|
2405
|
-
setSelectedIndex((current) => {
|
|
2406
|
-
if (visiblePullRequests.length === 0) return 0
|
|
2407
|
-
return Math.min(visiblePullRequests.length - 1, current + halfPage)
|
|
2408
|
-
})
|
|
2409
|
-
return
|
|
2410
|
-
}
|
|
2411
|
-
if (key.name === "up" || key.name === "k") {
|
|
2412
|
-
setSelectedIndex((current) => {
|
|
2413
|
-
if (visiblePullRequests.length === 0) return 0
|
|
2414
|
-
return current <= 0 ? visiblePullRequests.length - 1 : current - 1
|
|
2415
|
-
})
|
|
2416
|
-
return
|
|
2417
|
-
}
|
|
2418
|
-
if (key.name === "down" || key.name === "j") {
|
|
2419
|
-
if (visiblePullRequests.length > 0 && selectedIndex >= visiblePullRequests.length - 1 && hasMorePullRequests) {
|
|
2420
|
-
loadMorePullRequests()
|
|
2421
|
-
return
|
|
2422
|
-
}
|
|
2423
|
-
setSelectedIndex((current) => {
|
|
2424
|
-
if (visiblePullRequests.length === 0) return 0
|
|
2425
|
-
return current >= visiblePullRequests.length - 1 ? 0 : current + 1
|
|
2426
|
-
})
|
|
2427
|
-
return
|
|
2428
|
-
}
|
|
2429
|
-
if (handleVimGoto(key,
|
|
2430
|
-
() => setSelectedIndex(0),
|
|
2431
|
-
() => setSelectedIndex(visiblePullRequests.length === 0 ? 0 : visiblePullRequests.length - 1),
|
|
2432
|
-
)) return
|
|
2433
2291
|
})
|
|
2434
2292
|
|
|
2435
2293
|
const fullscreenContentWidth = Math.max(24, contentWidth - 2)
|
|
2436
2294
|
const fullscreenBodyLines = Math.max(8, terminalHeight - 8)
|
|
2437
|
-
const
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
paneWidth: contentWidth,
|
|
2442
|
-
showChecks: true,
|
|
2443
|
-
}) > wideBodyHeight
|
|
2444
|
-
const narrowFullscreenDetailScrollable = getDetailsPaneHeight({
|
|
2445
|
-
pullRequest: selectedPullRequest,
|
|
2446
|
-
contentWidth: fullscreenContentWidth,
|
|
2447
|
-
bodyLines: DETAIL_BODY_SCROLL_LIMIT,
|
|
2448
|
-
paneWidth: contentWidth,
|
|
2449
|
-
}) > wideBodyHeight
|
|
2295
|
+
const fullscreenDetailHeaderHeight = getDetailHeaderHeight(selectedPullRequest, contentWidth, isWideLayout)
|
|
2296
|
+
const fullscreenDetailBodyViewportHeight = Math.max(1, wideBodyHeight - fullscreenDetailHeaderHeight)
|
|
2297
|
+
const fullscreenDetailBodyHeight = getScrollableDetailBodyHeight(selectedPullRequest, fullscreenContentWidth)
|
|
2298
|
+
const fullscreenDetailBodyScrollable = fullscreenDetailBodyHeight > fullscreenDetailBodyViewportHeight
|
|
2450
2299
|
const wideDetailHeaderHeight = getDetailHeaderHeight(selectedPullRequest, rightPaneWidth, true)
|
|
2451
2300
|
const wideDetailBodyViewportHeight = Math.max(1, wideBodyHeight - wideDetailHeaderHeight)
|
|
2452
2301
|
const wideDetailBodyHeight = getScrollableDetailBodyHeight(selectedPullRequest, rightContentWidth)
|
|
@@ -2463,6 +2312,7 @@ export const App = () => {
|
|
|
2463
2312
|
loadedCount: loadedPullRequestCount,
|
|
2464
2313
|
hasMore: hasMorePullRequests,
|
|
2465
2314
|
isLoadingMore: isLoadingMorePullRequests,
|
|
2315
|
+
loadingIndicator,
|
|
2466
2316
|
onSelectPullRequest: selectPullRequestByUrl,
|
|
2467
2317
|
} as const
|
|
2468
2318
|
|
|
@@ -2491,8 +2341,8 @@ export const App = () => {
|
|
|
2491
2341
|
const commentThreadModalHeight = commentThreadLayout.height
|
|
2492
2342
|
const commentThreadModalLeft = commentThreadLayout.left
|
|
2493
2343
|
const commentThreadModalTop = commentThreadLayout.top
|
|
2494
|
-
const commentAnchorLabel = selectedDiffCommentAnchor
|
|
2495
|
-
? `${selectedDiffCommentAnchor.path}
|
|
2344
|
+
const commentAnchorLabel = selectedDiffCommentAnchor && selectedDiffCommentLabel
|
|
2345
|
+
? `${selectedDiffCommentAnchor.path} ${selectedDiffCommentLabel}`
|
|
2496
2346
|
: "No diff line selected"
|
|
2497
2347
|
const mergeLayout = sizedModal(46, 68, 12, 16)
|
|
2498
2348
|
const mergeModalWidth = mergeLayout.width
|
|
@@ -2540,8 +2390,8 @@ export const App = () => {
|
|
|
2540
2390
|
loadingIndicator={loadingIndicator}
|
|
2541
2391
|
scrollRef={diffScrollRef}
|
|
2542
2392
|
setDiffRef={setDiffRenderableRef}
|
|
2543
|
-
commentMode={diffCommentMode}
|
|
2544
2393
|
selectedCommentAnchor={selectedDiffCommentAnchor}
|
|
2394
|
+
selectedCommentLabel={selectedDiffCommentLabel}
|
|
2545
2395
|
selectedCommentThread={selectedDiffCommentThread}
|
|
2546
2396
|
onSelectCommentLine={selectDiffCommentLine}
|
|
2547
2397
|
themeId={themeId}
|
|
@@ -2553,20 +2403,16 @@ export const App = () => {
|
|
|
2553
2403
|
</box>
|
|
2554
2404
|
) : isWideLayout && detailFullView ? (
|
|
2555
2405
|
<box flexGrow={1} flexDirection="column">
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
pullRequest={selectedPullRequest}
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
loadingIndicator={loadingIndicator}
|
|
2567
|
-
themeId={themeId}
|
|
2568
|
-
/>
|
|
2569
|
-
</scrollbox>
|
|
2406
|
+
{selectedPullRequest ? (
|
|
2407
|
+
<>
|
|
2408
|
+
<DetailHeader pullRequest={selectedPullRequest} viewerUsername={username} contentWidth={fullscreenContentWidth} paneWidth={contentWidth} showChecks />
|
|
2409
|
+
<scrollbox ref={detailScrollRef} focused flexGrow={1} verticalScrollbarOptions={{ visible: fullscreenDetailBodyScrollable }}>
|
|
2410
|
+
<DetailBody pullRequest={selectedPullRequest} contentWidth={fullscreenContentWidth} bodyLines={fullscreenBodyLines} bodyLineLimit={DETAIL_BODY_SCROLL_LIMIT} loadingIndicator={loadingIndicator} themeId={themeId} />
|
|
2411
|
+
</scrollbox>
|
|
2412
|
+
</>
|
|
2413
|
+
) : (
|
|
2414
|
+
<DetailsPane pullRequest={null} viewerUsername={username} contentWidth={fullscreenContentWidth} paneWidth={contentWidth} placeholderContent={detailPlaceholderContent} loadingIndicator={loadingIndicator} themeId={themeId} />
|
|
2415
|
+
)}
|
|
2570
2416
|
</box>
|
|
2571
2417
|
) : isWideLayout ? (
|
|
2572
2418
|
<box key="wide-main" flexGrow={1} flexDirection="row">
|
|
@@ -2598,19 +2444,16 @@ export const App = () => {
|
|
|
2598
2444
|
</box>
|
|
2599
2445
|
) : detailFullView ? (
|
|
2600
2446
|
<box flexGrow={1} flexDirection="column">
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
pullRequest={selectedPullRequest}
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
themeId={themeId}
|
|
2612
|
-
/>
|
|
2613
|
-
</scrollbox>
|
|
2447
|
+
{selectedPullRequest ? (
|
|
2448
|
+
<>
|
|
2449
|
+
<DetailHeader pullRequest={selectedPullRequest} viewerUsername={username} contentWidth={fullscreenContentWidth} paneWidth={contentWidth} />
|
|
2450
|
+
<scrollbox ref={detailScrollRef} focused flexGrow={1} verticalScrollbarOptions={{ visible: fullscreenDetailBodyScrollable }}>
|
|
2451
|
+
<DetailBody pullRequest={selectedPullRequest} contentWidth={fullscreenContentWidth} bodyLines={fullscreenBodyLines} bodyLineLimit={DETAIL_BODY_SCROLL_LIMIT} loadingIndicator={loadingIndicator} themeId={themeId} />
|
|
2452
|
+
</scrollbox>
|
|
2453
|
+
</>
|
|
2454
|
+
) : (
|
|
2455
|
+
<DetailsPane pullRequest={null} viewerUsername={username} contentWidth={fullscreenContentWidth} paneWidth={contentWidth} placeholderContent={detailPlaceholderContent} loadingIndicator={loadingIndicator} themeId={themeId} />
|
|
2456
|
+
)}
|
|
2614
2457
|
</box>
|
|
2615
2458
|
) : (
|
|
2616
2459
|
<box key="narrow-main" height={wideBodyHeight} flexDirection="column">
|
|
@@ -2640,7 +2483,7 @@ export const App = () => {
|
|
|
2640
2483
|
showFilterClear={filterMode || filterQuery.length > 0}
|
|
2641
2484
|
detailFullView={detailFullView}
|
|
2642
2485
|
diffFullView={diffFullView}
|
|
2643
|
-
|
|
2486
|
+
diffRangeActive={diffCommentRangeActive}
|
|
2644
2487
|
hasSelection={selectedPullRequest !== null}
|
|
2645
2488
|
canCloseSelection={selectedPullRequest?.state === "open"}
|
|
2646
2489
|
hasError={pullRequestStatus === "error"}
|