truncate.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. package truncate
  2. import (
  3. "bytes"
  4. "io"
  5. "github.com/mattn/go-runewidth"
  6. "github.com/muesli/reflow/ansi"
  7. )
  8. type Writer struct {
  9. width uint
  10. tail string
  11. ansiWriter *ansi.Writer
  12. buf bytes.Buffer
  13. ansi bool
  14. }
  15. func NewWriter(width uint, tail string) *Writer {
  16. w := &Writer{
  17. width: width,
  18. tail: tail,
  19. }
  20. w.ansiWriter = &ansi.Writer{
  21. Forward: &w.buf,
  22. }
  23. return w
  24. }
  25. func NewWriterPipe(forward io.Writer, width uint, tail string) *Writer {
  26. return &Writer{
  27. width: width,
  28. tail: tail,
  29. ansiWriter: &ansi.Writer{
  30. Forward: forward,
  31. },
  32. }
  33. }
  34. // Bytes is shorthand for declaring a new default truncate-writer instance,
  35. // used to immediately truncate a byte slice.
  36. func Bytes(b []byte, width uint) []byte {
  37. return BytesWithTail(b, width, []byte(""))
  38. }
  39. // Bytes is shorthand for declaring a new default truncate-writer instance,
  40. // used to immediately truncate a byte slice. A tail is then added to the
  41. // end of the byte slice.
  42. func BytesWithTail(b []byte, width uint, tail []byte) []byte {
  43. f := NewWriter(width, string(tail))
  44. _, _ = f.Write(b)
  45. return f.Bytes()
  46. }
  47. // String is shorthand for declaring a new default truncate-writer instance,
  48. // used to immediately truncate a string.
  49. func String(s string, width uint) string {
  50. return StringWithTail(s, width, "")
  51. }
  52. // StringWithTail is shorthand for declaring a new default truncate-writer instance,
  53. // used to immediately truncate a string. A tail is then added to the end of the
  54. // string.
  55. func StringWithTail(s string, width uint, tail string) string {
  56. return string(BytesWithTail([]byte(s), width, []byte(tail)))
  57. }
  58. // Write truncates content at the given printable cell width, leaving any
  59. // ansi sequences intact.
  60. func (w *Writer) Write(b []byte) (int, error) {
  61. tw := ansi.PrintableRuneWidth(w.tail)
  62. if w.width < uint(tw) {
  63. return w.buf.WriteString(w.tail)
  64. }
  65. w.width -= uint(tw)
  66. var curWidth uint
  67. for _, c := range string(b) {
  68. if c == ansi.Marker {
  69. // ANSI escape sequence
  70. w.ansi = true
  71. } else if w.ansi {
  72. if ansi.IsTerminator(c) {
  73. // ANSI sequence terminated
  74. w.ansi = false
  75. }
  76. } else {
  77. curWidth += uint(runewidth.RuneWidth(c))
  78. }
  79. if curWidth > w.width {
  80. n, err := w.buf.WriteString(w.tail)
  81. if w.ansiWriter.LastSequence() != "" {
  82. w.ansiWriter.ResetAnsi()
  83. }
  84. return n, err
  85. }
  86. _, err := w.ansiWriter.Write([]byte(string(c)))
  87. if err != nil {
  88. return 0, err
  89. }
  90. }
  91. return len(b), nil
  92. }
  93. // Bytes returns the truncated result as a byte slice.
  94. func (w *Writer) Bytes() []byte {
  95. return w.buf.Bytes()
  96. }
  97. // String returns the truncated result as a string.
  98. func (w *Writer) String() string {
  99. return w.buf.String()
  100. }