buffer.go 12.4 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
16
17
18
19
20
21
	DisplayPos int
	Pos        int
	Buf        *arraylist.List
	//LineHasSpace is an arraylist of bools to keep track of whether a line has a space at the end
	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
57
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
58
59
func (b *Buffer) MoveLeft() {
	if b.Pos > 0 {
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
		//asserts that we retrieve a rune
		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
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
109
		}
	}
}

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() {
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
	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
133
134
135
136
137
		}
	}
}

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

146
			if b.Pos == b.Buf.Size() {
Patrick Devine's avatar
Patrick Devine committed
147
148
149
150
151
152
153
154
				break
			}
		}
	}
}

func (b *Buffer) MoveToStart() {
	if b.Pos > 0 {
155
		currLine := b.DisplayPos / b.LineWidth
Patrick Devine's avatar
Patrick Devine committed
156
157
		if currLine > 0 {
			for cnt := 0; cnt < currLine; cnt++ {
158
				fmt.Print(CursorUp)
Patrick Devine's avatar
Patrick Devine committed
159
160
			}
		}
Michael Yang's avatar
Michael Yang committed
161
		fmt.Printf(CursorBOL + cursorRightN(len(b.Prompt.prompt())))
Patrick Devine's avatar
Patrick Devine committed
162
		b.Pos = 0
163
		b.DisplayPos = 0
Patrick Devine's avatar
Patrick Devine committed
164
165
166
167
	}
}

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

181
182
		b.Pos = b.Buf.Size()
		b.DisplayPos = b.DisplaySize()
Patrick Devine's avatar
Patrick Devine committed
183
184
185
	}
}

186
187
188
189
190
191
192
193
194
195
196
func (b *Buffer) DisplaySize() int {
	sum := 0
	for i := 0; i < b.Buf.Size(); i++ {
		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
197
198
199
}

func (b *Buffer) Add(r rune) {
200

Patrick Devine's avatar
Patrick Devine committed
201
	if b.Pos == b.Buf.Size() {
202
203
204
205
206
207
208
209
210
211
212
213
214
215
		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
216
			fmt.Printf("\n%s", b.Prompt.AltPrompt)
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240

			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
241
242
243
		}
	} else {
		fmt.Printf("%c", r)
244
245
246
	}

	if insert {
Patrick Devine's avatar
Patrick Devine committed
247
		b.Buf.Insert(b.Pos, r)
248
249
250
251
252
253
254
	} else {
		b.Buf.Add(r)
	}

	b.Pos += 1

	if insert {
Patrick Devine's avatar
Patrick Devine committed
255
256
257
258
		b.drawRemaining()
	}
}

259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
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
280
281
282
283
func (b *Buffer) drawRemaining() {
	var place int
	remainingText := b.StringN(b.Pos)
	if b.Pos > 0 {
284
		place = b.DisplayPos % b.LineWidth
Patrick Devine's avatar
Patrick Devine committed
285
	}
286
	fmt.Print(CursorHide)
Patrick Devine's avatar
Patrick Devine committed
287
288

	// render the rest of the current line
289
290
291
292
293
294
	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
295
296
	if len(currLine) > 0 {
		fmt.Printf(ClearToEOL + currLine)
297
		fmt.Print(cursorLeftN(currLineSpace))
Patrick Devine's avatar
Patrick Devine committed
298
	} else {
299
		fmt.Print(ClearToEOL)
Patrick Devine's avatar
Patrick Devine committed
300
301
	}

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

				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
337
			}
338
339
340

			displayLength += runewidth.RuneWidth(c)
			lineLength += runewidth.RuneWidth(c)
Patrick Devine's avatar
Patrick Devine committed
341
342
			fmt.Printf("%c", c)
		}
343
344
		fmt.Print(ClearToEOL)
		fmt.Print(cursorUpN(totalLines))
345
346
347
348
349
350
351
		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
352
353
	}

354
	fmt.Print(CursorShow)
Patrick Devine's avatar
Patrick Devine committed
355
356
357
358
359
}

