view.go

  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}