buffer.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. package readline
  2. import (
  3. "fmt"
  4. "os"
  5. "github.com/emirpasic/gods/v2/lists/arraylist"
  6. "github.com/mattn/go-runewidth"
  7. "golang.org/x/term"
  8. )
  9. type Buffer struct {
  10. DisplayPos int
  11. Pos int
  12. Buf *arraylist.List[rune]
  13. // LineHasSpace is an arraylist of bools to keep track of whether a line has a space at the end
  14. LineHasSpace *arraylist.List[bool]
  15. Prompt *Prompt
  16. LineWidth int
  17. Width int
  18. Height int
  19. }
  20. func NewBuffer(prompt *Prompt) (*Buffer, error) {
  21. fd := int(os.Stdout.Fd())
  22. width, height := 80, 24
  23. if termWidth, termHeight, err := term.GetSize(fd); err == nil {
  24. width, height = termWidth, termHeight
  25. }
  26. lwidth := width - len(prompt.prompt())
  27. b := &Buffer{
  28. DisplayPos: 0,
  29. Pos: 0,
  30. Buf: arraylist.New[rune](),
  31. LineHasSpace: arraylist.New[bool](),
  32. Prompt: prompt,
  33. Width: width,
  34. Height: height,
  35. LineWidth: lwidth,
  36. }
  37. return b, nil
  38. }
  39. func (b *Buffer) GetLineSpacing(line int) bool {
  40. hasSpace, _ := b.LineHasSpace.Get(line)
  41. return hasSpace
  42. }
  43. func (b *Buffer) MoveLeft() {
  44. if b.Pos > 0 {
  45. // asserts that we retrieve a rune
  46. if r, ok := b.Buf.Get(b.Pos - 1); ok {
  47. rLength := runewidth.RuneWidth(r)
  48. if b.DisplayPos%b.LineWidth == 0 {
  49. fmt.Print(CursorUp + CursorBOL + CursorRightN(b.Width))
  50. if rLength == 2 {
  51. fmt.Print(CursorLeft)
  52. }
  53. line := b.DisplayPos/b.LineWidth - 1
  54. hasSpace := b.GetLineSpacing(line)
  55. if hasSpace {
  56. b.DisplayPos -= 1
  57. fmt.Print(CursorLeft)
  58. }
  59. } else {
  60. fmt.Print(CursorLeftN(rLength))
  61. }
  62. b.Pos -= 1
  63. b.DisplayPos -= rLength
  64. }
  65. }
  66. }
  67. func (b *Buffer) MoveLeftWord() {
  68. if b.Pos > 0 {
  69. var foundNonspace bool
  70. for {
  71. v, _ := b.Buf.Get(b.Pos - 1)
  72. if v == ' ' {
  73. if foundNonspace {
  74. break
  75. }
  76. } else {
  77. foundNonspace = true
  78. }
  79. b.MoveLeft()
  80. if b.Pos == 0 {
  81. break
  82. }
  83. }
  84. }
  85. }
  86. func (b *Buffer) MoveRight() {
  87. if b.Pos < b.Buf.Size() {
  88. if r, ok := b.Buf.Get(b.Pos); ok {
  89. rLength := runewidth.RuneWidth(r)
  90. b.Pos += 1
  91. hasSpace := b.GetLineSpacing(b.DisplayPos / b.LineWidth)
  92. b.DisplayPos += rLength
  93. if b.DisplayPos%b.LineWidth == 0 {
  94. fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt())))
  95. } else if (b.DisplayPos-rLength)%b.LineWidth == b.LineWidth-1 && hasSpace {
  96. fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt())+rLength))
  97. b.DisplayPos += 1
  98. } else if b.LineHasSpace.Size() > 0 && b.DisplayPos%b.LineWidth == b.LineWidth-1 && hasSpace {
  99. fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt())))
  100. b.DisplayPos += 1
  101. } else {
  102. fmt.Print(CursorRightN(rLength))
  103. }
  104. }
  105. }
  106. }
  107. func (b *Buffer) MoveRightWord() {
  108. if b.Pos < b.Buf.Size() {
  109. for {
  110. b.MoveRight()
  111. v, _ := b.Buf.Get(b.Pos)
  112. if v == ' ' {
  113. break
  114. }
  115. if b.Pos == b.Buf.Size() {
  116. break
  117. }
  118. }
  119. }
  120. }
  121. func (b *Buffer) MoveToStart() {
  122. if b.Pos > 0 {
  123. currLine := b.DisplayPos / b.LineWidth
  124. if currLine > 0 {
  125. for range currLine {
  126. fmt.Print(CursorUp)
  127. }
  128. }
  129. fmt.Print(CursorBOL + CursorRightN(len(b.Prompt.prompt())))
  130. b.Pos = 0
  131. b.DisplayPos = 0
  132. }
  133. }
  134. func (b *Buffer) MoveToEnd() {
  135. if b.Pos < b.Buf.Size() {
  136. currLine := b.DisplayPos / b.LineWidth
  137. totalLines := b.DisplaySize() / b.LineWidth
  138. if currLine < totalLines {
  139. for range totalLines - currLine {
  140. fmt.Print(CursorDown)
  141. }
  142. remainder := b.DisplaySize() % b.LineWidth
  143. fmt.Print(CursorBOL + CursorRightN(len(b.Prompt.prompt())+remainder))
  144. } else {
  145. fmt.Print(CursorRightN(b.DisplaySize() - b.DisplayPos))
  146. }
  147. b.Pos = b.Buf.Size()
  148. b.DisplayPos = b.DisplaySize()
  149. }
  150. }
  151. func (b *Buffer) DisplaySize() int {
  152. sum := 0
  153. for i := range b.Buf.Size() {
  154. if r, ok := b.Buf.Get(i); ok {
  155. sum += runewidth.RuneWidth(r)
  156. }
  157. }
  158. return sum
  159. }
  160. func (b *Buffer) Add(r rune) {
  161. if b.Pos == b.Buf.Size() {
  162. b.AddChar(r, false)
  163. } else {
  164. b.AddChar(r, true)
  165. }
  166. }
  167. func (b *Buffer) AddChar(r rune, insert bool) {
  168. rLength := runewidth.RuneWidth(r)
  169. b.DisplayPos += rLength
  170. if b.Pos > 0 {
  171. if b.DisplayPos%b.LineWidth == 0 {
  172. fmt.Printf("%c", r)
  173. fmt.Printf("\n%s", b.Prompt.AltPrompt)
  174. if insert {
  175. b.LineHasSpace.Set(b.DisplayPos/b.LineWidth-1, false)
  176. } else {
  177. b.LineHasSpace.Add(false)
  178. }
  179. // this case occurs when a double-width rune crosses the line boundary
  180. } else if b.DisplayPos%b.LineWidth < (b.DisplayPos-rLength)%b.LineWidth {
  181. if insert {
  182. fmt.Print(ClearToEOL)
  183. }
  184. fmt.Printf("\n%s", b.Prompt.AltPrompt)
  185. b.DisplayPos += 1
  186. fmt.Printf("%c", r)
  187. if insert {
  188. b.LineHasSpace.Set(b.DisplayPos/b.LineWidth-1, true)
  189. } else {
  190. b.LineHasSpace.Add(true)
  191. }
  192. } else {
  193. fmt.Printf("%c", r)
  194. }
  195. } else {
  196. fmt.Printf("%c", r)
  197. }
  198. if insert {
  199. b.Buf.Insert(b.Pos, r)
  200. } else {
  201. b.Buf.Add(r)
  202. }
  203. b.Pos += 1
  204. if insert {
  205. b.drawRemaining()
  206. }
  207. }
  208. func (b *Buffer) countRemainingLineWidth(place int) int {
  209. var sum int
  210. counter := -1
  211. var prevLen int
  212. for place <= b.LineWidth {
  213. counter += 1
  214. sum += prevLen
  215. if r, ok := b.Buf.Get(b.Pos + counter); ok {
  216. place += runewidth.RuneWidth(r)
  217. prevLen = len(string(r))
  218. } else {
  219. break
  220. }
  221. }
  222. return sum
  223. }
  224. func (b *Buffer) drawRemaining() {
  225. var place int
  226. remainingText := b.StringN(b.Pos)
  227. if b.Pos > 0 {
  228. place = b.DisplayPos % b.LineWidth
  229. }
  230. fmt.Print(CursorHide)
  231. // render the rest of the current line
  232. currLineLength := b.countRemainingLineWidth(place)
  233. currLine := remainingText[:min(currLineLength, len(remainingText))]
  234. currLineSpace := runewidth.StringWidth(currLine)
  235. remLength := runewidth.StringWidth(remainingText)
  236. if len(currLine) > 0 {
  237. fmt.Print(ClearToEOL + currLine + CursorLeftN(currLineSpace))
  238. } else {
  239. fmt.Print(ClearToEOL)
  240. }
  241. if currLineSpace != b.LineWidth-place && currLineSpace != remLength {
  242. b.LineHasSpace.Set(b.DisplayPos/b.LineWidth, true)
  243. } else if currLineSpace != b.LineWidth-place {
  244. b.LineHasSpace.Remove(b.DisplayPos / b.LineWidth)
  245. } else {
  246. b.LineHasSpace.Set(b.DisplayPos/b.LineWidth, false)
  247. }
  248. if (b.DisplayPos+currLineSpace)%b.LineWidth == 0 && currLine == remainingText {
  249. fmt.Print(CursorRightN(currLineSpace))
  250. fmt.Printf("\n%s", b.Prompt.AltPrompt)
  251. fmt.Print(CursorUp + CursorBOL + CursorRightN(b.Width-currLineSpace))
  252. }
  253. // render the other lines
  254. if remLength > currLineSpace {
  255. remaining := (remainingText[len(currLine):])
  256. var totalLines int
  257. var displayLength int
  258. var lineLength int = currLineSpace
  259. for _, c := range remaining {
  260. if displayLength == 0 || (displayLength+runewidth.RuneWidth(c))%b.LineWidth < displayLength%b.LineWidth {
  261. fmt.Printf("\n%s", b.Prompt.AltPrompt)
  262. totalLines += 1
  263. if displayLength != 0 {
  264. if lineLength == b.LineWidth {
  265. b.LineHasSpace.Set(b.DisplayPos/b.LineWidth+totalLines-1, false)
  266. } else {
  267. b.LineHasSpace.Set(b.DisplayPos/b.LineWidth+totalLines-1, true)
  268. }
  269. }
  270. lineLength = 0
  271. }
  272. displayLength += runewidth.RuneWidth(c)
  273. lineLength += runewidth.RuneWidth(c)
  274. fmt.Printf("%c", c)
  275. }
  276. fmt.Print(ClearToEOL + CursorUpN(totalLines) + CursorBOL + CursorRightN(b.Width-currLineSpace))
  277. hasSpace := b.GetLineSpacing(b.DisplayPos / b.LineWidth)
  278. if hasSpace && b.DisplayPos%b.LineWidth != b.LineWidth-1 {
  279. fmt.Print(CursorLeft)
  280. }
  281. }
  282. fmt.Print(CursorShow)
  283. }
  284. func (b *Buffer) Remove() {
  285. if b.Buf.Size() > 0 && b.Pos > 0 {
  286. if r, ok := b.Buf.Get(b.Pos - 1); ok {
  287. rLength := runewidth.RuneWidth(r)
  288. hasSpace := b.GetLineSpacing(b.DisplayPos/b.LineWidth - 1)
  289. if b.DisplayPos%b.LineWidth == 0 {
  290. // if the user backspaces over the word boundary, do this magic to clear the line
  291. // and move to the end of the previous line
  292. fmt.Print(CursorBOL + ClearToEOL + CursorUp + CursorBOL + CursorRightN(b.Width))
  293. if b.DisplaySize()%b.LineWidth < (b.DisplaySize()-rLength)%b.LineWidth {
  294. b.LineHasSpace.Remove(b.DisplayPos/b.LineWidth - 1)
  295. }
  296. if hasSpace {
  297. b.DisplayPos -= 1
  298. fmt.Print(CursorLeft)
  299. }
  300. if rLength == 2 {
  301. fmt.Print(CursorLeft + " " + CursorLeftN(2))
  302. } else {
  303. fmt.Print(" " + CursorLeft)
  304. }
  305. } else if (b.DisplayPos-rLength)%b.LineWidth == 0 && hasSpace {
  306. fmt.Print(CursorBOL + ClearToEOL + CursorUp + CursorBOL + CursorRightN(b.Width))
  307. if b.Pos == b.Buf.Size() {
  308. b.LineHasSpace.Remove(b.DisplayPos/b.LineWidth - 1)
  309. }
  310. b.DisplayPos -= 1
  311. } else {
  312. fmt.Print(CursorLeftN(rLength))
  313. for range rLength {
  314. fmt.Print(" ")
  315. }
  316. fmt.Print(CursorLeftN(rLength))
  317. }
  318. var eraseExtraLine bool
  319. if (b.DisplaySize()-1)%b.LineWidth == 0 || (rLength == 2 && ((b.DisplaySize()-2)%b.LineWidth == 0)) || b.DisplaySize()%b.LineWidth == 0 {
  320. eraseExtraLine = true
  321. }
  322. b.Pos -= 1
  323. b.DisplayPos -= rLength
  324. b.Buf.Remove(b.Pos)
  325. if b.Pos < b.Buf.Size() {
  326. b.drawRemaining()
  327. // this erases a line which is left over when backspacing in the middle of a line and there
  328. // are trailing characters which go over the line width boundary
  329. if eraseExtraLine {
  330. remainingLines := (b.DisplaySize() - b.DisplayPos) / b.LineWidth
  331. fmt.Print(CursorDownN(remainingLines+1) + CursorBOL + ClearToEOL)
  332. place := b.DisplayPos % b.LineWidth
  333. fmt.Print(CursorUpN(remainingLines+1) + CursorRightN(place+len(b.Prompt.prompt())))
  334. }
  335. }
  336. }
  337. }
  338. }
  339. func (b *Buffer) Delete() {
  340. if b.Buf.Size() > 0 && b.Pos < b.Buf.Size() {
  341. b.Buf.Remove(b.Pos)
  342. b.drawRemaining()
  343. if b.DisplaySize()%b.LineWidth == 0 {
  344. if b.DisplayPos != b.DisplaySize() {
  345. remainingLines := (b.DisplaySize() - b.DisplayPos) / b.LineWidth
  346. fmt.Print(CursorDownN(remainingLines) + CursorBOL + ClearToEOL)
  347. place := b.DisplayPos % b.LineWidth
  348. fmt.Print(CursorUpN(remainingLines) + CursorRightN(place+len(b.Prompt.prompt())))
  349. }
  350. }
  351. }
  352. }
  353. func (b *Buffer) DeleteBefore() {
  354. if b.Pos > 0 {
  355. for cnt := b.Pos - 1; cnt >= 0; cnt-- {
  356. b.Remove()
  357. }
  358. }
  359. }
  360. func (b *Buffer) DeleteRemaining() {
  361. if b.DisplaySize() > 0 && b.Pos < b.DisplaySize() {
  362. charsToDel := b.Buf.Size() - b.Pos
  363. for range charsToDel {
  364. b.Delete()
  365. }
  366. }
  367. }
  368. func (b *Buffer) DeleteWord() {
  369. if b.Buf.Size() > 0 && b.Pos > 0 {
  370. var foundNonspace bool
  371. for {
  372. v, _ := b.Buf.Get(b.Pos - 1)
  373. if v == ' ' {
  374. if !foundNonspace {
  375. b.Remove()
  376. } else {
  377. break
  378. }
  379. } else {
  380. foundNonspace = true
  381. b.Remove()
  382. }
  383. if b.Pos == 0 {
  384. break
  385. }
  386. }
  387. }
  388. }
  389. func (b *Buffer) ClearScreen() {
  390. fmt.Print(ClearScreen + CursorReset + b.Prompt.prompt())
  391. if b.IsEmpty() {
  392. ph := b.Prompt.placeholder()
  393. fmt.Print(ColorGrey + ph + CursorLeftN(len(ph)) + ColorDefault)
  394. } else {
  395. currPos := b.DisplayPos
  396. currIndex := b.Pos
  397. b.Pos = 0
  398. b.DisplayPos = 0
  399. b.drawRemaining()
  400. fmt.Print(CursorReset + CursorRightN(len(b.Prompt.prompt())))
  401. if currPos > 0 {
  402. targetLine := currPos / b.LineWidth
  403. if targetLine > 0 {
  404. for range targetLine {
  405. fmt.Print(CursorDown)
  406. }
  407. }
  408. remainder := currPos % b.LineWidth
  409. if remainder > 0 {
  410. fmt.Print(CursorRightN(remainder))
  411. }
  412. if currPos%b.LineWidth == 0 {
  413. fmt.Print(CursorBOL + b.Prompt.AltPrompt)
  414. }
  415. }
  416. b.Pos = currIndex
  417. b.DisplayPos = currPos
  418. }
  419. }
  420. func (b *Buffer) IsEmpty() bool {
  421. return b.Buf.Empty()
  422. }
  423. func (b *Buffer) Replace(r []rune) {
  424. b.DisplayPos = 0
  425. b.Pos = 0
  426. lineNums := b.DisplaySize() / b.LineWidth
  427. b.Buf.Clear()
  428. fmt.Print(CursorBOL + ClearToEOL)
  429. for range lineNums {
  430. fmt.Print(CursorUp + CursorBOL + ClearToEOL)
  431. }
  432. fmt.Print(CursorBOL + b.Prompt.prompt())
  433. for _, c := range r {
  434. b.Add(c)
  435. }
  436. }
  437. func (b *Buffer) String() string {
  438. return b.StringN(0)
  439. }
  440. func (b *Buffer) StringN(n int) string {
  441. return b.StringNM(n, 0)
  442. }
  443. func (b *Buffer) StringNM(n, m int) string {
  444. var s string
  445. if m == 0 {
  446. m = b.Buf.Size()
  447. }
  448. for cnt := n; cnt < m; cnt++ {
  449. c, _ := b.Buf.Get(cnt)
  450. s += string(c)
  451. }
  452. return s
  453. }