wcswidth.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package wcswidth
  3. import (
  4. "fmt"
  5. "strconv"
  6. "strings"
  7. "github.com/kovidgoyal/kitty/tools/utils"
  8. )
  9. var _ = fmt.Print
  10. type WCWidthIterator struct {
  11. prev_ch rune
  12. prev_width, current_width int
  13. seg GraphemeSegmentationResult
  14. can_combine bool
  15. parser EscapeCodeParser
  16. rune_count uint
  17. }
  18. func CreateWCWidthIterator() *WCWidthIterator {
  19. var ans WCWidthIterator
  20. ans.parser.HandleRune = ans.handle_rune
  21. ans.parser.HandleCSI = ans.handle_csi
  22. ans.parser.HandleOSC = ans.handle_st_terminated
  23. ans.parser.HandleDCS = ans.handle_st_terminated
  24. ans.parser.HandlePM = ans.handle_st_terminated
  25. ans.parser.HandleSOS = ans.handle_st_terminated
  26. ans.parser.HandleAPC = ans.handle_st_terminated
  27. return &ans
  28. }
  29. func (self *WCWidthIterator) Reset() {
  30. self.prev_ch = 0
  31. self.prev_width = 0
  32. self.current_width = 0
  33. self.rune_count = 0
  34. self.can_combine = false
  35. self.seg = 0
  36. self.parser.Reset()
  37. }
  38. func (self *WCWidthIterator) handle_csi(csi []byte) error {
  39. if len(csi) > 1 && csi[len(csi)-1] == 'b' {
  40. num_string := utils.UnsafeBytesToString(csi[:len(csi)-1])
  41. n, err := strconv.Atoi(num_string)
  42. if err == nil && n > 0 {
  43. for i := 0; i < n; i++ {
  44. err = self.handle_rune(self.prev_ch)
  45. if err != nil {
  46. return err
  47. }
  48. }
  49. }
  50. }
  51. self.can_combine = false
  52. self.seg = 0
  53. return nil
  54. }
  55. func (self *WCWidthIterator) handle_st_terminated(data []byte) error {
  56. self.can_combine = false
  57. self.seg = 0
  58. return nil
  59. }
  60. func (self *WCWidthIterator) handle_rune(ch rune) error {
  61. self.rune_count += 1
  62. cp := CharPropsFor(ch)
  63. self.seg = self.seg.Step(cp)
  64. if self.can_combine && self.seg.Add_to_current_cell() == 1 {
  65. switch ch {
  66. case 0xfe0f:
  67. if CharPropsFor(self.prev_ch).Is_emoji_presentation_base() == 1 && self.prev_width == 1 {
  68. self.current_width += 1
  69. self.prev_width = 2
  70. }
  71. case 0xfe0e:
  72. if CharPropsFor(self.prev_ch).Is_emoji_presentation_base() == 1 && self.prev_width == 2 {
  73. self.current_width -= 1
  74. self.prev_width = 1
  75. }
  76. }
  77. } else {
  78. width := cp.Width()
  79. switch width {
  80. case -1:
  81. case 0:
  82. self.prev_width = 0
  83. case 2:
  84. self.prev_width = 2
  85. default:
  86. self.prev_width = 1
  87. }
  88. self.current_width += self.prev_width
  89. self.can_combine = true
  90. }
  91. self.prev_ch = ch
  92. return nil
  93. }
  94. func (self *WCWidthIterator) ParseByte(b byte) (ans int) {
  95. self.parser.ParseByte(b)
  96. return self.current_width
  97. }
  98. func (self *WCWidthIterator) Parse(b []byte) (ans int) {
  99. self.current_width = 0
  100. self.parser.Parse(b)
  101. return self.current_width
  102. }
  103. func (self *WCWidthIterator) CurrentWidth() int {
  104. return self.current_width
  105. }
  106. func Stringwidth(text string) int {
  107. w := CreateWCWidthIterator()
  108. return w.Parse(utils.UnsafeStringToBytes(text))
  109. }
  110. func StripEscapeCodes(text string) string {
  111. out := strings.Builder{}
  112. out.Grow(len(text))
  113. p := EscapeCodeParser{}
  114. p.HandleRune = func(ch rune) error {
  115. out.WriteRune(ch)
  116. return nil
  117. }
  118. p.ParseString(text)
  119. return out.String()
  120. }