buffer.go 11.9 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
		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 {
Michael Yang's avatar
lint  
Michael Yang committed
65
					fmt.Print(CursorUp + CursorBOL + CursorRightN(b.Width))
66
67
68
69
70
71
72
73
74
75
76
					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 {
Michael Yang's avatar
lint  
Michael Yang committed
77
					fmt.Print(CursorLeftN(rLength))
78
79
80
81
82
				}

				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
	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 {
Michael Yang's avatar
lint  
Michael Yang committed
118
					fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt())))
119
				} else if (b.DisplayPos-rLength)%b.LineWidth == b.LineWidth-1 && hasSpace {
Michael Yang's avatar
lint  
Michael Yang committed
120
					fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt())+rLength))
121
122
					b.DisplayPos += 1
				} else if b.LineHasSpace.Size() > 0 && b.DisplayPos%b.LineWidth == b.LineWidth-1 && hasSpace {
Michael Yang's avatar
lint  
Michael Yang committed
123
					fmt.Print(CursorDown + CursorBOL + CursorRightN(len(b.Prompt.prompt())))
124
125
					b.DisplayPos += 1
				} else {
Michael Yang's avatar
lint  
Michael Yang committed
126
					fmt.Print(CursorRightN(rLength))
127
128
				}
			}
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
lint  
Michael Yang committed
157
		fmt.Print(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
lint  
Michael Yang committed
172
			fmt.Print(CursorBOL + CursorRightN(len(b.Prompt.prompt())+remainder))
Patrick Devine's avatar
Patrick Devine committed
173
		} else {
Michael Yang's avatar
lint  
Michael Yang committed
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
	if len(currLine) > 0 {
Michael Yang's avatar
lint  
Michael Yang committed
289
		fmt.Print(ClearToEOL + currLine + CursorLeftN(currLineSpace))
Patrick Devine's avatar
Patrick Devine committed
290
	} else {
291
		fmt.Print(ClearToEOL)
Patrick Devine's avatar
Patrick Devine committed
292
293
	}

294
295
296
297
298
299
300
301
302
	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 {
Michael Yang's avatar
lint  
Michael Yang committed
303
		fmt.Print(CursorRightN(currLineSpace))
304
		fmt.Printf("\n%s", b.Prompt.AltPrompt)
Michael Yang's avatar
lint  
Michael Yang committed
305
		fmt.Print(CursorUp + CursorBOL + CursorRightN(b.Width-currLineSpace))
306
307
	}

Patrick Devine's avatar
Patrick Devine committed
308
	// render the other lines
309
310
	if remLength > currLineSpace {
		remaining := (remainingText[len(currLine):])
Patrick Devine's avatar
Patrick Devine committed
311
		var totalLines int
312
313
314
315
316
		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
317
318
				fmt.Printf("\n%s", b.Prompt.AltPrompt)
				totalLines += 1
319
320
321
322
323
324
325
326
327
328

				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
329
			}
330
331
332

			displayLength += runewidth.RuneWidth(c)
			lineLength += runewidth.RuneWidth(c)
Patrick Devine's avatar
Patrick Devine committed
333
334
			fmt.Printf("%c", c)
		}
Michael Yang's avatar
lint  
Michael Yang committed
335
		fmt.Print(ClearToEOL + CursorUpN(totalLines) + CursorBOL + CursorRightN(b.Width-currLineSpace))
336
337
338
339
340
341

		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
342
343
	}

344
	fmt.Print(CursorShow)
Patrick Devine's avatar
Patrick Devine committed
345
346
347
348
}

func (b *Buffer) Remove() {
	if b.Buf.Size() > 0 && b.Pos > 0 {
349
350
351
352
353
354
355
356
		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
Michael Yang's avatar
lint  
Michael Yang committed
357
					fmt.Print(CursorBOL + ClearToEOL + CursorUp + CursorBOL + CursorRightN(b.Width))
358
359
360
361
362
363
364
365
366
367
368

					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 {
Michael Yang's avatar
lint  
Michael Yang committed
369
						fmt.Print(CursorLeft + "  " + CursorLeftN(2))
370
371
372
373
					} else {
						fmt.Print(" " + CursorLeft)
					}
				} else if (b.DisplayPos-rLength)%b.LineWidth == 0 && hasSpace {
Michael Yang's avatar
lint  
Michael Yang committed
374
					fmt.Print(CursorBOL + ClearToEOL + CursorUp + CursorBOL + CursorRightN(b.Width))
375
376
377
378
379
380

					if b.Pos == b.Buf.Size() {
						b.LineHasSpace.Remove(b.DisplayPos/b.LineWidth - 1)
					}
					b.DisplayPos -= 1
				} else {
Michael Yang's avatar
lint  
Michael Yang committed
381
					fmt.Print(CursorLeftN(rLength))
Michael Yang's avatar
Michael Yang committed
382
					for range rLength {
383
384
						fmt.Print(" ")
					}
Michael Yang's avatar
lint  
Michael Yang committed
385
					fmt.Print(CursorLeftN(rLength))
386
387
388
389
390
391
				}

				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
392

393
394
395
396
397
398
399
400
401
402
				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
Michael Yang's avatar
lint  
Michael Yang committed
403
						fmt.Print(CursorDownN(remainingLines+1) + CursorBOL + ClearToEOL)
404
						place := b.DisplayPos % b.LineWidth
Michael Yang's avatar
lint  
Michael Yang committed
405
						fmt.Print(CursorUpN(remainingLines+1) + CursorRightN(place+len(b.Prompt.prompt())))
406
407
					}
				}
