width.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. package ansi
  2. import (
  3. "bytes"
  4. "github.com/charmbracelet/x/ansi/parser"
  5. "github.com/mattn/go-runewidth"
  6. "github.com/rivo/uniseg"
  7. )
  8. // Strip removes ANSI escape codes from a string.
  9. func Strip(s string) string {
  10. var (
  11. buf bytes.Buffer // buffer for collecting printable characters
  12. ri int // rune index
  13. rw int // rune width
  14. pstate = parser.GroundState // initial state
  15. )
  16. // This implements a subset of the Parser to only collect runes and
  17. // printable characters.
  18. for i := 0; i < len(s); i++ {
  19. if pstate == parser.Utf8State {
  20. // During this state, collect rw bytes to form a valid rune in the
  21. // buffer. After getting all the rune bytes into the buffer,
  22. // transition to GroundState and reset the counters.
  23. buf.WriteByte(s[i])
  24. ri++
  25. if ri < rw {
  26. continue
  27. }
  28. pstate = parser.GroundState
  29. ri = 0
  30. rw = 0
  31. continue
  32. }
  33. state, action := parser.Table.Transition(pstate, s[i])
  34. switch action {
  35. case parser.CollectAction:
  36. if state == parser.Utf8State {
  37. // This action happens when we transition to the Utf8State.
  38. rw = utf8ByteLen(s[i])
  39. buf.WriteByte(s[i])
  40. ri++
  41. }
  42. case parser.PrintAction, parser.ExecuteAction:
  43. // collects printable ASCII and non-printable characters
  44. buf.WriteByte(s[i])
  45. }
  46. // Transition to the next state.
  47. // The Utf8State is managed separately above.
  48. if pstate != parser.Utf8State {
  49. pstate = state
  50. }
  51. }
  52. return buf.String()
  53. }
  54. // StringWidth returns the width of a string in cells. This is the number of
  55. // cells that the string will occupy when printed in a terminal. ANSI escape
  56. // codes are ignored and wide characters (such as East Asians and emojis) are
  57. // accounted for.
  58. // This treats the text as a sequence of grapheme clusters.
  59. func StringWidth(s string) int {
  60. return stringWidth(GraphemeWidth, s)
  61. }
  62. // StringWidthWc returns the width of a string in cells. This is the number of
  63. // cells that the string will occupy when printed in a terminal. ANSI escape
  64. // codes are ignored and wide characters (such as East Asians and emojis) are
  65. // accounted for.
  66. // This treats the text as a sequence of wide characters and runes.
  67. func StringWidthWc(s string) int {
  68. return stringWidth(WcWidth, s)
  69. }
  70. func stringWidth(m Method, s string) int {
  71. if s == "" {
  72. return 0
  73. }
  74. var (
  75. pstate = parser.GroundState // initial state
  76. cluster string
  77. width int
  78. )
  79. for i := 0; i < len(s); i++ {
  80. state, action := parser.Table.Transition(pstate, s[i])
  81. if state == parser.Utf8State {
  82. var w int
  83. cluster, _, w, _ = uniseg.FirstGraphemeClusterInString(s[i:], -1)
  84. if m == WcWidth {
  85. w = runewidth.StringWidth(cluster)
  86. }
  87. width += w
  88. i += len(cluster) - 1
  89. pstate = parser.GroundState
  90. continue
  91. }
  92. if action == parser.PrintAction {
  93. width++
  94. }
  95. pstate = state
  96. }
  97. return width
  98. }