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
| 1 | /* @jsxRuntime classic */ |
| 2 | /* @jsx h */ |
| 3 | /* @jsxFrag Fragment */ |
| 4 | import type { RenderElement } from 'claude-code' |
| 5 | |
| 6 | import Limits from '../../limits' |
| 7 | import PaneState from '../../pane-state' |
| 8 | import Detail from '../detail' |
| 9 | import Entries from '../entries' |
| 10 | import type { Kit } from '../kit' |
| 11 | import Layout from '../layout' |
| 12 | import { preludeOf } from '../prelude-of' |
| 13 | import Sections from '../sections' |
| 14 | import { dialogEntriesOf } from './dialog-entries-of' |
| 15 | import { 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 | */ |
| 29 | export 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 | } |