buffer.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. package prompt
  2. import (
  3. "strings"
  4. "github.com/c-bata/go-prompt/internal/debug"
  5. )
  6. // Buffer emulates the console buffer.
  7. type Buffer struct {
  8. workingLines []string // The working lines. Similar to history
  9. workingIndex int
  10. cursorPosition int
  11. cacheDocument *Document
  12. preferredColumn int // Remember the original column for the next up/down movement.
  13. lastKeyStroke Key
  14. }
  15. // Text returns string of the current line.
  16. func (b *Buffer) Text() string {
  17. return b.workingLines[b.workingIndex]
  18. }
  19. // Document method to return document instance from the current text and cursor position.
  20. func (b *Buffer) Document() (d *Document) {
  21. if b.cacheDocument == nil ||
  22. b.cacheDocument.Text != b.Text() ||
  23. b.cacheDocument.cursorPosition != b.cursorPosition {
  24. b.cacheDocument = &Document{
  25. Text: b.Text(),
  26. cursorPosition: b.cursorPosition,
  27. }
  28. }
  29. b.cacheDocument.lastKey = b.lastKeyStroke
  30. return b.cacheDocument
  31. }
  32. // DisplayCursorPosition returns the cursor position on rendered text on terminal emulators.
  33. // So if Document is "日本(cursor)語", DisplayedCursorPosition returns 4 because '日' and '本' are double width characters.
  34. func (b *Buffer) DisplayCursorPosition() int {
  35. return b.Document().DisplayCursorPosition()
  36. }
  37. // InsertText insert string from current line.
  38. func (b *Buffer) InsertText(v string, overwrite bool, moveCursor bool) {
  39. or := []rune(b.Text())
  40. oc := b.cursorPosition
  41. if overwrite {
  42. overwritten := string(or[oc : oc+len(v)])
  43. if strings.Contains(overwritten, "\n") {
  44. i := strings.IndexAny(overwritten, "\n")
  45. overwritten = overwritten[:i]
  46. }
  47. b.setText(string(or[:oc]) + v + string(or[oc+len(overwritten):]))
  48. } else {
  49. b.setText(string(or[:oc]) + v + string(or[oc:]))
  50. }
  51. if moveCursor {
  52. b.cursorPosition += len([]rune(v))
  53. }
  54. }
  55. // SetText method to set text and update cursorPosition.
  56. // (When doing this, make sure that the cursor_position is valid for this text.
  57. // text/cursor_position should be consistent at any time, otherwise set a Document instead.)
  58. func (b *Buffer) setText(v string) {
  59. debug.Assert(b.cursorPosition <= len([]rune(v)), "length of input should be shorter than cursor position")
  60. b.workingLines[b.workingIndex] = v
  61. }
  62. // Set cursor position. Return whether it changed.
  63. func (b *Buffer) setCursorPosition(p int) {
  64. if p > 0 {
  65. b.cursorPosition = p
  66. } else {
  67. b.cursorPosition = 0
  68. }
  69. }
  70. func (b *Buffer) setDocument(d *Document) {
  71. b.cacheDocument = d
  72. b.setCursorPosition(d.cursorPosition) // Call before setText because setText check the relation between cursorPosition and line length.
  73. b.setText(d.Text)
  74. }
  75. // CursorLeft move to left on the current line.
  76. func (b *Buffer) CursorLeft(count int) {
  77. l := b.Document().GetCursorLeftPosition(count)
  78. b.cursorPosition += l
  79. }
  80. // CursorRight move to right on the current line.
  81. func (b *Buffer) CursorRight(count int) {
  82. l := b.Document().GetCursorRightPosition(count)
  83. b.cursorPosition += l
  84. }
  85. // CursorUp move cursor to the previous line.
  86. // (for multi-line edit).
  87. func (b *Buffer) CursorUp(count int) {
  88. orig := b.preferredColumn
  89. if b.preferredColumn == -1 { // -1 means nil
  90. orig = b.Document().CursorPositionCol()
  91. }
  92. b.cursorPosition += b.Document().GetCursorUpPosition(count, orig)
  93. // Remember the original column for the next up/down movement.
  94. b.preferredColumn = orig
  95. }
  96. // CursorDown move cursor to the next line.
  97. // (for multi-line edit).
  98. func (b *Buffer) CursorDown(count int) {
  99. orig := b.preferredColumn
  100. if b.preferredColumn == -1 { // -1 means nil
  101. orig = b.Document().CursorPositionCol()
  102. }
  103. b.cursorPosition += b.Document().GetCursorDownPosition(count, orig)
  104. // Remember the original column for the next up/down movement.
  105. b.preferredColumn = orig
  106. }
  107. // DeleteBeforeCursor delete specified number of characters before cursor and return the deleted text.
  108. func (b *Buffer) DeleteBeforeCursor(count int) (deleted string) {
  109. debug.Assert(count >= 0, "count should be positive")
  110. r := []rune(b.Text())
  111. if b.cursorPosition > 0 {
  112. start := b.cursorPosition - count
  113. if start < 0 {
  114. start = 0
  115. }
  116. deleted = string(r[start:b.cursorPosition])
  117. b.setDocument(&Document{
  118. Text: string(r[:start]) + string(r[b.cursorPosition:]),
  119. cursorPosition: b.cursorPosition - len([]rune(deleted)),
  120. })
  121. }
  122. return
  123. }
  124. // NewLine means CR.
  125. func (b *Buffer) NewLine(copyMargin bool) {
  126. if copyMargin {
  127. b.InsertText("\n"+b.Document().leadingWhitespaceInCurrentLine(), false, true)
  128. } else {
  129. b.InsertText("\n", false, true)
  130. }
  131. }
  132. // Delete specified number of characters and Return the deleted text.
  133. func (b *Buffer) Delete(count int) (deleted string) {
  134. r := []rune(b.Text())
  135. if b.cursorPosition < len(r) {
  136. deleted = b.Document().TextAfterCursor()[:count]
  137. b.setText(string(r[:b.cursorPosition]) + string(r[b.cursorPosition+len(deleted):]))
  138. }
  139. return
  140. }
  141. // JoinNextLine joins the next line to the current one by deleting the line ending after the current line.
  142. func (b *Buffer) JoinNextLine(separator string) {
  143. if !b.Document().OnLastLine() {
  144. b.cursorPosition += b.Document().GetEndOfLinePosition()
  145. b.Delete(1)
  146. // Remove spaces
  147. b.setText(b.Document().TextBeforeCursor() + separator + strings.TrimLeft(b.Document().TextAfterCursor(), " "))
  148. }
  149. }
  150. // SwapCharactersBeforeCursor swaps the last two characters before the cursor.
  151. func (b *Buffer) SwapCharactersBeforeCursor() {
  152. if b.cursorPosition >= 2 {
  153. x := b.Text()[b.cursorPosition-2 : b.cursorPosition-1]
  154. y := b.Text()[b.cursorPosition-1 : b.cursorPosition]
  155. b.setText(b.Text()[:b.cursorPosition-2] + y + x + b.Text()[b.cursorPosition:])
  156. }
  157. }
  158. // NewBuffer is constructor of Buffer struct.
  159. func NewBuffer() (b *Buffer) {
  160. b = &Buffer{
  161. workingLines: []string{""},
  162. workingIndex: 0,
  163. preferredColumn: -1, // -1 means nil
  164. }
  165. return
  166. }