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
| @@ -4,18 +4,20 @@ | ||
| 4 | 4 | import type { RenderElement } from 'claude-code' |
| 5 | 5 | |
| 6 | 6 | import Limits from '../../limits' |
| 7 | +import Detail from '../detail' | |
| 7 | 8 | import type { Kit } from '../kit' |
| 9 | +import Layout from '../layout' | |
| 8 | 10 | import Sections from '../sections' |
| 9 | 11 | import Plan from './plan' |
| 10 | -import type { BodyLayout, Segment } from './types' | |
| 12 | +import type { BodyLayout, HunkSegment, Segment, WindowCut } from './types' | |
| 11 | 13 | |
| 12 | 14 | /** |
| 13 | 15 | * The rows of the docked body its window shows from a row down: each |
| 14 | 16 | * segment drawn from the row the window cuts it at, until the window fills. |
| 15 | 17 | * |
| 16 | - * A hunk cut by the window's top draws from the line holding that row on; | |
| 17 | - * rows past its foot are the pane's to clip, so a few more are drawn than | |
| 18 | - * fit (SCROLL_MARGIN_ROWS) lest a miscounted wrap leave it short. | |
| 18 | + * A hunk draws only its lines in view, and past MAX_BODY_CHARS of hunk | |
| 19 | + * text the rest waits for a scroll (the tree's cap); rows past the foot | |
| 20 | + * are the pane's to clip, so SCROLL_MARGIN_ROWS more are drawn than fit. | |
| 19 | 21 | * |
| 20 | 22 | * @param kit the elements, the handlers, the width |
| 21 | 23 | * @param layout the body laid out |
| @@ -27,7 +29,7 @@ | ||
| 27 | 29 | layout: BodyLayout, |
| 28 | 30 | top: number, |
| 29 | 31 | ): RenderElement[] { |
| 30 | - const { Box, Text } = kit.ui | |
| 32 | + const { Box, Text, Code } = kit.ui | |
| 31 | 33 | const drawn: RenderElement[] = [] |
| 32 | 34 | const wanted = layout.visibleRows + Limits.SCROLL_MARGIN_ROWS |
| 33 | 35 | const at = Math.max(0, Math.min(top, layout.maxTop)) |
| @@ -35,8 +37,25 @@ | ||
| 35 | 37 | |
| 36 | 38 | let start = 0 |
| 37 | 39 | let filled = 0 |
| 40 | + let chars = 0 | |
| 38 | 41 | |
| 39 | - function elementOf(segment: Segment, skip: number): RenderElement { | |
| 42 | + function codeOf(segment: HunkSegment, cut: WindowCut): RenderElement { | |
| 43 | + const source = Detail.hunkSourceOf(Plan.hunkWindowOf(segment, cut)) | |
| 44 | + const path = Layout.sanitizeName(segment.path) | |
| 45 | + chars += source.length | |
| 46 | + | |
| 47 | + const isOver = chars > Detail.MAX_BODY_CHARS | |
| 48 | + | |
| 49 | + return isOver ? ( | |
| 50 | + <Text dimColor italic> | |
| 51 | + … | |
| 52 | + </Text> | |
| 53 | + ) : ( | |
| 54 | + <Code source={source} format="diff" path={path} /> | |
| 55 | + ) | |
| 56 | + } | |
| 57 | + | |
| 58 | + function elementOf(segment: Segment, cut: WindowCut): RenderElement { | |
| 40 | 59 | switch (segment.kind) { |
| 41 | 60 | case 'rule': |
| 42 | 61 | return Sections.divider(kit) |
| @@ -45,7 +64,7 @@ | ||
| 45 | 64 | case 'name': |
| 46 | 65 | return Sections.nameRow(kit, segment.name) |
| 47 | 66 | case 'hunk': |
| 48 | - return Plan.hunkCodeOf(kit, segment, skip) | |
| 67 | + return codeOf(segment, cut) | |
| 49 | 68 | case 'note': |
| 50 | 69 | return ( |
| 51 | 70 | <Text dimColor italic wrap="wrap"> |
| @@ -74,7 +93,7 @@ | ||
| 74 | 93 | if (isShown) { |
| 75 | 94 | const skip = Math.max(0, at - start) |
| 76 | 95 | |
| 77 | - drawn.push(elementOf(segment, skip)) | |
| 96 | + drawn.push(elementOf(segment, { skip, take: wanted - filled })) | |
| 78 | 97 | filled += rows - skip |
| 79 | 98 | } |
| 80 | 99 |
| @@ -1,47 +1,0 @@ | ||
| 1 | -/* @jsxRuntime classic */ | |
| 2 | -/* @jsx h */ | |
| 3 | -/* @jsxFrag Fragment */ | |
| 4 | -import type { RenderElement } from 'claude-code' | |
| 5 | - | |
| 6 | -import Git from '../../../git' | |
| 7 | -import Detail from '../../detail' | |
| 8 | -import type { Kit } from '../../kit' | |
| 9 | -import Layout from '../../layout' | |
| 10 | -import type { HunkSegment } from '../types' | |
| 11 | - | |
| 12 | -/** | |
| 13 | - * One hunk of the docked body as the engine's diff `Code` block, drawn from | |
| 14 | - * the line holding a wrapped row of it on (hunkAfter); whole at row 0. | |
| 15 | - * | |
| 16 | - * @param kit the elements | |
| 17 | - * @param segment the hunk, whose file it is, the rows its lines take | |
| 18 | - * @param skip the wrapped rows the window's top cuts from it | |
| 19 | - * @returns the block | |
| 20 | - */ | |
| 21 | -export function hunkCodeOf( | |
| 22 | - kit: Kit, | |
| 23 | - segment: HunkSegment, | |
| 24 | - skip: number, | |
| 25 | -): RenderElement { | |
| 26 | - const { Code } = kit.ui | |
| 27 | - | |
| 28 | - let passed = 0 | |
| 29 | - let first = 0 | |
| 30 | - | |
| 31 | - for (const rows of segment.lineRows) { | |
| 32 | - if (passed + rows > skip) { | |
| 33 | - break | |
| 34 | - } | |
| 35 | - | |
| 36 | - passed += rows | |
| 37 | - first += 1 | |
| 38 | - } | |
| 39 | - | |
| 40 | - return ( | |
| 41 | - <Code | |
| 42 | - source={Detail.hunkSourceOf(Git.hunkAfter(segment.hunk, first))} | |
| 43 | - format="diff" | |
| 44 | - path={Layout.sanitizeName(segment.path).slice(-Detail.MAX_CODE_CHARS)} | |
| 45 | - /> | |
| 46 | - ) | |
| 47 | -} |
| @@ -1,0 +1,32 @@ | ||
| 1 | +import Git from '../../../git' | |
| 2 | +import type { HunkSegment, WindowCut } from '../types' | |
| 3 | + | |
| 4 | +/** | |
| 5 | + * The part of a hunk the window shows: from the line holding its first | |
| 6 | + * shown row (hunkAfter renumbers) to the last that starts in the room left. | |
| 7 | + * | |
| 8 | + * @param segment the hunk and the rows each of its lines takes | |
| 9 | + * @param cut the rows hidden above the window and the rows it has left | |
| 10 | + * @returns the hunk's shown lines | |
| 11 | + */ | |
| 12 | +export function hunkWindowOf(segment: HunkSegment, cut: WindowCut): Git.Hunk { | |
| 13 | + let passed = 0 | |
| 14 | + let first = 0 | |
| 15 | + let shown = 0 | |
| 16 | + let last = 0 | |
| 17 | + | |
| 18 | + for (const rows of segment.lineRows) { | |
| 19 | + if (passed + rows <= cut.skip) { | |
| 20 | + first += 1 | |
| 21 | + } else if (shown < cut.take) { | |
| 22 | + shown += rows | |
| 23 | + last += 1 | |
| 24 | + } | |
| 25 | + | |
| 26 | + passed += rows | |
| 27 | + } | |
| 28 | + | |
| 29 | + const tail = Git.hunkAfter(segment.hunk, first) | |
| 30 | + | |
| 31 | + return { ...tail, lines: tail.lines.slice(0, last) } | |
| 32 | +} |
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | export * from './body-layout-of.js' |
| 2 | 2 | export * from './dock-plan-of.js' |
| 3 | -export * from './hunk-code-of.jsx' | |
| 3 | +export * from './hunk-window-of.js' | |
| 4 | 4 | export * from './segments' |
| 5 | 5 | |
| 6 | 6 | export * as default from '.' |
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | import { countOf } from '../../../../count-of' |
| 2 | -import type Git from '../../../../git' | |
| 2 | +import Git from '../../../../git' | |
| 3 | 3 | import { keptOf } from '../../../../kept-of' |
| 4 | 4 | import Limits from '../../../../limits' |
| 5 | 5 | import type PaneState from '../../../../pane-state' |
| @@ -13,8 +13,8 @@ | ||
| 13 | 13 | * box): each listed file's block, a blank between, the pre-session section. |
| 14 | 14 | * |
| 15 | 15 | * Under an empty state whose pre-session section is open, the empty lines |
| 16 | - * head the body instead. A hunk carries the rows its lines take wrapped | |
| 17 | - * past the engine's gutter at the body's width; pre-session bodies stack. | |
| 16 | + * head the body instead. Hunks are made printable and split under a leaf's | |
| 17 | + * cap (subHunksOf), each carrying the rows its lines take wrapped. | |
| 18 | 18 | * |
| 19 | 19 | * @param model the pane's state: which file is armed, which section is open |
| 20 | 20 | * @param plan the docked pane's decisions over that state |
| @@ -64,14 +64,27 @@ | ||
| 64 | 64 | } |
| 65 | 65 | } |
| 66 | 66 | |
| 67 | + const splitOf = (hunk: Git.Hunk) => | |
| 68 | + Detail.subHunksOf( | |
| 69 | + { | |
| 70 | + ...hunk, | |
| 71 | + lines: hunk.lines.filter(Git.isBodyLine).map(Detail.drawnLineOf), | |
| 72 | + }, | |
| 73 | + Detail.MAX_CODE_CHARS, | |
| 74 | + ) | |
| 75 | + | |
| 67 | 76 | function fileOf(entry: Entries.BodyEntry): Segment[] { |
| 68 | 77 | const detail = Entries.detailModelOf(entry, model) |
| 69 | 78 | const notes = Detail.placeholderOf(detail) |
| 70 | - const isTruncated = entry.body?.isTruncated === true | |
| 79 | + const splits = (entry.body?.hunks ?? []).map(splitOf) | |
| 71 | 80 | |
| 81 | + const isTruncated = | |
| 82 | + entry.body?.isTruncated === true || | |
| 83 | + splits.some(split => split.isTruncated) | |
| 84 | + | |
| 72 | 85 | const body: Segment[] = notes |
| 73 | 86 | ? notes.map(text => ({ kind: 'note', text })) |
| 74 | - : (entry.body?.hunks ?? []).map(hunk => hunkOf(entry, hunk)) | |
| 87 | + : splits.flatMap(split => split.hunks).map(hunk => hunkOf(entry, hunk)) | |
| 75 | 88 | |
| 76 | 89 | return [ |
| 77 | 90 | { kind: 'rule', path: entry.path }, |
| @@ -2,5 +2,6 @@ | ||
| 2 | 2 | export type * from './dock-plan.js' |
| 3 | 3 | export type * from './list-window.js' |
| 4 | 4 | export * from './segment' |
| 5 | +export * from './window-cut' | |
| 5 | 6 | |
| 6 | 7 | export * as default from '.' |
| @@ -1,0 +1,3 @@ | ||
| 1 | +export type * from './window-cut.js' | |
| 2 | + | |
| 3 | +export * as default from '.' |
| @@ -1,0 +1,8 @@ | ||
| 1 | +/** | |
| 2 | + * How the body's window cuts one hunk: the wrapped rows its top hides, and | |
| 3 | + * the rows it still has room for from there. | |
| 4 | + */ | |
| 5 | +export type WindowCut = { | |
| 6 | + skip: number | |
| 7 | + take: number | |
| 8 | +} |