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