OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / distribute / hypertps-win64 / lib / style.js

134 lines · javascript ref:
import ansi from 'bare-ansi-escapes'

const { SGR } = ansi.constants

// --- Text styling (wraps string with SGR codes) ---

export function bold (s) { return ansi.modifierBold + s + ansi.modifierNormal }
export function dim (s) { return ansi.modifierDim + s + ansi.modifierNormal }
export function italic (s) { return ansi.modifierItalic + s + ansi.modifierNotItalic }
export function underline (s) { return ansi.modifierUnderline + s + ansi.modifierNotUnderline }

export function red (s) { return ansi.colorRed + s + ansi.colorDefault }
export function green (s) { return ansi.colorGreen + s + ansi.colorDefault }
export function yellow (s) { return ansi.colorYellow + s + ansi.colorDefault }
export function blue (s) { return ansi.colorBlue + s + ansi.colorDefault }
export function magenta (s) { return ansi.colorMagenta + s + ansi.colorDefault }
export function cyan (s) { return ansi.colorCyan + s + ansi.colorDefault }
export function white (s) { return ansi.colorWhite + s + ansi.colorDefault }
export function brightBlack (s) { return ansi.colorBrightBlack + s + ansi.colorDefault }
export function brightCyan (s) { return ansi.colorBrightCyan + s + ansi.colorDefault }
export function brightGreen (s) { return ansi.colorBrightGreen + s + ansi.colorDefault }
export function brightYellow (s) { return ansi.colorBrightYellow + s + ansi.colorDefault }

export function bg256 (n, s) { return SGR('48;5;' + n) + s + SGR(49) }
export function fg256 (n, s) { return SGR('38;5;' + n) + s + SGR(39) }

export function invert (s) { return SGR(7) + s + SGR(27) }

// --- String measurement (strips ANSI) ---

const ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g

export function stripAnsi (s) {
  return s.replace(ANSI_RE, '')
}

export function visibleLength (s) {
  return stripAnsi(s).length
}

// --- Layout helpers ---

export function truncate (s, maxWidth) {
  if (maxWidth <= 0) return ''
  const stripped = stripAnsi(s)
  if (stripped.length <= maxWidth) return s

  // Walk through, tracking visible chars
  let visible = 0
  let result = ''
  let i = 0
  const raw = s
  while (i < raw.length && visible < maxWidth - 1) {
    if (raw[i] === '\x1b') {
      const end = raw.indexOf('m', i)
      if (end !== -1) {
        result += raw.slice(i, end + 1)
        i = end + 1
        continue
      }
    }
    result += raw[i]
    visible++
    i++
  }
  return result + ansi.modifierReset + '\u2026'
}

export function padRight (s, width) {
  const vl = visibleLength(s)
  if (vl >= width) return s
  return s + ' '.repeat(width - vl)
}

export function padLeft (s, width) {
  const vl = visibleLength(s)
  if (vl >= width) return s
  return ' '.repeat(width - vl) + s
}

export function center (s, width) {
  const vl = visibleLength(s)
  if (vl >= width) return s
  const left = Math.floor((width - vl) / 2)
  const right = width - vl - left
  return ' '.repeat(left) + s + ' '.repeat(right)
}

// --- Box drawing ---

const BOX = {
  rounded: { tl: '\u256d', tr: '\u256e', bl: '\u2570', br: '\u256f', h: '\u2500', v: '\u2502' },
  single: { tl: '\u250c', tr: '\u2510', bl: '\u2514', br: '\u2518', h: '\u2500', v: '\u2502' },
  double: { tl: '\u2554', tr: '\u2557', bl: '\u255a', br: '\u255d', h: '\u2550', v: '\u2551' },
  heavy: { tl: '\u250f', tr: '\u2513', bl: '\u2517', br: '\u251b', h: '\u2501', v: '\u2503' }
}

export function hline (width, style = 'single') {
  const ch = (BOX[style] || BOX.single).h
  return ch.repeat(width)
}

export function vline (style = 'single') {
  return (BOX[style] || BOX.single).v
}

export function box (lines, width, opts = {}) {
  const style = opts.border || 'rounded'
  const title = opts.title || ''
  const b = BOX[style] || BOX.rounded

  const inner = width - 2
  if (inner <= 0) return lines.map(() => '')

  let topLine = b.tl
  if (title) {
    const t = ' ' + title + ' '
    topLine += t + b.h.repeat(Math.max(0, inner - t.length))
  } else {
    topLine += b.h.repeat(inner)
  }
  topLine += b.tr

  const bottom = b.bl + b.h.repeat(inner) + b.br

  const result = [topLine]
  for (const line of lines) {
    const content = truncate(padRight(line, inner), inner)
    result.push(b.v + content + b.v)
  }
  result.push(bottom)
  return result
}