1// Copyright 2014 The gocui Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package gocui
6
7import (
8 "bytes"
9 "io"
10 "strings"
11 "sync"
12 "unicode/utf8"
13
14 "github.com/go-errors/errors"
15
16 "github.com/awesome-gocui/termbox-go"
17 "github.com/mattn/go-runewidth"
18)
19
20// Constants for overlapping edges
21const (
22 TOP = 1 // view is overlapping at top edge
23 BOTTOM = 2 // view is overlapping at bottom edge
24 LEFT = 4 // view is overlapping at left edge
25 RIGHT = 8 // view is overlapping at right edge
26)
27
28var (
29 // ErrInvalidPoint is returned when client passed invalid coordinates of a cell.
30 // Most likely client has passed negative coordinates of a cell.
31 ErrInvalidPoint = errors.New("invalid point")
32)
33
34// A View is a window. It maintains its own internal buffer and cursor
35// position.
36type View struct {
37 name string
38 x0, y0, x1, y1 int // left top right bottom
39 ox, oy int // view offsets
40 cx, cy int // cursor position
41 rx, ry int // Read() offsets
42 wx, wy int // Write() offsets
43 lines [][]cell // All the data
44
45 // readBuffer is used for storing unread bytes
46 readBuffer []byte
47
48 // tained is true if the viewLines must be updated
49 tainted bool
50
51 // internal representation of the view's buffer
52 viewLines []viewLine
53
54 // writeMutex protects locks the write process
55 writeMutex sync.Mutex
56
57 // ei is used to decode ESC sequences on Write
58 ei *escapeInterpreter
59
60 // Visible specifies whether the view is visible.
61 Visible bool
62
63 // BgColor and FgColor allow to configure the background and foreground
64 // colors of the View.
65 BgColor, FgColor Attribute
66
67 // SelBgColor and SelFgColor are used to configure the background and
68 // foreground colors of the selected line, when it is highlighted.
69 SelBgColor, SelFgColor Attribute
70
71 // If Editable is true, keystrokes will be added to the view's internal
72 // buffer at the cursor position.
73 Editable bool
74
75 // Editor allows to define the editor that manages the editing mode,
76 // including keybindings or cursor behaviour. DefaultEditor is used by
77 // default.
78 Editor Editor
79
80 // Overwrite enables or disables the overwrite mode of the view.
81 Overwrite bool
82
83 // If Highlight is true, Sel{Bg,Fg}Colors will be used
84 // for the line under the cursor position.
85 Highlight bool
86
87 // If Frame is true, a border will be drawn around the view.
88 Frame bool
89
90 // If Wrap is true, the content that is written to this View is
91 // automatically wrapped when it is longer than its width. If true the
92 // view's x-origin will be ignored.
93 Wrap bool
94
95 // If Autoscroll is true, the View will automatically scroll down when the
96 // text overflows. If true the view's y-origin will be ignored.
97 Autoscroll bool
98
99 // If Frame is true, Title allows to configure a title for the view.
100 Title string
101
102 // If Frame is true, Subtitle allows to configure a subtitle for the view.
103 Subtitle string
104
105 // If Mask is true, the View will display the mask instead of the real
106 // content
107 Mask rune
108
109 // Overlaps describes which edges are overlapping with another view's edges
110 Overlaps byte
111
112 // If HasLoader is true, the message will be appended with a spinning loader animation
113 HasLoader bool
114}
115
116type viewLine struct {
117 linesX, linesY int // coordinates relative to v.lines
118 line []cell
119}
120
121type cell struct {
122 chr rune
123 bgColor, fgColor Attribute
124}
125
126type lineType []cell
127
128// String returns a string from a given cell slice.
129func (l lineType) String() string {
130 str := ""
131 for _, c := range l {
132 str += string(c.chr)
133 }
134 return str
135}
136
137// newView returns a new View object.
138func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
139 v := &View{
140 name: name,
141 x0: x0,
142 y0: y0,
143 x1: x1,
144 y1: y1,
145 Visible: true,
146 Frame: true,
147 Editor: DefaultEditor,
148 tainted: true,
149 ei: newEscapeInterpreter(mode),
150 }
151 return v
152}
153
154// Dimensions returns the dimensions of the View
155func (v *View) Dimensions() (int, int, int, int) {
156 return v.x0, v.y0, v.x1, v.y1
157}
158
159// Size returns the number of visible columns and rows in the View.
160func (v *View) Size() (x, y int) {
161 return v.x1 - v.x0 - 1, v.y1 - v.y0 - 1
162}
163
164// Name returns the name of the view.
165func (v *View) Name() string {
166 return v.name
167}
168
169// setRune sets a rune at the given point relative to the view. It applies the
170// specified colors, taking into account if the cell must be highlighted. Also,
171// it checks if the position is valid.
172func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
173 maxX, maxY := v.Size()
174 if x < 0 || x >= maxX || y < 0 || y >= maxY {
175 return ErrInvalidPoint
176 }
177 var (
178 ry, rcy int
179 err error
180 )
181 if v.Highlight {
182 _, ry, err = v.realPosition(x, y)
183 if err != nil {
184 return err
185 }
186 _, rcy, err = v.realPosition(v.cx, v.cy)
187 if err != nil {
188 return err
189 }
190 }
191
192 if v.Mask != 0 {
193 fgColor = v.FgColor
194 bgColor = v.BgColor
195 ch = v.Mask
196 } else if v.Highlight && ry == rcy {
197 fgColor = fgColor | AttrBold
198 }
199
200 // Don't display NUL characters
201 if ch == 0 {
202 ch = ' '
203 }
204
205 termbox.SetCell(v.x0+x+1, v.y0+y+1, ch,
206 termbox.Attribute(fgColor), termbox.Attribute(bgColor))
207
208 return nil
209}
210
211// SetCursor sets the cursor position of the view at the given point,
212// relative to the view. It checks if the position is valid.
213func (v *View) SetCursor(x, y int) error {
214 maxX, maxY := v.Size()
215 if x < 0 || x >= maxX || y < 0 || y >= maxY {
216 return ErrInvalidPoint
217 }
218 v.cx = x
219 v.cy = y
220 return nil
221}
222
223// Cursor returns the cursor position of the view.
224func (v *View) Cursor() (x, y int) {
225 return v.cx, v.cy
226}
227
228// SetOrigin sets the origin position of the view's internal buffer,
229// so the buffer starts to be printed from this point, which means that
230// it is linked with the origin point of view. It can be used to
231// implement Horizontal and Vertical scrolling with just incrementing
232// or decrementing ox and oy.
233func (v *View) SetOrigin(x, y int) error {
234 if x < 0 || y < 0 {
235 return ErrInvalidPoint
236 }
237 v.ox = x
238 v.oy = y
239 return nil
240}
241
242// Origin returns the origin position of the view.
243func (v *View) Origin() (x, y int) {
244 return v.ox, v.oy
245}
246
247// SetWritePos sets the write position of the view's internal buffer.
248// So the next Write call would write directly to the specified position.
249func (v *View) SetWritePos(x, y int) error {
250 if x < 0 || y < 0 {
251 return ErrInvalidPoint
252 }
253 v.wx = x
254 v.wy = y
255 return nil
256}
257
258// WritePos returns the current write position of the view's internal buffer.
259func (v *View) WritePos() (x, y int) {
260 return v.wx, v.wy
261}
262
263// SetReadPos sets the read position of the view's internal buffer.
264// So the next Read call would read from the specified position.
265func (v *View) SetReadPos(x, y int) error {
266 if x < 0 || y < 0 {
267 return ErrInvalidPoint
268 }
269 v.readBuffer = nil
270 v.rx = x
271 v.ry = y
272 return nil
273}
274
275// ReadPos returns the current read position of the view's internal buffer.
276func (v *View) ReadPos() (x, y int) {
277 return v.rx, v.ry
278}
279
280// makeWriteable creates empty cells if required to make position (x, y) writeable.
281func (v *View) makeWriteable(x, y int) {
282 // TODO: make this more efficient
283
284 // line `y` must be index-able (that's why `<=`)
285 for len(v.lines) <= y {
286 if cap(v.lines) > len(v.lines) {
287 newLen := cap(v.lines)
288 if newLen > y {
289 newLen = y + 1
290 }
291 v.lines = v.lines[:newLen]
292 } else {
293 v.lines = append(v.lines, nil)
294 }
295 }
296 // cell `x` must not be index-able (that's why `<`)
297 // append should be used by `lines[y]` user if he wants to write beyond `x`
298 for len(v.lines[y]) < x {
299 if cap(v.lines[y]) > len(v.lines[y]) {
300 newLen := cap(v.lines[y])
301 if newLen > x {
302 newLen = x
303 }
304 v.lines[y] = v.lines[y][:newLen]
305 } else {
306 v.lines[y] = append(v.lines[y], cell{})
307 }
308 }
309}
310
311// writeCells copies []cell to specified location (x, y)
312// !!! caller MUST ensure that specified location (x, y) is writeable by calling makeWriteable
313func (v *View) writeCells(x, y int, cells []cell) {
314 var newLen int
315 // use maximum len available
316 line := v.lines[y][:cap(v.lines[y])]
317 maxCopy := len(line) - x
318 if maxCopy < len(cells) {
319 copy(line[x:], cells[:maxCopy])
320 line = append(line, cells[maxCopy:]...)
321 newLen = len(line)
322 } else { // maxCopy >= len(cells)
323 copy(line[x:], cells)
324 newLen = x + len(cells)
325 if newLen < len(v.lines[y]) {
326 newLen = len(v.lines[y])
327 }
328 }
329 v.lines[y] = line[:newLen]
330}
331
332// Write appends a byte slice into the view's internal buffer. Because
333// View implements the io.Writer interface, it can be passed as parameter
334// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must
335// be called to clear the view's buffer.
336func (v *View) Write(p []byte) (n int, err error) {
337 v.tainted = true
338 v.writeMutex.Lock()
339 v.makeWriteable(v.wx, v.wy)
340 v.writeRunes(bytes.Runes(p))
341 v.writeMutex.Unlock()
342
343 return len(p), nil
344}
345
346func (v *View) WriteRunes(p []rune) {
347 v.tainted = true
348
349 // Fill with empty cells, if writing outside current view buffer
350 v.makeWriteable(v.wx, v.wy)
351 v.writeRunes(p)
352}
353
354func (v *View) WriteString(s string) {
355 v.WriteRunes([]rune(s))
356}
357
358// writeRunes copies slice of runes into internal lines buffer.
359// caller must make sure that writing position is accessable.
360func (v *View) writeRunes(p []rune) {
361 for _, r := range p {
362 switch r {
363 case '\n':
364 v.wy++
365 if v.wy >= len(v.lines) {
366 v.lines = append(v.lines, nil)
367 }
368
369 fallthrough
370 // not valid in every OS, but making runtime OS checks in cycle is bad.
371 case '\r':
372 v.wx = 0
373 default:
374 cells := v.parseInput(r)
375 if cells == nil {
376 continue
377 }
378 v.writeCells(v.wx, v.wy, cells)
379 v.wx += len(cells)
380 }
381 }
382}
383
384// parseInput parses char by char the input written to the View. It returns nil
385// while processing ESC sequences. Otherwise, it returns a cell slice that
386// contains the processed data.
387func (v *View) parseInput(ch rune) []cell {
388 cells := []cell{}
389
390 isEscape, err := v.ei.parseOne(ch)
391 if err != nil {
392 for _, r := range v.ei.runes() {
393 c := cell{
394 fgColor: v.FgColor,
395 bgColor: v.BgColor,
396 chr: r,
397 }
398 cells = append(cells, c)
399 }
400 v.ei.reset()
401 } else {
402 if isEscape {
403 return nil
404 }
405 repeatCount := 1
406 if ch == '\t' {
407 ch = ' '
408 repeatCount = 4
409 }
410 for i := 0; i < repeatCount; i++ {
411 c := cell{
412 fgColor: v.ei.curFgColor,
413 bgColor: v.ei.curBgColor,
414 chr: ch,
415 }
416 cells = append(cells, c)
417 }
418 }
419
420 return cells
421}
422
423// Read reads data into p from the current reading position set by SetReadPos.
424// It returns the number of bytes read into p.
425// At EOF, err will be io.EOF.
426func (v *View) Read(p []byte) (n int, err error) {
427 buffer := make([]byte, utf8.UTFMax)
428 offset := 0
429 if v.readBuffer != nil {
430 copy(p, v.readBuffer)
431 if len(v.readBuffer) >= len(p) {
432 if len(v.readBuffer) > len(p) {
433 v.readBuffer = v.readBuffer[len(p):]
434 }
435 return len(p), nil
436 }
437 v.readBuffer = nil
438 }
439 for v.ry < len(v.lines) {
440 for v.rx < len(v.lines[v.ry]) {
441 count := utf8.EncodeRune(buffer, v.lines[v.ry][v.rx].chr)
442 copy(p[offset:], buffer[:count])
443 v.rx++
444 newOffset := offset + count
445 if newOffset >= len(p) {
446 if newOffset > len(p) {
447 v.readBuffer = buffer[newOffset-len(p):]
448 }
449 return len(p), nil
450 }
451 offset += count
452 }
453 v.rx = 0
454 v.ry++
455 }
456 return offset, io.EOF
457}
458
459// Rewind sets read and write pos to (0, 0).
460func (v *View) Rewind() {
461 if err := v.SetReadPos(0, 0); err != nil {
462 // SetReadPos returns error only if x and y are negative
463 // we are passing 0, 0, thus no error should occur.
464 panic(err)
465 }
466 if err := v.SetWritePos(0, 0); err != nil {
467 // SetWritePos returns error only if x and y are negative
468 // we are passing 0, 0, thus no error should occur.
469 panic(err)
470 }
471}
472
473// IsTainted tells us if the view is tainted
474func (v *View) IsTainted() bool {
475 return v.tainted
476}
477
478// draw re-draws the view's contents.
479func (v *View) draw() error {
480 if !v.Visible {
481 return nil
482 }
483
484 maxX, maxY := v.Size()
485
486 if v.Wrap {
487 if maxX == 0 {
488 return errors.New("X size of the view cannot be 0")
489 }
490 v.ox = 0
491 }
492 if v.tainted {
493 v.viewLines = nil
494 lines := v.lines
495 if v.HasLoader {
496 lines = v.loaderLines()
497 }
498 for i, line := range lines {
499 wrap := 0
500 if v.Wrap {
501 wrap = maxX
502 }
503
504 ls := lineWrap(line, wrap)
505 for j := range ls {
506 vline := viewLine{linesX: j, linesY: i, line: ls[j]}
507 v.viewLines = append(v.viewLines, vline)
508 }
509 }
510 if !v.HasLoader {
511 v.tainted = false
512 }
513 }
514
515 if v.Autoscroll && len(v.viewLines) > maxY {
516 v.oy = len(v.viewLines) - maxY
517 }
518 y := 0
519 for i, vline := range v.viewLines {
520 if i < v.oy {
521 continue
522 }
523 if y >= maxY {
524 break
525 }
526 x := 0
527 for j, c := range vline.line {
528 if j < v.ox {
529 continue
530 }
531 if x >= maxX {
532 break
533 }
534
535 fgColor := c.fgColor
536 if fgColor == ColorDefault {
537 fgColor = v.FgColor
538 }
539 bgColor := c.bgColor
540 if bgColor == ColorDefault {
541 bgColor = v.BgColor
542 }
543
544 if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil {
545 return err
546 }
547
548 if c.chr != 0 {
549 // If it is a rune, add rune width
550 x += runewidth.RuneWidth(c.chr)
551 } else {
552 // If it is NULL rune, add 1 to be able to use SetWritePos
553 // (runewidth.RuneWidth of space is 1)
554 x++
555 }
556 }
557 y++
558 }
559 return nil
560}
561
562// realPosition returns the position in the internal buffer corresponding to the
563// point (x, y) of the view.
564func (v *View) realPosition(vx, vy int) (x, y int, err error) {
565 vx = v.ox + vx
566 vy = v.oy + vy
567
568 if vx < 0 || vy < 0 {
569 return 0, 0, ErrInvalidPoint
570 }
571
572 if len(v.viewLines) == 0 {
573 return vx, vy, nil
574 }
575
576 if vy < len(v.viewLines) {
577 vline := v.viewLines[vy]
578 x = vline.linesX + vx
579 y = vline.linesY
580 } else {
581 vline := v.viewLines[len(v.viewLines)-1]
582 x = vx
583 y = vline.linesY + vy - len(v.viewLines) + 1
584 }
585
586 return x, y, nil
587}
588
589// Clear empties the view's internal buffer.
590// And resets reading and writing offsets.
591func (v *View) Clear() {
592 v.writeMutex.Lock()
593 v.Rewind()
594 v.tainted = true
595 v.ei.reset()
596 v.lines = nil
597 v.viewLines = nil
598 v.clearRunes()
599 v.writeMutex.Unlock()
600}
601
602// clearRunes erases all the cells in the view.
603func (v *View) clearRunes() {
604 maxX, maxY := v.Size()
605 for x := 0; x < maxX; x++ {
606 for y := 0; y < maxY; y++ {
607 termbox.SetCell(v.x0+x+1, v.y0+y+1, ' ',
608 termbox.Attribute(v.FgColor), termbox.Attribute(v.BgColor))
609 }
610 }
611}
612
613// BufferLines returns the lines in the view's internal
614// buffer.
615func (v *View) BufferLines() []string {
616 lines := make([]string, len(v.lines))
617 for i, l := range v.lines {
618 str := lineType(l).String()
619 str = strings.Replace(str, "\x00", " ", -1)
620 lines[i] = str
621 }
622 return lines
623}
624
625// Buffer returns a string with the contents of the view's internal
626// buffer.
627func (v *View) Buffer() string {
628 return linesToString(v.lines)
629}
630
631// ViewBufferLines returns the lines in the view's internal
632// buffer that is shown to the user.
633func (v *View) ViewBufferLines() []string {
634 lines := make([]string, len(v.viewLines))
635 for i, l := range v.viewLines {
636 str := lineType(l.line).String()
637 str = strings.Replace(str, "\x00", " ", -1)
638 lines[i] = str
639 }
640 return lines
641}
642
643// LinesHeight is the count of view lines (i.e. lines excluding wrapping)
644func (v *View) LinesHeight() int {
645 return len(v.lines)
646}
647
648// ViewLinesHeight is the count of view lines (i.e. lines including wrapping)
649func (v *View) ViewLinesHeight() int {
650 return len(v.viewLines)
651}
652
653// ViewBuffer returns a string with the contents of the view's buffer that is
654// shown to the user.
655func (v *View) ViewBuffer() string {
656 lines := make([][]cell, len(v.viewLines))
657 for i := range v.viewLines {
658 lines[i] = v.viewLines[i].line
659 }
660
661 return linesToString(lines)
662}
663
664// Line returns a string with the line of the view's internal buffer
665// at the position corresponding to the point (x, y).
666func (v *View) Line(y int) (string, error) {
667 _, y, err := v.realPosition(0, y)
668 if err != nil {
669 return "", err
670 }
671
672 if y < 0 || y >= len(v.lines) {
673 return "", ErrInvalidPoint
674 }
675
676 return lineType(v.lines[y]).String(), nil
677}
678
679// Word returns a string with the word of the view's internal buffer
680// at the position corresponding to the point (x, y).
681func (v *View) Word(x, y int) (string, error) {
682 x, y, err := v.realPosition(x, y)
683 if err != nil {
684 return "", err
685 }
686
687 if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
688 return "", ErrInvalidPoint
689 }
690
691 str := lineType(v.lines[y]).String()
692
693 nl := strings.LastIndexFunc(str[:x], indexFunc)
694 if nl == -1 {
695 nl = 0
696 } else {
697 nl = nl + 1
698 }
699 nr := strings.IndexFunc(str[x:], indexFunc)
700 if nr == -1 {
701 nr = len(str)
702 } else {
703 nr = nr + x
704 }
705 return string(str[nl:nr]), nil
706}
707
708// indexFunc allows to split lines by words taking into account spaces
709// and 0.
710func indexFunc(r rune) bool {
711 return r == ' ' || r == 0
712}
713
714// SetLine changes the contents of an existing line.
715func (v *View) SetLine(y int, text string) error {
716 if y < 0 || y >= len(v.lines) {
717 err := ErrInvalidPoint
718 return err
719 }
720
721 v.tainted = true
722 line := make([]cell, 0)
723 for _, r := range text {
724 c := v.parseInput(r)
725 line = append(line, c...)
726 }
727 v.lines[y] = line
728 return nil
729}
730
731// SetHighlight toggles highlighting of separate lines, for custom lists
732// or multiple selection in views.
733func (v *View) SetHighlight(y int, on bool) error {
734 if y < 0 || y >= len(v.lines) {
735 err := ErrInvalidPoint
736 return err
737 }
738
739 line := v.lines[y]
740 cells := make([]cell, 0)
741 for _, c := range line {
742 if on {
743 c.bgColor = v.SelBgColor
744 c.fgColor = v.SelFgColor
745 } else {
746 c.bgColor = v.BgColor
747 c.fgColor = v.FgColor
748 }
749 cells = append(cells, c)
750 }
751 v.tainted = true
752 v.lines[y] = cells
753 return nil
754}
755
756func lineWidth(line []cell) (n int) {
757 for i := range line {
758 n += runewidth.RuneWidth(line[i].chr)
759 }
760
761 return
762}
763
764func lineWrap(line []cell, columns int) [][]cell {
765 if columns == 0 {
766 return [][]cell{line}
767 }
768
769 var n int
770 var offset int
771 lines := make([][]cell, 0, 1)
772 for i := range line {
773 rw := runewidth.RuneWidth(line[i].chr)
774 n += rw
775 if n > columns {
776 n = rw
777 lines = append(lines, line[offset:i])
778 offset = i
779 }
780 }
781
782 lines = append(lines, line[offset:])
783 return lines
784}
785
786func linesToString(lines [][]cell) string {
787 str := make([]string, len(lines))
788 for i := range lines {
789 rns := make([]rune, 0, len(lines[i]))
790 line := lineType(lines[i]).String()
791 for _, c := range line {
792 if c != '\x00' {
793 rns = append(rns, c)
794 }
795 }
796 str[i] = string(rns)
797 }
798
799 return strings.Join(str, "\n")
800}