buffer.go 12.3 KB
Newer Older
Patrick Devine's avatar
Patrick Devine committed
1
2
3
4
package readline

import (
	"fmt"
5
	"os"
Patrick Devine's avatar
Patrick Devine committed
6
7

	"github.com/emirpasic/gods/lists/arraylist"
8
	"github.com/mattn/go-runewidth"
Patrick Devine's avatar
Patrick Devine committed
9
10
11
12
	"golang.org/x/term"
)

type Buffer struct {
13
14
15
	DisplayPos int
	Pos        int
	Buf        *arraylist.List
Michael Yang's avatar
lint  
Michael Yang committed
16
	// LineHasSpace is an arraylist of bools to keep track of whether a line has a space at the end
17
18
19
20
21
	LineHasSpace *arraylist.List
	Prompt       *Prompt
	LineWidth    int
	Width        int
	Height       int
Patrick Devine's avatar
Patrick Devine committed
22
23
24
}

func NewBuffer(prompt *Prompt) (*Buffer, error) {
25
	fd := int(os.Stdout.Fd())
Michael Yang's avatar
Michael Yang committed
26
27
28
	width, height := 80, 24
	if termWidth, termHeight, err := term.GetSize(fd); err == nil {
		width, height = termWidth, termHeight
Patrick Devine's avatar
Patrick Devine committed
29
30
	}

Michael Yang's avatar
Michael Yang committed
31
	lwidth := width - len(prompt.prompt())
Patrick Devine's avatar
Patrick Devine committed
32
33

	b := &Buffer{
34
35
36
37
38
39
40
41
		DisplayPos:   0,
		Pos:          0,
		Buf:          arraylist.New(),
		LineHasSpace: arraylist.New(),
		Prompt:       prompt,
		Width:        width,
		Height:       height,
		LineWidth:    lwidth,
Patrick Devine's avatar
Patrick Devine committed
42
43
44
45
46
	}

	return b, nil
}

47
48
49
50
51
52
53
54
55
56
func (b *Buffer) GetLineSpacing(line int) bool {
	hasSpace, _ := b.LineHasSpace.Get(line)

	if hasSpace == nil {
		return false
	}

	return hasSpace.(bool)
}

Patrick Devine's avatar
Patrick Devine committed
57
58
func (b *Buffer) MoveLeft() {
	if b.Pos > 0 {
Michael Yang's avatar
lint  
Michael Yang committed
59
		// asserts that we retrieve a rune
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
		if e, ok := b.Buf.Get(b.Pos - 1); ok {
			if r, ok := e.(rune); ok {
				rLength := runewidth.RuneWidth(r)

				if b.DisplayPos%b.LineWidth == 0 {
					fmt.Printf(CursorUp + CursorBOL + cursorRightN(b.Width))
					if rLength == 2 {
						fmt.Print(CursorLeft)
					}

					line := b.DisplayPos/b.LineWidth - 1
					hasSpace := b.GetLineSpacing(line)
					if hasSpace {
						b.DisplayPos -= 1
						fmt.Print(CursorLeft)
					}
				} else {
					fmt.Print(cursorLeftN(rLength))
				}

				b.Pos -= 1
				b.DisplayPos -= rLength
			}
Patrick Devine's avatar
Patrick Devine committed
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
		}
	}
}

func (b *Buffer) MoveLeftWord() {
	if b.Pos > 0 {
		var foundNonspace bool
		for {
			v, _ := b.Buf.Get(b.Pos - 1)
			if v == ' ' {
				if foundNonspace {
					break
				}
			} else {
				foundNonspace = true
			}
			b.MoveLeft()

			if b.Pos == 0 {
				break
			}
		}
	}
}

func (b *Buffer) MoveRight() {
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
	if b.Pos < b.Buf.Size() {
		if e, ok := b.Buf.Get(b.Pos); ok {
			if r, ok := e.(rune); ok {
				rLength := runewidth.RuneWidth(r)
				b.Pos += 1
				hasSpace := b.GetLineSpacing(b.DisplayPos / b.LineWidth)
				b.DisplayPos += rLength

				if b.DisplayPos%b.LineWidth == 0 {
					fmt.Printf(CursorDown + CursorBOL + cursorRightN(len(b.Prompt.prompt())))
				} else if (b.DisplayPos-rLength)%b.LineWidth == b.LineWidth-1 && hasSpace {
					fmt.Printf(CursorDown + CursorBOL + cursorRightN(len(b.Prompt.prompt())+rLength))
					b.DisplayPos += 1
				} else if b.LineHasSpace.Size() > 0 && b.DisplayPos%b.LineWidth == b.LineWidth-1 && hasSpace {
					fmt.Printf(CursorDown + CursorBOL + cursorRightN(len(b.Prompt.prompt())))
					b.DisplayPos += 1
				} else {
					fmt.Print(cursorRightN(rLength))
				}
			}
Patrick Devine's avatar
Patrick Devine committed
129
130
131
132
133
		}
	}
}

