spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / mods / diff / hooks / views / dialog-pane / dialog-pane.tsx
146 lines · 4.3 KBRaw
1/* @jsxRuntime classic */
2/* @jsx h */
3/* @jsxFrag Fragment */
4import type { RenderElement } from 'claude-code'
5
6import Limits from '../../limits'
7import PaneState from '../../pane-state'
8import Detail from '../detail'
9import Entries from '../entries'
10import type { Kit } from '../kit'
11import Layout from '../layout'
12import { preludeOf } from '../prelude-of'
13import Sections from '../sections'
14import { dialogEntriesOf } from './dialog-entries-of'
15import { dialogWindowOf } from './dialog-window-of'
16
17/**
18 * The inline pane (DiffDialog): its title, the dim count, then the file
19 * list's window, or the picked file's detail alone, and the key hints.
20 *
21 * Every file lists in the built-in's order, five at a time round the
22 * selected row (`❯ `, where the focus ring starts and what its walk moves,
23 * ui.focus); Enter views that file, Escape backs out or closes.
24 *
25 * @param kit the elements, the handlers, the width
26 * @param model the pane's state
27 * @returns the pane's tree
28 */
29export function dialogPane(
30 kit: Kit,
31 model: PaneState.PaneModel,
32): RenderElement {
33 const { Box, Text } = kit.ui
34 const prelude = preludeOf(kit, model)
35
36 if (prelude) {
37 return prelude
38 }
39
40 const turn = PaneState.pickedTurnOf(model)
41 const { data } = model
42 const entries = dialogEntriesOf(model)
43 const totals = turn ? PaneState.turnTotalsOf(turn) : (data?.stats ?? null)
44 const isTurn = model.source.kind === 'turn'
45 const isPaged = entries.length > Limits.MAX_VISIBLE_FILES
46 const picked = entries.find(entry => entry.path === model.selectedPath)
47 const selected = picked ?? entries[0]
48 const isDetail = model.dialogView === 'detail' && picked !== undefined
49
50 const start = dialogWindowOf(
51 model,
52 entries.map(entry => entry.path),
53 )
54
55 const shown = entries.slice(start, start + Limits.MAX_VISIBLE_FILES)
56 const blankEdge = Sections.dimNote(kit, ' ')
57 const below = entries.length - start - shown.length
58
59 const isTooMany =
60 !isTurn &&
61 data !== null &&
62 data.stats.filesCount > 0 &&
63 entries.length === 0
64
65 const emptyWords = isTurn
66 ? 'No file changes in this turn'
67 : isTooMany
68 ? 'Too many files to display details'
69 : 'No changes yet'
70
71 const message = entries.length > 0 ? null : emptyWords
72
73 const subtitle = totals && (
74 <Text dimColor wrap="truncate-end">
75 {`${Layout.plural(totals.filesCount, 'file')} changed `}
76 {Sections.diffStat(kit, totals.linesAdded, totals.linesRemoved)}
77 </Text>
78 )
79
80 const noteOf = (entry: Entries.BodyEntry) =>
81 entry.isUntracked ? 'untracked' : entry.isBinary ? 'Binary file' : null
82
83 const rowOf = (entry: Entries.BodyEntry) =>
84 Sections.dialogFileRow(
85 kit,
86 {
87 key: Sections.fileKeyOf(entry),
88 path: entry.path,
89 displayPath: entry.displayPath,
90 added: entry.added,
91 removed: entry.removed,
92 note: noteOf(entry),
93 isSelected: entry === selected,
94 },
95 () => kit.actions.selectFile(entry.path),
96 )
97
98 function moreOf(way: 'up' | 'down', count: number): RenderElement {
99 const arrow = way === 'up' ? '\u2191' : '\u2193'
100 const words = Layout.plural(count, 'file').replace(' ', ' more ')
101
102 return Sections.dimNote(kit, ` ${arrow} ${words}`)
103 }
104
105 const pagedEdgeOf = (way: 'up' | 'down', count: number) =>
106 count > 0 ? moreOf(way, count) : blankEdge
107
108 const edgeOf = (way: 'up' | 'down', count: number) =>
109 isPaged ? pagedEdgeOf(way, count) : null
110
111 const detailOf = (entry: Entries.BodyEntry): RenderElement[] => [
112 Detail.detailView(
113 kit,
114 Entries.detailModelOf(entry, model),
115 Detail.BODY_BUDGET,
116 ).element,
117 ]
118
119 const rows = Sections.present([
120 edgeOf('up', start),
121 ...shown.map(rowOf),
122 edgeOf('down', below),
123 ])
124
125 const listing = isDetail && picked ? detailOf(picked) : rows
126 const body = message === null ? listing : [Sections.dimNote(kit, message)]
127
128 const hint = isDetail
129 ? '\u2191/\u2193 to scroll \u00b7 Esc to back'
130 : '\u2191/\u2193 to select \u00b7 Enter to view \u00b7 Esc to close'
131
132 return (
133 <Box flexDirection="column">
134 {Sections.present([
135 Sections.titleRow(kit, PaneState.dialogTitleOf(model)),
136 <Box height={1} />,
137 subtitle,
138 Sections.sourcePicker(kit, model),
139 <Box flexDirection="column" marginTop={1} marginBottom={1}>
140 {body}
141 </Box>,
142 Sections.dimNote(kit, hint),
143 ])}
144 </Box>
145 )
146}