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	"errors"
 10	"io"
 11	"strings"
 12
 13	"github.com/mattn/go-runewidth"
 14	"github.com/nsf/termbox-go"
 15)
 16
 17// A View is a window. It maintains its own internal buffer and cursor
 18// position.
 19type View struct {
 20	name           string
 21	x0, y0, x1, y1 int
 22	ox, oy         int
 23	cx, cy         int
 24	lines          [][]cell
 25	readOffset     int
 26	readCache      string
 27
 28	tainted   bool       // marks if the viewBuffer must be updated
 29	viewLines []viewLine // internal representation of the view's buffer
 30
 31	ei *escapeInterpreter // used to decode ESC sequences on Write
 32
 33	// BgColor and FgColor allow to configure the background and foreground
 34	// colors of the View.
 35	BgColor, FgColor Attribute
 36
 37	// SelBgColor and SelFgColor are used to configure the background and
 38	// foreground colors of the selected line, when it is highlighted.
 39	SelBgColor, SelFgColor Attribute
 40
 41	// If Editable is true, keystrokes will be added to the view's internal
 42	// buffer at the cursor position.
 43	Editable bool
 44
 45	// Editor allows to define the editor that manages the edition mode,
 46	// including keybindings or cursor behaviour. DefaultEditor is used by
 47	// default.
 48	Editor Editor
 49
 50	// Overwrite enables or disables the overwrite mode of the view.
 51	Overwrite bool
 52
 53	// If Highlight is true, Sel{Bg,Fg}Colors will be used
 54	// for the line under the cursor position.
 55	Highlight bool
 56
 57	// If Frame is true, a border will be drawn around the view.
 58	Frame bool
 59
 60	// If Wrap is true, the content that is written to this View is
 61	// automatically wrapped when it is longer than its width. If true the
 62	// view's x-origin will be ignored.
 63	Wrap bool
 64
 65	// If Autoscroll is true, the View will automatically scroll down when the
 66	// text overflows. If true the view's y-origin will be ignored.
 67	Autoscroll bool
 68
 69	// If Frame is true, Title allows to configure a title for the view.
 70	Title string
 71
 72	// If Mask is true, the View will display the mask instead of the real
 73	// content
 74	Mask rune
 75}
 76
 77type viewLine struct {
 78	linesX, linesY int // coordinates relative to v.lines
 79	line           []cell
 80}
 81
 82type cell struct {
 83	chr              rune
 84	bgColor, fgColor Attribute
 85}
 86
 87type lineType []cell
 88
 89// String returns a string from a given cell slice.
 90func (l lineType) String() string {
 91	str := ""
 92	for _, c := range l {
 93		str += string(c.chr)
 94	}
 95	return str
 96}
 97
 98// newView returns a new View object.
 99func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
