termui: migrate to awesome-gocui instead of the old fork I had

This commit is contained in:
Michael Muré 2019-11-03 20:47:29 +01:00
parent 163ea9c933
commit cb8236c9c2
No known key found for this signature in database
GPG Key ID: A4457C029293126F
56 changed files with 1992 additions and 257 deletions

42
Gopkg.lock generated
View File

@ -32,14 +32,6 @@
revision = "60f9049b9d18b9370b8ed1247fe4334af5db131a" revision = "60f9049b9d18b9370b8ed1247fe4334af5db131a"
version = "v0.2.1" version = "v0.2.1"
[[projects]]
branch = "master"
digest = "1:38a84d9b4cf50b3e8eb2b54f218413ac163076e3a7763afe5fa15a4eb15fbda6"
name = "github.com/MichaelMure/gocui"
packages = ["."]
pruneopts = "UT"
revision = "d753c235dd8582d55e99bbb7f7fe453fb3fd3a19"
[[projects]] [[projects]]
digest = "1:897d91c431ce469d35a5e6030e60e617dccd9a0e95bdffa6a80594f5c5800d29" digest = "1:897d91c431ce469d35a5e6030e60e617dccd9a0e95bdffa6a80594f5c5800d29"
name = "github.com/agnivade/levenshtein" name = "github.com/agnivade/levenshtein"
@ -56,6 +48,22 @@
pruneopts = "UT" pruneopts = "UT"
revision = "0fb0a474d195a3449cf412ae0176faa193f0ef0b" revision = "0fb0a474d195a3449cf412ae0176faa193f0ef0b"
[[projects]]
branch = "master"
digest = "1:96d56c73765f6ba0dbccf953502342da2c4f0d4280a5aef4e4e3eea9e6674ba1"
name = "github.com/awesome-gocui/gocui"
packages = ["."]
pruneopts = "UT"
revision = "c9d3c2bec453a8d648228640c79cc769bbc78df8"
[[projects]]
branch = "master"
digest = "1:237f3e0692e330851b8b5a53117cfcea1beba09aec7668d34db91b809879c296"
name = "github.com/awesome-gocui/termbox-go"
packages = ["."]
pruneopts = "UT"
revision = "c0aef3d18bcc218a92e318293310ca0191f29654"
[[projects]] [[projects]]
digest = "1:b6d886569181ec96ca83d529f4d6ba0cbf92ace7bb6f633f90c5f34d9bba7aab" digest = "1:b6d886569181ec96ca83d529f4d6ba0cbf92ace7bb6f633f90c5f34d9bba7aab"
name = "github.com/blang/semver" name = "github.com/blang/semver"
@ -112,6 +120,14 @@
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4" revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
version = "v1.7.0" version = "v1.7.0"
[[projects]]
digest = "1:aacef5f5e45685f2aeda5534d0a750dee6859de7e9088cdd06192787bb01ae6d"
name = "github.com/go-errors/errors"
packages = ["."]
pruneopts = "UT"
revision = "a6af135bd4e28680facf08a3d206b454abc877a4"
version = "v1.0.1"
[[projects]] [[projects]]
digest = "1:97df918963298c287643883209a2c3f642e6593379f97ab400c2a2e219ab647d" digest = "1:97df918963298c287643883209a2c3f642e6593379f97ab400c2a2e219ab647d"
name = "github.com/golang/protobuf" name = "github.com/golang/protobuf"
@ -203,14 +219,6 @@
pruneopts = "UT" pruneopts = "UT"
revision = "fa473d140ef3c6adf42d6b391fe76707f1f243c8" revision = "fa473d140ef3c6adf42d6b391fe76707f1f243c8"
[[projects]]
branch = "master"
digest = "1:c9b6e36dbd23f8403a04493376916ca5dad8c01b2da5ae0a05e6a468eb0b6f24"
name = "github.com/nsf/termbox-go"
packages = ["."]
pruneopts = "UT"
revision = "5c94acc5e6eb520f1bcd183974e01171cc4c23b3"
[[projects]] [[projects]]
digest = "1:7413525ee648f20b4181be7fe8103d0cb98be9e141926a03ee082dc207061e4e" digest = "1:7413525ee648f20b4181be7fe8103d0cb98be9e141926a03ee082dc207061e4e"
name = "github.com/phayes/freeport" name = "github.com/phayes/freeport"
@ -481,8 +489,8 @@
"github.com/99designs/gqlgen/graphql/introspection", "github.com/99designs/gqlgen/graphql/introspection",
"github.com/99designs/gqlgen/handler", "github.com/99designs/gqlgen/handler",
"github.com/MichaelMure/go-term-text", "github.com/MichaelMure/go-term-text",
"github.com/MichaelMure/gocui",
"github.com/araddon/dateparse", "github.com/araddon/dateparse",
"github.com/awesome-gocui/gocui",
"github.com/blang/semver", "github.com/blang/semver",
"github.com/cheekybits/genny/generic", "github.com/cheekybits/genny/generic",
"github.com/dustin/go-humanize", "github.com/dustin/go-humanize",

View File

@ -61,7 +61,7 @@
version = "0.9.2" version = "0.9.2"
[[constraint]] [[constraint]]
name = "github.com/MichaelMure/gocui" name = "github.com/awesome-gocui/gocui"
branch = "master" branch = "master"
[[override]] [[override]]

View File

