runeutil.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. // Package runeutil provides utility functions for tidying up incoming runes
  2. // from Key messages.
  3. package textarea
  4. import (
  5. "unicode"
  6. "unicode/utf8"
  7. )
  8. // Sanitizer is a helper for bubble widgets that want to process
  9. // Runes from input key messages.
  10. type Sanitizer interface {
  11. // Sanitize removes control characters from runes in a KeyRunes
  12. // message, and optionally replaces newline/carriage return/tabs by a
  13. // specified character.
  14. //
  15. // The rune array is modified in-place if possible. In that case, the
  16. // returned slice is the original slice shortened after the control
  17. // characters have been removed/translated.
  18. Sanitize(runes []rune) []rune
  19. }
  20. // NewSanitizer constructs a rune sanitizer.
  21. func NewSanitizer(opts ...Option) Sanitizer {
  22. s := sanitizer{
  23. replaceNewLine: []rune("\n"),
  24. replaceTab: []rune(" "),
  25. }
  26. for _, o := range opts {
  27. s = o(s)
  28. }
  29. return &s
  30. }
  31. // Option is the type of option that can be passed to Sanitize().
  32. type Option func(sanitizer) sanitizer
  33. // ReplaceTabs replaces tabs by the specified string.
  34. func ReplaceTabs(tabRepl string) Option {
  35. return func(s sanitizer) sanitizer {
  36. s.replaceTab = []rune(tabRepl)
  37. return s
  38. }
  39. }
  40. // ReplaceNewlines replaces newline characters by the specified string.
  41. func ReplaceNewlines(nlRepl string) Option {
  42. return func(s sanitizer) sanitizer {
  43. s.replaceNewLine = []rune(nlRepl)
  44. return s
  45. }
  46. }
  47. func (s *sanitizer) Sanitize(runes []rune) []rune {
  48. // dstrunes are where we are storing the result.
  49. dstrunes := runes[:0:len(runes)]
  50. // copied indicates whether dstrunes is an alias of runes
  51. // or a copy. We need a copy when dst moves past src.
  52. // We use this as an optimization to avoid allocating
  53. // a new rune slice in the common case where the output
  54. // is smaller or equal to the input.
  55. copied := false
  56. for src := 0; src < len(runes); src++ {
  57. r := runes[src]
  58. switch {
  59. case r == utf8.RuneError:
  60. // skip
  61. case r == '\r' || r == '\n':
  62. if len(dstrunes)+len(s.replaceNewLine) > src && !copied {
  63. dst := len(dstrunes)
  64. dstrunes = make([]rune, dst, len(runes)+len(s.replaceNewLine))
  65. copy(dstrunes, runes[:dst])
  66. copied = true
  67. }
  68. dstrunes = append(dstrunes, s.replaceNewLine...)
  69. case r == '\t':
  70. if len(dstrunes)+len(s.replaceTab) > src && !copied {
  71. dst := len(dstrunes)
  72. dstrunes = make([]rune, dst, len(runes)+len(s.replaceTab))
  73. copy(dstrunes, runes[:dst])
  74. copied = true
  75. }
  76. dstrunes = append(dstrunes, s.replaceTab...)
  77. case unicode.IsControl(r):
  78. // Other control characters: skip.
  79. default:
  80. // Keep the character.
  81. dstrunes = append(dstrunes, runes[src])
  82. }
  83. }
  84. return dstrunes
  85. }
  86. type sanitizer struct {
  87. replaceNewLine []rune
  88. replaceTab []rune
  89. }