@kitlangton/ghui 0.3.0 → 0.3.2

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.
Files changed (54) hide show
  1. package/bin/ghui.js +6 -1
  2. package/dist/index.js +9419 -0
  3. package/package.json +7 -4
  4. package/src/App.tsx +0 -2779
  5. package/src/appCommands.ts +0 -409
  6. package/src/commands.ts +0 -73
  7. package/src/config.ts +0 -20
  8. package/src/date.ts +0 -20
  9. package/src/domain.ts +0 -149
  10. package/src/errors.ts +0 -10
  11. package/src/index.tsx +0 -50
  12. package/src/keyboard/opentuiAdapter.ts +0 -43
  13. package/src/keymap/all.ts +0 -116
  14. package/src/keymap/changedFilesModal.ts +0 -23
  15. package/src/keymap/closeModal.ts +0 -13
  16. package/src/keymap/commandPalette.ts +0 -16
  17. package/src/keymap/commentModal.ts +0 -73
  18. package/src/keymap/commentThreadModal.ts +0 -24
  19. package/src/keymap/detailView.ts +0 -30
  20. package/src/keymap/diffView.ts +0 -93
  21. package/src/keymap/filterMode.ts +0 -13
  22. package/src/keymap/helpers.ts +0 -24
  23. package/src/keymap/labelModal.ts +0 -16
  24. package/src/keymap/listNav.ts +0 -119
  25. package/src/keymap/mergeModal.ts +0 -23
  26. package/src/keymap/openRepositoryModal.ts +0 -13
  27. package/src/keymap/submitReviewModal.ts +0 -48
  28. package/src/keymap/themeModal.ts +0 -49
  29. package/src/mergeActions.ts +0 -88
  30. package/src/observability.ts +0 -46
  31. package/src/pullRequestCache.ts +0 -19
  32. package/src/pullRequestViews.ts +0 -43
  33. package/src/services/BrowserOpener.ts +0 -22
  34. package/src/services/Clipboard.ts +0 -46
  35. package/src/services/CommandRunner.ts +0 -91
  36. package/src/services/GitHubService.ts +0 -756
  37. package/src/services/MockGitHubService.ts +0 -182
  38. package/src/themeStore.ts +0 -60
  39. package/src/ui/CommandPalette.tsx +0 -176
  40. package/src/ui/DetailsPane.tsx +0 -656
  41. package/src/ui/FooterHints.tsx +0 -90
  42. package/src/ui/LoadingLogo.tsx +0 -75
  43. package/src/ui/PullRequestDiffPane.tsx +0 -249
  44. package/src/ui/PullRequestList.tsx +0 -194
  45. package/src/ui/colors.ts +0 -821
  46. package/src/ui/commentEditor.ts +0 -126
  47. package/src/ui/comments.tsx +0 -143
  48. package/src/ui/diff.ts +0 -650
  49. package/src/ui/diffStats.tsx +0 -25
  50. package/src/ui/modals.tsx +0 -964
  51. package/src/ui/primitives.tsx +0 -350
  52. package/src/ui/pullRequests.ts +0 -106
  53. package/src/ui/singleLineInput.ts +0 -26
  54. package/src/ui/spinner.ts +0 -1
@@ -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
- }
@@ -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
- )