@kitlangton/ghui 0.3.0 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/bin/ghui.js +6 -1
- package/dist/index.js +9456 -0
- package/package.json +7 -4
- package/src/App.tsx +0 -2779
- package/src/appCommands.ts +0 -409
- package/src/commands.ts +0 -73
- package/src/config.ts +0 -20
- package/src/date.ts +0 -20
- package/src/domain.ts +0 -149
- package/src/errors.ts +0 -10
- package/src/index.tsx +0 -50
- package/src/keyboard/opentuiAdapter.ts +0 -43
- package/src/keymap/all.ts +0 -116
- package/src/keymap/changedFilesModal.ts +0 -23
- package/src/keymap/closeModal.ts +0 -13
- package/src/keymap/commandPalette.ts +0 -16
- package/src/keymap/commentModal.ts +0 -73
- package/src/keymap/commentThreadModal.ts +0 -24
- package/src/keymap/detailView.ts +0 -30
- package/src/keymap/diffView.ts +0 -93
- package/src/keymap/filterMode.ts +0 -13
- package/src/keymap/helpers.ts +0 -24
- package/src/keymap/labelModal.ts +0 -16
- package/src/keymap/listNav.ts +0 -119
- package/src/keymap/mergeModal.ts +0 -23
- package/src/keymap/openRepositoryModal.ts +0 -13
- package/src/keymap/submitReviewModal.ts +0 -48
- package/src/keymap/themeModal.ts +0 -49
- package/src/mergeActions.ts +0 -88
- package/src/observability.ts +0 -46
- package/src/pullRequestCache.ts +0 -19
- package/src/pullRequestViews.ts +0 -43
- package/src/services/BrowserOpener.ts +0 -22
- package/src/services/Clipboard.ts +0 -46
- package/src/services/CommandRunner.ts +0 -91
- package/src/services/GitHubService.ts +0 -756
- package/src/services/MockGitHubService.ts +0 -182
- package/src/themeStore.ts +0 -60
- package/src/ui/CommandPalette.tsx +0 -176
- package/src/ui/DetailsPane.tsx +0 -656
- package/src/ui/FooterHints.tsx +0 -90
- package/src/ui/LoadingLogo.tsx +0 -75
- package/src/ui/PullRequestDiffPane.tsx +0 -249
- package/src/ui/PullRequestList.tsx +0 -194
- package/src/ui/colors.ts +0 -821
- package/src/ui/commentEditor.ts +0 -126
- package/src/ui/comments.tsx +0 -143
- package/src/ui/diff.ts +0 -650
- package/src/ui/diffStats.tsx +0 -25
- package/src/ui/modals.tsx +0 -964
- package/src/ui/primitives.tsx +0 -350
- package/src/ui/pullRequests.ts +0 -106
- package/src/ui/singleLineInput.ts +0 -26
- package/src/ui/spinner.ts +0 -1
package/src/ui/commentEditor.ts
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
export interface CommentEditorValue {
|
|
2
|
-
readonly body: string
|
|
3
|
-
readonly cursor: number
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export interface CommentEditorLine {
|
|
7
|
-
readonly text: string
|
|
8
|
-
readonly start: number
|
|
9
|
-
readonly end: number
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const clampCursor = (body: string, cursor: number) => Math.max(0, Math.min(cursor, body.length))
|
|
13
|
-
|
|
14
|
-
export const commentEditorLines = (body: string): readonly CommentEditorLine[] => body.split("\n").reduce<CommentEditorLine[]>((ranges, text) => {
|
|
15
|
-
const start = ranges.length === 0 ? 0 : ranges[ranges.length - 1]!.end + 1
|
|
16
|
-
ranges.push({ text, start, end: start + text.length })
|
|
17
|
-
return ranges
|
|
18
|
-
}, [])
|
|
19
|
-
|
|
20
|
-
export const cursorLineIndexForLines = (lines: readonly CommentEditorLine[], cursor: number) =>
|
|
21
|
-
Math.max(0, lines.findIndex((line, index) => cursor <= line.end || index === lines.length - 1))
|
|
22
|
-
|
|
23
|
-
export const cursorLineIndex = (body: string, cursor: number) => {
|
|
24
|
-
const lines = commentEditorLines(body)
|
|
25
|
-
const safeCursor = clampCursor(body, cursor)
|
|
26
|
-
return cursorLineIndexForLines(lines, safeCursor)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const lineStartAt = (body: string, cursor: number) => body.lastIndexOf("\n", Math.max(0, clampCursor(body, cursor) - 1)) + 1
|
|
30
|
-
|
|
31
|
-
export const lineEndAt = (body: string, cursor: number) => {
|
|
32
|
-
const end = body.indexOf("\n", clampCursor(body, cursor))
|
|
33
|
-
return end === -1 ? body.length : end
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const previousWordStart = (body: string, cursor: number) => {
|
|
37
|
-
let index = clampCursor(body, cursor)
|
|
38
|
-
while (index > 0 && /\s/.test(body[index - 1]!)) index--
|
|
39
|
-
while (index > 0 && !/\s/.test(body[index - 1]!)) index--
|
|
40
|
-
return index
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const nextWordEnd = (body: string, cursor: number) => {
|
|
44
|
-
let index = clampCursor(body, cursor)
|
|
45
|
-
while (index < body.length && /\s/.test(body[index]!)) index++
|
|
46
|
-
while (index < body.length && !/\s/.test(body[index]!)) index++
|
|
47
|
-
return index
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const replaceRange = (state: CommentEditorValue, start: number, end: number, text = ""): CommentEditorValue => ({
|
|
51
|
-
body: `${state.body.slice(0, start)}${text}${state.body.slice(end)}`,
|
|
52
|
-
cursor: start + text.length,
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
export const insertText = (state: CommentEditorValue, text: string): CommentEditorValue =>
|
|
56
|
-
replaceRange(state, clampCursor(state.body, state.cursor), clampCursor(state.body, state.cursor), text)
|
|
57
|
-
|
|
58
|
-
export const moveLeft = (state: CommentEditorValue): CommentEditorValue => ({
|
|
59
|
-
...state,
|
|
60
|
-
cursor: Math.max(0, clampCursor(state.body, state.cursor) - 1),
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
export const moveRight = (state: CommentEditorValue): CommentEditorValue => ({
|
|
64
|
-
...state,
|
|
65
|
-
cursor: Math.min(state.body.length, clampCursor(state.body, state.cursor) + 1),
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
export const moveLineStart = (state: CommentEditorValue): CommentEditorValue => ({
|
|
69
|
-
...state,
|
|
70
|
-
cursor: lineStartAt(state.body, state.cursor),
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
export const moveLineEnd = (state: CommentEditorValue): CommentEditorValue => ({
|
|
74
|
-
...state,
|
|
75
|
-
cursor: lineEndAt(state.body, state.cursor),
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
export const moveWordBackward = (state: CommentEditorValue): CommentEditorValue => ({
|
|
79
|
-
...state,
|
|
80
|
-
cursor: previousWordStart(state.body, state.cursor),
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
export const moveWordForward = (state: CommentEditorValue): CommentEditorValue => ({
|
|
84
|
-
...state,
|
|
85
|
-
cursor: nextWordEnd(state.body, state.cursor),
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
export const moveVertically = (state: CommentEditorValue, delta: number): CommentEditorValue => {
|
|
89
|
-
const lines = commentEditorLines(state.body)
|
|
90
|
-
const safeCursor = clampCursor(state.body, state.cursor)
|
|
91
|
-
const currentLineIndex = cursorLineIndexForLines(lines, safeCursor)
|
|
92
|
-
const currentLine = lines[currentLineIndex] ?? { text: state.body, start: 0, end: state.body.length }
|
|
93
|
-
const targetLine = lines[Math.max(0, Math.min(lines.length - 1, currentLineIndex + delta))] ?? currentLine
|
|
94
|
-
const column = safeCursor - currentLine.start
|
|
95
|
-
return { ...state, cursor: Math.min(targetLine.end, targetLine.start + column) }
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export const backspace = (state: CommentEditorValue): CommentEditorValue => {
|
|
99
|
-
const cursor = clampCursor(state.body, state.cursor)
|
|
100
|
-
return cursor === 0 ? { ...state, cursor } : replaceRange({ ...state, cursor }, cursor - 1, cursor)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export const deleteForward = (state: CommentEditorValue): CommentEditorValue => {
|
|
104
|
-
const cursor = clampCursor(state.body, state.cursor)
|
|
105
|
-
return cursor >= state.body.length ? { ...state, cursor } : replaceRange({ ...state, cursor }, cursor, cursor + 1)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export const deleteWordBackward = (state: CommentEditorValue): CommentEditorValue => {
|
|
109
|
-
const cursor = clampCursor(state.body, state.cursor)
|
|
110
|
-
return replaceRange({ ...state, cursor }, previousWordStart(state.body, cursor), cursor)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export const deleteWordForward = (state: CommentEditorValue): CommentEditorValue => {
|
|
114
|
-
const cursor = clampCursor(state.body, state.cursor)
|
|
115
|
-
return replaceRange({ ...state, cursor }, cursor, nextWordEnd(state.body, cursor))
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export const deleteToLineStart = (state: CommentEditorValue): CommentEditorValue => {
|
|
119
|
-
const cursor = clampCursor(state.body, state.cursor)
|
|
120
|
-
return replaceRange({ ...state, cursor }, lineStartAt(state.body, cursor), cursor)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export const deleteToLineEnd = (state: CommentEditorValue): CommentEditorValue => {
|
|
124
|
-
const cursor = clampCursor(state.body, state.cursor)
|
|
125
|
-
return replaceRange({ ...state, cursor }, cursor, lineEndAt(state.body, cursor))
|
|
126
|
-
}
|
package/src/ui/comments.tsx
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { TextAttributes } from "@opentui/core"
|
|
2
|
-
import { formatRelativeDate } from "../date.js"
|
|
3
|
-
import type { DiffCommentSide } from "../domain.js"
|
|
4
|
-
import { colors } from "./colors.js"
|
|
5
|
-
import { fitCell, TextLine } from "./primitives.js"
|
|
6
|
-
|
|
7
|
-
export interface CommentSegment {
|
|
8
|
-
readonly text: string
|
|
9
|
-
readonly fg: string
|
|
10
|
-
readonly bold?: boolean
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface CommentDisplayLine {
|
|
14
|
-
readonly key: string
|
|
15
|
-
readonly segments: readonly CommentSegment[]
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface CommentDisplayItem {
|
|
19
|
-
readonly id: string
|
|
20
|
-
readonly author: string
|
|
21
|
-
readonly body: string
|
|
22
|
-
readonly createdAt: Date | null
|
|
23
|
-
readonly side?: DiffCommentSide | null
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const commentCountText = (count: number) => count === 1 ? "1 comment" : `${count} comments`
|
|
27
|
-
|
|
28
|
-
export const commentSideColor = (side: DiffCommentSide | null | undefined) =>
|
|
29
|
-
side === "LEFT" ? colors.status.failing : side === "RIGHT" ? colors.status.passing : colors.count
|
|
30
|
-
|
|
31
|
-
const commentTimestamp = (date: Date | null) => {
|
|
32
|
-
if (!date) return ""
|
|
33
|
-
const ageMs = Date.now() - date.getTime()
|
|
34
|
-
const minuteMs = 60_000
|
|
35
|
-
const hourMs = 60 * minuteMs
|
|
36
|
-
if (ageMs < minuteMs) return "just now"
|
|
37
|
-
if (ageMs < hourMs) return `${Math.max(1, Math.floor(ageMs / minuteMs))}m ago`
|
|
38
|
-
if (ageMs < 24 * hourMs) return `${Math.max(1, Math.floor(ageMs / hourMs))}h ago`
|
|
39
|
-
return formatRelativeDate(date)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const inlineCommentSegments = (text: string, fg = colors.text): readonly CommentSegment[] =>
|
|
43
|
-
text.split(/(`[^`]+`)/g).filter((part) => part.length > 0).map((part) =>
|
|
44
|
-
part.startsWith("`") && part.endsWith("`")
|
|
45
|
-
? { text: part.slice(1, -1), fg: colors.inlineCode }
|
|
46
|
-
: { text: part, fg },
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
const wrapCommentText = (body: string, width: number) => {
|
|
50
|
-
const safeWidth = Math.max(1, width)
|
|
51
|
-
const lines = body.trim().length === 0 ? ["(empty comment)"] : body.replace(/\r/g, "").trim().split("\n")
|
|
52
|
-
return lines.flatMap((line) => {
|
|
53
|
-
const trimmed = line.trim()
|
|
54
|
-
if (trimmed.length === 0) return []
|
|
55
|
-
const wrapped: string[] = []
|
|
56
|
-
for (let index = 0; index < trimmed.length; index += safeWidth) {
|
|
57
|
-
wrapped.push(trimmed.slice(index, index + safeWidth))
|
|
58
|
-
}
|
|
59
|
-
return wrapped
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const appendMetaGroup = (segments: CommentSegment[], group: readonly CommentSegment[]) => {
|
|
64
|
-
if (group.length === 0) return
|
|
65
|
-
segments.push({ text: " · ", fg: colors.muted }, ...group)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export const commentMetaSegments = ({
|
|
69
|
-
item,
|
|
70
|
-
markerLabel,
|
|
71
|
-
groups = [],
|
|
72
|
-
}: {
|
|
73
|
-
readonly item: CommentDisplayItem
|
|
74
|
-
readonly markerLabel?: string | null | undefined
|
|
75
|
-
readonly groups?: readonly (readonly CommentSegment[])[] | undefined
|
|
76
|
-
}): readonly CommentSegment[] => {
|
|
77
|
-
const sideColor = commentSideColor(item.side)
|
|
78
|
-
const timestamp = commentTimestamp(item.createdAt)
|
|
79
|
-
const segments: CommentSegment[] = [
|
|
80
|
-
{ text: "•", fg: colors.count, bold: true },
|
|
81
|
-
...(markerLabel ? [{ text: ` ${markerLabel}`, fg: sideColor, bold: true }] : []),
|
|
82
|
-
{ text: " ", fg: colors.muted },
|
|
83
|
-
{ text: item.author, fg: colors.count, bold: true },
|
|
84
|
-
]
|
|
85
|
-
if (timestamp) appendMetaGroup(segments, [{ text: timestamp, fg: colors.muted }])
|
|
86
|
-
for (const group of groups) appendMetaGroup(segments, group)
|
|
87
|
-
return segments
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export const commentBodyRows = ({
|
|
91
|
-
keyPrefix,
|
|
92
|
-
body,
|
|
93
|
-
width,
|
|
94
|
-
}: {
|
|
95
|
-
readonly keyPrefix: string
|
|
96
|
-
readonly body: string
|
|
97
|
-
readonly width: number
|
|
98
|
-
}): readonly CommentDisplayLine[] =>
|
|
99
|
-
wrapCommentText(body, Math.max(1, width - 2)).map((line, index) => ({
|
|
100
|
-
key: `${keyPrefix}:body:${index}`,
|
|
101
|
-
segments: [
|
|
102
|
-
{ text: "│ ", fg: colors.muted },
|
|
103
|
-
...inlineCommentSegments(line),
|
|
104
|
-
],
|
|
105
|
-
}))
|
|
106
|
-
|
|
107
|
-
export const commentDisplayRows = ({
|
|
108
|
-
item,
|
|
109
|
-
width,
|
|
110
|
-
markerLabel,
|
|
111
|
-
groups,
|
|
112
|
-
}: {
|
|
113
|
-
readonly item: CommentDisplayItem
|
|
114
|
-
readonly width: number
|
|
115
|
-
readonly markerLabel?: string | null | undefined
|
|
116
|
-
readonly groups?: readonly (readonly CommentSegment[])[] | undefined
|
|
117
|
-
}): readonly CommentDisplayLine[] => [
|
|
118
|
-
{ key: `${item.id}:meta`, segments: commentMetaSegments({ item, markerLabel, groups }) },
|
|
119
|
-
...commentBodyRows({ keyPrefix: item.id, body: item.body, width }),
|
|
120
|
-
]
|
|
121
|
-
|
|
122
|
-
export const firstCommentBodyLine = (body: string) => {
|
|
123
|
-
const text = body.trim().length > 0 ? body : "(empty comment)"
|
|
124
|
-
const newlineIndex = text.indexOf("\n")
|
|
125
|
-
return (newlineIndex >= 0 ? text.slice(0, newlineIndex) : text).trim() || "(empty comment)"
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export const CommentSegmentsLine = ({ segments }: { segments: readonly CommentSegment[] }) => (
|
|
129
|
-
<TextLine>
|
|
130
|
-
{segments.map((segment, index) => segment.bold ? (
|
|
131
|
-
<span key={index} fg={segment.fg} attributes={TextAttributes.BOLD}>{segment.text}</span>
|
|
132
|
-
) : (
|
|
133
|
-
<span key={index} fg={segment.fg}>{segment.text}</span>
|
|
134
|
-
))}
|
|
135
|
-
</TextLine>
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
export const CommentBodyLine = ({ body, width }: { body: string; width: number }) => (
|
|
139
|
-
<CommentSegmentsLine segments={[
|
|
140
|
-
{ text: "│ ", fg: colors.muted },
|
|
141
|
-
{ text: fitCell(firstCommentBodyLine(body), Math.max(1, width - 2)), fg: colors.text },
|
|
142
|
-
]} />
|
|
143
|
-
)
|