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 }