aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/awesome-gocui/gocui/view.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/awesome-gocui/gocui/view.go')
-rw-r--r--vendor/github.com/awesome-gocui/gocui/view.go800
1 files changed, 800 insertions, 0 deletions
diff --git a/vendor/github.com/awesome-gocui/gocui/view.go b/vendor/github.com/awesome-gocui/gocui/view.go
new file mode 100644
index 00000000..81f90603
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/view.go
@@ -0,0 +1,800 @@
+// Copyright 2014 The gocui Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gocui
+
+import (
+ "bytes"
+ "io"
+ "strings"
+ "sync"
+ "unicode/utf8"
+
+ "github.com/go-errors/errors"
+
+ "github.com/awesome-gocui/termbox-go"
+ "github.com/mattn/go-runewidth"
+)
+
+// Constants for overlapping edges
+const (
+ TOP = 1 // view is overlapping at top edge
+ BOTTOM = 2 // view is overlapping at bottom edge
+ LEFT = 4 // view is overlapping at left edge
+ RIGHT = 8 // view is overlapping at right edge
+)
+
+var (
+ // ErrInvalidPoint is returned when client passed invalid coordinates of a cell.
+ // Most likely client has passed negative coordinates of a cell.
+ ErrInvalidPoint = errors.New("invalid point")
+)
+
+// A View is a window. It maintains its own internal buffer and cursor
+// position.
+type View struct {
+ name string
+ x0, y0, x1, y1 int // left top right bottom
+ ox, oy int // view offsets
+ cx, cy int // cursor position
+ rx, ry int // Read() offsets
+ wx, wy int // Write() offsets
+ lines [][]cell // All the data
+
+ // readBuffer is used for storing unread bytes
+ readBuffer []byte
+
+ // tained is true if the viewLines must be updated
+ tainted bool
+
+ // internal representation of the view's buffer
+ viewLines []viewLine
+
+ // writeMutex protects locks the write process
+ writeMutex sync.Mutex
+
+ // ei is used to decode ESC sequences on Write
+ ei *escapeInterpreter
+
+ // Visible specifies whether the view is visible.
+ Visible bool
+
+ // BgColor and FgColor allow to configure the background and foreground
+ // colors of the View.
+ BgColor, FgColor Attribute
+
+ // SelBgColor and SelFgColor are used to configure the background and
+ // foreground colors of the selected line, when it is highlighted.
+ SelBgColor, SelFgColor Attribute
+
+ // If Editable is true, keystrokes will be added to the view's internal
+ // buffer at the cursor position.
+ Editable bool
+
+ // Editor allows to define the editor that manages the editing mode,
+ // including keybindings or cursor behaviour. DefaultEditor is used by
+ // default.
+ Editor Editor
+
+ // Overwrite enables or disables the overwrite mode of the view.
+ Overwrite bool
+
+ // If Highlight is true, Sel{Bg,Fg}Colors will be used
+ // for the line under the cursor position.
+ Highlight bool
+
+ // If Frame is true, a border will be drawn around the view.
+ Frame bool
+
+ // If Wrap is true, the content that is written to this View is
+ // automatically wrapped when it is longer than its width. If true the
+ // view's x-origin will be ignored.
+ Wrap bool
+
+ // If Autoscroll is true, the View will automatically scroll down when the
+ // text overflows. If true the view's y-origin will be ignored.
+ Autoscroll bool
+
+ // If Frame is true, Title allows to configure a title for the view.
+ Title string
+
+ // If Frame is true, Subtitle allows to configure a subtitle for the view.
+ Subtitle string
+
+ // If Mask is true, the View will display the mask instead of the real
+ // content
+ Mask rune
+
+ // Overlaps describes which edges are overlapping with another view's edges
+ Overlaps byte
+
+ // If HasLoader is true, the message will be appended with a spinning loader animation
+ HasLoader bool
+}
+
+type viewLine struct {
+ linesX, linesY int // coordinates relative to v.lines
+ line []cell
+}
+
+type cell struct {
+ chr rune
+ bgColor, fgColor Attribute
+}
+
+type lineType []cell
+
+// String returns a string from a given cell slice.
+func (l lineType) String() string {
+ str := ""
+ for _, c := range l {
+ str += string(c.chr)
+ }
+ return str
+}
+
+// newView returns a new View object.
+func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
+ v := &View{
+ name: name,
+ x0: x0,
+ y0: y0,
+ x1: x1,
+ y1: y1,
+ Visible: true,
+ Frame: true,
+ Editor: DefaultEditor,
+ tainted: true,
+ ei: newEscapeInterpreter(mode),
+ }
+ return v
+}
+
+// Dimensions returns the dimensions of the View
+func (v *View) Dimensions() (int, int, int, int) {
+ return v.x0, v.y0, v.x1, v.y1
+}
+
+// Size returns the number of visible columns and rows in the View.
+func (v *View) Size() (x, y int) {
+ return v.x1 - v.x0 - 1, v.y1 - v.y0 - 1
+}
+
+// Name returns the name of the view.
+func (v *View) Name() string {
+ return v.name
+}
+
+// setRune sets a rune at the given point relative to the view. It applies the
+// specified colors, taking into account if the cell must be highlighted. Also,
+// it checks if the position is valid.
+func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
+ maxX, maxY := v.Size()
+ if x < 0 || x >= maxX || y < 0 || y >= maxY {
+ return ErrInvalidPoint
+ }
+ var (
+ ry, rcy int
+ err error
+ )
+ if v.Highlight {
+ _, ry, err = v.realPosition(x, y)
+ if err != nil {
+ return err
+ }
+ _, rcy, err = v.realPosition(v.cx, v.cy)
+ if err != nil {
+ return err
+ }
+ }
+
+ if v.Mask != 0 {
+ fgColor = v.FgColor
+ bgColor = v.BgColor
+ ch = v.Mask
+ } else if v.Highlight && ry == rcy {
+ fgColor = fgColor | AttrBold
+ }
+
+ // Don't display NUL characters
+ if ch == 0 {
+ ch = ' '
+ }
+
+ termbox.SetCell(v.x0+x+1, v.y0+y+1, ch,
+ termbox.Attribute(fgColor), termbox.Attribute(bgColor))
+
+ return nil
+}
+
+// SetCursor sets the cursor position of the view at the given point,
+// relative to the view. It checks if the position is valid.
+func (v *View) SetCursor(x, y int) error {
+ maxX, maxY := v.Size()
+ if x < 0 || x >= maxX || y < 0 || y >= maxY {
+ return ErrInvalidPoint
+ }
+ v.cx = x
+ v.cy = y
+ return nil
+}
+
+// Cursor returns the cursor position of the view.
+func (v *View) Cursor() (x, y int) {
+ return v.cx, v.cy
+}
+
+// SetOrigin sets the origin position of the view's internal buffer,
+// so the buffer starts to be printed from this point, which means that
+// it is linked with the origin point of view. It can be used to
+// implement Horizontal and Vertical scrolling with just incrementing
+// or decrementing ox and oy.
+func (v *View) SetOrigin(x, y int) error {
+ if x < 0 || y < 0 {
+ return ErrInvalidPoint
+ }
+ v.ox = x
+ v.oy = y
+ return nil
+}
+
+// Origin returns the origin position of the view.
+func (v *View) Origin() (x, y int) {
+ return v.ox, v.oy
+}
+
+// SetWritePos sets the write position of the view's internal buffer.
+// So the next Write call would write directly to the specified position.
+func (v *View) SetWritePos(x, y int) error {
+ if x < 0 || y < 0 {
+ return ErrInvalidPoint
+ }
+ v.wx = x
+ v.wy = y
+ return nil
+}
+
+// WritePos returns the current write position of the view's internal buffer.
+func (v *View) WritePos() (x, y int) {
+ return v.wx, v.wy
+}
+
+// SetReadPos sets the read position of the view's internal buffer.
+// So the next Read call would read from the specified position.
+func (v *View) SetReadPos(x, y int) error {
+ if x < 0 || y < 0 {
+ return ErrInvalidPoint
+ }
+ v.readBuffer = nil
+ v.rx = x
+ v.ry = y
+ return nil
+}
+
+// ReadPos returns the current read position of the view's internal buffer.
+func (v *View) ReadPos() (x, y int) {
+ return v.rx, v.ry
+}
+
+// makeWriteable creates empty cells if required to make position (x, y) writeable.
+func (v *View) makeWriteable(x, y int) {
+ // TODO: make this more efficient
+
+ // line `y` must be index-able (that's why `<=`)
+ for len(v.lines) <= y {
+ if cap(v.lines) > len(v.lines) {
+ newLen := cap(v.lines)
+ if newLen > y {
+ newLen = y + 1
+ }
+ v.lines = v.lines[:newLen]
+ } else {
+ v.lines = append(v.lines, nil)
+ }
+ }
+ // cell `x` must not be index-able (that's why `<`)
+ // append should be used by `lines[y]` user if he wants to write beyond `x`
+ for len(v.lines[y]) < x {
+ if cap(v.lines[y]) > len(v.lines[y]) {
+ newLen := cap(v.lines[y])
+ if newLen > x {
+ newLen = x
+ }
+ v.lines[y] = v.lines[y][:newLen]
+ } else {
+ v.lines[y] = append(v.lines[y], cell{})
+ }
+ }
+}
+
+// writeCells copies []cell to specified location (x, y)
+// !!! caller MUST ensure that specified location (x, y) is writeable by calling makeWriteable
+func (v *View) writeCells(x, y int, cells []cell) {
+ var newLen int
+ // use maximum len available
+ line := v.lines[y][:cap(v.lines[y])]
+ maxCopy := len(line) - x
+ if maxCopy < len(cells) {
+ copy(line[x:], cells[:maxCopy])
+ line = append(line, cells[maxCopy:]...)
+ newLen = len(line)
+ } else { // maxCopy >= len(cells)
+ copy(line[x:], cells)
+ newLen = x + len(cells)
+ if newLen < len(v.lines[y]) {
+ newLen = len(v.lines[y])
+ }
+ }
+ v.lines[y] = line[:newLen]
+}
+
+// Write appends a byte slice into the view's internal buffer. Because
+// View implements the io.Writer interface, it can be passed as parameter
+// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must
+// be called to clear the view's buffer.
+func (v *View) Write(p []byte) (n int, err error) {
+ v.tainted = true
+ v.writeMutex.Lock()
+ v.makeWriteable(v.wx, v.wy)
+ v.writeRunes(bytes.Runes(p))
+ v.writeMutex.Unlock()
+
+ return len(p), nil
+}
+
+func (v *View) WriteRunes(p []rune) {
+ v.tainted = true
+
+ // Fill with empty cells, if writing outside current view buffer
+ v.makeWriteable(v.wx, v.wy)
+ v.writeRunes(p)
+}
+
+func (v *View) WriteString(s string) {
+ v.WriteRunes([]rune(s))
+}
+
+// writeRunes copies slice of runes into internal lines buffer.
+// caller must make sure that writing position is accessable.
+func (v *View) writeRunes(p []rune) {
+ for _, r := range p {
+ switch r {
+ case '\n':
+ v.wy++
+ if v.wy >= len(v.lines) {
+ v.lines = append(v.lines, nil)
+ }
+
+ fallthrough
+ // not valid in every OS, but making runtime OS checks in cycle is bad.
+ case '\r':
+ v.wx = 0
+ default:
+ cells := v.parseInput(r)
+ if cells == nil {
+ continue
+ }
+ v.writeCells(v.wx, v.wy, cells)
+ v.wx += len(cells)
+ }
+ }
+}
+
+// parseInput parses char by char the input written to the View. It returns nil
+// while processing ESC sequences. Otherwise, it returns a cell slice that
+// contains the processed data.
+func (v *View) parseInput(ch rune) []cell {
+ cells := []cell{}
+
+ isEscape, err := v.ei.parseOne(ch)
+ if err != nil {
+ for _, r := range v.ei.runes() {
+ c := cell{
+ fgColor: v.FgColor,
+ bgColor: v.BgColor,
+ chr: r,
+ }
+ cells = append(cells, c)
+ }
+ v.ei.reset()
+ } else {
+ if isEscape {
+ return nil
+ }
+ repeatCount := 1
+ if ch == '\t' {
+ ch = ' '
+ repeatCount = 4
+ }
+ for i := 0; i < repeatCount; i++ {
+ c := cell{
+ fgColor: v.ei.curFgColor,
+ bgColor: v.ei.curBgColor,
+ chr: ch,
+ }
+ cells = append(cells, c)
+ }
+ }
+
+ return cells
+}
+
+// Read reads data into p from the current reading position set by SetReadPos.
+// It returns the number of bytes read into p.
+// At EOF, err will be io.EOF.
+func (v *View) Read(p []byte) (n int, err error) {
+ buffer := make([]byte, utf8.UTFMax)
+ offset := 0
+ if v.readBuffer != nil {
+ copy(p, v.readBuffer)
+ if len(v.readBuffer) >= len(p) {
+ if len(v.readBuffer) > len(p) {
+ v.readBuffer = v.readBuffer[len(p):]
+ }
+ return len(p), nil
+ }
+ v.readBuffer = nil
+ }
+ for v.ry < len(v.lines) {
+ for v.rx < len(v.lines[v.ry]) {
+ count := utf8.EncodeRune(buffer, v.lines[v.ry][v.rx].chr)
+ copy(p[offset:], buffer[:count])
+ v.rx++
+ newOffset := offset + count
+ if newOffset >= len(p) {
+ if newOffset > len(p) {
+ v.readBuffer = buffer[newOffset-len(p):]
+ }
+ return len(p), nil
+ }
+ offset += count
+ }
+ v.rx = 0
+ v.ry++
+ }
+ return offset, io.EOF
+}
+
+// Rewind sets read and write pos to (0, 0).
+func (v *View) Rewind() {
+ if err := v.SetReadPos(0, 0); err != nil {
+ // SetReadPos returns error only if x and y are negative
+ // we are passing 0, 0, thus no error should occur.
+ panic(err)
+ }
+ if err := v.SetWritePos(0, 0); err != nil {
+ // SetWritePos returns error only if x and y are negative
+ // we are passing 0, 0, thus no error should occur.
+ panic(err)
+ }
+}
+
+// IsTainted tells us if the view is tainted
+func (v *View) IsTainted() bool {
+ return v.tainted
+}
+
+// draw re-draws the view's contents.
+func (v *View) draw() error {
+ if !v.Visible {
+ return nil
+ }
+
+ maxX, maxY := v.Size()
+
+ if v.Wrap {
+ if maxX == 0 {
+ return errors.New("X size of the view cannot be 0")
+ }
+ v.ox = 0
+ }
+ if v.tainted {
+ v.viewLines = nil
+ lines := v.lines
+ if v.HasLoader {
+ lines = v.loaderLines()
+ }
+ for i, line := range lines {
+ wrap := 0
+ if v.Wrap {
+ wrap = maxX
+ }
+
+ ls := lineWrap(line, wrap)
+ for j := range ls {
+ vline := viewLine{linesX: j, linesY: i, line: ls[j]}
+ v.viewLines = append(v.viewLines, vline)
+ }
+ }
+ if !v.HasLoader {
+ v.tainted = false
+ }
+ }
+
+ if v.Autoscroll && len(v.viewLines) > maxY {
+ v.oy = len(v.viewLines) - maxY
+ }
+ y := 0
+ for i, vline := range v.viewLines {
+ if i < v.oy {
+ continue
+ }
+ if y >= maxY {
+ break
+ }
+ x := 0
+ for j, c := range vline.line {
+ if j < v.ox {
+ continue
+ }
+ if x >= maxX {
+ break
+ }
+
+ fgColor := c.fgColor
+ if fgColor == ColorDefault {
+ fgColor = v.FgColor
+ }
+ bgColor := c.bgColor
+ if bgColor == ColorDefault {
+ bgColor = v.BgColor
+ }
+
+ if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil {
+ return err
+ }
+
+ if c.chr != 0 {
+ // If it is a rune, add rune width
+ x += runewidth.RuneWidth(c.chr)
+ } else {
+ // If it is NULL rune, add 1 to be able to use SetWritePos
+ // (runewidth.RuneWidth of space is 1)
+ x++
+ }
+ }
+ y++
+ }
+ return nil
+}
+
+// realPosition returns the position in the internal buffer corresponding to the
+// point (x, y) of the view.
+func (v *View) realPosition(vx, vy int) (x, y int, err error) {
+ vx = v.ox + vx
+ vy = v.oy + vy
+
+ if vx < 0 || vy < 0 {
+ return 0, 0, ErrInvalidPoint
+ }
+
+ if len(v.viewLines) == 0 {
+ return vx, vy, nil
+ }
+
+ if vy < len(v.viewLines) {
+ vline := v.viewLines[vy]
+ x = vline.linesX + vx
+ y = vline.linesY
+ } else {
+ vline := v.viewLines[len(v.viewLines)-1]
+ x = vx
+ y = vline.linesY + vy - len(v.viewLines) + 1
+ }
+
+ return x, y, nil
+}
+
+// Clear empties the view's internal buffer.
+// And resets reading and writing offsets.
+func (v *View) Clear() {
+ v.writeMutex.Lock()
+ v.Rewind()
+ v.tainted = true
+ v.ei.reset()
+ v.lines = nil
+ v.viewLines = nil
+ v.clearRunes()
+ v.writeMutex.Unlock()
+}
+
+// clearRunes erases all the cells in the view.
+func (v *View) clearRunes() {
+ maxX, maxY := v.Size()
+ for x := 0; x < maxX; x++ {
+ for y := 0; y < maxY; y++ {
+ termbox.SetCell(v.x0+x+1, v.y0+y+1, ' ',
+ termbox.Attribute(v.FgColor), termbox.Attribute(v.BgColor))
+ }
+ }
+}
+
+// BufferLines returns the lines in the view's internal
+// buffer.
+func (v *View) BufferLines() []string {
+ lines := make([]string, len(v.lines))
+ for i, l := range v.lines {
+ str := lineType(l).String()
+ str = strings.Replace(str, "\x00", " ", -1)
+ lines[i] = str
+ }
+ return lines
+}
+
+// Buffer returns a string with the contents of the view's internal
+// buffer.
+func (v *View) Buffer() string {
+ return linesToString(v.lines)
+}
+
+// ViewBufferLines returns the lines in the view's internal
+// buffer that is shown to the user.
+func (v *View) ViewBufferLines() []string {
+ lines := make([]string, len(v.viewLines))
+ for i, l := range v.viewLines {
+ str := lineType(l.line).String()
+ str = strings.Replace(str, "\x00", " ", -1)
+ lines[i] = str
+ }
+ return lines
+}
+
+// LinesHeight is the count of view lines (i.e. lines excluding wrapping)
+func (v *View) LinesHeight() int {
+ return len(v.lines)
+}
+
+// ViewLinesHeight is the count of view lines (i.e. lines including wrapping)
+func (v *View) ViewLinesHeight() int {
+ return len(v.viewLines)
+}
+
+// ViewBuffer returns a string with the contents of the view's buffer that is
+// shown to the user.
+func (v *View) ViewBuffer() string {
+ lines := make([][]cell, len(v.viewLines))
+ for i := range v.viewLines {
+ lines[i] = v.viewLines[i].line
+ }
+
+ return linesToString(lines)
+}
+
+// Line returns a string with the line of the view's internal buffer
+// at the position corresponding to the point (x, y).
+func (v *View) Line(y int) (string, error) {
+ _, y, err := v.realPosition(0, y)
+ if err != nil {
+ return "", err
+ }
+
+ if y < 0 || y >= len(v.lines) {
+ return "", ErrInvalidPoint
+ }
+
+ return lineType(v.lines[y]).String(), nil
+}
+
+// Word returns a string with the word of the view's internal buffer
+// at the position corresponding to the point (x, y).
+func (v *View) Word(x, y int) (string, error) {
+ x, y, err := v.realPosition(x, y)
+ if err != nil {
+ return "", err
+ }
+
+ if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
+ return "", ErrInvalidPoint
+ }
+
+ str := lineType(v.lines[y]).String()
+
+ nl := strings.LastIndexFunc(str[:x], indexFunc)
+ if nl == -1 {
+ nl = 0
+ } else {
+ nl = nl + 1
+ }
+ nr := strings.IndexFunc(str[x:], indexFunc)
+ if nr == -1 {
+ nr = len(str)
+ } else {
+ nr = nr + x
+ }
+ return string(str[nl:nr]), nil
+}
+
+// indexFunc allows to split lines by words taking into account spaces
+// and 0.
+func indexFunc(r rune) bool {
+ return r == ' ' || r == 0
+}
+
+// SetLine changes the contents of an existing line.
+func (v *View) SetLine(y int, text string) error {
+ if y < 0 || y >= len(v.lines) {
+ err := ErrInvalidPoint
+ return err
+ }
+
+ v.tainted = true
+ line := make([]cell, 0)
+ for _, r := range text {
+ c := v.parseInput(r)
+ line = append(line, c...)
+ }
+ v.lines[y] = line
+ return nil
+}
+
+// SetHighlight toggles highlighting of separate lines, for custom lists
+// or multiple selection in views.
+func (v *View) SetHighlight(y int, on bool) error {
+ if y < 0 || y >= len(v.lines) {
+ err := ErrInvalidPoint
+ return err
+ }
+
+ line := v.lines[y]
+ cells := make([]cell, 0)
+ for _, c := range line {
+ if on {
+ c.bgColor = v.SelBgColor
+ c.fgColor = v.SelFgColor
+ } else {
+ c.bgColor = v.BgColor
+ c.fgColor = v.FgColor
+ }
+ cells = append(cells, c)
+ }
+ v.tainted = true
+ v.lines[y] = cells
+ return nil
+}
+
+func lineWidth(line []cell) (n int) {
+ for i := range line {
+ n += runewidth.RuneWidth(line[i].chr)
+ }
+
+ return
+}
+
+func lineWrap(line []cell, columns int) [][]cell {
+ if columns == 0 {
+ return [][]cell{line}
+ }
+
+ var n int
+ var offset int
+ lines := make([][]cell, 0, 1)
+ for i := range line {
+ rw := runewidth.RuneWidth(line[i].chr)
+ n += rw
+ if n > columns {
+ n = rw
+ lines = append(lines, line[offset:i])
+ offset = i
+ }
+ }
+
+ lines = append(lines, line[offset:])
+ return lines
+}
+
+func linesToString(lines [][]cell) string {
+ str := make([]string, len(lines))
+ for i := range lines {
+ rns := make([]rune, 0, len(lines[i]))
+ line := lineType(lines[i]).String()
+ for _, c := range line {
+ if c != '\x00' {
+ rns = append(rns, c)
+ }
+ }
+ str[i] = string(rns)
+ }
+
+ return strings.Join(str, "\n")
+}