fmt.v

3.5 KB · raw

1//! Presentation primitives shared by the two places Koh prints: the command2//! line and the full-screen history view.3//!4//! Colour lives here so both surfaces use one palette, and `NO_COLOR` /5//! `KOH_NO_COLOR` turn it off for both at once.67module fmt89import os10import time1112pub const glyph_ok = '✦'13pub const glyph_fail = '✗'1415pub fn colour_enabled() bool {16	if os.getenv('NO_COLOR') != '' || os.getenv('KOH_NO_COLOR') != '' {17		return false18	}19	return os.is_atty(1) != 020}2122fn paint(text string, code string) string {23	if text == '' || !colour_enabled() {24		return text25	}26	return '\x1b[${code}m${text}\x1b[0m'27}2829pub fn bold(text string) string {30	return paint(text, '1')31}3233pub fn reverse(text string) string {34	return paint(text, '7')35}3637pub fn amber(text string) string {38	return paint(text, '38;5;214')39}4041pub fn red(text string) string {42	return paint(text, '38;5;203')43}4445pub fn green(text string) string {46	return paint(text, '38;5;114')47}4849pub fn dim(text string) string {50	return paint(text, '38;5;245')51}5253// The history view keeps Koh 2's softer palette: gold for the selected save,54// muted grey for everything around it, blue for HEAD.55pub fn gold(text string) string {56	return paint(text, '38;5;179')57}5859pub fn muted(text string) string {60	return paint(text, '38;5;241')61}6263pub fn blue(text string) string {64	return paint(text, '38;5;110')65}6667// The manual keeps the rest of Koh 2's man-page palette.68pub fn light(text string) string {69	return paint(text, '38;5;223')70}7172pub fn warning(text string) string {73	return paint(text, '38;5;173')74}7576// strip_ansi removes the colour codes, for measuring how wide a line really is.77pub fn strip_ansi(text string) string {78	mut out := ''79	mut skipping := false80	for c in text.runes() {81		if c == `\x1b` {82			skipping = true83			continue84		}85		if skipping {86			if (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) {87				skipping = false88			}89			continue90		}91		out += c.str()92	}93	return out94}9596// visible_width counts characters, not bytes or colour codes; a double-width character counts as one.97pub fn visible_width(text string) int {98	return strip_ansi(text).runes().len99}100101// fit cuts a line to `width` visible columns and keeps its colour codes, so a102// long line can never wrap and scroll a full-screen frame.103pub fn fit(text string, width int) string {104	if visible_width(text) <= width {105		return text106	}107	mut out := []rune{}108	mut shown := 0109	mut skipping := false110	for c in text.runes() {111		if c == `\x1b` {112			skipping = true113		}114		if skipping {115			out << c116			if (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) {117				skipping = false118			}119			continue120		}121		if shown == width {122			break123		}124		out << c125		shown++126	}127	cut := out.string()128	return if cut.contains('\x1b') { cut + '\x1b[0m' } else { cut }129}130131pub fn stamp(unix_seconds i64) string {132	return time.unix(unix_seconds).local().format()133}134135pub fn date_of(unix_seconds i64) string {136	return time.unix(unix_seconds).local().ymmdd()137}138139pub fn hour_of(unix_seconds i64) string {140	return time.unix(unix_seconds).local().hhmm()141}142143pub fn clock_of(unix_seconds i64) string {144	return time.unix(unix_seconds).local().hhmmss()145}146147pub fn human_bytes(bytes i64) string {148	mut value := f64(bytes)149	mut unit := 'B'150	for suffix in ['KiB', 'MiB', 'GiB'] {151		if value < 1024 {152			break153		}154		value /= 1024155		unit = suffix156	}157	if unit == 'B' {158		return '${bytes} B'159	}160	return '${value:.1f} ${unit}'161}162163// plural renders "1 file" / "3 files".164pub fn plural(count int, noun string) string {165	if count == 1 {166		return '1 ${noun}'167	}168	return '${count} ${noun}s'169}