From bb9168f98a9dd50a7215652ab77a1c46615064cd Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Mon, 30 Jul 2018 18:11:53 +0200 Subject: vendor gocui --- vendor/github.com/jroimartin/gocui/.gitignore | 1 + vendor/github.com/jroimartin/gocui/AUTHORS | 24 + vendor/github.com/jroimartin/gocui/LICENSE | 23 + vendor/github.com/jroimartin/gocui/README.md | 91 ++++ vendor/github.com/jroimartin/gocui/attribute.go | 32 ++ vendor/github.com/jroimartin/gocui/doc.go | 116 ++++ vendor/github.com/jroimartin/gocui/edit.go | 341 ++++++++++++ vendor/github.com/jroimartin/gocui/escape.go | 168 ++++++ vendor/github.com/jroimartin/gocui/gui.go | 646 +++++++++++++++++++++++ vendor/github.com/jroimartin/gocui/keybinding.go | 141 +++++ vendor/github.com/jroimartin/gocui/view.go | 474 +++++++++++++++++ 11 files changed, 2057 insertions(+) create mode 100644 vendor/github.com/jroimartin/gocui/.gitignore create mode 100644 vendor/github.com/jroimartin/gocui/AUTHORS create mode 100644 vendor/github.com/jroimartin/gocui/LICENSE create mode 100644 vendor/github.com/jroimartin/gocui/README.md create mode 100644 vendor/github.com/jroimartin/gocui/attribute.go create mode 100644 vendor/github.com/jroimartin/gocui/doc.go create mode 100644 vendor/github.com/jroimartin/gocui/edit.go create mode 100644 vendor/github.com/jroimartin/gocui/escape.go create mode 100644 vendor/github.com/jroimartin/gocui/gui.go create mode 100644 vendor/github.com/jroimartin/gocui/keybinding.go create mode 100644 vendor/github.com/jroimartin/gocui/view.go (limited to 'vendor/github.com/jroimartin/gocui') diff --git a/vendor/github.com/jroimartin/gocui/.gitignore b/vendor/github.com/jroimartin/gocui/.gitignore new file mode 100644 index 00000000..1377554e --- /dev/null +++ b/vendor/github.com/jroimartin/gocui/.gitignore @@ -0,0 +1 @@ +*.swp diff --git a/vendor/github.com/jroimartin/gocui/AUTHORS b/vendor/github.com/jroimartin/gocui/AUTHORS new file mode 100644 index 00000000..acf97f47 --- /dev/null +++ b/vendor/github.com/jroimartin/gocui/AUTHORS @@ -0,0 +1,24 @@ +# This is the official list of gocui authors for copyright purposes. + +# Names should be added to this file as +# Name or Organization contribution +# Contribution +# The email address is not required for organizations. + +Roi Martin + Main developer + +Ryan Sullivan + Toggleable view frames + +Matthieu Rakotojaona + Wrapped views + +Harry Lawrence + Basic mouse support + +Danny Tylman + Masked views + +Frederik Deweerdt + Colored fonts diff --git a/vendor/github.com/jroimartin/gocui/LICENSE b/vendor/github.com/jroimartin/gocui/LICENSE new file mode 100644 index 00000000..8cb28215 --- /dev/null +++ b/vendor/github.com/jroimartin/gocui/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2014 The gocui Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the gocui Authors nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/jroimartin/gocui/README.md b/vendor/github.com/jroimartin/gocui/README.md new file mode 100644 index 00000000..d2e5065f --- /dev/null +++ b/vendor/github.com/jroimartin/gocui/README.md @@ -0,0 +1,91 @@ +# GOCUI - Go Console User Interface + +[![GoDoc](https://godoc.org/github.com/jroimartin/gocui?status.svg)](https://godoc.org/github.com/jroimartin/gocui) + +Minimalist Go package aimed at creating Console User Interfaces. + +## Features + +* Minimalist API. +* Views (the "windows" in the GUI) implement the interface io.ReadWriter. +* Support for overlapping views. +* The GUI can be modified at runtime (concurrent-safe). +* Global and view-level keybindings. +* Mouse support. +* Colored text. +* Customizable edition mode. + +## Installation + +Execute: + +``` +$ go get github.com/jroimartin/gocui +``` + +## Documentation + +Execute: + +``` +$ go doc github.com/jroimartin/gocui +``` + +Or visit [godoc.org](https://godoc.org/github.com/jroimartin/gocui) to read it +online. + +## Example + +```go +package main + +import ( + "fmt" + "log" + + "github.com/jroimartin/gocui" +) + +func main() { + g := gocui.NewGui() + if err := g.Init(); err != nil { + log.Panicln(err) + } + defer g.Close() + + g.SetLayout(layout) + + if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { + log.Panicln(err) + } + + if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { + log.Panicln(err) + } +} + +func layout(g *gocui.Gui) error { + maxX, maxY := g.Size() + if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2); err != nil { + if err != gocui.ErrUnknownView { + return err + } + fmt.Fprintln(v, "Hello world!") + } + return nil +} + +func quit(g *gocui.Gui, v *gocui.View) error { + return gocui.ErrQuit +} +``` + +## Screenshots + +_examples/demo.go: + +![_examples/demo.go](https://cloud.githubusercontent.com/assets/1223476/5992750/720b84f0-aa36-11e4-88ec-296fa3247b52.png) + +_examples/dynamic.go: + +![_examples/dynamic.go](https://cloud.githubusercontent.com/assets/1223476/5992751/76ad5cc2-aa36-11e4-8204-6a90269db827.png) diff --git a/vendor/github.com/jroimartin/gocui/attribute.go b/vendor/github.com/jroimartin/gocui/attribute.go new file mode 100644 index 00000000..bad758a1 --- /dev/null +++ b/vendor/github.com/jroimartin/gocui/attribute.go @@ -0,0 +1,32 @@ +// 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 "github.com/nsf/termbox-go" + +// Attribute represents a terminal attribute, like color, font style, etc. They +// can be combined using bitwise OR (|). Note that it is not possible to +// combine multiple color attributes. +type Attribute termbox.Attribute + +// Color attributes. +const ( + ColorDefault Attribute = Attribute(termbox.ColorDefault) + ColorBlack = Attribute(termbox.ColorBlack) + ColorRed = Attribute(termbox.ColorRed) + ColorGreen = Attribute(termbox.ColorGreen) + ColorYellow = Attribute(termbox.ColorYellow) + ColorBlue = Attribute(termbox.ColorBlue) + ColorMagenta = Attribute(termbox.ColorMagenta) + ColorCyan = Attribute(termbox.ColorCyan) + ColorWhite = Attribute(termbox.ColorWhite) +) + +// Text style attributes. +const ( + AttrBold Attribute = Attribute(termbox.AttrBold) + AttrUnderline = Attribute(termbox.AttrUnderline) + AttrReverse = Attribute(termbox.AttrReverse) +) diff --git a/vendor/github.com/jroimartin/gocui/doc.go b/vendor/github.com/jroimartin/gocui/doc.go new file mode 100644 index 00000000..2c753c99 --- /dev/null +++ b/vendor/github.com/jroimartin/gocui/doc.go @@ -0,0 +1,116 @@ +// 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 allows to create console user interfaces. + +Create a new GUI: + + g := gocui.NewGui() + if err := g.Init(); err != nil { + // handle error + } + defer g.Close() + + // Set layout and key bindings + // ... + + if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { + // handle error + } + +Set the layout function: + + g.SetLayout(fcn) + +On each iteration of the GUI's main loop, the "layout function" is executed. +These layout functions can be used to set-up and update the application's main +views, being possible to freely switch between them. Also, it is important to +mention that a main loop iteration is executed on each reported event +(key-press, mouse event, window resize, etc). + +GUIs are composed by Views, you can think of it as buffers. Views implement the +io.ReadWriter interface, so you can just write to them if you want to modify +their content. The same is valid for reading. + +Create and initialize a view with absolute coordinates: + + if v, err := g.SetView("viewname", 2, 2, 22, 7); err != nil { + if err != gocui.ErrUnknownView { + // handle error + } + fmt.Fprintln(v, "This is a new view") + // ... + } + +Views can also be created using relative coordinates: + + maxX, maxY := g.Size() + if v, err := g.SetView("viewname", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil { + // ... + } + +Configure keybindings: + + if err := g.SetKeybinding("viewname", gocui.KeyEnter, gocui.ModNone, fcn); err != nil { + // handle error + } + +gocui implements full mouse support that can be enabled with: + + g.Mouse = true + +Mouse events are handled like any other keybinding: + + if err := g.SetKeybinding("viewname", gocui.MouseLeft, gocui.ModNone, fcn); err != nil { + // handle error + } + +IMPORTANT: Views can only be created, destroyed or updated in three ways: from +layout functions, from keybinding callbacks or via *Gui.Execute(). The reason +for this is that it allows gocui to be conccurent-safe. So, if you want to +update your GUI from a goroutine, you must use *Gui.Execute(). For example: + + g.Execute(func(g *gocui.Gui) error { + v, err := g.View("viewname") + if err != nil { + // handle error + } + v.Clear() + fmt.Fprintln(v, "Writing from different goroutines") + return nil + }) + +By default, gocui provides a basic edition mode. This mode can be extended +and customized creating a new Editor and assigning it to *Gui.Editor: + + type Editor interface { + Edit(v *View, key Key, ch rune, mod Modifier) + } + +DefaultEditor can be taken as example to create your own custom Editor: + + var DefaultEditor Editor = EditorFunc(simpleEditor) + + func simpleEditor(v *View, key Key, ch rune, mod Modifier) { + switch { + case ch != 0 && mod == 0: + v.EditWrite(ch) + case key == KeySpace: + v.EditWrite(' ') + case key == KeyBackspace || key == KeyBackspace2: + v.EditDelete(true) + // ... + } + } + +Colored text: + +Views allow to add colored text using ANSI colors. For example: + + fmt.Fprintln(v, "\x1b[0;31mHello world") + +For more information, see the examples in folder "_examples/". +*/ +package gocui diff --git a/vendor/github.com/jroimartin/gocui/edit.go b/vendor/github.com/jroimartin/gocui/edit.go new file mode 100644 index 00000000..66d84c8d --- /dev/null +++ b/vendor/github.com/jroimartin/gocui/edit.go @@ -0,0 +1,341 @@ +// 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 "errors" + +const maxInt = int(^uint(0) >> 1) + +// Editor interface must be satisfied by gocui editors. +type Editor interface { + Edit(v *View, key Key, ch rune, mod Modifier) +} + +// The EditorFunc type is an adapter to allow the use of ordinary functions as +// Editors. If f is a function with the appropriate signature, EditorFunc(f) +// is an Editor object that calls f. +type EditorFunc func(v *View, key Key, ch rune, mod Modifier) + +// Edit calls f(v, key, ch, mod) +func (f EditorFunc) Edit(v *View, key Key, ch rune, mod Modifier) { + f(v, key, ch, mod) +} + +// DefaultEditor is the default editor. +var DefaultEditor Editor = EditorFunc(simpleEditor) + +// simpleEditor is used as the default gocui editor. +func simpleEditor(v *View, key Key, ch rune, mod Modifier) { + switch { + case ch != 0 && mod == 0: + v.EditWrite(ch) + case key == KeySpace: + v.EditWrite(' ') + case key == KeyBackspace || key == KeyBackspace2: + v.EditDelete(true) + case key == KeyDelete: + v.EditDelete(false) + case key == KeyInsert: + v.Overwrite = !v.Overwrite + case key == KeyEnter: + v.EditNewLine() + case key == KeyArrowDown: + v.MoveCursor(0, 1, false) + case key == KeyArrowUp: + v.MoveCursor(0, -1, false) + case key == KeyArrowLeft: + v.MoveCursor(-1, 0, false) + case key == KeyArrowRight: + v.MoveCursor(1, 0, false) + } +} + +// EditWrite writes a rune at the cursor position. +func (v *View) EditWrite(ch rune) { + v.writeRune(v.cx, v.cy, ch) + v.MoveCursor(1, 0, true) +} + +// EditDelete deletes a rune at the cursor position. back determines the +// direction. +func (v *View) EditDelete(back bool) { + x, y := v.ox+v.cx, v.oy+v.cy + if y < 0 { + return + } else if y >= len(v.viewLines) { + v.MoveCursor(-1, 0, true) + return + } + + maxX, _ := v.Size() + if back { + if x == 0 { // start of the line + if y < 1 { + return + } + + var maxPrevWidth int + if v.Wrap { + maxPrevWidth = maxX + } else { + maxPrevWidth = maxInt + } + + if v.viewLines[y].linesX == 0 { // regular line + v.mergeLines(v.cy - 1) + if len(v.viewLines[y-1].line) < maxPrevWidth { + v.MoveCursor(-1, 0, true) + } + } else { // wrapped line + v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1) + v.MoveCursor(-1, 0, true) + } + } else { // middle/end of the line + v.deleteRune(v.cx-1, v.cy) + v.MoveCursor(-1, 0, true) + } + } else { + if x == len(v.viewLines[y].line) { // end of the line + v.mergeLines(v.cy) + } else { // start/middle of the line + v.deleteRune(v.cx, v.cy) + } + } +} + +// EditNewLine inserts a new line under the cursor. +func (v *View) EditNewLine() { + v.breakLine(v.cx, v.cy) + + y := v.oy + v.cy + if y >= len(v.viewLines) || (y >= 0 && y < len(v.viewLines) && + !(v.Wrap && v.cx == 0 && v.viewLines[y].linesX > 0)) { + // new line at the end of the buffer or + // cursor is not at the beginning of a wrapped line + v.ox = 0 + v.cx = 0 + v.MoveCursor(0, 1, true) + } +} + +// MoveCursor moves the cursor taking into account the width of the line/view, +// displacing the origin if necessary. +func (v *View) MoveCursor(dx, dy int, writeMode bool) { + maxX, maxY := v.Size() + cx, cy := v.cx+dx, v.cy+dy + x, y := v.ox+cx, v.oy+cy + + var curLineWidth, prevLineWidth int + // get the width of the current line + if writeMode { + if v.Wrap { + curLineWidth = maxX - 1 + } else { + curLineWidth = maxInt + } + } else { + if y >= 0 && y < len(v.viewLines) { + curLineWidth = len(v.viewLines[y].line) + if v.Wrap && curLineWidth >= maxX { + curLineWidth = maxX - 1 + } + } else { + curLineWidth = 0 + } + } + // get the width of the previous line + if y-1 >= 0 && y-1 < len(v.viewLines) { + prevLineWidth = len(v.viewLines[y-1].line) + } else { + prevLineWidth = 0 + } + + // adjust cursor's x position and view's x origin + if x > curLineWidth { // move to next line + if dx > 0 { // horizontal movement + if !v.Wrap { + v.ox = 0 + } + v.cx = 0 + cy++ + } else { // vertical movement + if curLineWidth > 0 { // move cursor to the EOL + if v.Wrap { + v.cx = curLineWidth + } else { + ncx := curLineWidth - v.ox + if ncx < 0 { + v.ox += ncx + if v.ox < 0 { + v.ox = 0 + } + v.cx = 0 + } else { + v.cx = ncx + } + } + } else { + if !v.Wrap { + v.ox = 0 + } + v.cx = 0 + } + } + } else if cx < 0 { + if !v.Wrap && v.ox > 0 { // move origin to the left + v.ox-- + } else { // move to previous line + if prevLineWidth > 0 { + if !v.Wrap { // set origin so the EOL is visible + nox := prevLineWidth - maxX + 1 + if nox < 0 { + v.ox = 0 + } else { + v.ox = nox + } + } + v.cx = prevLineWidth + } else { + if !v.Wrap { + v.ox = 0 + } + v.cx = 0 + } + cy-- + } + } else { // stay on the same line + if v.Wrap { + v.cx = cx + } else { + if cx >= maxX { + v.ox++ + } else { + v.cx = cx + } + } + } + + // adjust cursor's y position and view's y origin + if cy >= maxY { + v.oy++ + } else if cy < 0 { + if v.oy > 0 { + v.oy-- + } + } else { + v.cy = cy + } +} + +// writeRune writes a rune into the view's internal buffer, at the +// position corresponding to the point (x, y). The length of the internal +// buffer is increased if the point is out of bounds. Overwrite mode is +// governed by the value of View.overwrite. +func (v *View) writeRune(x, y int, ch rune) error { + v.tainted = true + + x, y, err := v.realPosition(x, y) + if err != nil { + return err + } + + if x < 0 || y < 0 { + return errors.New("invalid point") + } + + if y >= len(v.lines) { + s := make([][]cell, y-len(v.lines)+1) + v.lines = append(v.lines, s...) + } + + olen := len(v.lines[y]) + if x >= len(v.lines[y]) { + s := make([]cell, x-len(v.lines[y])+1) + v.lines[y] = append(v.lines[y], s...) + } + + c := cell{ + fgColor: v.FgColor, + bgColor: v.BgColor, + } + if !v.Overwrite || (v.Overwrite && x >= olen-1) { + c.chr = '\x00' + v.lines[y] = append(v.lines[y], c) + copy(v.lines[y][x+1:], v.lines[y][x:]) + } + c.chr = ch + v.lines[y][x] = c + return nil +} + +// deleteRune removes a rune from the view's internal buffer, at the +// position corresponding to the point (x, y). +func (v *View) deleteRune(x, y int) error { + v.tainted = true + + 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 errors.New("invalid point") + } + v.lines[y] = append(v.lines[y][:x], v.lines[y][x+1:]...) + return nil +} + +// mergeLines merges the lines "y" and "y+1" if possible. +func (v *View) mergeLines(y int) error { + v.tainted = true + + _, y, err := v.realPosition(0, y) + if err != nil { + return err + } + + if y < 0 || y >= len(v.lines) { + return errors.New("invalid point") + } + + if y < len(v.lines)-1 { // otherwise we don't need to merge anything + v.lines[y] = append(v.lines[y], v.lines[y+1]...) + v.lines = append(v.lines[:y+1], v.lines[y+2:]...) + } + return nil +} + +// breakLine breaks a line of the internal buffer at the position corresponding +// to the point (x, y). +func (v *View) breakLine(x, y int) error { + v.tainted = true + + x, y, err := v.realPosition(x, y) + if err != nil { + return err + } + + if y < 0 || y >= len(v.lines) { + return errors.New("invalid point") + } + + var left, right []cell + if x < len(v.lines[y]) { // break line + left = make([]cell, len(v.lines[y][:x])) + copy(left, v.lines[y][:x]) + right = make([]cell, len(v.lines[y][x:])) + copy(right, v.lines[y][x:]) + } else { // new empty line + left = v.lines[y] + } + + lines := make([][]cell, len(v.lines)+1) + lines[y] = left + lines[y+1] = right + copy(lines, v.lines[:y]) + copy(lines[y+2:], v.lines[y+1:]) + v.lines = lines + return nil +} diff --git a/vendor/github.com/jroimartin/gocui/escape.go b/vendor/github.com/jroimartin/gocui/escape.go new file mode 100644 index 00000000..07871d52 --- /dev/null +++ b/vendor/github.com/jroimartin/gocui/escape.go @@ -0,0 +1,168 @@ +// 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 ( + "errors" + "strconv" +) + +type escapeInterpreter struct { + state escapeState + curch rune + csiParam []string + curFgColor, curBgColor Attribute +} + +type escapeState int + +const ( + stateNone escapeState = iota + stateEscape + stateCSI + stateParams +) + +var ( + errNotCSI = errors.New("Not a CSI escape sequence") + errCSINotANumber = errors.New("CSI escape sequence was expecting a number or a ;") + errCSIParseError = errors.New("CSI escape sequence parsing error") + errCSITooLong = errors.New("CSI escape sequence is too long") +) + +// runes in case of error will output the non-parsed runes as a string. +func (ei *escapeInterpreter) runes() []rune { + switch ei.state { + case stateNone: + return []rune{0x1b} + case stateEscape: + return []rune{0x1b, ei.curch} + case stateCSI: + return []rune{0x1b, '[', ei.curch} + case stateParams: + ret := []rune{0x1b, '['} + for _, s := range ei.csiParam { + ret = append(ret, []rune(s)...) + ret = append(ret, ';') + } + return append(ret, ei.curch) + } + return nil +} + +// newEscapeInterpreter returns an escapeInterpreter that will be able to parse +// terminal escape sequences. +func newEscapeInterpreter() *escapeInterpreter { + ei := &escapeInterpreter{ + state: stateNone, + curFgColor: ColorDefault, + curBgColor: ColorDefault, + } + return ei +} + +// reset sets the escapeInterpreter in initial state. +func (ei *escapeInterpreter) reset() { + ei.state = stateNone + ei.curFgColor = ColorDefault + ei.curBgColor = ColorDefault + ei.csiParam = nil +} + +// paramToColor returns an attribute given a terminfo coloring. +func paramToColor(p int) Attribute { + switch p { + case 0: + return ColorBlack + case 1: + return ColorRed + case 2: + return ColorGreen + case 3: + return ColorYellow + case 4: + return ColorBlue + case 5: + return ColorMagenta + case 6: + return ColorCyan + case 7: + return ColorWhite + } + return ColorDefault +} + +// parseOne parses a rune. If isEscape is true, it means that the rune is part +// of an escape sequence, and as such should not be printed verbatim. Otherwise, +// it's not an escape sequence. +func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) { + // Sanity checks to make sure we're not parsing something totally bogus. + if len(ei.csiParam) > 20 { + return false, errCSITooLong + } + if len(ei.csiParam) > 0 && len(ei.csiParam[len(ei.csiParam)-1]) > 255 { + return false, errCSITooLong + } + ei.curch = ch + switch ei.state { + case stateNone: + if ch == 0x1b { + ei.state = stateEscape + return true, nil + } + return false, nil + case stateEscape: + if ch == '[' { + ei.state = stateCSI + return true, nil + } + return false, errNotCSI + case stateCSI: + if ch >= '0' && ch <= '9' { + ei.state = stateParams + ei.csiParam = append(ei.csiParam, string(ch)) + return true, nil + } + return false, errCSINotANumber + case stateParams: + switch { + case ch >= '0' && ch <= '9': + ei.csiParam[len(ei.csiParam)-1] += string(ch) + return true, nil + case ch == ';': + ei.csiParam = append(ei.csiParam, "") + return true, nil + case ch == 'm': + if len(ei.csiParam) < 1 { + return false, errCSIParseError + } + for _, param := range ei.csiParam { + p, err := strconv.Atoi(param) + if err != nil { + return false, errCSIParseError + } + switch { + case p >= 30 && p <= 37: + ei.curFgColor = paramToColor(p - 30) + case p >= 40 && p <= 47: + ei.curBgColor = paramToColor(p - 40) + case p == 1: + ei.curFgColor |= AttrBold + case p == 4: + ei.curFgColor |= AttrUnderline + case p == 7: + ei.curFgColor |= AttrReverse + case p == 0 || p == 39: + ei.curFgColor = ColorDefault + ei.curBgColor = ColorDefault + } + } + ei.state = stateNone + ei.csiParam = nil + return true, nil + } + } + return false, nil +} diff --git a/vendor/github.com/jroimartin/gocui/gui.go b/vendor/github.com/jroimartin/gocui/gui.go new file mode 100644 index 00000000..39579dc4 --- /dev/null +++ b/vendor/github.com/jroimartin/gocui/gui.go @@ -0,0 +1,646 @@ +// 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 ( + "errors" + + "github.com/nsf/termbox-go" +) + +// Handler represents a handler that can be used to update or modify the GUI. +type Handler func(*Gui) error + +// userEvent represents an event triggered by the user. +type userEvent struct { + h Handler +} + +var ( + // ErrQuit is used to decide if the MainLoop finished successfully. + ErrQuit = errors.New("quit") + + // ErrUnknownView allows to assert if a View must be initialized. + ErrUnknownView = errors.New("unknown view") +) + +// Gui represents the whole User Interface, including the views, layouts +// and keybindings. +type Gui struct { + tbEvents chan termbox.Event + userEvents chan userEvent + views []*View + currentView *View + layout Handler + keybindings []*keybinding + maxX, maxY int + + // BgColor and FgColor allow to configure the background and foreground + // colors of the GUI. + 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 Cursor is true then the cursor is enabled. + Cursor bool + + // If Mouse is true then mouse events will be enabled. + Mouse bool + + // If InputEsc is true, when ESC sequence is in the buffer and it doesn't + // match any known sequence, ESC means KeyEsc. + InputEsc bool + + // Editor allows to define the editor that manages the edition mode, + // including keybindings or cursor behaviour. DefaultEditor is used by + // default. + Editor Editor +} + +// NewGui returns a new Gui object. +func NewGui() *Gui { + return &Gui{} +} + +// Init initializes the library. This function must be called before +// any other functions. +func (g *Gui) Init() error { + if err := termbox.Init(); err != nil { + return err + } + g.tbEvents = make(chan termbox.Event, 20) + g.userEvents = make(chan userEvent, 20) + g.maxX, g.maxY = termbox.Size() + g.BgColor = ColorBlack + g.FgColor = ColorWhite + g.Editor = DefaultEditor + return nil +} + +// Close finalizes the library. It should be called after a successful +// initialization and when gocui is not needed anymore. +func (g *Gui) Close() { + termbox.Close() +} + +// Size returns the terminal's size. +func (g *Gui) Size() (x, y int) { + return g.maxX, g.maxY +} + +// SetRune writes a rune at the given point, relative to the top-left +// corner of the terminal. It checks if the position is valid and applies +// the gui's colors. +func (g *Gui) SetRune(x, y int, ch rune) error { + if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY { + return errors.New("invalid point") + } + termbox.SetCell(x, y, ch, termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor)) + return nil +} + +// Rune returns the rune contained in the cell at the given position. +// It checks if the position is valid. +func (g *Gui) Rune(x, y int) (rune, error) { + if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY { + return ' ', errors.New("invalid point") + } + c := termbox.CellBuffer()[y*g.maxX+x] + return c.Ch, nil +} + +// SetView creates a new view with its top-left corner at (x0, y0) +// and the bottom-right one at (x1, y1). If a view with the same name +// already exists, its dimensions are updated; otherwise, the error +// ErrUnknownView is returned, which allows to assert if the View must +// be initialized. It checks if the position is valid. +func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) { + if x0 >= x1 || y0 >= y1 { + return nil, errors.New("invalid dimensions") + } + if name == "" { + return nil, errors.New("invalid name") + } + + if v, err := g.View(name); err == nil { + v.x0 = x0 + v.y0 = y0 + v.x1 = x1 + v.y1 = y1 + v.tainted = true + return v, nil + } + + v := newView(name, x0, y0, x1, y1) + v.BgColor, v.FgColor = g.BgColor, g.FgColor + v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor + g.views = append(g.views, v) + return v, ErrUnknownView +} + +// SetViewOnTop sets the given view on top of the existing ones. +func (g *Gui) SetViewOnTop(name string) (*View, error) { + for i, v := range g.views { + if v.name == name { + s := append(g.views[:i], g.views[i+1:]...) + g.views = append(s, v) + return v, nil + } + } + return nil, ErrUnknownView +} + +// View returns a pointer to the view with the given name, or error +// ErrUnknownView if a view with that name does not exist. +func (g *Gui) View(name string) (*View, error) { + for _, v := range g.views { + if v.name == name { + return v, nil + } + } + return nil, ErrUnknownView +} + +// ViewByPosition returns a pointer to a view matching the given position, or +// error ErrUnknownView if a view in that position does not exist. +func (g *Gui) ViewByPosition(x, y int) (*View, error) { + for _, v := range g.views { + if x > v.x0 && x < v.x1 && y > v.y0 && y < v.y1 { + return v, nil + } + } + return nil, ErrUnknownView +} + +// ViewPosition returns the coordinates of the view with the given name, or +// error ErrUnknownView if a view with that name does not exist. +func (g *Gui) ViewPosition(name string) (x0, y0, x1, y1 int, err error) { + for _, v := range g.views { + if v.name == name { + return v.x0, v.y0, v.x1, v.y1, nil + } + } + return 0, 0, 0, 0, ErrUnknownView +} + +// DeleteView deletes a view by name. +func (g *Gui) DeleteView(name string) error { + for i, v := range g.views { + if v.name == name { + g.views = append(g.views[:i], g.views[i+1:]...) + return nil + } + } + return ErrUnknownView +} + +// SetCurrentView gives the focus to a given view. +func (g *Gui) SetCurrentView(name string) error { + for _, v := range g.views { + if v.name == name { + g.currentView = v + return nil + } + } + return ErrUnknownView +} + +// CurrentView returns the currently focused view, or nil if no view +// owns the focus. +func (g *Gui) CurrentView() *View { + return g.currentView +} + +// SetKeybinding creates a new keybinding. If viewname equals to "" +// (empty string) then the keybinding will apply to all views. key must +// be a rune or a Key. +func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, h KeybindingHandler) error { + var kb *keybinding + + k, ch, err := getKey(key) + if err != nil { + return err + } + kb = newKeybinding(viewname, k, ch, mod, h) + g.keybindings = append(g.keybindings, kb) + return nil +} + +// DeleteKeybinding deletes a keybinding. +func (g *Gui) DeleteKeybinding(viewname string, key interface{}, mod Modifier) error { + k, ch, err := getKey(key) + if err != nil { + return err + } + + for i, kb := range g.keybindings { + if kb.viewName == viewname && kb.ch == ch && kb.key == k && kb.mod == mod { + g.keybindings = append(g.keybindings[:i], g.keybindings[i+1:]...) + return nil + } + } + return errors.New("keybinding not found") +} + +// DeleteKeybindings deletes all keybindings of view. +func (g *Gui) DeleteKeybindings(viewname string) { + var s []*keybinding + for _, kb := range g.keybindings { + if kb.viewName != viewname { + s = append(s, kb) + } + } + g.keybindings = s +} + +// getKey takes an empty interface with a key and returns the corresponding +// typed Key or rune. +func getKey(key interface{}) (Key, rune, error) { + switch t := key.(type) { + case Key: + return t, 0, nil + case rune: + return 0, t, nil + default: + return 0, 0, errors.New("unknown type") + } +} + +// Execute executes the given handler. This function can be called safely from +// a goroutine in order to update the GUI. It is important to note that it +// won't be executed immediately, instead it will be added to the user events +// queue. +func (g *Gui) Execute(h Handler) { + go func() { g.userEvents <- userEvent{h: h} }() +} + +// SetLayout sets the current layout. A layout is a function that +// will be called every time the gui is redrawn, it must contain +// the base views and its initializations. +func (g *Gui) SetLayout(layout Handler) { + g.layout = layout + g.currentView = nil + g.views = nil + go func() { g.tbEvents <- termbox.Event{Type: termbox.EventResize} }() +} + +// MainLoop runs the main loop until an error is returned. A successful +// finish should return ErrQuit. +func (g *Gui) MainLoop() error { + go func() { + for { + g.tbEvents <- termbox.PollEvent() + } + }() + + inputMode := termbox.InputAlt + if g.InputEsc { + inputMode = termbox.InputEsc + } + if g.Mouse { + inputMode |= termbox.InputMouse + } + termbox.SetInputMode(inputMode) + + if err := g.flush(); err != nil { + return err + } + for { + select { + case ev := <-g.tbEvents: + if err := g.handleEvent(&ev); err != nil { + return err + } + case ev := <-g.userEvents: + if err := ev.h(g); err != nil { + return err + } + } + if err := g.consumeevents(); err != nil { + return err + } + if err := g.flush(); err != nil { + return err + } + } +} + +// consumeevents handles the remaining events in the events pool. +func (g *Gui) consumeevents() error { + for { + select { + case ev := <-g.tbEvents: + if err := g.handleEvent(&ev); err != nil { + return err + } + case ev := <-g.userEvents: + if err := ev.h(g); err != nil { + return err + } + default: + return nil + } + } +} + +// handleEvent handles an event, based on its type (key-press, error, +// etc.) +func (g *Gui) handleEvent(ev *termbox.Event) error { + switch ev.Type { + case termbox.EventKey, termbox.EventMouse: + return g.onKey(ev) + case termbox.EventError: + return ev.Err + default: + return nil + } +} + +// flush updates the gui, re-drawing frames and buffers. +func (g *Gui) flush() error { + if g.layout == nil { + return errors.New("Null layout") + } + + termbox.Clear(termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor)) + + maxX, maxY := termbox.Size() + // if GUI's size has changed, we need to redraw all views + if maxX != g.maxX || maxY != g.maxY { + for _, v := range g.views { + v.tainted = true + } + } + g.maxX, g.maxY = maxX, maxY + + if err := g.layout(g); err != nil { + return err + } + for _, v := range g.views { + if v.Frame { + if err := g.drawFrame(v); err != nil { + return err + } + if err := g.drawCorners(v); err != nil { + return err + } + if v.Title != "" { + if err := g.drawTitle(v); err != nil { + return err + } + } + } + + if err := g.draw(v); err != nil { + return err + } + } + if err := g.drawIntersections(); err != nil { + return err + } + termbox.Flush() + return nil + +} + +// drawFrame draws the horizontal and vertical edges of a view. +func (g *Gui) drawFrame(v *View) error { + for x := v.x0 + 1; x < v.x1 && x < g.maxX; x++ { + if x < 0 { + continue + } + if v.y0 > -1 && v.y0 < g.maxY { + if err := g.SetRune(x, v.y0, '─'); err != nil { + return err + } + } + if v.y1 > -1 && v.y1 < g.maxY { + if err := g.SetRune(x, v.y1, '─'); err != nil { + return err + } + } + } + for y := v.y0 + 1; y < v.y1 && y < g.maxY; y++ { + if y < 0 { + continue + } + if v.x0 > -1 && v.x0 < g.maxX { + if err := g.SetRune(v.x0, y, '│'); err != nil { + return err + } + } + if v.x1 > -1 && v.x1 < g.maxX { + if err := g.SetRune(v.x1, y, '│'); err != nil { + return err + } + } + } + return nil +} + +// drawCorners draws the corners of the view. +func (g *Gui) drawCorners(v *View) error { + if v.x0 >= 0 && v.y0 >= 0 && v.x0 < g.maxX && v.y0 < g.maxY { + if err := g.SetRune(v.x0, v.y0, '┌'); err != nil { + return err + } + } + if v.x1 >= 0 && v.y0 >= 0 && v.x1 < g.maxX && v.y0 < g.maxY { + if err := g.SetRune(v.x1, v.y0, '┐'); err != nil { + return err + } + } + if v.x0 >= 0 && v.y1 >= 0 && v.x0 < g.maxX && v.y1 < g.maxY { + if err := g.SetRune(v.x0, v.y1, '└'); err != nil { + return err + } + } + if v.x1 >= 0 && v.y1 >= 0 && v.x1 < g.maxX && v.y1 < g.maxY { + if err := g.SetRune(v.x1, v.y1, '┘'); err != nil { + return err + } + } + return nil +} + +// drawTitle draws the title of the view. +func (g *Gui) drawTitle(v *View) error { + if v.y0 < 0 || v.y0 >= g.maxY { + return nil + } + + for i, ch := range v.Title { + x := v.x0 + i + 2 + if x < 0 { + continue + } else if x > v.x1-2 || x >= g.maxX { + break + } + if err := g.SetRune(x, v.y0, ch); err != nil { + return err + } + } + return nil +} + +// draw manages the cursor and calls the draw function of a view. +func (g *Gui) draw(v *View) error { + if g.Cursor { + if v := g.currentView; v != nil { + vMaxX, vMaxY := v.Size() + if v.cx < 0 { + v.cx = 0 + } else if v.cx >= vMaxX { + v.cx = vMaxX - 1 + } + if v.cy < 0 { + v.cy = 0 + } else if v.cy >= vMaxY { + v.cy = vMaxY - 1 + } + + gMaxX, gMaxY := g.Size() + cx, cy := v.x0+v.cx+1, v.y0+v.cy+1 + if cx >= 0 && cx < gMaxX && cy >= 0 && cy < gMaxY { + termbox.SetCursor(cx, cy) + } else { + termbox.HideCursor() + } + } + } else { + termbox.HideCursor() + } + + v.clearRunes() + if err := v.draw(); err != nil { + return err + } + return nil +} + +// drawIntersections draws the corners of each view, based on the type +// of the edges that converge at these points. +func (g *Gui) drawIntersections() error { + for _, v := range g.views { + if ch, ok := g.intersectionRune(v.x0, v.y0); ok { + if err := g.SetRune(v.x0, v.y0, ch); err != nil { + return err + } + } + if ch, ok := g.intersectionRune(v.x0, v.y1); ok { + if err := g.SetRune(v.x0, v.y1, ch); err != nil { + return err + } + } + if ch, ok := g.intersectionRune(v.x1, v.y0); ok { + if err := g.SetRune(v.x1, v.y0, ch); err != nil { + return err + } + } + if ch, ok := g.intersectionRune(v.x1, v.y1); ok { + if err := g.SetRune(v.x1, v.y1, ch); err != nil { + return err + } + } + } + return nil +} + +// intersectionRune returns the correct intersection rune at a given +// point. +func (g *Gui) intersectionRune(x, y int) (rune, bool) { + if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY { + return ' ', false + } + + chTop, _ := g.Rune(x, y-1) + top := verticalRune(chTop) + chBottom, _ := g.Rune(x, y+1) + bottom := verticalRune(chBottom) + chLeft, _ := g.Rune(x-1, y) + left := horizontalRune(chLeft) + chRight, _ := g.Rune(x+1, y) + right := horizontalRune(chRight) + + var ch rune + switch { + case top && bottom && left && right: + ch = '┼' + case top && bottom && !left && right: + ch = '├' + case top && bottom && left && !right: + ch = '┤' + case !top && bottom && left && right: + ch = '┬' + case top && !bottom && left && right: + ch = '┴' + default: + return ' ', false + } + return ch, true +} + +// verticalRune returns if the given character is a vertical rune. +func verticalRune(ch rune) bool { + if ch == '│' || ch == '┼' || ch == '├' || ch == '┤' { + return true + } + return false +} + +// verticalRune returns if the given character is a horizontal rune. +func horizontalRune(ch rune) bool { + if ch == '─' || ch == '┼' || ch == '┬' || ch == '┴' { + return true + } + return false +} + +// onKey manages key-press events. A keybinding handler is called when +// a key-press or mouse event satisfies a configured keybinding. Furthermore, +// currentView's internal buffer is modified if currentView.Editable is true. +func (g *Gui) onKey(ev *termbox.Event) error { + switch ev.Type { + case termbox.EventKey: + if err := g.execKeybindings(g.currentView, ev); err != nil { + return err + } + if g.currentView != nil && g.currentView.Editable && g.Editor != nil { + g.Editor.Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod)) + } + case termbox.EventMouse: + mx, my := ev.MouseX, ev.MouseY + v, err := g.ViewByPosition(mx, my) + if err != nil { + break + } + if err := v.SetCursor(mx-v.x0-1, my-v.y0-1); err != nil { + return err + } + if err := g.execKeybindings(v, ev); err != nil { + return err + } + } + + return nil +} + +// execKeybindings executes the keybinding handlers that match the passed view +// and event. +func (g *Gui) execKeybindings(v *View, ev *termbox.Event) error { + for _, kb := range g.keybindings { + if kb.h == nil { + continue + } + if kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) && kb.matchView(v) { + if err := kb.h(g, v); err != nil { + return err + } + } + } + return nil +} diff --git a/vendor/github.com/jroimartin/gocui/keybinding.go b/vendor/github.com/jroimartin/gocui/keybinding.go new file mode 100644 index 00000000..61516821 --- /dev/null +++ b/vendor/github.com/jroimartin/gocui/keybinding.go @@ -0,0 +1,141 @@ +// 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 "github.com/nsf/termbox-go" + +type ( + // Key represents special keys or keys combinations. + Key termbox.Key + + // Modifier allows to define special keys combinations. They can be used + // in combination with Keys or Runes when a new keybinding is defined. + Modifier termbox.Modifier + + // KeybindingHandler represents the handler linked to a specific + // keybindings. The handler is called when a key-press event satisfies a + // configured keybinding. + KeybindingHandler func(*Gui, *View) error +) + +// Special keys. +const ( + KeyF1 Key = Key(termbox.KeyF1) + KeyF2 = Key(termbox.KeyF2) + KeyF3 = Key(termbox.KeyF3) + KeyF4 = Key(termbox.KeyF4) + KeyF5 = Key(termbox.KeyF5) + KeyF6 = Key(termbox.KeyF6) + KeyF7 = Key(termbox.KeyF7) + KeyF8 = Key(termbox.KeyF8) + KeyF9 = Key(termbox.KeyF9) + KeyF10 = Key(termbox.KeyF10) + KeyF11 = Key(termbox.KeyF11) + KeyF12 = Key(termbox.KeyF12) + KeyInsert = Key(termbox.KeyInsert) + KeyDelete = Key(termbox.KeyDelete) + KeyHome = Key(termbox.KeyHome) + KeyEnd = Key(termbox.KeyEnd) + KeyPgup = Key(termbox.KeyPgup) + KeyPgdn = Key(termbox.KeyPgdn) + KeyArrowUp = Key(termbox.KeyArrowUp) + KeyArrowDown = Key(termbox.KeyArrowDown) + KeyArrowLeft = Key(termbox.KeyArrowLeft) + KeyArrowRight = Key(termbox.KeyArrowRight) + + MouseLeft = Key(termbox.MouseLeft) + MouseMiddle = Key(termbox.MouseMiddle) + MouseRight = Key(termbox.MouseRight) +) + +// Keys combinations. +const ( + KeyCtrlTilde Key = Key(termbox.KeyCtrlTilde) + KeyCtrl2 = Key(termbox.KeyCtrl2) + KeyCtrlSpace = Key(termbox.KeyCtrlSpace) + KeyCtrlA = Key(termbox.KeyCtrlA) + KeyCtrlB = Key(termbox.KeyCtrlB) + KeyCtrlC = Key(termbox.KeyCtrlC) + KeyCtrlD = Key(termbox.KeyCtrlD) + KeyCtrlE = Key(termbox.KeyCtrlE) + KeyCtrlF = Key(termbox.KeyCtrlF) + KeyCtrlG = Key(termbox.KeyCtrlG) + KeyBackspace = Key(termbox.KeyBackspace) + KeyCtrlH = Key(termbox.KeyCtrlH) + KeyTab = Key(termbox.KeyTab) + KeyCtrlI = Key(termbox.KeyCtrlI) + KeyCtrlJ = Key(termbox.KeyCtrlJ) + KeyCtrlK = Key(termbox.KeyCtrlK) + KeyCtrlL = Key(termbox.KeyCtrlL) + KeyEnter = Key(termbox.KeyEnter) + KeyCtrlM = Key(termbox.KeyCtrlM) + KeyCtrlN = Key(termbox.KeyCtrlN) + KeyCtrlO = Key(termbox.KeyCtrlO) + KeyCtrlP = Key(termbox.KeyCtrlP) + KeyCtrlQ = Key(termbox.KeyCtrlQ) + KeyCtrlR = Key(termbox.KeyCtrlR) + KeyCtrlS = Key(termbox.KeyCtrlS) + KeyCtrlT = Key(termbox.KeyCtrlT) + KeyCtrlU = Key(termbox.KeyCtrlU) + KeyCtrlV = Key(termbox.KeyCtrlV) + KeyCtrlW = Key(termbox.KeyCtrlW) + KeyCtrlX = Key(termbox.KeyCtrlX) + KeyCtrlY = Key(termbox.KeyCtrlY) + KeyCtrlZ = Key(termbox.KeyCtrlZ) + KeyEsc = Key(termbox.KeyEsc) + KeyCtrlLsqBracket = Key(termbox.KeyCtrlLsqBracket) + KeyCtrl3 = Key(termbox.KeyCtrl3) + KeyCtrl4 = Key(termbox.KeyCtrl4) + KeyCtrlBackslash = Key(termbox.KeyCtrlBackslash) + KeyCtrl5 = Key(termbox.KeyCtrl5) + KeyCtrlRsqBracket = Key(termbox.KeyCtrlRsqBracket) + KeyCtrl6 = Key(termbox.KeyCtrl6) + KeyCtrl7 = Key(termbox.KeyCtrl7) + KeyCtrlSlash = Key(termbox.KeyCtrlSlash) + KeyCtrlUnderscore = Key(termbox.KeyCtrlUnderscore) + KeySpace = Key(termbox.KeySpace) + KeyBackspace2 = Key(termbox.KeyBackspace2) + KeyCtrl8 = Key(termbox.KeyCtrl8) +) + +// Modifiers. +const ( + ModNone Modifier = Modifier(0) + ModAlt = Modifier(termbox.ModAlt) +) + +// Keybidings are used to link a given key-press event with a handler. +type keybinding struct { + viewName string + key Key + ch rune + mod Modifier + h KeybindingHandler +} + +// newKeybinding returns a new Keybinding object. +func newKeybinding(viewname string, key Key, ch rune, mod Modifier, h KeybindingHandler) (kb *keybinding) { + kb = &keybinding{ + viewName: viewname, + key: key, + ch: ch, + mod: mod, + h: h, + } + return kb +} + +// matchKeypress returns if the keybinding matches the keypress. +func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool { + return kb.key == key && kb.ch == ch && kb.mod == mod +} + +// matchView returns if the keybinding matches the current view. +func (kb *keybinding) matchView(v *View) bool { + if kb.viewName == "" { + return true + } + return v != nil && kb.viewName == v.name +} diff --git a/vendor/github.com/jroimartin/gocui/view.go b/vendor/github.com/jroimartin/gocui/view.go new file mode 100644 index 00000000..f8d86940 --- /dev/null +++ b/vendor/github.com/jroimartin/gocui/view.go @@ -0,0 +1,474 @@ +// 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" + "errors" + "io" + "strings" + + "github.com/nsf/termbox-go" +) + +// A View is a window. It maintains its own internal buffer and cursor +// position. +type View struct { + name string + x0, y0, x1, y1 int + ox, oy int + cx, cy int + lines [][]cell + readOffset int + readCache string + + tainted bool // marks if the viewBuffer must be updated + viewLines []viewLine // internal representation of the view's buffer + + ei *escapeInterpreter // used to decode ESC sequences on Write + + // 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 + + // 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 Mask is true, the View will display the mask instead of the real + // content + Mask rune +} + +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) *View { + v := &View{ + name: name, + x0: x0, + y0: y0, + x1: x1, + y1: y1, + Frame: true, + tainted: true, + ei: newEscapeInterpreter(), + } + return v +} + +// 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 errors.New("invalid point") + } + + 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 = v.SelFgColor + bgColor = v.SelBgColor + } + + 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 errors.New("invalid point") + } + 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 errors.New("invalid point") + } + 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 +} + +// 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 + + for _, ch := range bytes.Runes(p) { + switch ch { + case '\n': + v.lines = append(v.lines, nil) + case '\r': + nl := len(v.lines) + if nl > 0 { + v.lines[nl-1] = nil + } else { + v.lines = make([][]cell, 1) + } + default: + cells := v.parseInput(ch) + if cells == nil { + continue + } + + nl := len(v.lines) + if nl > 0 { + v.lines[nl-1] = append(v.lines[nl-1], cells...) + } else { + v.lines = append(v.lines, cells) + } + } + } + return len(p), nil +} + +// 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 + } + c := cell{ + fgColor: v.ei.curFgColor, + bgColor: v.ei.curBgColor, + chr: ch, + } + cells = append(cells, c) + } + + return cells +} + +// Read reads data into p. It returns the number of bytes read into p. +// At EOF, err will be io.EOF. Calling Read() after Rewind() makes the +// cache to be refreshed with the contents of the view. +func (v *View) Read(p []byte) (n int, err error) { + if v.readOffset == 0 { + v.readCache = v.Buffer() + } + if v.readOffset < len(v.readCache) { + n = copy(p, v.readCache[v.readOffset:]) + v.readOffset += n + } else { + err = io.EOF + } + return +} + +// Rewind sets the offset for the next Read to 0, which also refresh the +// read cache. +func (v *View) Rewind() { + v.readOffset = 0 +} + +// draw re-draws the view's contents. +func (v *View) draw() error { + 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 + for i, line := range v.lines { + if v.Wrap { + if len(line) <= maxX { + vline := viewLine{linesX: 0, linesY: i, line: line} + v.viewLines = append(v.viewLines, vline) + continue + } else { + vline := viewLine{linesX: 0, linesY: i, line: line[:maxX]} + v.viewLines = append(v.viewLines, vline) + } + // Append remaining lines + for n := maxX; n < len(line); n += maxX { + if len(line[n:]) <= maxX { + vline := viewLine{linesX: n, linesY: i, line: line[n:]} + v.viewLines = append(v.viewLines, vline) + } else { + vline := viewLine{linesX: n, linesY: i, line: line[n : n+maxX]} + v.viewLines = append(v.viewLines, vline) + } + } + } else { + vline := viewLine{linesX: 0, linesY: i, line: line} + v.viewLines = append(v.viewLines, vline) + } + } + 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 + } + 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, errors.New("invalid point") + } + + 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. +func (v *View) Clear() { + v.tainted = true + + v.lines = nil + v.clearRunes() +} + +// 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)) + } + } +} + +// Buffer returns a string with the contents of the view's internal +// buffer. +func (v *View) Buffer() string { + str := "" + for _, l := range v.lines { + str += lineType(l).String() + "\n" + } + return strings.Replace(str, "\x00", " ", -1) +} + +// ViewBuffer returns a string with the contents of the view's buffer that is +// shown to the user. +func (v *View) ViewBuffer() string { + str := "" + for _, l := range v.viewLines { + str += lineType(l.line).String() + "\n" + } + return strings.Replace(str, "\x00", " ", -1) +} + +// 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 "", errors.New("invalid point") + } + + 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 "", errors.New("invalid point") + } + + 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 +} -- cgit