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 / current-pane / current-pane.tsx
175 lines · 5.3 KBRaw
1/* @jsxRuntime classic */
2/* @jsx h */
3/* @jsxFrag Fragment */
4import type { RenderElement } from 'claude-code'
5
6import type Git from '../../git'
7import Names from '../../names'
8import PaneState from '../../pane-state'
9import Detail from '../detail'
10import type { Kit } from '../kit'
11import Layout from '../layout'
12import Sections from '../sections'
13import { fileRowOf } from './file-row-of'
14import { listBodyOf } from './list-body-of'
15import { messagePaneOf } from './message-pane-of'
16
17/**
18 * The pane over the repository's diff now (ReplDiffSidebarBody, one file's
19 * body at a time): header, base line, todo bar, pickers, rows, detail.
20 *
21 * The toggle sits above the rows; the elision count, withheld-untracked
22 * note and pre-session line below, with blank rows as the built-in leaves.
23 * With nothing to list, messagePaneOf draws the body, the header a close.
24 *
25 * @param kit the elements, the handlers, the width, the rows
26 * @param model the pane's state
27 * @returns the pane's tree
28 */
29export function currentPane(
30 kit: Kit,
31 model: PaneState.PaneModel,
32): RenderElement {
33 const { Box } = kit.ui
34 const { data } = model
35
36 if (model.isOutsideRepository) {
37 return <Box>{Sections.dimNote(kit, Names.NOT_IN_REPOSITORY_TEXT)}</Box>
38 }
39
40 if (!data && !model.hasSettled) {
41 return messagePaneOf(kit, {
42 top: [Sections.headerView(kit, null)],
43 message: ['Loading diff…'],
44 controls: null,
45 earlier: null,
46 rest: [],
47 })
48 }
49
50 const noise = model.isNoiseShown ? 'shown' : 'hidden'
51 const preSession = model.isPreSessionShown ? 'shown' : 'hidden'
52 const partition = PaneState.partitionOf(data?.files ?? [], noise)
53
54 const totals = data
55 ? PaneState.headerTotalsOf(data, partition)
56 : PaneState.ZERO_TOTALS
57
58 const empty = PaneState.emptyStateOf(data, totals.filesCount, model.words)
59 const baseLabel = PaneState.baseLabelOf(model, totals.filesCount)
60
61 const selected = PaneState.selectionOf(
62 PaneState.listedOf(partition, preSession),
63 model.selectedPath,
64 )
65
66 const rowOf = (file: Git.FileStat): RenderElement =>
67 Sections.fileRow(kit, fileRowOf(file, selected?.path ?? null), () =>
68 kit.actions.selectFile(file.path),
69 )
70
71 const noteOf = (text: string | null): RenderElement | null =>
72 text === null ? null : Sections.dimNote(kit, Layout.sanitizeName(text))
73
74 const noiseFace = model.isNoiseShown ? 'hide' : 'show'
75 const noiseCount = Layout.plural(partition.noiseCount, 'test')
76 const hasNoise = partition.noiseCount > 0
77
78 const noiseToggle = hasNoise
79 ? Sections.toggleRow(kit, 'noise', {
80 label: `${noiseCount}/generated (${noiseFace})`,
81 onPress: kit.actions.toggleNoise,
82 })
83 : null
84
85 const earlierFace = model.isPreSessionShown ? 'hide' : 'show'
86 const earlierCount = Layout.plural(partition.preSession.length, 'file')
87 const hasEarlier = partition.preSession.length > 0
88
89 const earlierToggle = hasEarlier
90 ? Sections.toggleRow(kit, 'presession', {
91 label: `+${earlierCount} edited before this session (${earlierFace})`,
92 onPress: kit.actions.togglePreSession,
93 })
94 : null
95
96 const earlierFiles = model.isPreSessionShown ? partition.preSession : []
97 const isEarlierListed = earlierFiles.length > 0
98
99 const earlierRows = isEarlierListed
100 ? [<Box height={1} />, ...earlierFiles.map(rowOf)]
101 : []
102
103 const isUntrackedNoted = data?.isUntrackedWithheld === true && !empty
104 const listed = listBodyOf(kit, { data, partition, totals, empty })
105 const notes = listed.filter(line => typeof line === 'string')
106 const hasRows = notes.length < listed.length
107 const message = empty ? [empty.headline, ...notes] : hasRows ? [] : notes
108 const header = Sections.headerView(kit, empty ? null : totals)
109
110 const notShownNote = noteOf(
111 totals.notShown > 0 ? `${totals.notShown} not shown` : null,
112 )
113
114 const untrackedNote = noteOf(
115 isUntrackedNoted ? Names.untrackedWithheldTextOf(model.words) : null,
116 )
117
118 const detail = selected
119 ? [
120 Sections.divider(kit),
121 Detail.detailView(
122 kit,
123 {
124 words: model.words,
125 path: selected.path,
126 renamedFrom: selected.renamedFrom,
127 isUntracked: selected.isUntracked,
128 isBinary: selected.isBinary,
129 body: model.body,
130 bodyState: model.bodyState,
131 isArmed: model.armedPath === selected.path,
132 },
133 () => kit.actions.toggleAsk(selected.path),
134 ),
135 ]
136 : []
137
138 const isMessageShown = message.length > 0
139
140 return isMessageShown ? (
141 messagePaneOf(kit, {
142 top: Sections.present([
143 header,
144 noteOf(baseLabel),
145 Sections.todoBar(kit, model),
146 noiseToggle,
147 notShownNote,
148 untrackedNote,
149 ]),
150 message,
151 controls: Sections.controlsView(kit, model),
152 earlier: earlierToggle,
153 rest: [...earlierRows, ...detail],
154 })
155 ) : (
156 <Box flexDirection="column">
157 {Sections.present([
158 header,
159 noteOf(baseLabel),
160 Sections.todoBar(kit, model),
161 Sections.controlsView(kit, model),
162 noiseToggle,
163 ...listed.map(line =>
164 typeof line === 'string' ? noteOf(line) : rowOf(line),
165 ),
166 notShownNote,
167 untrackedNote,
168 hasEarlier ? <Box height={1} /> : null,
169 earlierToggle,
170 ...earlierRows,
171 ...detail,
172 ])}
173 </Box>
174 )
175}