|
@@ -0,0 +1,190 @@
|
|
|
+package ztermtab
|
|
|
+
|
|
|
+import (
|
|
|
+ "github.com/mattn/go-runewidth"
|
|
|
+ "strings"
|
|
|
+)
|
|
|
+
|
|
|
+type TerminalTablePrinter struct {
|
|
|
+ colsdef []columnDefine
|
|
|
+ colCnt int
|
|
|
+ schLock bool
|
|
|
+ rowData [][]string
|
|
|
+ termWidth int
|
|
|
+ hasFreeLine bool
|
|
|
+ freeLineIndex int
|
|
|
+ split string
|
|
|
+ splitWidth int
|
|
|
+}
|
|
|
+
|
|
|
+func NewTerminalTablePrinter(termWidth int, splitString string) *TerminalTablePrinter {
|
|
|
+ ttp := &TerminalTablePrinter{
|
|
|
+ colsdef: make([]columnDefine, 0),
|
|
|
+ colCnt: 0,
|
|
|
+ schLock: false,
|
|
|
+ termWidth: termWidth,
|
|
|
+ hasFreeLine: false,
|
|
|
+ split: splitString,
|
|
|
+ splitWidth: runewidth.StringWidth(splitString),
|
|
|
+ }
|
|
|
+ return ttp
|
|
|
+}
|
|
|
+
|
|
|
+func (p *TerminalTablePrinter) AddColumn(title string, minWidth int, maxWidth int, truncateOverflow bool) {
|
|
|
+ if p.schLock {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ p.colsdef = append(p.colsdef, columnDefine{
|
|
|
+ title: title,
|
|
|
+ minWidth: minWidth,
|
|
|
+ maxWidth: maxWidth,
|
|
|
+ truncateOverflow: truncateOverflow,
|
|
|
+ autoWidth: false,
|
|
|
+ })
|
|
|
+ p.colCnt++
|
|
|
+}
|
|
|
+
|
|
|
+func (p *TerminalTablePrinter) SetAutoWidthColumn(index int) {
|
|
|
+ if p.schLock {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if index >= p.colCnt {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ for i := range p.colsdef {
|
|
|
+ if i == index {
|
|
|
+ p.colsdef[i].autoWidth = true
|
|
|
+ } else {
|
|
|
+ p.colsdef[i].autoWidth = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ p.freeLineIndex = index
|
|
|
+ p.hasFreeLine = true
|
|
|
+}
|
|
|
+
|
|
|
+func (p *TerminalTablePrinter) ClearData() {
|
|
|
+ p.schLock = true
|
|
|
+ p.rowData = make([][]string, 0)
|
|
|
+ for i := range p.colsdef {
|
|
|
+ p.colsdef[i].currentWidth = p.colsdef[i].minWidth
|
|
|
+ updateCurrentWidth(&p.colsdef[i], runewidth.StringWidth(p.colsdef[i].title))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (p *TerminalTablePrinter) AddRow(data []string) {
|
|
|
+ if !p.schLock {
|
|
|
+ p.ClearData()
|
|
|
+ }
|
|
|
+ line := make([]string, p.colCnt)
|
|
|
+ for i, v := range data {
|
|
|
+ if i >= p.colCnt {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ line[i] = v
|
|
|
+ updateCurrentWidth(&p.colsdef[i], runewidth.StringWidth(v))
|
|
|
+ }
|
|
|
+ p.rowData = append(p.rowData, line)
|
|
|
+}
|
|
|
+
|
|
|
+func (p *TerminalTablePrinter) RenderToTerminalLines() []string {
|
|
|
+ var fll int
|
|
|
+ if p.hasFreeLine {
|
|
|
+ nfll := 0
|
|
|
+ for i, v := range p.colsdef {
|
|
|
+ if !v.autoWidth {
|
|
|
+ nfll += v.currentWidth
|
|
|
+ }
|
|
|
+ if i < p.colCnt-1 {
|
|
|
+ nfll += p.splitWidth
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if nfll < p.termWidth {
|
|
|
+ fll = p.termWidth - nfll
|
|
|
+ } else {
|
|
|
+ fll = 1
|
|
|
+ }
|
|
|
+ }
|
|
|
+ colwidtab := make([]int, p.colCnt)
|
|
|
+ for i, v := range p.colsdef {
|
|
|
+ if v.autoWidth {
|
|
|
+ colwidtab[i] = fll
|
|
|
+ } else {
|
|
|
+ colwidtab[i] = v.currentWidth
|
|
|
+ }
|
|
|
+ }
|
|
|
+ out := make([]string, 0, len(p.rowData))
|
|
|
+ for _, v := range p.rowData {
|
|
|
+ maxline := 0
|
|
|
+ sublines := make([][]string, len(v))
|
|
|
+ for j, cell := range v {
|
|
|
+ osl := printStringIntoLines(cell, colwidtab[j])
|
|
|
+ if len(osl) > maxline {
|
|
|
+ maxline = len(osl)
|
|
|
+ }
|
|
|
+ sublines[j] = osl
|
|
|
+ }
|
|
|
+ for k := 0; k < maxline; k++ {
|
|
|
+ sb := strings.Builder{}
|
|
|
+ for kj := 0; kj < p.colCnt; kj++ {
|
|
|
+ kjv := sublines[kj]
|
|
|
+ if k < len(kjv) {
|
|
|
+ sb.WriteString(fillString(kjv[k], colwidtab[kj]))
|
|
|
+ } else {
|
|
|
+ sb.WriteString(strings.Repeat(" ", colwidtab[kj]))
|
|
|
+ }
|
|
|
+ if kj != p.colCnt-1 {
|
|
|
+ sb.WriteString(p.split)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ out = append(out, sb.String())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return out
|
|
|
+}
|
|
|
+
|
|
|
+func fillString(s string, fn int) string {
|
|
|
+ l := runewidth.StringWidth(s)
|
|
|
+ fill := fn - l
|
|
|
+ sb := strings.Builder{}
|
|
|
+ sb.WriteString(s)
|
|
|
+ if fill > 0 {
|
|
|
+ sb.WriteString(strings.Repeat(" ", fill))
|
|
|
+ }
|
|
|
+ return sb.String()
|
|
|
+}
|
|
|
+
|
|
|
+func updateCurrentWidth(cd *columnDefine, newwid int) {
|
|
|
+ if newwid > cd.currentWidth {
|
|
|
+ if newwid >= cd.maxWidth {
|
|
|
+ cd.currentWidth = cd.maxWidth
|
|
|
+ } else {
|
|
|
+ cd.currentWidth = newwid
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func printStringIntoLines(s string, termWidth int) []string {
|
|
|
+ if runewidth.StringWidth(s) <= termWidth {
|
|
|
+ return []string{s}
|
|
|
+ }
|
|
|
+ rlns := []string{}
|
|
|
+ cln := strings.Builder{}
|
|
|
+ clcnt := 0
|
|
|
+ ra := []rune(s)
|
|
|
+ for _, v := range ra {
|
|
|
+ rw := runewidth.RuneWidth(v)
|
|
|
+ if rw+clcnt > termWidth {
|
|
|
+ rlns = append(rlns, cln.String())
|
|
|
+ cln = strings.Builder{}
|
|
|
+ cln.WriteRune(v)
|
|
|
+ clcnt = rw
|
|
|
+ } else {
|
|
|
+ cln.WriteRune(v)
|
|
|
+ clcnt += rw
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if cln.Len() > 0 {
|
|
|
+ rlns = append(rlns, cln.String())
|
|
|
+ }
|
|
|
+ return rlns
|
|
|
+}
|