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