truncate.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package wcswidth
  3. import (
  4. "errors"
  5. "fmt"
  6. "strconv"
  7. "kitty/tools/utils"
  8. )
  9. var _ = fmt.Print
  10. type truncate_error struct {
  11. pos, width int
  12. }
  13. func (self *truncate_error) Error() string {
  14. return fmt.Sprint("Truncation at:", self.pos, " with width:", self.width)
  15. }
  16. type truncate_iterator struct {
  17. w WCWidthIterator
  18. pos, limit int
  19. limit_exceeded_at *truncate_error
  20. }
  21. func (self *truncate_iterator) handle_csi(csi []byte) error {
  22. if len(csi) > 1 && csi[len(csi)-1] == 'b' { // repeat previous char escape code
  23. num_string := utils.UnsafeBytesToString(csi[:len(csi)-1])
  24. n, err := strconv.Atoi(num_string)
  25. if err == nil && n > 0 {
  26. width_before_repeat := self.w.current_width
  27. for ; n > 0; n-- {
  28. self.w.handle_rune(self.w.prev_ch)
  29. if self.w.current_width > self.limit {
  30. return &truncate_error{pos: self.pos, width: width_before_repeat}
  31. }
  32. }
  33. }
  34. }
  35. self.pos += len(csi) + 2
  36. return nil
  37. }
  38. func (self *truncate_iterator) handle_st_terminated_escape_code(body []byte) error {
  39. self.pos += len(body) + 4
  40. return nil
  41. }
  42. func create_truncate_iterator() *truncate_iterator {
  43. var ans truncate_iterator
  44. ans.w.parser.HandleRune = ans.handle_rune
  45. ans.w.parser.HandleCSI = ans.handle_csi
  46. ans.w.parser.HandleOSC = ans.handle_st_terminated_escape_code
  47. ans.w.parser.HandleAPC = ans.handle_st_terminated_escape_code
  48. ans.w.parser.HandleDCS = ans.handle_st_terminated_escape_code
  49. ans.w.parser.HandlePM = ans.handle_st_terminated_escape_code
  50. ans.w.parser.HandleSOS = ans.handle_st_terminated_escape_code
  51. return &ans
  52. }
  53. func (self *truncate_iterator) handle_rune(ch rune) error {
  54. width := self.w.current_width
  55. self.w.handle_rune(ch)
  56. if self.limit_exceeded_at != nil {
  57. if self.w.current_width <= self.limit { // emoji variation selectors can cause width to decrease
  58. return &truncate_error{pos: self.pos + len(string(ch)), width: self.w.current_width}
  59. }
  60. return self.limit_exceeded_at
  61. }
  62. if self.w.current_width > self.limit {
  63. self.limit_exceeded_at = &truncate_error{pos: self.pos, width: width}
  64. }
  65. self.pos += len(string(ch))
  66. return nil
  67. }
  68. func (self *truncate_iterator) parse(b []byte) (ans int, width int) {
  69. err := self.w.parser.Parse(b)
  70. var te *truncate_error
  71. if err != nil && errors.As(err, &te) {
  72. return te.pos, te.width
  73. }
  74. if self.limit_exceeded_at != nil {
  75. return self.limit_exceeded_at.pos, self.limit_exceeded_at.width
  76. }
  77. return len(b), self.w.current_width
  78. }
  79. func TruncateToVisualLengthWithWidth(text string, length int) (truncated string, width_of_truncated int) {
  80. if length < 1 {
  81. return text[:0], 0
  82. }
  83. t := create_truncate_iterator()
  84. t.limit = length
  85. t.limit_exceeded_at = nil
  86. t.w.current_width = 0
  87. truncate_point, width := t.parse(utils.UnsafeStringToBytes(text))
  88. return text[:truncate_point], width
  89. }
  90. func TruncateToVisualLength(text string, length int) string {
  91. ans, _ := TruncateToVisualLengthWithWidth(text, length)
  92. return ans
  93. }