buffer.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. package readline
  2. import (
  3. "fmt"
  4. "os"
  5. "github.com/emirpasic/gods/lists/arraylist"
  6. "golang.org/x/term"
  7. )
  8. type Buffer struct {
  9. Pos int
  10. Buf *arraylist.List
  11. Prompt *Prompt
  12. LineWidth int
  13. Width int
  14. Height int
  15. }
  16. func NewBuffer(prompt *Prompt) (*Buffer, error) {
  17. fd := int(os.Stdout.Fd())
  18. width, height := 80, 24
  19. if termWidth, termHeight, err := term.GetSize(fd); err == nil {
  20. width, height = termWidth, termHeight
  21. }
  22. lwidth := width - len(prompt.prompt())
  23. b := &Buffer{
  24. Pos: 0,
  25. Buf: arraylist.New(),
  26. Prompt: prompt,
  27. Width: width,
  28. Height: height,
  29. LineWidth: lwidth,
  30. }
  31. return b, nil
  32. }
  33. func (b *Buffer) MoveLeft() {
  34. if b.Pos > 0 {
  35. if b.Pos%b.LineWidth == 0 {
  36. fmt.Printf(CursorUp + CursorBOL + cursorRightN(b.Width))
  37. } else {
  38. fmt.Print(CursorLeft)
  39. }
  40. b.Pos -= 1
  41. }
  42. }
  43. func (b *Buffer) MoveLeftWord() {
  44. if b.Pos > 0 {
  45. var foundNonspace bool
  46. for {
  47. v, _ := b.Buf.Get(b.Pos - 1)
  48. if v == ' ' {
  49. if foundNonspace {
  50. break
  51. }
  52. } else {
  53. foundNonspace = true
  54. }
  55. b.MoveLeft()
  56. if b.Pos == 0 {
  57. break
  58. }
  59. }
  60. }
  61. }
  62. func (b *Buffer) MoveRight() {
  63. if b.Pos < b.Size() {
  64. b.Pos += 1
  65. if b.Pos%b.LineWidth == 0 {
  66. fmt.Printf(CursorDown + CursorBOL + cursorRightN(len(b.Prompt.prompt())))
  67. } else {
  68. fmt.Print(CursorRight)
  69. }
  70. }
  71. }
  72. func (b *Buffer) MoveRightWord() {
  73. if b.Pos < b.Size() {
  74. for {
  75. b.MoveRight()
  76. v, _ := b.Buf.Get(b.Pos)
  77. if v == ' ' {
  78. break
  79. }
  80. if b.Pos == b.Size() {
  81. break
  82. }
  83. }
  84. }
  85. }
  86. func (b *Buffer) MoveToStart() {
  87. if b.Pos > 0 {
  88. currLine := b.Pos / b.LineWidth
  89. if currLine > 0 {
  90. for cnt := 0; cnt < currLine; cnt++ {
  91. fmt.Print(CursorUp)
  92. }
  93. }
  94. fmt.Printf(CursorBOL + cursorRightN(len(b.Prompt.prompt())))
  95. b.Pos = 0
  96. }
  97. }
  98. func (b *Buffer) MoveToEnd() {
  99. if b.Pos < b.Size() {
  100. currLine := b.Pos / b.LineWidth
  101. totalLines := b.Size() / b.LineWidth
  102. if currLine < totalLines {
  103. for cnt := 0; cnt < totalLines-currLine; cnt++ {
  104. fmt.Print(CursorDown)
  105. }
  106. remainder := b.Size() % b.LineWidth
  107. fmt.Printf(CursorBOL + cursorRightN(len(b.Prompt.prompt())+remainder))
  108. } else {
  109. fmt.Print(cursorRightN(b.Size() - b.Pos))
  110. }
  111. b.Pos = b.Size()
  112. }
  113. }
  114. func (b *Buffer) Size() int {
  115. return b.Buf.Size()
  116. }
  117. func (b *Buffer) Add(r rune) {
  118. if b.Pos == b.Buf.Size() {
  119. fmt.Printf("%c", r)
  120. b.Buf.Add(r)
  121. b.Pos += 1
  122. if b.Pos > 0 && b.Pos%b.LineWidth == 0 {
  123. fmt.Printf("\n%s", b.Prompt.AltPrompt)
  124. }
  125. } else {
  126. fmt.Printf("%c", r)
  127. b.Buf.Insert(b.Pos, r)
  128. b.Pos += 1
  129. if b.Pos > 0 && b.Pos%b.LineWidth == 0 {
  130. fmt.Printf("\n%s", b.Prompt.AltPrompt)
  131. }
  132. b.drawRemaining()
  133. }
  134. }
  135. func (b *Buffer) drawRemaining() {
  136. var place int
  137. remainingText := b.StringN(b.Pos)
  138. if b.Pos > 0 {
  139. place = b.Pos % b.LineWidth
  140. }
  141. fmt.Print(CursorHide)
  142. // render the rest of the current line
  143. currLine := remainingText[:min(b.LineWidth-place, len(remainingText))]
  144. if len(currLine) > 0 {
  145. fmt.Printf(ClearToEOL + currLine)
  146. fmt.Print(cursorLeftN(len(currLine)))
  147. } else {
  148. fmt.Print(ClearToEOL)
  149. }
  150. // render the other lines
  151. if len(remainingText) > len(currLine) {
  152. remaining := []rune(remainingText[len(currLine):])
  153. var totalLines int
  154. for i, c := range remaining {
  155. if i%b.LineWidth == 0 {
  156. fmt.Printf("\n%s", b.Prompt.AltPrompt)
  157. totalLines += 1
  158. }
  159. fmt.Printf("%c", c)
  160. }
  161. fmt.Print(ClearToEOL)
  162. fmt.Print(cursorUpN(totalLines))
  163. fmt.Printf(CursorBOL + cursorRightN(b.Width-len(currLine)))
  164. }
  165. fmt.Print(CursorShow)
  166. }
  167. func (b *Buffer) Remove() {
  168. if b.Buf.Size() > 0 && b.Pos > 0 {
  169. if b.Pos%b.LineWidth == 0 {
  170. // if the user backspaces over the word boundary, do this magic to clear the line
  171. // and move to the end of the previous line
  172. fmt.Printf(CursorBOL + ClearToEOL)
  173. fmt.Printf(CursorUp + CursorBOL + cursorRightN(b.Width) + " " + CursorLeft)
  174. } else {
  175. fmt.Printf(CursorLeft + " " + CursorLeft)
  176. }
  177. var eraseExtraLine bool
  178. if (b.Size()-1)%b.LineWidth == 0 {
  179. eraseExtraLine = true
  180. }
  181. b.Pos -= 1
  182. b.Buf.Remove(b.Pos)
  183. if b.Pos < b.Size() {
  184. b.drawRemaining()
  185. // this erases a line which is left over when backspacing in the middle of a line and there
  186. // are trailing characters which go over the line width boundary
  187. if eraseExtraLine {
  188. remainingLines := (b.Size() - b.Pos) / b.LineWidth
  189. fmt.Printf(cursorDownN(remainingLines+1) + CursorBOL + ClearToEOL)
  190. place := b.Pos % b.LineWidth
  191. fmt.Printf(cursorUpN(remainingLines+1) + cursorRightN(place+len(b.Prompt.prompt())))
  192. }
  193. }
  194. }
  195. }
  196. func (b *Buffer) Delete() {
  197. if b.Size() > 0 && b.Pos < b.Size() {
  198. b.Buf.Remove(b.Pos)
  199. b.drawRemaining()
  200. if b.Size()%b.LineWidth == 0 {
  201. if b.Pos != b.Size() {
  202. remainingLines := (b.Size() - b.Pos) / b.LineWidth
  203. fmt.Printf(cursorDownN(remainingLines) + CursorBOL + ClearToEOL)
  204. place := b.Pos % b.LineWidth
  205. fmt.Printf(cursorUpN(remainingLines) + cursorRightN(place+len(b.Prompt.prompt())))
  206. }
  207. }
  208. }
  209. }
  210. func (b *Buffer) DeleteBefore() {
  211. if b.Pos > 0 {
  212. for cnt := b.Pos - 1; cnt >= 0; cnt-- {
  213. b.Remove()
  214. }
  215. }
  216. }
  217. func (b *Buffer) DeleteRemaining() {
  218. if b.Size() > 0 && b.Pos < b.Size() {
  219. charsToDel := b.Size() - b.Pos
  220. for cnt := 0; cnt < charsToDel; cnt++ {
  221. b.Delete()
  222. }
  223. }
  224. }
  225. func (b *Buffer) DeleteWord() {
  226. if b.Buf.Size() > 0 && b.Pos > 0 {
  227. var foundNonspace bool
  228. for {
  229. v, _ := b.Buf.Get(b.Pos - 1)
  230. if v == ' ' {
  231. if !foundNonspace {
  232. b.Remove()
  233. } else {
  234. break
  235. }
  236. } else {
  237. foundNonspace = true
  238. b.Remove()
  239. }
  240. if b.Pos == 0 {
  241. break
  242. }
  243. }
  244. }
  245. }
  246. func (b *Buffer) ClearScreen() {
  247. fmt.Printf(ClearScreen + CursorReset + b.Prompt.prompt())
  248. if b.IsEmpty() {
  249. ph := b.Prompt.placeholder()
  250. fmt.Printf(ColorGrey + ph + cursorLeftN(len(ph)) + ColorDefault)
  251. } else {
  252. currPos := b.Pos
  253. b.Pos = 0
  254. b.drawRemaining()
  255. fmt.Printf(CursorReset + cursorRightN(len(b.Prompt.prompt())))
  256. if currPos > 0 {
  257. targetLine := currPos / b.LineWidth
  258. if targetLine > 0 {
  259. for cnt := 0; cnt < targetLine; cnt++ {
  260. fmt.Print(CursorDown)
  261. }
  262. }
  263. remainder := currPos % b.LineWidth
  264. if remainder > 0 {
  265. fmt.Print(cursorRightN(remainder))
  266. }
  267. if currPos%b.LineWidth == 0 {
  268. fmt.Printf(CursorBOL + b.Prompt.AltPrompt)
  269. }
  270. }
  271. b.Pos = currPos
  272. }
  273. }
  274. func (b *Buffer) IsEmpty() bool {
  275. return b.Buf.Empty()
  276. }
  277. func (b *Buffer) Replace(r []rune) {
  278. b.Pos = 0
  279. b.Buf.Clear()
  280. fmt.Printf(ClearLine + CursorBOL + b.Prompt.prompt())
  281. for _, c := range r {
  282. b.Add(c)
  283. }
  284. }
  285. func (b *Buffer) String() string {
  286. return b.StringN(0)
  287. }
  288. func (b *Buffer) StringN(n int) string {
  289. return b.StringNM(n, 0)
  290. }
  291. func (b *Buffer) StringNM(n, m int) string {
  292. var s string
  293. if m == 0 {
  294. m = b.Size()
  295. }
  296. for cnt := n; cnt < m; cnt++ {
  297. c, _ := b.Buf.Get(cnt)
  298. s += string(c.(rune))
  299. }
  300. return s
  301. }
  302. func cursorLeftN(n int) string {
  303. return fmt.Sprintf(CursorLeftN, n)
  304. }
  305. func cursorRightN(n int) string {
  306. return fmt.Sprintf(CursorRightN, n)
  307. }
  308. func cursorUpN(n int) string {
  309. return fmt.Sprintf(CursorUpN, n)
  310. }
  311. func cursorDownN(n int) string {
  312. return fmt.Sprintf(CursorDownN, n)
  313. }