Patrick Devine's avatar
Patrick Devine committed
408
409
410
411
412
413
			}
		}
	}
}

func (b *Buffer) Delete() {
414
	if b.Buf.Size() > 0 && b.Pos < b.Buf.Size() {
Patrick Devine's avatar
Patrick Devine committed
415
416
		b.Buf.Remove(b.Pos)
		b.drawRemaining()
417
418
419
		if b.DisplaySize()%b.LineWidth == 0 {
			if b.DisplayPos != b.DisplaySize() {
				remainingLines := (b.DisplaySize() - b.DisplayPos) / b.LineWidth
Michael Yang's avatar
lint  
Michael Yang committed
420
				fmt.Print(CursorDownN(remainingLines) + CursorBOL + ClearToEOL)
421
				place := b.DisplayPos % b.LineWidth
Michael Yang's avatar
lint  
Michael Yang committed
422
				fmt.Print(CursorUpN(remainingLines) + CursorRightN(place+len(b.Prompt.prompt())))
Patrick Devine's avatar
Patrick Devine committed
423
424
425
426
427
428
429
430
431
432
433
434
435
436
			}
		}
	}
}

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

func (b *Buffer) DeleteRemaining() {
437
438
	if b.DisplaySize() > 0 && b.Pos < b.DisplaySize() {
		charsToDel := b.Buf.Size() - b.Pos
Michael Yang's avatar
lint  
Michael Yang committed
439
		for range charsToDel {
Patrick Devine's avatar
Patrick Devine committed
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
			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
lint  
Michael Yang committed
469
	fmt.Print(ClearScreen + CursorReset + b.Prompt.prompt())
Patrick Devine's avatar
Patrick Devine committed
470
	if b.IsEmpty() {
Michael Yang's avatar
Michael Yang committed
471
		ph := b.Prompt.placeholder()
Michael Yang's avatar
lint  
Michael Yang committed
472
		fmt.Print(ColorGrey + ph + CursorLeftN(len(ph)) + ColorDefault)
Patrick Devine's avatar
Patrick Devine committed
473
	} else {
474
475
		currPos := b.DisplayPos
		currIndex := b.Pos
Patrick Devine's avatar
Patrick Devine committed
476
		b.Pos = 0
477
		b.DisplayPos = 0
Patrick Devine's avatar
Patrick Devine committed
478
		b.drawRemaining()
Michael Yang's avatar
lint  
Michael Yang committed
479
		fmt.Print(CursorReset + CursorRightN(len(b.Prompt.prompt())))
Patrick Devine's avatar
Patrick Devine committed
480
481
482
		if currPos > 0 {
			targetLine := currPos / b.LineWidth
			if targetLine > 0 {
Michael Yang's avatar
lint  
Michael Yang committed
483
				for range targetLine {
484
					fmt.Print(CursorDown)
Patrick Devine's avatar
Patrick Devine committed
485
486
487
488
				}
			}
			remainder := currPos % b.LineWidth
			if remainder > 0 {
Michael Yang's avatar
lint  
Michael Yang committed
489
				fmt.Print(CursorRightN(remainder))
Patrick Devine's avatar
Patrick Devine committed
490
491
			}
			if currPos%b.LineWidth == 0 {
Michael Yang's avatar
lint  
Michael Yang committed
492
				fmt.Print(CursorBOL + b.Prompt.AltPrompt)
Patrick Devine's avatar
Patrick Devine committed
493
494
			}
		}
495
496
		b.Pos = currIndex
		b.DisplayPos = currPos
Patrick Devine's avatar
Patrick Devine committed
497
498
499
500
501
502
503
504
	}
}

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

func (b *Buffer) Replace(r []rune) {
505
	b.DisplayPos = 0
Patrick Devine's avatar
Patrick Devine committed
506
	b.Pos = 0
507
508
	lineNums := b.DisplaySize() / b.LineWidth

Patrick Devine's avatar
Patrick Devine committed
509
	b.Buf.Clear()
510

Michael Yang's avatar
lint  
Michael Yang committed
511
	fmt.Print(CursorBOL + ClearToEOL)
512

Michael Yang's avatar
Michael Yang committed
513
	for range lineNums {
514
515
516
		fmt.Print(CursorUp + CursorBOL + ClearToEOL)
	}

Michael Yang's avatar
lint  
Michael Yang committed
517
	fmt.Print(CursorBOL + b.Prompt.prompt())
518

Patrick Devine's avatar
Patrick Devine committed
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
	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 {
535
		m = b.Buf.Size()
Patrick Devine's avatar
Patrick Devine committed
536
537
538
539
540
541
542
	}
	for cnt := n; cnt < m; cnt++ {
		c, _ := b.Buf.Get(cnt)
		s += string(c.(rune))
	}
	return s
}