100	v := &View{
101		name:    name,
102		x0:      x0,
103		y0:      y0,
104		x1:      x1,
105		y1:      y1,
106		Frame:   true,
107		Editor:  DefaultEditor,
108		tainted: true,
109		ei:      newEscapeInterpreter(mode),
110	}
111	return v
112}
113
114// Size returns the number of visible columns and rows in the View.
115func (v *View) Size() (x, y int) {
116	return v.x1 - v.x0 - 1, v.y1 - v.y0 - 1
117}
118
119// Name returns the name of the view.
120func (v *View) Name() string {
121	return v.name
122}
123
124// setRune sets a rune at the given point relative to the view. It applies the
125// specified colors, taking into account if the cell must be highlighted. Also,
126// it checks if the position is valid.
127func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
128	maxX, maxY := v.Size()
129	if x < 0 || x >= maxX || y < 0 || y >= maxY {
130		return errors.New("invalid point")
131	}
132
133	var (
134		ry, rcy int
135		err     error
136	)
137	if v.Highlight {
138		_, ry, err = v.realPosition(x, y)
139		if err != nil {
140			return err
141		}
142		_, rcy, err = v.realPosition(v.cx, v.cy)
143		if err != nil {
144			return err
145		}
146	}
147
148	if v.Mask != 0 {
149		fgColor = v.FgColor
150		bgColor = v.BgColor
151		ch = v.Mask
152	} else if v.Highlight && ry == rcy {
153		fgColor = v.SelFgColor
154		bgColor = v.SelBgColor
155	}
156
157	termbox.SetCell(v.x0+x+1, v.y0+y+1, ch,
158		termbox.Attribute(fgColor), termbox.Attribute(bgColor))
159
160	return nil
161}
162
163// SetCursor sets the cursor position of the view at the given point,
164// relative to the view. It checks if the position is valid.
165func (v *View) SetCursor(x, y int) error {
166	maxX, maxY := v.Size()
167	if x < 0 || x >= maxX || y < 0 || y >= maxY {
168		return errors.New("invalid point")
169	}
170	v.cx = x
171	v.cy = y
172	return nil
173}
174
175// Cursor returns the cursor position of the view.
176func (v *View) Cursor() (x, y int) {
177	return v.cx, v.cy
178}
179
180// SetOrigin sets the origin position of the view's internal buffer,
181// so the buffer starts to be printed from this point, which means that
182// it is linked with the origin point of view. It can be used to
183// implement Horizontal and Vertical scrolling with just incrementing
184// or decrementing ox and oy.
185func (v *View) SetOrigin(x, y int) error {
186	if x < 0 || y < 0 {
187		return errors.New("invalid point")
188	}
189	v.ox = x
190	v.oy = y
191	return nil
192}
193
194// Origin returns the origin position of the view.
195func (v *View) Origin() (x, y int) {
196	return v.ox, v.oy
197}
198
199// Write appends a byte slice into the view's internal buffer. Because
200// View implements the io.Writer interface, it can be passed as parameter
201// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must
202// be called to clear the view's buffer.
203func (v *View) Write(p []byte) (n int, err error) {
204	v.tainted = true
205
206	for _, ch := range bytes.Runes(p) {
207		switch ch {
208		case '\n':
209			v.lines = append(v.lines, nil)
210		case '\r':
211			nl := len(v.lines)
212			if nl > 0 {
213				v.lines[nl-1] = nil
214			} else {
215				v.lines = make([][]cell, 1)
216			}
217		default:
218			cells := v.parseInput(ch)
219			if cells == nil {
220				continue
221			}
222
223			nl := len(v.lines)
224			if nl > 0 {
225				v.lines[nl-1] = append(v.lines[nl-1], cells...)
226			} else {
227				v.lines = append(v.lines, cells)
228			}
229		}
230	}
231	return len(p), nil
232}
233
234// parseInput parses char by char the input written to the View. It returns nil
235// while processing ESC sequences. Otherwise, it returns a cell slice that
236// contains the processed data.
237func (v *View) parseInput(ch rune) []cell {
238	cells := []cell{}
239
240	isEscape, err := v.ei.parseOne(ch)
241	if err != nil {
242		for _, r := range v.ei.runes() {
243			c := cell{
244				fgColor: v.FgColor,
245				bgColor: v.BgColor,
246				chr:     r,
247			}
248			cells = append(cells, c)
249		}
250		v.ei.reset()
251	} else {
252		if isEscape {
253			return nil
254		}
255		c := cell{
256			fgColor: v.ei.curFgColor,
257			bgColor: v.ei.curBgColor,
258			chr:     ch,
259		}
260		cells = append(cells, c)
261	}
262
263	return cells
264}
265
266// Read reads data into p. It returns the number of bytes read into p.
267// At EOF, err will be io.EOF. Calling Read() after Rewind() makes the
268// cache to be refreshed with the contents of the view.
269func (v *View) Read(p []byte) (n int, err error) {
270	if v.readOffset == 0 {
271		v.readCache = v.Buffer()
272	}
273	if v.readOffset < len(v.readCache) {
274		n = copy(p, v.readCache[v.readOffset:])
275		v.readOffset += n
276	} else {
277		err = io.EOF
278	}
279	return
280}
281
282// Rewind sets the offset for the next Read to 0, which also refresh the
283// read cache.
284func (v *View) Rewind() {
285	v.readOffset = 0
286}
287
288// draw re-draws the view's contents.
289func (v *View) draw() error {
290	maxX, maxY := v.Size()
291
292	if v.Wrap {
293		if maxX == 0 {
294			return errors.New("X size of the view cannot be 0")
295		}
296		v.ox = 0
297	}
298	if v.tainted {
299		v.viewLines = nil
300		for i, line := range v.lines {
301			if v.Wrap {
302				if len(line) < maxX {
303					vline := viewLine{linesX: 0, linesY: i, line: line}
304					v.viewLines = append(v.viewLines, vline)
305					continue
306				} else {
307					for n := 0; n <= len(line); n += maxX {
308						if len(line[n:]) <= maxX {
309							vline := viewLine{linesX: n, linesY: i, line: line[n:]}
310							v.viewLines = append(v.viewLines, vline)
311						} else {
312							vline := viewLine{linesX: n, linesY: i, line: line[n : n+maxX]}
313							v.viewLines = append(v.viewLines, vline)
314						}
315					}
316				}
317			} else {
318				vline := viewLine{linesX: 0, linesY: i, line: line}
319				v.viewLines = append(v.viewLines, vline)
320			}
321		}
322		v.tainted = false
323	}
324
325	if v.Autoscroll && len(v.viewLines) > maxY {
326		v.oy = len(v.viewLines) - maxY
327	}
328	y := 0
329	for i, vline := range v.viewLines {
330		if i < v.oy {
331			continue
332		}
333		if y >= maxY {
334			break
335		}
336		x := 0
337		for j, c := range vline.line {
338			if j < v.ox {
339				continue
340			}
341			if x >= maxX {
342				break
343			}
344
345			fgColor := c.fgColor
346			if fgColor == ColorDefault {
347				fgColor = v.FgColor
348			}
349			bgColor := c.bgColor
350			if bgColor == ColorDefault {
351				bgColor = v.BgColor
352			}
353
354			if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil {
355				return err
356			}
357			x += runewidth.RuneWidth(c.chr)
358		}
359		y++
360	}
361	return nil
362}
363
364// realPosition returns the position in the internal buffer corresponding to the
365// point (x, y) of the view.
366func (v *View) realPosition(vx, vy int) (x, y int, err error) {
367	vx = v.ox + vx
368	vy = v.oy + vy
369
370	if vx < 0 || vy < 0 {
371		return 0, 0, errors.New("invalid point")
372	}
373
374	if len(v.viewLines) == 0 {
375		return vx, vy, nil
376	}
377
378	if vy < len(v.viewLines) {
379		vline := v.viewLines[vy]
380		x = vline.linesX + vx
381		y = vline.linesY
382	} else {
383		vline := v.viewLines[len(v.viewLines)-1]
384		x = vx
385		y = vline.linesY + vy - len(v.viewLines) + 1
386	}
387
388	return x, y, nil
389}
390
391// Clear empties the view's internal buffer.
392func (v *View) Clear() {
393	v.tainted = true
394
395	v.lines = nil
396	v.viewLines = nil
397	v.readOffset = 0
398	v.clearRunes()
399}
400
401// clearRunes erases all the cells in the view.
402func (v *View) clearRunes() {
403	maxX, maxY := v.Size()
404	for x := 0; x < maxX; x++ {
405		for y := 0; y < maxY; y++ {
406			termbox.SetCell(v.x0+x+1, v.y0+y+1, ' ',
407				termbox.Attribute(v.FgColor), termbox.Attribute(v.BgColor))
408		}
409	}
410}
411
412// BufferLines returns the lines in the view's internal
413// buffer.
414func (v *View) BufferLines() []string {
415	lines := make([]string, len(v.lines))
416	for i, l := range v.lines {
417		str := lineType(l).String()
418		str = strings.Replace(str, "\x00", " ", -1)
419		lines[i] = str
420	}
421	return lines
422}
423
424// Buffer returns a string with the contents of the view's internal
425// buffer.
426func (v *View) Buffer() string {
427	str := ""
428	for _, l := range v.lines {
429		str += lineType(l).String() + "\n"
430	}
431	return strings.Replace(str, "\x00", " ", -1)
432}
433
434// ViewBufferLines returns the lines in the view's internal
435// buffer that is shown to the user.
436func (v *View) ViewBufferLines() []string {
437	lines := make([]string, len(v.viewLines))
438	for i, l := range v.viewLines {
439		str := lineType(l.line).String()
440		str = strings.Replace(str, "\x00", " ", -1)
441		lines[i] = str
442	}
443	return lines
444}
445
446// ViewBuffer returns a string with the contents of the view's buffer that is
447// shown to the user.
448func (v *View) ViewBuffer() string {
449	str := ""
450	for _, l := range v.viewLines {
451		str += lineType(l.line).String() + "\n"
452	}
453	return strings.Replace(str, "\x00", " ", -1)
454}
455
456// Line returns a string with the line of the view's internal buffer
457// at the position corresponding to the point (x, y).
458func (v *View) Line(y int) (string, error) {
459	_, y, err := v.realPosition(0, y)
460	if err != nil {
461		return "", err
462	}
463
464	if y < 0 || y >= len(v.lines) {
465		return "", errors.New("invalid point")
466	}
467
468	return lineType(v.lines[y]).String(), nil
469}
470
471// Word returns a string with the word of the view's internal buffer
472// at the position corresponding to the point (x, y).
473func (v *View) Word(x, y int) (string, error) {
474	x, y, err := v.realPosition(x, y)
475	if err != nil {
476		return "", err
477	}
478
479	if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
480		return "", errors.New("invalid point")
481	}
482
483	str := lineType(v.lines[y]).String()
484
485	nl := strings.LastIndexFunc(str[:x], indexFunc)
486	if nl == -1 {
487		nl = 0
488	} else {
489		nl = nl + 1
490	}
491	nr := strings.IndexFunc(str[x:], indexFunc)
492	if nr == -1 {
493		nr = len(str)
494	} else {
495		nr = nr + x
496	}
497	return string(str[nl:nr]), nil
498}
499
500// indexFunc allows to split lines by words taking into account spaces
501// and 0.
502func indexFunc(r rune) bool {
503	return r == ' ' || r == 0
504}