mirror of
https://github.com/MichaelMure/git-bug.git
synced 2024-12-14 08:45:30 +03:00
termui: migrate to awesome-gocui instead of the old fork I had
This commit is contained in:
parent
163ea9c933
commit
cb8236c9c2
42
Gopkg.lock
generated
42
Gopkg.lock
generated
@ -32,14 +32,6 @@
|
||||
revision = "60f9049b9d18b9370b8ed1247fe4334af5db131a"
|
||||
version = "v0.2.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:38a84d9b4cf50b3e8eb2b54f218413ac163076e3a7763afe5fa15a4eb15fbda6"
|
||||
name = "github.com/MichaelMure/gocui"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "d753c235dd8582d55e99bbb7f7fe453fb3fd3a19"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:897d91c431ce469d35a5e6030e60e617dccd9a0e95bdffa6a80594f5c5800d29"
|
||||
name = "github.com/agnivade/levenshtein"
|
||||
@ -56,6 +48,22 @@
|
||||
pruneopts = "UT"
|
||||
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]]
|
||||
digest = "1:b6d886569181ec96ca83d529f4d6ba0cbf92ace7bb6f633f90c5f34d9bba7aab"
|
||||
name = "github.com/blang/semver"
|
||||
@ -112,6 +120,14 @@
|
||||
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
|
||||
version = "v1.7.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:aacef5f5e45685f2aeda5534d0a750dee6859de7e9088cdd06192787bb01ae6d"
|
||||
name = "github.com/go-errors/errors"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "a6af135bd4e28680facf08a3d206b454abc877a4"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:97df918963298c287643883209a2c3f642e6593379f97ab400c2a2e219ab647d"
|
||||
name = "github.com/golang/protobuf"
|
||||
@ -203,14 +219,6 @@
|
||||
pruneopts = "UT"
|
||||
revision = "fa473d140ef3c6adf42d6b391fe76707f1f243c8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:c9b6e36dbd23f8403a04493376916ca5dad8c01b2da5ae0a05e6a468eb0b6f24"
|
||||
name = "github.com/nsf/termbox-go"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "5c94acc5e6eb520f1bcd183974e01171cc4c23b3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7413525ee648f20b4181be7fe8103d0cb98be9e141926a03ee082dc207061e4e"
|
||||
name = "github.com/phayes/freeport"
|
||||
@ -481,8 +489,8 @@
|
||||
"github.com/99designs/gqlgen/graphql/introspection",
|
||||
"github.com/99designs/gqlgen/handler",
|
||||
"github.com/MichaelMure/go-term-text",
|
||||
"github.com/MichaelMure/gocui",
|
||||
"github.com/araddon/dateparse",
|
||||
"github.com/awesome-gocui/gocui",
|
||||
"github.com/blang/semver",
|
||||
"github.com/cheekybits/genny/generic",
|
||||
"github.com/dustin/go-humanize",
|
||||
|
@ -61,7 +61,7 @@
|
||||
version = "0.9.2"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/MichaelMure/gocui"
|
||||
name = "github.com/awesome-gocui/gocui"
|
||||
branch = "master"
|
||||
|
||||
[[override]]
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/MichaelMure/go-term-text"
|
||||
"github.com/MichaelMure/gocui"
|
||||
"github.com/awesome-gocui/gocui"
|
||||
"github.com/dustin/go-humanize"
|
||||
|
||||
"github.com/MichaelMure/git-bug/cache"
|
||||
@ -56,10 +56,10 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
|
||||
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 != gocui.ErrUnknownView {
|
||||
if !gocui.IsUnknownView(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -69,10 +69,10 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
|
||||
v.Clear()
|
||||
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 != gocui.ErrUnknownView {
|
||||
if !gocui.IsUnknownView(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -100,10 +100,10 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
|
||||
v.Clear()
|
||||
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 != gocui.ErrUnknownView {
|
||||
if !gocui.IsUnknownView(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -113,10 +113,10 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
|
||||
v.Clear()
|
||||
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 != gocui.ErrUnknownView {
|
||||
if !gocui.IsUnknownView(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -216,16 +216,16 @@ func (bt *bugTable) keybindings(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
|
||||
}
|
||||
if err := g.DeleteView(bugTableHeaderView); err != nil && err != gocui.ErrUnknownView {
|
||||
if err := g.DeleteView(bugTableHeaderView); err != nil && !gocui.IsUnknownView(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
|
||||
}
|
||||
if err := g.DeleteView(bugTableInstructionView); err != nil && err != gocui.ErrUnknownView {
|
||||
if err := g.DeleteView(bugTableInstructionView); err != nil && !gocui.IsUnknownView(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -3,7 +3,7 @@ package termui
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/MichaelMure/gocui"
|
||||
"github.com/awesome-gocui/gocui"
|
||||
)
|
||||
|
||||
const inputPopupView = "inputPopupView"
|
||||
@ -46,9 +46,9 @@ func (ip *inputPopup) layout(g *gocui.Gui) error {
|
||||
x0 := (maxX - width) / 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 != gocui.ErrUnknownView {
|
||||
if !gocui.IsUnknownView(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/MichaelMure/gocui"
|
||||
"github.com/awesome-gocui/gocui"
|
||||
|
||||
"github.com/MichaelMure/git-bug/bug"
|
||||
"github.com/MichaelMure/git-bug/cache"
|
||||
@ -106,9 +106,9 @@ func (ls *labelSelect) layout(g *gocui.Gui) error {
|
||||
x0 := 1
|
||||
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 != gocui.ErrUnknownView {
|
||||
if !gocui.IsUnknownView(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -117,8 +117,8 @@ func (ls *labelSelect) layout(g *gocui.Gui) error {
|
||||
|
||||
for i, label := range ls.labels {
|
||||
viewname := fmt.Sprintf("view%d", i)
|
||||
v, err := g.SetView(viewname, x0+2, y0, x0+width-2, y0+2)
|
||||
if err != nil && err != gocui.ErrUnknownView {
|
||||
v, err := g.SetView(viewname, x0+2, y0, x0+width-2, y0+2, 0)
|
||||
if err != nil && !gocui.IsUnknownView(err) {
|
||||
return err
|
||||
}
|
||||
ls.childViews = append(ls.childViews, viewname)
|
||||
@ -137,10 +137,10 @@ func (ls *labelSelect) layout(g *gocui.Gui) error {
|
||||
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)
|
||||
if err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
if !gocui.IsUnknownView(err) {
|
||||
return err
|
||||
}
|
||||
v.Frame = false
|
||||
@ -159,7 +159,7 @@ func (ls *labelSelect) layout(g *gocui.Gui) error {
|
||||
|
||||
func (ls *labelSelect) disable(g *gocui.Gui) error {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/MichaelMure/go-term-text"
|
||||
"github.com/MichaelMure/gocui"
|
||||
"github.com/awesome-gocui/gocui"
|
||||
)
|
||||
|
||||
const msgPopupView = "msgPopupView"
|
||||
@ -50,9 +50,9 @@ func (ep *msgPopup) layout(g *gocui.Gui) error {
|
||||
x0 := (maxX - width) / 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 != gocui.ErrUnknownView {
|
||||
if !gocui.IsUnknownView(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"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/cache"
|
||||
@ -49,10 +49,10 @@ func (sb *showBug) layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
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 != gocui.ErrUnknownView {
|
||||
if !gocui.IsUnknownView(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -66,10 +66,10 @@ func (sb *showBug) layout(g *gocui.Gui) error {
|
||||
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 != gocui.ErrUnknownView {
|
||||
if !gocui.IsUnknownView(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -83,10 +83,10 @@ func (sb *showBug) layout(g *gocui.Gui) error {
|
||||
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 != gocui.ErrUnknownView {
|
||||
if !gocui.IsUnknownView(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -190,7 +190,7 @@ func (sb *showBug) keybindings(g *gocui.Gui) error {
|
||||
|
||||
func (sb *showBug) disable(g *gocui.Gui) error {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
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
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,13 @@
|
||||
package termui
|
||||
|
||||
import (
|
||||
"github.com/MichaelMure/gocui"
|
||||
"fmt"
|
||||
|
||||
"github.com/awesome-gocui/gocui"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
errors2 "github.com/go-errors/errors"
|
||||
|
||||
"github.com/MichaelMure/git-bug/cache"
|
||||
"github.com/MichaelMure/git-bug/entity"
|
||||
"github.com/MichaelMure/git-bug/input"
|
||||
@ -63,15 +67,15 @@ func Run(cache *cache.RepoCache) error {
|
||||
err := <-ui.gError
|
||||
|
||||
if err != nil && err != gocui.ErrQuit {
|
||||
fmt.Println(err.(*errors2.Error).ErrorStack())
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initGui(action func(ui *termUI) error) {
|
||||
g, err := gocui.NewGui(gocui.Output256)
|
||||
g, err := gocui.NewGui(gocui.Output256, false)
|
||||
|
||||
if err != nil {
|
||||
ui.gError <- err
|
||||
|
76
vendor/github.com/awesome-gocui/gocui/CODE_OF_CONDUCT.md
generated
vendored
Normal file
76
vendor/github.com/awesome-gocui/gocui/CODE_OF_CONDUCT.md
generated
vendored
Normal 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
33
vendor/github.com/awesome-gocui/gocui/CONTRIBUTING.md
generated
vendored
Normal 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!
|
@ -1,8 +1,13 @@
|
||||
# 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.
|
||||
Minimalist Go package aimed at creating Console User Interfaces.
|
||||
A community fork based on the amazing work of [jroimartin](https://github.com/jroimartin/gocui)
|
||||
|
||||
## Features
|
||||
|
||||
@ -13,15 +18,29 @@ Minimalist Go package aimed at creating Console User Interfaces.
|
||||
* Global and view-level keybindings.
|
||||
* Mouse support.
|
||||
* Colored text.
|
||||
* Customizable edition mode.
|
||||
* Customizable editing mode.
|
||||
* Easy to build reusable widgets, complex layouts...
|
||||
|
||||
## About fork
|
||||
|
||||
This fork has many improvements over the original work from [jroimartin](https://github.com/jroimartin/gocui).
|
||||
|
||||
* Better wide character support
|
||||
* Support for 1 Line height views
|
||||
* Better support for running in docker container
|
||||
* Customize frame colors
|
||||
* Improved code comments and quality
|
||||
* Many small improvements
|
||||
* Change Visibility of views
|
||||
|
||||
For information about this org see: [awesome-gocui/about](https://github.com/awesome-gocui/about).
|
||||
|
||||
## Installation
|
||||
|
||||
Execute:
|
||||
|
||||
```
|
||||
$ go get github.com/jroimartin/gocui
|
||||
$ go get github.com/awesome-gocui/gocui
|
||||
```
|
||||
|
||||
## Documentation
|
||||
@ -29,13 +48,14 @@ $ go get github.com/jroimartin/gocui
|
||||
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.
|
||||
|
||||
## Example
|
||||
See the [_example](./_example/) folder for more examples
|
||||
|
||||
```go
|
||||
package main
|
||||
@ -44,11 +64,11 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
"github.com/awesome-gocui/gocui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
g, err := gocui.NewGui(gocui.OutputNormal, false)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
@ -60,18 +80,21 @@ func main() {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2, 0); err != nil {
|
||||
if !gocui.IsUnknownView(err) {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, "Hello world!")
|
||||
if _, err := g.SetCurrentView("hello"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -106,5 +129,7 @@ func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
* [fac](https://github.com/mkchoi212/fac): git merge conflict resolver
|
||||
* [jsonui](https://github.com/gulyasm/jsonui): Interactive JSON explorer for your terminal.
|
||||
* [cointop](https://github.com/miguelmota/cointop): Interactive terminal based UI application for tracking cryptocurrencies.
|
||||
* [lazygit](https://github.com/jesseduffield/lazygit): simple terminal UI for git commands.
|
||||
* [lazydocker](https://github.com/jesseduffield/lazydocker): The lazier way to manage everything docker.
|
||||
|
||||
Note: if your project is not listed here, let us know! :)
|
@ -4,7 +4,7 @@
|
||||
|
||||
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
|
||||
// can be combined using bitwise OR (|). Note that it is not possible to
|
@ -16,7 +16,7 @@ Create a new GUI:
|
||||
// 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
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ their content. The same is valid for reading.
|
||||
Create and initialize a view with absolute coordinates:
|
||||
|
||||
if v, err := g.SetView("viewname", 2, 2, 22, 7); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
if !gocui.IsUnknownView(err) {
|
||||
// handle error
|
||||
}
|
||||
fmt.Fprintln(v, "This is a new view")
|
||||
@ -84,7 +84,7 @@ use *Gui.Update(). For example:
|
||||
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:
|
||||
|
||||
type Editor interface {
|
158
vendor/github.com/MichaelMure/gocui/edit.go → vendor/github.com/awesome-gocui/gocui/edit.go
generated
vendored
158
vendor/github.com/MichaelMure/gocui/edit.go → vendor/github.com/awesome-gocui/gocui/edit.go
generated
vendored
@ -5,7 +5,7 @@
|
||||
package gocui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
@ -53,13 +53,64 @@ func simpleEditor(v *View, key Key, ch rune, mod Modifier) {
|
||||
v.MoveCursor(-1, 0, false)
|
||||
case key == KeyArrowRight:
|
||||
v.MoveCursor(1, 0, false)
|
||||
case key == KeyTab:
|
||||
v.EditWrite('\t')
|
||||
case key == KeySpace:
|
||||
v.EditWrite(' ')
|
||||
case key == KeyInsert:
|
||||
v.Overwrite = !v.Overwrite
|
||||
default:
|
||||
v.EditWrite(ch)
|
||||
}
|
||||
}
|
||||
|
||||
// EditWrite writes a rune at the cursor position.
|
||||
func (v *View) EditWrite(ch rune) {
|
||||
w := runewidth.RuneWidth(ch)
|
||||
v.writeRune(v.cx, v.cy, ch)
|
||||
v.MoveCursor(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
|
||||
@ -93,12 +144,12 @@ func (v *View) EditDelete(back bool) {
|
||||
v.MoveCursor(-1, 0, true)
|
||||
}
|
||||
} else { // wrapped line
|
||||
ch, _ := v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1)
|
||||
v.MoveCursor(0-runewidth.RuneWidth(ch), 0, true)
|
||||
n, _ := v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1)
|
||||
v.MoveCursor(-n, 0, true)
|
||||
}
|
||||
} else { // middle/end of the line
|
||||
ch, _ := v.deleteRune(v.cx-1, v.cy)
|
||||
v.MoveCursor(0-runewidth.RuneWidth(ch), 0, true)
|
||||
n, _ := v.deleteRune(v.cx-1, v.cy)
|
||||
v.MoveCursor(-n, 0, true)
|
||||
}
|
||||
} else {
|
||||
if x == len(v.viewLines[y].line) { // end of the line
|
||||
@ -113,42 +164,81 @@ func (v *View) EditDelete(back bool) {
|
||||
func (v *View) EditNewLine() {
|
||||
v.breakLine(v.cx, v.cy)
|
||||
v.ox = 0
|
||||
v.cy = v.cy + 1
|
||||
v.cx = 0
|
||||
v.MoveCursor(0, 1, true)
|
||||
}
|
||||
|
||||
// MoveCursor moves the cursor taking into account the width of the line/view,
|
||||
// displacing the origin if necessary.
|
||||
func (v *View) MoveCursor(dx, dy int, writeMode bool) {
|
||||
ox, oy := v.cx+v.ox, v.cy+v.oy
|
||||
x, y := ox+dx, oy+dy
|
||||
|
||||
if y < 0 || y >= len(v.viewLines) {
|
||||
v.moveCursor(dx, dy, writeMode)
|
||||
return
|
||||
}
|
||||
|
||||
// Removing newline.
|
||||
if x < 0 {
|
||||
var prevLen int
|
||||
if y-1 >= 0 && y-1 < len(v.viewLines) {
|
||||
prevLen = lineWidth(v.viewLines[y-1].line)
|
||||
}
|
||||
|
||||
v.MoveCursor(prevLen, -1, writeMode)
|
||||
return
|
||||
}
|
||||
|
||||
line := v.viewLines[y].line
|
||||
var col int
|
||||
var prevCol int
|
||||
for i := range line {
|
||||
prevCol = col
|
||||
col += runewidth.RuneWidth(line[i].chr)
|
||||
if dx > 0 {
|
||||
if x <= col {
|
||||
x = col
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if x < col {
|
||||
x = prevCol
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
v.moveCursor(x-ox, y-oy, writeMode)
|
||||
}
|
||||
|
||||
func (v *View) moveCursor(dx, dy int, writeMode bool) {
|
||||
maxX, maxY := v.Size()
|
||||
cx, cy := v.cx+dx, v.cy+dy
|
||||
x, y := v.ox+cx, v.oy+cy
|
||||
|
||||
var curLineWidth, prevLineWidth int
|
||||
// get the width of the current line
|
||||
if writeMode {
|
||||
if v.Wrap {
|
||||
curLineWidth = maxX - 1
|
||||
} else {
|
||||
curLineWidth = maxInt
|
||||
}
|
||||
} else {
|
||||
curLineWidth = maxInt
|
||||
if v.Wrap {
|
||||
curLineWidth = maxX - 1
|
||||
}
|
||||
|
||||
if !writeMode {
|
||||
curLineWidth = 0
|
||||
if y >= 0 && y < len(v.viewLines) {
|
||||
curLineWidth = len(v.viewLines[y].line)
|
||||
curLineWidth = lineWidth(v.viewLines[y].line)
|
||||
if v.Wrap && curLineWidth >= maxX {
|
||||
curLineWidth = maxX - 1
|
||||
}
|
||||
} else {
|
||||
curLineWidth = 0
|
||||
}
|
||||
}
|
||||
// get the width of the previous line
|
||||
prevLineWidth = 0
|
||||
if y-1 >= 0 && y-1 < len(v.viewLines) {
|
||||
prevLineWidth = len(v.viewLines[y-1].line)
|
||||
} else {
|
||||
prevLineWidth = 0
|
||||
prevLineWidth = lineWidth(v.viewLines[y-1].line)
|
||||
}
|
||||
|
||||
// adjust cursor's x position and view's x origin
|
||||
if x > curLineWidth { // move to next line
|
||||
if dx > 0 { // horizontal movement
|
||||
@ -194,10 +284,9 @@ func (v *View) MoveCursor(dx, dy int, writeMode bool) {
|
||||
if !v.Wrap { // set origin so the EOL is visible
|
||||
nox := prevLineWidth - maxX + 1
|
||||
if nox < 0 {
|
||||
v.ox = 0
|
||||
} else {
|
||||
v.ox = nox
|
||||
nox = 0
|
||||
}
|
||||
v.ox = nox
|
||||
}
|
||||
v.cx = prevLineWidth
|
||||
} 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
|
||||
// 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
|
||||
|
||||
x, y, err = v.realPosition(x, y)
|
||||
x, y, err := v.realPosition(x, y)
|
||||
if err != nil {
|
||||
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]) {
|
||||
return 0, errors.New("invalid point")
|
||||
}
|
||||
chx := v.lines[y][x]
|
||||
v.lines[y] = append(v.lines[y][:x], v.lines[y][x+1:]...)
|
||||
return chx.chr, nil
|
||||
|
||||
var tw int
|
||||
for i := range v.lines[y] {
|
||||
w := runewidth.RuneWidth(v.lines[y][i].chr)
|
||||
tw += w
|
||||
if tw > x {
|
||||
v.lines[y] = append(v.lines[y][:i], v.lines[y][i+1:]...)
|
||||
return w, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// mergeLines merges the lines "y" and "y+1" if possible.
|
@ -5,7 +5,7 @@
|
||||
package gocui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/go-errors/errors"
|
||||
"strconv"
|
||||
)
|
||||
|
9
vendor/github.com/awesome-gocui/gocui/go.mod
generated
vendored
Normal file
9
vendor/github.com/awesome-gocui/gocui/go.mod
generated
vendored
Normal 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
6
vendor/github.com/awesome-gocui/gocui/go.sum
generated
vendored
Normal 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=
|
268
vendor/github.com/MichaelMure/gocui/gui.go → vendor/github.com/awesome-gocui/gocui/gui.go
generated
vendored
268
vendor/github.com/MichaelMure/gocui/gui.go → vendor/github.com/awesome-gocui/gocui/gui.go
generated
vendored
@ -5,28 +5,49 @@
|
||||
package gocui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
standardErrors "errors"
|
||||
"runtime"
|
||||
|
||||
"github.com/nsf/termbox-go"
|
||||
)
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
var (
|
||||
// ErrQuit is used to decide if the MainLoop finished successfully.
|
||||
ErrQuit = errors.New("quit")
|
||||
|
||||
// ErrUnknownView allows to assert if a View must be initialized.
|
||||
ErrUnknownView = errors.New("unknown view")
|
||||
"github.com/awesome-gocui/termbox-go"
|
||||
)
|
||||
|
||||
// OutputMode represents the terminal's output mode (8 or 256 colors).
|
||||
type OutputMode termbox.OutputMode
|
||||
|
||||
var (
|
||||
// ErrAlreadyBlacklisted is returned when the keybinding is already blacklisted.
|
||||
ErrAlreadyBlacklisted = standardErrors.New("keybind already blacklisted")
|
||||
|
||||
// ErrBlacklisted is returned when the keybinding being parsed / used is blacklisted.
|
||||
ErrBlacklisted = standardErrors.New("keybind blacklisted")
|
||||
|
||||
// ErrNotBlacklisted is returned when a keybinding being whitelisted is not blacklisted.
|
||||
ErrNotBlacklisted = standardErrors.New("keybind not blacklisted")
|
||||
|
||||
// ErrNoSuchKeybind is returned when the keybinding being parsed does not exist.
|
||||
ErrNoSuchKeybind = standardErrors.New("no such keybind")
|
||||
|
||||
// ErrUnknownView allows to assert if a View must be initialized.
|
||||
ErrUnknownView = standardErrors.New("unknown view")
|
||||
|
||||
// ErrQuit is used to decide if the MainLoop finished successfully.
|
||||
ErrQuit = standardErrors.New("quit")
|
||||
)
|
||||
|
||||
const (
|
||||
// OutputNormal provides 8-colors terminal mode.
|
||||
OutputNormal = OutputMode(termbox.OutputNormal)
|
||||
|
||||
// Output256 provides 256-colors terminal mode.
|
||||
Output256 = OutputMode(termbox.Output256)
|
||||
|
||||
// OutputGrayScale provides greyscale terminal mode.
|
||||
OutputGrayScale = OutputMode(termbox.OutputGrayscale)
|
||||
|
||||
// Output216 provides greyscale terminal mode.
|
||||
Output216 = OutputMode(termbox.Output216)
|
||||
)
|
||||
|
||||
// Gui represents the whole User Interface, including the views, layouts
|
||||
@ -40,14 +61,16 @@ type Gui struct {
|
||||
keybindings []*keybinding
|
||||
maxX, maxY int
|
||||
outputMode OutputMode
|
||||
stop chan struct{}
|
||||
blacklist []Key
|
||||
|
||||
// BgColor and FgColor allow to configure the background and foreground
|
||||
// colors of the GUI.
|
||||
BgColor, FgColor Attribute
|
||||
BgColor, FgColor, FrameColor Attribute
|
||||
|
||||
// SelBgColor and SelFgColor allow to configure the background and
|
||||
// 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
|
||||
// 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
|
||||
// interface. Using ASCII is more portable.
|
||||
ASCII bool
|
||||
|
||||
// SupportOverlaps is true when we allow for view edges to overlap with other
|
||||
// view edges
|
||||
SupportOverlaps bool
|
||||
}
|
||||
|
||||
// NewGui returns a new Gui object with a given output mode.
|
||||
func NewGui(mode OutputMode) (*Gui, error) {
|
||||
if err := termbox.Init(); err != nil {
|
||||
func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) {
|
||||
err := termbox.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -79,20 +107,36 @@ func NewGui(mode OutputMode) (*Gui, error) {
|
||||
g.outputMode = mode
|
||||
termbox.SetOutputMode(termbox.OutputMode(mode))
|
||||
|
||||
g.stop = make(chan struct{})
|
||||
|
||||
g.tbEvents = make(chan termbox.Event, 20)
|
||||
g.userEvents = make(chan userEvent, 20)
|
||||
|
||||
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.SelBgColor, g.SelFgColor = ColorDefault, ColorDefault
|
||||
|
||||
// SupportOverlaps is true when we allow for view edges to overlap with other
|
||||
// view edges
|
||||
g.SupportOverlaps = supportOverlaps
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// Close finalizes the library. It should be called after a successful
|
||||
// initialization and when gocui is not needed anymore.
|
||||
func (g *Gui) Close() {
|
||||
go func() {
|
||||
g.stop <- struct{}{}
|
||||
}()
|
||||
termbox.Close()
|
||||
}
|
||||
|
||||
@ -127,8 +171,8 @@ func (g *Gui) Rune(x, y int) (rune, error) {
|
||||
// already exists, its dimensions are updated; otherwise, the error
|
||||
// ErrUnknownView is returned, which allows to assert if the View must
|
||||
// be initialized. It checks if the position is valid.
|
||||
func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) {
|
||||
if x0 >= x1 || y0 >= y1 {
|
||||
func (g *Gui) SetView(name string, x0, y0, x1, y1 int, overlaps byte) (*View, error) {
|
||||
if x0 >= x1 {
|
||||
return nil, errors.New("invalid dimensions")
|
||||
}
|
||||
if name == "" {
|
||||
@ -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.BgColor, v.FgColor = g.BgColor, g.FgColor
|
||||
v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor
|
||||
v.Overlaps = overlaps
|
||||
g.views = append(g.views, v)
|
||||
return v, 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.
|
||||
@ -160,7 +216,7 @@ func (g *Gui) SetViewOnTop(name string) (*View, error) {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrUnknownView
|
||||
return nil, errors.Wrap(ErrUnknownView, 0)
|
||||
}
|
||||
|
||||
// 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 nil, ErrUnknownView
|
||||
return nil, errors.Wrap(ErrUnknownView, 0)
|
||||
}
|
||||
|
||||
// Views returns all the views in the GUI.
|
||||
@ -188,7 +244,7 @@ func (g *Gui) View(name string) (*View, error) {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrUnknownView
|
||||
return nil, errors.Wrap(ErrUnknownView, 0)
|
||||
}
|
||||
|
||||
// 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 nil, ErrUnknownView
|
||||
return nil, errors.Wrap(ErrUnknownView, 0)
|
||||
}
|
||||
|
||||
// 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 0, 0, 0, 0, ErrUnknownView
|
||||
return 0, 0, 0, 0, errors.Wrap(ErrUnknownView, 0)
|
||||
}
|
||||
|
||||
// DeleteView deletes a view by name.
|
||||
@ -223,7 +279,7 @@ func (g *Gui) DeleteView(name string) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrUnknownView
|
||||
return errors.Wrap(ErrUnknownView, 0)
|
||||
}
|
||||
|
||||
// SetCurrentView gives the focus to a given view.
|
||||
@ -234,7 +290,7 @@ func (g *Gui) SetCurrentView(name string) (*View, error) {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrUnknownView
|
||||
return nil, errors.Wrap(ErrUnknownView, 0)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
|
||||
if g.isBlacklisted(k) {
|
||||
return ErrBlacklisted
|
||||
}
|
||||
|
||||
kb = newKeybinding(viewname, k, ch, mod, handler)
|
||||
g.keybindings = append(g.keybindings, kb)
|
||||
return nil
|
||||
@ -285,6 +346,28 @@ func (g *Gui) DeleteKeybindings(viewname string) {
|
||||
g.keybindings = s
|
||||
}
|
||||
|
||||
// BlackListKeybinding adds a keybinding to the blacklist
|
||||
func (g *Gui) BlacklistKeybinding(k Key) error {
|
||||
for _, j := range g.blacklist {
|
||||
if j == k {
|
||||
return ErrAlreadyBlacklisted
|
||||
}
|
||||
}
|
||||
g.blacklist = append(g.blacklist, k)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WhiteListKeybinding removes a keybinding from the blacklist
|
||||
func (g *Gui) WhitelistKeybinding(k Key) error {
|
||||
for i, j := range g.blacklist {
|
||||
if j == k {
|
||||
g.blacklist = append(g.blacklist[:i], g.blacklist[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrNotBlacklisted
|
||||
}
|
||||
|
||||
// getKey takes an empty interface with a key and returns the corresponding
|
||||
// typed Key or rune.
|
||||
func getKey(key interface{}) (Key, rune, error) {
|
||||
@ -349,14 +432,24 @@ func (g *Gui) SetManagerFunc(manager func(*Gui) error) {
|
||||
// MainLoop runs the main loop until an error is returned. A successful
|
||||
// finish should return ErrQuit.
|
||||
func (g *Gui) MainLoop() error {
|
||||
g.loaderTick()
|
||||
if err := g.flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
g.tbEvents <- termbox.PollEvent()
|
||||
select {
|
||||
case <-g.stop:
|
||||
return
|
||||
default:
|
||||
g.tbEvents <- termbox.PollEvent()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
inputMode := termbox.InputAlt
|
||||
if g.InputEsc {
|
||||
if true { // previously g.InputEsc, but didn't seem to work
|
||||
inputMode = termbox.InputEsc
|
||||
}
|
||||
if g.Mouse {
|
||||
@ -437,20 +530,25 @@ func (g *Gui) flush() error {
|
||||
}
|
||||
}
|
||||
for _, v := range g.views {
|
||||
if !v.Visible || v.y1 < v.y0 {
|
||||
continue
|
||||
}
|
||||
if v.Frame {
|
||||
var fgColor, bgColor Attribute
|
||||
var fgColor, bgColor, frameColor Attribute
|
||||
if g.Highlight && v == g.currentView {
|
||||
fgColor = g.SelFgColor
|
||||
bgColor = g.SelBgColor
|
||||
frameColor = g.SelFrameColor
|
||||
} else {
|
||||
fgColor = g.FgColor
|
||||
bgColor = g.BgColor
|
||||
frameColor = g.FrameColor
|
||||
}
|
||||
|
||||
if err := g.drawFrameEdges(v, fgColor, bgColor); err != nil {
|
||||
if err := g.drawFrameEdges(v, frameColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.drawFrameCorners(v, fgColor, bgColor); err != nil {
|
||||
if err := g.drawFrameCorners(v, frameColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
if v.Title != "" {
|
||||
@ -458,6 +556,11 @@ func (g *Gui) flush() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if v.Subtitle != "" {
|
||||
if err := g.drawSubtitle(v, fgColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := g.draw(v); err != nil {
|
||||
return err
|
||||
@ -507,9 +610,36 @@ func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func cornerRune(index byte) rune {
|
||||
return []rune{' ', '│', '│', '│', '─', '┘', '┐', '┤', '─', '└', '┌', '├', '├', '┴', '┬', '┼'}[index]
|
||||
}
|
||||
|
||||
func corner(v *View, directions byte) rune {
|
||||
index := v.Overlaps | directions
|
||||
return cornerRune(index)
|
||||
}
|
||||
|
||||
// drawFrameCorners draws the corners of the view.
|
||||
func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error {
|
||||
if v.y0 == v.y1 {
|
||||
if !g.SupportOverlaps && v.x0 >= 0 && v.x1 >= 0 && v.y0 >= 0 && v.x0 < g.maxX && v.x1 < g.maxX && v.y0 < g.maxY {
|
||||
if err := g.SetRune(v.x0, v.y0, '╶', fgColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetRune(v.x1, v.y0, '╴', fgColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
runeTL, runeTR, runeBL, runeBR := '┌', '┐', '└', '┘'
|
||||
if g.SupportOverlaps {
|
||||
runeTL = corner(v, BOTTOM|RIGHT)
|
||||
runeTR = corner(v, BOTTOM|LEFT)
|
||||
runeBL = corner(v, TOP|RIGHT)
|
||||
runeBR = corner(v, TOP|LEFT)
|
||||
}
|
||||
if g.ASCII {
|
||||
runeTL, runeTR, runeBL, runeBR = '+', '+', '+', '+'
|
||||
}
|
||||
@ -549,6 +679,28 @@ func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// drawSubtitle draws the subtitle of the view.
|
||||
func (g *Gui) drawSubtitle(v *View, fgColor, bgColor Attribute) error {
|
||||
if v.y0 < 0 || v.y0 >= g.maxY {
|
||||
return nil
|
||||
}
|
||||
|
||||
start := v.x1 - 5 - len(v.Subtitle)
|
||||
if start < v.x0 {
|
||||
return nil
|
||||
}
|
||||
for i, ch := range v.Subtitle {
|
||||
x := start + i
|
||||
if x >= v.x1 {
|
||||
break
|
||||
}
|
||||
if err := g.SetRune(x, v.y0, ch, fgColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// draw manages the cursor and calls the draw function of a view.
|
||||
func (g *Gui) draw(v *View) error {
|
||||
if g.Cursor {
|
||||
@ -620,17 +772,61 @@ func (g *Gui) onKey(ev *termbox.Event) error {
|
||||
// execKeybindings executes the keybinding handlers that match the passed view
|
||||
// and event. The value of matched is true if there is a match and no errors.
|
||||
func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err error) {
|
||||
matched = false
|
||||
var globalKb *keybinding
|
||||
|
||||
for _, kb := range g.keybindings {
|
||||
if kb.handler == nil {
|
||||
continue
|
||||
}
|
||||
if kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) && kb.matchView(v) {
|
||||
if err := kb.handler(g, v); err != nil {
|
||||
return false, err
|
||||
}
|
||||
matched = true
|
||||
|
||||
if !kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if kb.matchView(v) {
|
||||
return g.execKeybinding(v, kb)
|
||||
}
|
||||
|
||||
if kb.viewName == "" && ((v != nil && !v.Editable) || kb.ch == 0) {
|
||||
globalKb = kb
|
||||
}
|
||||
}
|
||||
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
60
vendor/github.com/awesome-gocui/gocui/gui_others.go
generated
vendored
Normal 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
53
vendor/github.com/awesome-gocui/gocui/gui_windows.go
generated
vendored
Normal 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
|
||||
}
|
@ -4,7 +4,18 @@
|
||||
|
||||
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.
|
||||
type keybinding struct {
|
||||
@ -15,6 +26,71 @@ type keybinding struct {
|
||||
handler func(*Gui, *View) error
|
||||
}
|
||||
|
||||
// Parse takes the input string and extracts the keybinding.
|
||||
// Returns a Key / rune, a Modifier and an error.
|
||||
func Parse(input string) (interface{}, Modifier, error) {
|
||||
if len(input) == 1 {
|
||||
_, r, err := getKey(rune(input[0]))
|
||||
if err != nil {
|
||||
return nil, ModNone, err
|
||||
}
|
||||
return r, ModNone, nil
|
||||
}
|
||||
|
||||
var modifier Modifier
|
||||
cleaned := make([]string, 0)
|
||||
|
||||
tokens := strings.Split(input, "+")
|
||||
for _, t := range tokens {
|
||||
normalized := strings.Title(strings.ToLower(t))
|
||||
if t == "Alt" {
|
||||
modifier = ModAlt
|
||||
continue
|
||||
}
|
||||
cleaned = append(cleaned, normalized)
|
||||
}
|
||||
|
||||
key, exist := translate[strings.Join(cleaned, "")]
|
||||
if !exist {
|
||||
return nil, ModNone, ErrNoSuchKeybind
|
||||
}
|
||||
|
||||
return key, modifier, nil
|
||||
}
|
||||
|
||||
// ParseAll takes an array of strings and returns a map of all keybindings.
|
||||
func ParseAll(input []string) (map[interface{}]Modifier, error) {
|
||||
ret := make(map[interface{}]Modifier)
|
||||
for _, i := range input {
|
||||
k, m, err := Parse(i)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
ret[k] = m
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// MustParse takes the input string and returns a Key / rune and a Modifier.
|
||||
// It will panic if any error occured.
|
||||
func MustParse(input string) (interface{}, Modifier) {
|
||||
k, m, err := Parse(input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return k, m
|
||||
}
|
||||
|
||||
// MustParseAll takes an array of strings and returns a map of all keybindings.
|
||||
// It will panic if any error occured.
|
||||
func MustParseAll(input []string) map[interface{}]Modifier {
|
||||
result, err := ParseAll(input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// newKeybinding returns a new Keybinding object.
|
||||
func newKeybinding(viewname string, key Key, ch rune, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) {
|
||||
kb = &keybinding{
|
||||
@ -34,14 +110,90 @@ func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
|
||||
|
||||
// matchView returns if the keybinding matches the current view.
|
||||
func (kb *keybinding) matchView(v *View) bool {
|
||||
if kb.viewName == "" {
|
||||
return true
|
||||
// if the user is typing in a field, ignore char keys
|
||||
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.
|
||||
type Key termbox.Key
|
||||
// translations for strings to keys
|
||||
var translate = map[string]Key{
|
||||
"F1": KeyF1,
|
||||
"F2": KeyF2,
|
||||
"F3": KeyF3,
|
||||
"F4": KeyF4,
|
||||
"F5": KeyF5,
|
||||
"F6": KeyF6,
|
||||
"F7": KeyF7,
|
||||
"F8": KeyF8,
|
||||
"F9": KeyF9,
|
||||
"F10": KeyF10,
|
||||
"F11": KeyF11,
|
||||
"F12": KeyF12,
|
||||
"Insert": KeyInsert,
|
||||
"Delete": KeyDelete,
|
||||
"Home": KeyHome,
|
||||
"End": KeyEnd,
|
||||
"Pgup": KeyPgup,
|
||||
"Pgdn": KeyPgdn,
|
||||
"ArrowUp": KeyArrowUp,
|
||||
"ArrowDown": KeyArrowDown,
|
||||
"ArrowLeft": KeyArrowLeft,
|
||||
"ArrowRight": KeyArrowRight,
|
||||
"CtrlTilde": KeyCtrlTilde,
|
||||
"Ctrl2": KeyCtrl2,
|
||||
"CtrlSpace": KeyCtrlSpace,
|
||||
"CtrlA": KeyCtrlA,
|
||||
"CtrlB": KeyCtrlB,
|
||||
"CtrlC": KeyCtrlC,
|
||||
"CtrlD": KeyCtrlD,
|
||||
"CtrlE": KeyCtrlE,
|
||||
"CtrlF": KeyCtrlF,
|
||||
"CtrlG": KeyCtrlG,
|
||||
"Backspace": KeyBackspace,
|
||||
"CtrlH": KeyCtrlH,
|
||||
"Tab": KeyTab,
|
||||
"CtrlI": KeyCtrlI,
|
||||
"CtrlJ": KeyCtrlJ,
|
||||
"CtrlK": KeyCtrlK,
|
||||
"CtrlL": KeyCtrlL,
|
||||
"Enter": KeyEnter,
|
||||
"CtrlM": KeyCtrlM,
|
||||
"CtrlN": KeyCtrlN,
|
||||
"CtrlO": KeyCtrlO,
|
||||
"CtrlP": KeyCtrlP,
|
||||
"CtrlQ": KeyCtrlQ,
|
||||
"CtrlR": KeyCtrlR,
|
||||
"CtrlS": KeyCtrlS,
|
||||
"CtrlT": KeyCtrlT,
|
||||
"CtrlU": KeyCtrlU,
|
||||
"CtrlV": KeyCtrlV,
|
||||
"CtrlW": KeyCtrlW,
|
||||
"CtrlX": KeyCtrlX,
|
||||
"CtrlY": KeyCtrlY,
|
||||
"CtrlZ": KeyCtrlZ,
|
||||
"Esc": KeyEsc,
|
||||
"CtrlLsqBracket": KeyCtrlLsqBracket,
|
||||
"Ctrl3": KeyCtrl3,
|
||||
"Ctrl4": KeyCtrl4,
|
||||
"CtrlBackslash": KeyCtrlBackslash,
|
||||
"Ctrl5": KeyCtrl5,
|
||||
"CtrlRsqBracket": KeyCtrlRsqBracket,
|
||||
"Ctrl6": KeyCtrl6,
|
||||
"Ctrl7": KeyCtrl7,
|
||||
"CtrlSlash": KeyCtrlSlash,
|
||||
"CtrlUnderscore": KeyCtrlUnderscore,
|
||||
"Space": KeySpace,
|
||||
"Backspace2": KeyBackspace2,
|
||||
"Ctrl8": KeyCtrl8,
|
||||
"Mouseleft": MouseLeft,
|
||||
"Mousemiddle": MouseMiddle,
|
||||
"Mouseright": MouseRight,
|
||||
"Mouserelease": MouseRelease,
|
||||
"MousewheelUp": MouseWheelUp,
|
||||
"MousewheelDown": MouseWheelDown,
|
||||
}
|
||||
|
||||
// Special keys.
|
||||
const (
|
||||
@ -126,10 +278,6 @@ const (
|
||||
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.
|
||||
const (
|
||||
ModNone Modifier = Modifier(0)
|
46
vendor/github.com/awesome-gocui/gocui/loader.go
generated
vendored
Normal file
46
vendor/github.com/awesome-gocui/gocui/loader.go
generated
vendored
Normal 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,
|
||||
}
|
||||
}
|
474
vendor/github.com/MichaelMure/gocui/view.go → vendor/github.com/awesome-gocui/gocui/view.go
generated
vendored
474
vendor/github.com/MichaelMure/gocui/view.go → vendor/github.com/awesome-gocui/gocui/view.go
generated
vendored
@ -6,29 +6,59 @@ package gocui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/awesome-gocui/termbox-go"
|
||||
"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
|
||||
// position.
|
||||
type View struct {
|
||||
name string
|
||||
x0, y0, x1, y1 int
|
||||
ox, oy int
|
||||
cx, cy int
|
||||
lines [][]cell
|
||||
readOffset int
|
||||
readCache string
|
||||
x0, y0, x1, y1 int // left top right bottom
|
||||
ox, oy int // view offsets
|
||||
cx, cy int // cursor position
|
||||
rx, ry int // Read() offsets
|
||||
wx, wy int // Write() offsets
|
||||
lines [][]cell // All the data
|
||||
|
||||
tainted bool // marks if the viewBuffer must be updated
|
||||
viewLines []viewLine // internal representation of the view's buffer
|
||||
// readBuffer is used for storing unread bytes
|
||||
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
|
||||
// colors of the View.
|
||||
@ -42,7 +72,7 @@ type View struct {
|
||||
// buffer at the cursor position.
|
||||
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
|
||||
// default.
|
||||
Editor Editor
|
||||
@ -69,9 +99,18 @@ type View struct {
|
||||
// If Frame is true, Title allows to configure a title for the view.
|
||||
Title string
|
||||
|
||||
// If Frame is true, Subtitle allows to configure a subtitle for the view.
|
||||
Subtitle string
|
||||
|
||||
// If Mask is true, the View will display the mask instead of the real
|
||||
// content
|
||||
Mask rune
|
||||
|
||||
// Overlaps describes which edges are overlapping with another view's edges
|
||||
Overlaps byte
|
||||
|
||||
// If HasLoader is true, the message will be appended with a spinning loader animation
|
||||
HasLoader bool
|
||||
}
|
||||
|
||||
type viewLine struct {
|
||||
@ -103,6 +142,7 @@ func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
|
||||
y0: y0,
|
||||
x1: x1,
|
||||
y1: y1,
|
||||
Visible: true,
|
||||
Frame: true,
|
||||
Editor: DefaultEditor,
|
||||
tainted: true,
|
||||
@ -111,6 +151,11 @@ func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
|
||||
return v
|
||||
}
|
||||
|
||||
// Dimensions returns the dimensions of the View
|
||||
func (v *View) Dimensions() (int, int, int, int) {
|
||||
return v.x0, v.y0, v.x1, v.y1
|
||||
}
|
||||
|
||||
// Size returns the number of visible columns and rows in the View.
|
||||
func (v *View) Size() (x, y int) {
|
||||
return v.x1 - v.x0 - 1, v.y1 - v.y0 - 1
|
||||
@ -127,9 +172,8 @@ func (v *View) Name() string {
|
||||
func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
|
||||
maxX, maxY := v.Size()
|
||||
if x < 0 || x >= maxX || y < 0 || y >= maxY {
|
||||
return errors.New("invalid point")
|
||||
return ErrInvalidPoint
|
||||
}
|
||||
|
||||
var (
|
||||
ry, rcy int
|
||||
err error
|
||||
@ -150,8 +194,12 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
|
||||
bgColor = v.BgColor
|
||||
ch = v.Mask
|
||||
} else if v.Highlight && ry == rcy {
|
||||
fgColor = v.SelFgColor
|
||||
bgColor = v.SelBgColor
|
||||
fgColor = fgColor | AttrBold
|
||||
}
|
||||
|
||||
// Don't display NUL characters
|
||||
if ch == 0 {
|
||||
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 {
|
||||
maxX, maxY := v.Size()
|
||||
if x < 0 || x >= maxX || y < 0 || y >= maxY {
|
||||
return errors.New("invalid point")
|
||||
return ErrInvalidPoint
|
||||
}
|
||||
v.cx = x
|
||||
v.cy = y
|
||||
@ -184,7 +232,7 @@ func (v *View) Cursor() (x, y int) {
|
||||
// or decrementing ox and oy.
|
||||
func (v *View) SetOrigin(x, y int) error {
|
||||
if x < 0 || y < 0 {
|
||||
return errors.New("invalid point")
|
||||
return ErrInvalidPoint
|
||||
}
|
||||
v.ox = x
|
||||
v.oy = y
|
||||
@ -196,39 +244,141 @@ func (v *View) Origin() (x, y int) {
|
||||
return v.ox, v.oy
|
||||
}
|
||||
|
||||
// SetWritePos sets the write position of the view's internal buffer.
|
||||
// So the next Write call would write directly to the specified position.
|
||||
func (v *View) SetWritePos(x, y int) error {
|
||||
if x < 0 || y < 0 {
|
||||
return ErrInvalidPoint
|
||||
}
|
||||
v.wx = x
|
||||
v.wy = y
|
||||
return nil
|
||||
}
|
||||
|
||||
// WritePos returns the current write position of the view's internal buffer.
|
||||
func (v *View) WritePos() (x, y int) {
|
||||
return v.wx, v.wy
|
||||
}
|
||||
|
||||
// SetReadPos sets the read position of the view's internal buffer.
|
||||
// So the next Read call would read from the specified position.
|
||||
func (v *View) SetReadPos(x, y int) error {
|
||||
if x < 0 || y < 0 {
|
||||
return ErrInvalidPoint
|
||||
}
|
||||
v.readBuffer = nil
|
||||
v.rx = x
|
||||
v.ry = y
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadPos returns the current read position of the view's internal buffer.
|
||||
func (v *View) ReadPos() (x, y int) {
|
||||
return v.rx, v.ry
|
||||
}
|
||||
|
||||
// makeWriteable creates empty cells if required to make position (x, y) writeable.
|
||||
func (v *View) makeWriteable(x, y int) {
|
||||
// TODO: make this more efficient
|
||||
|
||||
// line `y` must be index-able (that's why `<=`)
|
||||
for len(v.lines) <= y {
|
||||
if cap(v.lines) > len(v.lines) {
|
||||
newLen := cap(v.lines)
|
||||
if newLen > y {
|
||||
newLen = y + 1
|
||||
}
|
||||
v.lines = v.lines[:newLen]
|
||||
} else {
|
||||
v.lines = append(v.lines, nil)
|
||||
}
|
||||
}
|
||||
// cell `x` must not be index-able (that's why `<`)
|
||||
// append should be used by `lines[y]` user if he wants to write beyond `x`
|
||||
for len(v.lines[y]) < x {
|
||||
if cap(v.lines[y]) > len(v.lines[y]) {
|
||||
newLen := cap(v.lines[y])
|
||||
if newLen > x {
|
||||
newLen = x
|
||||
}
|
||||
v.lines[y] = v.lines[y][:newLen]
|
||||
} else {
|
||||
v.lines[y] = append(v.lines[y], cell{})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// writeCells copies []cell to specified location (x, y)
|
||||
// !!! caller MUST ensure that specified location (x, y) is writeable by calling makeWriteable
|
||||
func (v *View) writeCells(x, y int, cells []cell) {
|
||||
var newLen int
|
||||
// use maximum len available
|
||||
line := v.lines[y][:cap(v.lines[y])]
|
||||
maxCopy := len(line) - x
|
||||
if maxCopy < len(cells) {
|
||||
copy(line[x:], cells[:maxCopy])
|
||||
line = append(line, cells[maxCopy:]...)
|
||||
newLen = len(line)
|
||||
} else { // maxCopy >= len(cells)
|
||||
copy(line[x:], cells)
|
||||
newLen = x + len(cells)
|
||||
if newLen < len(v.lines[y]) {
|
||||
newLen = len(v.lines[y])
|
||||
}
|
||||
}
|
||||
v.lines[y] = line[:newLen]
|
||||
}
|
||||
|
||||
// Write appends a byte slice into the view's internal buffer. Because
|
||||
// View implements the io.Writer interface, it can be passed as parameter
|
||||
// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must
|
||||
// be called to clear the view's buffer.
|
||||
func (v *View) Write(p []byte) (n int, err error) {
|
||||
v.tainted = true
|
||||
v.writeMutex.Lock()
|
||||
v.makeWriteable(v.wx, v.wy)
|
||||
v.writeRunes(bytes.Runes(p))
|
||||
v.writeMutex.Unlock()
|
||||
|
||||
for _, ch := range bytes.Runes(p) {
|
||||
switch ch {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (v *View) WriteRunes(p []rune) {
|
||||
v.tainted = true
|
||||
|
||||
// Fill with empty cells, if writing outside current view buffer
|
||||
v.makeWriteable(v.wx, v.wy)
|
||||
v.writeRunes(p)
|
||||
}
|
||||
|
||||
func (v *View) WriteString(s string) {
|
||||
v.WriteRunes([]rune(s))
|
||||
}
|
||||
|
||||
// writeRunes copies slice of runes into internal lines buffer.
|
||||
// caller must make sure that writing position is accessable.
|
||||
func (v *View) writeRunes(p []rune) {
|
||||
for _, r := range p {
|
||||
switch r {
|
||||
case '\n':
|
||||
v.lines = append(v.lines, nil)
|
||||
case '\r':
|
||||
nl := len(v.lines)
|
||||
if nl > 0 {
|
||||
v.lines[nl-1] = nil
|
||||
} else {
|
||||
v.lines = make([][]cell, 1)
|
||||
v.wy++
|
||||
if v.wy >= len(v.lines) {
|
||||
v.lines = append(v.lines, nil)
|
||||
}
|
||||
|
||||
fallthrough
|
||||
// not valid in every OS, but making runtime OS checks in cycle is bad.
|
||||
case '\r':
|
||||
v.wx = 0
|
||||
default:
|
||||
cells := v.parseInput(ch)
|
||||
cells := v.parseInput(r)
|
||||
if cells == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
nl := len(v.lines)
|
||||
if nl > 0 {
|
||||
v.lines[nl-1] = append(v.lines[nl-1], cells...)
|
||||
} else {
|
||||
v.lines = append(v.lines, cells)
|
||||
}
|
||||
v.writeCells(v.wx, v.wy, cells)
|
||||
v.wx += len(cells)
|
||||
}
|
||||
}
|
||||
return len(p), 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 {
|
||||
return nil
|
||||
}
|
||||
c := cell{
|
||||
fgColor: v.ei.curFgColor,
|
||||
bgColor: v.ei.curBgColor,
|
||||
chr: ch,
|
||||
repeatCount := 1
|
||||
if ch == '\t' {
|
||||
ch = ' '
|
||||
repeatCount = 4
|
||||
}
|
||||
for i := 0; i < repeatCount; i++ {
|
||||
c := cell{
|
||||
fgColor: v.ei.curFgColor,
|
||||
bgColor: v.ei.curBgColor,
|
||||
chr: ch,
|
||||
}
|
||||
cells = append(cells, c)
|
||||
}
|
||||
cells = append(cells, c)
|
||||
}
|
||||
|
||||
return cells
|
||||
}
|
||||
|
||||
// Read reads data into p. It returns the number of bytes read into p.
|
||||
// At EOF, err will be io.EOF. Calling Read() after Rewind() makes the
|
||||
// cache to be refreshed with the contents of the view.
|
||||
// Read reads data into p from the current reading position set by SetReadPos.
|
||||
// It returns the number of bytes read into p.
|
||||
// At EOF, err will be io.EOF.
|
||||
func (v *View) Read(p []byte) (n int, err error) {
|
||||
if v.readOffset == 0 {
|
||||
v.readCache = v.Buffer()
|
||||
buffer := make([]byte, utf8.UTFMax)
|
||||
offset := 0
|
||||
if v.readBuffer != nil {
|
||||
copy(p, v.readBuffer)
|
||||
if len(v.readBuffer) >= len(p) {
|
||||
if len(v.readBuffer) > len(p) {
|
||||
v.readBuffer = v.readBuffer[len(p):]
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
v.readBuffer = nil
|
||||
}
|
||||
if v.readOffset < len(v.readCache) {
|
||||
n = copy(p, v.readCache[v.readOffset:])
|
||||
v.readOffset += n
|
||||
} else {
|
||||
err = io.EOF
|
||||
for v.ry < len(v.lines) {
|
||||
for v.rx < len(v.lines[v.ry]) {
|
||||
count := utf8.EncodeRune(buffer, v.lines[v.ry][v.rx].chr)
|
||||
copy(p[offset:], buffer[:count])
|
||||
v.rx++
|
||||
newOffset := offset + count
|
||||
if newOffset >= len(p) {
|
||||
if newOffset > len(p) {
|
||||
v.readBuffer = buffer[newOffset-len(p):]
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
offset += count
|
||||
}
|
||||
v.rx = 0
|
||||
v.ry++
|
||||
}
|
||||
return
|
||||
return offset, io.EOF
|
||||
}
|
||||
|
||||
// Rewind sets the offset for the next Read to 0, which also refresh the
|
||||
// read cache.
|
||||
// Rewind sets read and write pos to (0, 0).
|
||||
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.
|
||||
func (v *View) draw() error {
|
||||
if !v.Visible {
|
||||
return nil
|
||||
}
|
||||
|
||||
maxX, maxY := v.Size()
|
||||
|
||||
if v.Wrap {
|
||||
@ -297,29 +491,25 @@ func (v *View) draw() error {
|
||||
}
|
||||
if v.tainted {
|
||||
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 len(line) < maxX {
|
||||
vline := viewLine{linesX: 0, linesY: i, line: line}
|
||||
v.viewLines = append(v.viewLines, vline)
|
||||
continue
|
||||
} else {
|
||||
for n := 0; n <= len(line); n += maxX {
|
||||
if len(line[n:]) <= maxX {
|
||||
vline := viewLine{linesX: n, linesY: i, line: line[n:]}
|
||||
v.viewLines = append(v.viewLines, vline)
|
||||
} else {
|
||||
vline := viewLine{linesX: n, linesY: i, line: line[n : n+maxX]}
|
||||
v.viewLines = append(v.viewLines, vline)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
vline := viewLine{linesX: 0, linesY: i, line: line}
|
||||
wrap = maxX
|
||||
}
|
||||
|
||||
ls := lineWrap(line, wrap)
|
||||
for j := range ls {
|
||||
vline := viewLine{linesX: j, linesY: i, line: ls[j]}
|
||||
v.viewLines = append(v.viewLines, vline)
|
||||
}
|
||||
}
|
||||
v.tainted = false
|
||||
if !v.HasLoader {
|
||||
v.tainted = false
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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++
|
||||
}
|
||||
@ -368,7 +566,7 @@ func (v *View) realPosition(vx, vy int) (x, y int, err error) {
|
||||
vy = v.oy + vy
|
||||
|
||||
if vx < 0 || vy < 0 {
|
||||
return 0, 0, errors.New("invalid point")
|
||||
return 0, 0, ErrInvalidPoint
|
||||
}
|
||||
|
||||
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.
|
||||
// And resets reading and writing offsets.
|
||||
func (v *View) Clear() {
|
||||
v.writeMutex.Lock()
|
||||
v.Rewind()
|
||||
v.tainted = true
|
||||
|
||||
v.ei.reset()
|
||||
v.lines = nil
|
||||
v.viewLines = nil
|
||||
v.readOffset = 0
|
||||
v.clearRunes()
|
||||
v.writeMutex.Unlock()
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (v *View) Buffer() string {
|
||||
str := ""
|
||||
for _, l := range v.lines {
|
||||
str += lineType(l).String() + "\n"
|
||||
}
|
||||
return strings.Replace(str, "\x00", " ", -1)
|
||||
return linesToString(v.lines)
|
||||
}
|
||||
|
||||
// ViewBufferLines returns the lines in the view's internal
|
||||
@ -443,14 +640,25 @@ func (v *View) ViewBufferLines() []string {
|
||||
return lines
|
||||
}
|
||||
|
||||
// LinesHeight is the count of view lines (i.e. lines excluding wrapping)
|
||||
func (v *View) LinesHeight() int {
|
||||
return len(v.lines)
|
||||
}
|
||||
|
||||
// ViewLinesHeight is the count of view lines (i.e. lines including wrapping)
|
||||
func (v *View) ViewLinesHeight() int {
|
||||
return len(v.viewLines)
|
||||
}
|
||||
|
||||
// ViewBuffer returns a string with the contents of the view's buffer that is
|
||||
// shown to the user.
|
||||
func (v *View) ViewBuffer() string {
|
||||
str := ""
|
||||
for _, l := range v.viewLines {
|
||||
str += lineType(l.line).String() + "\n"
|
||||
lines := make([][]cell, len(v.viewLines))
|
||||
for i := range v.viewLines {
|
||||
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
|
||||
@ -462,7 +670,7 @@ func (v *View) Line(y int) (string, error) {
|
||||
}
|
||||
|
||||
if y < 0 || y >= len(v.lines) {
|
||||
return "", errors.New("invalid point")
|
||||
return "", ErrInvalidPoint
|
||||
}
|
||||
|
||||
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]) {
|
||||
return "", errors.New("invalid point")
|
||||
return "", ErrInvalidPoint
|
||||
}
|
||||
|
||||
str := lineType(v.lines[y]).String()
|
||||
@ -502,3 +710,91 @@ func (v *View) Word(x, y int) (string, error) {
|
||||
func indexFunc(r rune) bool {
|
||||
return r == ' ' || r == 0
|
||||
}
|
||||
|
||||
// SetLine changes the contents of an existing line.
|
||||
func (v *View) SetLine(y int, text string) error {
|
||||
if y < 0 || y >= len(v.lines) {
|
||||
err := ErrInvalidPoint
|
||||
return err
|
||||
}
|
||||
|
||||
v.tainted = true
|
||||
line := make([]cell, 0)
|
||||
for _, r := range text {
|
||||
c := v.parseInput(r)
|
||||
line = append(line, c...)
|
||||
}
|
||||
v.lines[y] = line
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetHighlight toggles highlighting of separate lines, for custom lists
|
||||
// or multiple selection in views.
|
||||
func (v *View) SetHighlight(y int, on bool) error {
|
||||
if y < 0 || y >= len(v.lines) {
|
||||
err := ErrInvalidPoint
|
||||
return err
|
||||
}
|
||||
|
||||
line := v.lines[y]
|
||||
cells := make([]cell, 0)
|
||||
for _, c := range line {
|
||||
if on {
|
||||
c.bgColor = v.SelBgColor
|
||||
c.fgColor = v.SelFgColor
|
||||
} else {
|
||||
c.bgColor = v.BgColor
|
||||
c.fgColor = v.FgColor
|
||||
}
|
||||
cells = append(cells, c)
|
||||
}
|
||||
v.tainted = true
|
||||
v.lines[y] = cells
|
||||
return nil
|
||||
}
|
||||
|
||||
func lineWidth(line []cell) (n int) {
|
||||
for i := range line {
|
||||
n += runewidth.RuneWidth(line[i].chr)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func lineWrap(line []cell, columns int) [][]cell {
|
||||
if columns == 0 {
|
||||
return [][]cell{line}
|
||||
}
|
||||
|
||||
var n int
|
||||
var offset int
|
||||
lines := make([][]cell, 0, 1)
|
||||
for i := range line {
|
||||
rw := runewidth.RuneWidth(line[i].chr)
|
||||
n += rw
|
||||
if n > columns {
|
||||
n = rw
|
||||
lines = append(lines, line[offset:i])
|
||||
offset = i
|
||||
}
|
||||
}
|
||||
|
||||
lines = append(lines, line[offset:])
|
||||
return lines
|
||||
}
|
||||
|
||||
func linesToString(lines [][]cell) string {
|
||||
str := make([]string, len(lines))
|
||||
for i := range lines {
|
||||
rns := make([]rune, 0, len(lines[i]))
|
||||
line := lineType(lines[i]).String()
|
||||
for _, c := range line {
|
||||
if c != '\x00' {
|
||||
rns = append(rns, c)
|
||||
}
|
||||
}
|
||||
str[i] = string(rns)
|
||||
}
|
||||
|
||||
return strings.Join(str, "\n")
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
[![GoDoc](https://godoc.org/github.com/nsf/termbox-go?status.svg)](http://godoc.org/github.com/nsf/termbox-go)
|
||||
|
||||
## IMPORTANT
|
||||
|
||||
This library is somewhat not maintained anymore. But I'm glad that it did what I wanted the most. It moved people away from "ncurses" mindset and these days we see both re-implementations of termbox API in various languages and even possibly better libs with similar API design. If you're looking for a Go lib that provides terminal-based user interface facilities, I've heard that https://github.com/gdamore/tcell is good (never used it myself). Also for more complicated interfaces and/or computer games I recommend you to consider using HTML-based UI. Having said that, termbox still somewhat works. In fact I'm writing this line of text right now in godit (which is a text editor written using termbox-go). So, be aware. Good luck and have a nice day.
|
||||
|
||||
## Termbox
|
||||
Termbox is a library that provides a minimalistic API which allows the programmer to write text-based user interfaces. The library is crossplatform and has both terminal-based implementations on *nix operating systems and a winapi console based implementation for windows operating systems. The basic idea is an abstraction of the greatest common subset of features available on all major terminals and other terminal-like APIs in a minimalistic fashion. Small API means it is easy to implement, test, maintain and learn it, that's what makes the termbox a distinct library in its area.
|
||||
|
||||
@ -17,6 +21,7 @@ There are also some interesting projects using termbox-go:
|
||||
- [httopd](https://github.com/verdverm/httopd) is top for httpd logs.
|
||||
- [mop](https://github.com/mop-tracker/mop) is stock market tracker for hackers.
|
||||
- [termui](https://github.com/gizak/termui) is a terminal dashboard.
|
||||
- [termdash](https://github.com/mum4k/termdash) is a terminal dashboard.
|
||||
- [termloop](https://github.com/JoelOtter/termloop) is a terminal game engine.
|
||||
- [xterm-color-chart](https://github.com/kutuluk/xterm-color-chart) is a XTerm 256 color chart.
|
||||
- [gocui](https://github.com/jroimartin/gocui) is a minimalist Go library aimed at creating console user interfaces.
|
||||
@ -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.
|
||||
- [cointop](https://github.com/miguelmota/cointop) is an interactive terminal based UI application for tracking cryptocurrencies.
|
||||
- [pexpo](https://github.com/nnao45/pexpo) is a terminal sending ping tool written in Go.
|
||||
- [jid](https://github.com/simeji/jid) is an interactive JSON drill down tool using filtering queries like jq.
|
||||
|
||||
### API reference
|
||||
[godoc.org/github.com/nsf/termbox-go](http://godoc.org/github.com/nsf/termbox-go)
|
@ -24,13 +24,21 @@ import "time"
|
||||
func Init() error {
|
||||
var err error
|
||||
|
||||
out, err = os.OpenFile("/dev/tty", syscall.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
in, err = syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
if runtime.GOOS == "openbsd" {
|
||||
out, err = os.OpenFile("/dev/tty", os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
in = int(out.Fd())
|
||||
} else {
|
||||
out, err = os.OpenFile("/dev/tty", os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
in, err = syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = setup_term()
|
||||
@ -317,6 +325,9 @@ func PollEvent() Event {
|
||||
event.Type = EventKey
|
||||
status := extract_event(inbuf, &event, true)
|
||||
if event.N != 0 {
|
||||
if event.N > len(inbuf) {
|
||||
event.N = len(inbuf)
|
||||
}
|
||||
copy(inbuf, inbuf[event.N:])
|
||||
inbuf = inbuf[:len(inbuf)-event.N]
|
||||
}
|
||||
@ -345,6 +356,9 @@ func PollEvent() Event {
|
||||
input_comm <- ev
|
||||
status := extract_event(inbuf, &event, true)
|
||||
if event.N != 0 {
|
||||
if event.N > len(inbuf) {
|
||||
event.N = len(inbuf)
|
||||
}
|
||||
copy(inbuf, inbuf[event.N:])
|
||||
inbuf = inbuf[:len(inbuf)-event.N]
|
||||
}
|
||||
@ -359,6 +373,9 @@ func PollEvent() Event {
|
||||
|
||||
status := extract_event(inbuf, &event, false)
|
||||
if event.N != 0 {
|
||||
if event.N > len(inbuf) {
|
||||
event.N = len(inbuf)
|
||||
}
|
||||
copy(inbuf, inbuf[event.N:])
|
||||
inbuf = inbuf[:len(inbuf)-event.N]
|
||||
}
|
@ -42,7 +42,7 @@ func Init() error {
|
||||
return err
|
||||
}
|
||||
|
||||
orig_size = get_term_size(out)
|
||||
orig_size, orig_window = get_term_size(out)
|
||||
win_size := get_win_size(out)
|
||||
|
||||
err = set_console_screen_buffer_size(out, win_size)
|
||||
@ -50,13 +50,18 @@ func Init() error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = fix_win_size(out, win_size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = get_console_cursor_info(out, &orig_cursor_info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
show_cursor(false)
|
||||
term_size = get_term_size(out)
|
||||
term_size, _ = get_term_size(out)
|
||||
back_buffer.init(int(term_size.x), int(term_size.y))
|
||||
front_buffer.init(int(term_size.x), int(term_size.y))
|
||||
back_buffer.clear()
|
||||
@ -86,9 +91,10 @@ func Close() {
|
||||
}
|
||||
<-cancel_done_comm
|
||||
|
||||
set_console_screen_buffer_size(out, orig_size)
|
||||
set_console_window_info(out, &orig_window)
|
||||
set_console_cursor_info(out, &orig_cursor_info)
|
||||
set_console_cursor_position(out, coord{})
|
||||
set_console_screen_buffer_size(out, orig_size)
|
||||
set_console_mode(in, orig_mode)
|
||||
syscall.Close(in)
|
||||
syscall.Close(out)
|
@ -76,6 +76,10 @@ func (this coord) uintptr() uintptr {
|
||||
return uintptr(*(*int32)(unsafe.Pointer(&this)))
|
||||
}
|
||||
|
||||
func (this *small_rect) uintptr() uintptr {
|
||||
return uintptr(unsafe.Pointer(this))
|
||||
}
|
||||
|
||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
var moduser32 = syscall.NewLazyDLL("user32.dll")
|
||||
var is_cjk = runewidth.IsEastAsian()
|
||||
@ -83,6 +87,7 @@ var is_cjk = runewidth.IsEastAsian()
|
||||
var (
|
||||
proc_set_console_active_screen_buffer = kernel32.NewProc("SetConsoleActiveScreenBuffer")
|
||||
proc_set_console_screen_buffer_size = kernel32.NewProc("SetConsoleScreenBufferSize")
|
||||
proc_set_console_window_info = kernel32.NewProc("SetConsoleWindowInfo")
|
||||
proc_create_console_screen_buffer = kernel32.NewProc("CreateConsoleScreenBuffer")
|
||||
proc_get_console_screen_buffer_info = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||
proc_write_console_output = kernel32.NewProc("WriteConsoleOutputW")
|
||||
@ -129,6 +134,21 @@ func set_console_screen_buffer_size(h syscall.Handle, size coord) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func set_console_window_info(h syscall.Handle, window *small_rect) (err error) {
|
||||
var absolute uint32
|
||||
absolute = 1
|
||||
r0, _, e1 := syscall.Syscall(proc_set_console_window_info.Addr(),
|
||||
3, uintptr(h), uintptr(absolute), window.uintptr())
|
||||
if int(r0) == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func create_console_screen_buffer() (h syscall.Handle, err error) {
|
||||
r0, _, e1 := syscall.Syscall6(proc_create_console_screen_buffer.Addr(),
|
||||
5, uintptr(generic_read|generic_write), 0, 0, console_textmode_buffer, 0, 0)
|
||||
@ -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) {
|
||||
tmp_coord = coord{0, 0}
|
||||
r0, _, e1 := syscall.Syscall6(proc_fill_console_output_character.Addr(),
|
||||
5, uintptr(h), uintptr(char), uintptr(n), tmp_coord.uintptr(),
|
||||
uintptr(unsafe.Pointer(&tmp_arg)), 0)
|
||||
@ -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) {
|
||||
tmp_coord = coord{0, 0}
|
||||
r0, _, e1 := syscall.Syscall6(proc_fill_console_output_attribute.Addr(),
|
||||
5, uintptr(h), uintptr(attr), uintptr(n), tmp_coord.uintptr(),
|
||||
uintptr(unsafe.Pointer(&tmp_arg)), 0)
|
||||
@ -372,6 +394,7 @@ type input_event struct {
|
||||
var (
|
||||
orig_cursor_info console_cursor_info
|
||||
orig_size coord
|
||||
orig_window small_rect
|
||||
orig_mode dword
|
||||
orig_screen syscall.Handle
|
||||
back_buffer cellbuf
|
||||
@ -413,12 +436,12 @@ func get_cursor_position(out syscall.Handle) coord {
|
||||
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)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return tmp_info.size
|
||||
return tmp_info.size, tmp_info.window
|
||||
}
|
||||
|
||||
func get_win_min_size(out syscall.Handle) coord {
|
||||
@ -466,10 +489,20 @@ func get_win_size(out syscall.Handle) coord {
|
||||
return size
|
||||
}
|
||||
|
||||
func fix_win_size(out syscall.Handle, size coord) (err error) {
|
||||
window := small_rect{}
|
||||
window.top = 0
|
||||
window.bottom = size.y - 1
|
||||
window.left = 0
|
||||
window.right = size.x - 1
|
||||
return set_console_window_info(out, &window)
|
||||
}
|
||||
|
||||
func update_size_maybe() {
|
||||
size := get_win_size(out)
|
||||
if size.x != term_size.x || size.y != term_size.y {
|
||||
set_console_screen_buffer_size(out, size)
|
||||
fix_win_size(out, size)
|
||||
term_size = size
|
||||
back_buffer.resize(int(size.x), int(size.y))
|
||||
front_buffer.resize(int(size.x), int(size.y))
|
||||
@ -490,8 +523,8 @@ var color_table_bg = []word{
|
||||
background_green,
|
||||
background_red | background_green, // yellow
|
||||
background_blue,
|
||||
background_red | background_blue, // magenta
|
||||
background_green | background_blue, // cyan
|
||||
background_red | background_blue, // magenta
|
||||
background_green | background_blue, // cyan
|
||||
background_red | background_blue | background_green, // white
|
||||
}
|
||||
|
||||
@ -502,8 +535,8 @@ var color_table_fg = []word{
|
||||
foreground_green,
|
||||
foreground_red | foreground_green, // yellow
|
||||
foreground_blue,
|
||||
foreground_red | foreground_blue, // magenta
|
||||
foreground_green | foreground_blue, // cyan
|
||||
foreground_red | foreground_blue, // magenta
|
||||
foreground_green | foreground_blue, // cyan
|
||||
foreground_red | foreground_blue | foreground_green, // white
|
||||
}
|
||||
|
5
vendor/github.com/go-errors/errors/.travis.yml
generated
vendored
Normal file
5
vendor/github.com/go-errors/errors/.travis.yml
generated
vendored
Normal 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
7
vendor/github.com/go-errors/errors/LICENSE.MIT
generated
vendored
Normal 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
66
vendor/github.com/go-errors/errors/README.md
generated
vendored
Normal 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
89
vendor/github.com/go-errors/errors/cover.out
generated
vendored
Normal 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
217
vendor/github.com/go-errors/errors/error.go
generated
vendored
Normal 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
127
vendor/github.com/go-errors/errors/parse_panic.go
generated
vendored
Normal 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
102
vendor/github.com/go-errors/errors/stackframe.go
generated
vendored
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user