@ -7,7 +7,7 @@ import (
"time" "time"
"github.com/MichaelMure/go-term-text" "github.com/MichaelMure/go-term-text"
"github.com/MichaelMure/gocui" "github.com/awesome-gocui/gocui"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/cache"
@ -56,10 +56,10 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
return nil return nil
} }
v, err := g.SetView(bugTableHeaderView, -1, -1, maxX, 3) v, err := g.SetView(bugTableHeaderView, -1, -1, maxX, 3, 0)
if err != nil { if err != nil {
if err != gocui.ErrUnknownView { if !gocui.IsUnknownView(err) {
return err return err
} }
@ -69,10 +69,10 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
v.Clear() v.Clear()
bt.renderHeader(v, maxX) bt.renderHeader(v, maxX)
v, err = g.SetView(bugTableView, -1, 1, maxX, maxY-3) v, err = g.SetView(bugTableView, -1, 1, maxX, maxY-3, 0)
if err != nil { if err != nil {
if err != gocui.ErrUnknownView { if !gocui.IsUnknownView(err) {
return err return err
} }
@ -100,10 +100,10 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
v.Clear() v.Clear()
bt.render(v, maxX) bt.render(v, maxX)
v, err = g.SetView(bugTableFooterView, -1, maxY-4, maxX, maxY) v, err = g.SetView(bugTableFooterView, -1, maxY-4, maxX, maxY, 0)
if err != nil { if err != nil {
if err != gocui.ErrUnknownView { if !gocui.IsUnknownView(err) {
return err return err
} }
@ -113,10 +113,10 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
v.Clear() v.Clear()
bt.renderFooter(v, maxX) bt.renderFooter(v, maxX)
v, err = g.SetView(bugTableInstructionView, -1, maxY-2, maxX, maxY) v, err = g.SetView(bugTableInstructionView, -1, maxY-2, maxX, maxY, 0)
if err != nil { if err != nil {
if err != gocui.ErrUnknownView { if !gocui.IsUnknownView(err) {
return err return err
} }
@ -216,16 +216,16 @@ func (bt *bugTable) keybindings(g *gocui.Gui) error {
} }
func (bt *bugTable) disable(g *gocui.Gui) error { func (bt *bugTable) disable(g *gocui.Gui) error {
if err := g.DeleteView(bugTableView); err != nil && err != gocui.ErrUnknownView { if err := g.DeleteView(bugTableView); err != nil && !gocui.IsUnknownView(err) {
return err return err
} }
if err := g.DeleteView(bugTableHeaderView); err != nil && err != gocui.ErrUnknownView { if err := g.DeleteView(bugTableHeaderView); err != nil && !gocui.IsUnknownView(err) {
return err return err
} }
if err := g.DeleteView(bugTableFooterView); err != nil && err != gocui.ErrUnknownView { if err := g.DeleteView(bugTableFooterView); err != nil && !gocui.IsUnknownView(err) {
return err return err
} }
if err := g.DeleteView(bugTableInstructionView); err != nil && err != gocui.ErrUnknownView { if err := g.DeleteView(bugTableInstructionView); err != nil && !gocui.IsUnknownView(err) {
return err return err
} }
return nil return nil

View File

@ -3,7 +3,7 @@ package termui
import ( import (
"io/ioutil" "io/ioutil"
"github.com/MichaelMure/gocui" "github.com/awesome-gocui/gocui"
) )
const inputPopupView = "inputPopupView" const inputPopupView = "inputPopupView"
@ -46,9 +46,9 @@ func (ip *inputPopup) layout(g *gocui.Gui) error {
x0 := (maxX - width) / 2 x0 := (maxX - width) / 2
y0 := (maxY - height) / 2 y0 := (maxY - height) / 2
v, err := g.SetView(inputPopupView, x0, y0, x0+width, y0+height) v, err := g.SetView(inputPopupView, x0, y0, x0+width, y0+height, 0)
if err != nil { if err != nil {
if err != gocui.ErrUnknownView { if !gocui.IsUnknownView(err) {
return err return err
} }

View File

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/MichaelMure/gocui" "github.com/awesome-gocui/gocui"
"github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/cache"
@ -106,9 +106,9 @@ func (ls *labelSelect) layout(g *gocui.Gui) error {
x0 := 1 x0 := 1
y0 := 0 - ls.scroll y0 := 0 - ls.scroll
v, err := g.SetView(labelSelectView, x0, 0, x0+width, maxY-2) v, err := g.SetView(labelSelectView, x0, 0, x0+width, maxY-2, 0)
if err != nil { if err != nil {
if err != gocui.ErrUnknownView { if !gocui.IsUnknownView(err) {
return err return err
} }
@ -117,8 +117,8 @@ func (ls *labelSelect) layout(g *gocui.Gui) error {
for i, label := range ls.labels { for i, label := range ls.labels {
viewname := fmt.Sprintf("view%d", i) viewname := fmt.Sprintf("view%d", i)
v, err := g.SetView(viewname, x0+2, y0, x0+width-2, y0+2) v, err := g.SetView(viewname, x0+2, y0, x0+width-2, y0+2, 0)
if err != nil && err != gocui.ErrUnknownView { if err != nil && !gocui.IsUnknownView(err) {
return err return err
} }
ls.childViews = append(ls.childViews, viewname) ls.childViews = append(ls.childViews, viewname)
@ -137,10 +137,10 @@ func (ls *labelSelect) layout(g *gocui.Gui) error {
y0 += 2 y0 += 2
} }
v, err = g.SetView(labelSelectInstructionsView, -1, maxY-2, maxX, maxY) v, err = g.SetView(labelSelectInstructionsView, -1, maxY-2, maxX, maxY, 0)
ls.childViews = append(ls.childViews, labelSelectInstructionsView) ls.childViews = append(ls.childViews, labelSelectInstructionsView)
if err != nil { if err != nil {
if err != gocui.ErrUnknownView { if !gocui.IsUnknownView(err) {
return err return err
} }
v.Frame = false v.Frame = false
@ -159,7 +159,7 @@ func (ls *labelSelect) layout(g *gocui.Gui) error {
func (ls *labelSelect) disable(g *gocui.Gui) error { func (ls *labelSelect) disable(g *gocui.Gui) error {
for _, view := range ls.childViews { for _, view := range ls.childViews {
if err := g.DeleteView(view); err != nil && err != gocui.ErrUnknownView { if err := g.DeleteView(view); err != nil && !gocui.IsUnknownView(err) {
return err return err
} }
} }

View File

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"github.com/MichaelMure/go-term-text" "github.com/MichaelMure/go-term-text"
"github.com/MichaelMure/gocui" "github.com/awesome-gocui/gocui"
) )
const msgPopupView = "msgPopupView" const msgPopupView = "msgPopupView"
@ -50,9 +50,9 @@ func (ep *msgPopup) layout(g *gocui.Gui) error {
x0 := (maxX - width) / 2 x0 := (maxX - width) / 2
y0 := (maxY - height) / 2 y0 := (maxY - height) / 2
v, err := g.SetView(msgPopupView, x0, y0, x0+width, y0+height) v, err := g.SetView(msgPopupView, x0, y0, x0+width, y0+height, 0)
if err != nil { if err != nil {
if err != gocui.ErrUnknownView { if !gocui.IsUnknownView(err) {
return err return err
} }

View File

@ -6,7 +6,7 @@ import (
"strings" "strings"
"github.com/MichaelMure/go-term-text" "github.com/MichaelMure/go-term-text"
"github.com/MichaelMure/gocui" "github.com/awesome-gocui/gocui"
"github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/cache"
@ -49,10 +49,10 @@ func (sb *showBug) layout(g *gocui.Gui) error {
maxX, maxY := g.Size() maxX, maxY := g.Size()
sb.childViews = nil sb.childViews = nil
v, err := g.SetView(showBugView, 0, 0, maxX*2/3, maxY-2) v, err := g.SetView(showBugView, 0, 0, maxX*2/3, maxY-2, 0)
if err != nil { if err != nil {
if err != gocui.ErrUnknownView { if !gocui.IsUnknownView(err) {
return err return err
} }
@ -66,10 +66,10 @@ func (sb *showBug) layout(g *gocui.Gui) error {
return err return err
} }
v, err = g.SetView(showBugSidebarView, maxX*2/3+1, 0, maxX-1, maxY-2) v, err = g.SetView(showBugSidebarView, maxX*2/3+1, 0, maxX-1, maxY-2, 0)
if err != nil { if err != nil {
if err != gocui.ErrUnknownView { if !gocui.IsUnknownView(err) {
return err return err
} }
@ -83,10 +83,10 @@ func (sb *showBug) layout(g *gocui.Gui) error {
return err return err
} }
v, err = g.SetView(showBugInstructionView, -1, maxY-2, maxX, maxY) v, err = g.SetView(showBugInstructionView, -1, maxY-2, maxX, maxY, 0)
if err != nil { if err != nil {
if err != gocui.ErrUnknownView { if !gocui.IsUnknownView(err) {
return err return err
} }
@ -190,7 +190,7 @@ func (sb *showBug) keybindings(g *gocui.Gui) error {
func (sb *showBug) disable(g *gocui.Gui) error { func (sb *showBug) disable(g *gocui.Gui) error {
for _, view := range sb.childViews { for _, view := range sb.childViews {
if err := g.DeleteView(view); err != nil && err != gocui.ErrUnknownView { if err := g.DeleteView(view); err != nil && !gocui.IsUnknownView(err) {
return err return err
} }
} }
@ -383,9 +383,9 @@ func emptyMessagePlaceholder() string {
} }
func (sb *showBug) createOpView(g *gocui.Gui, name string, x0 int, y0 int, maxX int, height int, selectable bool) (*gocui.View, error) { func (sb *showBug) createOpView(g *gocui.Gui, name string, x0 int, y0 int, maxX int, height int, selectable bool) (*gocui.View, error) {
v, err := g.SetView(name, x0, y0, maxX, y0+height+1) v, err := g.SetView(name, x0, y0, maxX, y0+height+1, 0)
if err != nil && err != gocui.ErrUnknownView { if err != nil && !gocui.IsUnknownView(err) {
return nil, err return nil, err
} }
@ -403,9 +403,9 @@ func (sb *showBug) createOpView(g *gocui.Gui, name string, x0 int, y0 int, maxX
} }
func (sb *showBug) createSideView(g *gocui.Gui, name string, x0 int, y0 int, maxX int, height int) (*gocui.View, error) { func (sb *showBug) createSideView(g *gocui.Gui, name string, x0 int, y0 int, maxX int, height int) (*gocui.View, error) {
v, err := g.SetView(name, x0, y0, maxX, y0+height+1) v, err := g.SetView(name, x0, y0, maxX, y0+height+1, 0)
if err != nil && err != gocui.ErrUnknownView { if err != nil && !gocui.IsUnknownView(err) {
return nil, err return nil, err
} }

View File

@ -2,9 +2,13 @@
package termui package termui
import ( import (
"github.com/MichaelMure/gocui" "fmt"
"github.com/awesome-gocui/gocui"
"github.com/pkg/errors" "github.com/pkg/errors"
errors2 "github.com/go-errors/errors"
"github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/input" "github.com/MichaelMure/git-bug/input"
@ -63,15 +67,15 @@ func Run(cache *cache.RepoCache) error {
err := <-ui.gError err := <-ui.gError
if err != nil && err != gocui.ErrQuit { if err != nil && err != gocui.ErrQuit {
fmt.Println(err.(*errors2.Error).ErrorStack())
return err return err
} }
return nil return nil
} }
func initGui(action func(ui *termUI) error) { func initGui(action func(ui *termUI) error) {
g, err := gocui.NewGui(gocui.Output256) g, err := gocui.NewGui(gocui.Output256, false)
if err != nil { if err != nil {
ui.gError <- err ui.gError <- err

View File

@ -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

33
vendor/github.com/awesome-gocui/gocui/CONTRIBUTING.md generated vendored Normal file
View File

@ -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!

View File

@ -1,8 +1,13 @@
# GOCUI - Go Console User Interface # 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)
[![GoDoc](https://godoc.org/github.com/jroimartin/gocui?status.svg)](https://godoc.org/github.com/jroimartin/gocui) Minimalist Go package aimed at creating Console User Interfaces.
A community fork based on the amazing work of [jroimartin](https://github.com/jroimartin/gocui)
Minimalist Go package aimed at creating Console User Interfaces.
## Features ## Features
@ -13,15 +18,29 @@ Minimalist Go package aimed at creating Console User Interfaces.
* Global and view-level keybindings. * Global and view-level keybindings.
* Mouse support. * Mouse support.
* Colored text. * Colored text.
* Customizable edition mode. * Customizable editing mode.
* Easy to build reusable widgets, complex layouts... * 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 ## Installation
Execute: Execute:
``` ```
$ go get github.com/jroimartin/gocui $ go get github.com/awesome-gocui/gocui
``` ```
## Documentation ## Documentation
@ -29,13 +48,14 @@ $ go get github.com/jroimartin/gocui
Execute: Execute:
``` ```
$ go doc github.com/jroimartin/gocui $ go doc github.com/awesome-gocui/gocui
``` ```
Or visit [godoc.org](https://godoc.org/github.com/jroimartin/gocui) to read it Or visit [godoc.org](https://godoc.org/github.com/awesome-gocui/gocui) to read it
online. online.
## Example ## Example
See the [_example](./_example/) folder for more examples
```go ```go
package main package main
@ -44,11 +64,11 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/jroimartin/gocui" "github.com/awesome-gocui/gocui"
) )
func main() { func main() {
g, err := gocui.NewGui(gocui.OutputNormal) g, err := gocui.NewGui(gocui.OutputNormal, false)
if err != nil { if err != nil {
log.Panicln(err) log.Panicln(err)
} }
@ -60,18 +80,21 @@ func main() {
log.Panicln(err) log.Panicln(err)
} }
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) {
log.Panicln(err) log.Panicln(err)
} }
} }
func layout(g *gocui.Gui) error { func layout(g *gocui.Gui) error {
maxX, maxY := g.Size() maxX, maxY := g.Size()
if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2); err != nil { if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2, 0); err != nil {
if err != gocui.ErrUnknownView { if !gocui.IsUnknownView(err) {
return err return err
} }
fmt.Fprintln(v, "Hello world!") fmt.Fprintln(v, "Hello world!")
if _, err := g.SetCurrentView("hello"); err != nil {
return err
}
} }
return nil return nil
} }
@ -106,5 +129,7 @@ func quit(g *gocui.Gui, v *gocui.View) error {
* [fac](https://github.com/mkchoi212/fac): git merge conflict resolver * [fac](https://github.com/mkchoi212/fac): git merge conflict resolver
* [jsonui](https://github.com/gulyasm/jsonui): Interactive JSON explorer for your terminal. * [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. * [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! :) Note: if your project is not listed here, let us know! :)

View File

@ -4,7 +4,7 @@
package gocui package gocui
import "github.com/nsf/termbox-go" import "github.com/awesome-gocui/termbox-go"
// Attribute represents a terminal attribute, like color, font style, etc. They // Attribute represents a terminal attribute, like color, font style, etc. They
// can be combined using bitwise OR (|). Note that it is not possible to // can be combined using bitwise OR (|). Note that it is not possible to

View File

@ -16,7 +16,7 @@ Create a new GUI:
// Set GUI managers and key bindings // Set GUI managers and key bindings
// ... // ...
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) {
// handle error // handle error
} }
@ -38,7 +38,7 @@ their content. The same is valid for reading.
Create and initialize a view with absolute coordinates: Create and initialize a view with absolute coordinates:
if v, err := g.SetView("viewname", 2, 2, 22, 7); err != nil { if v, err := g.SetView("viewname", 2, 2, 22, 7); err != nil {
if err != gocui.ErrUnknownView { if !gocui.IsUnknownView(err) {
// handle error // handle error
} }
fmt.Fprintln(v, "This is a new view") fmt.Fprintln(v, "This is a new view")
@ -84,7 +84,7 @@ use *Gui.Update(). For example:
return nil return nil
}) })
By default, gocui provides a basic edition mode. This mode can be extended 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: and customized creating a new Editor and assigning it to *View.Editor:
type Editor interface { type Editor interface {

View File

@ -5,7 +5,7 @@
package gocui package gocui
import ( import (
"errors" "github.com/go-errors/errors"
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
) )
@ -53,13 +53,64 @@ func simpleEditor(v *View, key Key, ch rune, mod Modifier) {
v.MoveCursor(-1, 0, false) v.MoveCursor(-1, 0, false)
case key == KeyArrowRight: case key == KeyArrowRight:
v.MoveCursor(1, 0, false) 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. // EditWrite writes a rune at the cursor position.
func (v *View) EditWrite(ch rune) { func (v *View) EditWrite(ch rune) {
w := runewidth.RuneWidth(ch)
v.writeRune(v.cx, v.cy, ch) v.writeRune(v.cx, v.cy, ch)
v.MoveCursor(runewidth.RuneWidth(ch), 0, true) 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 // EditDelete deletes a rune at the cursor position. back determines the
@ -93,12 +144,12 @@ func (v *View) EditDelete(back bool) {
v.MoveCursor(-1, 0, true) v.MoveCursor(-1, 0, true)
} }
} else { // wrapped line } else { // wrapped line
ch, _ := v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1) n, _ := v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1)
v.MoveCursor(0-runewidth.RuneWidth(ch), 0, true) v.MoveCursor(-n, 0, true)
} }
} else { // middle/end of the line } else { // middle/end of the line
ch, _ := v.deleteRune(v.cx-1, v.cy) n, _ := v.deleteRune(v.cx-1, v.cy)
v.MoveCursor(0-runewidth.RuneWidth(ch), 0, true) v.MoveCursor(-n, 0, true)
} }
} else { } else {
if x == len(v.viewLines[y].line) { // end of the line if x == len(v.viewLines[y].line) { // end of the line
@ -113,42 +164,81 @@ func (v *View) EditDelete(back bool) {
func (v *View) EditNewLine() { func (v *View) EditNewLine() {
v.breakLine(v.cx, v.cy) v.breakLine(v.cx, v.cy)
v.ox = 0 v.ox = 0
v.cy = v.cy + 1
v.cx = 0 v.cx = 0
v.MoveCursor(0, 1, true)
} }
// MoveCursor moves the cursor taking into account the width of the line/view, // MoveCursor moves the cursor taking into account the width of the line/view,
// displacing the origin if necessary. // displacing the origin if necessary.
func (v *View) MoveCursor(dx, dy int, writeMode bool) { 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() maxX, maxY := v.Size()
cx, cy := v.cx+dx, v.cy+dy cx, cy := v.cx+dx, v.cy+dy
x, y := v.ox+cx, v.oy+cy x, y := v.ox+cx, v.oy+cy
var curLineWidth, prevLineWidth int var curLineWidth, prevLineWidth int
// get the width of the current line // get the width of the current line
if writeMode { curLineWidth = maxInt
if v.Wrap { if v.Wrap {
curLineWidth = maxX - 1 curLineWidth = maxX - 1
} else { }
curLineWidth = maxInt
} if !writeMode {
} else { curLineWidth = 0
if y >= 0 && y < len(v.viewLines) { if y >= 0 && y < len(v.viewLines) {
curLineWidth = len(v.viewLines[y].line) curLineWidth = lineWidth(v.viewLines[y].line)
if v.Wrap && curLineWidth >= maxX { if v.Wrap && curLineWidth >= maxX {
curLineWidth = maxX - 1 curLineWidth = maxX - 1
} }
} else {
curLineWidth = 0
} }
} }
// get the width of the previous line // get the width of the previous line
prevLineWidth = 0
if y-1 >= 0 && y-1 < len(v.viewLines) { if y-1 >= 0 && y-1 < len(v.viewLines) {
prevLineWidth = len(v.viewLines[y-1].line) prevLineWidth = lineWidth(v.viewLines[y-1].line)
} else {
prevLineWidth = 0
} }
// adjust cursor's x position and view's x origin // adjust cursor's x position and view's x origin
if x > curLineWidth { // move to next line if x > curLineWidth { // move to next line
if dx > 0 { // horizontal movement if dx > 0 { // horizontal movement
@ -194,10 +284,9 @@ func (v *View) MoveCursor(dx, dy int, writeMode bool) {
if !v.Wrap { // set origin so the EOL is visible if !v.Wrap { // set origin so the EOL is visible
nox := prevLineWidth - maxX + 1 nox := prevLineWidth - maxX + 1
if nox < 0 { if nox < 0 {
v.ox = 0 nox = 0
} else {
v.ox = nox
} }
v.ox = nox
} }
v.cx = prevLineWidth v.cx = prevLineWidth
} else { } else {
@ -279,10 +368,11 @@ func (v *View) writeRune(x, y int, ch rune) error {
// deleteRune removes a rune from the view's internal buffer, at the // deleteRune removes a rune from the view's internal buffer, at the
// position corresponding to the point (x, y). // position corresponding to the point (x, y).
func (v *View) deleteRune(x, y int) (ch rune, err error) { // returns the amount of columns that where removed.
func (v *View) deleteRune(x, y int) (int, error) {
v.tainted = true v.tainted = true
x, y, err = v.realPosition(x, y) x, y, err := v.realPosition(x, y)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -290,9 +380,19 @@ func (v *View) deleteRune(x, y int) (ch rune, err error) {
if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) { if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
return 0, errors.New("invalid point") return 0, errors.New("invalid point")
} }
chx := v.lines[y][x]
v.lines[y] = append(v.lines[y][:x], v.lines[y][x+1:]...) var tw int
return chx.chr, nil 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. // mergeLines merges the lines "y" and "y+1" if possible.

View File

@ -5,7 +5,7 @@
package gocui package gocui
import ( import (
"errors" "github.com/go-errors/errors"
"strconv" "strconv"
) )

9
vendor/github.com/awesome-gocui/gocui/go.mod generated vendored Normal file
View File

@ -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
)

6
vendor/github.com/awesome-gocui/gocui/go.sum generated vendored Normal file
View File

@ -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=

View File

@ -5,28 +5,49 @@
package gocui package gocui
import ( import (
"errors" standardErrors "errors"
"runtime"
"github.com/nsf/termbox-go" "github.com/go-errors/errors"
)
var ( "github.com/awesome-gocui/termbox-go"
// ErrQuit is used to decide if the MainLoop finished successfully.
ErrQuit = errors.New("quit")
// ErrUnknownView allows to assert if a View must be initialized.
ErrUnknownView = errors.New("unknown view")
) )
// OutputMode represents the terminal's output mode (8 or 256 colors). // OutputMode represents the terminal's output mode (8 or 256 colors).
type OutputMode termbox.OutputMode 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 ( const (
// OutputNormal provides 8-colors terminal mode. // OutputNormal provides 8-colors terminal mode.
OutputNormal = OutputMode(termbox.OutputNormal) OutputNormal = OutputMode(termbox.OutputNormal)
// Output256 provides 256-colors terminal mode. // Output256 provides 256-colors terminal mode.
Output256 = OutputMode(termbox.Output256) 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 // Gui represents the whole User Interface, including the views, layouts
@ -40,14 +61,16 @@ type Gui struct {
keybindings []*keybinding keybindings []*keybinding
maxX, maxY int maxX, maxY int
outputMode OutputMode outputMode OutputMode
stop chan struct{}
blacklist []Key
// BgColor and FgColor allow to configure the background and foreground // BgColor and FgColor allow to configure the background and foreground
// colors of the GUI. // colors of the GUI.
BgColor, FgColor Attribute BgColor, FgColor, FrameColor Attribute
// SelBgColor and SelFgColor allow to configure the background and // SelBgColor and SelFgColor allow to configure the background and
// foreground colors of the frame of the current view. // foreground colors of the frame of the current view.
SelBgColor, SelFgColor Attribute SelBgColor, SelFgColor, SelFrameColor Attribute
// If Highlight is true, Sel{Bg,Fg}Colors will be used to draw the // If Highlight is true, Sel{Bg,Fg}Colors will be used to draw the
// frame of the current view. // frame of the current view.
@ -66,11 +89,16 @@ type Gui struct {
// If ASCII is true then use ASCII instead of unicode to draw the // If ASCII is true then use ASCII instead of unicode to draw the
// interface. Using ASCII is more portable. // interface. Using ASCII is more portable.
ASCII bool 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. // NewGui returns a new Gui object with a given output mode.
func NewGui(mode OutputMode) (*Gui, error) { func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) {
if err := termbox.Init(); err != nil { err := termbox.Init()
if err != nil {
return nil, err return nil, err
} }
@ -79,20 +107,36 @@ func NewGui(mode OutputMode) (*Gui, error) {
g.outputMode = mode g.outputMode = mode
termbox.SetOutputMode(termbox.OutputMode(mode)) termbox.SetOutputMode(termbox.OutputMode(mode))
g.stop = make(chan struct{})
g.tbEvents = make(chan termbox.Event, 20) g.tbEvents = make(chan termbox.Event, 20)
g.userEvents = make(chan userEvent, 20) g.userEvents = make(chan userEvent, 20)
g.maxX, g.maxY = termbox.Size() 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.BgColor, g.FgColor = ColorDefault, ColorDefault
g.SelBgColor, g.SelFgColor = 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 return g, nil
} }
// Close finalizes the library. It should be called after a successful // Close finalizes the library. It should be called after a successful
// initialization and when gocui is not needed anymore. // initialization and when gocui is not needed anymore.
func (g *Gui) Close() { func (g *Gui) Close() {
go func() {
g.stop <- struct{}{}
}()
termbox.Close() termbox.Close()
} }
@ -127,8 +171,8 @@ func (g *Gui) Rune(x, y int) (rune, error) {
// already exists, its dimensions are updated; otherwise, the error // already exists, its dimensions are updated; otherwise, the error
// ErrUnknownView is returned, which allows to assert if the View must // ErrUnknownView is returned, which allows to assert if the View must
// be initialized. It checks if the position is valid. // be initialized. It checks if the position is valid.
func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) { func (g *Gui) SetView(name string, x0, y0, x1, y1 int, overlaps byte) (*View, error) {
if x0 >= x1 || y0 >= y1 { if x0 >= x1 {
return nil, errors.New("invalid dimensions") return nil, errors.New("invalid dimensions")
} }
if name == "" { if name == "" {
@ -147,8 +191,20 @@ func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) {
v := newView(name, x0, y0, x1, y1, g.outputMode) v := newView(name, x0, y0, x1, y1, g.outputMode)
v.BgColor, v.FgColor = g.BgColor, g.FgColor v.BgColor, v.FgColor = g.BgColor, g.FgColor
v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor
v.Overlaps = overlaps
g.views = append(g.views, v) g.views = append(g.views, v)
return v, ErrUnknownView 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. // SetViewOnTop sets the given view on top of the existing ones.
@ -160,7 +216,7 @@ func (g *Gui) SetViewOnTop(name string) (*View, error) {
return v, nil return v, nil
} }
} }
return nil, ErrUnknownView return nil, errors.Wrap(ErrUnknownView, 0)
} }
// SetViewOnBottom sets the given view on bottom of the existing ones. // SetViewOnBottom sets the given view on bottom of the existing ones.
@ -172,7 +228,7 @@ func (g *Gui) SetViewOnBottom(name string) (*View, error) {
return v, nil return v, nil
} }
} }
return nil, ErrUnknownView return nil, errors.Wrap(ErrUnknownView, 0)
} }
// Views returns all the views in the GUI. // Views returns all the views in the GUI.
@ -188,7 +244,7 @@ func (g *Gui) View(name string) (*View, error) {
return v, nil return v, nil
} }
} }
return nil, ErrUnknownView return nil, errors.Wrap(ErrUnknownView, 0)
} }
// ViewByPosition returns a pointer to a view matching the given position, or // ViewByPosition returns a pointer to a view matching the given position, or
@ -201,7 +257,7 @@ func (g *Gui) ViewByPosition(x, y int) (*View, error) {
return v, nil return v, nil
} }
} }
return nil, ErrUnknownView return nil, errors.Wrap(ErrUnknownView, 0)
} }
// ViewPosition returns the coordinates of the view with the given name, or // ViewPosition returns the coordinates of the view with the given name, or
@ -212,7 +268,7 @@ func (g *Gui) ViewPosition(name string) (x0, y0, x1, y1 int, err error) {
return v.x0, v.y0, v.x1, v.y1, nil return v.x0, v.y0, v.x1, v.y1, nil
} }
} }
return 0, 0, 0, 0, ErrUnknownView return 0, 0, 0, 0, errors.Wrap(ErrUnknownView, 0)
} }
// DeleteView deletes a view by name. // DeleteView deletes a view by name.
@ -223,7 +279,7 @@ func (g *Gui) DeleteView(name string) error {
return nil return nil
} }
} }
return ErrUnknownView return errors.Wrap(ErrUnknownView, 0)
} }
// SetCurrentView gives the focus to a given view. // SetCurrentView gives the focus to a given view.
@ -234,7 +290,7 @@ func (g *Gui) SetCurrentView(name string) (*View, error) {
return v, nil return v, nil
} }
} }
return nil, ErrUnknownView return nil, errors.Wrap(ErrUnknownView, 0)
} }
// CurrentView returns the currently focused view, or nil if no view // CurrentView returns the currently focused view, or nil if no view
@ -253,6 +309,11 @@ func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, hand
if err != nil { if err != nil {
return err return err
} }
if g.isBlacklisted(k) {
return ErrBlacklisted
}
kb = newKeybinding(viewname, k, ch, mod, handler) kb = newKeybinding(viewname, k, ch, mod, handler)
g.keybindings = append(g.keybindings, kb) g.keybindings = append(g.keybindings, kb)
return nil return nil
@ -285,6 +346,28 @@ func (g *Gui) DeleteKeybindings(viewname string) {
g.keybindings = s 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 // getKey takes an empty interface with a key and returns the corresponding
// typed Key or rune. // typed Key or rune.
func getKey(key interface{}) (Key, rune, error) { func getKey(key interface{}) (Key, rune, error) {
@ -349,14 +432,24 @@ func (g *Gui) SetManagerFunc(manager func(*Gui) error) {
// MainLoop runs the main loop until an error is returned. A successful // MainLoop runs the main loop until an error is returned. A successful
// finish should return ErrQuit. // finish should return ErrQuit.
func (g *Gui) MainLoop() error { func (g *Gui) MainLoop() error {
g.loaderTick()
if err := g.flush(); err != nil {
return err
}
go func() { go func() {
for { for {
g.tbEvents <- termbox.PollEvent() select {
case <-g.stop:
return
default:
g.tbEvents <- termbox.PollEvent()
}
} }
}() }()
inputMode := termbox.InputAlt inputMode := termbox.InputAlt
if g.InputEsc { if true { // previously g.InputEsc, but didn't seem to work
inputMode = termbox.InputEsc inputMode = termbox.InputEsc
} }
if g.Mouse { if g.Mouse {
@ -437,20 +530,25 @@ func (g *Gui) flush() error {
} }
} }
for _, v := range g.views { for _, v := range g.views {
if !v.Visible || v.y1 < v.y0 {
continue
}
if v.Frame { if v.Frame {
var fgColor, bgColor Attribute var fgColor, bgColor, frameColor Attribute
if g.Highlight && v == g.currentView { if g.Highlight && v == g.currentView {
fgColor = g.SelFgColor fgColor = g.SelFgColor
bgColor = g.SelBgColor bgColor = g.SelBgColor
frameColor = g.SelFrameColor
} else { } else {
fgColor = g.FgColor fgColor = g.FgColor
bgColor = g.BgColor bgColor = g.BgColor
frameColor = g.FrameColor
} }
if err := g.drawFrameEdges(v, fgColor, bgColor); err != nil { if err := g.drawFrameEdges(v, frameColor, bgColor); err != nil {
return err return err
} }
if err := g.drawFrameCorners(v, fgColor, bgColor); err != nil { if err := g.drawFrameCorners(v, frameColor, bgColor); err != nil {
return err return err
} }
if v.Title != "" { if v.Title != "" {
@ -458,6 +556,11 @@ func (g *Gui) flush() error {
return err return err
} }
} }
if v.Subtitle != "" {
if err := g.drawSubtitle(v, fgColor, bgColor); err != nil {
return err
}
}
} }
if err := g.draw(v); err != nil { if err := g.draw(v); err != nil {
return err return err
@ -507,9 +610,36 @@ func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error {
return nil 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. // drawFrameCorners draws the corners of the view.
func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error { 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 := '┌', '┐', '└', '┘' 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 { if g.ASCII {
runeTL, runeTR, runeBL, runeBR = '+', '+', '+', '+' runeTL, runeTR, runeBL, runeBR = '+', '+', '+', '+'
} }
@ -549,6 +679,28 @@ func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) error {
return nil 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. // draw manages the cursor and calls the draw function of a view.
func (g *Gui) draw(v *View) error { func (g *Gui) draw(v *View) error {
if g.Cursor { if g.Cursor {
@ -620,17 +772,61 @@ func (g *Gui) onKey(ev *termbox.Event) error {
// execKeybindings executes the keybinding handlers that match the passed view // 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. // 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) { func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err error) {
matched = false var globalKb *keybinding
for _, kb := range g.keybindings { for _, kb := range g.keybindings {
if kb.handler == nil { if kb.handler == nil {
continue continue
} }
if kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) && kb.matchView(v) {
if err := kb.handler(g, v); err != nil { if !kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) {
return false, err continue
} }
matched = true
if kb.matchView(v) {
return g.execKeybinding(v, kb)
}
if kb.viewName == "" && ((v != nil && !v.Editable) || kb.ch == 0) {
globalKb = kb
} }
} }
return matched, nil
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()
} }

60
vendor/github.com/awesome-gocui/gocui/gui_others.go generated vendored Normal file
View File

@ -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")
}
}
}
}

53
vendor/github.com/awesome-gocui/gocui/gui_windows.go generated vendored Normal file
View File

@ -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
}

View File

@ -4,7 +4,18 @@
package gocui package gocui
import "github.com/nsf/termbox-go" 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. // Keybidings are used to link a given key-press event with a handler.
type keybinding struct { type keybinding struct {
@ -15,6 +26,71 @@ type keybinding struct {
handler func(*Gui, *View) error 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. // newKeybinding returns a new Keybinding object.
func newKeybinding(viewname string, key Key, ch rune, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) { func newKeybinding(viewname string, key Key, ch rune, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) {
kb = &keybinding{ kb = &keybinding{
@ -34,14 +110,90 @@ func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
// matchView returns if the keybinding matches the current view. // matchView returns if the keybinding matches the current view.
func (kb *keybinding) matchView(v *View) bool { func (kb *keybinding) matchView(v *View) bool {
if kb.viewName == "" { // if the user is typing in a field, ignore char keys
return true if v == nil || (v.Editable && kb.ch != 0) {
return false
} }
return v != nil && kb.viewName == v.name return kb.viewName == v.name
} }
// Key represents special keys or keys combinations. // translations for strings to keys
type Key termbox.Key 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. // Special keys.
const ( const (
@ -126,10 +278,6 @@ const (
KeyCtrl8 = Key(termbox.KeyCtrl8) KeyCtrl8 = Key(termbox.KeyCtrl8)
) )
// 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
// Modifiers. // Modifiers.
const ( const (
ModNone Modifier = Modifier(0) ModNone Modifier = Modifier(0)

46
vendor/github.com/awesome-gocui/gocui/loader.go generated vendored Normal file
View File

@ -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,
}
}

View File

@ -6,29 +6,59 @@ package gocui
import ( import (
"bytes" "bytes"
"errors"
"io" "io"
"strings" "strings"
"sync"
"unicode/utf8"
"github.com/go-errors/errors"
"github.com/awesome-gocui/termbox-go"
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
"github.com/nsf/termbox-go" )
// 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 // A View is a window. It maintains its own internal buffer and cursor
// position. // position.
type View struct { type View struct {
name string name string
x0, y0, x1, y1 int x0, y0, x1, y1 int // left top right bottom
ox, oy int ox, oy int // view offsets
cx, cy int cx, cy int // cursor position
lines [][]cell rx, ry int // Read() offsets
readOffset int wx, wy int // Write() offsets
readCache string lines [][]cell // All the data
tainted bool // marks if the viewBuffer must be updated // readBuffer is used for storing unread bytes
viewLines []viewLine // internal representation of the view's buffer readBuffer []byte
ei *escapeInterpreter // used to decode ESC sequences on Write // 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 // BgColor and FgColor allow to configure the background and foreground
// colors of the View. // colors of the View.
@ -42,7 +72,7 @@ type View struct {
// buffer at the cursor position. // buffer at the cursor position.
Editable bool Editable bool
// Editor allows to define the editor that manages the edition mode, // Editor allows to define the editor that manages the editing mode,
// including keybindings or cursor behaviour. DefaultEditor is used by // including keybindings or cursor behaviour. DefaultEditor is used by
// default. // default.
Editor Editor Editor Editor
@ -69,9 +99,18 @@ type View struct {
// If Frame is true, Title allows to configure a title for the view. // If Frame is true, Title allows to configure a title for the view.
Title string 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 // If Mask is true, the View will display the mask instead of the real
// content // content
Mask rune 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 { type viewLine struct {
@ -103,6 +142,7 @@ func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
y0: y0, y0: y0,
x1: x1, x1: x1,
y1: y1, y1: y1,
Visible: true,
Frame: true, Frame: true,
Editor: DefaultEditor, Editor: DefaultEditor,
tainted: true, tainted: true,
@ -111,6 +151,11 @@ func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
return v 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. // Size returns the number of visible columns and rows in the View.
func (v *View) Size() (x, y int) { func (v *View) Size() (x, y int) {
return v.x1 - v.x0 - 1, v.y1 - v.y0 - 1 return v.x1 - v.x0 - 1, v.y1 - v.y0 - 1
@ -127,9 +172,8 @@ func (v *View) Name() string {
func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error { func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
maxX, maxY := v.Size() maxX, maxY := v.Size()
if x < 0 || x >= maxX || y < 0 || y >= maxY { if x < 0 || x >= maxX || y < 0 || y >= maxY {
return errors.New("invalid point") return ErrInvalidPoint
} }
var ( var (
ry, rcy int ry, rcy int
err error err error
@ -150,8 +194,12 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
bgColor = v.BgColor bgColor = v.BgColor
ch = v.Mask ch = v.Mask
} else if v.Highlight && ry == rcy { } else if v.Highlight && ry == rcy {
fgColor = v.SelFgColor fgColor = fgColor | AttrBold
bgColor = v.SelBgColor }
// Don't display NUL characters
if ch == 0 {
ch = ' '
} }
termbox.SetCell(v.x0+x+1, v.y0+y+1, ch, termbox.SetCell(v.x0+x+1, v.y0+y+1, ch,
@ -165,7 +213,7 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
func (v *View) SetCursor(x, y int) error { func (v *View) SetCursor(x, y int) error {
maxX, maxY := v.Size() maxX, maxY := v.Size()
if x < 0 || x >= maxX || y < 0 || y >= maxY { if x < 0 || x >= maxX || y < 0 || y >= maxY {
return errors.New("invalid point") return ErrInvalidPoint
} }
v.cx = x v.cx = x
v.cy = y v.cy = y
@ -184,7 +232,7 @@ func (v *View) Cursor() (x, y int) {
// or decrementing ox and oy. // or decrementing ox and oy.
func (v *View) SetOrigin(x, y int) error { func (v *View) SetOrigin(x, y int) error {
if x < 0 || y < 0 { if x < 0 || y < 0 {
return errors.New("invalid point") return ErrInvalidPoint
} }
v.ox = x v.ox = x
v.oy = y v.oy = y
@ -196,39 +244,141 @@ func (v *View) Origin() (x, y int) {
return v.ox, v.oy 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 // Write appends a byte slice into the view's internal buffer. Because
// View implements the io.Writer interface, it can be passed as parameter // View implements the io.Writer interface, it can be passed as parameter
// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must // of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must
// be called to clear the view's buffer. // be called to clear the view's buffer.
func (v *View) Write(p []byte) (n int, err error) { func (v *View) Write(p []byte) (n int, err error) {
v.tainted = true v.tainted = true
v.writeMutex.Lock()
v.makeWriteable(v.wx, v.wy)
v.writeRunes(bytes.Runes(p))
v.writeMutex.Unlock()
for _, ch := range bytes.Runes(p) { return len(p), nil
switch ch { }
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': case '\n':
v.lines = append(v.lines, nil) v.wy++
case '\r': if v.wy >= len(v.lines) {
nl := len(v.lines) v.lines = append(v.lines, nil)
if nl > 0 {
v.lines[nl-1] = nil
} else {
v.lines = make([][]cell, 1)
} }
fallthrough
// not valid in every OS, but making runtime OS checks in cycle is bad.
case '\r':
v.wx = 0
default: default:
cells := v.parseInput(ch) cells := v.parseInput(r)
if cells == nil { if cells == nil {
continue continue
} }
v.writeCells(v.wx, v.wy, cells)
nl := len(v.lines) v.wx += len(cells)
if nl > 0 {
v.lines[nl-1] = append(v.lines[nl-1], cells...)
} else {
v.lines = append(v.lines, cells)
}
} }
} }
return len(p), nil
} }
// parseInput parses char by char the input written to the View. It returns nil // parseInput parses char by char the input written to the View. It returns nil
@ -252,41 +402,85 @@ func (v *View) parseInput(ch rune) []cell {
if isEscape { if isEscape {
return nil return nil
} }
c := cell{ repeatCount := 1
fgColor: v.ei.curFgColor, if ch == '\t' {
bgColor: v.ei.curBgColor, ch = ' '
chr: 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)
} }
cells = append(cells, c)
} }
return cells return cells
} }
// Read reads data into p. It returns the number of bytes read into p. // Read reads data into p from the current reading position set by SetReadPos.
// At EOF, err will be io.EOF. Calling Read() after Rewind() makes the // It returns the number of bytes read into p.
// cache to be refreshed with the contents of the view. // At EOF, err will be io.EOF.
func (v *View) Read(p []byte) (n int, err error) { func (v *View) Read(p []byte) (n int, err error) {
if v.readOffset == 0 { buffer := make([]byte, utf8.UTFMax)
v.readCache = v.Buffer() 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
} }
if v.readOffset < len(v.readCache) { for v.ry < len(v.lines) {
n = copy(p, v.readCache[v.readOffset:]) for v.rx < len(v.lines[v.ry]) {
v.readOffset += n count := utf8.EncodeRune(buffer, v.lines[v.ry][v.rx].chr)
} else { copy(p[offset:], buffer[:count])
err = io.EOF 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 return offset, io.EOF
} }
// Rewind sets the offset for the next Read to 0, which also refresh the // Rewind sets read and write pos to (0, 0).
// read cache.
func (v *View) Rewind() { func (v *View) Rewind() {
v.readOffset = 0 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. // draw re-draws the view's contents.
func (v *View) draw() error { func (v *View) draw() error {
if !v.Visible {
return nil
}
maxX, maxY := v.Size() maxX, maxY := v.Size()
if v.Wrap { if v.Wrap {
@ -297,29 +491,25 @@ func (v *View) draw() error {
} }
if v.tainted { if v.tainted {
v.viewLines = nil v.viewLines = nil
for i, line := range v.lines { lines := v.lines
if v.HasLoader {
lines = v.loaderLines()
}
for i, line := range lines {
wrap := 0
if v.Wrap { if v.Wrap {
if len(line) < maxX { wrap = maxX
vline := viewLine{linesX: 0, linesY: i, line: line} }
v.viewLines = append(v.viewLines, vline)
continue ls := lineWrap(line, wrap)
} else { for j := range ls {
for n := 0; n <= len(line); n += maxX { vline := viewLine{linesX: j, linesY: i, line: ls[j]}
if len(line[n:]) <= maxX {
vline := viewLine{linesX: n, linesY: i, line: line[n:]}
v.viewLines = append(v.viewLines, vline)
} else {
vline := viewLine{linesX: n, linesY: i, line: line[n : n+maxX]}
v.viewLines = append(v.viewLines, vline)
}
}
}
} else {
vline := viewLine{linesX: 0, linesY: i, line: line}
v.viewLines = append(v.viewLines, vline) v.viewLines = append(v.viewLines, vline)
} }
} }
v.tainted = false if !v.HasLoader {
v.tainted = false
}
} }
if v.Autoscroll && len(v.viewLines) > maxY { if v.Autoscroll && len(v.viewLines) > maxY {
@ -354,7 +544,15 @@ func (v *View) draw() error {
if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil { if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil {
return err return err
} }
x += runewidth.RuneWidth(c.chr)
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++ y++
} }
@ -368,7 +566,7 @@ func (v *View) realPosition(vx, vy int) (x, y int, err error) {
vy = v.oy + vy vy = v.oy + vy
if vx < 0 || vy < 0 { if vx < 0 || vy < 0 {
return 0, 0, errors.New("invalid point") return 0, 0, ErrInvalidPoint
} }
if len(v.viewLines) == 0 { if len(v.viewLines) == 0 {
@ -389,13 +587,16 @@ func (v *View) realPosition(vx, vy int) (x, y int, err error) {
} }
// Clear empties the view's internal buffer. // Clear empties the view's internal buffer.
// And resets reading and writing offsets.
func (v *View) Clear() { func (v *View) Clear() {
v.writeMutex.Lock()
v.Rewind()
v.tainted = true v.tainted = true
v.ei.reset()
v.lines = nil v.lines = nil
v.viewLines = nil v.viewLines = nil
v.readOffset = 0
v.clearRunes() v.clearRunes()
v.writeMutex.Unlock()
} }
// clearRunes erases all the cells in the view. // clearRunes erases all the cells in the view.
@ -424,11 +625,7 @@ func (v *View) BufferLines() []string {
// Buffer returns a string with the contents of the view's internal // Buffer returns a string with the contents of the view's internal
// buffer. // buffer.
func (v *View) Buffer() string { func (v *View) Buffer() string {
str := "" return linesToString(v.lines)
for _, l := range v.lines {
str += lineType(l).String() + "\n"
}
return strings.Replace(str, "\x00", " ", -1)
} }
// ViewBufferLines returns the lines in the view's internal // ViewBufferLines returns the lines in the view's internal
@ -443,14 +640,25 @@ func (v *View) ViewBufferLines() []string {
return lines 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 // ViewBuffer returns a string with the contents of the view's buffer that is
// shown to the user. // shown to the user.
func (v *View) ViewBuffer() string { func (v *View) ViewBuffer() string {
str := "" lines := make([][]cell, len(v.viewLines))
for _, l := range v.viewLines { for i := range v.viewLines {
str += lineType(l.line).String() + "\n" lines[i] = v.viewLines[i].line
} }
return strings.Replace(str, "\x00", " ", -1)
return linesToString(lines)
} }
// Line returns a string with the line of the view's internal buffer // Line returns a string with the line of the view's internal buffer
@ -462,7 +670,7 @@ func (v *View) Line(y int) (string, error) {
} }
if y < 0 || y >= len(v.lines) { if y < 0 || y >= len(v.lines) {
return "", errors.New("invalid point") return "", ErrInvalidPoint
} }
return lineType(v.lines[y]).String(), nil return lineType(v.lines[y]).String(), nil
@ -477,7 +685,7 @@ func (v *View) Word(x, y int) (string, error) {
} }
if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) { if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
return "", errors.New("invalid point") return "", ErrInvalidPoint
} }
str := lineType(v.lines[y]).String() str := lineType(v.lines[y]).String()
@ -502,3 +710,91 @@ func (v *View) Word(x, y int) (string, error) {
func indexFunc(r rune) bool { func indexFunc(r rune) bool {
return r == ' ' || r == 0 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")
}

View File

@ -1,5 +1,9 @@
[![GoDoc](https://godoc.org/github.com/nsf/termbox-go?status.svg)](http://godoc.org/github.com/nsf/termbox-go) [![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
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. 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.
@ -17,6 +21,7 @@ There are also some interesting projects using termbox-go:
- [httopd](https://github.com/verdverm/httopd) is top for httpd logs. - [httopd](https://github.com/verdverm/httopd) is top for httpd logs.
- [mop](https://github.com/mop-tracker/mop) is stock market tracker for hackers. - [mop](https://github.com/mop-tracker/mop) is stock market tracker for hackers.
- [termui](https://github.com/gizak/termui) is a terminal dashboard. - [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. - [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. - [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. - [gocui](https://github.com/jroimartin/gocui) is a minimalist Go library aimed at creating console user interfaces.
@ -38,6 +43,7 @@ There are also some interesting projects using termbox-go:
- [gotypist](https://github.com/pb-/gotypist) is a fun touch-typing tutor following Steve Yegge's method. - [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. - [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. - [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 ### API reference
[godoc.org/github.com/nsf/termbox-go](http://godoc.org/github.com/nsf/termbox-go) [godoc.org/github.com/nsf/termbox-go](http://godoc.org/github.com/nsf/termbox-go)

View File

@ -24,13 +24,21 @@ import "time"
func Init() error { func Init() error {
var err error var err error
out, err = os.OpenFile("/dev/tty", syscall.O_WRONLY, 0) if runtime.GOOS == "openbsd" {
if err != nil { out, err = os.OpenFile("/dev/tty", os.O_RDWR, 0)
return err if err != nil {
} return err
in, err = syscall.Open("/dev/tty", syscall.O_RDONLY, 0) }
if err != nil { in = int(out.Fd())
return err } 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() err = setup_term()
@ -317,6 +325,9 @@ func PollEvent() Event {
event.Type = EventKey event.Type = EventKey
status := extract_event(inbuf, &event, true) status := extract_event(inbuf, &event, true)
if event.N != 0 { if event.N != 0 {
if event.N > len(inbuf) {
event.N = len(inbuf)
}
copy(inbuf, inbuf[event.N:]) copy(inbuf, inbuf[event.N:])
inbuf = inbuf[:len(inbuf)-event.N] inbuf = inbuf[:len(inbuf)-event.N]
} }
@ -345,6 +356,9 @@ func PollEvent() Event {
input_comm <- ev input_comm <- ev
status := extract_event(inbuf, &event, true) status := extract_event(inbuf, &event, true)
if event.N != 0 { if event.N != 0 {
if event.N > len(inbuf) {
event.N = len(inbuf)
}
copy(inbuf, inbuf[event.N:]) copy(inbuf, inbuf[event.N:])
inbuf = inbuf[:len(inbuf)-event.N] inbuf = inbuf[:len(inbuf)-event.N]
} }
@ -359,6 +373,9 @@ func PollEvent() Event {
status := extract_event(inbuf, &event, false) status := extract_event(inbuf, &event, false)
if event.N != 0 { if event.N != 0 {
if event.N > len(inbuf) {
event.N = len(inbuf)
}
copy(inbuf, inbuf[event.N:]) copy(inbuf, inbuf[event.N:])
inbuf = inbuf[:len(inbuf)-event.N] inbuf = inbuf[:len(inbuf)-event.N]
} }

View File

@ -42,7 +42,7 @@ func Init() error {
return err return err
} }
orig_size = get_term_size(out) orig_size, orig_window = get_term_size(out)
win_size := get_win_size(out) win_size := get_win_size(out)
err = set_console_screen_buffer_size(out, win_size) err = set_console_screen_buffer_size(out, win_size)
@ -50,13 +50,18 @@ func Init() error {
return err return err
} }
err = fix_win_size(out, win_size)
if err != nil {
return err
}
err = get_console_cursor_info(out, &orig_cursor_info) err = get_console_cursor_info(out, &orig_cursor_info)
if err != nil { if err != nil {
return err return err
} }
show_cursor(false) show_cursor(false)
term_size = get_term_size(out) term_size, _ = get_term_size(out)
back_buffer.init(int(term_size.x), int(term_size.y)) back_buffer.init(int(term_size.x), int(term_size.y))
front_buffer.init(int(term_size.x), int(term_size.y)) front_buffer.init(int(term_size.x), int(term_size.y))
back_buffer.clear() back_buffer.clear()
@ -86,9 +91,10 @@ func Close() {
} }
<-cancel_done_comm <-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_info(out, &orig_cursor_info)
set_console_cursor_position(out, coord{}) set_console_cursor_position(out, coord{})
set_console_screen_buffer_size(out, orig_size)
set_console_mode(in, orig_mode) set_console_mode(in, orig_mode)
syscall.Close(in) syscall.Close(in)
syscall.Close(out) syscall.Close(out)

View File

@ -76,6 +76,10 @@ func (this coord) uintptr() uintptr {
return uintptr(*(*int32)(unsafe.Pointer(&this))) return uintptr(*(*int32)(unsafe.Pointer(&this)))
} }
func (this *small_rect) uintptr() uintptr {
return uintptr(unsafe.Pointer(this))
}
var kernel32 = syscall.NewLazyDLL("kernel32.dll") var kernel32 = syscall.NewLazyDLL("kernel32.dll")
var moduser32 = syscall.NewLazyDLL("user32.dll") var moduser32 = syscall.NewLazyDLL("user32.dll")
var is_cjk = runewidth.IsEastAsian() var is_cjk = runewidth.IsEastAsian()
@ -83,6 +87,7 @@ var is_cjk = runewidth.IsEastAsian()
var ( var (
proc_set_console_active_screen_buffer = kernel32.NewProc("SetConsoleActiveScreenBuffer") proc_set_console_active_screen_buffer = kernel32.NewProc("SetConsoleActiveScreenBuffer")
proc_set_console_screen_buffer_size = kernel32.NewProc("SetConsoleScreenBufferSize") 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_create_console_screen_buffer = kernel32.NewProc("CreateConsoleScreenBuffer")
proc_get_console_screen_buffer_info = kernel32.NewProc("GetConsoleScreenBufferInfo") proc_get_console_screen_buffer_info = kernel32.NewProc("GetConsoleScreenBufferInfo")
proc_write_console_output = kernel32.NewProc("WriteConsoleOutputW") proc_write_console_output = kernel32.NewProc("WriteConsoleOutputW")
@ -129,6 +134,21 @@ func set_console_screen_buffer_size(h syscall.Handle, size coord) (err error) {
return 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) { func create_console_screen_buffer() (h syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall6(proc_create_console_screen_buffer.Addr(), r0, _, e1 := syscall.Syscall6(proc_create_console_screen_buffer.Addr(),
5, uintptr(generic_read|generic_write), 0, 0, console_textmode_buffer, 0, 0) 5, uintptr(generic_read|generic_write), 0, 0, console_textmode_buffer, 0, 0)
@ -278,6 +298,7 @@ func set_console_mode(h syscall.Handle, mode dword) (err error) {
} }
func fill_console_output_character(h syscall.Handle, char wchar, n int) (err error) { 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(), r0, _, e1 := syscall.Syscall6(proc_fill_console_output_character.Addr(),
5, uintptr(h), uintptr(char), uintptr(n), tmp_coord.uintptr(), 5, uintptr(h), uintptr(char), uintptr(n), tmp_coord.uintptr(),
uintptr(unsafe.Pointer(&tmp_arg)), 0) uintptr(unsafe.Pointer(&tmp_arg)), 0)
@ -292,6 +313,7 @@ func fill_console_output_character(h syscall.Handle, char wchar, n int) (err err
} }
func fill_console_output_attribute(h syscall.Handle, attr word, n int) (err error) { 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(), r0, _, e1 := syscall.Syscall6(proc_fill_console_output_attribute.Addr(),
5, uintptr(h), uintptr(attr), uintptr(n), tmp_coord.uintptr(), 5, uintptr(h), uintptr(attr), uintptr(n), tmp_coord.uintptr(),
uintptr(unsafe.Pointer(&tmp_arg)), 0) uintptr(unsafe.Pointer(&tmp_arg)), 0)
@ -372,6 +394,7 @@ type input_event struct {
var ( var (
orig_cursor_info console_cursor_info orig_cursor_info console_cursor_info
orig_size coord orig_size coord
orig_window small_rect
orig_mode dword orig_mode dword
orig_screen syscall.Handle orig_screen syscall.Handle
back_buffer cellbuf back_buffer cellbuf
@ -413,12 +436,12 @@ func get_cursor_position(out syscall.Handle) coord {
return tmp_info.cursor_position return tmp_info.cursor_position
} }
func get_term_size(out syscall.Handle) coord { func get_term_size(out syscall.Handle) (coord, small_rect) {
err := get_console_screen_buffer_info(out, &tmp_info) err := get_console_screen_buffer_info(out, &tmp_info)
if err != nil { if err != nil {
panic(err) panic(err)
} }
return tmp_info.size return tmp_info.size, tmp_info.window
} }
func get_win_min_size(out syscall.Handle) coord { func get_win_min_size(out syscall.Handle) coord {
@ -466,10 +489,20 @@ func get_win_size(out syscall.Handle) coord {
return size 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() { func update_size_maybe() {
size := get_win_size(out) size := get_win_size(out)
if size.x != term_size.x || size.y != term_size.y { if size.x != term_size.x || size.y != term_size.y {
set_console_screen_buffer_size(out, size) set_console_screen_buffer_size(out, size)
fix_win_size(out, size)
term_size = size term_size = size
back_buffer.resize(int(size.x), int(size.y)) back_buffer.resize(int(size.x), int(size.y))
front_buffer.resize(int(size.x), int(size.y)) front_buffer.resize(int(size.x), int(size.y))
@ -490,8 +523,8 @@ var color_table_bg = []word{
background_green, background_green,
background_red | background_green, // yellow background_red | background_green, // yellow
background_blue, background_blue,
background_red | background_blue, // magenta background_red | background_blue, // magenta
background_green | background_blue, // cyan background_green | background_blue, // cyan
background_red | background_blue | background_green, // white background_red | background_blue | background_green, // white
} }
@ -502,8 +535,8 @@ var color_table_fg = []word{
foreground_green, foreground_green,
foreground_red | foreground_green, // yellow foreground_red | foreground_green, // yellow
foreground_blue, foreground_blue,
foreground_red | foreground_blue, // magenta foreground_red | foreground_blue, // magenta
foreground_green | foreground_blue, // cyan foreground_green | foreground_blue, // cyan
foreground_red | foreground_blue | foreground_green, // white foreground_red | foreground_blue | foreground_green, // white
} }

5
vendor/github.com/go-errors/errors/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,5 @@
language: go
go:
- "1.8.x"
- "1.10.x"

7
vendor/github.com/go-errors/errors/LICENSE.MIT generated vendored Normal file
View File

@ -0,0 +1,7 @@
Copyright (c) 2015 Conrad Irwin <conrad@bugsnag.com>
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.

66
vendor/github.com/go-errors/errors/README.md generated vendored Normal file
View File

@ -0,0 +1,66 @@
go-errors/errors
================
[![Build Status](https://travis-ci.org/go-errors/errors.svg?branch=master)](https://travis-ci.org/go-errors/errors)
Package errors adds stacktrace support to errors in go.
This is particularly useful when you want to understand the state of execution
when an error was returned unexpectedly.
It provides the type \*Error which implements the standard golang error
interface, so you can use this library interchangably with code that is
expecting a normal error return.
Usage
-----
Full documentation is available on
[godoc](https://godoc.org/github.com/go-errors/errors), but here's a simple
example:
```go
package crashy
import "github.com/go-errors/errors"
var Crashed = errors.Errorf("oh dear")
func Crash() error {
return errors.New(Crashed)
}
```
This can be called as follows:
```go
package main
import (
"crashy"
"fmt"
"github.com/go-errors/errors"
)
func main() {
err := crashy.Crash()
if err != nil {
if errors.Is(err, crashy.Crashed) {
fmt.Println(err.(*errors.Error).ErrorStack())
} else {
panic(err)
}
}
}
```
Meta-fu
-------
This package was original written to allow reporting to
[Bugsnag](https://bugsnag.com/) from
[bugsnag-go](https://github.com/bugsnag/bugsnag-go), but after I found similar
packages by Facebook and Dropbox, it was moved to one canonical location so
everyone can benefit.
This package is licensed under the MIT license, see LICENSE.MIT for details.

89
vendor/github.com/go-errors/errors/cover.out generated vendored Normal file
View File

@ -0,0 +1,89 @@
mode: set
github.com/go-errors/errors/stackframe.go:27.51,30.25 2 1
github.com/go-errors/errors/stackframe.go:33.2,38.8 3 1
github.com/go-errors/errors/stackframe.go:30.25,32.3 1 0
github.com/go-errors/errors/stackframe.go:43.47,44.31 1 1
github.com/go-errors/errors/stackframe.go:47.2,47.48 1 1
github.com/go-errors/errors/stackframe.go:44.31,46.3 1 1
github.com/go-errors/errors/stackframe.go:52.42,56.16 3 1
github.com/go-errors/errors/stackframe.go:60.2,60.60 1 1
github.com/go-errors/errors/stackframe.go:56.16,58.3 1 0
github.com/go-errors/errors/stackframe.go:64.55,67.16 2 1
github.com/go-errors/errors/stackframe.go:71.2,72.61 2 1
github.com/go-errors/errors/stackframe.go:76.2,76.66 1 1
github.com/go-errors/errors/stackframe.go:67.16,69.3 1 0
github.com/go-errors/errors/stackframe.go:72.61,74.3 1 0
github.com/go-errors/errors/stackframe.go:79.56,91.63 3 1
github.com/go-errors/errors/stackframe.go:95.2,95.53 1 1
github.com/go-errors/errors/stackframe.go:100.2,101.18 2 1
github.com/go-errors/errors/stackframe.go:91.63,94.3 2 1
github.com/go-errors/errors/stackframe.go:95.53,98.3 2 1
github.com/go-errors/errors/error.go:70.32,73.23 2 1
github.com/go-errors/errors/error.go:80.2,85.3 3 1
github.com/go-errors/errors/error.go:74.2,75.10 1 1
github.com/go-errors/errors/error.go:76.2,77.28 1 1
github.com/go-errors/errors/error.go:92.43,95.23 2 1
github.com/go-errors/errors/error.go:104.2,109.3 3 1
github.com/go-errors/errors/error.go:96.2,97.11 1 1
github.com/go-errors/errors/error.go:98.2,99.10 1 1
github.com/go-errors/errors/error.go:100.2,101.28 1 1
github.com/go-errors/errors/error.go:115.39,117.19 1 1
github.com/go-errors/errors/error.go:121.2,121.29 1 1
github.com/go-errors/errors/error.go:125.2,125.43 1 1
github.com/go-errors/errors/error.go:129.2,129.14 1 1
github.com/go-errors/errors/error.go:117.19,119.3 1 1
github.com/go-errors/errors/error.go:121.29,123.3 1 1
github.com/go-errors/errors/error.go:125.43,127.3 1 1
github.com/go-errors/errors/error.go:135.53,137.2 1 1
github.com/go-errors/errors/error.go:140.34,142.2 1 1
github.com/go-errors/errors/error.go:146.34,149.42 2 1
github.com/go-errors/errors/error.go:153.2,153.20 1 1
github.com/go-errors/errors/error.go:149.42,151.3 1 1
github.com/go-errors/errors/error.go:158.39,160.2 1 1
github.com/go-errors/errors/error.go:164.46,165.23 1 1
github.com/go-errors/errors/error.go:173.2,173.19 1 1
github.com/go-errors/errors/error.go:165.23,168.32 2 1
github.com/go-errors/errors/error.go:168.32,170.4 1 1
github.com/go-errors/errors/error.go:177.37,178.42 1 1
github.com/go-errors/errors/error.go:181.2,181.41 1 1
github.com/go-errors/errors/error.go:178.42,180.3 1 1
github.com/go-errors/errors/parse_panic.go:10.39,12.2 1 1
github.com/go-errors/errors/parse_panic.go:16.46,24.34 5 1
github.com/go-errors/errors/parse_panic.go:70.2,70.43 1 1
github.com/go-errors/errors/parse_panic.go:73.2,73.55 1 0
github.com/go-errors/errors/parse_panic.go:24.34,27.23 2 1
github.com/go-errors/errors/parse_panic.go:27.23,28.42 1 1
github.com/go-errors/errors/parse_panic.go:28.42,31.5 2 1
github.com/go-errors/errors/parse_panic.go:31.6,33.5 1 0
github.com/go-errors/errors/parse_panic.go:35.5,35.29 1 1
github.com/go-errors/errors/parse_panic.go:35.29,36.86 1 1
github.com/go-errors/errors/parse_panic.go:36.86,38.5 1 1
github.com/go-errors/errors/parse_panic.go:40.5,40.32 1 1
github.com/go-errors/errors/parse_panic.go:40.32,41.18 1 1
github.com/go-errors/errors/parse_panic.go:45.4,46.46 2 1
github.com/go-errors/errors/parse_panic.go:51.4,53.23 2 1
github.com/go-errors/errors/parse_panic.go:57.4,58.18 2 1
github.com/go-errors/errors/parse_panic.go:62.4,63.17 2 1
github.com/go-errors/errors/parse_panic.go:41.18,43.10 2 1
github.com/go-errors/errors/parse_panic.go:46.46,49.5 2 1
github.com/go-errors/errors/parse_panic.go:53.23,55.5 1 0
github.com/go-errors/errors/parse_panic.go:58.18,60.5 1 0
github.com/go-errors/errors/parse_panic.go:63.17,65.10 2 1
github.com/go-errors/errors/parse_panic.go:70.43,72.3 1 1
github.com/go-errors/errors/parse_panic.go:80.85,82.29 2 1
github.com/go-errors/errors/parse_panic.go:85.2,85.15 1 1
github.com/go-errors/errors/parse_panic.go:88.2,90.63 2 1
github.com/go-errors/errors/parse_panic.go:94.2,94.53 1 1
github.com/go-errors/errors/parse_panic.go:99.2,101.36 2 1
github.com/go-errors/errors/parse_panic.go:105.2,106.15 2 1
github.com/go-errors/errors/parse_panic.go:109.2,112.49 3 1
github.com/go-errors/errors/parse_panic.go:116.2,117.16 2 1
github.com/go-errors/errors/parse_panic.go:121.2,126.8 1 1
github.com/go-errors/errors/parse_panic.go:82.29,84.3 1 0
github.com/go-errors/errors/parse_panic.go:85.15,87.3 1 1
github.com/go-errors/errors/parse_panic.go:90.63,93.3 2 1
github.com/go-errors/errors/parse_panic.go:94.53,97.3 2 1
github.com/go-errors/errors/parse_panic.go:101.36,103.3 1 0
github.com/go-errors/errors/parse_panic.go:106.15,108.3 1 0
github.com/go-errors/errors/parse_panic.go:112.49,114.3 1 1
github.com/go-errors/errors/parse_panic.go:117.16,119.3 1 0

217
vendor/github.com/go-errors/errors/error.go generated vendored Normal file
View File

@ -0,0 +1,217 @@
// Package errors provides errors that have stack-traces.
//
// This is particularly useful when you want to understand the
// state of execution when an error was returned unexpectedly.
//
// It provides the type *Error which implements the standard
// golang error interface, so you can use this library interchangably
// with code that is expecting a normal error return.
//
// For example:
//
// package crashy
//
// import "github.com/go-errors/errors"
//
// var Crashed = errors.Errorf("oh dear")
//
// func Crash() error {
// return errors.New(Crashed)
// }
//
// This can be called as follows:
//
// package main
//
// import (
// "crashy"
// "fmt"
// "github.com/go-errors/errors"
// )
//
// func main() {
// err := crashy.Crash()
// if err != nil {
// if errors.Is(err, crashy.Crashed) {
// fmt.Println(err.(*errors.Error).ErrorStack())
// } else {
// panic(err)
// }
// }
// }
//
// This package was original written to allow reporting to Bugsnag,
// but after I found similar packages by Facebook and Dropbox, it
// was moved to one canonical location so everyone can benefit.
package errors
import (
"bytes"
"fmt"
"reflect"
"runtime"
)
// The maximum number of stackframes on any error.
var MaxStackDepth = 50
// Error is an error with an attached stacktrace. It can be used
// wherever the builtin error interface is expected.
type Error struct {
Err error
stack []uintptr
frames []StackFrame
prefix string
}
// New makes an Error from the given value. If that value is already an
// error then it will be used directly, if not, it will be passed to
// fmt.Errorf("%v"). The stacktrace will point to the line of code that
// called New.
func New(e interface{}) *Error {
var err error
switch e := e.(type) {
case error:
err = e
default:
err = fmt.Errorf("%v", e)
}
stack := make([]uintptr, MaxStackDepth)
length := runtime.Callers(2, stack[:])
return &Error{
Err: err,
stack: stack[:length],
}
}
// Wrap makes an Error from the given value. If that value is already an
// error then it will be used directly, if not, it will be passed to
// fmt.Errorf("%v"). The skip parameter indicates how far up the stack
// to start the stacktrace. 0 is from the current call, 1 from its caller, etc.
func Wrap(e interface{}, skip int) *Error {
var err error
switch e := e.(type) {
case *Error:
return e
case error:
err = e
default:
err = fmt.Errorf("%v", e)
}
stack := make([]uintptr, MaxStackDepth)
length := runtime.Callers(2+skip, stack[:])
return &Error{
Err: err,
stack: stack[:length],
}
}
// WrapPrefix makes an Error from the given value. If that value is already an
// error then it will be used directly, if not, it will be passed to
// fmt.Errorf("%v"). The prefix parameter is used to add a prefix to the
// error message when calling Error(). The skip parameter indicates how far
// up the stack to start the stacktrace. 0 is from the current call,
// 1 from its caller, etc.
func WrapPrefix(e interface{}, prefix string, skip int) *Error {
err := Wrap(e, 1+skip)
if err.prefix != "" {
prefix = fmt.Sprintf("%s: %s", prefix, err.prefix)
}
return &Error{
Err: err.Err,
stack: err.stack,
prefix: prefix,
}
}
// Is detects whether the error is equal to a given error. Errors
// are considered equal by this function if they are the same object,
// or if they both contain the same error inside an errors.Error.
func Is(e error, original error) bool {
if e == original {
return true
}
if e, ok := e.(*Error); ok {
return Is(e.Err, original)
}
if original, ok := original.(*Error); ok {
return Is(e, original.Err)
}
return false
}
// Errorf creates a new error with the given message. You can use it
// as a drop-in replacement for fmt.Errorf() to provide descriptive
// errors in return values.
func Errorf(format string, a ...interface{}) *Error {
return Wrap(fmt.Errorf(format, a...), 1)
}
// Error returns the underlying error's message.
func (err *Error) Error() string {
msg := err.Err.Error()
if err.prefix != "" {
msg = fmt.Sprintf("%s: %s", err.prefix, msg)
}
return msg
}
// Stack returns the callstack formatted the same way that go does
// in runtime/debug.Stack()
func (err *Error) Stack() []byte {
buf := bytes.Buffer{}
for _, frame := range err.StackFrames() {
buf.WriteString(frame.String())
}
return buf.Bytes()
}
// Callers satisfies the bugsnag ErrorWithCallerS() interface
// so that the stack can be read out.
func (err *Error) Callers() []uintptr {
return err.stack
}
// ErrorStack returns a string that contains both the
// error message and the callstack.
func (err *Error) ErrorStack() string {
return err.TypeName() + " " + err.Error() + "\n" + string(err.Stack())
}
// StackFrames returns an array of frames containing information about the
// stack.
func (err *Error) StackFrames() []StackFrame {
if err.frames == nil {
err.frames = make([]StackFrame, len(err.stack))
for i, pc := range err.stack {
err.frames[i] = NewStackFrame(pc)
}
}
return err.frames
}
// TypeName returns the type this error. e.g. *errors.stringError.
func (err *Error) TypeName() string {
if _, ok := err.Err.(uncaughtPanic); ok {
return "panic"
}
return reflect.TypeOf(err.Err).String()
}

127
vendor/github.com/go-errors/errors/parse_panic.go generated vendored Normal file
View File

@ -0,0 +1,127 @@
package errors
import (
"strconv"
"strings"
)
type uncaughtPanic struct{ message string }
func (p uncaughtPanic) Error() string {
return p.message
}
// ParsePanic allows you to get an error object from the output of a go program
// that panicked. This is particularly useful with https://github.com/mitchellh/panicwrap.
func ParsePanic(text string) (*Error, error) {
lines := strings.Split(text, "\n")
state := "start"
var message string
var stack []StackFrame
for i := 0; i < len(lines); i++ {
line := lines[i]
if state == "start" {
if strings.HasPrefix(line, "panic: ") {
message = strings.TrimPrefix(line, "panic: ")
state = "seek"
} else {
return nil, Errorf("bugsnag.panicParser: Invalid line (no prefix): %s", line)
}
} else if state == "seek" {
if strings.HasPrefix(line, "goroutine ") && strings.HasSuffix(line, "[running]:") {
state = "parsing"
}
} else if state == "parsing" {
if line == "" {
state = "done"
break
}
createdBy := false
if strings.HasPrefix(line, "created by ") {
line = strings.TrimPrefix(line, "created by ")
createdBy = true
}
i++
if i >= len(lines) {
return nil, Errorf("bugsnag.panicParser: Invalid line (unpaired): %s", line)
}
frame, err := parsePanicFrame(line, lines[i], createdBy)
if err != nil {
return nil, err
}
stack = append(stack, *frame)
if createdBy {
state = "done"
break
}
}
}
if state == "done" || state == "parsing" {
return &Error{Err: uncaughtPanic{message}, frames: stack}, nil
}
return nil, Errorf("could not parse panic: %v", text)
}
// The lines we're passing look like this:
//
// main.(*foo).destruct(0xc208067e98)
// /0/go/src/github.com/bugsnag/bugsnag-go/pan/main.go:22 +0x151
func parsePanicFrame(name string, line string, createdBy bool) (*StackFrame, error) {
idx := strings.LastIndex(name, "(")
if idx == -1 && !createdBy {
return nil, Errorf("bugsnag.panicParser: Invalid line (no call): %s", name)
}
if idx != -1 {
name = name[:idx]
}
pkg := ""
if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
pkg += name[:lastslash] + "/"
name = name[lastslash+1:]
}
if period := strings.Index(name, "."); period >= 0 {
pkg += name[:period]
name = name[period+1:]
}
name = strings.Replace(name, "·", ".", -1)
if !strings.HasPrefix(line, "\t") {
return nil, Errorf("bugsnag.panicParser: Invalid line (no tab): %s", line)
}
idx = strings.LastIndex(line, ":")
if idx == -1 {
return nil, Errorf("bugsnag.panicParser: Invalid line (no line number): %s", line)
}
file := line[1:idx]
number := line[idx+1:]
if idx = strings.Index(number, " +"); idx > -1 {
number = number[:idx]
}
lno, err := strconv.ParseInt(number, 10, 32)
if err != nil {
return nil, Errorf("bugsnag.panicParser: Invalid line (bad line number): %s", line)
}
return &StackFrame{
File: file,
LineNumber: int(lno),
Package: pkg,
Name: name,
}, nil
}

102
vendor/github.com/go-errors/errors/stackframe.go generated vendored Normal file
View File

@ -0,0 +1,102 @@
package errors
import (
"bytes"
"fmt"
"io/ioutil"
"runtime"
"strings"
)
// A StackFrame contains all necessary information about to generate a line
// in a callstack.
type StackFrame struct {
// The path to the file containing this ProgramCounter
File string
// The LineNumber in that file
LineNumber int
// The Name of the function that contains this ProgramCounter
Name string
// The Package that contains this function
Package string
// The underlying ProgramCounter
ProgramCounter uintptr
}
// NewStackFrame popoulates a stack frame object from the program counter.
func NewStackFrame(pc uintptr) (frame StackFrame) {
frame = StackFrame{ProgramCounter: pc}
if frame.Func() == nil {
return
}
frame.Package, frame.Name = packageAndName(frame.Func())
// pc -1 because the program counters we use are usually return addresses,
// and we want to show the line that corresponds to the function call
frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1)
return
}
// Func returns the function that contained this frame.
func (frame *StackFrame) Func() *runtime.Func {
if frame.ProgramCounter == 0 {
return nil
}
return runtime.FuncForPC(frame.ProgramCounter)
}
// String returns the stackframe formatted in the same way as go does
// in runtime/debug.Stack()
func (frame *StackFrame) String() string {
str := fmt.Sprintf("%s:%d (0x%x)\n", frame.File, frame.LineNumber, frame.ProgramCounter)
source, err := frame.SourceLine()
if err != nil {
return str
}
return str + fmt.Sprintf("\t%s: %s\n", frame.Name, source)
}
// SourceLine gets the line of code (from File and Line) of the original source if possible.
func (frame *StackFrame) SourceLine() (string, error) {
data, err := ioutil.ReadFile(frame.File)
if err != nil {
return "", New(err)
}
lines := bytes.Split(data, []byte{'\n'})
if frame.LineNumber <= 0 || frame.LineNumber >= len(lines) {
return "???", nil
}
// -1 because line-numbers are 1 based, but our array is 0 based
return string(bytes.Trim(lines[frame.LineNumber-1], " \t")), nil
}
func packageAndName(fn *runtime.Func) (string, string) {
name := fn.Name()
pkg := ""
// The name includes the path name to the package, which is unnecessary
// since the file name is already included. Plus, it has center dots.
// That is, we see
// runtime/debug.*T·ptrmethod
// and want
// *T.ptrmethod
// Since the package path might contains dots (e.g. code.google.com/...),
// we first remove the path prefix if there is one.
if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
pkg += name[:lastslash] + "/"
name = name[lastslash+1:]
}
if period := strings.Index(name, "."); period >= 0 {
pkg += name[:period]
name = name[period+1:]
}
name = strings.Replace(name, "·", ".", -1)
return pkg, name
}