buffer.go 11 KB

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