term_table_printer.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. package ztermtab
  2. import (
  3. "github.com/mattn/go-runewidth"
  4. "strings"
  5. )
  6. type TerminalTablePrinter struct {
  7. colsdef []columnDefine
  8. colCnt int
  9. schLock bool
  10. rowData [][]string
  11. termWidth int
  12. hasFreeLine bool
  13. freeLineIndex int
  14. split string
  15. splitWidth int
  16. }
  17. func NewTerminalTablePrinter(termWidth int, splitString string) *TerminalTablePrinter {
  18. ttp := &TerminalTablePrinter{
  19. colsdef: make([]columnDefine, 0),
  20. colCnt: 0,
  21. schLock: false,
  22. termWidth: termWidth,
  23. hasFreeLine: false,
  24. split: splitString,
  25. splitWidth: runewidth.StringWidth(splitString),
  26. }
  27. return ttp
  28. }
  29. func (p *TerminalTablePrinter) AddColumn(title string, minWidth int, maxWidth int, truncateOverflow bool) {
  30. if p.schLock {
  31. return
  32. }
  33. p.colsdef = append(p.colsdef, columnDefine{
  34. title: title,
  35. minWidth: minWidth,
  36. maxWidth: maxWidth,
  37. truncateOverflow: truncateOverflow,
  38. autoWidth: false,
  39. })
  40. p.colCnt++
  41. }
  42. func (p *TerminalTablePrinter) SetAutoWidthColumn(index int) {
  43. if p.schLock {
  44. return
  45. }
  46. if index >= p.colCnt {
  47. return
  48. }
  49. for i := range p.colsdef {
  50. if i == index {
  51. p.colsdef[i].autoWidth = true
  52. } else {
  53. p.colsdef[i].autoWidth = false
  54. }
  55. }
  56. p.freeLineIndex = index
  57. p.hasFreeLine = true
  58. }
  59. func (p *TerminalTablePrinter) ClearData() {
  60. p.schLock = true
  61. p.rowData = make([][]string, 0)
  62. for i := range p.colsdef {
  63. p.colsdef[i].currentWidth = p.colsdef[i].minWidth
  64. updateCurrentWidth(&p.colsdef[i], runewidth.StringWidth(p.colsdef[i].title))
  65. }
  66. }
  67. func (p *TerminalTablePrinter) AddRow(data []string) {
  68. if !p.schLock {
  69. p.ClearData()
  70. }
  71. line := make([]string, p.colCnt)
  72. for i, v := range data {
  73. if i >= p.colCnt {
  74. break
  75. }
  76. line[i] = v
  77. updateCurrentWidth(&p.colsdef[i], runewidth.StringWidth(v))
  78. }
  79. p.rowData = append(p.rowData, line)
  80. }
  81. func (p *TerminalTablePrinter) RenderToTerminalLines() []string {
  82. var fll int
  83. if p.hasFreeLine {
  84. nfll := 0
  85. for i, v := range p.colsdef {
  86. if !v.autoWidth {
  87. nfll += v.currentWidth
  88. }
  89. if i < p.colCnt-1 {
  90. nfll += p.splitWidth
  91. }
  92. }
  93. if nfll < p.termWidth {
  94. fll = p.termWidth - nfll
  95. } else {
  96. fll = 1
  97. }
  98. }
  99. colwidtab := make([]int, p.colCnt)
  100. for i, v := range p.colsdef {
  101. if v.autoWidth {
  102. colwidtab[i] = fll
  103. } else {
  104. colwidtab[i] = v.currentWidth
  105. }
  106. }
  107. out := make([]string, 0, len(p.rowData))
  108. for _, v := range p.rowData {
  109. maxline := 0
  110. sublines := make([][]string, len(v))
  111. for j, cell := range v {
  112. osl := printStringIntoLines(cell, colwidtab[j])
  113. if len(osl) > maxline {
  114. maxline = len(osl)
  115. }
  116. sublines[j] = osl
  117. }
  118. for k := 0; k < maxline; k++ {
  119. sb := strings.Builder{}
  120. for kj := 0; kj < p.colCnt; kj++ {
  121. kjv := sublines[kj]
  122. if k < len(kjv) {
  123. sb.WriteString(fillString(kjv[k], colwidtab[kj]))
  124. } else {
  125. sb.WriteString(strings.Repeat(" ", colwidtab[kj]))
  126. }
  127. if kj != p.colCnt-1 {
  128. sb.WriteString(p.split)
  129. }
  130. }
  131. out = append(out, sb.String())
  132. }
  133. }
  134. return out
  135. }
  136. func fillString(s string, fn int) string {
  137. l := runewidth.StringWidth(s)
  138. fill := fn - l
  139. sb := strings.Builder{}
  140. sb.WriteString(s)
  141. if fill > 0 {
  142. sb.WriteString(strings.Repeat(" ", fill))
  143. }
  144. return sb.String()
  145. }
  146. func updateCurrentWidth(cd *columnDefine, newwid int) {
  147. if newwid > cd.currentWidth {
  148. if newwid >= cd.maxWidth {
  149. cd.currentWidth = cd.maxWidth
  150. } else {
  151. cd.currentWidth = newwid
  152. }
  153. }
  154. }
  155. func printStringIntoLines(s string, termWidth int) []string {
  156. if runewidth.StringWidth(s) <= termWidth {
  157. return []string{s}
  158. }
  159. rlns := []string{}
  160. cln := strings.Builder{}
  161. clcnt := 0
  162. ra := []rune(s)
  163. for _, v := range ra {
  164. rw := runewidth.RuneWidth(v)
  165. if rw+clcnt > termWidth {
  166. rlns = append(rlns, cln.String())
  167. cln = strings.Builder{}
  168. cln.WriteRune(v)
  169. clcnt = rw
  170. } else {
  171. cln.WriteRune(v)
  172. clcnt += rw
  173. }
  174. }
  175. if cln.Len() > 0 {
  176. rlns = append(rlns, cln.String())
  177. }
  178. return rlns
  179. }