func (b *Buffer) MoveRightWord() {
134
	if b.Pos < b.Buf.Size() {
Patrick Devine's avatar
Patrick Devine committed
135
136
137
138
139
140
141
		for {
			b.MoveRight()
			v, _ := b.Buf.Get(b.Pos)
			if v == ' ' {
				break
			}

142
			if b.Pos == b.Buf.Size() {
Patrick Devine's avatar
Patrick Devine committed
143
144
145
146
147
148
149
150
				break
			}
		}
	}
}

func (b *Buffer) MoveToStart() {
	if b.Pos > 0 {
151
		currLine := b.DisplayPos / b.LineWidth
Patrick Devine's avatar
Patrick Devine committed
152
		if currLine > 0 {
Michael Yang's avatar
lint  
Michael Yang committed
153
			for range currLine {
154
				fmt.Print(CursorUp)
Patrick Devine's avatar
Patrick Devine committed
155
156
			}
		}
Michael Yang's avatar
Michael Yang committed
157
		fmt.Printf(CursorBOL + cursorRightN(len(b.Prompt.prompt())))
Patrick Devine's avatar
Patrick Devine committed
158
		b.Pos = 0
159
		b.DisplayPos = 0
Patrick Devine's avatar
Patrick Devine committed
160
161
162
163
	}
}

func (b *Buffer) MoveToEnd() {
164
165
166
	if b.Pos < b.Buf.Size() {
		currLine := b.DisplayPos / b.LineWidth
		totalLines := b.DisplaySize() / b.LineWidth
Patrick Devine's avatar
Patrick Devine committed
167
		if currLine < totalLines {
Michael Yang's avatar
lint  
Michael Yang committed
168
			for range totalLines - currLine {
169
				fmt.Print(CursorDown)
Patrick Devine's avatar
Patrick Devine committed
170
			}
171
			remainder := b.DisplaySize() % b.LineWidth
Michael Yang's avatar
Michael Yang committed
172
			fmt.Printf(CursorBOL + cursorRightN(len(b.Prompt.prompt())+remainder))
Patrick Devine's avatar
Patrick Devine committed
173
		} else {
174
			fmt.Print(cursorRightN(b.DisplaySize() - b.DisplayPos))
Patrick Devine's avatar
Patrick Devine committed
175
176
		}

177
178
		b.Pos = b.Buf.Size()
		b.DisplayPos = b.DisplaySize()
Patrick Devine's avatar
Patrick Devine committed
179
180
181
	}
}

182
183
func (b *Buffer) DisplaySize() int {
	sum := 0
Michael Yang's avatar
Michael Yang committed
184
	for i := range b.Buf.Size() {
185
186
187
188
189
190
191
192
		if e, ok := b.Buf.Get(i); ok {
			if r, ok := e.(rune); ok {
				sum += runewidth.RuneWidth(r)
			}
		}
	}

	return sum
Patrick Devine's avatar
Patrick Devine committed
193
194
195
196
}

func (b *Buffer) Add(r rune) {
	if b.Pos == b.Buf.Size() {
197
198
199
200
201
202
203
204
205
206
207
208
209
		b.AddChar(r, false)
	} else {
		b.AddChar(r, true)
	}
}

func (b *Buffer) AddChar(r rune, insert bool) {
	rLength := runewidth.RuneWidth(r)
	b.DisplayPos += rLength

	if b.Pos > 0 {
		if b.DisplayPos%b.LineWidth == 0 {
			fmt.Printf("%c", r)
Patrick Devine's avatar
Patrick Devine committed
210
			fmt.Printf("\n%s", b.Prompt.AltPrompt)
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233

			if insert {
				b.LineHasSpace.Set(b.DisplayPos/b.LineWidth-1, false)
			} else {
				b.LineHasSpace.Add(false)
			}

			// this case occurs when a double-width rune crosses the line boundary
		} else if b.DisplayPos%b.LineWidth < (b.DisplayPos-rLength)%b.LineWidth {
			if insert {
				fmt.Print(ClearToEOL)
			}
			fmt.Printf("\n%s", b.Prompt.AltPrompt)
			b.DisplayPos += 1
			fmt.Printf("%c", r)

			if insert {
				b.LineHasSpace.Set(b.DisplayPos/b.LineWidth-1, true)
			} else {
				b.LineHasSpace.Add(true)
			}
		} else {
			fmt.Printf("%c", r)
Patrick Devine's avatar
Patrick Devine committed
234
235
236
		}
	} else {
		fmt.Printf("%c", r)
237
238
239
	}

	if insert {
Patrick Devine's avatar
Patrick Devine committed
240
		b.Buf.Insert(b.Pos, r)
241
242
243
244
245
246
247
	} else {
		b.Buf.Add(r)
	}

	b.Pos += 1

	if insert {
Patrick Devine's avatar
Patrick Devine committed
248
249
250
251
		b.drawRemaining()
	}
}

