From cb8236c9c22007eb622b7219c58d3342f1f53d50 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sun, 3 Nov 2019 20:47:29 +0100 Subject: termui: migrate to awesome-gocui instead of the old fork I had --- vendor/github.com/awesome-gocui/gocui/.gitignore | 1 + vendor/github.com/awesome-gocui/gocui/AUTHORS | 30 + .../awesome-gocui/gocui/CODE_OF_CONDUCT.md | 76 ++ .../github.com/awesome-gocui/gocui/CONTRIBUTING.md | 33 + vendor/github.com/awesome-gocui/gocui/LICENSE | 23 + vendor/github.com/awesome-gocui/gocui/README.md | 135 +++ vendor/github.com/awesome-gocui/gocui/attribute.go | 32 + vendor/github.com/awesome-gocui/gocui/doc.go | 118 +++ vendor/github.com/awesome-gocui/gocui/edit.go | 449 ++++++++++ vendor/github.com/awesome-gocui/gocui/escape.go | 229 +++++ vendor/github.com/awesome-gocui/gocui/go.mod | 9 + vendor/github.com/awesome-gocui/gocui/go.sum | 6 + vendor/github.com/awesome-gocui/gocui/gui.go | 832 ++++++++++++++++++ .../github.com/awesome-gocui/gocui/gui_others.go | 60 ++ .../github.com/awesome-gocui/gocui/gui_windows.go | 53 ++ .../github.com/awesome-gocui/gocui/keybinding.go | 285 +++++++ vendor/github.com/awesome-gocui/gocui/loader.go | 46 + vendor/github.com/awesome-gocui/gocui/view.go | 800 +++++++++++++++++ vendor/github.com/awesome-gocui/termbox-go/AUTHORS | 4 + vendor/github.com/awesome-gocui/termbox-go/LICENSE | 19 + .../github.com/awesome-gocui/termbox-go/README.md | 49 ++ vendor/github.com/awesome-gocui/termbox-go/api.go | 506 +++++++++++ .../awesome-gocui/termbox-go/api_common.go | 187 ++++ .../awesome-gocui/termbox-go/api_windows.go | 245 ++++++ .../awesome-gocui/termbox-go/collect_terminfo.py | 110 +++ .../github.com/awesome-gocui/termbox-go/escwait.go | 11 + .../awesome-gocui/termbox-go/escwait_darwin.go | 9 + .../awesome-gocui/termbox-go/syscalls.go | 39 + .../awesome-gocui/termbox-go/syscalls_darwin.go | 41 + .../termbox-go/syscalls_darwin_amd64.go | 40 + .../awesome-gocui/termbox-go/syscalls_dragonfly.go | 39 + .../awesome-gocui/termbox-go/syscalls_freebsd.go | 39 + .../awesome-gocui/termbox-go/syscalls_linux.go | 33 + .../awesome-gocui/termbox-go/syscalls_netbsd.go | 39 + .../awesome-gocui/termbox-go/syscalls_openbsd.go | 39 + .../awesome-gocui/termbox-go/syscalls_windows.go | 61 ++ .../github.com/awesome-gocui/termbox-go/termbox.go | 529 ++++++++++++ .../awesome-gocui/termbox-go/termbox_common.go | 59 ++ .../awesome-gocui/termbox-go/termbox_windows.go | 948 +++++++++++++++++++++ .../awesome-gocui/termbox-go/terminfo.go | 232 +++++ .../awesome-gocui/termbox-go/terminfo_builtin.go | 64 ++ 41 files changed, 6559 insertions(+) create mode 100644 vendor/github.com/awesome-gocui/gocui/.gitignore create mode 100644 vendor/github.com/awesome-gocui/gocui/AUTHORS create mode 100644 vendor/github.com/awesome-gocui/gocui/CODE_OF_CONDUCT.md create mode 100644 vendor/github.com/awesome-gocui/gocui/CONTRIBUTING.md create mode 100644 vendor/github.com/awesome-gocui/gocui/LICENSE create mode 100644 vendor/github.com/awesome-gocui/gocui/README.md create mode 100644 vendor/github.com/awesome-gocui/gocui/attribute.go create mode 100644 vendor/github.com/awesome-gocui/gocui/doc.go create mode 100644 vendor/github.com/awesome-gocui/gocui/edit.go create mode 100644 vendor/github.com/awesome-gocui/gocui/escape.go create mode 100644 vendor/github.com/awesome-gocui/gocui/go.mod create mode 100644 vendor/github.com/awesome-gocui/gocui/go.sum create mode 100644 vendor/github.com/awesome-gocui/gocui/gui.go create mode 100644 vendor/github.com/awesome-gocui/gocui/gui_others.go create mode 100644 vendor/github.com/awesome-gocui/gocui/gui_windows.go create mode 100644 vendor/github.com/awesome-gocui/gocui/keybinding.go create mode 100644 vendor/github.com/awesome-gocui/gocui/loader.go create mode 100644 vendor/github.com/awesome-gocui/gocui/view.go create mode 100644 vendor/github.com/awesome-gocui/termbox-go/AUTHORS create mode 100644 vendor/github.com/awesome-gocui/termbox-go/LICENSE create mode 100644 vendor/github.com/awesome-gocui/termbox-go/README.md create mode 100644 vendor/github.com/awesome-gocui/termbox-go/api.go create mode 100644 vendor/github.com/awesome-gocui/termbox-go/api_common.go create mode 100644 vendor/github.com/awesome-gocui/termbox-go/api_windows.go create mode 100755 vendor/github.com/awesome-gocui/termbox-go/collect_terminfo.py create mode 100644 vendor/github.com/awesome-gocui/termbox-go/escwait.go create mode 100644 vendor/github.com/awesome-gocui/termbox-go/escwait_darwin.go create mode 100644 vendor/github.com/awesome-gocui/termbox-go/syscalls.go create mode 100644 vendor/github.com/awesome-gocui/termbox-go/syscalls_darwin.go create mode 100644 vendor/github.com/awesome-gocui/termbox-go/syscalls_darwin_amd64.go create mode 100644 vendor/github.com/awesome-gocui/termbox-go/syscalls_dragonfly.go create mode 100644 vendor/github.com/awesome-gocui/termbox-go/syscalls_freebsd.go create mode 100644 vendor/github.com/awesome-gocui/termbox-go/syscalls_linux.go create mode 100644 vendor/github.com/awesome-gocui/termbox-go/syscalls_netbsd.go create mode 100644 vendor/github.com/awesome-gocui/termbox-go/syscalls_openbsd.go create mode 100644 vendor/github.com/awesome-gocui/termbox-go/syscalls_windows.go create mode 100644 vendor/github.com/awesome-gocui/termbox-go/termbox.go create mode 100644 vendor/github.com/awesome-gocui/termbox-go/termbox_common.go create mode 100644 vendor/github.com/awesome-gocui/termbox-go/termbox_windows.go create mode 100644 vendor/github.com/awesome-gocui/termbox-go/terminfo.go create mode 100644 vendor/github.com/awesome-gocui/termbox-go/terminfo_builtin.go (limited to 'vendor/github.com/awesome-gocui') 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 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 + +Henri Koski + Custom current view color + +Dustin Willis Webber + 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") +} diff --git a/vendor/github.com/awesome-gocui/termbox-go/AUTHORS b/vendor/github.com/awesome-gocui/termbox-go/AUTHORS new file mode 100644 index 00000000..fe26fb0f --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/AUTHORS @@ -0,0 +1,4 @@ +# Please keep this file sorted. + +Georg Reinke +nsf diff --git a/vendor/github.com/awesome-gocui/termbox-go/LICENSE b/vendor/github.com/awesome-gocui/termbox-go/LICENSE new file mode 100644 index 00000000..d9bc068c --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2012 termbox-go authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/awesome-gocui/termbox-go/README.md b/vendor/github.com/awesome-gocui/termbox-go/README.md new file mode 100644 index 00000000..734aab73 --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/README.md @@ -0,0 +1,49 @@ +[![GoDoc](https://godoc.org/github.com/nsf/termbox-go?status.svg)](http://godoc.org/github.com/nsf/termbox-go) + +## IMPORTANT + +This library is somewhat not maintained anymore. But I'm glad that it did what I wanted the most. It moved people away from "ncurses" mindset and these days we see both re-implementations of termbox API in various languages and even possibly better libs with similar API design. If you're looking for a Go lib that provides terminal-based user interface facilities, I've heard that https://github.com/gdamore/tcell is good (never used it myself). Also for more complicated interfaces and/or computer games I recommend you to consider using HTML-based UI. Having said that, termbox still somewhat works. In fact I'm writing this line of text right now in godit (which is a text editor written using termbox-go). So, be aware. Good luck and have a nice day. + +## Termbox +Termbox is a library that provides a minimalistic API which allows the programmer to write text-based user interfaces. The library is crossplatform and has both terminal-based implementations on *nix operating systems and a winapi console based implementation for windows operating systems. The basic idea is an abstraction of the greatest common subset of features available on all major terminals and other terminal-like APIs in a minimalistic fashion. Small API means it is easy to implement, test, maintain and learn it, that's what makes the termbox a distinct library in its area. + +### Installation +Install and update this go package with `go get -u github.com/nsf/termbox-go` + +### Examples +For examples of what can be done take a look at demos in the _demos directory. You can try them with go run: `go run _demos/keyboard.go` + +There are also some interesting projects using termbox-go: + - [godit](https://github.com/nsf/godit) is an emacsish lightweight text editor written using termbox. + - [gotetris](https://github.com/jjinux/gotetris) is an implementation of Tetris. + - [sokoban-go](https://github.com/rn2dy/sokoban-go) is an implementation of sokoban game. + - [hecate](https://github.com/evanmiller/hecate) is a hex editor designed by Satan. + - [httopd](https://github.com/verdverm/httopd) is top for httpd logs. + - [mop](https://github.com/mop-tracker/mop) is stock market tracker for hackers. + - [termui](https://github.com/gizak/termui) is a terminal dashboard. + - [termdash](https://github.com/mum4k/termdash) is a terminal dashboard. + - [termloop](https://github.com/JoelOtter/termloop) is a terminal game engine. + - [xterm-color-chart](https://github.com/kutuluk/xterm-color-chart) is a XTerm 256 color chart. + - [gocui](https://github.com/jroimartin/gocui) is a minimalist Go library aimed at creating console user interfaces. + - [dry](https://github.com/moncho/dry) is an interactive cli to manage Docker containers. + - [pxl](https://github.com/ichinaski/pxl) displays images in the terminal. + - [snake-game](https://github.com/DyegoCosta/snake-game) is an implementation of the Snake game. + - [gone](https://github.com/guillaumebreton/gone) is a CLI pomodoro® timer. + - [Spoof.go](https://github.com/sabey/spoofgo) controllable movement spoofing from the cli + - [lf](https://github.com/gokcehan/lf) is a terminal file manager + - [rat](https://github.com/ericfreese/rat) lets you compose shell commands to build terminal applications. + - [httplab](https://github.com/gchaincl/httplab) An interactive web server. + - [tetris](https://github.com/MichaelS11/tetris) Go Tetris with AI option + - [wot](https://github.com/kyu-suke/wot) Wait time during command is completed. + - [2048-go](https://github.com/1984weed/2048-go) is 2048 in Go + - [jv](https://github.com/maxzender/jv) helps you view JSON on the command-line. + - [pinger](https://github.com/hirose31/pinger) helps you to monitor numerous hosts using ICMP ECHO_REQUEST. + - [vixl44](https://github.com/sebashwa/vixl44) lets you create pixel art inside your terminal using vim movements + - [zterm](https://github.com/varunrau/zterm) is a typing game inspired by http://zty.pe/ + - [gotypist](https://github.com/pb-/gotypist) is a fun touch-typing tutor following Steve Yegge's method. + - [cointop](https://github.com/miguelmota/cointop) is an interactive terminal based UI application for tracking cryptocurrencies. + - [pexpo](https://github.com/nnao45/pexpo) is a terminal sending ping tool written in Go. + - [jid](https://github.com/simeji/jid) is an interactive JSON drill down tool using filtering queries like jq. + +### API reference +[godoc.org/github.com/nsf/termbox-go](http://godoc.org/github.com/nsf/termbox-go) diff --git a/vendor/github.com/awesome-gocui/termbox-go/api.go b/vendor/github.com/awesome-gocui/termbox-go/api.go new file mode 100644 index 00000000..a461fc27 --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/api.go @@ -0,0 +1,506 @@ +// +build !windows + +package termbox + +import "github.com/mattn/go-runewidth" +import "fmt" +import "os" +import "os/signal" +import "syscall" +import "runtime" +import "time" + +// public API + +// Initializes termbox library. This function should be called before any other functions. +// After successful initialization, the library must be finalized using 'Close' function. +// +// Example usage: +// err := termbox.Init() +// if err != nil { +// panic(err) +// } +// defer termbox.Close() +func Init() error { + var err error + + if runtime.GOOS == "openbsd" { + out, err = os.OpenFile("/dev/tty", os.O_RDWR, 0) + if err != nil { + return err + } + in = int(out.Fd()) + } else { + out, err = os.OpenFile("/dev/tty", os.O_WRONLY, 0) + if err != nil { + return err + } + in, err = syscall.Open("/dev/tty", syscall.O_RDONLY, 0) + if err != nil { + return err + } + } + + err = setup_term() + if err != nil { + return fmt.Errorf("termbox: error while reading terminfo data: %v", err) + } + + signal.Notify(sigwinch, syscall.SIGWINCH) + signal.Notify(sigio, syscall.SIGIO) + + _, err = fcntl(in, syscall.F_SETFL, syscall.O_ASYNC|syscall.O_NONBLOCK) + if err != nil { + return err + } + _, err = fcntl(in, syscall.F_SETOWN, syscall.Getpid()) + if runtime.GOOS != "darwin" && err != nil { + return err + } + err = tcgetattr(out.Fd(), &orig_tios) + if err != nil { + return err + } + + tios := orig_tios + tios.Iflag &^= syscall_IGNBRK | syscall_BRKINT | syscall_PARMRK | + syscall_ISTRIP | syscall_INLCR | syscall_IGNCR | + syscall_ICRNL | syscall_IXON + tios.Lflag &^= syscall_ECHO | syscall_ECHONL | syscall_ICANON | + syscall_ISIG | syscall_IEXTEN + tios.Cflag &^= syscall_CSIZE | syscall_PARENB + tios.Cflag |= syscall_CS8 + tios.Cc[syscall_VMIN] = 1 + tios.Cc[syscall_VTIME] = 0 + + err = tcsetattr(out.Fd(), &tios) + if err != nil { + return err + } + + out.WriteString(funcs[t_enter_ca]) + out.WriteString(funcs[t_enter_keypad]) + out.WriteString(funcs[t_hide_cursor]) + out.WriteString(funcs[t_clear_screen]) + + termw, termh = get_term_size(out.Fd()) + back_buffer.init(termw, termh) + front_buffer.init(termw, termh) + back_buffer.clear() + front_buffer.clear() + + go func() { + buf := make([]byte, 128) + for { + select { + case <-sigio: + for { + n, err := syscall.Read(in, buf) + if err == syscall.EAGAIN || err == syscall.EWOULDBLOCK { + break + } + select { + case input_comm <- input_event{buf[:n], err}: + ie := <-input_comm + buf = ie.data[:128] + case <-quit: + return + } + } + case <-quit: + return + } + } + }() + + IsInit = true + return nil +} + +// Interrupt an in-progress call to PollEvent by causing it to return +// EventInterrupt. Note that this function will block until the PollEvent +// function has successfully been interrupted. +func Interrupt() { + interrupt_comm <- struct{}{} +} + +// Finalizes termbox library, should be called after successful initialization +// when termbox's functionality isn't required anymore. +func Close() { + quit <- 1 + out.WriteString(funcs[t_show_cursor]) + out.WriteString(funcs[t_sgr0]) + out.WriteString(funcs[t_clear_screen]) + out.WriteString(funcs[t_exit_ca]) + out.WriteString(funcs[t_exit_keypad]) + out.WriteString(funcs[t_exit_mouse]) + tcsetattr(out.Fd(), &orig_tios) + + out.Close() + syscall.Close(in) + + // reset the state, so that on next Init() it will work again + termw = 0 + termh = 0 + input_mode = InputEsc + out = nil + in = 0 + lastfg = attr_invalid + lastbg = attr_invalid + lastx = coord_invalid + lasty = coord_invalid + cursor_x = cursor_hidden + cursor_y = cursor_hidden + foreground = ColorDefault + background = ColorDefault + IsInit = false +} + +// Synchronizes the internal back buffer with the terminal. +func Flush() error { + // invalidate cursor position + lastx = coord_invalid + lasty = coord_invalid + + update_size_maybe() + + for y := 0; y < front_buffer.height; y++ { + line_offset := y * front_buffer.width + for x := 0; x < front_buffer.width; { + cell_offset := line_offset + x + back := &back_buffer.cells[cell_offset] + front := &front_buffer.cells[cell_offset] + if back.Ch < ' ' { + back.Ch = ' ' + } + w := runewidth.RuneWidth(back.Ch) + if w == 0 || w == 2 && runewidth.IsAmbiguousWidth(back.Ch) { + w = 1 + } + if *back == *front { + x += w + continue + } + *front = *back + send_attr(back.Fg, back.Bg) + + if w == 2 && x == front_buffer.width-1 { + // there's not enough space for 2-cells rune, + // let's just put a space in there + send_char(x, y, ' ') + } else { + send_char(x, y, back.Ch) + if w == 2 { + next := cell_offset + 1 + front_buffer.cells[next] = Cell{ + Ch: 0, + Fg: back.Fg, + Bg: back.Bg, + } + } + } + x += w + } + } + if !is_cursor_hidden(cursor_x, cursor_y) { + write_cursor(cursor_x, cursor_y) + } + return flush() +} + +// Sets the position of the cursor. See also HideCursor(). +func SetCursor(x, y int) { + if is_cursor_hidden(cursor_x, cursor_y) && !is_cursor_hidden(x, y) { + outbuf.WriteString(funcs[t_show_cursor]) + } + + if !is_cursor_hidden(cursor_x, cursor_y) && is_cursor_hidden(x, y) { + outbuf.WriteString(funcs[t_hide_cursor]) + } + + cursor_x, cursor_y = x, y + if !is_cursor_hidden(cursor_x, cursor_y) { + write_cursor(cursor_x, cursor_y) + } +} + +// The shortcut for SetCursor(-1, -1). +func HideCursor() { + SetCursor(cursor_hidden, cursor_hidden) +} + +// Changes cell's parameters in the internal back buffer at the specified +// position. +func SetCell(x, y int, ch rune, fg, bg Attribute) { + if x < 0 || x >= back_buffer.width { + return + } + if y < 0 || y >= back_buffer.height { + return + } + + back_buffer.cells[y*back_buffer.width+x] = Cell{ch, fg, bg} +} + +// Returns a slice into the termbox's back buffer. You can get its dimensions +// using 'Size' function. The slice remains valid as long as no 'Clear' or +// 'Flush' function calls were made after call to this function. +func CellBuffer() []Cell { + return back_buffer.cells +} + +// After getting a raw event from PollRawEvent function call, you can parse it +// again into an ordinary one using termbox logic. That is parse an event as +// termbox would do it. Returned event in addition to usual Event struct fields +// sets N field to the amount of bytes used within 'data' slice. If the length +// of 'data' slice is zero or event cannot be parsed for some other reason, the +// function will return a special event type: EventNone. +// +// IMPORTANT: EventNone may contain a non-zero N, which means you should skip +// these bytes, because termbox cannot recognize them. +// +// NOTE: This API is experimental and may change in future. +func ParseEvent(data []byte) Event { + event := Event{Type: EventKey} + status := extract_event(data, &event, false) + if status != event_extracted { + return Event{Type: EventNone, N: event.N} + } + return event +} + +// Wait for an event and return it. This is a blocking function call. Instead +// of EventKey and EventMouse it returns EventRaw events. Raw event is written +// into `data` slice and Event's N field is set to the amount of bytes written. +// The minimum required length of the 'data' slice is 1. This requirement may +// vary on different platforms. +// +// NOTE: This API is experimental and may change in future. +func PollRawEvent(data []byte) Event { + if len(data) == 0 { + panic("len(data) >= 1 is a requirement") + } + + var event Event + if extract_raw_event(data, &event) { + return event + } + + for { + select { + case ev := <-input_comm: + if ev.err != nil { + return Event{Type: EventError, Err: ev.err} + } + + inbuf = append(inbuf, ev.data...) + input_comm <- ev + if extract_raw_event(data, &event) { + return event + } + case <-interrupt_comm: + event.Type = EventInterrupt + return event + + case <-sigwinch: + event.Type = EventResize + event.Width, event.Height = get_term_size(out.Fd()) + return event + } + } +} + +// Wait for an event and return it. This is a blocking function call. +func PollEvent() Event { + // Constant governing macOS specific behavior. See https://github.com/nsf/termbox-go/issues/132 + // This is an arbitrary delay which hopefully will be enough time for any lagging + // partial escape sequences to come through. + const esc_wait_delay = 100 * time.Millisecond + + var event Event + var esc_wait_timer *time.Timer + var esc_timeout <-chan time.Time + + // try to extract event from input buffer, return on success + event.Type = EventKey + status := extract_event(inbuf, &event, true) + if event.N != 0 { + if event.N > len(inbuf) { + event.N = len(inbuf) + } + copy(inbuf, inbuf[event.N:]) + inbuf = inbuf[:len(inbuf)-event.N] + } + if status == event_extracted { + return event + } else if status == esc_wait { + esc_wait_timer = time.NewTimer(esc_wait_delay) + esc_timeout = esc_wait_timer.C + } + + for { + select { + case ev := <-input_comm: + if esc_wait_timer != nil { + if !esc_wait_timer.Stop() { + <-esc_wait_timer.C + } + esc_wait_timer = nil + } + + if ev.err != nil { + return Event{Type: EventError, Err: ev.err} + } + + inbuf = append(inbuf, ev.data...) + input_comm <- ev + status := extract_event(inbuf, &event, true) + if event.N != 0 { + if event.N > len(inbuf) { + event.N = len(inbuf) + } + copy(inbuf, inbuf[event.N:]) + inbuf = inbuf[:len(inbuf)-event.N] + } + if status == event_extracted { + return event + } else if status == esc_wait { + esc_wait_timer = time.NewTimer(esc_wait_delay) + esc_timeout = esc_wait_timer.C + } + case <-esc_timeout: + esc_wait_timer = nil + + status := extract_event(inbuf, &event, false) + if event.N != 0 { + if event.N > len(inbuf) { + event.N = len(inbuf) + } + copy(inbuf, inbuf[event.N:]) + inbuf = inbuf[:len(inbuf)-event.N] + } + if status == event_extracted { + return event + } + case <-interrupt_comm: + event.Type = EventInterrupt + return event + + case <-sigwinch: + event.Type = EventResize + event.Width, event.Height = get_term_size(out.Fd()) + return event + } + } +} + +// Returns the size of the internal back buffer (which is mostly the same as +// terminal's window size in characters). But it doesn't always match the size +// of the terminal window, after the terminal size has changed, the internal +// back buffer will get in sync only after Clear or Flush function calls. +func Size() (width int, height int) { + return termw, termh +} + +// Clears the internal back buffer. +func Clear(fg, bg Attribute) error { + foreground, background = fg, bg + err := update_size_maybe() + back_buffer.clear() + return err +} + +// Sets termbox input mode. Termbox has two input modes: +// +// 1. Esc input mode. When ESC sequence is in the buffer and it doesn't match +// any known sequence. ESC means KeyEsc. This is the default input mode. +// +// 2. Alt input mode. When ESC sequence is in the buffer and it doesn't match +// any known sequence. ESC enables ModAlt modifier for the next keyboard event. +// +// Both input modes can be OR'ed with Mouse mode. Setting Mouse mode bit up will +// enable mouse button press/release and drag events. +// +// If 'mode' is InputCurrent, returns the current input mode. See also Input* +// constants. +func SetInputMode(mode InputMode) InputMode { + if mode == InputCurrent { + return input_mode + } + if mode&(InputEsc|InputAlt) == 0 { + mode |= InputEsc + } + if mode&(InputEsc|InputAlt) == InputEsc|InputAlt { + mode &^= InputAlt + } + if mode&InputMouse != 0 { + out.WriteString(funcs[t_enter_mouse]) + } else { + out.WriteString(funcs[t_exit_mouse]) + } + + input_mode = mode + return input_mode +} + +// Sets the termbox output mode. Termbox has four output options: +// +// 1. OutputNormal => [1..8] +// This mode provides 8 different colors: +// black, red, green, yellow, blue, magenta, cyan, white +// Shortcut: ColorBlack, ColorRed, ... +// Attributes: AttrBold, AttrUnderline, AttrReverse +// +// Example usage: +// SetCell(x, y, '@', ColorBlack | AttrBold, ColorRed); +// +// 2. Output256 => [1..256] +// In this mode you can leverage the 256 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 +// +// Example usage: +// SetCell(x, y, '@', 184, 240); +// SetCell(x, y, '@', 0xb8, 0xf0); +// +// 3. Output216 => [1..216] +// This mode supports the 3rd range of the 256 mode only. +// But you don't need to provide an offset. +// +// 4. OutputGrayscale => [1..26] +// This mode supports the 4th range of the 256 mode +// and black and white colors from 3th range of the 256 mode +// But you don't need to provide an offset. +// +// In all modes, 0x00 represents the default color. +// +// `go run _demos/output.go` to see its impact on your terminal. +// +// If 'mode' is OutputCurrent, it returns the current output mode. +// +// Note that this may return a different OutputMode than the one requested, +// as the requested mode may not be available on the target platform. +func SetOutputMode(mode OutputMode) OutputMode { + if mode == OutputCurrent { + return output_mode + } + + output_mode = mode + return output_mode +} + +// Sync comes handy when something causes desync between termbox's understanding +// of a terminal buffer and the reality. Such as a third party process. Sync +// forces a complete resync between the termbox and a terminal, it may not be +// visually pretty though. +func Sync() error { + front_buffer.clear() + err := send_clear() + if err != nil { + return err + } + + return Flush() +} diff --git a/vendor/github.com/awesome-gocui/termbox-go/api_common.go b/vendor/github.com/awesome-gocui/termbox-go/api_common.go new file mode 100644 index 00000000..5ca1371a --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/api_common.go @@ -0,0 +1,187 @@ +// termbox is a library for creating cross-platform text-based interfaces +package termbox + +// public API, common OS agnostic part + +type ( + InputMode int + OutputMode int + EventType uint8 + Modifier uint8 + Key uint16 + Attribute uint16 +) + +// This type represents a termbox event. The 'Mod', 'Key' and 'Ch' fields are +// valid if 'Type' is EventKey. The 'Width' and 'Height' fields are valid if +// 'Type' is EventResize. The 'Err' field is valid if 'Type' is EventError. +type Event struct { + Type EventType // one of Event* constants + Mod Modifier // one of Mod* constants or 0 + Key Key // one of Key* constants, invalid if 'Ch' is not 0 + Ch rune // a unicode character + Width int // width of the screen + Height int // height of the screen + Err error // error in case if input failed + MouseX int // x coord of mouse + MouseY int // y coord of mouse + N int // number of bytes written when getting a raw event +} + +// A cell, single conceptual entity on the screen. The screen is basically a 2d +// array of cells. 'Ch' is a unicode character, 'Fg' and 'Bg' are foreground +// and background attributes respectively. +type Cell struct { + Ch rune + Fg Attribute + Bg Attribute +} + +// To know if termbox has been initialized or not +var ( + IsInit bool = false +) + +// Key constants, see Event.Key field. +const ( + KeyF1 Key = 0xFFFF - iota + KeyF2 + KeyF3 + KeyF4 + KeyF5 + KeyF6 + KeyF7 + KeyF8 + KeyF9 + KeyF10 + KeyF11 + KeyF12 + KeyInsert + KeyDelete + KeyHome + KeyEnd + KeyPgup + KeyPgdn + KeyArrowUp + KeyArrowDown + KeyArrowLeft + KeyArrowRight + key_min // see terminfo + MouseLeft + MouseMiddle + MouseRight + MouseRelease + MouseWheelUp + MouseWheelDown +) + +const ( + KeyCtrlTilde Key = 0x00 + KeyCtrl2 Key = 0x00 + KeyCtrlSpace Key = 0x00 + KeyCtrlA Key = 0x01 + KeyCtrlB Key = 0x02 + KeyCtrlC Key = 0x03 + KeyCtrlD Key = 0x04 + KeyCtrlE Key = 0x05 + KeyCtrlF Key = 0x06 + KeyCtrlG Key = 0x07 + KeyBackspace Key = 0x08 + KeyCtrlH Key = 0x08 + KeyTab Key = 0x09 + KeyCtrlI Key = 0x09 + KeyCtrlJ Key = 0x0A + KeyCtrlK Key = 0x0B + KeyCtrlL Key = 0x0C + KeyEnter Key = 0x0D + KeyCtrlM Key = 0x0D + KeyCtrlN Key = 0x0E + KeyCtrlO Key = 0x0F + KeyCtrlP Key = 0x10 + KeyCtrlQ Key = 0x11 + KeyCtrlR Key = 0x12 + KeyCtrlS Key = 0x13 + KeyCtrlT Key = 0x14 + KeyCtrlU Key = 0x15 + KeyCtrlV Key = 0x16 + KeyCtrlW Key = 0x17 + KeyCtrlX Key = 0x18 + KeyCtrlY Key = 0x19 + KeyCtrlZ Key = 0x1A + KeyEsc Key = 0x1B + KeyCtrlLsqBracket Key = 0x1B + KeyCtrl3 Key = 0x1B + KeyCtrl4 Key = 0x1C + KeyCtrlBackslash Key = 0x1C + KeyCtrl5 Key = 0x1D + KeyCtrlRsqBracket Key = 0x1D + KeyCtrl6 Key = 0x1E + KeyCtrl7 Key = 0x1F + KeyCtrlSlash Key = 0x1F + KeyCtrlUnderscore Key = 0x1F + KeySpace Key = 0x20 + KeyBackspace2 Key = 0x7F + KeyCtrl8 Key = 0x7F +) + +// Alt modifier constant, see Event.Mod field and SetInputMode function. +const ( + ModAlt Modifier = 1 << iota + ModMotion +) + +// Cell colors, you can combine a color with multiple attributes using bitwise +// OR ('|'). +const ( + ColorDefault Attribute = iota + ColorBlack + ColorRed + ColorGreen + ColorYellow + ColorBlue + ColorMagenta + ColorCyan + ColorWhite +) + +// Cell attributes, it is possible to use multiple attributes by combining them +// using bitwise OR ('|'). Although, colors cannot be combined. But you can +// combine attributes and a single color. +// +// It's worth mentioning that some platforms don't support certain attributes. +// For example windows console doesn't support AttrUnderline. And on some +// terminals applying AttrBold to background may result in blinking text. Use +// them with caution and test your code on various terminals. +const ( + AttrBold Attribute = 1 << (iota + 9) + AttrUnderline + AttrReverse +) + +// Input mode. See SetInputMode function. +const ( + InputEsc InputMode = 1 << iota + InputAlt + InputMouse + InputCurrent InputMode = 0 +) + +// Output mode. See SetOutputMode function. +const ( + OutputCurrent OutputMode = iota + OutputNormal + Output256 + Output216 + OutputGrayscale +) + +// Event type. See Event.Type field. +const ( + EventKey EventType = iota + EventResize + EventMouse + EventError + EventInterrupt + EventRaw + EventNone +) diff --git a/vendor/github.com/awesome-gocui/termbox-go/api_windows.go b/vendor/github.com/awesome-gocui/termbox-go/api_windows.go new file mode 100644 index 00000000..5c1e388a --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/api_windows.go @@ -0,0 +1,245 @@ +package termbox + +import ( + "syscall" +) + +// public API + +// Initializes termbox library. This function should be called before any other functions. +// After successful initialization, the library must be finalized using 'Close' function. +// +// Example usage: +// err := termbox.Init() +// if err != nil { +// panic(err) +// } +// defer termbox.Close() +func Init() error { + var err error + + interrupt, err = create_event() + if err != nil { + return err + } + + in, err = syscall.Open("CONIN$", syscall.O_RDWR, 0) + if err != nil { + return err + } + out, err = syscall.Open("CONOUT$", syscall.O_RDWR, 0) + if err != nil { + return err + } + + err = get_console_mode(in, &orig_mode) + if err != nil { + return err + } + + err = set_console_mode(in, enable_window_input) + if err != nil { + return err + } + + orig_size, orig_window = get_term_size(out) + win_size := get_win_size(out) + + err = set_console_screen_buffer_size(out, win_size) + if err != nil { + return err + } + + err = fix_win_size(out, win_size) + if err != nil { + return err + } + + err = get_console_cursor_info(out, &orig_cursor_info) + if err != nil { + return err + } + + show_cursor(false) + term_size, _ = get_term_size(out) + back_buffer.init(int(term_size.x), int(term_size.y)) + front_buffer.init(int(term_size.x), int(term_size.y)) + back_buffer.clear() + front_buffer.clear() + clear() + + diffbuf = make([]diff_msg, 0, 32) + + go input_event_producer() + IsInit = true + return nil +} + +// Finalizes termbox library, should be called after successful initialization +// when termbox's functionality isn't required anymore. +func Close() { + // we ignore errors here, because we can't really do anything about them + Clear(0, 0) + Flush() + + // stop event producer + cancel_comm <- true + set_event(interrupt) + select { + case <-input_comm: + default: + } + <-cancel_done_comm + + set_console_screen_buffer_size(out, orig_size) + set_console_window_info(out, &orig_window) + set_console_cursor_info(out, &orig_cursor_info) + set_console_cursor_position(out, coord{}) + set_console_mode(in, orig_mode) + syscall.Close(in) + syscall.Close(out) + syscall.Close(interrupt) + IsInit = false +} + +// Interrupt an in-progress call to PollEvent by causing it to return +// EventInterrupt. Note that this function will block until the PollEvent +// function has successfully been interrupted. +func Interrupt() { + interrupt_comm <- struct{}{} +} + +// Synchronizes the internal back buffer with the terminal. +func Flush() error { + update_size_maybe() + prepare_diff_messages() + for _, diff := range diffbuf { + r := small_rect{ + left: 0, + top: diff.pos, + right: term_size.x - 1, + bottom: diff.pos + diff.lines - 1, + } + write_console_output(out, diff.chars, r) + } + if !is_cursor_hidden(cursor_x, cursor_y) { + move_cursor(cursor_x, cursor_y) + } + return nil +} + +// Sets the position of the cursor. See also HideCursor(). +func SetCursor(x, y int) { + if is_cursor_hidden(cursor_x, cursor_y) && !is_cursor_hidden(x, y) { + show_cursor(true) + } + + if !is_cursor_hidden(cursor_x, cursor_y) && is_cursor_hidden(x, y) { + show_cursor(false) + } + + cursor_x, cursor_y = x, y + if !is_cursor_hidden(cursor_x, cursor_y) { + move_cursor(cursor_x, cursor_y) + } +} + +// The shortcut for SetCursor(-1, -1). +func HideCursor() { + SetCursor(cursor_hidden, cursor_hidden) +} + +// Changes cell's parameters in the internal back buffer at the specified +// position. +func SetCell(x, y int, ch rune, fg, bg Attribute) { + if x < 0 || x >= back_buffer.width { + return + } + if y < 0 || y >= back_buffer.height { + return + } + + back_buffer.cells[y*back_buffer.width+x] = Cell{ch, fg, bg} +} + +// Returns a slice into the termbox's back buffer. You can get its dimensions +// using 'Size' function. The slice remains valid as long as no 'Clear' or +// 'Flush' function calls were made after call to this function. +func CellBuffer() []Cell { + return back_buffer.cells +} + +// Wait for an event and return it. This is a blocking function call. +func PollEvent() Event { + select { + case ev := <-input_comm: + return ev + case <-interrupt_comm: + return Event{Type: EventInterrupt} + } +} + +// Returns the size of the internal back buffer (which is mostly the same as +// console's window size in characters). But it doesn't always match the size +// of the console window, after the console size has changed, the internal back +// buffer will get in sync only after Clear or Flush function calls. +func Size() (int, int) { + return int(term_size.x), int(term_size.y) +} + +// Clears the internal back buffer. +func Clear(fg, bg Attribute) error { + foreground, background = fg, bg + update_size_maybe() + back_buffer.clear() + return nil +} + +// Sets termbox input mode. Termbox has two input modes: +// +// 1. Esc input mode. When ESC sequence is in the buffer and it doesn't match +// any known sequence. ESC means KeyEsc. This is the default input mode. +// +// 2. Alt input mode. When ESC sequence is in the buffer and it doesn't match +// any known sequence. ESC enables ModAlt modifier for the next keyboard event. +// +// Both input modes can be OR'ed with Mouse mode. Setting Mouse mode bit up will +// enable mouse button press/release and drag events. +// +// If 'mode' is InputCurrent, returns the current input mode. See also Input* +// constants. +func SetInputMode(mode InputMode) InputMode { + if mode == InputCurrent { + return input_mode + } + if mode&InputMouse != 0 { + err := set_console_mode(in, enable_window_input|enable_mouse_input|enable_extended_flags) + if err != nil { + panic(err) + } + } else { + err := set_console_mode(in, enable_window_input) + if err != nil { + panic(err) + } + } + + input_mode = mode + return input_mode +} + +// Sets the termbox output mode. +// +// Windows console does not support extra colour modes, +// so this will always set and return OutputNormal. +func SetOutputMode(mode OutputMode) OutputMode { + return OutputNormal +} + +// Sync comes handy when something causes desync between termbox's understanding +// of a terminal buffer and the reality. Such as a third party process. Sync +// forces a complete resync between the termbox and a terminal, it may not be +// visually pretty though. At the moment on Windows it does nothing. +func Sync() error { + return nil +} diff --git a/vendor/github.com/awesome-gocui/termbox-go/collect_terminfo.py b/vendor/github.com/awesome-gocui/termbox-go/collect_terminfo.py new file mode 100755 index 00000000..5e50975e --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/collect_terminfo.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python + +import sys, os, subprocess + +def escaped(s): + return repr(s)[1:-1] + +def tput(term, name): + try: + return subprocess.check_output(['tput', '-T%s' % term, name]).decode() + except subprocess.CalledProcessError as e: + return e.output.decode() + + +def w(s): + if s == None: + return + sys.stdout.write(s) + +terminals = { + 'xterm' : 'xterm', + 'rxvt-256color' : 'rxvt_256color', + 'rxvt-unicode' : 'rxvt_unicode', + 'linux' : 'linux', + 'Eterm' : 'eterm', + 'screen' : 'screen' +} + +keys = [ + "F1", "kf1", + "F2", "kf2", + "F3", "kf3", + "F4", "kf4", + "F5", "kf5", + "F6", "kf6", + "F7", "kf7", + "F8", "kf8", + "F9", "kf9", + "F10", "kf10", + "F11", "kf11", + "F12", "kf12", + "INSERT", "kich1", + "DELETE", "kdch1", + "HOME", "khome", + "END", "kend", + "PGUP", "kpp", + "PGDN", "knp", + "KEY_UP", "kcuu1", + "KEY_DOWN", "kcud1", + "KEY_LEFT", "kcub1", + "KEY_RIGHT", "kcuf1" +] + +funcs = [ + "T_ENTER_CA", "smcup", + "T_EXIT_CA", "rmcup", + "T_SHOW_CURSOR", "cnorm", + "T_HIDE_CURSOR", "civis", + "T_CLEAR_SCREEN", "clear", + "T_SGR0", "sgr0", + "T_UNDERLINE", "smul", + "T_BOLD", "bold", + "T_BLINK", "blink", + "T_REVERSE", "rev", + "T_ENTER_KEYPAD", "smkx", + "T_EXIT_KEYPAD", "rmkx" +] + +def iter_pairs(iterable): + iterable = iter(iterable) + while True: + yield (next(iterable), next(iterable)) + +def do_term(term, nick): + w("// %s\n" % term) + w("var %s_keys = []string{\n\t" % nick) + for k, v in iter_pairs(keys): + w('"') + w(escaped(tput(term, v))) + w('",') + w("\n}\n") + w("var %s_funcs = []string{\n\t" % nick) + for k,v in iter_pairs(funcs): + w('"') + if v == "sgr": + w("\\033[3%d;4%dm") + elif v == "cup": + w("\\033[%d;%dH") + else: + w(escaped(tput(term, v))) + w('", ') + w("\n}\n\n") + +def do_terms(d): + w("var terms = []struct {\n") + w("\tname string\n") + w("\tkeys []string\n") + w("\tfuncs []string\n") + w("}{\n") + for k, v in d.items(): + w('\t{"%s", %s_keys, %s_funcs},\n' % (k, v, v)) + w("}\n\n") + +w("// +build !windows\n\npackage termbox\n\n") + +for k,v in terminals.items(): + do_term(k, v) + +do_terms(terminals) + diff --git a/vendor/github.com/awesome-gocui/termbox-go/escwait.go b/vendor/github.com/awesome-gocui/termbox-go/escwait.go new file mode 100644 index 00000000..b7bbb891 --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/escwait.go @@ -0,0 +1,11 @@ +// +build !darwin + +package termbox + +// On all systems other than macOS, disable behavior which will wait before +// deciding that the escape key was pressed, to account for partially send +// escape sequences, especially with regard to lengthy mouse sequences. +// See https://github.com/nsf/termbox-go/issues/132 +func enable_wait_for_escape_sequence() bool { + return false +} diff --git a/vendor/github.com/awesome-gocui/termbox-go/escwait_darwin.go b/vendor/github.com/awesome-gocui/termbox-go/escwait_darwin.go new file mode 100644 index 00000000..dde69b6c --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/escwait_darwin.go @@ -0,0 +1,9 @@ +package termbox + +// On macOS, enable behavior which will wait before deciding that the escape +// key was pressed, to account for partially send escape sequences, especially +// with regard to lengthy mouse sequences. +// See https://github.com/nsf/termbox-go/issues/132 +func enable_wait_for_escape_sequence() bool { + return true +} diff --git a/vendor/github.com/awesome-gocui/termbox-go/syscalls.go b/vendor/github.com/awesome-gocui/termbox-go/syscalls.go new file mode 100644 index 00000000..4f52bb9a --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/syscalls.go @@ -0,0 +1,39 @@ +// +build ignore + +package termbox + +/* +#include +#include +*/ +import "C" + +type syscall_Termios C.struct_termios + +const ( + syscall_IGNBRK = C.IGNBRK + syscall_BRKINT = C.BRKINT + syscall_PARMRK = C.PARMRK + syscall_ISTRIP = C.ISTRIP + syscall_INLCR = C.INLCR + syscall_IGNCR = C.IGNCR + syscall_ICRNL = C.ICRNL + syscall_IXON = C.IXON + syscall_OPOST = C.OPOST + syscall_ECHO = C.ECHO + syscall_ECHONL = C.ECHONL + syscall_ICANON = C.ICANON + syscall_ISIG = C.ISIG + syscall_IEXTEN = C.IEXTEN + syscall_CSIZE = C.CSIZE + syscall_PARENB = C.PARENB + syscall_CS8 = C.CS8 + syscall_VMIN = C.VMIN + syscall_VTIME = C.VTIME + + // on darwin change these to (on *bsd too?): + // C.TIOCGETA + // C.TIOCSETA + syscall_TCGETS = C.TCGETS + syscall_TCSETS = C.TCSETS +) diff --git a/vendor/github.com/awesome-gocui/termbox-go/syscalls_darwin.go b/vendor/github.com/awesome-gocui/termbox-go/syscalls_darwin.go new file mode 100644 index 00000000..25b78f7a --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/syscalls_darwin.go @@ -0,0 +1,41 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs syscalls.go + +// +build !amd64 + +package termbox + +type syscall_Termios struct { + Iflag uint32 + Oflag uint32 + Cflag uint32 + Lflag uint32 + Cc [20]uint8 + Ispeed uint32 + Ospeed uint32 +} + +const ( + syscall_IGNBRK = 0x1 + syscall_BRKINT = 0x2 + syscall_PARMRK = 0x8 + syscall_ISTRIP = 0x20 + syscall_INLCR = 0x40 + syscall_IGNCR = 0x80 + syscall_ICRNL = 0x100 + syscall_IXON = 0x200 + syscall_OPOST = 0x1 + syscall_ECHO = 0x8 + syscall_ECHONL = 0x10 + syscall_ICANON = 0x100 + syscall_ISIG = 0x80 + syscall_IEXTEN = 0x400 + syscall_CSIZE = 0x300 + syscall_PARENB = 0x1000 + syscall_CS8 = 0x300 + syscall_VMIN = 0x10 + syscall_VTIME = 0x11 + + syscall_TCGETS = 0x402c7413 + syscall_TCSETS = 0x802c7414 +) diff --git a/vendor/github.com/awesome-gocui/termbox-go/syscalls_darwin_amd64.go b/vendor/github.com/awesome-gocui/termbox-go/syscalls_darwin_amd64.go new file mode 100644 index 00000000..11f25be7 --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/syscalls_darwin_amd64.go @@ -0,0 +1,40 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs syscalls.go + +package termbox + +type syscall_Termios struct { + Iflag uint64 + Oflag uint64 + Cflag uint64 + Lflag uint64 + Cc [20]uint8 + Pad_cgo_0 [4]byte + Ispeed uint64 + Ospeed uint64 +} + +const ( + syscall_IGNBRK = 0x1 + syscall_BRKINT = 0x2 + syscall_PARMRK = 0x8 + syscall_ISTRIP = 0x20 + syscall_INLCR = 0x40 + syscall_IGNCR = 0x80 + syscall_ICRNL = 0x100 + syscall_IXON = 0x200 + syscall_OPOST = 0x1 + syscall_ECHO = 0x8 + syscall_ECHONL = 0x10 + syscall_ICANON = 0x100 + syscall_ISIG = 0x80 + syscall_IEXTEN = 0x400 + syscall_CSIZE = 0x300 + syscall_PARENB = 0x1000 + syscall_CS8 = 0x300 + syscall_VMIN = 0x10 + syscall_VTIME = 0x11 + + syscall_TCGETS = 0x40487413 + syscall_TCSETS = 0x80487414 +) diff --git a/vendor/github.com/awesome-gocui/termbox-go/syscalls_dragonfly.go b/vendor/github.com/awesome-gocui/termbox-go/syscalls_dragonfly.go new file mode 100644 index 00000000..e03624eb --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/syscalls_dragonfly.go @@ -0,0 +1,39 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs syscalls.go + +package termbox + +type syscall_Termios struct { + Iflag uint32 + Oflag uint32 + Cflag uint32 + Lflag uint32 + Cc [20]uint8 + Ispeed uint32 + Ospeed uint32 +} + +const ( + syscall_IGNBRK = 0x1 + syscall_BRKINT = 0x2 + syscall_PARMRK = 0x8 + syscall_ISTRIP = 0x20 + syscall_INLCR = 0x40 + syscall_IGNCR = 0x80 + syscall_ICRNL = 0x100 + syscall_IXON = 0x200 + syscall_OPOST = 0x1 + syscall_ECHO = 0x8 + syscall_ECHONL = 0x10 + syscall_ICANON = 0x100 + syscall_ISIG = 0x80 + syscall_IEXTEN = 0x400 + syscall_CSIZE = 0x300 + syscall_PARENB = 0x1000 + syscall_CS8 = 0x300 + syscall_VMIN = 0x10 + syscall_VTIME = 0x11 + + syscall_TCGETS = 0x402c7413 + syscall_TCSETS = 0x802c7414 +) diff --git a/vendor/github.com/awesome-gocui/termbox-go/syscalls_freebsd.go b/vendor/github.com/awesome-gocui/termbox-go/syscalls_freebsd.go new file mode 100644 index 00000000..e03624eb --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/syscalls_freebsd.go @@ -0,0 +1,39 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs syscalls.go + +package termbox + +type syscall_Termios struct { + Iflag uint32 + Oflag uint32 + Cflag uint32 + Lflag uint32 + Cc [20]uint8 + Ispeed uint32 + Ospeed uint32 +} + +const ( + syscall_IGNBRK = 0x1 + syscall_BRKINT = 0x2 + syscall_PARMRK = 0x8 + syscall_ISTRIP = 0x20 + syscall_INLCR = 0x40 + syscall_IGNCR = 0x80 + syscall_ICRNL = 0x100 + syscall_IXON = 0x200 + syscall_OPOST = 0x1 + syscall_ECHO = 0x8 + syscall_ECHONL = 0x10 + syscall_ICANON = 0x100 + syscall_ISIG = 0x80 + syscall_IEXTEN = 0x400 + syscall_CSIZE = 0x300 + syscall_PARENB = 0x1000 + syscall_CS8 = 0x300 + syscall_VMIN = 0x10 + syscall_VTIME = 0x11 + + syscall_TCGETS = 0x402c7413 + syscall_TCSETS = 0x802c7414 +) diff --git a/vendor/github.com/awesome-gocui/termbox-go/syscalls_linux.go b/vendor/github.com/awesome-gocui/termbox-go/syscalls_linux.go new file mode 100644 index 00000000..b88960de --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/syscalls_linux.go @@ -0,0 +1,33 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs syscalls.go + +package termbox + +import "syscall" + +type syscall_Termios syscall.Termios + +const ( + syscall_IGNBRK = syscall.IGNBRK + syscall_BRKINT = syscall.BRKINT + syscall_PARMRK = syscall.PARMRK + syscall_ISTRIP = syscall.ISTRIP + syscall_INLCR = syscall.INLCR + syscall_IGNCR = syscall.IGNCR + syscall_ICRNL = syscall.ICRNL + syscall_IXON = syscall.IXON + syscall_OPOST = syscall.OPOST + syscall_ECHO = syscall.ECHO + syscall_ECHONL = syscall.ECHONL + syscall_ICANON = syscall.ICANON + syscall_ISIG = syscall.ISIG + syscall_IEXTEN = syscall.IEXTEN + syscall_CSIZE = syscall.CSIZE + syscall_PARENB = syscall.PARENB + syscall_CS8 = syscall.CS8 + syscall_VMIN = syscall.VMIN + syscall_VTIME = syscall.VTIME + + syscall_TCGETS = syscall.TCGETS + syscall_TCSETS = syscall.TCSETS +) diff --git a/vendor/github.com/awesome-gocui/termbox-go/syscalls_netbsd.go b/vendor/github.com/awesome-gocui/termbox-go/syscalls_netbsd.go new file mode 100644 index 00000000..49a3355b --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/syscalls_netbsd.go @@ -0,0 +1,39 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs syscalls.go + +package termbox + +type syscall_Termios struct { + Iflag uint32 + Oflag uint32 + Cflag uint32 + Lflag uint32 + Cc [20]uint8 + Ispeed int32 + Ospeed int32 +} + +const ( + syscall_IGNBRK = 0x1 + syscall_BRKINT = 0x2 + syscall_PARMRK = 0x8 + syscall_ISTRIP = 0x20 + syscall_INLCR = 0x40 + syscall_IGNCR = 0x80 + syscall_ICRNL = 0x100 + syscall_IXON = 0x200 + syscall_OPOST = 0x1 + syscall_ECHO = 0x8 + syscall_ECHONL = 0x10 + syscall_ICANON = 0x100 + syscall_ISIG = 0x80 + syscall_IEXTEN = 0x400 + syscall_CSIZE = 0x300 + syscall_PARENB = 0x1000 + syscall_CS8 = 0x300 + syscall_VMIN = 0x10 + syscall_VTIME = 0x11 + + syscall_TCGETS = 0x402c7413 + syscall_TCSETS = 0x802c7414 +) diff --git a/vendor/github.com/awesome-gocui/termbox-go/syscalls_openbsd.go b/vendor/github.com/awesome-gocui/termbox-go/syscalls_openbsd.go new file mode 100644 index 00000000..49a3355b --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/syscalls_openbsd.go @@ -0,0 +1,39 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs syscalls.go + +package termbox + +type syscall_Termios struct { + Iflag uint32 + Oflag uint32 + Cflag uint32 + Lflag uint32 + Cc [20]uint8 + Ispeed int32 + Ospeed int32 +} + +const ( + syscall_IGNBRK = 0x1 + syscall_BRKINT = 0x2 + syscall_PARMRK = 0x8 + syscall_ISTRIP = 0x20 + syscall_INLCR = 0x40 + syscall_IGNCR = 0x80 + syscall_ICRNL = 0x100 + syscall_IXON = 0x200 + syscall_OPOST = 0x1 + syscall_ECHO = 0x8 + syscall_ECHONL = 0x10 + syscall_ICANON = 0x100 + syscall_ISIG = 0x80 + syscall_IEXTEN = 0x400 + syscall_CSIZE = 0x300 + syscall_PARENB = 0x1000 + syscall_CS8 = 0x300 + syscall_VMIN = 0x10 + syscall_VTIME = 0x11 + + syscall_TCGETS = 0x402c7413 + syscall_TCSETS = 0x802c7414 +) diff --git a/vendor/github.com/awesome-gocui/termbox-go/syscalls_windows.go b/vendor/github.com/awesome-gocui/termbox-go/syscalls_windows.go new file mode 100644 index 00000000..472d002a --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/syscalls_windows.go @@ -0,0 +1,61 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs -- -DUNICODE syscalls.go + +package termbox + +const ( + foreground_blue = 0x1 + foreground_green = 0x2 + foreground_red = 0x4 + foreground_intensity = 0x8 + background_blue = 0x10 + background_green = 0x20 + background_red = 0x40 + background_intensity = 0x80 + std_input_handle = -0xa + std_output_handle = -0xb + key_event = 0x1 + mouse_event = 0x2 + window_buffer_size_event = 0x4 + enable_window_input = 0x8 + enable_mouse_input = 0x10 + enable_extended_flags = 0x80 + + vk_f1 = 0x70 + vk_f2 = 0x71 + vk_f3 = 0x72 + vk_f4 = 0x73 + vk_f5 = 0x74 + vk_f6 = 0x75 + vk_f7 = 0x76 + vk_f8 = 0x77 + vk_f9 = 0x78 + vk_f10 = 0x79 + vk_f11 = 0x7a + vk_f12 = 0x7b + vk_insert = 0x2d + vk_delete = 0x2e + vk_home = 0x24 + vk_end = 0x23 + vk_pgup = 0x21 + vk_pgdn = 0x22 + vk_arrow_up = 0x26 + vk_arrow_down = 0x28 + vk_arrow_left = 0x25 + vk_arrow_right = 0x27 + vk_backspace = 0x8 + vk_tab = 0x9 + vk_enter = 0xd + vk_esc = 0x1b + vk_space = 0x20 + + left_alt_pressed = 0x2 + left_ctrl_pressed = 0x8 + right_alt_pressed = 0x1 + right_ctrl_pressed = 0x4 + shift_pressed = 0x10 + + generic_read = 0x80000000 + generic_write = 0x40000000 + console_textmode_buffer = 0x1 +) diff --git a/vendor/github.com/awesome-gocui/termbox-go/termbox.go b/vendor/github.com/awesome-gocui/termbox-go/termbox.go new file mode 100644 index 00000000..fbe4c3de --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/termbox.go @@ -0,0 +1,529 @@ +// +build !windows + +package termbox + +import "unicode/utf8" +import "bytes" +import "syscall" +import "unsafe" +import "strings" +import "strconv" +import "os" +import "io" + +// private API + +const ( + t_enter_ca = iota + t_exit_ca + t_show_cursor + t_hide_cursor + t_clear_screen + t_sgr0 + t_underline + t_bold + t_blink + t_reverse + t_enter_keypad + t_exit_keypad + t_enter_mouse + t_exit_mouse + t_max_funcs +) + +const ( + coord_invalid = -2 + attr_invalid = Attribute(0xFFFF) +) + +type input_event struct { + data []byte + err error +} + +type extract_event_res int + +const ( + event_not_extracted extract_event_res = iota + event_extracted + esc_wait +) + +var ( + // term specific sequences + keys []string + funcs []string + + // termbox inner state + orig_tios syscall_Termios + back_buffer cellbuf + front_buffer cellbuf + termw int + termh int + input_mode = InputEsc + output_mode = OutputNormal + out *os.File + in int + lastfg = attr_invalid + lastbg = attr_invalid + lastx = coord_invalid + lasty = coord_invalid + cursor_x = cursor_hidden + cursor_y = cursor_hidden + foreground = ColorDefault + background = ColorDefault + inbuf = make([]byte, 0, 64) + outbuf bytes.Buffer + sigwinch = make(chan os.Signal, 1) + sigio = make(chan os.Signal, 1) + quit = make(chan int) + input_comm = make(chan input_event) + interrupt_comm = make(chan struct{}) + intbuf = make([]byte, 0, 16) + + // grayscale indexes + grayscale = []Attribute{ + 0, 17, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, + 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 232, + } +) + +func write_cursor(x, y int) { + outbuf.WriteString("\033[") + outbuf.Write(strconv.AppendUint(intbuf, uint64(y+1), 10)) + outbuf.WriteString(";") + outbuf.Write(strconv.AppendUint(intbuf, uint64(x+1), 10)) + outbuf.WriteString("H") +} + +func write_sgr_fg(a Attribute) { + switch output_mode { + case Output256, Output216, OutputGrayscale: + outbuf.WriteString("\033[38;5;") + outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10)) + outbuf.WriteString("m") + default: + outbuf.WriteString("\033[3") + outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10)) + outbuf.WriteString("m") + } +} + +func write_sgr_bg(a Attribute) { + switch output_mode { + case Output256, Output216, OutputGrayscale: + outbuf.WriteString("\033[48;5;") + outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10)) + outbuf.WriteString("m") + default: + outbuf.WriteString("\033[4") + outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10)) + outbuf.WriteString("m") + } +} + +func write_sgr(fg, bg Attribute) { + switch output_mode { + case Output256, Output216, OutputGrayscale: + outbuf.WriteString("\033[38;5;") + outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-1), 10)) + outbuf.WriteString("m") + outbuf.WriteString("\033[48;5;") + outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-1), 10)) + outbuf.WriteString("m") + default: + outbuf.WriteString("\033[3") + outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-1), 10)) + outbuf.WriteString(";4") + outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-1), 10)) + outbuf.WriteString("m") + } +} + +type winsize struct { + rows uint16 + cols uint16 + xpixels uint16 + ypixels uint16 +} + +func get_term_size(fd uintptr) (int, int) { + var sz winsize + _, _, _ = syscall.Syscall(syscall.SYS_IOCTL, + fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz))) + return int(sz.cols), int(sz.rows) +} + +func send_attr(fg, bg Attribute) { + if fg == lastfg && bg == lastbg { + return + } + + outbuf.WriteString(funcs[t_sgr0]) + + var fgcol, bgcol Attribute + + switch output_mode { + case Output256: + fgcol = fg & 0x1FF + bgcol = bg & 0x1FF + case Output216: + fgcol = fg & 0xFF + bgcol = bg & 0xFF + if fgcol > 216 { + fgcol = ColorDefault + } + if bgcol > 216 { + bgcol = ColorDefault + } + if fgcol != ColorDefault { + fgcol += 0x10 + } + if bgcol != ColorDefault { + bgcol += 0x10 + } + case OutputGrayscale: + fgcol = fg & 0x1F + bgcol = bg & 0x1F + if fgcol > 26 { + fgcol = ColorDefault + } + if bgcol > 26 { + bgcol = ColorDefault + } + if fgcol != ColorDefault { + fgcol = grayscale[fgcol] + } + if bgcol != ColorDefault { + bgcol = grayscale[bgcol] + } + default: + fgcol = fg & 0x0F + bgcol = bg & 0x0F + } + + if fgcol != ColorDefault { + if bgcol != ColorDefault { + write_sgr(fgcol, bgcol) + } else { + write_sgr_fg(fgcol) + } + } else if bgcol != ColorDefault { + write_sgr_bg(bgcol) + } + + if fg&AttrBold != 0 { + outbuf.WriteString(funcs[t_bold]) + } + if bg&AttrBold != 0 { + outbuf.WriteString(funcs[t_blink]) + } + if fg&AttrUnderline != 0 { + outbuf.WriteString(funcs[t_underline]) + } + if fg&AttrReverse|bg&AttrReverse != 0 { + outbuf.WriteString(funcs[t_reverse]) + } + + lastfg, lastbg = fg, bg +} + +func send_char(x, y int, ch rune) { + var buf [8]byte + n := utf8.EncodeRune(buf[:], ch) + if x-1 != lastx || y != lasty { + write_cursor(x, y) + } + lastx, lasty = x, y + outbuf.Write(buf[:n]) +} + +func flush() error { + _, err := io.Copy(out, &outbuf) + outbuf.Reset() + return err +} + +func send_clear() error { + send_attr(foreground, background) + outbuf.WriteString(funcs[t_clear_screen]) + if !is_cursor_hidden(cursor_x, cursor_y) { + write_cursor(cursor_x, cursor_y) + } + + // we need to invalidate cursor position too and these two vars are + // used only for simple cursor positioning optimization, cursor + // actually may be in the correct place, but we simply discard + // optimization once and it gives us simple solution for the case when + // cursor moved + lastx = coord_invalid + lasty = coord_invalid + + return flush() +} + +func update_size_maybe() error { + w, h := get_term_size(out.Fd()) + if w != termw || h != termh { + termw, termh = w, h + back_buffer.resize(termw, termh) + front_buffer.resize(termw, termh) + front_buffer.clear() + return send_clear() + } + return nil +} + +func tcsetattr(fd uintptr, termios *syscall_Termios) error { + r, _, e := syscall.Syscall(syscall.SYS_IOCTL, + fd, uintptr(syscall_TCSETS), uintptr(unsafe.Pointer(termios))) + if r != 0 { + return os.NewSyscallError("SYS_IOCTL", e) + } + return nil +} + +func tcgetattr(fd uintptr, termios *syscall_Termios) error { + r, _, e := syscall.Syscall(syscall.SYS_IOCTL, + fd, uintptr(syscall_TCGETS), uintptr(unsafe.Pointer(termios))) + if r != 0 { + return os.NewSyscallError("SYS_IOCTL", e) + } + return nil +} + +func parse_mouse_event(event *Event, buf string) (int, bool) { + if strings.HasPrefix(buf, "\033[M") && len(buf) >= 6 { + // X10 mouse encoding, the simplest one + // \033 [ M Cb Cx Cy + b := buf[3] - 32 + switch b & 3 { + case 0: + if b&64 != 0 { + event.Key = MouseWheelUp + } else { + event.Key = MouseLeft + } + case 1: + if b&64 != 0 { + event.Key = MouseWheelDown + } else { + event.Key = MouseMiddle + } + case 2: + event.Key = MouseRight + case 3: + event.Key = MouseRelease + default: + return 6, false + } + event.Type = EventMouse // KeyEvent by default + if b&32 != 0 { + event.Mod |= ModMotion + } + + // the coord is 1,1 for upper left + event.MouseX = int(buf[4]) - 1 - 32 + event.MouseY = int(buf[5]) - 1 - 32 + return 6, true + } else if strings.HasPrefix(buf, "\033[<") || strings.HasPrefix(buf, "\033[") { + // xterm 1006 extended mode or urxvt 1015 extended mode + // xterm: \033 [ < Cb ; Cx ; Cy (M or m) + // urxvt: \033 [ Cb ; Cx ; Cy M + + // find the first M or m, that's where we stop + mi := strings.IndexAny(buf, "Mm") + if mi == -1 { + return 0, false + } + + // whether it's a capital M or not + isM := buf[mi] == 'M' + + // whether it's urxvt or not + isU := false + + // buf[2] is safe here, because having M or m found means we have at + // least 3 bytes in a string + if buf[2] == '<' { + buf = buf[3:mi] + } else { + isU = true + buf = buf[2:mi] + } + + s1 := strings.Index(buf, ";") + s2 := strings.LastIndex(buf, ";") + // not found or only one ';' + if s1 == -1 || s2 == -1 || s1 == s2 { + return 0, false + } + + n1, err := strconv.ParseInt(buf[0:s1], 10, 64) + if err != nil { + return 0, false + } + n2, err := strconv.ParseInt(buf[s1+1:s2], 10, 64) + if err != nil { + return 0, false + } + n3, err := strconv.ParseInt(buf[s2+1:], 10, 64) + if err != nil { + return 0, false + } + + // on urxvt, first number is encoded exactly as in X10, but we need to + // make it zero-based, on xterm it is zero-based already + if isU { + n1 -= 32 + } + switch n1 & 3 { + case 0: + if n1&64 != 0 { + event.Key = MouseWheelUp + } else { + event.Key = MouseLeft + } + case 1: + if n1&64 != 0 { + event.Key = MouseWheelDown + } else { + event.Key = MouseMiddle + } + case 2: + event.Key = MouseRight + case 3: + event.Key = MouseRelease + default: + return mi + 1, false + } + if !isM { + // on xterm mouse release is signaled by lowercase m + event.Key = MouseRelease + } + + event.Type = EventMouse // KeyEvent by default + if n1&32 != 0 { + event.Mod |= ModMotion + } + + event.MouseX = int(n2) - 1 + event.MouseY = int(n3) - 1 + return mi + 1, true + } + + return 0, false +} + +func parse_escape_sequence(event *Event, buf []byte) (int, bool) { + bufstr := string(buf) + for i, key := range keys { + if strings.HasPrefix(bufstr, key) { + event.Ch = 0 + event.Key = Key(0xFFFF - i) + return len(key), true + } + } + + // if none of the keys match, let's try mouse sequences + return parse_mouse_event(event, bufstr) +} + +func extract_raw_event(data []byte, event *Event) bool { + if len(inbuf) == 0 { + return false + } + + n := len(data) + if n == 0 { + return false + } + + n = copy(data, inbuf) + copy(inbuf, inbuf[n:]) + inbuf = inbuf[:len(inbuf)-n] + + event.N = n + event.Type = EventRaw + return true +} + +func extract_event(inbuf []byte, event *Event, allow_esc_wait bool) extract_event_res { + if len(inbuf) == 0 { + event.N = 0 + return event_not_extracted + } + + if inbuf[0] == '\033' { + // possible escape sequence + if n, ok := parse_escape_sequence(event, inbuf); n != 0 { + event.N = n + if ok { + return event_extracted + } else { + return event_not_extracted + } + } + + // possible partially read escape sequence; trigger a wait if appropriate + if enable_wait_for_escape_sequence() && allow_esc_wait { + event.N = 0 + return esc_wait + } + + // it's not escape sequence, then it's Alt or Esc, check input_mode + switch { + case input_mode&InputEsc != 0: + // if we're in escape mode, fill Esc event, pop buffer, return success + event.Ch = 0 + event.Key = KeyEsc + event.Mod = 0 + event.N = 1 + return event_extracted + case input_mode&InputAlt != 0: + // if we're in alt mode, set Alt modifier to event and redo parsing + event.Mod = ModAlt + status := extract_event(inbuf[1:], event, false) + if status == event_extracted { + event.N++ + } else { + event.N = 0 + } + return status + default: + panic("unreachable") + } + } + + // if we're here, this is not an escape sequence and not an alt sequence + // so, it's a FUNCTIONAL KEY or a UNICODE character + + // first of all check if it's a functional key + if Key(inbuf[0]) <= KeySpace || Key(inbuf[0]) == KeyBackspace2 { + // fill event, pop buffer, return success + event.Ch = 0 + event.Key = Key(inbuf[0]) + event.N = 1 + return event_extracted + } + + // the only possible option is utf8 rune + if r, n := utf8.DecodeRune(inbuf); r != utf8.RuneError { + event.Ch = r + event.Key = 0 + event.N = n + return event_extracted + } + + return event_not_extracted +} + +func fcntl(fd int, cmd int, arg int) (val int, err error) { + r, _, e := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), uintptr(cmd), + uintptr(arg)) + val = int(r) + if e != 0 { + err = e + } + return +} diff --git a/vendor/github.com/awesome-gocui/termbox-go/termbox_common.go b/vendor/github.com/awesome-gocui/termbox-go/termbox_common.go new file mode 100644 index 00000000..c3355cc2 --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/termbox_common.go @@ -0,0 +1,59 @@ +package termbox + +// private API, common OS agnostic part + +type cellbuf struct { + width int + height int + cells []Cell +} + +func (this *cellbuf) init(width, height int) { + this.width = width + this.height = height + this.cells = make([]Cell, width*height) +} + +func (this *cellbuf) resize(width, height int) { + if this.width == width && this.height == height { + return + } + + oldw := this.width + oldh := this.height + oldcells := this.cells + + this.init(width, height) + this.clear() + + minw, minh := oldw, oldh + + if width < minw { + minw = width + } + if height < minh { + minh = height + } + + for i := 0; i < minh; i++ { + srco, dsto := i*oldw, i*width + src := oldcells[srco : srco+minw] + dst := this.cells[dsto : dsto+minw] + copy(dst, src) + } +} + +func (this *cellbuf) clear() { + for i := range this.cells { + c := &this.cells[i] + c.Ch = ' ' + c.Fg = foreground + c.Bg = background + } +} + +const cursor_hidden = -1 + +func is_cursor_hidden(x, y int) bool { + return x == cursor_hidden || y == cursor_hidden +} diff --git a/vendor/github.com/awesome-gocui/termbox-go/termbox_windows.go b/vendor/github.com/awesome-gocui/termbox-go/termbox_windows.go new file mode 100644 index 00000000..22e0f9ea --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/termbox_windows.go @@ -0,0 +1,948 @@ +package termbox + +import "math" +import "syscall" +import "unsafe" +import "unicode/utf16" +import "github.com/mattn/go-runewidth" + +type ( + wchar uint16 + short int16 + dword uint32 + word uint16 + char_info struct { + char wchar + attr word + } + coord struct { + x short + y short + } + small_rect struct { + left short + top short + right short + bottom short + } + console_screen_buffer_info struct { + size coord + cursor_position coord + attributes word + window small_rect + maximum_window_size coord + } + console_cursor_info struct { + size dword + visible int32 + } + input_record struct { + event_type word + _ [2]byte + event [16]byte + } + key_event_record struct { + key_down int32 + repeat_count word + virtual_key_code word + virtual_scan_code word + unicode_char wchar + control_key_state dword + } + window_buffer_size_record struct { + size coord + } + mouse_event_record struct { + mouse_pos coord + button_state dword + control_key_state dword + event_flags dword + } + console_font_info struct { + font uint32 + font_size coord + } +) + +const ( + mouse_lmb = 0x1 + mouse_rmb = 0x2 + mouse_mmb = 0x4 | 0x8 | 0x10 + SM_CXMIN = 28 + SM_CYMIN = 29 +) + +func (this coord) uintptr() uintptr { + return uintptr(*(*int32)(unsafe.Pointer(&this))) +} + +func (this *small_rect) uintptr() uintptr { + return uintptr(unsafe.Pointer(this)) +} + +var kernel32 = syscall.NewLazyDLL("kernel32.dll") +var moduser32 = syscall.NewLazyDLL("user32.dll") +var is_cjk = runewidth.IsEastAsian() + +var ( + proc_set_console_active_screen_buffer = kernel32.NewProc("SetConsoleActiveScreenBuffer") + proc_set_console_screen_buffer_size = kernel32.NewProc("SetConsoleScreenBufferSize") + proc_set_console_window_info = kernel32.NewProc("SetConsoleWindowInfo") + proc_create_console_screen_buffer = kernel32.NewProc("CreateConsoleScreenBuffer") + proc_get_console_screen_buffer_info = kernel32.NewProc("GetConsoleScreenBufferInfo") + proc_write_console_output = kernel32.NewProc("WriteConsoleOutputW") + proc_write_console_output_character = kernel32.NewProc("WriteConsoleOutputCharacterW") + proc_write_console_output_attribute = kernel32.NewProc("WriteConsoleOutputAttribute") + proc_set_console_cursor_info = kernel32.NewProc("SetConsoleCursorInfo") + proc_set_console_cursor_position = kernel32.NewProc("SetConsoleCursorPosition") + proc_get_console_cursor_info = kernel32.NewProc("GetConsoleCursorInfo") + proc_read_console_input = kernel32.NewProc("ReadConsoleInputW") + proc_get_console_mode = kernel32.NewProc("GetConsoleMode") + proc_set_console_mode = kernel32.NewProc("SetConsoleMode") + proc_fill_console_output_character = kernel32.NewProc("FillConsoleOutputCharacterW") + proc_fill_console_output_attribute = kernel32.NewProc("FillConsoleOutputAttribute") + proc_create_event = kernel32.NewProc("CreateEventW") + proc_wait_for_multiple_objects = kernel32.NewProc("WaitForMultipleObjects") + proc_set_event = kernel32.NewProc("SetEvent") + proc_get_current_console_font = kernel32.NewProc("GetCurrentConsoleFont") + get_system_metrics = moduser32.NewProc("GetSystemMetrics") +) + +func set_console_active_screen_buffer(h syscall.Handle) (err error) { + r0, _, e1 := syscall.Syscall(proc_set_console_active_screen_buffer.Addr(), + 1, uintptr(h), 0, 0) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func set_console_screen_buffer_size(h syscall.Handle, size coord) (err error) { + r0, _, e1 := syscall.Syscall(proc_set_console_screen_buffer_size.Addr(), + 2, uintptr(h), size.uintptr(), 0) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func set_console_window_info(h syscall.Handle, window *small_rect) (err error) { + var absolute uint32 + absolute = 1 + r0, _, e1 := syscall.Syscall(proc_set_console_window_info.Addr(), + 3, uintptr(h), uintptr(absolute), window.uintptr()) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func create_console_screen_buffer() (h syscall.Handle, err error) { + r0, _, e1 := syscall.Syscall6(proc_create_console_screen_buffer.Addr(), + 5, uintptr(generic_read|generic_write), 0, 0, console_textmode_buffer, 0, 0) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return syscall.Handle(r0), err +} + +func get_console_screen_buffer_info(h syscall.Handle, info *console_screen_buffer_info) (err error) { + r0, _, e1 := syscall.Syscall(proc_get_console_screen_buffer_info.Addr(), + 2, uintptr(h), uintptr(unsafe.Pointer(info)), 0) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func write_console_output(h syscall.Handle, chars []char_info, dst small_rect) (err error) { + tmp_coord = coord{dst.right - dst.left + 1, dst.bottom - dst.top + 1} + tmp_rect = dst + r0, _, e1 := syscall.Syscall6(proc_write_console_output.Addr(), + 5, uintptr(h), uintptr(unsafe.Pointer(&chars[0])), tmp_coord.uintptr(), + tmp_coord0.uintptr(), uintptr(unsafe.Pointer(&tmp_rect)), 0) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func write_console_output_character(h syscall.Handle, chars []wchar, pos coord) (err error) { + r0, _, e1 := syscall.Syscall6(proc_write_console_output_character.Addr(), + 5, uintptr(h), uintptr(unsafe.Pointer(&chars[0])), uintptr(len(chars)), + pos.uintptr(), uintptr(unsafe.Pointer(&tmp_arg)), 0) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func write_console_output_attribute(h syscall.Handle, attrs []word, pos coord) (err error) { + r0, _, e1 := syscall.Syscall6(proc_write_console_output_attribute.Addr(), + 5, uintptr(h), uintptr(unsafe.Pointer(&attrs[0])), uintptr(len(attrs)), + pos.uintptr(), uintptr(unsafe.Pointer(&tmp_arg)), 0) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func set_console_cursor_info(h syscall.Handle, info *console_cursor_info) (err error) { + r0, _, e1 := syscall.Syscall(proc_set_console_cursor_info.Addr(), + 2, uintptr(h), uintptr(unsafe.Pointer(info)), 0) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func get_console_cursor_info(h syscall.Handle, info *console_cursor_info) (err error) { + r0, _, e1 := syscall.Syscall(proc_get_console_cursor_info.Addr(), + 2, uintptr(h), uintptr(unsafe.Pointer(info)), 0) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func set_console_cursor_position(h syscall.Handle, pos coord) (err error) { + r0, _, e1 := syscall.Syscall(proc_set_console_cursor_position.Addr(), + 2, uintptr(h), pos.uintptr(), 0) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func read_console_input(h syscall.Handle, record *input_record) (err error) { + r0, _, e1 := syscall.Syscall6(proc_read_console_input.Addr(), + 4, uintptr(h), uintptr(unsafe.Pointer(record)), 1, uintptr(unsafe.Pointer(&tmp_arg)), 0, 0) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func get_console_mode(h syscall.Handle, mode *dword) (err error) { + r0, _, e1 := syscall.Syscall(proc_get_console_mode.Addr(), + 2, uintptr(h), uintptr(unsafe.Pointer(mode)), 0) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func set_console_mode(h syscall.Handle, mode dword) (err error) { + r0, _, e1 := syscall.Syscall(proc_set_console_mode.Addr(), + 2, uintptr(h), uintptr(mode), 0) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func fill_console_output_character(h syscall.Handle, char wchar, n int) (err error) { + tmp_coord = coord{0, 0} + r0, _, e1 := syscall.Syscall6(proc_fill_console_output_character.Addr(), + 5, uintptr(h), uintptr(char), uintptr(n), tmp_coord.uintptr(), + uintptr(unsafe.Pointer(&tmp_arg)), 0) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func fill_console_output_attribute(h syscall.Handle, attr word, n int) (err error) { + tmp_coord = coord{0, 0} + r0, _, e1 := syscall.Syscall6(proc_fill_console_output_attribute.Addr(), + 5, uintptr(h), uintptr(attr), uintptr(n), tmp_coord.uintptr(), + uintptr(unsafe.Pointer(&tmp_arg)), 0) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func create_event() (out syscall.Handle, err error) { + r0, _, e1 := syscall.Syscall6(proc_create_event.Addr(), + 4, 0, 0, 0, 0, 0, 0) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return syscall.Handle(r0), err +} + +func wait_for_multiple_objects(objects []syscall.Handle) (err error) { + r0, _, e1 := syscall.Syscall6(proc_wait_for_multiple_objects.Addr(), + 4, uintptr(len(objects)), uintptr(unsafe.Pointer(&objects[0])), + 0, 0xFFFFFFFF, 0, 0) + if uint32(r0) == 0xFFFFFFFF { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func set_event(ev syscall.Handle) (err error) { + r0, _, e1 := syscall.Syscall(proc_set_event.Addr(), + 1, uintptr(ev), 0, 0) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func get_current_console_font(h syscall.Handle, info *console_font_info) (err error) { + r0, _, e1 := syscall.Syscall(proc_get_current_console_font.Addr(), + 3, uintptr(h), 0, uintptr(unsafe.Pointer(info))) + if int(r0) == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +type diff_msg struct { + pos short + lines short + chars []char_info +} + +type input_event struct { + event Event + err error +} + +var ( + orig_cursor_info console_cursor_info + orig_size coord + orig_window small_rect + orig_mode dword + orig_screen syscall.Handle + back_buffer cellbuf + front_buffer cellbuf + term_size coord + input_mode = InputEsc + cursor_x = cursor_hidden + cursor_y = cursor_hidden + foreground = ColorDefault + background = ColorDefault + in syscall.Handle + out syscall.Handle + interrupt syscall.Handle + charbuf []char_info + diffbuf []diff_msg + beg_x = -1 + beg_y = -1 + beg_i = -1 + input_comm = make(chan Event) + interrupt_comm = make(chan struct{}) + cancel_comm = make(chan bool, 1) + cancel_done_comm = make(chan bool) + alt_mode_esc = false + + // these ones just to prevent heap allocs at all costs + tmp_info console_screen_buffer_info + tmp_arg dword + tmp_coord0 = coord{0, 0} + tmp_coord = coord{0, 0} + tmp_rect = small_rect{0, 0, 0, 0} + tmp_finfo console_font_info +) + +func get_cursor_position(out syscall.Handle) coord { + err := get_console_screen_buffer_info(out, &tmp_info) + if err != nil { + panic(err) + } + return tmp_info.cursor_position +} + +func get_term_size(out syscall.Handle) (coord, small_rect) { + err := get_console_screen_buffer_info(out, &tmp_info) + if err != nil { + panic(err) + } + return tmp_info.size, tmp_info.window +} + +func get_win_min_size(out syscall.Handle) coord { + x, _, err := get_system_metrics.Call(SM_CXMIN) + y, _, err := get_system_metrics.Call(SM_CYMIN) + + if x == 0 || y == 0 { + if err != nil { + panic(err) + } + } + + err1 := get_current_console_font(out, &tmp_finfo) + if err1 != nil { + panic(err1) + } + + return coord{ + x: short(math.Ceil(float64(x) / float64(tmp_finfo.font_size.x))), + y: short(math.Ceil(float64(y) / float64(tmp_finfo.font_size.y))), + } +} + +func get_win_size(out syscall.Handle) coord { + err := get_console_screen_buffer_info(out, &tmp_info) + if err != nil { + panic(err) + } + + min_size := get_win_min_size(out) + + size := coord{ + x: tmp_info.window.right - tmp_info.window.left + 1, + y: tmp_info.window.bottom - tmp_info.window.top + 1, + } + + if size.x < min_size.x { + size.x = min_size.x + } + + if size.y < min_size.y { + size.y = min_size.y + } + + return size +} + +func fix_win_size(out syscall.Handle, size coord) (err error) { + window := small_rect{} + window.top = 0 + window.bottom = size.y - 1 + window.left = 0 + window.right = size.x - 1 + return set_console_window_info(out, &window) +} + +func update_size_maybe() { + size := get_win_size(out) + if size.x != term_size.x || size.y != term_size.y { + set_console_screen_buffer_size(out, size) + fix_win_size(out, size) + term_size = size + back_buffer.resize(int(size.x), int(size.y)) + front_buffer.resize(int(size.x), int(size.y)) + front_buffer.clear() + clear() + + area := int(size.x) * int(size.y) + if cap(charbuf) < area { + charbuf = make([]char_info, 0, area) + } + } +} + +var color_table_bg = []word{ + 0, // default (black) + 0, // black + background_red, + background_green, + background_red | background_green, // yellow + background_blue, + background_red | background_blue, // magenta + background_green | background_blue, // cyan + background_red | background_blue | background_green, // white +} + +var color_table_fg = []word{ + foreground_red | foreground_blue | foreground_green, // default (white) + 0, + foreground_red, + foreground_green, + foreground_red | foreground_green, // yellow + foreground_blue, + foreground_red | foreground_blue, // magenta + foreground_green | foreground_blue, // cyan + foreground_red | foreground_blue | foreground_green, // white +} + +const ( + replacement_char = '\uFFFD' + max_rune = '\U0010FFFF' + surr1 = 0xd800 + surr2 = 0xdc00 + surr3 = 0xe000 + surr_self = 0x10000 +) + +func append_diff_line(y int) int { + n := 0 + for x := 0; x < front_buffer.width; { + cell_offset := y*front_buffer.width + x + back := &back_buffer.cells[cell_offset] + front := &front_buffer.cells[cell_offset] + attr, char := cell_to_char_info(*back) + charbuf = append(charbuf, char_info{attr: attr, char: char[0]}) + *front = *back + n++ + w := runewidth.RuneWidth(back.Ch) + if w == 0 || w == 2 && runewidth.IsAmbiguousWidth(back.Ch) { + w = 1 + } + x += w + // If not CJK, fill trailing space with whitespace + if !is_cjk && w == 2 { + charbuf = append(charbuf, char_info{attr: attr, char: ' '}) + } + } + return n +} + +// compares 'back_buffer' with 'front_buffer' and prepares all changes in the form of +// 'diff_msg's in the 'diff_buf' +func prepare_diff_messages() { + // clear buffers + diffbuf = diffbuf[:0] + charbuf = charbuf[:0] + + var diff diff_msg + gbeg := 0 + for y := 0; y < front_buffer.height; y++ { + same := true + line_offset := y * front_buffer.width + for x := 0; x < front_buffer.width; x++ { + cell_offset := line_offset + x + back := &back_buffer.cells[cell_offset] + front := &front_buffer.cells[cell_offset] + if *back != *front { + same = false + break + } + } + if same && diff.lines > 0 { + diffbuf = append(diffbuf, diff) + diff = diff_msg{} + } + if !same { + beg := len(charbuf) + end := beg + append_diff_line(y) + if diff.lines == 0 { + diff.pos = short(y) + gbeg = beg + } + diff.lines++ + diff.chars = charbuf[gbeg:end] + } + } + if diff.lines > 0 { + diffbuf = append(diffbuf, diff) + diff = diff_msg{} + } +} + +func get_ct(table []word, idx int) word { + idx = idx & 0x0F + if idx >= len(table) { + idx = len(table) - 1 + } + return table[idx] +} + +func cell_to_char_info(c Cell) (attr word, wc [2]wchar) { + attr = get_ct(color_table_fg, int(c.Fg)) | get_ct(color_table_bg, int(c.Bg)) + if c.Fg&AttrReverse|c.Bg&AttrReverse != 0 { + attr = (attr&0xF0)>>4 | (attr&0x0F)<<4 + } + if c.Fg&AttrBold != 0 { + attr |= foreground_intensity + } + if c.Bg&AttrBold != 0 { + attr |= background_intensity + } + + r0, r1 := utf16.EncodeRune(c.Ch) + if r0 == 0xFFFD { + wc[0] = wchar(c.Ch) + wc[1] = ' ' + } else { + wc[0] = wchar(r0) + wc[1] = wchar(r1) + } + return +} + +func move_cursor(x, y int) { + err := set_console_cursor_position(out, coord{short(x), short(y)}) + if err != nil { + panic(err) + } +} + +func show_cursor(visible bool) { + var v int32 + if visible { + v = 1 + } + + var info console_cursor_info + info.size = 100 + info.visible = v + err := set_console_cursor_info(out, &info) + if err != nil { + panic(err) + } +} + +func clear() { + var err error + attr, char := cell_to_char_info(Cell{ + ' ', + foreground, + background, + }) + + area := int(term_size.x) * int(term_size.y) + err = fill_console_output_attribute(out, attr, area) + if err != nil { + panic(err) + } + err = fill_console_output_character(out, char[0], area) + if err != nil { + panic(err) + } + if !is_cursor_hidden(cursor_x, cursor_y) { + move_cursor(cursor_x, cursor_y) + } +} + +func key_event_record_to_event(r *key_event_record) (Event, bool) { + if r.key_down == 0 { + return Event{}, false + } + + e := Event{Type: EventKey} + if input_mode&InputAlt != 0 { + if alt_mode_esc { + e.Mod = ModAlt + alt_mode_esc = false + } + if r.control_key_state&(left_alt_pressed|right_alt_pressed) != 0 { + e.Mod = ModAlt + } + } + + ctrlpressed := r.control_key_state&(left_ctrl_pressed|right_ctrl_pressed) != 0 + + if r.virtual_key_code >= vk_f1 && r.virtual_key_code <= vk_f12 { + switch r.virtual_key_code { + case vk_f1: + e.Key = KeyF1 + case vk_f2: + e.Key = KeyF2 + case vk_f3: + e.Key = KeyF3 + case vk_f4: + e.Key = KeyF4 + case vk_f5: + e.Key = KeyF5 + case vk_f6: + e.Key = KeyF6 + case vk_f7: + e.Key = KeyF7 + case vk_f8: + e.Key = KeyF8 + case vk_f9: + e.Key = KeyF9 + case vk_f10: + e.Key = KeyF10 + case vk_f11: + e.Key = KeyF11 + case vk_f12: + e.Key = KeyF12 + default: + panic("unreachable") + } + + return e, true + } + + if r.virtual_key_code <= vk_delete { + switch r.virtual_key_code { + case vk_insert: + e.Key = KeyInsert + case vk_delete: + e.Key = KeyDelete + case vk_home: + e.Key = KeyHome + case vk_end: + e.Key = KeyEnd + case vk_pgup: + e.Key = KeyPgup + case vk_pgdn: + e.Key = KeyPgdn + case vk_arrow_up: + e.Key = KeyArrowUp + case vk_arrow_down: + e.Key = KeyArrowDown + case vk_arrow_left: + e.Key = KeyArrowLeft + case vk_arrow_right: + e.Key = KeyArrowRight + case vk_backspace: + if ctrlpressed { + e.Key = KeyBackspace2 + } else { + e.Key = KeyBackspace + } + case vk_tab: + e.Key = KeyTab + case vk_enter: + e.Key = KeyEnter + case vk_esc: + switch { + case input_mode&InputEsc != 0: + e.Key = KeyEsc + case input_mode&InputAlt != 0: + alt_mode_esc = true + return Event{}, false + } + case vk_space: + if ctrlpressed { + // manual return here, because KeyCtrlSpace is zero + e.Key = KeyCtrlSpace + return e, true + } else { + e.Key = KeySpace + } + } + + if e.Key != 0 { + return e, true + } + } + + if ctrlpressed { + if Key(r.unicode_char) >= KeyCtrlA && Key(r.unicode_char) <= KeyCtrlRsqBracket { + e.Key = Key(r.unicode_char) + if input_mode&InputAlt != 0 && e.Key == KeyEsc { + alt_mode_esc = true + return Event{}, false + } + return e, true + } + switch r.virtual_key_code { + case 192, 50: + // manual return here, because KeyCtrl2 is zero + e.Key = KeyCtrl2 + return e, true + case 51: + if input_mode&InputAlt != 0 { + alt_mode_esc = true + return Event{}, false + } + e.Key = KeyCtrl3 + case 52: + e.Key = KeyCtrl4 + case 53: + e.Key = KeyCtrl5 + case 54: + e.Key = KeyCtrl6 + case 189, 191, 55: + e.Key = KeyCtrl7 + case 8, 56: + e.Key = KeyCtrl8 + } + + if e.Key != 0 { + return e, true + } + } + + if r.unicode_char != 0 { + e.Ch = rune(r.unicode_char) + return e, true + } + + return Event{}, false +} + +func input_event_producer() { + var r input_record + var err error + var last_button Key + var last_button_pressed Key + var last_state = dword(0) + var last_x, last_y = -1, -1 + handles := []syscall.Handle{in, interrupt} + for { + err = wait_for_multiple_objects(handles) + if err != nil { + input_comm <- Event{Type: EventError, Err: err} + } + + select { + case <-cancel_comm: + cancel_done_comm <- true + return + default: + } + + err = read_console_input(in, &r) + if err != nil { + input_comm <- Event{Type: EventError, Err: err} + } + + switch r.event_type { + case key_event: + kr := (*key_event_record)(unsafe.Pointer(&r.event)) + ev, ok := key_event_record_to_event(kr) + if ok { + for i := 0; i < int(kr.repeat_count); i++ { + input_comm <- ev + } + } + case window_buffer_size_event: + sr := *(*window_buffer_size_record)(unsafe.Pointer(&r.event)) + input_comm <- Event{ + Type: EventResize, + Width: int(sr.size.x), + Height: int(sr.size.y), + } + case mouse_event: + mr := *(*mouse_event_record)(unsafe.Pointer(&r.event)) + ev := Event{Type: EventMouse} + switch mr.event_flags { + case 0, 2: + // single or double click + cur_state := mr.button_state + switch { + case last_state&mouse_lmb == 0 && cur_state&mouse_lmb != 0: + last_button = MouseLeft + last_button_pressed = last_button + case last_state&mouse_rmb == 0 && cur_state&mouse_rmb != 0: + last_button = MouseRight + last_button_pressed = last_button + case last_state&mouse_mmb == 0 && cur_state&mouse_mmb != 0: + last_button = MouseMiddle + last_button_pressed = last_button + case last_state&mouse_lmb != 0 && cur_state&mouse_lmb == 0: + last_button = MouseRelease + case last_state&mouse_rmb != 0 && cur_state&mouse_rmb == 0: + last_button = MouseRelease + case last_state&mouse_mmb != 0 && cur_state&mouse_mmb == 0: + last_button = MouseRelease + default: + last_state = cur_state + continue + } + last_state = cur_state + ev.Key = last_button + last_x, last_y = int(mr.mouse_pos.x), int(mr.mouse_pos.y) + ev.MouseX = last_x + ev.MouseY = last_y + case 1: + // mouse motion + x, y := int(mr.mouse_pos.x), int(mr.mouse_pos.y) + if last_state != 0 && (last_x != x || last_y != y) { + ev.Key = last_button_pressed + ev.Mod = ModMotion + ev.MouseX = x + ev.MouseY = y + last_x, last_y = x, y + } else { + ev.Type = EventNone + } + case 4: + // mouse wheel + n := int16(mr.button_state >> 16) + if n > 0 { + ev.Key = MouseWheelUp + } else { + ev.Key = MouseWheelDown + } + last_x, last_y = int(mr.mouse_pos.x), int(mr.mouse_pos.y) + ev.MouseX = last_x + ev.MouseY = last_y + default: + ev.Type = EventNone + } + if ev.Type != EventNone { + input_comm <- ev + } + } + } +} diff --git a/vendor/github.com/awesome-gocui/termbox-go/terminfo.go b/vendor/github.com/awesome-gocui/termbox-go/terminfo.go new file mode 100644 index 00000000..ab2e7a19 --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/terminfo.go @@ -0,0 +1,232 @@ +// +build !windows +// This file contains a simple and incomplete implementation of the terminfo +// database. Information was taken from the ncurses manpages term(5) and +// terminfo(5). Currently, only the string capabilities for special keys and for +// functions without parameters are actually used. Colors are still done with +// ANSI escape sequences. Other special features that are not (yet?) supported +// are reading from ~/.terminfo, the TERMINFO_DIRS variable, Berkeley database +// format and extended capabilities. + +package termbox + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + "io/ioutil" + "os" + "strings" +) + +const ( + ti_magic = 0432 + ti_header_length = 12 + ti_mouse_enter = "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h" + ti_mouse_leave = "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l" +) + +func load_terminfo() ([]byte, error) { + var data []byte + var err error + + term := os.Getenv("TERM") + if term == "" { + return nil, fmt.Errorf("termbox: TERM not set") + } + + // The following behaviour follows the one described in terminfo(5) as + // distributed by ncurses. + + terminfo := os.Getenv("TERMINFO") + if terminfo != "" { + // if TERMINFO is set, no other directory should be searched + return ti_try_path(terminfo) + } + + // next, consider ~/.terminfo + home := os.Getenv("HOME") + if home != "" { + data, err = ti_try_path(home + "/.terminfo") + if err == nil { + return data, nil + } + } + + // next, TERMINFO_DIRS + dirs := os.Getenv("TERMINFO_DIRS") + if dirs != "" { + for _, dir := range strings.Split(dirs, ":") { + if dir == "" { + // "" -> "/usr/share/terminfo" + dir = "/usr/share/terminfo" + } + data, err = ti_try_path(dir) + if err == nil { + return data, nil + } + } + } + + // next, /lib/terminfo + data, err = ti_try_path("/lib/terminfo") + if err == nil { + return data, nil + } + + // fall back to /usr/share/terminfo + return ti_try_path("/usr/share/terminfo") +} + +func ti_try_path(path string) (data []byte, err error) { + // load_terminfo already made sure it is set + term := os.Getenv("TERM") + + // first try, the typical *nix path + terminfo := path + "/" + term[0:1] + "/" + term + data, err = ioutil.ReadFile(terminfo) + if err == nil { + return + } + + // fallback to darwin specific dirs structure + terminfo = path + "/" + hex.EncodeToString([]byte(term[:1])) + "/" + term + data, err = ioutil.ReadFile(terminfo) + return +} + +func setup_term_builtin() error { + name := os.Getenv("TERM") + if name == "" { + return errors.New("termbox: TERM environment variable not set") + } + + for _, t := range terms { + if t.name == name { + keys = t.keys + funcs = t.funcs + return nil + } + } + + compat_table := []struct { + partial string + keys []string + funcs []string + }{ + {"xterm", xterm_keys, xterm_funcs}, + {"rxvt", rxvt_unicode_keys, rxvt_unicode_funcs}, + {"linux", linux_keys, linux_funcs}, + {"Eterm", eterm_keys, eterm_funcs}, + {"screen", screen_keys, screen_funcs}, + // let's assume that 'cygwin' is xterm compatible + {"cygwin", xterm_keys, xterm_funcs}, + {"st", xterm_keys, xterm_funcs}, + } + + // try compatibility variants + for _, it := range compat_table { + if strings.Contains(name, it.partial) { + keys = it.keys + funcs = it.funcs + return nil + } + } + + return errors.New("termbox: unsupported terminal") +} + +func setup_term() (err error) { + var data []byte + var header [6]int16 + var str_offset, table_offset int16 + + data, err = load_terminfo() + if err != nil { + return setup_term_builtin() + } + + rd := bytes.NewReader(data) + // 0: magic number, 1: size of names section, 2: size of boolean section, 3: + // size of numbers section (in integers), 4: size of the strings section (in + // integers), 5: size of the string table + + err = binary.Read(rd, binary.LittleEndian, header[:]) + if err != nil { + return + } + + number_sec_len := int16(2) + if header[0] == 542 { // doc says it should be octal 0542, but what I see it terminfo files is 542, learn to program please... thank you.. + number_sec_len = 4 + } + + if (header[1]+header[2])%2 != 0 { + // old quirk to align everything on word boundaries + header[2] += 1 + } + str_offset = ti_header_length + header[1] + header[2] + number_sec_len*header[3] + table_offset = str_offset + 2*header[4] + + keys = make([]string, 0xFFFF-key_min) + for i, _ := range keys { + keys[i], err = ti_read_string(rd, str_offset+2*ti_keys[i], table_offset) + if err != nil { + return + } + } + funcs = make([]string, t_max_funcs) + // the last two entries are reserved for mouse. because the table offset is + // not there, the two entries have to fill in manually + for i, _ := range funcs[:len(funcs)-2] { + funcs[i], err = ti_read_string(rd, str_offset+2*ti_funcs[i], table_offset) + if err != nil { + return + } + } + funcs[t_max_funcs-2] = ti_mouse_enter + funcs[t_max_funcs-1] = ti_mouse_leave + return nil +} + +func ti_read_string(rd *bytes.Reader, str_off, table int16) (string, error) { + var off int16 + + _, err := rd.Seek(int64(str_off), 0) + if err != nil { + return "", err + } + err = binary.Read(rd, binary.LittleEndian, &off) + if err != nil { + return "", err + } + _, err = rd.Seek(int64(table+off), 0) + if err != nil { + return "", err + } + var bs []byte + for { + b, err := rd.ReadByte() + if err != nil { + return "", err + } + if b == byte(0x00) { + break + } + bs = append(bs, b) + } + return string(bs), nil +} + +// "Maps" the function constants from termbox.go to the number of the respective +// string capability in the terminfo file. Taken from (ncurses) term.h. +var ti_funcs = []int16{ + 28, 40, 16, 13, 5, 39, 36, 27, 26, 34, 89, 88, +} + +// Same as above for the special keys. +var ti_keys = []int16{ + 66, 68 /* apparently not a typo; 67 is F10 for whatever reason */, 69, 70, + 71, 72, 73, 74, 75, 67, 216, 217, 77, 59, 76, 164, 82, 81, 87, 61, 79, 83, +} diff --git a/vendor/github.com/awesome-gocui/termbox-go/terminfo_builtin.go b/vendor/github.com/awesome-gocui/termbox-go/terminfo_builtin.go new file mode 100644 index 00000000..a9486606 --- /dev/null +++ b/vendor/github.com/awesome-gocui/termbox-go/terminfo_builtin.go @@ -0,0 +1,64 @@ +// +build !windows + +package termbox + +// Eterm +var eterm_keys = []string{ + "\x1b[11~", "\x1b[12~", "\x1b[13~", "\x1b[14~", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[7~", "\x1b[8~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C", +} +var eterm_funcs = []string{ + "\x1b7\x1b[?47h", "\x1b[2J\x1b[?47l\x1b8", "\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[2J", "\x1b[m\x0f", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "", "", "", "", +} + +// screen +var screen_keys = []string{ + "\x1bOP", "\x1bOQ", "\x1bOR", "\x1bOS", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[1~", "\x1b[4~", "\x1b[5~", "\x1b[6~", "\x1bOA", "\x1bOB", "\x1bOD", "\x1bOC", +} +var screen_funcs = []string{ + "\x1b[?1049h", "\x1b[?1049l", "\x1b[34h\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[J", "\x1b[m\x0f", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "\x1b[?1h\x1b=", "\x1b[?1l\x1b>", ti_mouse_enter, ti_mouse_leave, +} + +// xterm +var xterm_keys = []string{ + "\x1bOP", "\x1bOQ", "\x1bOR", "\x1bOS", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1bOH", "\x1bOF", "\x1b[5~", "\x1b[6~", "\x1bOA", "\x1bOB", "\x1bOD", "\x1bOC", +} +var xterm_funcs = []string{ + "\x1b[?1049h", "\x1b[?1049l", "\x1b[?12l\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[2J", "\x1b(B\x1b[m", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "\x1b[?1h\x1b=", "\x1b[?1l\x1b>", ti_mouse_enter, ti_mouse_leave, +} + +// rxvt-unicode +var rxvt_unicode_keys = []string{ + "\x1b[11~", "\x1b[12~", "\x1b[13~", "\x1b[14~", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[7~", "\x1b[8~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C", +} +var rxvt_unicode_funcs = []string{ + "\x1b[?1049h", "\x1b[r\x1b[?1049l", "\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[2J", "\x1b[m\x1b(B", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "\x1b=", "\x1b>", ti_mouse_enter, ti_mouse_leave, +} + +// linux +var linux_keys = []string{ + "\x1b[[A", "\x1b[[B", "\x1b[[C", "\x1b[[D", "\x1b[[E", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[1~", "\x1b[4~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C", +} +var linux_funcs = []string{ + "", "", "\x1b[?25h\x1b[?0c", "\x1b[?25l\x1b[?1c", "\x1b[H\x1b[J", "\x1b[0;10m", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "", "", "", "", +} + +// rxvt-256color +var rxvt_256color_keys = []string{ + "\x1b[11~", "\x1b[12~", "\x1b[13~", "\x1b[14~", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[7~", "\x1b[8~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C", +} +var rxvt_256color_funcs = []string{ + "\x1b7\x1b[?47h", "\x1b[2J\x1b[?47l\x1b8", "\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[2J", "\x1b[m\x0f", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "\x1b=", "\x1b>", ti_mouse_enter, ti_mouse_leave, +} + +var terms = []struct { + name string + keys []string + funcs []string +}{ + {"Eterm", eterm_keys, eterm_funcs}, + {"screen", screen_keys, screen_funcs}, + {"xterm", xterm_keys, xterm_funcs}, + {"rxvt-unicode", rxvt_unicode_keys, rxvt_unicode_funcs}, + {"linux", linux_keys, linux_funcs}, + {"rxvt-256color", rxvt_256color_keys, rxvt_256color_funcs}, +} -- cgit