@kitlangton/ghui 0.1.20 → 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 +17 -6
- package/src/App.tsx +617 -767
- package/src/appCommands.ts +54 -12
- package/src/commands.ts +5 -0
- package/src/domain.ts +2 -0
- package/src/index.tsx +7 -1
- 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 +33 -10
- 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,5 +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 { useAppCommandRegistry } from "./keyboard/useAppCommandRegistry.js"
|
|
4
|
+
import { scrollBindings, useScopedBindings, type ScopedBindingAction } from "./keyboard/useScopedBindings.js"
|
|
3
5
|
import { useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/react"
|
|
4
6
|
import { Cause, Effect, Layer, Schedule } from "effect"
|
|
5
7
|
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
|
|
@@ -8,7 +10,7 @@ import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"
|
|
|
8
10
|
import { useContext, useEffect, useMemo, useRef, useState } from "react"
|
|
9
11
|
import { buildAppCommands } from "./appCommands.js"
|
|
10
12
|
import type { AppCommand } from "./commands.js"
|
|
11
|
-
import { clampCommandIndex, commandEnabled, filterCommands } from "./commands.js"
|
|
13
|
+
import { clampCommandIndex, commandEnabled, defineCommand, filterCommands, sortCommandsByScope } from "./commands.js"
|
|
12
14
|
import { config } from "./config.js"
|
|
13
15
|
import { type CreatePullRequestCommentInput, type DiffCommentSide, type ListPullRequestPageInput, type LoadStatus, type PullRequestItem, type PullRequestLabel, type PullRequestMergeAction, type PullRequestReviewComment } from "./domain.js"
|
|
14
16
|
import { formatShortDate, formatTimestamp } from "./date.js"
|
|
@@ -24,8 +26,8 @@ import { GitHubService } from "./services/GitHubService.js"
|
|
|
24
26
|
import { loadStoredThemeId, saveStoredThemeId } from "./themeStore.js"
|
|
25
27
|
import { colors, filterThemeDefinitions, mixHex, setActiveTheme, themeDefinitions, type ThemeId } from "./ui/colors.js"
|
|
26
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"
|
|
27
|
-
import { buildStackedDiffFiles, diffCommentLocationKey,
|
|
28
|
-
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"
|
|
29
31
|
import { FooterHints, initialRetryProgress, RetryProgress } from "./ui/FooterHints.js"
|
|
30
32
|
import { Divider, fitCell, PlainLine, SeparatorColumn } from "./ui/primitives.js"
|
|
31
33
|
import { CommandPalette } from "./ui/CommandPalette.js"
|
|
@@ -95,6 +97,11 @@ interface AppliedDiffLineColorState {
|
|
|
95
97
|
readonly entries: readonly AppliedDiffLineColor[]
|
|
96
98
|
}
|
|
97
99
|
|
|
100
|
+
interface DiffCommentRangeSelection {
|
|
101
|
+
readonly start: StackedDiffCommentAnchor
|
|
102
|
+
readonly end: StackedDiffCommentAnchor
|
|
103
|
+
}
|
|
104
|
+
|
|
98
105
|
interface DetailHydration {
|
|
99
106
|
readonly token: symbol
|
|
100
107
|
notifyError: boolean
|
|
@@ -108,10 +115,28 @@ const DIFF_STICKY_HEADER_LINES = 2
|
|
|
108
115
|
const LOADING_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const
|
|
109
116
|
const MAX_REPOSITORY_CACHE_ENTRIES = 8
|
|
110
117
|
const LOAD_MORE_SELECTION_THRESHOLD = 8
|
|
118
|
+
const LOAD_MORE_SCROLL_THRESHOLD = 3
|
|
111
119
|
const DETAIL_PREFETCH_BEHIND = 1
|
|
112
120
|
const DETAIL_PREFETCH_AHEAD = 3
|
|
113
121
|
const DETAIL_PREFETCH_CONCURRENCY = 3
|
|
114
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
|
+
}
|
|
115
140
|
|
|
116
141
|
const appendPullRequestPage = (existing: readonly PullRequestItem[], incoming: readonly PullRequestItem[]) => {
|
|
117
142
|
const seen = new Set(existing.map((pullRequest) => pullRequest.url))
|
|
@@ -177,7 +202,6 @@ const noticeAtom = Atom.make<string | null>(null)
|
|
|
177
202
|
const filterQueryAtom = Atom.make("")
|
|
178
203
|
const filterDraftAtom = Atom.make("")
|
|
179
204
|
const filterModeAtom = Atom.make(false)
|
|
180
|
-
const pendingGAtom = Atom.make(false)
|
|
181
205
|
const detailFullViewAtom = Atom.make(false)
|
|
182
206
|
const detailScrollOffsetAtom = Atom.make(0)
|
|
183
207
|
const diffFullViewAtom = Atom.make(false)
|
|
@@ -185,8 +209,8 @@ const diffFileIndexAtom = Atom.make(0)
|
|
|
185
209
|
const diffScrollTopAtom = Atom.make(0)
|
|
186
210
|
const diffRenderViewAtom = Atom.make<DiffView>("split")
|
|
187
211
|
const diffWrapModeAtom = Atom.make<DiffWrapMode>("none")
|
|
188
|
-
const diffCommentModeAtom = Atom.make(false)
|
|
189
212
|
const diffCommentAnchorIndexAtom = Atom.make(0)
|
|
213
|
+
const diffCommentRangeStartIndexAtom = Atom.make<number | null>(null)
|
|
190
214
|
const diffCommentThreadsAtom = Atom.make<Record<string, readonly PullRequestReviewComment[]>>({}).pipe(Atom.keepAlive)
|
|
191
215
|
const diffCommentsLoadedAtom = Atom.make<Record<string, "loading" | "ready">>({}).pipe(Atom.keepAlive)
|
|
192
216
|
const pullRequestDiffCacheAtom = Atom.make<Record<string, PullRequestDiffState>>({}).pipe(Atom.keepAlive)
|
|
@@ -379,12 +403,13 @@ const pullRequestDiffAtomKey = pullRequestRevisionAtomKey
|
|
|
379
403
|
const parsePullRequestDetailAtomKey = (key: string) => parsePullRequestRevisionAtomKey(key, "detail")
|
|
380
404
|
const parsePullRequestDiffAtomKey = (key: string) => parsePullRequestRevisionAtomKey(key, "diff")
|
|
381
405
|
|
|
382
|
-
const isShiftG = (key: { readonly name: string; readonly shift?: boolean }) => key.name === "G" || key.name === "g" && key.shift
|
|
383
406
|
|
|
384
|
-
|
|
407
|
+
|
|
408
|
+
const diffCommentThreadMapKey = (diffKey: string, location: Pick<PullRequestReviewComment, "path" | "side" | "line">) =>
|
|
409
|
+
`${diffKey}:${diffCommentLocationKey(location)}`
|
|
385
410
|
|
|
386
411
|
const diffCommentThreadKey = (pullRequest: PullRequestItem, comment: Pick<PullRequestReviewComment, "path" | "side" | "line">) =>
|
|
387
|
-
|
|
412
|
+
diffCommentThreadMapKey(pullRequestDiffKey(pullRequest), comment)
|
|
388
413
|
|
|
389
414
|
const groupDiffCommentThreads = (pullRequest: PullRequestItem, comments: readonly PullRequestReviewComment[]) => {
|
|
390
415
|
const threads: Record<string, PullRequestReviewComment[]> = {}
|
|
@@ -409,13 +434,43 @@ const originalDiffLineColor = (anchor: DiffCommentAnchor): DiffLineColorConfig =
|
|
|
409
434
|
return { gutter: colors.diff.lineNumberBg, content: colors.diff.contextBg }
|
|
410
435
|
}
|
|
411
436
|
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
+
}
|
|
456
|
+
}
|
|
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 }
|
|
417
464
|
}
|
|
418
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
|
+
|
|
419
474
|
const diffSideTargets = (diff: DiffRenderable, anchor: DiffCommentAnchor, view: DiffView) => {
|
|
420
475
|
const withSides = diff as unknown as DiffRenderableRuntimeSides
|
|
421
476
|
if (view === "split") {
|
|
@@ -486,7 +541,6 @@ export const App = () => {
|
|
|
486
541
|
const [filterQuery, setFilterQuery] = useAtom(filterQueryAtom)
|
|
487
542
|
const [filterDraft, setFilterDraft] = useAtom(filterDraftAtom)
|
|
488
543
|
const [filterMode, setFilterMode] = useAtom(filterModeAtom)
|
|
489
|
-
const [pendingG, setPendingG] = useAtom(pendingGAtom)
|
|
490
544
|
const [detailFullView, setDetailFullView] = useAtom(detailFullViewAtom)
|
|
491
545
|
const setDetailScrollOffset = useAtomSet(detailScrollOffsetAtom)
|
|
492
546
|
const [diffFullView, setDiffFullView] = useAtom(diffFullViewAtom)
|
|
@@ -494,8 +548,8 @@ export const App = () => {
|
|
|
494
548
|
const [diffScrollTop, setDiffScrollTop] = useAtom(diffScrollTopAtom)
|
|
495
549
|
const [diffRenderView, setDiffRenderView] = useAtom(diffRenderViewAtom)
|
|
496
550
|
const [diffWrapMode, setDiffWrapMode] = useAtom(diffWrapModeAtom)
|
|
497
|
-
const [diffCommentMode, setDiffCommentMode] = useAtom(diffCommentModeAtom)
|
|
498
551
|
const [diffCommentAnchorIndex, setDiffCommentAnchorIndex] = useAtom(diffCommentAnchorIndexAtom)
|
|
552
|
+
const [diffCommentRangeStartIndex, setDiffCommentRangeStartIndex] = useAtom(diffCommentRangeStartIndexAtom)
|
|
499
553
|
const [diffCommentThreads, setDiffCommentThreads] = useAtom(diffCommentThreadsAtom)
|
|
500
554
|
const setDiffCommentsLoaded = useAtomSet(diffCommentsLoadedAtom)
|
|
501
555
|
const setPullRequestDiffCache = useAtomSet(pullRequestDiffCacheAtom)
|
|
@@ -577,7 +631,6 @@ export const App = () => {
|
|
|
577
631
|
const wideDetailLines = Math.max(8, terminalHeight - 8)
|
|
578
632
|
const wideBodyHeight = Math.max(8, terminalHeight - 4)
|
|
579
633
|
const noticeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
580
|
-
const pendingGTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
581
634
|
const diffPrefetchTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
582
635
|
const detailPrefetchTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
583
636
|
const detailHydrationRef = useRef(new Map<string, DetailHydration>())
|
|
@@ -618,9 +671,6 @@ export const App = () => {
|
|
|
618
671
|
if (noticeTimeoutRef.current !== null) {
|
|
619
672
|
clearTimeout(noticeTimeoutRef.current)
|
|
620
673
|
}
|
|
621
|
-
if (pendingGTimeoutRef.current !== null) {
|
|
622
|
-
clearTimeout(pendingGTimeoutRef.current)
|
|
623
|
-
}
|
|
624
674
|
if (diffPrefetchTimeoutRef.current !== null) {
|
|
625
675
|
clearTimeout(diffPrefetchTimeoutRef.current)
|
|
626
676
|
}
|
|
@@ -668,14 +718,39 @@ export const App = () => {
|
|
|
668
718
|
() => diffFullView ? getStackedDiffCommentAnchors(stackedDiffFiles, effectiveDiffRenderView, diffWrapMode, contentWidth) : [],
|
|
669
719
|
[diffFullView, stackedDiffFiles, effectiveDiffRenderView, diffWrapMode, contentWidth],
|
|
670
720
|
)
|
|
671
|
-
const
|
|
672
|
-
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
|
|
673
741
|
const selectedDiffCommentThread = selectedDiffCommentThreadKey ? diffCommentThreads[selectedDiffCommentThreadKey] ?? [] : []
|
|
674
742
|
const diffLineColorContextKey = selectedDiffKey ? `${selectedDiffKey}:${effectiveDiffRenderView}:${diffWrapMode}` : null
|
|
675
|
-
const
|
|
676
|
-
()
|
|
677
|
-
|
|
678
|
-
|
|
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])
|
|
679
754
|
const groupStarts = useAtomValue(groupStartsAtom)
|
|
680
755
|
const getCurrentGroupIndex = (current: number) => {
|
|
681
756
|
if (groupStarts.length === 0) return 0
|
|
@@ -734,7 +809,7 @@ export const App = () => {
|
|
|
734
809
|
setLoadingMoreKey(null)
|
|
735
810
|
setDetailFullView(false)
|
|
736
811
|
setDiffFullView(false)
|
|
737
|
-
|
|
812
|
+
setDiffCommentRangeStartIndex(null)
|
|
738
813
|
setFilterDraft(filterQuery)
|
|
739
814
|
setNotice(null)
|
|
740
815
|
setRefreshCompletionMessage(null)
|
|
@@ -904,6 +979,20 @@ export const App = () => {
|
|
|
904
979
|
if (selectedIndex >= thresholdIndex) loadMorePullRequests()
|
|
905
980
|
}, [selectedIndex, visiblePullRequests.length, filterMode, filterQuery, hasMorePullRequests, isLoadingMorePullRequests, currentQueueCacheKey])
|
|
906
981
|
|
|
982
|
+
useEffect(() => {
|
|
983
|
+
if (filterMode || filterQuery.length > 0 || visiblePullRequests.length === 0 || detailFullView || diffFullView) return
|
|
984
|
+
if (!hasMorePullRequests || isLoadingMorePullRequests) return
|
|
985
|
+
const checkScroll = () => {
|
|
986
|
+
const scroll = prListScrollRef.current
|
|
987
|
+
if (!scroll || scroll.viewport.height <= 0) return
|
|
988
|
+
const bottom = scroll.scrollTop + scroll.viewport.height
|
|
989
|
+
if (bottom >= scroll.scrollHeight - LOAD_MORE_SCROLL_THRESHOLD) loadMorePullRequests()
|
|
990
|
+
}
|
|
991
|
+
checkScroll()
|
|
992
|
+
const interval = globalThis.setInterval(checkScroll, 120)
|
|
993
|
+
return () => globalThis.clearInterval(interval)
|
|
994
|
+
}, [visiblePullRequests.length, filterMode, filterQuery, detailFullView, diffFullView, hasMorePullRequests, isLoadingMorePullRequests, currentQueueCacheKey])
|
|
995
|
+
|
|
907
996
|
useEffect(() => {
|
|
908
997
|
const scroll = prListScrollRef.current
|
|
909
998
|
if (!scroll || selectedPullRequestRowIndex === null) return
|
|
@@ -917,6 +1006,7 @@ export const App = () => {
|
|
|
917
1006
|
setDiffFileIndex(0)
|
|
918
1007
|
setDiffScrollTop(0)
|
|
919
1008
|
setDiffCommentAnchorIndex(0)
|
|
1009
|
+
setDiffCommentRangeStartIndex(null)
|
|
920
1010
|
detailPreviewScrollRef.current?.scrollTo({ x: 0, y: 0 })
|
|
921
1011
|
}, [selectedIndex])
|
|
922
1012
|
|
|
@@ -925,12 +1015,16 @@ export const App = () => {
|
|
|
925
1015
|
if (diffCommentAnchors.length === 0) return 0
|
|
926
1016
|
return Math.max(0, Math.min(current, diffCommentAnchors.length - 1))
|
|
927
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
|
+
})
|
|
928
1022
|
}, [diffCommentAnchors.length])
|
|
929
1023
|
|
|
930
1024
|
useEffect(() => {
|
|
931
|
-
if (!
|
|
1025
|
+
if (!diffFullView || !selectedDiffCommentAnchor) return
|
|
932
1026
|
setDiffFileIndex((current) => current === selectedDiffCommentAnchor.fileIndex ? current : selectedDiffCommentAnchor.fileIndex)
|
|
933
|
-
}, [
|
|
1027
|
+
}, [diffFullView, selectedDiffCommentAnchor?.fileIndex])
|
|
934
1028
|
|
|
935
1029
|
useEffect(() => {
|
|
936
1030
|
const previous = diffCommentLineColorsRef.current
|
|
@@ -943,27 +1037,28 @@ export const App = () => {
|
|
|
943
1037
|
|
|
944
1038
|
const nextEntries: AppliedDiffLineColor[] = []
|
|
945
1039
|
const appliedKeys = new Set<string>()
|
|
946
|
-
const applyLineColor = (anchor: StackedDiffCommentAnchor,
|
|
1040
|
+
const applyLineColor = (anchor: StackedDiffCommentAnchor, color: DiffLineColorConfig, override = false) => {
|
|
947
1041
|
const key = `${effectiveDiffRenderView}:${anchor.side}:${anchor.renderLine}`
|
|
948
1042
|
if (appliedKeys.has(key) && !override) return
|
|
949
1043
|
appliedKeys.add(key)
|
|
950
1044
|
const entry = { anchor, view: effectiveDiffRenderView } satisfies AppliedDiffLineColor
|
|
951
1045
|
const diff = diffRenderableRefs.current.get(anchor.fileIndex)
|
|
952
|
-
if (diff) setDiffCommentLineColor(diff, entry,
|
|
1046
|
+
if (diff) setDiffCommentLineColor(diff, entry, color)
|
|
953
1047
|
if (!nextEntries.some((existing) => existing.view === entry.view && existing.anchor.side === anchor.side && existing.anchor.renderLine === anchor.renderLine)) {
|
|
954
1048
|
nextEntries.push(entry)
|
|
955
1049
|
}
|
|
956
1050
|
}
|
|
957
1051
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
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)
|
|
963
1058
|
}
|
|
964
1059
|
}
|
|
965
|
-
if (
|
|
966
|
-
applyLineColor(selectedDiffCommentAnchor,
|
|
1060
|
+
if (selectedDiffCommentAnchor) {
|
|
1061
|
+
applyLineColor(selectedDiffCommentAnchor, diffCommentLineColor(selectedDiffCommentAnchor, "selected"), true)
|
|
967
1062
|
if (suppressNextDiffCommentScrollRef.current) {
|
|
968
1063
|
suppressNextDiffCommentScrollRef.current = false
|
|
969
1064
|
} else {
|
|
@@ -973,10 +1068,10 @@ export const App = () => {
|
|
|
973
1068
|
suppressNextDiffCommentScrollRef.current = false
|
|
974
1069
|
}
|
|
975
1070
|
diffCommentLineColorsRef.current = { contextKey: diffLineColorContextKey, entries: nextEntries }
|
|
976
|
-
}, [
|
|
1071
|
+
}, [selectedDiffCommentAnchor?.renderLine, selectedDiffCommentAnchor?.localRenderLine, selectedDiffCommentAnchor?.side, selectedDiffCommentAnchor?.fileIndex, selectedDiffCommentRangeAnchors, diffLineColorContextKey, effectiveDiffRenderView, diffCommentThreadAnchors])
|
|
977
1072
|
const isHydratingPullRequestDetails = pullRequestStatus === "ready" && selectedPullRequest?.state === "open" && !selectedPullRequest.detailLoaded
|
|
978
1073
|
const isRefreshingPullRequests = pullRequestResult.waiting && pullRequestLoad !== null
|
|
979
|
-
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"
|
|
980
1075
|
const loadingIndicator = LOADING_FRAMES[loadingFrame % LOADING_FRAMES.length]!
|
|
981
1076
|
|
|
982
1077
|
useEffect(() => {
|
|
@@ -1119,9 +1214,10 @@ export const App = () => {
|
|
|
1119
1214
|
diffCommentLineColorsRef.current = { contextKey: null, entries: [] }
|
|
1120
1215
|
setDiffFullView(true)
|
|
1121
1216
|
setDetailFullView(false)
|
|
1122
|
-
setDiffCommentMode(false)
|
|
1123
1217
|
setDiffFileIndex(0)
|
|
1124
1218
|
setDiffScrollTop(0)
|
|
1219
|
+
setDiffCommentAnchorIndex(0)
|
|
1220
|
+
setDiffCommentRangeStartIndex(null)
|
|
1125
1221
|
setDiffRenderView(contentWidth >= 100 ? "split" : "unified")
|
|
1126
1222
|
diffScrollRef.current?.scrollTo({ x: 0, y: 0 })
|
|
1127
1223
|
loadPullRequestDiff(selectedPullRequest, { includeComments: true })
|
|
@@ -1146,49 +1242,9 @@ export const App = () => {
|
|
|
1146
1242
|
setDiffFileIndex((current) => current === nextIndex ? current : nextIndex)
|
|
1147
1243
|
}
|
|
1148
1244
|
|
|
1149
|
-
const scrollDiffBy = (y: number) => {
|
|
1150
|
-
diffScrollRef.current?.scrollBy({ x: 0, y })
|
|
1151
|
-
syncDiffScrollState()
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
const scrollDiffTo = (y: number) => {
|
|
1155
|
-
diffScrollRef.current?.scrollTo({ x: 0, y })
|
|
1156
|
-
syncDiffScrollState()
|
|
1157
|
-
}
|
|
1158
1245
|
const scrollDetailPreviewBy = (y: number) => detailPreviewScrollRef.current?.scrollBy({ x: 0, y })
|
|
1159
1246
|
const scrollDetailPreviewTo = (y: number) => detailPreviewScrollRef.current?.scrollTo({ x: 0, y })
|
|
1160
1247
|
|
|
1161
|
-
const clearPendingGTimeout = () => {
|
|
1162
|
-
if (pendingGTimeoutRef.current !== null) {
|
|
1163
|
-
clearTimeout(pendingGTimeoutRef.current)
|
|
1164
|
-
pendingGTimeoutRef.current = null
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
const handleVimGoto = (key: { readonly name: string; readonly shift?: boolean }, gotoStart: () => void, gotoEnd: () => void): boolean => {
|
|
1169
|
-
if (isShiftG(key)) {
|
|
1170
|
-
gotoEnd()
|
|
1171
|
-
setPendingG(false)
|
|
1172
|
-
clearPendingGTimeout()
|
|
1173
|
-
return true
|
|
1174
|
-
}
|
|
1175
|
-
if (key.name === "g") {
|
|
1176
|
-
if (pendingG) {
|
|
1177
|
-
gotoStart()
|
|
1178
|
-
setPendingG(false)
|
|
1179
|
-
clearPendingGTimeout()
|
|
1180
|
-
} else {
|
|
1181
|
-
setPendingG(true)
|
|
1182
|
-
pendingGTimeoutRef.current = setTimeout(() => {
|
|
1183
|
-
setPendingG(false)
|
|
1184
|
-
pendingGTimeoutRef.current = null
|
|
1185
|
-
}, 500)
|
|
1186
|
-
}
|
|
1187
|
-
return true
|
|
1188
|
-
}
|
|
1189
|
-
return false
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
1248
|
const ensureDiffLineVisible = (line: number) => {
|
|
1193
1249
|
const scroll = diffScrollRef.current
|
|
1194
1250
|
if (!scroll) return
|
|
@@ -1210,37 +1266,72 @@ export const App = () => {
|
|
|
1210
1266
|
if (readyDiffFiles.length === 0) return
|
|
1211
1267
|
const nextIndex = safeDiffFileIndex(readyDiffFiles, diffFileIndex + delta)
|
|
1212
1268
|
setDiffFileIndex(nextIndex)
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
}
|
|
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))
|
|
1218
1273
|
scrollToDiffFile(nextIndex)
|
|
1219
1274
|
}
|
|
1220
1275
|
|
|
1221
|
-
const
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
setDiffCommentAnchorIndex(nearestDiffCommentAnchorIndex(diffCommentAnchors, scrollTop + DIFF_STICKY_HEADER_LINES))
|
|
1225
|
-
setDiffCommentMode(true)
|
|
1226
|
-
}
|
|
1276
|
+
const navigableDiffCommentAnchors = () => diffCommentRangeStartAnchor
|
|
1277
|
+
? diffCommentAnchors.filter((anchor) => sameDiffCommentTarget(anchor, diffCommentRangeStartAnchor))
|
|
1278
|
+
: diffCommentAnchors
|
|
1227
1279
|
|
|
1228
|
-
const moveDiffCommentAnchor = (delta: number) => {
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
const
|
|
1232
|
-
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))]
|
|
1233
1287
|
if (nextRow === undefined) return
|
|
1234
|
-
const nextAnchor =
|
|
1235
|
-
??
|
|
1288
|
+
const nextAnchor = anchors.find((anchor) => anchor.renderLine === nextRow && anchor.side === currentAnchor?.side)
|
|
1289
|
+
?? anchors.find((anchor) => anchor.renderLine === nextRow)
|
|
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
|
+
}
|
|
1303
|
+
setDiffCommentAnchorIndex(diffCommentAnchors.indexOf(nextAnchor))
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
const moveDiffCommentToBoundary = (boundary: "first" | "last") => {
|
|
1307
|
+
const anchors = navigableDiffCommentAnchors()
|
|
1308
|
+
const nextAnchor = boundary === "first" ? anchors[0] : anchors[anchors.length - 1]
|
|
1236
1309
|
if (!nextAnchor) return
|
|
1237
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()
|
|
1238
1328
|
}
|
|
1239
1329
|
|
|
1240
1330
|
const selectDiffCommentSide = (side: DiffCommentSide) => {
|
|
1241
1331
|
if (!selectedDiffCommentAnchor) return
|
|
1242
1332
|
const nextAnchor = diffCommentAnchors.find((anchor) => anchor.renderLine === selectedDiffCommentAnchor.renderLine && anchor.side === side)
|
|
1243
1333
|
if (!nextAnchor) return
|
|
1334
|
+
setDiffCommentRangeStartIndex(null)
|
|
1244
1335
|
setDiffCommentAnchorIndex(diffCommentAnchors.indexOf(nextAnchor))
|
|
1245
1336
|
}
|
|
1246
1337
|
|
|
@@ -1249,9 +1340,11 @@ export const App = () => {
|
|
|
1249
1340
|
const nextAnchor = (side ? lineAnchors.find((anchor) => anchor.side === side) : undefined) ?? lineAnchors[0]
|
|
1250
1341
|
if (!nextAnchor) return
|
|
1251
1342
|
suppressNextDiffCommentScrollRef.current = true
|
|
1343
|
+
if (diffCommentRangeStartAnchor && !sameDiffCommentTarget(diffCommentRangeStartAnchor, nextAnchor)) {
|
|
1344
|
+
setDiffCommentRangeStartIndex(null)
|
|
1345
|
+
}
|
|
1252
1346
|
setDiffCommentAnchorIndex(diffCommentAnchors.indexOf(nextAnchor))
|
|
1253
1347
|
setDiffFileIndex(nextAnchor.fileIndex)
|
|
1254
|
-
setDiffCommentMode(true)
|
|
1255
1348
|
}
|
|
1256
1349
|
|
|
1257
1350
|
const editComment = (transform: (state: CommentEditorValue) => CommentEditorValue) => {
|
|
@@ -1272,6 +1365,39 @@ export const App = () => {
|
|
|
1272
1365
|
setCommentThreadModal({ scrollOffset: 0 })
|
|
1273
1366
|
}
|
|
1274
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
|
+
|
|
1275
1401
|
const submitDiffComment = () => {
|
|
1276
1402
|
if (!selectedPullRequest || !selectedDiffCommentAnchor) return
|
|
1277
1403
|
const body = commentModal.body.trim()
|
|
@@ -1280,8 +1406,9 @@ export const App = () => {
|
|
|
1280
1406
|
return
|
|
1281
1407
|
}
|
|
1282
1408
|
|
|
1283
|
-
const
|
|
1284
|
-
const target = selectedDiffCommentAnchor
|
|
1409
|
+
const targetRange = selectedDiffCommentRange
|
|
1410
|
+
const target = targetRange?.end ?? selectedDiffCommentAnchor
|
|
1411
|
+
const threadKey = selectedDiffKey ? diffCommentThreadMapKey(selectedDiffKey, target) : null
|
|
1285
1412
|
const optimisticComment = {
|
|
1286
1413
|
id: `local:${Date.now()}`,
|
|
1287
1414
|
path: target.path,
|
|
@@ -1292,6 +1419,9 @@ export const App = () => {
|
|
|
1292
1419
|
createdAt: new Date(),
|
|
1293
1420
|
url: null,
|
|
1294
1421
|
} satisfies PullRequestReviewComment
|
|
1422
|
+
const rangeInput = targetRange && targetRange.start.line !== targetRange.end.line
|
|
1423
|
+
? { startLine: targetRange.start.line, startSide: targetRange.start.side }
|
|
1424
|
+
: {}
|
|
1295
1425
|
const input = {
|
|
1296
1426
|
repository: selectedPullRequest.repository,
|
|
1297
1427
|
number: selectedPullRequest.number,
|
|
@@ -1300,6 +1430,7 @@ export const App = () => {
|
|
|
1300
1430
|
line: target.line,
|
|
1301
1431
|
side: target.side,
|
|
1302
1432
|
body,
|
|
1433
|
+
...rangeInput,
|
|
1303
1434
|
} satisfies CreatePullRequestCommentInput
|
|
1304
1435
|
|
|
1305
1436
|
if (threadKey) {
|
|
@@ -1309,6 +1440,7 @@ export const App = () => {
|
|
|
1309
1440
|
}))
|
|
1310
1441
|
}
|
|
1311
1442
|
closeActiveModal()
|
|
1443
|
+
setDiffCommentRangeStartIndex(null)
|
|
1312
1444
|
flashNotice(`Commenting on ${target.path}:${target.line}`)
|
|
1313
1445
|
void createPullRequestComment(input).then((comment) => {
|
|
1314
1446
|
if (threadKey) {
|
|
@@ -1671,8 +1803,10 @@ export const App = () => {
|
|
|
1671
1803
|
diffWrapMode,
|
|
1672
1804
|
readyDiffFileCount: readyDiffFiles.length,
|
|
1673
1805
|
diffFileIndex,
|
|
1674
|
-
|
|
1675
|
-
selectedDiffCommentAnchorLabel:
|
|
1806
|
+
diffRangeActive: diffCommentRangeActive,
|
|
1807
|
+
selectedDiffCommentAnchorLabel: selectedDiffCommentLabel,
|
|
1808
|
+
selectedDiffCommentThreadCount: selectedDiffCommentThread.length,
|
|
1809
|
+
hasDiffCommentThreads: diffCommentThreadAnchors.length > 0,
|
|
1676
1810
|
actions: {
|
|
1677
1811
|
openCommandPalette,
|
|
1678
1812
|
refreshPullRequests,
|
|
@@ -1700,7 +1834,7 @@ export const App = () => {
|
|
|
1700
1834
|
openDiffView,
|
|
1701
1835
|
closeDiffView: () => {
|
|
1702
1836
|
setDiffFullView(false)
|
|
1703
|
-
|
|
1837
|
+
setDiffCommentRangeStartIndex(null)
|
|
1704
1838
|
},
|
|
1705
1839
|
reloadDiff: () => {
|
|
1706
1840
|
if (!selectedPullRequest) return
|
|
@@ -1710,10 +1844,9 @@ export const App = () => {
|
|
|
1710
1844
|
toggleDiffRenderView: () => setDiffRenderView((current) => current === "unified" ? "split" : "unified"),
|
|
1711
1845
|
toggleDiffWrapMode: () => setDiffWrapMode((current) => current === "none" ? "word" : "none"),
|
|
1712
1846
|
jumpDiffFile,
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
},
|
|
1847
|
+
openSelectedDiffComment,
|
|
1848
|
+
toggleDiffCommentRange,
|
|
1849
|
+
moveDiffCommentThread,
|
|
1717
1850
|
openDiffCommentModal,
|
|
1718
1851
|
togglePullRequestDraftStatus: toggleSelectedPullRequestDraftStatus,
|
|
1719
1852
|
openLabelModal,
|
|
@@ -1739,69 +1872,377 @@ export const App = () => {
|
|
|
1739
1872
|
const command = appCommands.find((entry) => entry.id === id)
|
|
1740
1873
|
return command ? runCommand(command, options) : false
|
|
1741
1874
|
}
|
|
1742
|
-
const
|
|
1875
|
+
const runCommandByIdRef = useRef(runCommandById)
|
|
1876
|
+
runCommandByIdRef.current = runCommandById
|
|
1877
|
+
useAppCommandRegistry(appCommands, runCommandByIdRef)
|
|
1878
|
+
const dynamicPaletteCommands: readonly AppCommand[] = (() => {
|
|
1879
|
+
if (!commandPaletteActive) return []
|
|
1880
|
+
const repository = parseRepositoryInput(commandPalette.query)
|
|
1881
|
+
if (!repository || repository === selectedRepository) return []
|
|
1882
|
+
return [defineCommand({
|
|
1883
|
+
id: `view.repository.dynamic:${repository}`,
|
|
1884
|
+
title: `Open ${repository}`,
|
|
1885
|
+
scope: "View",
|
|
1886
|
+
subtitle: "Switch to this repository",
|
|
1887
|
+
run: () => switchViewTo({ _tag: "Repository", repository }),
|
|
1888
|
+
})]
|
|
1889
|
+
})()
|
|
1890
|
+
// Dynamic commands always pin to the top of the palette; they came directly from the
|
|
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
|
+
: []
|
|
1895
|
+
const commandPaletteCommands = commandPaletteActive
|
|
1896
|
+
? [
|
|
1897
|
+
...dynamicPaletteCommands,
|
|
1898
|
+
...(commandPalette.query.trim().length > 0 ? staticPaletteCommands : sortCommandsByScope(staticPaletteCommands)),
|
|
1899
|
+
]
|
|
1900
|
+
: []
|
|
1743
1901
|
const selectedCommandIndex = clampCommandIndex(commandPalette.selectedIndex, commandPaletteCommands)
|
|
1744
1902
|
const selectedCommand = commandPaletteCommands[selectedCommandIndex] ?? null
|
|
1745
1903
|
|
|
1904
|
+
const noModalActive = activeModal._tag === "None"
|
|
1905
|
+
const globalLayerActive = noModalActive
|
|
1906
|
+
&& !diffFullView
|
|
1907
|
+
&& !detailFullView
|
|
1908
|
+
&& !filterMode
|
|
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)
|
|
1927
|
+
return { ...current, selectedIndex: Math.max(0, Math.min(max, current.selectedIndex + delta)) }
|
|
1928
|
+
})
|
|
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) => ({
|
|
1944
|
+
...current,
|
|
1945
|
+
scrollOffset: Math.max(0, current.scrollOffset + delta),
|
|
1946
|
+
}))
|
|
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)
|
|
1968
|
+
return { ...current, selectedIndex: Math.max(0, Math.min(max, current.selectedIndex + delta)) }
|
|
1969
|
+
})
|
|
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
|
+
},
|
|
1980
|
+
})
|
|
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
|
+
},
|
|
1999
|
+
})
|
|
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)
|
|
2043
|
+
return selectedIndex === current.selectedIndex ? current : { ...current, selectedIndex }
|
|
2044
|
+
})
|
|
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
|
+
})
|
|
2223
|
+
|
|
1746
2224
|
useKeyboard((key) => {
|
|
1747
2225
|
if (commandPaletteActive) {
|
|
1748
|
-
if (key.name === "escape" || key.ctrl && key.name === "c") {
|
|
1749
|
-
closeActiveModal()
|
|
1750
|
-
return
|
|
1751
|
-
}
|
|
1752
|
-
if (key.name === "return" || key.name === "enter") {
|
|
1753
|
-
if (selectedCommand) runCommand(selectedCommand, { notifyDisabled: true, closePalette: true })
|
|
1754
|
-
return
|
|
1755
|
-
}
|
|
1756
|
-
if (key.name === "up" || key.name === "k" && !key.ctrl && !key.meta) {
|
|
1757
|
-
setCommandPalette((current) => {
|
|
1758
|
-
const selectedIndex = clampCommandIndex(current.selectedIndex - 1, commandPaletteCommands)
|
|
1759
|
-
return selectedIndex === current.selectedIndex ? current : { ...current, selectedIndex }
|
|
1760
|
-
})
|
|
1761
|
-
return
|
|
1762
|
-
}
|
|
1763
|
-
if (key.name === "down" || key.name === "j" && !key.ctrl && !key.meta) {
|
|
1764
|
-
setCommandPalette((current) => {
|
|
1765
|
-
const selectedIndex = clampCommandIndex(current.selectedIndex + 1, commandPaletteCommands)
|
|
1766
|
-
return selectedIndex === current.selectedIndex ? current : { ...current, selectedIndex }
|
|
1767
|
-
})
|
|
1768
|
-
return
|
|
1769
|
-
}
|
|
1770
2226
|
if (isSingleLineInputKey(key)) {
|
|
1771
2227
|
setCommandPalette((current) => {
|
|
1772
2228
|
const query = editSingleLineInput(current.query, key) ?? current.query
|
|
1773
2229
|
return current.query === query && current.selectedIndex === 0 ? current : { ...current, query, selectedIndex: 0 }
|
|
1774
2230
|
})
|
|
1775
|
-
return
|
|
1776
2231
|
}
|
|
1777
2232
|
return
|
|
1778
2233
|
}
|
|
1779
2234
|
|
|
1780
2235
|
if (openRepositoryModalActive) {
|
|
1781
|
-
if (key.name === "escape" || key.ctrl && key.name === "c") {
|
|
1782
|
-
closeActiveModal()
|
|
1783
|
-
return
|
|
1784
|
-
}
|
|
1785
|
-
if (key.name === "return" || key.name === "enter") {
|
|
1786
|
-
openRepositoryFromInput()
|
|
1787
|
-
return
|
|
1788
|
-
}
|
|
1789
2236
|
if (isSingleLineInputKey(key)) {
|
|
1790
2237
|
setOpenRepositoryModal((current) => ({
|
|
1791
2238
|
...current,
|
|
1792
2239
|
query: editSingleLineInput(current.query, key) ?? current.query,
|
|
1793
2240
|
error: null,
|
|
1794
2241
|
}))
|
|
1795
|
-
return
|
|
1796
2242
|
}
|
|
1797
2243
|
return
|
|
1798
2244
|
}
|
|
1799
2245
|
|
|
1800
|
-
if ((key.ctrl && key.name === "p") || (key.meta && key.name === "k")) {
|
|
1801
|
-
runCommandById("command.open")
|
|
1802
|
-
return
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
2246
|
if ((key.name === "q" && !commentModalActive && !(themeModalActive && themeModal.filterMode)) || (key.ctrl && key.name === "c")) {
|
|
1806
2247
|
if (themeModalActive) {
|
|
1807
2248
|
closeThemeModal(false)
|
|
@@ -1816,630 +2257,45 @@ export const App = () => {
|
|
|
1816
2257
|
}
|
|
1817
2258
|
|
|
1818
2259
|
if (themeModalActive) {
|
|
1819
|
-
if (key.name === "escape") {
|
|
1820
|
-
if (themeModal.filterMode) {
|
|
1821
|
-
updateThemeQuery("", { filterMode: false })
|
|
1822
|
-
return
|
|
1823
|
-
}
|
|
1824
|
-
closeThemeModal(false)
|
|
1825
|
-
return
|
|
1826
|
-
}
|
|
1827
|
-
if (key.name === "/") {
|
|
1828
|
-
updateThemeQuery("", { filterMode: true })
|
|
1829
|
-
return
|
|
1830
|
-
}
|
|
1831
|
-
if (key.name === "return" || key.name === "enter") {
|
|
1832
|
-
if (themeModal.filterMode && filterThemeDefinitions(themeModal.query).length === 0) return
|
|
1833
|
-
closeThemeModal(true)
|
|
1834
|
-
return
|
|
1835
|
-
}
|
|
1836
|
-
if (key.name === "up" || (!themeModal.filterMode && key.name === "k")) {
|
|
1837
|
-
moveThemeSelection(-1)
|
|
1838
|
-
return
|
|
1839
|
-
}
|
|
1840
|
-
if (key.name === "down" || (!themeModal.filterMode && key.name === "j")) {
|
|
1841
|
-
moveThemeSelection(1)
|
|
1842
|
-
return
|
|
1843
|
-
}
|
|
1844
2260
|
if (themeModal.filterMode && isSingleLineInputKey(key)) {
|
|
1845
2261
|
editThemeQuery((query) => editSingleLineInput(query, key) ?? query)
|
|
1846
|
-
return
|
|
1847
2262
|
}
|
|
1848
2263
|
return
|
|
1849
2264
|
}
|
|
1850
2265
|
|
|
1851
2266
|
if (commentModalActive) {
|
|
1852
|
-
if (key.name === "escape") {
|
|
1853
|
-
closeActiveModal()
|
|
1854
|
-
return
|
|
1855
|
-
}
|
|
1856
|
-
if (key.ctrl && key.name === "s") {
|
|
1857
|
-
submitDiffComment()
|
|
1858
|
-
return
|
|
1859
|
-
}
|
|
1860
|
-
if (key.ctrl && key.name === "a") {
|
|
1861
|
-
editComment(moveLineStart)
|
|
1862
|
-
return
|
|
1863
|
-
}
|
|
1864
|
-
if (key.ctrl && key.name === "e") {
|
|
1865
|
-
editComment(moveLineEnd)
|
|
1866
|
-
return
|
|
1867
|
-
}
|
|
1868
|
-
if (key.ctrl && key.name === "b") {
|
|
1869
|
-
editComment(editorMoveLeft)
|
|
1870
|
-
return
|
|
1871
|
-
}
|
|
1872
|
-
if (key.ctrl && key.name === "f") {
|
|
1873
|
-
editComment(editorMoveRight)
|
|
1874
|
-
return
|
|
1875
|
-
}
|
|
1876
|
-
if (key.ctrl && key.name === "w") {
|
|
1877
|
-
editComment(deleteWordBackward)
|
|
1878
|
-
return
|
|
1879
|
-
}
|
|
1880
|
-
if (key.ctrl && key.name === "u") {
|
|
1881
|
-
editComment(deleteToLineStart)
|
|
1882
|
-
return
|
|
1883
|
-
}
|
|
1884
|
-
if (key.ctrl && key.name === "k") {
|
|
1885
|
-
editComment(deleteToLineEnd)
|
|
1886
|
-
return
|
|
1887
|
-
}
|
|
1888
|
-
if (key.ctrl && key.name === "d") {
|
|
1889
|
-
editComment(editorDeleteForward)
|
|
1890
|
-
return
|
|
1891
|
-
}
|
|
1892
|
-
if ((key.meta || key.option) && (key.name === "b" || key.name === "left")) {
|
|
1893
|
-
editComment(moveWordBackward)
|
|
1894
|
-
return
|
|
1895
|
-
}
|
|
1896
|
-
if ((key.meta || key.option) && (key.name === "f" || key.name === "right")) {
|
|
1897
|
-
editComment(moveWordForward)
|
|
1898
|
-
return
|
|
1899
|
-
}
|
|
1900
|
-
if ((key.meta || key.option) && (key.name === "backspace" || key.name === "delete")) {
|
|
1901
|
-
editComment(key.name === "delete" ? deleteWordForward : deleteWordBackward)
|
|
1902
|
-
return
|
|
1903
|
-
}
|
|
1904
|
-
if (key.name === "backspace") {
|
|
1905
|
-
editComment(editorBackspace)
|
|
1906
|
-
return
|
|
1907
|
-
}
|
|
1908
|
-
if (key.name === "delete") {
|
|
1909
|
-
editComment(editorDeleteForward)
|
|
1910
|
-
return
|
|
1911
|
-
}
|
|
1912
|
-
if (key.name === "left") {
|
|
1913
|
-
editComment(editorMoveLeft)
|
|
1914
|
-
return
|
|
1915
|
-
}
|
|
1916
|
-
if (key.name === "right") {
|
|
1917
|
-
editComment(editorMoveRight)
|
|
1918
|
-
return
|
|
1919
|
-
}
|
|
1920
|
-
if (key.name === "up") {
|
|
1921
|
-
editComment((state) => moveVertically(state, -1))
|
|
1922
|
-
return
|
|
1923
|
-
}
|
|
1924
|
-
if (key.name === "down") {
|
|
1925
|
-
editComment((state) => moveVertically(state, 1))
|
|
1926
|
-
return
|
|
1927
|
-
}
|
|
1928
|
-
if (key.name === "home") {
|
|
1929
|
-
editComment(moveLineStart)
|
|
1930
|
-
return
|
|
1931
|
-
}
|
|
1932
|
-
if (key.name === "end") {
|
|
1933
|
-
editComment(moveLineEnd)
|
|
1934
|
-
return
|
|
1935
|
-
}
|
|
1936
|
-
if ((key.name === "return" || key.name === "enter") && key.shift) {
|
|
1937
|
-
editComment((state) => insertText(state, "\n"))
|
|
1938
|
-
return
|
|
1939
|
-
}
|
|
1940
|
-
if (key.name === "return" || key.name === "enter") {
|
|
1941
|
-
submitDiffComment()
|
|
1942
|
-
return
|
|
1943
|
-
}
|
|
1944
2267
|
const text = printableKeyText(key)
|
|
1945
|
-
if (text)
|
|
1946
|
-
editComment((state) => insertText(state, text))
|
|
1947
|
-
return
|
|
1948
|
-
}
|
|
2268
|
+
if (text) editComment((state) => insertText(state, text))
|
|
1949
2269
|
return
|
|
1950
2270
|
}
|
|
1951
2271
|
|
|
1952
|
-
if (commentThreadModalActive) {
|
|
1953
|
-
if (key.name === "escape") {
|
|
1954
|
-
closeActiveModal()
|
|
1955
|
-
return
|
|
1956
|
-
}
|
|
1957
|
-
if (key.name === "return" || key.name === "enter" || key.name === "a" || key.name === "c") {
|
|
1958
|
-
openDiffCommentModal()
|
|
1959
|
-
return
|
|
1960
|
-
}
|
|
1961
|
-
if (key.name === "up" || key.name === "k") {
|
|
1962
|
-
setCommentThreadModal((current) => ({ ...current, scrollOffset: Math.max(0, current.scrollOffset - 1) }))
|
|
1963
|
-
return
|
|
1964
|
-
}
|
|
1965
|
-
if (key.name === "down" || key.name === "j") {
|
|
1966
|
-
setCommentThreadModal((current) => ({ ...current, scrollOffset: current.scrollOffset + 1 }))
|
|
1967
|
-
return
|
|
1968
|
-
}
|
|
1969
|
-
if (key.name === "pageup" || key.ctrl && key.name === "u") {
|
|
1970
|
-
setCommentThreadModal((current) => ({ ...current, scrollOffset: Math.max(0, current.scrollOffset - halfPage) }))
|
|
1971
|
-
return
|
|
1972
|
-
}
|
|
1973
|
-
if (key.name === "pagedown" || key.ctrl && (key.name === "d" || key.name === "v")) {
|
|
1974
|
-
setCommentThreadModal((current) => ({ ...current, scrollOffset: current.scrollOffset + halfPage }))
|
|
1975
|
-
return
|
|
1976
|
-
}
|
|
1977
|
-
return
|
|
1978
|
-
}
|
|
1979
2272
|
|
|
1980
|
-
if (closeModalActive) {
|
|
1981
|
-
if (key.name === "escape") {
|
|
1982
|
-
closeActiveModal()
|
|
1983
|
-
return
|
|
1984
|
-
}
|
|
1985
|
-
if (key.name === "return" || key.name === "enter") {
|
|
1986
|
-
confirmClosePullRequest()
|
|
1987
|
-
return
|
|
1988
|
-
}
|
|
1989
|
-
return
|
|
1990
|
-
}
|
|
1991
2273
|
|
|
1992
|
-
if (mergeModalActive) {
|
|
1993
|
-
const options = availableMergeActions(mergeModal.info)
|
|
1994
|
-
if (key.name === "escape") {
|
|
1995
|
-
closeActiveModal()
|
|
1996
|
-
return
|
|
1997
|
-
}
|
|
1998
|
-
if ((key.name === "return" || key.name === "enter") && options.length > 0) {
|
|
1999
|
-
confirmMergeAction()
|
|
2000
|
-
return
|
|
2001
|
-
}
|
|
2002
|
-
if (key.name === "up" || key.name === "k") {
|
|
2003
|
-
setMergeModal((current) => ({
|
|
2004
|
-
...current,
|
|
2005
|
-
selectedIndex: Math.max(0, current.selectedIndex - 1),
|
|
2006
|
-
}))
|
|
2007
|
-
return
|
|
2008
|
-
}
|
|
2009
|
-
if (key.name === "down" || key.name === "j") {
|
|
2010
|
-
setMergeModal((current) => ({
|
|
2011
|
-
...current,
|
|
2012
|
-
selectedIndex: Math.min(Math.max(0, options.length - 1), current.selectedIndex + 1),
|
|
2013
|
-
}))
|
|
2014
|
-
return
|
|
2015
|
-
}
|
|
2016
|
-
return
|
|
2017
|
-
}
|
|
2018
2274
|
|
|
2019
2275
|
if (labelModalActive) {
|
|
2020
|
-
if (key.name === "escape") {
|
|
2021
|
-
closeActiveModal()
|
|
2022
|
-
return
|
|
2023
|
-
}
|
|
2024
|
-
if (key.name === "return" || key.name === "enter") {
|
|
2025
|
-
toggleLabelAtIndex()
|
|
2026
|
-
return
|
|
2027
|
-
}
|
|
2028
|
-
if (key.name === "up" || key.name === "k") {
|
|
2029
|
-
setLabelModal((current) => ({
|
|
2030
|
-
...current,
|
|
2031
|
-
selectedIndex: Math.max(0, current.selectedIndex - 1),
|
|
2032
|
-
}))
|
|
2033
|
-
return
|
|
2034
|
-
}
|
|
2035
|
-
if (key.name === "down" || key.name === "j") {
|
|
2036
|
-
const filtered = filterLabels(labelModal.availableLabels, labelModal.query)
|
|
2037
|
-
setLabelModal((current) => ({
|
|
2038
|
-
...current,
|
|
2039
|
-
selectedIndex: Math.min(Math.max(0, filtered.length - 1), current.selectedIndex + 1),
|
|
2040
|
-
}))
|
|
2041
|
-
return
|
|
2042
|
-
}
|
|
2043
2276
|
if (isSingleLineInputKey(key)) {
|
|
2044
2277
|
setLabelModal((current) => ({
|
|
2045
2278
|
...current,
|
|
2046
2279
|
query: editSingleLineInput(current.query, key) ?? current.query,
|
|
2047
2280
|
selectedIndex: 0,
|
|
2048
2281
|
}))
|
|
2049
|
-
return
|
|
2050
|
-
}
|
|
2051
|
-
return
|
|
2052
|
-
}
|
|
2053
|
-
|
|
2054
|
-
if (diffFullView) {
|
|
2055
|
-
if (diffCommentMode) {
|
|
2056
|
-
if (key.name === "escape") {
|
|
2057
|
-
setDiffCommentMode(false)
|
|
2058
|
-
return
|
|
2059
|
-
}
|
|
2060
|
-
if (key.name === "c") {
|
|
2061
|
-
runCommandById("diff.comment-mode")
|
|
2062
|
-
return
|
|
2063
|
-
}
|
|
2064
|
-
if (key.name === "return" || key.name === "enter") {
|
|
2065
|
-
if (selectedDiffCommentThread.length > 0) openDiffCommentThreadModal()
|
|
2066
|
-
else openDiffCommentModal()
|
|
2067
|
-
return
|
|
2068
|
-
}
|
|
2069
|
-
if (key.name === "a") {
|
|
2070
|
-
runCommandById("diff.add-comment")
|
|
2071
|
-
return
|
|
2072
|
-
}
|
|
2073
|
-
if (key.name === "pageup" || key.ctrl && key.name === "u") {
|
|
2074
|
-
moveDiffCommentAnchor(-halfPage)
|
|
2075
|
-
return
|
|
2076
|
-
}
|
|
2077
|
-
if (key.name === "pagedown" || key.ctrl && (key.name === "d" || key.name === "v")) {
|
|
2078
|
-
moveDiffCommentAnchor(halfPage)
|
|
2079
|
-
return
|
|
2080
|
-
}
|
|
2081
|
-
if ((key.shift || key.option || key.meta) && (key.name === "up" || key.name === "k") || key.name === "K") {
|
|
2082
|
-
moveDiffCommentAnchor(-8)
|
|
2083
|
-
return
|
|
2084
|
-
}
|
|
2085
|
-
if ((key.shift || key.option || key.meta) && (key.name === "down" || key.name === "j") || key.name === "J") {
|
|
2086
|
-
moveDiffCommentAnchor(8)
|
|
2087
|
-
return
|
|
2088
|
-
}
|
|
2089
|
-
if (key.name === "up" || key.name === "k") {
|
|
2090
|
-
moveDiffCommentAnchor(-1)
|
|
2091
|
-
return
|
|
2092
|
-
}
|
|
2093
|
-
if (key.name === "down" || key.name === "j") {
|
|
2094
|
-
moveDiffCommentAnchor(1)
|
|
2095
|
-
return
|
|
2096
|
-
}
|
|
2097
|
-
if (key.name === "left" || key.name === "h") {
|
|
2098
|
-
selectDiffCommentSide("LEFT")
|
|
2099
|
-
return
|
|
2100
|
-
}
|
|
2101
|
-
if (key.name === "right" || key.name === "l") {
|
|
2102
|
-
selectDiffCommentSide("RIGHT")
|
|
2103
|
-
return
|
|
2104
|
-
}
|
|
2105
|
-
if (key.name === "]" && selectedDiffState?._tag === "Ready") {
|
|
2106
|
-
runCommandById("diff.next-file")
|
|
2107
|
-
return
|
|
2108
|
-
}
|
|
2109
|
-
if (key.name === "[" && selectedDiffState?._tag === "Ready") {
|
|
2110
|
-
runCommandById("diff.previous-file")
|
|
2111
|
-
return
|
|
2112
|
-
}
|
|
2113
|
-
return
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
if (key.name === "escape" || key.name === "return" || key.name === "enter") {
|
|
2117
|
-
runCommandById("diff.close")
|
|
2118
|
-
return
|
|
2119
|
-
}
|
|
2120
|
-
if (key.name === "c" && selectedDiffState?._tag === "Ready") {
|
|
2121
|
-
runCommandById("diff.comment-mode")
|
|
2122
|
-
return
|
|
2123
|
-
}
|
|
2124
|
-
if (key.name === "home") {
|
|
2125
|
-
scrollDiffTo(0)
|
|
2126
|
-
return
|
|
2127
|
-
}
|
|
2128
|
-
if (key.name === "end") {
|
|
2129
|
-
scrollDiffTo(Number.MAX_SAFE_INTEGER)
|
|
2130
|
-
return
|
|
2131
|
-
}
|
|
2132
|
-
if (key.name === "pageup") {
|
|
2133
|
-
scrollDiffBy(-halfPage)
|
|
2134
|
-
return
|
|
2135
|
-
}
|
|
2136
|
-
if (key.name === "pagedown") {
|
|
2137
|
-
scrollDiffBy(halfPage)
|
|
2138
|
-
return
|
|
2139
|
-
}
|
|
2140
|
-
if (handleVimGoto(key, () => scrollDiffTo(0), () => scrollDiffTo(Number.MAX_SAFE_INTEGER))) return
|
|
2141
|
-
if (key.name === "up" || key.name === "k") {
|
|
2142
|
-
scrollDiffBy(-1)
|
|
2143
|
-
return
|
|
2144
|
-
}
|
|
2145
|
-
if (key.name === "down" || key.name === "j") {
|
|
2146
|
-
scrollDiffBy(1)
|
|
2147
|
-
return
|
|
2148
|
-
}
|
|
2149
|
-
if (key.ctrl && key.name === "u") {
|
|
2150
|
-
scrollDiffBy(-halfPage)
|
|
2151
|
-
return
|
|
2152
|
-
}
|
|
2153
|
-
if (key.ctrl && (key.name === "d" || key.name === "v")) {
|
|
2154
|
-
scrollDiffBy(halfPage)
|
|
2155
|
-
return
|
|
2156
|
-
}
|
|
2157
|
-
if (key.name === "v") {
|
|
2158
|
-
runCommandById("diff.toggle-view")
|
|
2159
|
-
return
|
|
2160
|
-
}
|
|
2161
|
-
if (key.name === "w") {
|
|
2162
|
-
runCommandById("diff.toggle-wrap")
|
|
2163
|
-
return
|
|
2164
|
-
}
|
|
2165
|
-
if (key.name === "r" && selectedPullRequest) {
|
|
2166
|
-
runCommandById("diff.reload")
|
|
2167
|
-
return
|
|
2168
|
-
}
|
|
2169
|
-
if ((key.name === "]" || key.name === "right" || key.name === "l") && selectedDiffState?._tag === "Ready") {
|
|
2170
|
-
runCommandById("diff.next-file")
|
|
2171
|
-
return
|
|
2172
|
-
}
|
|
2173
|
-
if ((key.name === "[" || key.name === "left" || key.name === "h") && selectedDiffState?._tag === "Ready") {
|
|
2174
|
-
runCommandById("diff.previous-file")
|
|
2175
|
-
return
|
|
2176
|
-
}
|
|
2177
|
-
if (key.name === "o" && selectedPullRequest) {
|
|
2178
|
-
runCommandById("pull.open-browser")
|
|
2179
|
-
return
|
|
2180
|
-
}
|
|
2181
|
-
return
|
|
2182
|
-
}
|
|
2183
|
-
|
|
2184
|
-
if (detailFullView) {
|
|
2185
|
-
const plainKey = !key.ctrl && !key.meta && !key.option
|
|
2186
|
-
if (key.name === "escape" || (key.name === "return" || key.name === "enter")) {
|
|
2187
|
-
runCommandById("detail.close")
|
|
2188
|
-
return
|
|
2189
|
-
}
|
|
2190
|
-
if (isThemeKey(key)) {
|
|
2191
|
-
runCommandById("theme.open")
|
|
2192
|
-
return
|
|
2193
|
-
}
|
|
2194
|
-
if (plainKey && key.name === "d" && selectedPullRequest) {
|
|
2195
|
-
runCommandById("diff.open")
|
|
2196
|
-
return
|
|
2197
|
-
}
|
|
2198
|
-
if (plainKey && key.name === "x" && selectedPullRequest?.state === "open") {
|
|
2199
|
-
runCommandById("pull.close")
|
|
2200
|
-
return
|
|
2201
|
-
}
|
|
2202
|
-
if (plainKey && key.name === "l" && selectedPullRequest) {
|
|
2203
|
-
runCommandById("pull.labels")
|
|
2204
|
-
return
|
|
2205
|
-
}
|
|
2206
|
-
if (plainKey && (key.name === "m" || key.name === "M") && selectedPullRequest) {
|
|
2207
|
-
runCommandById("pull.merge")
|
|
2208
|
-
return
|
|
2209
|
-
}
|
|
2210
|
-
if (plainKey && (key.name === "s" || key.name === "S") && selectedPullRequest) {
|
|
2211
|
-
runCommandById("pull.toggle-draft")
|
|
2212
|
-
return
|
|
2213
|
-
}
|
|
2214
|
-
if (plainKey && key.name === "r") {
|
|
2215
|
-
runCommandById("pull.refresh")
|
|
2216
|
-
return
|
|
2217
|
-
}
|
|
2218
|
-
if (key.name === "home") {
|
|
2219
|
-
detailScrollRef.current?.scrollTo({ x: 0, y: 0 })
|
|
2220
|
-
setDetailScrollOffset(0)
|
|
2221
|
-
return
|
|
2222
|
-
}
|
|
2223
|
-
if (key.name === "end") {
|
|
2224
|
-
detailScrollRef.current?.scrollTo({ x: 0, y: Number.MAX_SAFE_INTEGER })
|
|
2225
|
-
setDetailScrollOffset(Number.MAX_SAFE_INTEGER)
|
|
2226
|
-
return
|
|
2227
|
-
}
|
|
2228
|
-
if (key.name === "pageup") {
|
|
2229
|
-
detailScrollRef.current?.scrollBy({ x: 0, y: -halfPage })
|
|
2230
|
-
setDetailScrollOffset((current) => Math.max(0, current - halfPage))
|
|
2231
|
-
return
|
|
2232
|
-
}
|
|
2233
|
-
if (key.name === "pagedown") {
|
|
2234
|
-
detailScrollRef.current?.scrollBy({ x: 0, y: halfPage })
|
|
2235
|
-
setDetailScrollOffset((current) => current + halfPage)
|
|
2236
|
-
return
|
|
2237
|
-
}
|
|
2238
|
-
if (handleVimGoto(key,
|
|
2239
|
-
() => { detailScrollRef.current?.scrollTo({ x: 0, y: 0 }); setDetailScrollOffset(0) },
|
|
2240
|
-
() => { detailScrollRef.current?.scrollTo({ x: 0, y: Number.MAX_SAFE_INTEGER }); setDetailScrollOffset(Number.MAX_SAFE_INTEGER) },
|
|
2241
|
-
)) return
|
|
2242
|
-
if (key.name === "up" || key.name === "k") {
|
|
2243
|
-
detailScrollRef.current?.scrollBy({ x: 0, y: -1 })
|
|
2244
|
-
setDetailScrollOffset((current) => Math.max(0, current - 1))
|
|
2245
|
-
return
|
|
2246
|
-
}
|
|
2247
|
-
if (key.name === "down" || key.name === "j") {
|
|
2248
|
-
detailScrollRef.current?.scrollBy({ x: 0, y: 1 })
|
|
2249
|
-
setDetailScrollOffset((current) => current + 1)
|
|
2250
|
-
return
|
|
2251
|
-
}
|
|
2252
|
-
if (key.ctrl && key.name === "u") {
|
|
2253
|
-
detailScrollRef.current?.scrollBy({ x: 0, y: -halfPage })
|
|
2254
|
-
setDetailScrollOffset((current) => Math.max(0, current - halfPage))
|
|
2255
|
-
return
|
|
2256
|
-
}
|
|
2257
|
-
if (key.ctrl && (key.name === "d" || key.name === "v")) {
|
|
2258
|
-
detailScrollRef.current?.scrollBy({ x: 0, y: halfPage })
|
|
2259
|
-
setDetailScrollOffset((current) => current + halfPage)
|
|
2260
|
-
return
|
|
2261
|
-
}
|
|
2262
|
-
if (plainKey && key.name === "o" && selectedPullRequest) {
|
|
2263
|
-
runCommandById("pull.open-browser")
|
|
2264
|
-
return
|
|
2265
|
-
}
|
|
2266
|
-
if (plainKey && key.name === "y" && selectedPullRequest) {
|
|
2267
|
-
runCommandById("pull.copy-metadata")
|
|
2268
|
-
return
|
|
2269
2282
|
}
|
|
2270
2283
|
return
|
|
2271
2284
|
}
|
|
2272
2285
|
|
|
2273
2286
|
if (filterMode) {
|
|
2274
|
-
if (key.name === "escape") {
|
|
2275
|
-
setFilterDraft(filterQuery)
|
|
2276
|
-
setFilterMode(false)
|
|
2277
|
-
return
|
|
2278
|
-
}
|
|
2279
|
-
if (key.name === "return" || key.name === "enter") {
|
|
2280
|
-
setFilterQuery(filterDraft)
|
|
2281
|
-
setFilterMode(false)
|
|
2282
|
-
return
|
|
2283
|
-
}
|
|
2284
2287
|
if (isSingleLineInputKey(key)) {
|
|
2285
2288
|
setFilterDraft((current) => editSingleLineInput(current, key) ?? current)
|
|
2286
|
-
return
|
|
2287
2289
|
}
|
|
2288
2290
|
}
|
|
2289
|
-
|
|
2290
|
-
if (key.name === "tab") {
|
|
2291
|
-
switchQueueMode(key.shift ? -1 : 1)
|
|
2292
|
-
return
|
|
2293
|
-
}
|
|
2294
|
-
|
|
2295
|
-
if (isThemeKey(key)) {
|
|
2296
|
-
runCommandById("theme.open")
|
|
2297
|
-
return
|
|
2298
|
-
}
|
|
2299
|
-
|
|
2300
|
-
if (key.name === "/") {
|
|
2301
|
-
runCommandById("filter.open")
|
|
2302
|
-
return
|
|
2303
|
-
}
|
|
2304
|
-
if (key.name === "escape" && filterQuery.length > 0) {
|
|
2305
|
-
runCommandById("filter.clear")
|
|
2306
|
-
return
|
|
2307
|
-
}
|
|
2308
|
-
if (key.name === "r") {
|
|
2309
|
-
runCommandById("pull.refresh")
|
|
2310
|
-
return
|
|
2311
|
-
}
|
|
2312
|
-
if (isWideLayout && selectedPullRequest && !detailFullView && !diffFullView) {
|
|
2313
|
-
if (key.name === "home") {
|
|
2314
|
-
scrollDetailPreviewTo(0)
|
|
2315
|
-
return
|
|
2316
|
-
}
|
|
2317
|
-
if (key.name === "end") {
|
|
2318
|
-
scrollDetailPreviewTo(Number.MAX_SAFE_INTEGER)
|
|
2319
|
-
return
|
|
2320
|
-
}
|
|
2321
|
-
if (key.name === "pageup") {
|
|
2322
|
-
scrollDetailPreviewBy(-halfPage)
|
|
2323
|
-
return
|
|
2324
|
-
}
|
|
2325
|
-
if (key.name === "pagedown") {
|
|
2326
|
-
scrollDetailPreviewBy(halfPage)
|
|
2327
|
-
return
|
|
2328
|
-
}
|
|
2329
|
-
}
|
|
2330
|
-
if (
|
|
2331
|
-
key.name === "[" ||
|
|
2332
|
-
((key.option || key.meta) && (key.name === "up" || key.name === "k")) ||
|
|
2333
|
-
(key.shift && key.name === "k") ||
|
|
2334
|
-
key.name === "K"
|
|
2335
|
-
) {
|
|
2336
|
-
setSelectedIndex((current) => {
|
|
2337
|
-
if (visiblePullRequests.length === 0 || groupStarts.length === 0) return 0
|
|
2338
|
-
const currentGroup = getCurrentGroupIndex(current)
|
|
2339
|
-
if (currentGroup <= 0) return groupStarts[groupStarts.length - 1]!
|
|
2340
|
-
return groupStarts[currentGroup - 1]!
|
|
2341
|
-
})
|
|
2342
|
-
return
|
|
2343
|
-
}
|
|
2344
|
-
if (
|
|
2345
|
-
key.name === "]" ||
|
|
2346
|
-
((key.option || key.meta) && (key.name === "down" || key.name === "j")) ||
|
|
2347
|
-
(key.shift && key.name === "j") ||
|
|
2348
|
-
key.name === "J"
|
|
2349
|
-
) {
|
|
2350
|
-
setSelectedIndex((current) => {
|
|
2351
|
-
if (visiblePullRequests.length === 0 || groupStarts.length === 0) return 0
|
|
2352
|
-
const currentGroup = getCurrentGroupIndex(current)
|
|
2353
|
-
if (currentGroup >= groupStarts.length - 1) return groupStarts[0]!
|
|
2354
|
-
return groupStarts[currentGroup + 1]!
|
|
2355
|
-
})
|
|
2356
|
-
return
|
|
2357
|
-
}
|
|
2358
|
-
if (key.ctrl && key.name === "u") {
|
|
2359
|
-
setSelectedIndex((current) => {
|
|
2360
|
-
if (visiblePullRequests.length === 0) return 0
|
|
2361
|
-
return Math.max(0, current - halfPage)
|
|
2362
|
-
})
|
|
2363
|
-
return
|
|
2364
|
-
}
|
|
2365
|
-
if (key.ctrl && key.name === "d") {
|
|
2366
|
-
setSelectedIndex((current) => {
|
|
2367
|
-
if (visiblePullRequests.length === 0) return 0
|
|
2368
|
-
return Math.min(visiblePullRequests.length - 1, current + halfPage)
|
|
2369
|
-
})
|
|
2370
|
-
return
|
|
2371
|
-
}
|
|
2372
|
-
if (key.name === "up" || key.name === "k") {
|
|
2373
|
-
setSelectedIndex((current) => {
|
|
2374
|
-
if (visiblePullRequests.length === 0) return 0
|
|
2375
|
-
return current <= 0 ? visiblePullRequests.length - 1 : current - 1
|
|
2376
|
-
})
|
|
2377
|
-
return
|
|
2378
|
-
}
|
|
2379
|
-
if (key.name === "down" || key.name === "j") {
|
|
2380
|
-
if (visiblePullRequests.length > 0 && selectedIndex >= visiblePullRequests.length - 1 && hasMorePullRequests) {
|
|
2381
|
-
loadMorePullRequests()
|
|
2382
|
-
return
|
|
2383
|
-
}
|
|
2384
|
-
setSelectedIndex((current) => {
|
|
2385
|
-
if (visiblePullRequests.length === 0) return 0
|
|
2386
|
-
return current >= visiblePullRequests.length - 1 ? 0 : current + 1
|
|
2387
|
-
})
|
|
2388
|
-
return
|
|
2389
|
-
}
|
|
2390
|
-
if (handleVimGoto(key,
|
|
2391
|
-
() => setSelectedIndex(0),
|
|
2392
|
-
() => setSelectedIndex(visiblePullRequests.length === 0 ? 0 : visiblePullRequests.length - 1),
|
|
2393
|
-
)) return
|
|
2394
|
-
if ((key.name === "return" || key.name === "enter") && !detailFullView) {
|
|
2395
|
-
runCommandById("detail.open")
|
|
2396
|
-
return
|
|
2397
|
-
}
|
|
2398
|
-
if (key.name === "d" && selectedPullRequest) {
|
|
2399
|
-
runCommandById("diff.open")
|
|
2400
|
-
return
|
|
2401
|
-
}
|
|
2402
|
-
if (key.name === "x" && selectedPullRequest?.state === "open") {
|
|
2403
|
-
runCommandById("pull.close")
|
|
2404
|
-
return
|
|
2405
|
-
}
|
|
2406
|
-
if (key.name === "l" && selectedPullRequest) {
|
|
2407
|
-
runCommandById("pull.labels")
|
|
2408
|
-
return
|
|
2409
|
-
}
|
|
2410
|
-
if (key.name === "m" || key.name === "M") {
|
|
2411
|
-
if (selectedPullRequest) runCommandById("pull.merge")
|
|
2412
|
-
return
|
|
2413
|
-
}
|
|
2414
|
-
if (key.name === "o" && selectedPullRequest) {
|
|
2415
|
-
runCommandById("pull.open-browser")
|
|
2416
|
-
return
|
|
2417
|
-
}
|
|
2418
|
-
if ((key.name === "s" || key.name === "S") && selectedPullRequest) {
|
|
2419
|
-
runCommandById("pull.toggle-draft")
|
|
2420
|
-
return
|
|
2421
|
-
}
|
|
2422
|
-
if (key.name === "y" && selectedPullRequest) {
|
|
2423
|
-
runCommandById("pull.copy-metadata")
|
|
2424
|
-
return
|
|
2425
|
-
}
|
|
2426
2291
|
})
|
|
2427
2292
|
|
|
2428
2293
|
const fullscreenContentWidth = Math.max(24, contentWidth - 2)
|
|
2429
2294
|
const fullscreenBodyLines = Math.max(8, terminalHeight - 8)
|
|
2430
|
-
const
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
paneWidth: contentWidth,
|
|
2435
|
-
showChecks: true,
|
|
2436
|
-
}) > wideBodyHeight
|
|
2437
|
-
const narrowFullscreenDetailScrollable = getDetailsPaneHeight({
|
|
2438
|
-
pullRequest: selectedPullRequest,
|
|
2439
|
-
contentWidth: fullscreenContentWidth,
|
|
2440
|
-
bodyLines: DETAIL_BODY_SCROLL_LIMIT,
|
|
2441
|
-
paneWidth: contentWidth,
|
|
2442
|
-
}) > 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
|
|
2443
2299
|
const wideDetailHeaderHeight = getDetailHeaderHeight(selectedPullRequest, rightPaneWidth, true)
|
|
2444
2300
|
const wideDetailBodyViewportHeight = Math.max(1, wideBodyHeight - wideDetailHeaderHeight)
|
|
2445
2301
|
const wideDetailBodyHeight = getScrollableDetailBodyHeight(selectedPullRequest, rightContentWidth)
|
|
@@ -2456,6 +2312,7 @@ export const App = () => {
|
|
|
2456
2312
|
loadedCount: loadedPullRequestCount,
|
|
2457
2313
|
hasMore: hasMorePullRequests,
|
|
2458
2314
|
isLoadingMore: isLoadingMorePullRequests,
|
|
2315
|
+
loadingIndicator,
|
|
2459
2316
|
onSelectPullRequest: selectPullRequestByUrl,
|
|
2460
2317
|
} as const
|
|
2461
2318
|
|
|
@@ -2484,8 +2341,8 @@ export const App = () => {
|
|
|
2484
2341
|
const commentThreadModalHeight = commentThreadLayout.height
|
|
2485
2342
|
const commentThreadModalLeft = commentThreadLayout.left
|
|
2486
2343
|
const commentThreadModalTop = commentThreadLayout.top
|
|
2487
|
-
const commentAnchorLabel = selectedDiffCommentAnchor
|
|
2488
|
-
? `${selectedDiffCommentAnchor.path}
|
|
2344
|
+
const commentAnchorLabel = selectedDiffCommentAnchor && selectedDiffCommentLabel
|
|
2345
|
+
? `${selectedDiffCommentAnchor.path} ${selectedDiffCommentLabel}`
|
|
2489
2346
|
: "No diff line selected"
|
|
2490
2347
|
const mergeLayout = sizedModal(46, 68, 12, 16)
|
|
2491
2348
|
const mergeModalWidth = mergeLayout.width
|
|
@@ -2533,8 +2390,8 @@ export const App = () => {
|
|
|
2533
2390
|
loadingIndicator={loadingIndicator}
|
|
2534
2391
|
scrollRef={diffScrollRef}
|
|
2535
2392
|
setDiffRef={setDiffRenderableRef}
|
|
2536
|
-
commentMode={diffCommentMode}
|
|
2537
2393
|
selectedCommentAnchor={selectedDiffCommentAnchor}
|
|
2394
|
+
selectedCommentLabel={selectedDiffCommentLabel}
|
|
2538
2395
|
selectedCommentThread={selectedDiffCommentThread}
|
|
2539
2396
|
onSelectCommentLine={selectDiffCommentLine}
|
|
2540
2397
|
themeId={themeId}
|
|
@@ -2546,20 +2403,16 @@ export const App = () => {
|
|
|
2546
2403
|
</box>
|
|
2547
2404
|
) : isWideLayout && detailFullView ? (
|
|
2548
2405
|
<box flexGrow={1} flexDirection="column">
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
pullRequest={selectedPullRequest}
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
loadingIndicator={loadingIndicator}
|
|
2560
|
-
themeId={themeId}
|
|
2561
|
-
/>
|
|
2562
|
-
</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
|
+
)}
|
|
2563
2416
|
</box>
|
|
2564
2417
|
) : isWideLayout ? (
|
|
2565
2418
|
<box key="wide-main" flexGrow={1} flexDirection="row">
|
|
@@ -2591,19 +2444,16 @@ export const App = () => {
|
|
|
2591
2444
|
</box>
|
|
2592
2445
|
) : detailFullView ? (
|
|
2593
2446
|
<box flexGrow={1} flexDirection="column">
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
pullRequest={selectedPullRequest}
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
themeId={themeId}
|
|
2605
|
-
/>
|
|
2606
|
-
</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
|
+
)}
|
|
2607
2457
|
</box>
|
|
2608
2458
|
) : (
|
|
2609
2459
|
<box key="narrow-main" height={wideBodyHeight} flexDirection="column">
|
|
@@ -2633,7 +2483,7 @@ export const App = () => {
|
|
|
2633
2483
|
showFilterClear={filterMode || filterQuery.length > 0}
|
|
2634
2484
|
detailFullView={detailFullView}
|
|
2635
2485
|
diffFullView={diffFullView}
|
|
2636
|
-
|
|
2486
|
+
diffRangeActive={diffCommentRangeActive}
|
|
2637
2487
|
hasSelection={selectedPullRequest !== null}
|
|
2638
2488
|
canCloseSelection={selectedPullRequest?.state === "open"}
|
|
2639
2489
|
hasError={pullRequestStatus === "error"}
|