init claude-code
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
import chalk from 'chalk'
|
||||
import cliBoxes, { type Boxes, type BoxStyle } from 'cli-boxes'
|
||||
import { applyColor } from './colorize.js'
|
||||
import type { DOMNode } from './dom.js'
|
||||
import type Output from './output.js'
|
||||
import { stringWidth } from './stringWidth.js'
|
||||
import type { Color } from './styles.js'
|
||||
|
||||
export type BorderTextOptions = {
|
||||
content: string // Pre-rendered string with ANSI color codes
|
||||
position: 'top' | 'bottom'
|
||||
align: 'start' | 'end' | 'center'
|
||||
offset?: number // Only used with 'start' or 'end' alignment. Number of characters from the edge.
|
||||
}
|
||||
|
||||
export const CUSTOM_BORDER_STYLES = {
|
||||
dashed: {
|
||||
top: '╌',
|
||||
left: '╎',
|
||||
right: '╎',
|
||||
bottom: '╌',
|
||||
// there aren't any line-drawing characters for dashes unfortunately
|
||||
topLeft: ' ',
|
||||
topRight: ' ',
|
||||
bottomLeft: ' ',
|
||||
bottomRight: ' ',
|
||||
},
|
||||
} as const
|
||||
|
||||
export type BorderStyle =
|
||||
| keyof Boxes
|
||||
| keyof typeof CUSTOM_BORDER_STYLES
|
||||
| BoxStyle
|
||||
|
||||
function embedTextInBorder(
|
||||
borderLine: string,
|
||||
text: string,
|
||||
align: 'start' | 'end' | 'center',
|
||||
offset: number = 0,
|
||||
borderChar: string,
|
||||
): [before: string, text: string, after: string] {
|
||||
const textLength = stringWidth(text)
|
||||
const borderLength = borderLine.length
|
||||
|
||||
if (textLength >= borderLength - 2) {
|
||||
return ['', text.substring(0, borderLength), '']
|
||||
}
|
||||
|
||||
let position: number
|
||||
if (align === 'center') {
|
||||
position = Math.floor((borderLength - textLength) / 2)
|
||||
} else if (align === 'start') {
|
||||
position = offset + 1 // +1 to account for corner character
|
||||
} else {
|
||||
// align === 'end'
|
||||
position = borderLength - textLength - offset - 1 // -1 for corner character
|
||||
}
|
||||
|
||||
// Ensure position is valid
|
||||
position = Math.max(1, Math.min(position, borderLength - textLength - 1))
|
||||
|
||||
const before = borderLine.substring(0, 1) + borderChar.repeat(position - 1)
|
||||
const after =
|
||||
borderChar.repeat(borderLength - position - textLength - 1) +
|
||||
borderLine.substring(borderLength - 1)
|
||||
|
||||
return [before, text, after]
|
||||
}
|
||||
|
||||
function styleBorderLine(
|
||||
line: string,
|
||||
color: Color | undefined,
|
||||
dim: boolean | undefined,
|
||||
): string {
|
||||
let styled = applyColor(line, color)
|
||||
if (dim) {
|
||||
styled = chalk.dim(styled)
|
||||
}
|
||||
return styled
|
||||
}
|
||||
|
||||
const renderBorder = (
|
||||
x: number,
|
||||
y: number,
|
||||
node: DOMNode,
|
||||
output: Output,
|
||||
): void => {
|
||||
if (node.style.borderStyle) {
|
||||
const width = Math.floor(node.yogaNode!.getComputedWidth())
|
||||
const height = Math.floor(node.yogaNode!.getComputedHeight())
|
||||
const box =
|
||||
typeof node.style.borderStyle === 'string'
|
||||
? (CUSTOM_BORDER_STYLES[
|
||||
node.style.borderStyle as keyof typeof CUSTOM_BORDER_STYLES
|
||||
] ?? cliBoxes[node.style.borderStyle as keyof Boxes])
|
||||
: node.style.borderStyle
|
||||
|
||||
const topBorderColor = node.style.borderTopColor ?? node.style.borderColor
|
||||
const bottomBorderColor =
|
||||
node.style.borderBottomColor ?? node.style.borderColor
|
||||
const leftBorderColor = node.style.borderLeftColor ?? node.style.borderColor
|
||||
const rightBorderColor =
|
||||
node.style.borderRightColor ?? node.style.borderColor
|
||||
|
||||
const dimTopBorderColor =
|
||||
node.style.borderTopDimColor ?? node.style.borderDimColor
|
||||
|
||||
const dimBottomBorderColor =
|
||||
node.style.borderBottomDimColor ?? node.style.borderDimColor
|
||||
|
||||
const dimLeftBorderColor =
|
||||
node.style.borderLeftDimColor ?? node.style.borderDimColor
|
||||
|
||||
const dimRightBorderColor =
|
||||
node.style.borderRightDimColor ?? node.style.borderDimColor
|
||||
|
||||
const showTopBorder = node.style.borderTop !== false
|
||||
const showBottomBorder = node.style.borderBottom !== false
|
||||
const showLeftBorder = node.style.borderLeft !== false
|
||||
const showRightBorder = node.style.borderRight !== false
|
||||
|
||||
const contentWidth = Math.max(
|
||||
0,
|
||||
width - (showLeftBorder ? 1 : 0) - (showRightBorder ? 1 : 0),
|
||||
)
|
||||
|
||||
const topBorderLine = showTopBorder
|
||||
? (showLeftBorder ? box.topLeft : '') +
|
||||
box.top.repeat(contentWidth) +
|
||||
(showRightBorder ? box.topRight : '')
|
||||
: ''
|
||||
|
||||
// Handle text in top border
|
||||
let topBorder: string | undefined
|
||||
if (showTopBorder && node.style.borderText?.position === 'top') {
|
||||
const [before, text, after] = embedTextInBorder(
|
||||
topBorderLine,
|
||||
node.style.borderText.content,
|
||||
node.style.borderText.align,
|
||||
node.style.borderText.offset,
|
||||
box.top,
|
||||
)
|
||||
topBorder =
|
||||
styleBorderLine(before, topBorderColor, dimTopBorderColor) +
|
||||
text +
|
||||
styleBorderLine(after, topBorderColor, dimTopBorderColor)
|
||||
} else if (showTopBorder) {
|
||||
topBorder = styleBorderLine(
|
||||
topBorderLine,
|
||||
topBorderColor,
|
||||
dimTopBorderColor,
|
||||
)
|
||||
}
|
||||
|
||||
let verticalBorderHeight = height
|
||||
|
||||
if (showTopBorder) {
|
||||
verticalBorderHeight -= 1
|
||||
}
|
||||
|
||||
if (showBottomBorder) {
|
||||
verticalBorderHeight -= 1
|
||||
}
|
||||
|
||||
verticalBorderHeight = Math.max(0, verticalBorderHeight)
|
||||
|
||||
let leftBorder = (applyColor(box.left, leftBorderColor) + '\n').repeat(
|
||||
verticalBorderHeight,
|
||||
)
|
||||
|
||||
if (dimLeftBorderColor) {
|
||||
leftBorder = chalk.dim(leftBorder)
|
||||
}
|
||||
|
||||
let rightBorder = (applyColor(box.right, rightBorderColor) + '\n').repeat(
|
||||
verticalBorderHeight,
|
||||
)
|
||||
|
||||
if (dimRightBorderColor) {
|
||||
rightBorder = chalk.dim(rightBorder)
|
||||
}
|
||||
|
||||
const bottomBorderLine = showBottomBorder
|
||||
? (showLeftBorder ? box.bottomLeft : '') +
|
||||
box.bottom.repeat(contentWidth) +
|
||||
(showRightBorder ? box.bottomRight : '')
|
||||
: ''
|
||||
|
||||
// Handle text in bottom border
|
||||
let bottomBorder: string | undefined
|
||||
if (showBottomBorder && node.style.borderText?.position === 'bottom') {
|
||||
const [before, text, after] = embedTextInBorder(
|
||||
bottomBorderLine,
|
||||
node.style.borderText.content,
|
||||
node.style.borderText.align,
|
||||
node.style.borderText.offset,
|
||||
box.bottom,
|
||||
)
|
||||
bottomBorder =
|
||||
styleBorderLine(before, bottomBorderColor, dimBottomBorderColor) +
|
||||
text +
|
||||
styleBorderLine(after, bottomBorderColor, dimBottomBorderColor)
|
||||
} else if (showBottomBorder) {
|
||||
bottomBorder = styleBorderLine(
|
||||
bottomBorderLine,
|
||||
bottomBorderColor,
|
||||
dimBottomBorderColor,
|
||||
)
|
||||
}
|
||||
|
||||
const offsetY = showTopBorder ? 1 : 0
|
||||
|
||||
if (topBorder) {
|
||||
output.write(x, y, topBorder)
|
||||
}
|
||||
|
||||
if (showLeftBorder) {
|
||||
output.write(x, y + offsetY, leftBorder)
|
||||
}
|
||||
|
||||
if (showRightBorder) {
|
||||
output.write(x + width - 1, y + offsetY, rightBorder)
|
||||
}
|
||||
|
||||
if (bottomBorder) {
|
||||
output.write(x, y + height - 1, bottomBorder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default renderBorder
|
||||
Reference in New Issue
Block a user