aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/awesome-gocui/gocui
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2019-11-03 20:47:29 +0100
committerMichael Muré <batolettre@gmail.com>2019-11-03 20:47:29 +0100
commitcb8236c9c22007eb622b7219c58d3342f1f53d50 (patch)
treee232e5362486065d9b28bb55b9791690efb50c08 /vendor/github.com/awesome-gocui/gocui
parent163ea9c93306c387f84ff0b85c2d8fca4c01e449 (diff)
downloadgit-bug-cb8236c9c22007eb622b7219c58d3342f1f53d50.tar.gz
termui: migrate to awesome-gocui instead of the old fork I had
Diffstat (limited to 'vendor/github.com/awesome-gocui/gocui')
-rw-r--r--vendor/github.com/awesome-gocui/gocui/.gitignore1
-rw-r--r--vendor/github.com/awesome-gocui/gocui/AUTHORS30
-rw-r--r--vendor/github.com/awesome-gocui/gocui/CODE_OF_CONDUCT.md76
-rw-r--r--vendor/github.com/awesome-gocui/gocui/CONTRIBUTING.md33
-rw-r--r--vendor/github.com/awesome-gocui/gocui/LICENSE23
-rw-r--r--vendor/github.com/awesome-gocui/gocui/README.md135
-rw-r--r--vendor/github.com/awesome-gocui/gocui/attribute.go32
-rw-r--r--vendor/github.com/awesome-gocui/gocui/doc.go118
-rw-r--r--vendor/github.com/awesome-gocui/gocui/edit.go449
-rw-r--r--vendor/github.com/awesome-gocui/gocui/escape.go229
-rw-r--r--vendor/github.com/awesome-gocui/gocui/go.mod9
-rw-r--r--vendor/github.com/awesome-gocui/gocui/go.sum6
-rw-r--r--vendor/github.com/awesome-gocui/gocui/gui.go832
-rw-r--r--vendor/github.com/awesome-gocui/gocui/gui_others.go60
-rw-r--r--vendor/github.com/awesome-gocui/gocui/gui_windows.go53
-rw-r--r--vendor/github.com/awesome-gocui/gocui/keybinding.go285
-rw-r--r--vendor/github.com/awesome-gocui/gocui/loader.go46
-rw-r--r--vendor/github.com/awesome-gocui/gocui/view.go800
18 files changed, 3217 insertions, 0 deletions
diff --git a/vendor/github.com/awesome-gocui/gocui/.gitignore b/vendor/github.com/awesome-gocui/gocui/.gitignore
new file mode 100644
index 00000000..1377554e
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/.gitignore
@@ -0,0 +1 @@
+*.swp
diff --git a/vendor/github.com/awesome-gocui/gocui/AUTHORS b/vendor/github.com/awesome-gocui/gocui/AUTHORS
new file mode 100644
index 00000000..43ec4cec
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/AUTHORS
@@ -0,0 +1,30 @@
+# This is the official list of gocui authors for copyright purposes.
+
+# Names should be added to this file as
+# Name or Organization <email address> contribution
+# Contribution
+# The email address is not required for organizations.
+
+Roi Martin <jroi.martin@gmail.com>
+ Main developer
+
+Ryan Sullivan <kayoticsully@gmail.com>
+ Toggleable view frames
+
+Matthieu Rakotojaona <matthieu.rakotojaona@gmail.com>
+ Wrapped views
+
+Harry Lawrence <hazbo@gmx.com>
+ Basic mouse support
+
+Danny Tylman <dtylman@gmail.com>
+ Masked views
+
+Frederik Deweerdt <frederik.deweerdt@gmail.com>
+ Colored fonts
+
+Henri Koski <henri.t.koski@gmail.com>
+ Custom current view color
+
+Dustin Willis Webber <dustin.webber@gmail.com>
+ 256-colors output mode support
diff --git a/vendor/github.com/awesome-gocui/gocui/CODE_OF_CONDUCT.md b/vendor/github.com/awesome-gocui/gocui/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..1bdac055
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/CODE_OF_CONDUCT.md
@@ -0,0 +1,76 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at mkopenga@gmail.com. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
diff --git a/vendor/github.com/awesome-gocui/gocui/CONTRIBUTING.md b/vendor/github.com/awesome-gocui/gocui/CONTRIBUTING.md
new file mode 100644
index 00000000..b93e45b2
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+# Contributing
+
+Everyone is welcome to help make gocui better!
+
+When contributing to this repository, please first discuss the change you wish
+to make via issue, email, or any other method with the owners of this repository
+before making a change.
+
+## So all code changes happen through Pull Requests
+Pull requests are the best way to propose changes to the codebase. We actively
+welcome your pull requests:
+
+1. Fork the repo and create your branch from `master` with a name like `feature/contributors-guide`.
+2. If you've added code that should be tested, add tests.
+3. If you've added code that need documentation, update the documentation.
+4. Make sure your code follows the [effective go](https://golang.org/doc/effective_go.html) guidelines as much as possible.
+5. Be sure to test your modifications.
+6. Make sure your branch is up to date with the master branch.
+7. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
+8. Create that pull request!
+
+## Code of conduct
+Please note by participating in this project, you agree to abide by the [code of conduct].
+
+[code of conduct]: https://github.com/awesome-gocui/gocui/blob/master/CODE-OF-CONDUCT.md
+
+## Any contributions you make will be under the license indicated in the [license](LICENSE.md)
+In short, when you submit code changes, your submissions are understood to be
+under the same license as the rest of project. Feel free to contact the maintainers if that's a concern.
+
+## Report bugs using Github's [issues](https://github.com/awesome-gocui/gocui/issues)
+We use GitHub issues to track public bugs. Report a bug by [opening a new
+issue](https://github.com/awesome-gocui/gocui/issues/new); it's that easy! \ No newline at end of file
diff --git a/vendor/github.com/awesome-gocui/gocui/LICENSE b/vendor/github.com/awesome-gocui/gocui/LICENSE
new file mode 100644
index 00000000..8cb28215
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/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/awesome-gocui/gocui/README.md b/vendor/github.com/awesome-gocui/gocui/README.md
new file mode 100644
index 00000000..be212c58
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/README.md
@@ -0,0 +1,135 @@
+# GOCUI - Go Console User Interface
+[![CircleCI](https://circleci.com/gh/awesome-gocui/gocui/tree/master.svg?style=svg)](https://circleci.com/gh/awesome-gocui/gocui/tree/master)
+[![CodeCov](https://codecov.io/gh/awesome-gocui/gocui/branch/master/graph/badge.svg)](https://codecov.io/gh/awesome-gocui/gocui)
+[![Go Report Card](https://goreportcard.com/badge/github.com/awesome-gocui/gocui)](https://goreportcard.com/report/github.com/awesome-gocui/gocui)
+[![GolangCI](https://golangci.com/badges/github.com/awesome-gocui/gocui.svg)](https://golangci.com/badges/github.com/awesome-gocui/gocui.svg)
+[![GoDoc](https://godoc.org/github.com/awesome-gocui/gocui?status.svg)](https://godoc.org/github.com/awesome-gocui/gocui)
+![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/awesome-gocui/gocui.svg)
+
+Minimalist Go package aimed at creating Console User Interfaces.
+A community fork based on the amazing work of [jroimartin](https://github.com/jroimartin/gocui)
+
+## 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 editing mode.
+* Easy to build reusable widgets, complex layouts...
+
+## About fork
+
+This fork has many improvements over the original work from [jroimartin](https://github.com/jroimartin/gocui).
+
+* Better wide character support
+* Support for 1 Line height views
+* Better support for running in docker container
+* Customize frame colors
+* Improved code comments and quality
+* Many small improvements
+* Change Visibility of views
+
+For information about this org see: [awesome-gocui/about](https://github.com/awesome-gocui/about).
+
+## Installation
+
+Execute:
+
+```
+$ go get github.com/awesome-gocui/gocui
+```
+
+## Documentation
+
+Execute:
+
+```
+$ go doc github.com/awesome-gocui/gocui
+```
+
+Or visit [godoc.org](https://godoc.org/github.com/awesome-gocui/gocui) to read it
+online.
+
+## Example
+See the [_example](./_example/) folder for more examples
+
+```go
+package main
+
+import (
+ "fmt"
+ "log"
+
+ "github.com/awesome-gocui/gocui"
+)
+
+func main() {
+ g, err := gocui.NewGui(gocui.OutputNormal, false)
+ if err != nil {
+ log.Panicln(err)
+ }
+ defer g.Close()
+
+ g.SetManagerFunc(layout)
+
+ if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
+ log.Panicln(err)
+ }
+
+ if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) {
+ 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, 0); err != nil {
+ if !gocui.IsUnknownView(err) {
+ return err
+ }
+ fmt.Fprintln(v, "Hello world!")
+ if _, err := g.SetCurrentView("hello"); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func quit(g *gocui.Gui, v *gocui.View) error {
+ return gocui.ErrQuit
+}
+```
+
+## Screenshots
+
+![r2cui](https://cloud.githubusercontent.com/assets/1223476/19418932/63645052-93ce-11e6-867c-da5e97e37237.png)
+
+![_examples/demo.go](https://cloud.githubusercontent.com/assets/1223476/5992750/720b84f0-aa36-11e4-88ec-296fa3247b52.png)
+
+![_examples/dynamic.go](https://cloud.githubusercontent.com/assets/1223476/5992751/76ad5cc2-aa36-11e4-8204-6a90269db827.png)
+
+## Projects using gocui
+
+* [komanda-cli](https://github.com/mephux/komanda-cli): IRC Client For Developers.
+* [vuls](https://github.com/future-architect/vuls): Agentless vulnerability scanner for Linux/FreeBSD.
+* [wuzz](https://github.com/asciimoo/wuzz): Interactive cli tool for HTTP inspection.
+* [httplab](https://github.com/gchaincl/httplab): Interactive web server.
+* [domainr](https://github.com/MichaelThessel/domainr): Tool that checks the availability of domains based on keywords.
+* [gotime](https://github.com/nanohard/gotime): Time tracker for projects and tasks.
+* [claws](https://github.com/thehowl/claws): Interactive command line client for testing websockets.
+* [terminews](http://github.com/antavelos/terminews): Terminal based RSS reader.
+* [diagram](https://github.com/esimov/diagram): Tool to convert ascii arts into hand drawn diagrams.
+* [pody](https://github.com/JulienBreux/pody): CLI app to manage Pods in a Kubernetes cluster.
+* [kubexp](https://github.com/alitari/kubexp): Kubernetes client.
+* [kcli](https://github.com/cswank/kcli): Tool for inspecting kafka topics/partitions/messages.
+* [fac](https://github.com/mkchoi212/fac): git merge conflict resolver
+* [jsonui](https://github.com/gulyasm/jsonui): Interactive JSON explorer for your terminal.
+* [cointop](https://github.com/miguelmota/cointop): Interactive terminal based UI application for tracking cryptocurrencies.
+* [lazygit](https://github.com/jesseduffield/lazygit): simple terminal UI for git commands.
+* [lazydocker](https://github.com/jesseduffield/lazydocker): The lazier way to manage everything docker.
+
+Note: if your project is not listed here, let us know! :)
diff --git a/vendor/github.com/awesome-gocui/gocui/attribute.go b/vendor/github.com/awesome-gocui/gocui/attribute.go
new file mode 100644
index 00000000..3d986a71
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/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/awesome-gocui/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/awesome-gocui/gocui/doc.go b/vendor/github.com/awesome-gocui/gocui/doc.go
new file mode 100644
index 00000000..ca7113fa
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/doc.go
@@ -0,0 +1,118 @@
+// 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, err := gocui.NewGui(gocui.OutputNormal)
+ if err != nil {
+ // handle error
+ }
+ defer g.Close()
+
+ // Set GUI managers and key bindings
+ // ...
+
+ if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) {
+ // handle error
+ }
+
+Set GUI managers:
+
+ g.SetManager(mgr1, mgr2)
+
+Managers are in charge of GUI's layout and can be used to build widgets. On
+each iteration of the GUI's main loop, the Layout function of each configured
+manager is executed. Managers are used to set-up and update the application's
+main views, being possible to freely change them during execution. 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 !gocui.IsUnknownView(err) {
+ // 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
+the Layout function within managers, from keybinding callbacks or via
+*Gui.Update(). The reason for this is that it allows gocui to be
+concurrent-safe. So, if you want to update your GUI from a goroutine, you must
+use *Gui.Update(). For example:
+
+ g.Update(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 editing mode. This mode can be extended
+and customized creating a new Editor and assigning it to *View.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/awesome-gocui/gocui/edit.go b/vendor/github.com/awesome-gocui/gocui/edit.go
new file mode 100644
index 00000000..b5630df3
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/edit.go
@@ -0,0 +1,449 @@
+// 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/go-errors/errors"
+
+ "github.com/mattn/go-runewidth"
+)
+
+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)
+ case key == KeyTab:
+ v.EditWrite('\t')
+ case key == KeySpace:
+ v.EditWrite(' ')
+ case key == KeyInsert:
+ v.Overwrite = !v.Overwrite
+ default:
+ v.EditWrite(ch)
+ }
+}
+
+// EditWrite writes a rune at the cursor position.
+func (v *View) EditWrite(ch rune) {
+ w := runewidth.RuneWidth(ch)
+ v.writeRune(v.cx, v.cy, ch)
+ v.moveCursor(w, 0, true)
+}
+
+// EditDeleteToStartOfLine is the equivalent of pressing ctrl+U in your terminal, it deletes to the start of the line. Or if you are already at the start of the line, it deletes the newline character
+func (v *View) EditDeleteToStartOfLine() {
+ x, _ := v.Cursor()
+ if x == 0 {
+ v.EditDelete(true)
+ } else {
+ // delete characters until we are the start of the line
+ for x > 0 {
+ v.EditDelete(true)
+ x, _ = v.Cursor()
+ }
+ }
+}
+
+// EditGotoToStartOfLine takes you to the start of the current line
+func (v *View) EditGotoToStartOfLine() {
+ x, _ := v.Cursor()
+ for x > 0 {
+ v.MoveCursor(-1, 0, false)
+ x, _ = v.Cursor()
+ }
+}
+
+// EditGotoToEndOfLine takes you to the end of the line
+func (v *View) EditGotoToEndOfLine() {
+ _, y := v.Cursor()
+ _ = v.SetCursor(0, y+1)
+ x, newY := v.Cursor()
+ if newY == y {
+ // we must be on the last line, so lets move to the very end
+ prevX := -1
+ for prevX != x {
+ prevX = x
+ v.MoveCursor(1, 0, false)
+ x, _ = v.Cursor()
+ }
+ } else {
+ // most left so now we're at the end of the original line
+ v.MoveCursor(-1, 0, false)
+ }
+}
+
+// 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
+ n, _ := v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1)
+ v.MoveCursor(-n, 0, true)
+ }
+ } else { // middle/end of the line
+ n, _ := v.deleteRune(v.cx-1, v.cy)
+ v.MoveCursor(-n, 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)
+ v.ox = 0
+ v.cy = v.cy + 1
+ v.cx = 0
+}
+
+// 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) {
+ ox, oy := v.cx+v.ox, v.cy+v.oy
+ x, y := ox+dx, oy+dy
+
+ if y < 0 || y >= len(v.viewLines) {
+ v.moveCursor(dx, dy, writeMode)
+ return
+ }
+
+ // Removing newline.
+ if x < 0 {
+ var prevLen int
+ if y-1 >= 0 && y-1 < len(v.viewLines) {
+ prevLen = lineWidth(v.viewLines[y-1].line)
+ }
+
+ v.MoveCursor(prevLen, -1, writeMode)
+ return
+ }
+
+ line := v.viewLines[y].line
+ var col int
+ var prevCol int
+ for i := range line {
+ prevCol = col
+ col += runewidth.RuneWidth(line[i].chr)
+ if dx > 0 {
+ if x <= col {
+ x = col
+ break
+ }
+ continue
+ }
+
+ if x < col {
+ x = prevCol
+ break
+ }
+ }
+
+ v.moveCursor(x-ox, y-oy, writeMode)
+}
+
+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
+ curLineWidth = maxInt
+ if v.Wrap {
+ curLineWidth = maxX - 1
+ }
+
+ if !writeMode {
+ curLineWidth = 0
+ if y >= 0 && y < len(v.viewLines) {
+ curLineWidth = lineWidth(v.viewLines[y].line)
+ if v.Wrap && curLineWidth >= maxX {
+ curLineWidth = maxX - 1
+ }
+ }
+ }
+ // get the width of the previous line
+ prevLineWidth = 0
+ if y-1 >= 0 && y-1 < len(v.viewLines) {
+ prevLineWidth = lineWidth(v.viewLines[y-1].line)
+ }
+ // adjust cursor's x position and view's x origin
+ if x > curLineWidth { // move to next line
+ if dx > 0 { // horizontal movement
+ cy++
+ if writeMode || v.oy+cy < len(v.viewLines) {
+ if !v.Wrap {
+ v.ox = 0
+ }
+ v.cx = 0
+ }
+ } 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 writeMode || v.oy+cy < len(v.viewLines) {
+ 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 += cx
+ v.cx = 0
+ } else { // move to previous line
+ cy--
+ if prevLineWidth > 0 {
+ if !v.Wrap { // set origin so the EOL is visible
+ nox := prevLineWidth - maxX + 1
+ if nox < 0 {
+ nox = 0
+ }
+ v.ox = nox
+ }
+ v.cx = prevLineWidth
+ } else {
+ if !v.Wrap {
+ v.ox = 0
+ }
+ v.cx = 0
+ }
+ }
+ } else { // stay on the same line
+ if v.Wrap {
+ v.cx = cx
+ } else {
+ if cx >= maxX {
+ v.ox += cx - maxX + 1
+ v.cx = maxX
+ } else {
+ v.cx = cx
+ }
+ }
+ }
+
+ // adjust cursor's y position and view's y origin
+ if cy < 0 {
+ if v.oy > 0 {
+ v.oy--
+ }
+ } else if writeMode || v.oy+cy < len(v.viewLines) {
+ if cy >= maxY {
+ 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])
+
+ var s []cell
+ if x >= len(v.lines[y]) {
+ s = make([]cell, x-len(v.lines[y])+1)
+ } else if !v.Overwrite {
+ s = make([]cell, 1)
+ }
+ v.lines[y] = append(v.lines[y], s...)
+
+ if !v.Overwrite || (v.Overwrite && x >= olen-1) {
+ copy(v.lines[y][x+1:], v.lines[y][x:])
+ }
+ v.lines[y][x] = cell{
+ fgColor: v.FgColor,
+ bgColor: v.BgColor,
+ chr: ch,
+ }
+
+ return nil
+}
+
+// deleteRune removes a rune from the view's internal buffer, at the
+// position corresponding to the point (x, y).
+// returns the amount of columns that where removed.
+func (v *View) deleteRune(x, y int) (int, error) {
+ v.tainted = true
+
+ x, y, err := v.realPosition(x, y)
+ if err != nil {
+ return 0, err
+ }
+
+ if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
+ return 0, errors.New("invalid point")
+ }
+
+ var tw int
+ for i := range v.lines[y] {
+ w := runewidth.RuneWidth(v.lines[y][i].chr)
+ tw += w
+ if tw > x {
+ v.lines[y] = append(v.lines[y][:i], v.lines[y][i+1:]...)
+ return w, nil
+ }
+
+ }
+
+ return 0, 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/awesome-gocui/gocui/escape.go b/vendor/github.com/awesome-gocui/gocui/escape.go
new file mode 100644
index 00000000..c88309b0
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/escape.go
@@ -0,0 +1,229 @@
+// 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/go-errors/errors"
+ "strconv"
+)
+
+type escapeInterpreter struct {
+ state escapeState
+ curch rune
+ csiParam []string
+ curFgColor, curBgColor Attribute
+ mode OutputMode
+}
+
+type escapeState int
+
+const (
+ stateNone escapeState = iota
+ stateEscape
+ stateCSI
+ stateParams
+)
+
+var (
+ errNotCSI = errors.New("Not a CSI escape sequence")
+ 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(mode OutputMode) *escapeInterpreter {
+ ei := &escapeInterpreter{
+ state: stateNone,
+ curFgColor: ColorDefault,
+ curBgColor: ColorDefault,
+ mode: mode,
+ }
+ 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
+}
+
+// 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
+ 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:
+ switch {
+ case ch >= '0' && ch <= '9':
+ ei.csiParam = append(ei.csiParam, "")
+ case ch == 'm':
+ ei.csiParam = append(ei.csiParam, "0")
+ default:
+ return false, errCSIParseError
+ }
+ ei.state = stateParams
+ fallthrough
+ 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':
+ var err error
+ switch ei.mode {
+ case OutputNormal:
+ err = ei.outputNormal()
+ case Output256:
+ err = ei.output256()
+ }
+ if err != nil {
+ return false, errCSIParseError
+ }
+
+ ei.state = stateNone
+ ei.csiParam = nil
+ return true, nil
+ default:
+ return false, errCSIParseError
+ }
+ }
+ return false, nil
+}
+
+// outputNormal provides 8 different colors:
+// black, red, green, yellow, blue, magenta, cyan, white
+func (ei *escapeInterpreter) outputNormal() error {
+ for _, param := range ei.csiParam {
+ p, err := strconv.Atoi(param)
+ if err != nil {
+ return errCSIParseError
+ }
+
+ switch {
+ case p >= 30 && p <= 37:
+ ei.curFgColor = Attribute(p - 30 + 1)
+ case p == 39:
+ ei.curFgColor = ColorDefault
+ case p >= 40 && p <= 47:
+ ei.curBgColor = Attribute(p - 40 + 1)
+ case p == 49:
+ ei.curBgColor = ColorDefault
+ case p == 1:
+ ei.curFgColor |= AttrBold
+ case p == 4:
+ ei.curFgColor |= AttrUnderline
+ case p == 7:
+ ei.curFgColor |= AttrReverse
+ case p == 0:
+ ei.curFgColor = ColorDefault
+ ei.curBgColor = ColorDefault
+ }
+ }
+
+ return nil
+}
+
+// output256 allows you to leverage the 256-colors terminal mode:
+// 0x01 - 0x08: the 8 colors as in OutputNormal
+// 0x09 - 0x10: Color* | AttrBold
+// 0x11 - 0xe8: 216 different colors
+// 0xe9 - 0x1ff: 24 different shades of grey
+func (ei *escapeInterpreter) output256() error {
+ if len(ei.csiParam) < 3 {
+ return ei.outputNormal()
+ }
+
+ mode, err := strconv.Atoi(ei.csiParam[1])
+ if err != nil {
+ return errCSIParseError
+ }
+ if mode != 5 {
+ return ei.outputNormal()
+ }
+
+ fgbg, err := strconv.Atoi(ei.csiParam[0])
+ if err != nil {
+ return errCSIParseError
+ }
+ color, err := strconv.Atoi(ei.csiParam[2])
+ if err != nil {
+ return errCSIParseError
+ }
+
+ switch fgbg {
+ case 38:
+ ei.curFgColor = Attribute(color + 1)
+
+ for _, param := range ei.csiParam[3:] {
+ p, err := strconv.Atoi(param)
+ if err != nil {
+ return errCSIParseError
+ }
+
+ switch {
+ case p == 1:
+ ei.curFgColor |= AttrBold
+ case p == 4:
+ ei.curFgColor |= AttrUnderline
+ case p == 7:
+ ei.curFgColor |= AttrReverse
+ }
+ }
+ case 48:
+ ei.curBgColor = Attribute(color + 1)
+ default:
+ return errCSIParseError
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/awesome-gocui/gocui/go.mod b/vendor/github.com/awesome-gocui/gocui/go.mod
new file mode 100644
index 00000000..5791b4e4
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/go.mod
@@ -0,0 +1,9 @@
+module github.com/awesome-gocui/gocui
+
+go 1.12
+
+require (
+ github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc
+ github.com/go-errors/errors v1.0.1
+ github.com/mattn/go-runewidth v0.0.4
+)
diff --git a/vendor/github.com/awesome-gocui/gocui/go.sum b/vendor/github.com/awesome-gocui/gocui/go.sum
new file mode 100644
index 00000000..25f1c037
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/go.sum
@@ -0,0 +1,6 @@
+github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc h1:wGNpKcHU8Aadr9yOzsT3GEsFLS7HQu8HxQIomnekqf0=
+github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc/go.mod h1:tOy3o5Nf1bA17mnK4W41gD7PS3u4Cv0P0pqFcoWMy8s=
+github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
+github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
+github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
+github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
diff --git a/vendor/github.com/awesome-gocui/gocui/gui.go b/vendor/github.com/awesome-gocui/gocui/gui.go
new file mode 100644
index 00000000..6fe0d5d8
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/gui.go
@@ -0,0 +1,832 @@
+// 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 (
+ standardErrors "errors"
+ "runtime"
+
+ "github.com/go-errors/errors"
+
+ "github.com/awesome-gocui/termbox-go"
+)
+
+// OutputMode represents the terminal's output mode (8 or 256 colors).
+type OutputMode termbox.OutputMode
+
+var (
+ // ErrAlreadyBlacklisted is returned when the keybinding is already blacklisted.
+ ErrAlreadyBlacklisted = standardErrors.New("keybind already blacklisted")
+
+ // ErrBlacklisted is returned when the keybinding being parsed / used is blacklisted.
+ ErrBlacklisted = standardErrors.New("keybind blacklisted")
+
+ // ErrNotBlacklisted is returned when a keybinding being whitelisted is not blacklisted.
+ ErrNotBlacklisted = standardErrors.New("keybind not blacklisted")
+
+ // ErrNoSuchKeybind is returned when the keybinding being parsed does not exist.
+ ErrNoSuchKeybind = standardErrors.New("no such keybind")
+
+ // ErrUnknownView allows to assert if a View must be initialized.
+ ErrUnknownView = standardErrors.New("unknown view")
+
+ // ErrQuit is used to decide if the MainLoop finished successfully.
+ ErrQuit = standardErrors.New("quit")
+)
+
+const (
+ // OutputNormal provides 8-colors terminal mode.
+ OutputNormal = OutputMode(termbox.OutputNormal)
+
+ // Output256 provides 256-colors terminal mode.
+ Output256 = OutputMode(termbox.Output256)
+
+ // OutputGrayScale provides greyscale terminal mode.
+ OutputGrayScale = OutputMode(termbox.OutputGrayscale)
+
+ // Output216 provides greyscale terminal mode.
+ Output216 = OutputMode(termbox.Output216)
+)
+
+// 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
+ managers []Manager
+ keybindings []*keybinding
+ maxX, maxY int
+ outputMode OutputMode
+ stop chan struct{}
+ blacklist []Key
+
+ // BgColor and FgColor allow to configure the background and foreground
+ // colors of the GUI.
+ BgColor, FgColor, FrameColor Attribute
+
+ // SelBgColor and SelFgColor allow to configure the background and
+ // foreground colors of the frame of the current view.
+ SelBgColor, SelFgColor, SelFrameColor Attribute
+
+ // If Highlight is true, Sel{Bg,Fg}Colors will be used to draw the
+ // frame of the current view.
+ Highlight bool
+
+ // 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
+
+ // If ASCII is true then use ASCII instead of unicode to draw the
+ // interface. Using ASCII is more portable.
+ ASCII bool
+
+ // SupportOverlaps is true when we allow for view edges to overlap with other
+ // view edges
+ SupportOverlaps bool
+}
+
+// NewGui returns a new Gui object with a given output mode.
+func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) {
+ err := termbox.Init()
+ if err != nil {
+ return nil, err
+ }
+
+ g := &Gui{}
+
+ g.outputMode = mode
+ termbox.SetOutputMode(termbox.OutputMode(mode))
+
+ g.stop = make(chan struct{})
+
+ g.tbEvents = make(chan termbox.Event, 20)
+ g.userEvents = make(chan userEvent, 20)
+
+ if runtime.GOOS != "windows" {
+ g.maxX, g.maxY, err = g.getTermWindowSize()
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ g.maxX, g.maxY = termbox.Size()
+ }
+
+ g.BgColor, g.FgColor = ColorDefault, ColorDefault
+ g.SelBgColor, g.SelFgColor = ColorDefault, ColorDefault
+
+ // SupportOverlaps is true when we allow for view edges to overlap with other
+ // view edges
+ g.SupportOverlaps = supportOverlaps
+
+ return g, nil
+}
+
+// Close finalizes the library. It should be called after a successful
+// initialization and when gocui is not needed anymore.
+func (g *Gui) Close() {
+ go func() {
+ g.stop <- struct{}{}
+ }()
+ 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 given colors.
+func (g *Gui) SetRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
+ if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
+ return errors.New("invalid point")
+ }
+ termbox.SetCell(x, y, ch, termbox.Attribute(fgColor), termbox.Attribute(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, overlaps byte) (*View, error) {
+ if x0 >= x1 {
+ 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, g.outputMode)
+ v.BgColor, v.FgColor = g.BgColor, g.FgColor
+ v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor
+ v.Overlaps = overlaps
+ g.views = append(g.views, v)
+ return v, errors.Wrap(ErrUnknownView, 0)
+}
+
+// SetViewBeneath sets a view stacked beneath another view
+func (g *Gui) SetViewBeneath(name string, aboveViewName string, height int) (*View, error) {
+ aboveView, err := g.View(aboveViewName)
+ if err != nil {
+ return nil, err
+ }
+
+ viewTop := aboveView.y1 + 1
+ return g.SetView(name, aboveView.x0, viewTop, aboveView.x1, viewTop+height-1, 0)
+}
+
+// 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, errors.Wrap(ErrUnknownView, 0)
+}
+
+// SetViewOnBottom sets the given view on bottom of the existing ones.
+func (g *Gui) SetViewOnBottom(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([]*View{v}, s...)
+ return v, nil
+ }
+ }
+ return nil, errors.Wrap(ErrUnknownView, 0)
+}
+
+// Views returns all the views in the GUI.
+func (g *Gui) Views() []*View {
+ return g.views
+}
+
+// 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, errors.Wrap(ErrUnknownView, 0)
+}
+
+// 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) {
+ // traverse views in reverse order checking top views first
+ for i := len(g.views); i > 0; i-- {
+ v := g.views[i-1]
+ if x > v.x0 && x < v.x1 && y > v.y0 && y < v.y1 {
+ return v, nil
+ }
+ }
+ return nil, errors.Wrap(ErrUnknownView, 0)
+}
+
+// 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, errors.Wrap(ErrUnknownView, 0)
+}
+
+// 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 errors.Wrap(ErrUnknownView, 0)
+}
+
+// SetCurrentView gives the focus to a given view.
+func (g *Gui) SetCurrentView(name string) (*View, error) {
+ for _, v := range g.views {
+ if v.name == name {
+ g.currentView = v
+ return v, nil
+ }
+ }
+ return nil, errors.Wrap(ErrUnknownView, 0)
+}
+
+// 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, handler func(*Gui, *View) error) error {
+ var kb *keybinding
+
+ k, ch, err := getKey(key)
+ if err != nil {
+ return err
+ }
+
+ if g.isBlacklisted(k) {
+ return ErrBlacklisted
+ }
+
+ kb = newKeybinding(viewname, k, ch, mod, handler)
+ 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
+}
+
+// BlackListKeybinding adds a keybinding to the blacklist
+func (g *Gui) BlacklistKeybinding(k Key) error {
+ for _, j := range g.blacklist {
+ if j == k {
+ return ErrAlreadyBlacklisted
+ }
+ }
+ g.blacklist = append(g.blacklist, k)
+ return nil
+}
+
+// WhiteListKeybinding removes a keybinding from the blacklist
+func (g *Gui) WhitelistKeybinding(k Key) error {
+ for i, j := range g.blacklist {
+ if j == k {
+ g.blacklist = append(g.blacklist[:i], g.blacklist[i+1:]...)
+ return nil
+ }
+ }
+ return ErrNotBlacklisted
+}
+
+// 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")
+ }
+}
+
+// userEvent represents an event triggered by the user.
+type userEvent struct {
+ f func(*Gui) error
+}
+
+// Update executes the passed function. This method can be called safely from a
+// goroutine in order to update the GUI. It is important to note that the
+// passed function won't be executed immediately, instead it will be added to
+// the user events queue. Given that Update spawns a goroutine, the order in
+// which the user events will be handled is not guaranteed.
+func (g *Gui) Update(f func(*Gui) error) {
+ go func() { g.userEvents <- userEvent{f: f} }()
+}
+
+// A Manager is in charge of GUI's layout and can be used to build widgets.
+type Manager interface {
+ // Layout is called every time the GUI is redrawn, it must contain the
+ // base views and its initializations.
+ Layout(*Gui) error
+}
+
+// The ManagerFunc type is an adapter to allow the use of ordinary functions as
+// Managers. If f is a function with the appropriate signature, ManagerFunc(f)
+// is an Manager object that calls f.
+type ManagerFunc func(*Gui) error
+
+// Layout calls f(g)
+func (f ManagerFunc) Layout(g *Gui) error {
+ return f(g)
+}
+
+// SetManager sets the given GUI managers. It deletes all views and
+// keybindings.
+func (g *Gui) SetManager(managers ...Manager) {
+ g.managers = managers
+ g.currentView = nil
+ g.views = nil
+ g.keybindings = nil
+
+ go func() { g.tbEvents <- termbox.Event{Type: termbox.EventResize} }()
+}
+
+// SetManagerFunc sets the given manager function. It deletes all views and
+// keybindings.
+func (g *Gui) SetManagerFunc(manager func(*Gui) error) {
+ g.SetManager(ManagerFunc(manager))
+}
+
+// MainLoop runs the main loop until an error is returned. A successful
+// finish should return ErrQuit.
+func (g *Gui) MainLoop() error {
+ g.loaderTick()
+ if err := g.flush(); err != nil {
+ return err
+ }
+
+ go func() {
+ for {
+ select {
+ case <-g.stop:
+ return
+ default:
+ g.tbEvents <- termbox.PollEvent()
+ }
+ }
+ }()
+
+ inputMode := termbox.InputAlt
+ if true { // previously g.InputEsc, but didn't seem to work
+ 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.f(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.f(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 {
+ 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
+
+ for _, m := range g.managers {
+ if err := m.Layout(g); err != nil {
+ return err
+ }
+ }
+ for _, v := range g.views {
+ if !v.Visible || v.y1 < v.y0 {
+ continue
+ }
+ if v.Frame {
+ var fgColor, bgColor, frameColor Attribute
+ if g.Highlight && v == g.currentView {
+ fgColor = g.SelFgColor
+ bgColor = g.SelBgColor
+ frameColor = g.SelFrameColor
+ } else {
+ fgColor = g.FgColor
+ bgColor = g.BgColor
+ frameColor = g.FrameColor
+ }
+
+ if err := g.drawFrameEdges(v, frameColor, bgColor); err != nil {
+ return err
+ }
+ if err := g.drawFrameCorners(v, frameColor, bgColor); err != nil {
+ return err
+ }
+ if v.Title != "" {
+ if err := g.drawTitle(v, fgColor, bgColor); err != nil {
+ return err
+ }
+ }
+ if v.Subtitle != "" {
+ if err := g.drawSubtitle(v, fgColor, bgColor); err != nil {
+ return err
+ }
+ }
+ }
+ if err := g.draw(v); err != nil {
+ return err
+ }
+ }
+ termbox.Flush()
+ return nil
+}
+
+// drawFrameEdges draws the horizontal and vertical edges of a view.
+func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error {
+ runeH, runeV := '─', '│'
+ if g.ASCII {
+ runeH, runeV = '-', '|'
+ }
+
+ 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, runeH, fgColor, bgColor); err != nil {
+ return err
+ }
+ }
+ if v.y1 > -1 && v.y1 < g.maxY {
+ if err := g.SetRune(x, v.y1, runeH, fgColor, bgColor); 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, runeV, fgColor, bgColor); err != nil {
+ return err
+ }
+ }
+ if v.x1 > -1 && v.x1 < g.maxX {
+ if err := g.SetRune(v.x1, y, runeV, fgColor, bgColor); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func cornerRune(index byte) rune {
+ return []rune{' ', '│', '│', '│', '─', '┘', '┐', '┤', '─', '└', '┌', '├', '├', '┴', '┬', '┼'}[index]
+}
+
+func corner(v *View, directions byte) rune {
+ index := v.Overlaps | directions
+ return cornerRune(index)
+}
+
+// drawFrameCorners draws the corners of the view.
+func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error {
+ if v.y0 == v.y1 {
+ if !g.SupportOverlaps && v.x0 >= 0 && v.x1 >= 0 && v.y0 >= 0 && v.x0 < g.maxX && v.x1 < g.maxX && v.y0 < g.maxY {
+ if err := g.SetRune(v.x0, v.y0, '╶', fgColor, bgColor); err != nil {
+ return err
+ }
+ if err := g.SetRune(v.x1, v.y0, '╴', fgColor, bgColor); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+
+ runeTL, runeTR, runeBL, runeBR := '┌', '┐', '└', '┘'
+ if g.SupportOverlaps {
+ runeTL = corner(v, BOTTOM|RIGHT)
+ runeTR = corner(v, BOTTOM|LEFT)
+ runeBL = corner(v, TOP|RIGHT)
+ runeBR = corner(v, TOP|LEFT)
+ }
+ if g.ASCII {
+ runeTL, runeTR, runeBL, runeBR = '+', '+', '+', '+'
+ }
+
+ corners := []struct {
+ x, y int
+ ch rune
+ }{{v.x0, v.y0, runeTL}, {v.x1, v.y0, runeTR}, {v.x0, v.y1, runeBL}, {v.x1, v.y1, runeBR}}
+
+ for _, c := range corners {
+ if c.x >= 0 && c.y >= 0 && c.x < g.maxX && c.y < g.maxY {
+ if err := g.SetRune(c.x, c.y, c.ch, fgColor, bgColor); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// drawTitle draws the title of the view.
+func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) 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, fgColor, bgColor); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// drawSubtitle draws the subtitle of the view.
+func (g *Gui) drawSubtitle(v *View, fgColor, bgColor Attribute) error {
+ if v.y0 < 0 || v.y0 >= g.maxY {
+ return nil
+ }
+
+ start := v.x1 - 5 - len(v.Subtitle)
+ if start < v.x0 {
+ return nil
+ }
+ for i, ch := range v.Subtitle {
+ x := start + i
+ if x >= v.x1 {
+ break
+ }
+ if err := g.SetRune(x, v.y0, ch, fgColor, bgColor); 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 curview := g.currentView; curview != nil {
+ vMaxX, vMaxY := curview.Size()
+ if curview.cx < 0 {
+ curview.cx = 0
+ } else if curview.cx >= vMaxX {
+ curview.cx = vMaxX - 1
+ }
+ if curview.cy < 0 {
+ curview.cy = 0
+ } else if curview.cy >= vMaxY {
+ curview.cy = vMaxY - 1
+ }
+
+ gMaxX, gMaxY := g.Size()
+ cx, cy := curview.x0+curview.cx+1, curview.y0+curview.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
+}
+
+// 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:
+ matched, err := g.execKeybindings(g.currentView, ev)
+ if err != nil {
+ return err
+ }
+ if matched {
+ break
+ }
+ if g.currentView != nil && g.currentView.Editable && g.currentView.Editor != nil {
+ g.currentView.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. The value of matched is true if there is a match and no errors.
+func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err error) {
+ var globalKb *keybinding
+
+ for _, kb := range g.keybindings {
+ if kb.handler == nil {
+ continue
+ }
+
+ if !kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) {
+ continue
+ }
+
+ if kb.matchView(v) {
+ return g.execKeybinding(v, kb)
+ }
+
+ if kb.viewName == "" && ((v != nil && !v.Editable) || kb.ch == 0) {
+ globalKb = kb
+ }
+ }
+
+ if globalKb != nil {
+ return g.execKeybinding(v, globalKb)
+ }
+
+ return false, nil
+}
+
+// execKeybinding executes a given keybinding
+func (g *Gui) execKeybinding(v *View, kb *keybinding) (bool, error) {
+ if g.isBlacklisted(kb.key) {
+ return true, nil
+ }
+
+ if err := kb.handler(g, v); err != nil {
+ return false, err
+ }
+ return true, nil
+}
+
+// isBlacklisted reports whether the key is blacklisted
+func (g *Gui) isBlacklisted(k Key) bool {
+ for _, j := range g.blacklist {
+ if j == k {
+ return true
+ }
+ }
+ return false
+}
+
+// IsUnknownView reports whether the contents of an error is "unknown view".
+func IsUnknownView(err error) bool {
+ return err != nil && err.Error() == ErrUnknownView.Error()
+}
+
+// IsQuit reports whether the contents of an error is "quit".
+func IsQuit(err error) bool {
+ return err != nil && err.Error() == ErrQuit.Error()
+}
diff --git a/vendor/github.com/awesome-gocui/gocui/gui_others.go b/vendor/github.com/awesome-gocui/gocui/gui_others.go
new file mode 100644
index 00000000..5d247a19
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/gui_others.go
@@ -0,0 +1,60 @@
+// 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.
+
+// +build !windows
+
+package gocui
+
+import (
+ "os"
+ "os/signal"
+ "syscall"
+ "unsafe"
+
+ "github.com/go-errors/errors"
+)
+
+// getTermWindowSize is get terminal window size on linux or unix.
+// When gocui run inside the docker contaienr need to check and get the window size.
+func (g *Gui) getTermWindowSize() (int, int, error) {
+ var sz struct {
+ rows uint16
+ cols uint16
+ _ [2]uint16 // to match underlying syscall; see https://github.com/awesome-gocui/gocui/issues/33
+ }
+
+ var termw, termh int
+
+ out, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
+ if err != nil {
+ return 0, 0, err
+ }
+ defer out.Close()
+
+ signalCh := make(chan os.Signal, 1)
+ signal.Notify(signalCh, syscall.SIGWINCH, syscall.SIGINT)
+
+ for {
+ _, _, _ = syscall.Syscall(syscall.SYS_IOCTL,
+ out.Fd(), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz)))
+
+ // check terminal window size
+ termw, termh = int(sz.cols), int(sz.rows)
+ if termw > 0 && termh > 0 {
+ return termw, termh, nil
+ }
+
+ select {
+ case signal := <-signalCh:
+ switch signal {
+ // when the terminal window size is changed
+ case syscall.SIGWINCH:
+ continue
+ // ctrl + c to cancel
+ case syscall.SIGINT:
+ return 0, 0, errors.New("stop to get term window size")
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/awesome-gocui/gocui/gui_windows.go b/vendor/github.com/awesome-gocui/gocui/gui_windows.go
new file mode 100644
index 00000000..db1faab7
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/gui_windows.go
@@ -0,0 +1,53 @@
+// 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.
+
+// +build windows
+
+package gocui
+
+import (
+ "os"
+ "syscall"
+ "unsafe"
+)
+
+type wchar uint16
+type short int16
+type dword uint32
+type word uint16
+
+type coord struct {
+ x short
+ y short
+}
+
+type smallRect struct {
+ left short
+ top short
+ right short
+ bottom short
+}
+
+type consoleScreenBufferInfo struct {
+ size coord
+ cursorPosition coord
+ attributes word
+ window smallRect
+ maximumWindowSize coord
+}
+
+var (
+ kernel32 = syscall.NewLazyDLL("kernel32.dll")
+ procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
+)
+
+// getTermWindowSize is get terminal window size on windows.
+func (g *Gui) getTermWindowSize() (int, int, error) {
+ var csbi consoleScreenBufferInfo
+ r1, _, err := procGetConsoleScreenBufferInfo.Call(os.Stdout.Fd(), uintptr(unsafe.Pointer(&csbi)))
+ if r1 == 0 {
+ return 0, 0, err
+ }
+ return int(csbi.window.right - csbi.window.left + 1), int(csbi.window.bottom - csbi.window.top + 1), nil
+}
diff --git a/vendor/github.com/awesome-gocui/gocui/keybinding.go b/vendor/github.com/awesome-gocui/gocui/keybinding.go
new file mode 100644
index 00000000..d294e70d
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/keybinding.go
@@ -0,0 +1,285 @@
+// 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 (
+ "strings"
+
+ "github.com/awesome-gocui/termbox-go"
+)
+
+// Key represents special keys or keys combinations.
+type 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.
+type Modifier termbox.Modifier
+
+// Keybidings are used to link a given key-press event with a handler.
+type keybinding struct {
+ viewName string
+ key Key
+ ch rune
+ mod Modifier
+ handler func(*Gui, *View) error
+}
+
+// Parse takes the input string and extracts the keybinding.
+// Returns a Key / rune, a Modifier and an error.
+func Parse(input string) (interface{}, Modifier, error) {
+ if len(input) == 1 {
+ _, r, err := getKey(rune(input[0]))
+ if err != nil {
+ return nil, ModNone, err
+ }
+ return r, ModNone, nil
+ }
+
+ var modifier Modifier
+ cleaned := make([]string, 0)
+
+ tokens := strings.Split(input, "+")
+ for _, t := range tokens {
+ normalized := strings.Title(strings.ToLower(t))
+ if t == "Alt" {
+ modifier = ModAlt
+ continue
+ }
+ cleaned = append(cleaned, normalized)
+ }
+
+ key, exist := translate[strings.Join(cleaned, "")]
+ if !exist {
+ return nil, ModNone, ErrNoSuchKeybind
+ }
+
+ return key, modifier, nil
+}
+
+// ParseAll takes an array of strings and returns a map of all keybindings.
+func ParseAll(input []string) (map[interface{}]Modifier, error) {
+ ret := make(map[interface{}]Modifier)
+ for _, i := range input {
+ k, m, err := Parse(i)
+ if err != nil {
+ return ret, err
+ }
+ ret[k] = m
+ }
+ return ret, nil
+}
+
+// MustParse takes the input string and returns a Key / rune and a Modifier.
+// It will panic if any error occured.
+func MustParse(input string) (interface{}, Modifier) {
+ k, m, err := Parse(input)
+ if err != nil {
+ panic(err)
+ }
+ return k, m
+}
+
+// MustParseAll takes an array of strings and returns a map of all keybindings.
+// It will panic if any error occured.
+func MustParseAll(input []string) map[interface{}]Modifier {
+ result, err := ParseAll(input)
+ if err != nil {
+ panic(err)
+ }
+ return result
+}
+
+// newKeybinding returns a new Keybinding object.
+func newKeybinding(viewname string, key Key, ch rune, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) {
+ kb = &keybinding{
+ viewName: viewname,
+ key: key,
+ ch: ch,
+ mod: mod,
+ handler: handler,
+ }
+ 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 the user is typing in a field, ignore char keys
+ if v == nil || (v.Editable && kb.ch != 0) {
+ return false
+ }
+ return kb.viewName == v.name
+}
+
+// translations for strings to keys
+var translate = map[string]Key{
+ "F1": KeyF1,
+ "F2": KeyF2,
+ "F3": KeyF3,
+ "F4": KeyF4,
+ "F5": KeyF5,
+ "F6": KeyF6,
+ "F7": KeyF7,
+ "F8": KeyF8,
+ "F9": KeyF9,
+ "F10": KeyF10,
+ "F11": KeyF11,
+ "F12": KeyF12,
+ "Insert": KeyInsert,
+ "Delete": KeyDelete,
+ "Home": KeyHome,
+ "End": KeyEnd,
+ "Pgup": KeyPgup,
+ "Pgdn": KeyPgdn,
+ "ArrowUp": KeyArrowUp,
+ "ArrowDown": KeyArrowDown,
+ "ArrowLeft": KeyArrowLeft,
+ "ArrowRight": KeyArrowRight,
+ "CtrlTilde": KeyCtrlTilde,
+ "Ctrl2": KeyCtrl2,
+ "CtrlSpace": KeyCtrlSpace,
+ "CtrlA": KeyCtrlA,
+ "CtrlB": KeyCtrlB,
+ "CtrlC": KeyCtrlC,
+ "CtrlD": KeyCtrlD,
+ "CtrlE": KeyCtrlE,
+ "CtrlF": KeyCtrlF,
+ "CtrlG": KeyCtrlG,
+ "Backspace": KeyBackspace,
+ "CtrlH": KeyCtrlH,
+ "Tab": KeyTab,
+ "CtrlI": KeyCtrlI,
+ "CtrlJ": KeyCtrlJ,
+ "CtrlK": KeyCtrlK,
+ "CtrlL": KeyCtrlL,
+ "Enter": KeyEnter,
+ "CtrlM": KeyCtrlM,
+ "CtrlN": KeyCtrlN,
+ "CtrlO": KeyCtrlO,
+ "CtrlP": KeyCtrlP,
+ "CtrlQ": KeyCtrlQ,
+ "CtrlR": KeyCtrlR,
+ "CtrlS": KeyCtrlS,
+ "CtrlT": KeyCtrlT,
+ "CtrlU": KeyCtrlU,
+ "CtrlV": KeyCtrlV,
+ "CtrlW": KeyCtrlW,
+ "CtrlX": KeyCtrlX,
+ "CtrlY": KeyCtrlY,
+ "CtrlZ": KeyCtrlZ,
+ "Esc": KeyEsc,
+ "CtrlLsqBracket": KeyCtrlLsqBracket,
+ "Ctrl3": KeyCtrl3,
+ "Ctrl4": KeyCtrl4,
+ "CtrlBackslash": KeyCtrlBackslash,
+ "Ctrl5": KeyCtrl5,
+ "CtrlRsqBracket": KeyCtrlRsqBracket,
+ "Ctrl6": KeyCtrl6,
+ "Ctrl7": KeyCtrl7,
+ "CtrlSlash": KeyCtrlSlash,
+ "CtrlUnderscore": KeyCtrlUnderscore,
+ "Space": KeySpace,
+ "Backspace2": KeyBackspace2,
+ "Ctrl8": KeyCtrl8,
+ "Mouseleft": MouseLeft,
+ "Mousemiddle": MouseMiddle,
+ "Mouseright": MouseRight,
+ "Mouserelease": MouseRelease,
+ "MousewheelUp": MouseWheelUp,
+ "MousewheelDown": MouseWheelDown,
+}
+
+// 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)
+ MouseRelease = Key(termbox.MouseRelease)
+ MouseWheelUp = Key(termbox.MouseWheelUp)
+ MouseWheelDown = Key(termbox.MouseWheelDown)
+)
+
+// 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)
+)
diff --git a/vendor/github.com/awesome-gocui/gocui/loader.go b/vendor/github.com/awesome-gocui/gocui/loader.go
new file mode 100644
index 00000000..d6715ac6
--- /dev/null
+++ b/vendor/github.com/awesome-gocui/gocui/loader.go
@@ -0,0 +1,46 @@
+package gocui
+
+import "time"
+
+func (g *Gui) loaderTick() {
+ go func() {
+ for range time.Tick(time.Millisecond * 50) {
+ for _, view := range g.Views() {
+ if view.HasLoader {
+ g.userEvents <- userEvent{func(g *Gui) error { return nil }}
+ break
+ }
+ }
+ }
+ }()
+}
+
+func (v *View) loaderLines() [][]cell {
+ duplicate := make([][]cell, len(v.lines))
+ for i := range v.lines {
+ if i < len(v.lines)-1 {
+ duplicate[i] = make([]cell, len(v.lines[i]))
+ copy(duplicate[i], v.lines[i])
+ } else {
+ duplicate[i] = make([]cell, len(v.lines[i])+2)
+ copy(duplicate[i], v.lines[i])
+ duplicate[i][len(duplicate[i])-2] = cell{chr: ' '}
+ duplicate[i][len(duplicate[i])-1] = Loader()
+ }
+ }
+
+ return duplicate
+}
+
+// Loader can show a loading animation
+func Loader() cell {
+ characters := "|/-\\"
+ now := time.Now()
+ nanos := now.UnixNano()
+ index := nanos / 50000000 % int64(len(characters))
+ str := characters[index : index+1]
+ chr := []rune(str)[0]
+ return cell{
+ chr: chr,
+ }
+}
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")
+}