mirror of
https://gitee.com/rainbond/Rainbond.git
synced 2024-12-02 11:47:36 +08:00
169 lines
4.6 KiB
Go
169 lines
4.6 KiB
Go
// Copyright 2012 Apcera Inc. All rights reserved.
|
|
|
|
package termtables
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
runewidth "github.com/mattn/go-runewidth"
|
|
)
|
|
|
|
var (
|
|
// Must match SGR escape sequence, which is "CSI Pm m", where the Control
|
|
// Sequence Introducer (CSI) is "ESC ["; where Pm is "A multiple numeric
|
|
// parameter composed of any number of single numeric parameters, separated
|
|
// by ; character(s). Individual values for the parameters are listed with
|
|
// Ps" and where Ps is A single (usually optional) numeric parameter,
|
|
// composed of one of [sic] more digits."
|
|
//
|
|
// In practice, the end sequence is usually given as \e[0m but reading that
|
|
// definition, it's clear that the 0 is optional and some testing confirms
|
|
// that it is certainly optional with MacOS Terminal 2.3, so we need to
|
|
// support the string \e[m as a terminator too.
|
|
colorFilter = regexp.MustCompile(`\033\[(?:\d+(?:;\d+)*)?m`)
|
|
)
|
|
|
|
// A Cell denotes one cell of a table; it spans one row and a variable number
|
|
// of columns. A given Cell can only be used at one place in a table; the act
|
|
// of adding the Cell to the table mutates it with position information, so
|
|
// do not create one "const" Cell to add it multiple times.
|
|
type Cell struct {
|
|
column int
|
|
formattedValue string
|
|
alignment *tableAlignment
|
|
colSpan int
|
|
}
|
|
|
|
// CreateCell returns a Cell where the content is the supplied value, with the
|
|
// optional supplied style (which may be given as nil). The style can include
|
|
// a non-zero ColSpan to cause the cell to become column-spanning. Changing
|
|
// the style afterwards will not adjust the column-spanning state of the cell
|
|
// itself.
|
|
func CreateCell(v interface{}, style *CellStyle) *Cell {
|
|
return createCell(0, v, style)
|
|
}
|
|
|
|
func createCell(column int, v interface{}, style *CellStyle) *Cell {
|
|
cell := &Cell{column: column, formattedValue: renderValue(v), colSpan: 1}
|
|
if style != nil {
|
|
cell.alignment = &style.Alignment
|
|
if style.ColSpan != 0 {
|
|
cell.colSpan = style.ColSpan
|
|
}
|
|
}
|
|
return cell
|
|
}
|
|
|
|
// Width returns the width of the content of the cell, measured in runes as best
|
|
// as possible considering sophisticated Unicode.
|
|
func (c *Cell) Width() int {
|
|
return runewidth.StringWidth(filterColorCodes(c.formattedValue))
|
|
}
|
|
|
|
// Filter out terminal bold/color sequences in a string.
|
|
// This supports only basic bold/color escape sequences.
|
|
func filterColorCodes(s string) string {
|
|
return colorFilter.ReplaceAllString(s, "")
|
|
}
|
|
|
|
// Render returns a string representing the content of the cell, together with
|
|
// padding (to the widths specified) and handling any alignment.
|
|
func (c *Cell) Render(style *renderStyle) (buffer string) {
|
|
// if no alignment is set, import the table's default
|
|
if c.alignment == nil {
|
|
c.alignment = &style.Alignment
|
|
}
|
|
|
|
// left padding
|
|
buffer += strings.Repeat(" ", style.PaddingLeft)
|
|
|
|
// append the main value and handle alignment
|
|
buffer += c.alignCell(style)
|
|
|
|
// right padding
|
|
buffer += strings.Repeat(" ", style.PaddingRight)
|
|
|
|
// this handles escaping for, eg, Markdown, where we don't care about the
|
|
// alignment quite as much
|
|
if style.replaceContent != nil {
|
|
buffer = style.replaceContent(buffer)
|
|
}
|
|
|
|
return buffer
|
|
}
|
|
|
|
func (c *Cell) alignCell(style *renderStyle) string {
|
|
buffer := ""
|
|
width := style.CellWidth(c.column)
|
|
|
|
if c.colSpan > 1 {
|
|
for i := 1; i < c.colSpan; i++ {
|
|
w := style.CellWidth(c.column + i)
|
|
if w == 0 {
|
|
break
|
|
}
|
|
width += style.PaddingLeft + w + style.PaddingRight + utf8.RuneCountInString(style.BorderY)
|
|
}
|
|
}
|
|
|
|
switch *c.alignment {
|
|
|
|
default:
|
|
buffer += c.formattedValue
|
|
if l := width - c.Width(); l > 0 {
|
|
buffer += strings.Repeat(" ", l)
|
|
}
|
|
|
|
case AlignLeft:
|
|
buffer += c.formattedValue
|
|
if l := width - c.Width(); l > 0 {
|
|
buffer += strings.Repeat(" ", l)
|
|
}
|
|
|
|
case AlignRight:
|
|
if l := width - c.Width(); l > 0 {
|
|
buffer += strings.Repeat(" ", l)
|
|
}
|
|
buffer += c.formattedValue
|
|
|
|
case AlignCenter:
|
|
left, right := 0, 0
|
|
if l := width - c.Width(); l > 0 {
|
|
lf := float64(l)
|
|
left = int(math.Floor(lf / 2))
|
|
right = int(math.Ceil(lf / 2))
|
|
}
|
|
buffer += strings.Repeat(" ", left)
|
|
buffer += c.formattedValue
|
|
buffer += strings.Repeat(" ", right)
|
|
}
|
|
|
|
return buffer
|
|
}
|
|
|
|
// Format the raw value as a string depending on the type
|
|
func renderValue(v interface{}) string {
|
|
switch vv := v.(type) {
|
|
case string:
|
|
return vv
|
|
case bool:
|
|
return strconv.FormatBool(vv)
|
|
case int:
|
|
return strconv.Itoa(vv)
|
|
case int64:
|
|
return strconv.FormatInt(vv, 10)
|
|
case uint64:
|
|
return strconv.FormatUint(vv, 10)
|
|
case float64:
|
|
return strconv.FormatFloat(vv, 'f', 2, 64)
|
|
case fmt.Stringer:
|
|
return vv.String()
|
|
}
|
|
return fmt.Sprintf("%v", v)
|
|
}
|