252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
func (b *Buffer) countRemainingLineWidth(place int) int {
	var sum int
	counter := -1
	var prevLen int

	for place <= b.LineWidth {
		counter += 1
		sum += prevLen
		if e, ok := b.Buf.Get(b.Pos + counter); ok {
			if r, ok := e.(rune); ok {
				place += runewidth.RuneWidth(r)
				prevLen = len(string(r))
			}
		} else {
			break
		}
	}

	return sum
}

Patrick Devine's avatar
Patrick Devine committed
273
274
275
276
func (b *Buffer) drawRemaining() {
	var place int
	remainingText := b.StringN(b.Pos)
	if b.Pos > 0 {
277
		place = b.DisplayPos % b.LineWidth
Patrick Devine's avatar
Patrick Devine committed
278
	}
279
	fmt.Print(CursorHide)
Patrick Devine's avatar
Patrick Devine committed
280
281

	// render the rest of the current line
282
283
284
285
286
287
	currLineLength := b.countRemainingLineWidth(place)

	currLine := remainingText[:min(currLineLength, len(remainingText))]
	currLineSpace := runewidth.StringWidth(currLine)
	remLength := runewidth.StringWidth(remainingText)

Patrick Devine's avatar
Patrick Devine committed
288
289
	if len(currLine) > 0 {
		fmt.Printf(ClearToEOL + currLine)
290
		fmt.Print(cursorLeftN(currLineSpace))
Patrick Devine's avatar
Patrick Devine committed
291
	} else {
292
		fmt.Print(ClearToEOL)
Patrick Devine's avatar
Patrick Devine committed
293
294
	}

295
296
297
298
299
300
301
302
303
304
305
306
307
308
	if currLineSpace != b.LineWidth-place && currLineSpace != remLength {
		b.LineHasSpace.Set(b.DisplayPos/b.LineWidth, true)
	} else if currLineSpace != b.LineWidth-place {
		b.LineHasSpace.Remove(b.DisplayPos / b.LineWidth)
	} else {
		b.LineHasSpace.Set(b.DisplayPos/b.LineWidth, false)
	}

	if (b.DisplayPos+currLineSpace)%b.LineWidth == 0 && currLine == remainingText {
		fmt.Print(cursorRightN(currLineSpace))
		fmt.Printf("\n%s", b.Prompt.AltPrompt)
		fmt.Printf(CursorUp + CursorBOL + cursorRightN(b.Width-currLineSpace))
	}

Patrick Devine's avatar
Patrick Devine committed
309
	// render the other lines
310
311
	if remLength > currLineSpace {
		remaining := (remainingText[len(currLine):])
Patrick Devine's avatar
Patrick Devine committed
312
		var totalLines int
313
314
315
316
317
		var displayLength int
		var lineLength int = currLineSpace

		for _, c := range remaining {
			if displayLength == 0 || (displayLength+runewidth.RuneWidth(c))%b.LineWidth < displayLength%b.LineWidth {
Patrick Devine's avatar
Patrick Devine committed
318
319
				fmt.Printf("\n%s", b.Prompt.AltPrompt)
				totalLines += 1
320
321
322
323
324
325
326
327
328
329

				if displayLength != 0 {
					if lineLength == b.LineWidth {
						b.LineHasSpace.Set(b.DisplayPos/b.LineWidth+totalLines-1, false)
					} else {
						b.LineHasSpace.Set(b.DisplayPos/b.LineWidth+totalLines-1, true)
					}
				}

				lineLength = 0
Patrick Devine's avatar
Patrick Devine committed
330
			}
331
332
333

			displayLength += runewidth.RuneWidth(c)
			lineLength += runewidth.RuneWidth(c)
Patrick Devine's avatar
Patrick Devine committed
334
335
			fmt.Printf("%c", c)
		}
336
337
		fmt.Print(ClearToEOL)
		fmt.Print(cursorUpN(totalLines))
338
339
340
341
342
343
344
		fmt.Printf(CursorBOL + cursorRightN(b.Width-currLineSpace))

		hasSpace := b.GetLineSpacing(b.DisplayPos / b.LineWidth)

		if hasSpace && b.DisplayPos%b.LineWidth != b.LineWidth-1 {
			fmt.Print(CursorLeft)
		}
Patrick Devine's avatar
Patrick Devine committed
345
346
	}

347
	fmt.Print(CursorShow)
Patrick Devine's avatar
Patrick Devine committed
348
349
350
351
}

