diff --git a/vlib/term/term.v b/vlib/term/term.v index c2bf67f5af..811692e3c5 100644 --- a/vlib/term/term.v +++ b/vlib/term/term.v @@ -63,6 +63,66 @@ pub fn colorize(cfn fn (string) string, s string) string { return s } +struct AnsiScanner { +mut: + pos int + input string +} + +fn (mut ss AnsiScanner) next() int { + if ss.pos < ss.input.len { + opos := ss.pos + ss.pos++ + return ss.input[opos] + } + return -1 +} + +// strip_ansi removes any ANSI sequences in the `text` +pub fn strip_ansi(text string) string { + // This is a port of https://github.com/kilobyte/colorized-logs/blob/master/ansi2txt.c + // \e, [, 1, m, a, b, c, \e, [, 2, 2, m => abc + mut input := AnsiScanner{ + input: text + } + mut output := []byte{cap: text.len} + mut ch := 0 + for ch != -1 { + ch = input.next() + if ch == 27 { + ch = input.next() + if ch == `[` { + for { + ch = input.next() + if ch in [`;`, `?`] || (ch >= `0` && ch <= `9`) { + continue + } + break + } + } else if ch == `]` { + ch = input.next() + if ch >= `0` && ch <= `9` { + for { + ch = input.next() + if ch == -1 || ch == 7 { + break + } + if ch == 27 { + ch = input.next() + break + } + } + } + } else if ch == `%` { + ch = input.next() + } + } else if ch != -1 { + output << byte(ch) + } + } + return output.bytestr() +} + // h_divider returns a horizontal divider line with a dynamic width, // that depends on the current terminal settings. // If an empty string is passed in, print enough spaces to make a new line @@ -77,6 +137,20 @@ pub fn h_divider(divider string) string { return result[0..cols] } +// header_left returns a horizontal divider line with a title text on the left. +// e.g: term.header_left('TITLE', '=') +// ==== TITLE ========================= +pub fn header_left(text string, divider string) string { + plain_text := strip_ansi(text) + xcols, _ := get_terminal_size() + cols := imax(1, xcols) + relement := if divider.len > 0 { divider } else { ' ' } + hstart := relement.repeat(4)[0..4] + remaining_cols := (cols - (hstart.len + 1 + plain_text.len + 1)) + hend := relement.repeat((remaining_cols + 1) / relement.len)[0..remaining_cols] + return '$hstart $text $hend' +} + // header returns a horizontal divider line with a centered text in the middle. // e.g: term.header('TEXT', '=') // =============== TEXT =============== diff --git a/vlib/term/term_test.v b/vlib/term/term_test.v index 200ef5e55a..00f9293402 100644 --- a/vlib/term/term_test.v +++ b/vlib/term/term_test.v @@ -100,3 +100,16 @@ fn test_set_terminal_title() { title_change := term.set_terminal_title('v is awesome!') assert title_change == true } + +fn test_strip_ansi() { + strings := [ + 'abc', + term.bold('abc'), + term.yellow('abc'), + term.bold(term.red('abc')), + term.strikethrough(term.inverse(term.dim(term.bold(term.bright_bg_blue('abc'))))), + ] + for s in strings { + assert term.strip_ansi(s) == 'abc' + } +}