buffer.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. package editor
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/emirpasic/gods/lists/arraylist"
  6. "golang.org/x/term"
  7. )
  8. type Buffer struct {
  9. PosX int
  10. PosY int
  11. Buf []*arraylist.List
  12. Prompt *Prompt
  13. WordWrap int
  14. ScreenWidth int
  15. ScreenHeight int
  16. }
  17. func NewBuffer(prompt *Prompt) (*Buffer, error) {
  18. width, height, err := term.GetSize(0)
  19. if err != nil {
  20. fmt.Println("Error getting size:", err)
  21. return nil, err
  22. }
  23. b := &Buffer{
  24. PosX: 0,
  25. PosY: 0,
  26. Buf: []*arraylist.List{arraylist.New()},
  27. Prompt: prompt,
  28. ScreenWidth: width,
  29. ScreenHeight: height,
  30. }
  31. return b, nil
  32. }
  33. func (b *Buffer) LineWidth() int {
  34. return b.ScreenWidth - len(b.Prompt.Prompt)
  35. }
  36. func (b *Buffer) findWordAtPos(line string, pos int) string {
  37. return ""
  38. }
  39. func (b *Buffer) addLine(row int) {
  40. if row+1 == len(b.Buf) {
  41. b.Buf = append(b.Buf, arraylist.New())
  42. } else {
  43. b.Buf = append(b.Buf, nil)
  44. copy(b.Buf[row+2:], b.Buf[row+1:])
  45. b.Buf[row+1] = arraylist.New()
  46. }
  47. }
  48. func (b *Buffer) Add(r rune) {
  49. switch r {
  50. case CharCtrlJ, CharEnter:
  51. b.addLine(b.PosY)
  52. // handle Ctrl-J in the middle of a line
  53. var remainingText string
  54. if b.PosX < b.Buf[b.PosY].Size() {
  55. fmt.Print(ClearToEOL)
  56. remainingText = b.StringLine(b.PosX, b.PosY)
  57. for cnt := 0; cnt < len(remainingText); cnt++ {
  58. b.Buf[b.PosY].Remove(b.Buf[b.PosY].Size() - 1)
  59. b.Buf[b.PosY+1].Add(rune(remainingText[cnt]))
  60. }
  61. }
  62. b.PosY++
  63. b.PosX = 0
  64. fmt.Printf("\n... " + ClearToEOL)
  65. b.drawRemaining()
  66. default:
  67. if b.PosX == b.Buf[b.PosY].Size() {
  68. fmt.Printf("%c", r)
  69. b.PosX++
  70. b.Buf[b.PosY].Add(r)
  71. wrap, prefix, offset := b.splitLineInsert(b.PosY, b.PosX)
  72. if wrap {
  73. fmt.Print(CursorHide + cursorLeftN(len(prefix)+1) + ClearToEOL)
  74. fmt.Printf("\n%s... %s%c", ClearToEOL, prefix, r)
  75. b.PosY++
  76. b.PosX = offset
  77. b.ResetCursor()
  78. b.drawRemaining()
  79. fmt.Print(CursorShow)
  80. }
  81. } else {
  82. fmt.Printf("%c", r)
  83. b.Buf[b.PosY].Insert(b.PosX, r)
  84. b.PosX++
  85. _, prefix, offset := b.splitLineInsert(b.PosY, b.PosX)
  86. fmt.Print(CursorHide)
  87. if b.PosX > b.Buf[b.PosY].Size() {
  88. if offset > 0 {
  89. fmt.Print(cursorLeftN(offset))
  90. }
  91. fmt.Print(ClearToEOL + CursorDown + CursorBOL + ClearToEOL)
  92. fmt.Printf("... %s", prefix[:offset])
  93. b.PosY++
  94. b.PosX = offset
  95. b.ResetCursor()
  96. }
  97. b.drawRemaining()
  98. fmt.Print(CursorShow)
  99. }
  100. }
  101. }
  102. func (b *Buffer) ResetCursor() {
  103. fmt.Print(CursorHide + CursorBOL)
  104. fmt.Print(cursorRightN(b.PosX + len(b.Prompt.Prompt)))
  105. fmt.Print(CursorShow)
  106. }
  107. func (b *Buffer) splitLineInsert(posY, posX int) (bool, string, int) {
  108. line := b.StringLine(0, posY)
  109. screenEdge := b.LineWidth() - 5
  110. // if the current line doesn't need to be reflowed, none of the other
  111. // lines will either
  112. if len(line) <= screenEdge {
  113. return false, "", 0
  114. }
  115. // we know we're going to have to insert onto the next line, so
  116. // add another line if there isn't one already
  117. if posY == len(b.Buf)-1 {
  118. b.Buf = append(b.Buf, arraylist.New())
  119. }
  120. // make a truncated version of the current line
  121. currLine := line[:screenEdge]
  122. // figure out where the last space in the line is
  123. idx := strings.LastIndex(currLine, " ")
  124. // deal with strings that don't have spaces in them
  125. if idx == -1 {
  126. idx = len(currLine) - 1
  127. }
  128. // if the next line already has text on it, we need
  129. // to add a space to insert our new word
  130. if b.Buf[posY+1].Size() > 0 {
  131. b.Buf[posY+1].Insert(0, ' ')
  132. }
  133. // calculate the number of characters we need to remove
  134. // from the current line to add to the next one
  135. totalChars := len(line) - idx - 1
  136. for cnt := 0; cnt < totalChars; cnt++ {
  137. b.Buf[posY].Remove(b.Buf[posY].Size() - 1)
  138. b.Buf[posY+1].Insert(0, rune(line[len(line)-1-cnt]))
  139. }
  140. // remove the trailing space
  141. b.Buf[posY].Remove(b.Buf[posY].Size() - 1)
  142. // wrap any further lines
  143. if b.Buf[posY+1].Size() > b.LineWidth()-5 {
  144. b.splitLineInsert(posY+1, 0)
  145. }
  146. return true, currLine[idx+1:], posX - idx - 1
  147. }
  148. func (b *Buffer) drawRemaining() {
  149. remainingText := b.StringFromRow(b.PosY)
  150. remainingText = remainingText[b.PosX:]
  151. fmt.Print(CursorHide + ClearToEOL)
  152. var rowCount int
  153. for _, c := range remainingText {
  154. fmt.Print(string(c))
  155. if c == '\n' {
  156. fmt.Print("... " + ClearToEOL)
  157. rowCount++
  158. }
  159. }
  160. if rowCount > 0 {
  161. fmt.Print(cursorUpN(rowCount))
  162. }
  163. b.ResetCursor()
  164. }
  165. func (b *Buffer) findWordBeginning(posX int) int {
  166. for {
  167. if posX < 0 {
  168. return -1
  169. }
  170. r, ok := b.Buf[b.PosY].Get(posX)
  171. if !ok {
  172. return -1
  173. } else if r.(rune) == ' ' {
  174. return posX
  175. }
  176. posX--
  177. }
  178. }
  179. func (b *Buffer) Delete() {
  180. if b.PosX < b.Buf[b.PosY].Size()-1 {
  181. b.Buf[b.PosY].Remove(b.PosX)
  182. b.drawRemaining()
  183. } else {
  184. b.joinLines()
  185. }
  186. }
  187. func (b *Buffer) joinLines() {
  188. lineLen := b.Buf[b.PosY].Size()
  189. for cnt := 0; cnt < lineLen; cnt++ {
  190. r, _ := b.Buf[b.PosY].Get(0)
  191. b.Buf[b.PosY].Remove(0)
  192. b.Buf[b.PosY-1].Add(r)
  193. }
  194. }
  195. func (b *Buffer) Remove() {
  196. if b.PosX > 0 {
  197. fmt.Print(CursorLeft + " " + CursorLeft)
  198. b.PosX--
  199. b.Buf[b.PosY].Remove(b.PosX)
  200. if b.PosX < b.Buf[b.PosY].Size() {
  201. fmt.Print(ClearToEOL)
  202. b.drawRemaining()
  203. }
  204. } else if b.PosX == 0 && b.PosY > 0 {
  205. b.joinLines()
  206. lastPos := b.Buf[b.PosY-1].Size()
  207. var cnt int
  208. b.PosX = lastPos
  209. b.PosY--
  210. fmt.Print(CursorHide)
  211. for {
  212. if b.PosX+cnt > b.LineWidth()-5 {
  213. // the concatenated line won't fit, so find the beginning of the word
  214. // and copy the rest of the string from there
  215. idx := b.findWordBeginning(b.PosX)
  216. lineLen := b.Buf[b.PosY].Size()
  217. for offset := idx + 1; offset < lineLen; offset++ {
  218. r, _ := b.Buf[b.PosY].Get(idx + 1)
  219. b.Buf[b.PosY].Remove(idx + 1)
  220. b.Buf[b.PosY+1].Add(r)
  221. }
  222. // remove the trailing space
  223. b.Buf[b.PosY].Remove(idx)
  224. fmt.Print(CursorUp + ClearToEOL)
  225. b.PosX = 0
  226. b.drawRemaining()
  227. fmt.Print(CursorDown)
  228. if idx > 0 {
  229. if lastPos-idx-1 > 0 {
  230. b.PosX = lastPos - idx - 1
  231. b.ResetCursor()
  232. }
  233. }
  234. b.PosY++
  235. break
  236. }
  237. r, ok := b.Buf[b.PosY].Get(b.PosX + cnt)
  238. if !ok {
  239. // found the end of the string
  240. fmt.Print(CursorUp + cursorRightN(b.PosX) + ClearToEOL)
  241. b.drawRemaining()
  242. break
  243. }
  244. if r == ' ' {
  245. // found the end of the word
  246. lineLen := b.Buf[b.PosY].Size()
  247. for offset := b.PosX + cnt + 1; offset < lineLen; offset++ {
  248. r, _ := b.Buf[b.PosY].Get(b.PosX + cnt + 1)
  249. b.Buf[b.PosY].Remove(b.PosX + cnt + 1)
  250. b.Buf[b.PosY+1].Add(r)
  251. }
  252. fmt.Print(CursorUp + cursorRightN(b.PosX) + ClearToEOL)
  253. b.drawRemaining()
  254. break
  255. }
  256. cnt++
  257. }
  258. fmt.Print(CursorShow)
  259. }
  260. }
  261. func (b *Buffer) RemoveBefore() {
  262. for {
  263. if b.PosX == 0 && b.PosY == 0 {
  264. break
  265. }
  266. b.Remove()
  267. }
  268. }
  269. func (b *Buffer) RemoveWordBefore() {
  270. if b.PosX > 0 || b.PosY > 0 {
  271. var foundNonspace bool
  272. for {
  273. xPos := b.PosX
  274. yPos := b.PosY
  275. v, _ := b.Buf[yPos].Get(xPos - 1)
  276. if v == ' ' {
  277. if !foundNonspace {
  278. b.Remove()
  279. } else {
  280. break
  281. }
  282. } else {
  283. foundNonspace = true
  284. b.Remove()
  285. }
  286. if xPos == 0 && yPos == 0 {
  287. break
  288. }
  289. }
  290. }
  291. }
  292. func (b *Buffer) StringLine(x, y int) string {
  293. if y >= len(b.Buf) {
  294. return ""
  295. }
  296. var output string
  297. for cnt := x; cnt < b.Buf[y].Size(); cnt++ {
  298. r, _ := b.Buf[y].Get(cnt)
  299. output += string(r.(rune))
  300. }
  301. return output
  302. }
  303. func (b *Buffer) String() string {
  304. return b.StringFromRow(0)
  305. }
  306. func (b *Buffer) StringFromRow(n int) string {
  307. var output []string
  308. for _, row := range b.Buf[n:] {
  309. var currLine string
  310. for cnt := 0; cnt < row.Size(); cnt++ {
  311. r, _ := row.Get(cnt)
  312. currLine += string(r.(rune))
  313. }
  314. currLine = strings.TrimRight(currLine, " ")
  315. output = append(output, currLine)
  316. }
  317. return strings.Join(output, "\n")
  318. }
  319. func (b *Buffer) cursorUp() {
  320. fmt.Print(CursorUp)
  321. b.ResetCursor()
  322. }
  323. func (b *Buffer) cursorDown() {
  324. fmt.Print(CursorDown)
  325. b.ResetCursor()
  326. }
  327. func (b *Buffer) MoveUp() {
  328. if b.PosY > 0 {
  329. b.PosY--
  330. if b.Buf[b.PosY].Size() < b.PosX {
  331. b.PosX = b.Buf[b.PosY].Size()
  332. }
  333. b.cursorUp()
  334. } else {
  335. fmt.Print("\a")
  336. }
  337. }
  338. func (b *Buffer) MoveDown() {
  339. if b.PosY < len(b.Buf)-1 {
  340. b.PosY++
  341. if b.Buf[b.PosY].Size() < b.PosX {
  342. b.PosX = b.Buf[b.PosY].Size()
  343. }
  344. b.cursorDown()
  345. } else {
  346. fmt.Print("\a")
  347. }
  348. }
  349. func (b *Buffer) MoveLeft() {
  350. if b.PosX > 0 {
  351. b.PosX--
  352. fmt.Print(CursorLeft)
  353. } else if b.PosY > 0 {
  354. b.PosX = b.Buf[b.PosY-1].Size()
  355. b.PosY--
  356. b.cursorUp()
  357. } else if b.PosX == 0 && b.PosY == 0 {
  358. fmt.Print("\a")
  359. }
  360. }
  361. func (b *Buffer) MoveRight() {
  362. if b.PosX < b.Buf[b.PosY].Size() {
  363. b.PosX++
  364. fmt.Print(CursorRight)
  365. } else if b.PosY < len(b.Buf)-1 {
  366. b.PosY++
  367. b.PosX = 0
  368. b.cursorDown()
  369. } else {
  370. fmt.Print("\a")
  371. }
  372. }
  373. func (b *Buffer) MoveToBOL() {
  374. if b.PosX > 0 {
  375. b.PosX = 0
  376. b.ResetCursor()
  377. }
  378. }
  379. func (b *Buffer) MoveToEOL() {
  380. if b.PosX < b.Buf[b.PosY].Size() {
  381. b.PosX = b.Buf[b.PosY].Size()
  382. b.ResetCursor()
  383. }
  384. }
  385. func (b *Buffer) MoveToEnd() {
  386. fmt.Print(CursorHide)
  387. yDiff := len(b.Buf)-1 - b.PosY
  388. if yDiff > 0 {
  389. fmt.Print(cursorDownN(yDiff))
  390. }
  391. b.PosY = len(b.Buf)-1
  392. b.MoveToEOL()
  393. fmt.Print(CursorShow)
  394. }
  395. func cursorLeftN(n int) string {
  396. return fmt.Sprintf(CursorLeftN, n)
  397. }
  398. func cursorRightN(n int) string {
  399. return fmt.Sprintf(CursorRightN, n)
  400. }
  401. func cursorUpN(n int) string {
  402. return fmt.Sprintf(CursorUpN, n)
  403. }
  404. func cursorDownN(n int) string {
  405. return fmt.Sprintf(CursorDownN, n)
  406. }
  407. func (b *Buffer) ClearScreen() {
  408. fmt.Printf(CursorHide + ClearScreen + CursorReset + b.Prompt.Prompt)
  409. if b.IsEmpty() {
  410. ph := b.Prompt.Placeholder
  411. fmt.Printf(ColorGrey + ph + cursorLeftN(len(ph)) + ColorDefault)
  412. } else {
  413. currPosX := b.PosX
  414. currPosY := b.PosY
  415. b.PosX = 0
  416. b.PosY = 0
  417. b.drawRemaining()
  418. b.PosX = currPosX
  419. b.PosY = currPosY
  420. fmt.Print(CursorReset + cursorRightN(len(b.Prompt.Prompt)))
  421. if b.PosY > 0 {
  422. fmt.Print(cursorDownN(b.PosY))
  423. }
  424. if b.PosX > 0 {
  425. fmt.Print(cursorRightN(b.PosX))
  426. }
  427. }
  428. fmt.Print(CursorShow)
  429. }
  430. func (b *Buffer) IsEmpty() bool {
  431. return len(b.Buf) == 1 && b.Buf[0].Empty()
  432. }