func (b *Buffer) Remove() {
	if b.Buf.Size() > 0 && b.Pos > 0 {
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
		if e, ok := b.Buf.Get(b.Pos - 1); ok {
			if r, ok := e.(rune); ok {
				rLength := runewidth.RuneWidth(r)
				hasSpace := b.GetLineSpacing(b.DisplayPos/b.LineWidth - 1)

				if b.DisplayPos%b.LineWidth == 0 {
					// if the user backspaces over the word boundary, do this magic to clear the line
					// and move to the end of the previous line
					fmt.Printf(CursorBOL + ClearToEOL)
					fmt.Printf(CursorUp + CursorBOL + cursorRightN(b.Width))

					if b.DisplaySize()%b.LineWidth < (b.DisplaySize()-rLength)%b.LineWidth {
						b.LineHasSpace.Remove(b.DisplayPos/b.LineWidth - 1)
					}

					if hasSpace {
						b.DisplayPos -= 1
						fmt.Print(CursorLeft)
					}

					if rLength == 2 {
						fmt.Print(CursorLeft + "  " + cursorLeftN(2))
					} else {
						fmt.Print(" " + CursorLeft)
					}
				} else if (b.DisplayPos-rLength)%b.LineWidth == 0 && hasSpace {
					fmt.Printf(CursorBOL + ClearToEOL)
					fmt.Printf(CursorUp + CursorBOL + cursorRightN(b.Width))

					if b.Pos == b.Buf.Size() {
						b.LineHasSpace.Remove(b.DisplayPos/b.LineWidth - 1)
					}
					b.DisplayPos -= 1
				} else {
					fmt.Print(cursorLeftN(rLength))
Michael Yang's avatar
Michael Yang committed
387
					for range rLength {
388
389
390
391
392
393
394
395
396
						fmt.Print(" ")
					}
					fmt.Print(cursorLeftN(rLength))
				}

				var eraseExtraLine bool
				if (b.DisplaySize()-1)%b.LineWidth == 0 || (rLength == 2 && ((b.DisplaySize()-2)%b.LineWidth == 0)) || b.DisplaySize()%b.LineWidth == 0 {
					eraseExtraLine = true
				}
Patrick Devine's avatar
Patrick Devine committed
397

398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
				b.Pos -= 1
				b.DisplayPos -= rLength
				b.Buf.Remove(b.Pos)

				if b.Pos < b.Buf.Size() {
					b.drawRemaining()
					// this erases a line which is left over when backspacing in the middle of a line and there
					// are trailing characters which go over the line width boundary
					if eraseExtraLine {
						remainingLines := (b.DisplaySize() - b.DisplayPos) / b.LineWidth
						fmt.Printf(cursorDownN(remainingLines+1) + CursorBOL + ClearToEOL)
						place := b.DisplayPos % b.LineWidth
						fmt.Printf(cursorUpN(remainingLines+1) + cursorRightN(place+len(b.Prompt.prompt())))
					}
				}
Patrick Devine's avatar
Patrick Devine committed
413
414
415
416
417
418
			}
		}
	}
}

func (b *Buffer) Delete() {
419
	if b.Buf.Size() > 0 && b.Pos < b.Buf.Size() {
Patrick Devine's avatar
Patrick Devine committed
420
421
		b.Buf.Remove(b.Pos)
		b.drawRemaining()
422
423
424
		if b.DisplaySize()%b.LineWidth == 0 {
			if b.DisplayPos != b.DisplaySize() {
				remainingLines := (b.DisplaySize() - b.DisplayPos) / b.LineWidth
Patrick Devine's avatar
Patrick Devine committed
425
				fmt.Printf(cursorDownN(remainingLines) + CursorBOL + ClearToEOL)
426
				place := b.DisplayPos % b.LineWidth
Michael Yang's avatar
Michael Yang committed
427
				fmt.Printf(cursorUpN(remainingLines) + cursorRightN(place+len(b.Prompt.prompt())))
Patrick Devine's avatar
Patrick Devine committed
428
429
430
431
432
433
434
435
436
437
438
439
440
441
			}
		}
	}
}

func (b *Buffer) DeleteBefore() {
	if b.Pos > 0 {
		for cnt := b.Pos - 1; cnt >= 0; cnt-- {
			b.Remove()
		}
	}
}