func (b *Buffer) Remove() {
	if b.Buf.Size() > 0 && b.Pos > 0 {

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
387
388
389
390
391
392
393
		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
Patrick Devine's avatar
Patrick Devine committed
394

395
396
397
398
399
400
401
402
403
404
405
406
				} else {
					fmt.Print(cursorLeftN(rLength))
					for i := 0; i < rLength; i++ {
						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
407

408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
				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
423
424
425
426
427
428
			}
		}
	}
}

func (b *Buffer) Delete() {
429
	if b.Buf.Size() > 0 && b.Pos < b.Buf.Size() {
Patrick Devine's avatar
Patrick Devine committed
430
431
		b.Buf.Remove(b.Pos)
		b.drawRemaining()
432
433
434
		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
435
				fmt.Printf(cursorDownN(remainingLines) + CursorBOL + ClearToEOL)
436
				place := b.DisplayPos % b.LineWidth
Michael Yang's avatar
Michael Yang committed
437
				fmt.Printf(cursorUpN(remainingLines) + cursorRightN(place+len(b.Prompt.prompt())))
Patrick Devine's avatar
Patrick Devine committed
438
439
440
441
442
443
444
445
446
447
448
449
450
451
			}
		}
	}
}

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

func (b *Buffer) DeleteRemaining() {
452
453
	if b.DisplaySize() > 0 && b.Pos < b.DisplaySize() {
		charsToDel := b.Buf.Size() - b.Pos
Patrick Devine's avatar
Patrick Devine committed
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
		for cnt := 0; cnt < charsToDel; cnt++ {
			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
484
	fmt.Printf(ClearScreen + CursorReset + b.Prompt.prompt())
Patrick Devine's avatar
Patrick Devine committed
485
	if b.IsEmpty() {
Michael Yang's avatar
Michael Yang committed
486
		ph := b.Prompt.placeholder()
Patrick Devine's avatar
Patrick Devine committed
487
488
		fmt.Printf(ColorGrey + ph + cursorLeftN(len(ph)) + ColorDefault)
	} else {
489
490
		currPos := b.DisplayPos
		currIndex := b.Pos
Patrick Devine's avatar
Patrick Devine committed
491
		b.Pos = 0
492
		b.DisplayPos = 0
Patrick Devine's avatar
Patrick Devine committed
493
		b.drawRemaining()
Michael Yang's avatar
Michael Yang committed
494
		fmt.Printf(CursorReset + cursorRightN(len(b.Prompt.prompt())))
Patrick Devine's avatar
Patrick Devine committed
495
496
497
498
		if currPos > 0 {
			targetLine := currPos / b.LineWidth
			if targetLine > 0 {
				for cnt := 0; cnt < targetLine; cnt++ {
499
					fmt.Print(CursorDown)
Patrick Devine's avatar
Patrick Devine committed
500
501
502
503
				}
			}
			remainder := currPos % b.LineWidth
			if remainder > 0 {
504
				fmt.Print(cursorRightN(remainder))
Patrick Devine's avatar
Patrick Devine committed
505
506
507
508
509
			}
			if currPos%b.LineWidth == 0 {
				fmt.Printf(CursorBOL + b.Prompt.AltPrompt)
			}
		}
510
511
		b.Pos = currIndex
		b.DisplayPos = currPos
Patrick Devine's avatar
Patrick Devine committed
512
513
514
515
516
517
518
519
	}
}

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

func (b *Buffer) Replace(r []rune) {
520
	b.DisplayPos = 0
Patrick Devine's avatar
Patrick Devine committed
521
	b.Pos = 0
522
523
	lineNums := b.DisplaySize() / b.LineWidth

Patrick Devine's avatar
Patrick Devine committed
524
	b.Buf.Clear()
525
526
527
528
529
530
531
532
533

	fmt.Printf(CursorBOL + ClearToEOL)

	for i := 0; i < lineNums; i++ {
		fmt.Print(CursorUp + CursorBOL + ClearToEOL)
	}

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

Patrick Devine's avatar
Patrick Devine committed
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
	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 {
550
		m = b.Buf.Size()
Patrick Devine's avatar
Patrick Devine committed
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
	}
	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)
}