source dump of claude code
at main 231 lines 6.6 kB view raw
1import chalk from 'chalk' 2import cliBoxes, { type Boxes, type BoxStyle } from 'cli-boxes' 3import { applyColor } from './colorize.js' 4import type { DOMNode } from './dom.js' 5import type Output from './output.js' 6import { stringWidth } from './stringWidth.js' 7import type { Color } from './styles.js' 8 9export type BorderTextOptions = { 10 content: string // Pre-rendered string with ANSI color codes 11 position: 'top' | 'bottom' 12 align: 'start' | 'end' | 'center' 13 offset?: number // Only used with 'start' or 'end' alignment. Number of characters from the edge. 14} 15 16export const CUSTOM_BORDER_STYLES = { 17 dashed: { 18 top: '╌', 19 left: '╎', 20 right: '╎', 21 bottom: '╌', 22 // there aren't any line-drawing characters for dashes unfortunately 23 topLeft: ' ', 24 topRight: ' ', 25 bottomLeft: ' ', 26 bottomRight: ' ', 27 }, 28} as const 29 30export type BorderStyle = 31 | keyof Boxes 32 | keyof typeof CUSTOM_BORDER_STYLES 33 | BoxStyle 34 35function embedTextInBorder( 36 borderLine: string, 37 text: string, 38 align: 'start' | 'end' | 'center', 39 offset: number = 0, 40 borderChar: string, 41): [before: string, text: string, after: string] { 42 const textLength = stringWidth(text) 43 const borderLength = borderLine.length 44 45 if (textLength >= borderLength - 2) { 46 return ['', text.substring(0, borderLength), ''] 47 } 48 49 let position: number 50 if (align === 'center') { 51 position = Math.floor((borderLength - textLength) / 2) 52 } else if (align === 'start') { 53 position = offset + 1 // +1 to account for corner character 54 } else { 55 // align === 'end' 56 position = borderLength - textLength - offset - 1 // -1 for corner character 57 } 58 59 // Ensure position is valid 60 position = Math.max(1, Math.min(position, borderLength - textLength - 1)) 61 62 const before = borderLine.substring(0, 1) + borderChar.repeat(position - 1) 63 const after = 64 borderChar.repeat(borderLength - position - textLength - 1) + 65 borderLine.substring(borderLength - 1) 66 67 return [before, text, after] 68} 69 70function styleBorderLine( 71 line: string, 72 color: Color | undefined, 73 dim: boolean | undefined, 74): string { 75 let styled = applyColor(line, color) 76 if (dim) { 77 styled = chalk.dim(styled) 78 } 79 return styled 80} 81 82const renderBorder = ( 83 x: number, 84 y: number, 85 node: DOMNode, 86 output: Output, 87): void => { 88 if (node.style.borderStyle) { 89 const width = Math.floor(node.yogaNode!.getComputedWidth()) 90 const height = Math.floor(node.yogaNode!.getComputedHeight()) 91 const box = 92 typeof node.style.borderStyle === 'string' 93 ? (CUSTOM_BORDER_STYLES[ 94 node.style.borderStyle as keyof typeof CUSTOM_BORDER_STYLES 95 ] ?? cliBoxes[node.style.borderStyle as keyof Boxes]) 96 : node.style.borderStyle 97 98 const topBorderColor = node.style.borderTopColor ?? node.style.borderColor 99 const bottomBorderColor = 100 node.style.borderBottomColor ?? node.style.borderColor 101 const leftBorderColor = node.style.borderLeftColor ?? node.style.borderColor 102 const rightBorderColor = 103 node.style.borderRightColor ?? node.style.borderColor 104 105 const dimTopBorderColor = 106 node.style.borderTopDimColor ?? node.style.borderDimColor 107 108 const dimBottomBorderColor = 109 node.style.borderBottomDimColor ?? node.style.borderDimColor 110 111 const dimLeftBorderColor = 112 node.style.borderLeftDimColor ?? node.style.borderDimColor 113 114 const dimRightBorderColor = 115 node.style.borderRightDimColor ?? node.style.borderDimColor 116 117 const showTopBorder = node.style.borderTop !== false 118 const showBottomBorder = node.style.borderBottom !== false 119 const showLeftBorder = node.style.borderLeft !== false 120 const showRightBorder = node.style.borderRight !== false 121 122 const contentWidth = Math.max( 123 0, 124 width - (showLeftBorder ? 1 : 0) - (showRightBorder ? 1 : 0), 125 ) 126 127 const topBorderLine = showTopBorder 128 ? (showLeftBorder ? box.topLeft : '') + 129 box.top.repeat(contentWidth) + 130 (showRightBorder ? box.topRight : '') 131 : '' 132 133 // Handle text in top border 134 let topBorder: string | undefined 135 if (showTopBorder && node.style.borderText?.position === 'top') { 136 const [before, text, after] = embedTextInBorder( 137 topBorderLine, 138 node.style.borderText.content, 139 node.style.borderText.align, 140 node.style.borderText.offset, 141 box.top, 142 ) 143 topBorder = 144 styleBorderLine(before, topBorderColor, dimTopBorderColor) + 145 text + 146 styleBorderLine(after, topBorderColor, dimTopBorderColor) 147 } else if (showTopBorder) { 148 topBorder = styleBorderLine( 149 topBorderLine, 150 topBorderColor, 151 dimTopBorderColor, 152 ) 153 } 154 155 let verticalBorderHeight = height 156 157 if (showTopBorder) { 158 verticalBorderHeight -= 1 159 } 160 161 if (showBottomBorder) { 162 verticalBorderHeight -= 1 163 } 164 165 verticalBorderHeight = Math.max(0, verticalBorderHeight) 166 167 let leftBorder = (applyColor(box.left, leftBorderColor) + '\n').repeat( 168 verticalBorderHeight, 169 ) 170 171 if (dimLeftBorderColor) { 172 leftBorder = chalk.dim(leftBorder) 173 } 174 175 let rightBorder = (applyColor(box.right, rightBorderColor) + '\n').repeat( 176 verticalBorderHeight, 177 ) 178 179 if (dimRightBorderColor) { 180 rightBorder = chalk.dim(rightBorder) 181 } 182 183 const bottomBorderLine = showBottomBorder 184 ? (showLeftBorder ? box.bottomLeft : '') + 185 box.bottom.repeat(contentWidth) + 186 (showRightBorder ? box.bottomRight : '') 187 : '' 188 189 // Handle text in bottom border 190 let bottomBorder: string | undefined 191 if (showBottomBorder && node.style.borderText?.position === 'bottom') { 192 const [before, text, after] = embedTextInBorder( 193 bottomBorderLine, 194 node.style.borderText.content, 195 node.style.borderText.align, 196 node.style.borderText.offset, 197 box.bottom, 198 ) 199 bottomBorder = 200 styleBorderLine(before, bottomBorderColor, dimBottomBorderColor) + 201 text + 202 styleBorderLine(after, bottomBorderColor, dimBottomBorderColor) 203 } else if (showBottomBorder) { 204 bottomBorder = styleBorderLine( 205 bottomBorderLine, 206 bottomBorderColor, 207 dimBottomBorderColor, 208 ) 209 } 210 211 const offsetY = showTopBorder ? 1 : 0 212 213 if (topBorder) { 214 output.write(x, y, topBorder) 215 } 216 217 if (showLeftBorder) { 218 output.write(x, y + offsetY, leftBorder) 219 } 220 221 if (showRightBorder) { 222 output.write(x + width - 1, y + offsetY, rightBorder) 223 } 224 225 if (bottomBorder) { 226 output.write(x, y + height - 1, bottomBorder) 227 } 228 } 229} 230 231export default renderBorder