completion.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. package prompt
  2. import (
  3. "strings"
  4. "github.com/c-bata/go-prompt/internal/debug"
  5. runewidth "github.com/mattn/go-runewidth"
  6. )
  7. const (
  8. shortenSuffix = "..."
  9. leftPrefix = " "
  10. leftSuffix = " "
  11. rightPrefix = " "
  12. rightSuffix = " "
  13. )
  14. var (
  15. leftMargin = runewidth.StringWidth(leftPrefix + leftSuffix)
  16. rightMargin = runewidth.StringWidth(rightPrefix + rightSuffix)
  17. completionMargin = leftMargin + rightMargin
  18. )
  19. // Suggest is printed when completing.
  20. type Suggest struct {
  21. Text string
  22. Description string
  23. }
  24. // CompletionManager manages which suggestion is now selected.
  25. type CompletionManager struct {
  26. selected int // -1 means nothing one is selected.
  27. tmp []Suggest
  28. max uint16
  29. completer Completer
  30. verticalScroll int
  31. wordSeparator string
  32. showAtStart bool
  33. }
  34. // GetSelectedSuggestion returns the selected item.
  35. func (c *CompletionManager) GetSelectedSuggestion() (s Suggest, ok bool) {
  36. if c.selected == -1 {
  37. return Suggest{}, false
  38. } else if c.selected < -1 {
  39. debug.Assert(false, "must not reach here")
  40. c.selected = -1
  41. return Suggest{}, false
  42. }
  43. return c.tmp[c.selected], true
  44. }
  45. // GetSuggestions returns the list of suggestion.
  46. func (c *CompletionManager) GetSuggestions() []Suggest {
  47. return c.tmp
  48. }
  49. // Reset to select nothing.
  50. func (c *CompletionManager) Reset() {
  51. c.selected = -1
  52. c.verticalScroll = 0
  53. c.Update(*NewDocument())
  54. }
  55. // Update to update the suggestions.
  56. func (c *CompletionManager) Update(in Document) {
  57. c.tmp = c.completer(in)
  58. }
  59. // Previous to select the previous suggestion item.
  60. func (c *CompletionManager) Previous() {
  61. if c.verticalScroll == c.selected && c.selected > 0 {
  62. c.verticalScroll--
  63. }
  64. c.selected--
  65. c.update()
  66. }
  67. // Next to select the next suggestion item.
  68. func (c *CompletionManager) Next() {
  69. if c.verticalScroll+int(c.max)-1 == c.selected {
  70. c.verticalScroll++
  71. }
  72. c.selected++
  73. c.update()
  74. }
  75. // Completing returns whether the CompletionManager selects something one.
  76. func (c *CompletionManager) Completing() bool {
  77. return c.selected != -1
  78. }
  79. func (c *CompletionManager) update() {
  80. max := int(c.max)
  81. if len(c.tmp) < max {
  82. max = len(c.tmp)
  83. }
  84. if c.selected >= len(c.tmp) {
  85. c.Reset()
  86. } else if c.selected < -1 {
  87. c.selected = len(c.tmp) - 1
  88. c.verticalScroll = len(c.tmp) - max
  89. }
  90. }
  91. func deleteBreakLineCharacters(s string) string {
  92. s = strings.Replace(s, "\n", "", -1)
  93. s = strings.Replace(s, "\r", "", -1)
  94. return s
  95. }
  96. func formatTexts(o []string, max int, prefix, suffix string) (new []string, width int) {
  97. l := len(o)
  98. n := make([]string, l)
  99. lenPrefix := runewidth.StringWidth(prefix)
  100. lenSuffix := runewidth.StringWidth(suffix)
  101. lenShorten := runewidth.StringWidth(shortenSuffix)
  102. min := lenPrefix + lenSuffix + lenShorten
  103. for i := 0; i < l; i++ {
  104. o[i] = deleteBreakLineCharacters(o[i])
  105. w := runewidth.StringWidth(o[i])
  106. if width < w {
  107. width = w
  108. }
  109. }
  110. if width == 0 {
  111. return n, 0
  112. }
  113. if min >= max {
  114. return n, 0
  115. }
  116. if lenPrefix+width+lenSuffix > max {
  117. width = max - lenPrefix - lenSuffix
  118. }
  119. for i := 0; i < l; i++ {
  120. x := runewidth.StringWidth(o[i])
  121. if x <= width {
  122. spaces := strings.Repeat(" ", width-x)
  123. n[i] = prefix + o[i] + spaces + suffix
  124. } else if x > width {
  125. x := runewidth.Truncate(o[i], width, shortenSuffix)
  126. // When calling runewidth.Truncate("您好xxx您好xxx", 11, "...") returns "您好xxx..."
  127. // But the length of this result is 10. So we need fill right using runewidth.FillRight.
  128. n[i] = prefix + runewidth.FillRight(x, width) + suffix
  129. }
  130. }
  131. return n, lenPrefix + width + lenSuffix
  132. }
  133. func formatSuggestions(suggests []Suggest, max int) (new []Suggest, width int) {
  134. num := len(suggests)
  135. new = make([]Suggest, num)
  136. left := make([]string, num)
  137. for i := 0; i < num; i++ {
  138. left[i] = suggests[i].Text
  139. }
  140. right := make([]string, num)
  141. for i := 0; i < num; i++ {
  142. right[i] = suggests[i].Description
  143. }
  144. left, leftWidth := formatTexts(left, max, leftPrefix, leftSuffix)
  145. if leftWidth == 0 {
  146. return []Suggest{}, 0
  147. }
  148. right, rightWidth := formatTexts(right, max-leftWidth, rightPrefix, rightSuffix)
  149. for i := 0; i < num; i++ {
  150. new[i] = Suggest{Text: left[i], Description: right[i]}
  151. }
  152. return new, leftWidth + rightWidth
  153. }
  154. // NewCompletionManager returns initialized CompletionManager object.
  155. func NewCompletionManager(completer Completer, max uint16) *CompletionManager {
  156. return &CompletionManager{
  157. selected: -1,
  158. max: max,
  159. completer: completer,
  160. verticalScroll: 0,
  161. }
  162. }