Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
8.6 kB · 231 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232import noop from 'lodash-es/noop.js'import type { ReactElement } from 'react'import { LegacyRoot } from 'react-reconciler/constants.js'import { logForDebugging } from '../utils/debug.js'import { createNode, type DOMElement } from './dom.js'import { FocusManager } from './focus.js'import Output from './output.js'import reconciler from './reconciler.js'import renderNodeToOutput, { resetLayoutShifted,} from './render-node-to-output.js'import { CellWidth, CharPool, cellAtIndex, createScreen, HyperlinkPool, type Screen, StylePool, setCellStyleId,} from './screen.js'
/** Position of a match within a rendered message, relative to the message's * own bounding box (row 0 = message top). Stable across scroll — to * highlight on the real screen, add the message's screen-row offset. */export type MatchPosition = { row: number col: number /** Number of CELLS the match spans (= query.length for ASCII, more * for wide chars in the query). */ len: number}
// Shared across calls. Pools accumulate style/char interns — reusing them// means later calls hit cache more. Root/container reuse saves the// createContainer cost (~1ms). LegacyRoot: all work sync, no scheduling —// ConcurrentRoot's scheduler backlog leaks across roots via flushSyncWork.let root: DOMElement | undefinedlet container: ReturnType<typeof reconciler.createContainer> | undefinedlet stylePool: StylePool | undefinedlet charPool: CharPool | undefinedlet hyperlinkPool: HyperlinkPool | undefinedlet output: Output | undefined
const timing = { reconcile: 0, yoga: 0, paint: 0, scan: 0, calls: 0 }const LOG_EVERY = 20
/** Render a React element (wrapped in all contexts the component needs — * caller's job) to an isolated Screen buffer at the given width. Returns * the Screen + natural height (from yoga). Used for search: render ONE * message, scan its Screen for the query, get exact (row, col) positions. * * ~1-3ms per call (yoga alloc + calculateLayout + paint). The * flushSyncWork cross-root leak measured ~0.0003ms/call growth — fine * for on-demand single-message rendering, pathological for render-all- * 8k-upfront. Cache per (msg, query, width) upstream. * * Unmounts between calls. Root/container/pools persist for reuse. */export function renderToScreen( el: ReactElement, width: number,): { screen: Screen; height: number } { if (!root) { root = createNode('ink-root') root.focusManager = new FocusManager(() => false) stylePool = new StylePool() charPool = new CharPool() hyperlinkPool = new HyperlinkPool() // @ts-expect-error react-reconciler 0.33 takes 10 args; @types says 11 container = reconciler.createContainer( root, LegacyRoot, null, false, null, 'search-render', noop, noop, noop, noop, ) }
const t0 = performance.now() // @ts-expect-error updateContainerSync exists but not in @types reconciler.updateContainerSync(el, container, null, noop) // @ts-expect-error flushSyncWork exists but not in @types reconciler.flushSyncWork() const t1 = performance.now()
// Yoga layout. Root might not have a yogaNode if the tree is empty. root.yogaNode?.setWidth(width) root.yogaNode?.calculateLayout(width) const height = Math.ceil(root.yogaNode?.getComputedHeight() ?? 0) const t2 = performance.now()
// Paint to a fresh Screen. Width = given, height = yoga's natural. // No alt-screen, no prevScreen (every call is fresh). const screen = createScreen( width, Math.max(1, height), // avoid 0-height Screen (createScreen may choke) stylePool!, charPool!, hyperlinkPool!, ) if (!output) { output = new Output({ width, height, stylePool: stylePool!, screen }) } else { output.reset(width, height, screen) } resetLayoutShifted() renderNodeToOutput(root, output, { prevScreen: undefined }) // renderNodeToOutput queues writes into Output; .get() flushes the // queue into the Screen's cell arrays. Without this the screen is // blank (constructor-zero). const rendered = output.get() const t3 = performance.now()
// Unmount so next call gets a fresh tree. Leaves root/container/pools. // @ts-expect-error updateContainerSync exists but not in @types reconciler.updateContainerSync(null, container, null, noop) // @ts-expect-error flushSyncWork exists but not in @types reconciler.flushSyncWork()
timing.reconcile += t1 - t0 timing.yoga += t2 - t1 timing.paint += t3 - t2 if (++timing.calls % LOG_EVERY === 0) { const total = timing.reconcile + timing.yoga + timing.paint + timing.scan logForDebugging( `renderToScreen: ${timing.calls} calls · ` + `reconcile=${timing.reconcile.toFixed(1)}ms yoga=${timing.yoga.toFixed(1)}ms ` + `paint=${timing.paint.toFixed(1)}ms scan=${timing.scan.toFixed(1)}ms · ` + `total=${total.toFixed(1)}ms · avg ${(total / timing.calls).toFixed(2)}ms/call`, ) }
return { screen: rendered, height }}
/** Scan a Screen buffer for all occurrences of query. Returns positions * relative to the buffer (row 0 = buffer top). Same cell-skip logic as * applySearchHighlight (SpacerTail/SpacerHead/noSelect) so positions * match what the overlay highlight would find. Case-insensitive. * * For the side-render use: this Screen is the FULL message (natural * height, not viewport-clipped). Positions are stable — to highlight * on the real screen, add the message's screen offset (lo). */export function scanPositions(screen: Screen, query: string): MatchPosition[] { const lq = query.toLowerCase() if (!lq) return [] const qlen = lq.length const w = screen.width const h = screen.height const noSelect = screen.noSelect const positions: MatchPosition[] = []
const t0 = performance.now() for (let row = 0; row < h; row++) { const rowOff = row * w // Same text-build as applySearchHighlight. Keep in sync — or extract // to a shared helper (TODO once both are stable). codeUnitToCell // maps indexOf positions (code units in the LOWERCASED text) to cell // indices in colOf — surrogate pairs (emoji) and multi-unit lowercase // (Turkish İ → i + U+0307) make text.length > colOf.length. let text = '' const colOf: number[] = [] const codeUnitToCell: number[] = [] for (let col = 0; col < w; col++) { const idx = rowOff + col const cell = cellAtIndex(screen, idx) if ( cell.width === CellWidth.SpacerTail || cell.width === CellWidth.SpacerHead || noSelect[idx] === 1 ) { continue } const lc = cell.char.toLowerCase() const cellIdx = colOf.length for (let i = 0; i < lc.length; i++) { codeUnitToCell.push(cellIdx) } text += lc colOf.push(col) } // Non-overlapping — same advance as applySearchHighlight. let pos = text.indexOf(lq) while (pos >= 0) { const startCi = codeUnitToCell[pos]! const endCi = codeUnitToCell[pos + qlen - 1]! const col = colOf[startCi]! const endCol = colOf[endCi]! + 1 positions.push({ row, col, len: endCol - col }) pos = text.indexOf(lq, pos + qlen) } } timing.scan += performance.now() - t0
return positions}
/** Write CURRENT (yellow+bold+underline) at positions[currentIdx] + * rowOffset. OTHER positions are NOT styled here — the scan-highlight * (applySearchHighlight with null hint) does inverse for all visible * matches, including these. Two-layer: scan = 'you could go here', * position = 'you ARE here'. Writing inverse again here would be a * no-op (withInverse idempotent) but wasted work. * * Positions are message-relative (row 0 = message top). rowOffset = * message's current screen-top (lo). Clips outside [0, height). */export function applyPositionedHighlight( screen: Screen, stylePool: StylePool, positions: MatchPosition[], rowOffset: number, currentIdx: number,): boolean { if (currentIdx < 0 || currentIdx >= positions.length) return false const p = positions[currentIdx]! const row = p.row + rowOffset if (row < 0 || row >= screen.height) return false const transform = (id: number) => stylePool.withCurrentMatch(id) const rowOff = row * screen.width for (let col = p.col; col < p.col + p.len; col++) { if (col < 0 || col >= screen.width) continue const cell = cellAtIndex(screen, rowOff + col) setCellStyleId(screen, col, row, transform(cell.styleId)) } return true}