func (b *Buffer) DeleteRemaining() {
442
443
	if b.DisplaySize() > 0 && b.Pos < b.DisplaySize() {
		charsToDel := b.Buf.Size() - b.Pos
Michael Yang's avatar
lint  
Michael Yang committed
444
		for range charsToDel {
Patrick Devine's avatar
Patrick Devine committed
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
			b.Delete()
		}
	}
}

func (b *Buffer) DeleteWord() {
	if b.Buf.Size() > 0 && b.Pos > 0 {
		var foundNonspace bool
		for {
			v, _ := b.Buf.Get(b.Pos - 1)
			if v == ' ' {
				if !foundNonspace {
					b.Remove()
				} else {
					break
				}
			} else {
				foundNonspace = true
				b.Remove()
			}

			if b.Pos == 0 {
				break
			}
		}
	}
}

func (b *Buffer) ClearScreen() {
Michael Yang's avatar
Michael Yang committed
474
	fmt.Printf(ClearScreen + CursorReset + b.Prompt.prompt())
Patrick Devine's avatar
Patrick Devine committed
475
	if b.IsEmpty() {
Michael Yang's avatar
Michael Yang committed
476
		ph := b.Prompt.placeholder()
Patrick Devine's avatar
Patrick Devine committed
477
478
		fmt.Printf(ColorGrey + ph + cursorLeftN(len(ph)) + ColorDefault)
	} else {
479
480
		currPos := b.DisplayPos
		currIndex := b.Pos
Patrick Devine's avatar
Patrick Devine committed
481
		b.Pos = 0
482
		b.DisplayPos = 0
Patrick Devine's avatar
Patrick Devine committed
483
		b.drawRemaining()
Michael Yang's avatar
Michael Yang committed
484
		fmt.Printf(CursorReset + cursorRightN(len(b.Prompt.prompt())))
Patrick Devine's avatar
Patrick Devine committed
485
486
487
		if currPos > 0 {
			targetLine := currPos / b.LineWidth
			if targetLine > 0 {
Michael Yang's avatar
lint  
Michael Yang committed
488
				for range targetLine {
489
					fmt.Print(CursorDown)
Patrick Devine's avatar
Patrick Devine committed
490
491
492
493
				}
			}
			remainder := currPos % b.LineWidth
			if remainder > 0 {
494
				fmt.Print(cursorRightN(remainder))
Patrick Devine's avatar
Patrick Devine committed
495
496
497
498
499
			}
			if currPos%b.LineWidth == 0 {
				fmt.Printf(CursorBOL + b.Prompt.AltPrompt)
			}
		}
500
501
		b.Pos = currIndex
		b.DisplayPos = currPos
Patrick Devine's avatar
Patrick Devine committed
502
503
504
505
506
507
508
509
	}
}

func (b *Buffer) IsEmpty() bool {
	return b.Buf.Empty()
}

func (b *Buffer) Replace(r []rune) {
510
	b.DisplayPos = 0
Patrick Devine's avatar
Patrick Devine committed
511
	b.Pos = 0
512
513
	lineNums := b.DisplaySize() / b.LineWidth

Patrick Devine's avatar
Patrick Devine committed
514
	b.Buf.Clear()
515
516
517

	fmt.Printf(CursorBOL + ClearToEOL)

Michael Yang's avatar
Michael Yang committed
518
	for range lineNums {
519
520
521
522
523
		fmt.Print(CursorUp + CursorBOL + ClearToEOL)
	}

	fmt.Printf(CursorBOL + b.Prompt.prompt())

Patrick Devine's avatar
Patrick Devine committed
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
	for _, c := range r {
		b.Add(c)
	}
}

func (b *Buffer) String() string {
	return b.StringN(0)
}

func (b *Buffer) StringN(n int) string {
	return b.StringNM(n, 0)
}

func (b *Buffer) StringNM(n, m int) string {
	var s string
	if m == 0 {
540
		m = b.Buf.Size()
Patrick Devine's avatar
Patrick Devine committed
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
	}
	for cnt := n; cnt < m; cnt++ {
		c, _ := b.Buf.Get(cnt)
		s += string(c.(rune))
	}
	return s
}

func cursorLeftN(n int) string {
	return fmt.Sprintf(CursorLeftN, n)
}

func cursorRightN(n int) string {
	return fmt.Sprintf(CursorRightN, n)
}

func cursorUpN(n int) string {
	return fmt.Sprintf(CursorUpN, n)
}

func cursorDownN(n int) string {
	return fmt.Sprintf(CursorDownN, n)
}