From 6dca0ebd227489fd67398234adf5a2bc0c0e60de Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Sat, 27 Apr 2019 21:14:30 +0200 Subject: [PATCH 01/73] Added code of conduct --- CODE_OF_CONDUCT.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..1bdac055 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at mkopenga@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq From 65defda2018b5b561f562261df8c0d490b9ee8e9 Mon Sep 17 00:00:00 2001 From: Glenn Vriesman <28631886+glvr182@users.noreply.github.com> Date: Sat, 27 Apr 2019 21:17:28 +0200 Subject: [PATCH 02/73] Created templates --- .github/ISSUE_TEMPLATE/bug_report.md | 29 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 17 +++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..50323165 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,29 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG]" +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Environment (please complete the following information):** + - OS: [e.g. Linux] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..0ff45a24 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Describe the feature you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 70c5401ace442b8d79ffdd3d6e83d3e775c774cf Mon Sep 17 00:00:00 2001 From: mjarkk Date: Sat, 27 Apr 2019 21:34:50 +0200 Subject: [PATCH 03/73] Changed all refrences from jroimartin to awesome-gocui --- README.md | 10 +++++----- _examples/active.go | 2 +- _examples/bufs.go | 2 +- _examples/colors.go | 2 +- _examples/colors256.go | 2 +- _examples/demo.go | 2 +- _examples/dynamic.go | 2 +- _examples/flow_layout.go | 2 +- _examples/goroutine.go | 2 +- _examples/hello.go | 2 +- _examples/layout.go | 2 +- _examples/mask.go | 2 +- _examples/mouse.go | 2 +- _examples/ontop.go | 2 +- _examples/overlap.go | 2 +- _examples/size.go | 2 +- _examples/stdin.go | 2 +- _examples/title.go | 2 +- _examples/widgets.go | 2 +- _examples/wrap.go | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index d7b55a3b..d497a875 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # GOCUI - Go Console User Interface -[![GoDoc](https://godoc.org/github.com/jroimartin/gocui?status.svg)](https://godoc.org/github.com/jroimartin/gocui) +[![GoDoc](https://godoc.org/github.com/awesome-gocui/gocui?status.svg)](https://godoc.org/github.com/awesome-gocui/gocui) Minimalist Go package aimed at creating Console User Interfaces. @@ -21,7 +21,7 @@ Minimalist Go package aimed at creating Console User Interfaces. Execute: ``` -$ go get github.com/jroimartin/gocui +$ go get github.com/awesome-gocui/gocui ``` ## Documentation @@ -29,10 +29,10 @@ $ 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 @@ -44,7 +44,7 @@ import ( "fmt" "log" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) func main() { diff --git a/_examples/active.go b/_examples/active.go index 4c3e19b7..dea1dad1 100644 --- a/_examples/active.go +++ b/_examples/active.go @@ -8,7 +8,7 @@ import ( "fmt" "log" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) var ( diff --git a/_examples/bufs.go b/_examples/bufs.go index 7d145006..b0754f81 100644 --- a/_examples/bufs.go +++ b/_examples/bufs.go @@ -10,7 +10,7 @@ import ( "fmt" "log" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) var vbuf, buf string diff --git a/_examples/colors.go b/_examples/colors.go index 9542c6d7..b01f3ca7 100644 --- a/_examples/colors.go +++ b/_examples/colors.go @@ -8,7 +8,7 @@ import ( "fmt" "log" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) func main() { diff --git a/_examples/colors256.go b/_examples/colors256.go index 3abd4234..7379552d 100644 --- a/_examples/colors256.go +++ b/_examples/colors256.go @@ -8,7 +8,7 @@ import ( "fmt" "log" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) func main() { diff --git a/_examples/demo.go b/_examples/demo.go index 8f4b1ec3..361152ce 100644 --- a/_examples/demo.go +++ b/_examples/demo.go @@ -11,7 +11,7 @@ import ( "log" "strings" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) func nextView(g *gocui.Gui, v *gocui.View) error { diff --git a/_examples/dynamic.go b/_examples/dynamic.go index f1ec0472..8efad338 100644 --- a/_examples/dynamic.go +++ b/_examples/dynamic.go @@ -9,7 +9,7 @@ import ( "log" "strings" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) const delta = 1 diff --git a/_examples/flow_layout.go b/_examples/flow_layout.go index e2cae79f..afe69187 100644 --- a/_examples/flow_layout.go +++ b/_examples/flow_layout.go @@ -9,7 +9,7 @@ import ( "log" "strings" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) type Label struct { diff --git a/_examples/goroutine.go b/_examples/goroutine.go index 81c08350..9a7c90b8 100644 --- a/_examples/goroutine.go +++ b/_examples/goroutine.go @@ -10,7 +10,7 @@ import ( "sync" "time" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) const NumGoroutines = 10 diff --git a/_examples/hello.go b/_examples/hello.go index fc3c7f87..f0e44cbf 100644 --- a/_examples/hello.go +++ b/_examples/hello.go @@ -8,7 +8,7 @@ import ( "fmt" "log" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) func main() { diff --git a/_examples/layout.go b/_examples/layout.go index 3e9c3e32..eee24f7c 100644 --- a/_examples/layout.go +++ b/_examples/layout.go @@ -7,7 +7,7 @@ package main import ( "log" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) func layout(g *gocui.Gui) error { diff --git a/_examples/mask.go b/_examples/mask.go index 481f17e5..47f16283 100644 --- a/_examples/mask.go +++ b/_examples/mask.go @@ -8,7 +8,7 @@ import ( "fmt" "log" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) func main() { diff --git a/_examples/mouse.go b/_examples/mouse.go index 38dc8862..d79b0eee 100644 --- a/_examples/mouse.go +++ b/_examples/mouse.go @@ -8,7 +8,7 @@ import ( "fmt" "log" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) func main() { diff --git a/_examples/ontop.go b/_examples/ontop.go index 675f5937..a6197a19 100644 --- a/_examples/ontop.go +++ b/_examples/ontop.go @@ -8,7 +8,7 @@ import ( "fmt" "log" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) func main() { diff --git a/_examples/overlap.go b/_examples/overlap.go index 5cadf949..0c21f367 100644 --- a/_examples/overlap.go +++ b/_examples/overlap.go @@ -7,7 +7,7 @@ package main import ( "log" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) func layout(g *gocui.Gui) error { diff --git a/_examples/size.go b/_examples/size.go index 6fb4f911..ea5a43e2 100644 --- a/_examples/size.go +++ b/_examples/size.go @@ -8,7 +8,7 @@ import ( "fmt" "log" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) func main() { diff --git a/_examples/stdin.go b/_examples/stdin.go index 03670ab7..e0fb9aa6 100644 --- a/_examples/stdin.go +++ b/_examples/stdin.go @@ -11,7 +11,7 @@ import ( "log" "os" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) func main() { diff --git a/_examples/title.go b/_examples/title.go index 2be56895..8dddd7e9 100644 --- a/_examples/title.go +++ b/_examples/title.go @@ -7,7 +7,7 @@ package main import ( "log" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) func main() { diff --git a/_examples/widgets.go b/_examples/widgets.go index 8cfad4b2..57e8b6cb 100644 --- a/_examples/widgets.go +++ b/_examples/widgets.go @@ -10,7 +10,7 @@ import ( "log" "strings" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) const delta = 0.2 diff --git a/_examples/wrap.go b/_examples/wrap.go index 04dd5053..4319bb96 100644 --- a/_examples/wrap.go +++ b/_examples/wrap.go @@ -9,7 +9,7 @@ import ( "log" "strings" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) func layout(g *gocui.Gui) error { From 3744626de1a249f67ae9c584302e5809045a68a0 Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Sat, 27 Apr 2019 21:45:56 +0200 Subject: [PATCH 04/73] Added pr template Signed-off-by: Glenn Vriesman --- .github/PULL_REQUEST_TEMPLATE/pull_request.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request.md diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request.md b/.github/PULL_REQUEST_TEMPLATE/pull_request.md new file mode 100644 index 00000000..ce5fc11b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request.md @@ -0,0 +1,15 @@ +--- +name: Pull request +about: Create a pull request to solve an issue +title: "" +labels: +assignees: '' + +--- + +**Related issue** + +**Describe the fix** + +**Screenshots** +If applicable, add screenshots to help explain your implementation. From 9829f5172f63c80beac54a95ccba250841049ea3 Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Sat, 27 Apr 2019 22:06:11 +0200 Subject: [PATCH 05/73] Added contributors guide Signed-off-by: Glenn Vriesman --- CONTRIBUTING.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..b93e45b2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Contributing + +Everyone is welcome to help make gocui better! + +When contributing to this repository, please first discuss the change you wish +to make via issue, email, or any other method with the owners of this repository +before making a change. + +## So all code changes happen through Pull Requests +Pull requests are the best way to propose changes to the codebase. We actively +welcome your pull requests: + +1. Fork the repo and create your branch from `master` with a name like `feature/contributors-guide`. +2. If you've added code that should be tested, add tests. +3. If you've added code that need documentation, update the documentation. +4. Make sure your code follows the [effective go](https://golang.org/doc/effective_go.html) guidelines as much as possible. +5. Be sure to test your modifications. +6. Make sure your branch is up to date with the master branch. +7. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). +8. Create that pull request! + +## Code of conduct +Please note by participating in this project, you agree to abide by the [code of conduct]. + +[code of conduct]: https://github.com/awesome-gocui/gocui/blob/master/CODE-OF-CONDUCT.md + +## Any contributions you make will be under the license indicated in the [license](LICENSE.md) +In short, when you submit code changes, your submissions are understood to be +under the same license as the rest of project. Feel free to contact the maintainers if that's a concern. + +## Report bugs using Github's [issues](https://github.com/awesome-gocui/gocui/issues) +We use GitHub issues to track public bugs. Report a bug by [opening a new +issue](https://github.com/awesome-gocui/gocui/issues/new); it's that easy! \ No newline at end of file From f50da5a539df27ffdcdf5c1590e9ca046911acc0 Mon Sep 17 00:00:00 2001 From: Kobe Lipkens Date: Sat, 5 Nov 2016 11:41:43 +0100 Subject: [PATCH 06/73] Visually wide character support --- edit.go | 111 ++++++++++++++++++++++++++++++++++++++++++-------------- view.go | 88 ++++++++++++++++++++++++++++++-------------- 2 files changed, 144 insertions(+), 55 deletions(-) diff --git a/edit.go b/edit.go index e1b19c20..a5e6f690 100644 --- a/edit.go +++ b/edit.go @@ -4,7 +4,11 @@ package gocui -import "errors" +import ( + "errors" + + "github.com/mattn/go-runewidth" +) const maxInt = int(^uint(0) >> 1) @@ -54,8 +58,9 @@ func simpleEditor(v *View, key Key, ch rune, mod Modifier) { // 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(1, 0, true) + v.moveCursor(w, 0, true) } // EditDelete deletes a rune at the cursor position. back determines the @@ -89,12 +94,12 @@ func (v *View) EditDelete(back bool) { v.MoveCursor(-1, 0, true) } } else { // wrapped line - v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1) - v.MoveCursor(-1, 0, true) + n, _ := v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1) + v.MoveCursor(-n, 0, true) } } else { // middle/end of the line - v.deleteRune(v.cx-1, v.cy) - v.MoveCursor(-1, 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 @@ -116,35 +121,74 @@ func (v *View) EditNewLine() { // 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 @@ -190,10 +234,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 { @@ -275,19 +318,31 @@ 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) 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) if err != nil { - return err + return 0, err } if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) { - return errors.New("invalid point") + return 0, errors.New("invalid point") } - v.lines[y] = append(v.lines[y][:x], v.lines[y][x+1:]...) - return 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. diff --git a/view.go b/view.go index 42082f8c..baa32c11 100644 --- a/view.go +++ b/view.go @@ -10,6 +10,7 @@ import ( "io" "strings" + "github.com/mattn/go-runewidth" "github.com/nsf/termbox-go" ) @@ -297,24 +298,14 @@ func (v *View) draw() error { if v.tainted { v.viewLines = nil for i, line := range v.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) } } @@ -353,7 +344,7 @@ func (v *View) draw() error { if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil { return err } - x++ + x += runewidth.RuneWidth(c.chr) } y++ } @@ -423,11 +414,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 @@ -445,11 +432,12 @@ func (v *View) ViewBufferLines() []string { // 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 @@ -501,3 +489,49 @@ func (v *View) Word(x, y int) (string, error) { func indexFunc(r rune) bool { return r == ' ' || r == 0 } + +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-1]) + 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") +} From 8e407b75a85bdcd1a7df439e4425e9077daae436 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 5 Jun 2018 18:43:28 +1000 Subject: [PATCH 07/73] default to input mode of InputEsc --- gui.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui.go b/gui.go index 9499d3c3..e36a6d79 100644 --- a/gui.go +++ b/gui.go @@ -356,7 +356,7 @@ func (g *Gui) MainLoop() error { }() inputMode := termbox.InputAlt - if g.InputEsc { + if true { // previously g.InputEsc, but didn't seem to work inputMode = termbox.InputEsc } if g.Mouse { From d1cbd48d17c6ac851c35bc269bec8e78dc648b99 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 5 Jun 2018 18:43:39 +1000 Subject: [PATCH 08/73] Ignore char keys when in an editable panel --- keybinding.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/keybinding.go b/keybinding.go index 03fe677c..7efdb75c 100644 --- a/keybinding.go +++ b/keybinding.go @@ -34,6 +34,10 @@ 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 the user is typing in a field, ignore char keys + if v.Editable == true && kb.ch != 0 { + return false + } if kb.viewName == "" { return true } From d9a7952a95539a75d4f64ccdfe30bd09b549e2a9 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 5 Jun 2018 18:43:49 +1000 Subject: [PATCH 09/73] bold highlighted text --- view.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/view.go b/view.go index baa32c11..4ad663e2 100644 --- a/view.go +++ b/view.go @@ -150,8 +150,7 @@ 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 } termbox.SetCell(v.x0+x+1, v.y0+y+1, ch, From 8d3fcc62fbe17adfe6270b44ef558f32af15bf46 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 5 Aug 2018 21:44:09 +1000 Subject: [PATCH 10/73] support collapsed edges (WIP) --- gui.go | 28 +++++++++++++++++++++++++++- view.go | 11 +++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/gui.go b/gui.go index e36a6d79..d5e7a8b2 100644 --- a/gui.go +++ b/gui.go @@ -16,6 +16,10 @@ var ( // ErrUnknownView allows to assert if a View must be initialized. ErrUnknownView = errors.New("unknown view") + + // SupportOverlaps is true when we allow for view edges to overlap with other + // view edges + SupportOverlaps = true ) // OutputMode represents the terminal's output mode (8 or 256 colors). @@ -27,6 +31,12 @@ const ( // 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 @@ -127,7 +137,7 @@ 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) { +func (g *Gui) SetView(name string, x0, y0, x1, y1 int, overlaps byte) (*View, error) { if x0 >= x1 || y0 >= y1 { return nil, errors.New("invalid dimensions") } @@ -147,6 +157,7 @@ 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 } @@ -507,9 +518,24 @@ 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 { runeTL, runeTR, runeBL, runeBR := '┌', '┐', '└', '┘' + if 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 = '+', '+', '+', '+' } diff --git a/view.go b/view.go index 4ad663e2..90fee521 100644 --- a/view.go +++ b/view.go @@ -14,6 +14,14 @@ import ( "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 +) + // A View is a window. It maintains its own internal buffer and cursor // position. type View struct { @@ -72,6 +80,9 @@ type View struct { // 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 } type viewLine struct { From e572e636223d99dba48e9dd3337e1f1c7e939dff Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 5 Aug 2018 21:45:26 +1000 Subject: [PATCH 11/73] revert bolding of box character --- gui.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui.go b/gui.go index d5e7a8b2..657bf1bc 100644 --- a/gui.go +++ b/gui.go @@ -519,7 +519,7 @@ func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error { } func cornerRune(index byte) rune { - return []rune{' ', '│', '│', '│', '─', '┘', '┐', '┤', '─', '└', '┌', '┢', '├', '┴', '┬', '┼'}[index] + return []rune{' ', '│', '│', '│', '─', '┘', '┐', '┤', '─', '└', '┌', '├', '├', '┴', '┬', '┼'}[index] } func corner(v *View, directions byte) rune { From 1dc234707e4b232abd439b678f58cd6503be66c7 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 5 Aug 2018 21:58:02 +1000 Subject: [PATCH 12/73] disable overlapping views for now --- gui.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui.go b/gui.go index 657bf1bc..c9f276ba 100644 --- a/gui.go +++ b/gui.go @@ -19,7 +19,7 @@ var ( // SupportOverlaps is true when we allow for view edges to overlap with other // view edges - SupportOverlaps = true + SupportOverlaps = false ) // OutputMode represents the terminal's output mode (8 or 256 colors). From c50a372060c0cb76f0e62834f745965af2e8e057 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 5 Aug 2018 22:19:20 +1000 Subject: [PATCH 13/73] defining overlapping edge support in the Gui constructor --- gui.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/gui.go b/gui.go index c9f276ba..5f090327 100644 --- a/gui.go +++ b/gui.go @@ -16,10 +16,6 @@ var ( // ErrUnknownView allows to assert if a View must be initialized. ErrUnknownView = errors.New("unknown view") - - // SupportOverlaps is true when we allow for view edges to overlap with other - // view edges - SupportOverlaps = false ) // OutputMode represents the terminal's output mode (8 or 256 colors). @@ -76,10 +72,14 @@ 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) { +func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) { if err := termbox.Init(); err != nil { return nil, err } @@ -97,6 +97,10 @@ func NewGui(mode OutputMode) (*Gui, error) { 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 } @@ -530,7 +534,7 @@ func corner(v *View, directions byte) rune { // drawFrameCorners draws the corners of the view. func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error { runeTL, runeTR, runeBL, runeBR := '┌', '┐', '└', '┘' - if SupportOverlaps { + if g.SupportOverlaps { runeTL = corner(v, BOTTOM|RIGHT) runeTR = corner(v, BOTTOM|LEFT) runeBL = corner(v, TOP|RIGHT) From 2fb90d38c6fbe838754e1460915d092a33d66591 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 12 Aug 2018 11:33:36 +1000 Subject: [PATCH 14/73] make view dimensions accessible --- view.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/view.go b/view.go index 90fee521..471b3c6e 100644 --- a/view.go +++ b/view.go @@ -122,6 +122,11 @@ func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View { return v } +// Dimensions returns the dimensions of the View +func Dimensions(v *View) (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 From a80ffd899d1e209549174c3dfb4983f7ee0ea6b1 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 12 Aug 2018 11:49:26 +1000 Subject: [PATCH 15/73] use dimensions method rather than function --- view.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view.go b/view.go index 471b3c6e..0dd897aa 100644 --- a/view.go +++ b/view.go @@ -123,7 +123,7 @@ func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View { } // Dimensions returns the dimensions of the View -func Dimensions(v *View) (int, int, int, int) { +func (v *View) Dimensions() (int, int, int, int) { return v.x0, v.y0, v.x1, v.y1 } From 6c1b364b7bd3e2b5e0102ab93bfb574d4ab811d0 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 14 Aug 2018 08:31:18 +1000 Subject: [PATCH 16/73] flush the gui before polling for any events --- gui.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gui.go b/gui.go index 5f090327..28a52d1c 100644 --- a/gui.go +++ b/gui.go @@ -364,6 +364,9 @@ 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 { + if err := g.flush(); err != nil { + return err + } go func() { for { g.tbEvents <- termbox.PollEvent() From f27fbaa4687db790bd35743fe9c09feacaf3025f Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 21 Aug 2018 22:46:08 +1000 Subject: [PATCH 17/73] enforce that keybindings can only be handled by a single handler, prioritising view handlers over global handlers --- gui.go | 28 +++++++++++++++++++++------- keybinding.go | 3 --- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/gui.go b/gui.go index 28a52d1c..24ff4980 100644 --- a/gui.go +++ b/gui.go @@ -653,17 +653,31 @@ 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 == "" { + 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 err := kb.handler(g, v); err != nil { + return false, err + } + return true, nil } diff --git a/keybinding.go b/keybinding.go index 7efdb75c..82d1acc9 100644 --- a/keybinding.go +++ b/keybinding.go @@ -38,9 +38,6 @@ func (kb *keybinding) matchView(v *View) bool { if v.Editable == true && kb.ch != 0 { return false } - if kb.viewName == "" { - return true - } return v != nil && kb.viewName == v.name } From b2e97d2164bbdccf31a193703da7b0c08715afb3 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 25 Aug 2018 09:11:22 +1000 Subject: [PATCH 18/73] ignore global rune keybindings on editable views --- gui.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui.go b/gui.go index 24ff4980..26ba79bd 100644 --- a/gui.go +++ b/gui.go @@ -664,7 +664,7 @@ func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err err if kb.matchView(v) { return g.execKeybinding(v, kb) } - if kb.viewName == "" { + if kb.viewName == "" && (!v.Editable || kb.ch == 0) { globalKb = kb } } From 32c5b9b7aa2bf663f429103a377f1f7efe141b9b Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 5 Sep 2018 19:02:37 +1000 Subject: [PATCH 19/73] move cursor directly rather than through function because the old way was causing issues --- edit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edit.go b/edit.go index a5e6f690..c339d75f 100644 --- a/edit.go +++ b/edit.go @@ -114,8 +114,8 @@ 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, From 4ff405630791fe690a32c353d391f4188b01ec64 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 5 Sep 2018 20:40:05 +1000 Subject: [PATCH 20/73] support subtitles on panels --- gui.go | 24 ++++++++++++++++++++++++ view.go | 3 +++ 2 files changed, 27 insertions(+) diff --git a/gui.go b/gui.go index 26ba79bd..4393c06c 100644 --- a/gui.go +++ b/gui.go @@ -476,6 +476,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 @@ -582,6 +587,25 @@ 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) + 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 { diff --git a/view.go b/view.go index 0dd897aa..53ab06f8 100644 --- a/view.go +++ b/view.go @@ -77,6 +77,9 @@ 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 From d6ff636187e8bf1faf5369d2ba9699eba971a877 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 19 Sep 2018 19:40:19 +1000 Subject: [PATCH 21/73] use jesse's fork of termbox which handles poll event errors --- attribute.go | 2 +- keybinding.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/attribute.go b/attribute.go index bad758a1..5b46b142 100644 --- a/attribute.go +++ b/attribute.go @@ -4,7 +4,7 @@ package gocui -import "github.com/nsf/termbox-go" +import "github.com/jesseduffield/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 diff --git a/keybinding.go b/keybinding.go index 82d1acc9..65d9ec6c 100644 --- a/keybinding.go +++ b/keybinding.go @@ -4,7 +4,7 @@ package gocui -import "github.com/nsf/termbox-go" +import "github.com/jesseduffield/termbox-go" // Keybidings are used to link a given key-press event with a handler. type keybinding struct { From 6aacc83b15be206072a3188d4ad85fc5a5f9b362 Mon Sep 17 00:00:00 2001 From: Tommy Nguyen Date: Sun, 16 Sep 2018 23:28:09 -0400 Subject: [PATCH 22/73] view.go: add ViewLinesHeight() --- view.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/view.go b/view.go index 53ab06f8..85eb9360 100644 --- a/view.go +++ b/view.go @@ -447,6 +447,10 @@ func (v *View) ViewBufferLines() []string { return lines } +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 { From 44898ff5558a07ce18643d9d315a5f71487658b6 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 19 Sep 2018 19:58:27 +1000 Subject: [PATCH 23/73] missed a couple of imports that I need to switch to my fork of termbox --- gui.go | 2 +- view.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gui.go b/gui.go index 4393c06c..cb163e06 100644 --- a/gui.go +++ b/gui.go @@ -7,7 +7,7 @@ package gocui import ( "errors" - "github.com/nsf/termbox-go" + "github.com/jesseduffield/termbox-go" ) var ( diff --git a/view.go b/view.go index 85eb9360..46b86ec2 100644 --- a/view.go +++ b/view.go @@ -10,8 +10,8 @@ import ( "io" "strings" + "github.com/jesseduffield/termbox-go" "github.com/mattn/go-runewidth" - "github.com/nsf/termbox-go" ) // Constants for overlapping edges @@ -448,7 +448,7 @@ func (v *View) ViewBufferLines() []string { } func (v *View) ViewLinesHeight() int { - return len(v.viewLines); + return len(v.viewLines) } // ViewBuffer returns a string with the contents of the view's buffer that is From 4869a57fb0e6b03298ad19600956f81f834d3203 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Fri, 21 Sep 2018 16:56:32 +1000 Subject: [PATCH 24/73] use LinesHeight instead of ViewLinesHeight --- view.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/view.go b/view.go index 46b86ec2..939d1bdf 100644 --- a/view.go +++ b/view.go @@ -447,8 +447,8 @@ func (v *View) ViewBufferLines() []string { return lines } -func (v *View) ViewLinesHeight() int { - return len(v.viewLines) +func (v *View) LinesHeight() int { + return len(v.lines) } // ViewBuffer returns a string with the contents of the view's buffer that is From 9673d564c2f306c93206afedbcf44a5323aa1edd Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 9 Dec 2018 21:47:58 +1100 Subject: [PATCH 25/73] convert tab runes to 4 spaces --- view.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/view.go b/view.go index 939d1bdf..6b80b905 100644 --- a/view.go +++ b/view.go @@ -148,7 +148,6 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error { if x < 0 || x >= maxX || y < 0 || y >= maxY { return errors.New("invalid point") } - var ( ry, rcy int err error @@ -270,12 +269,19 @@ 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 From 6c25764500e4e33f42eeeb492db4af4bbd643c5b Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 15 Jan 2019 19:47:58 +1100 Subject: [PATCH 26/73] don't truncate lines on wrapped views --- view.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view.go b/view.go index 6b80b905..73df1dbc 100644 --- a/view.go +++ b/view.go @@ -539,7 +539,7 @@ func lineWrap(line []cell, columns int) [][]cell { n += rw if n > columns { n = rw - lines = append(lines, line[offset:i-1]) + lines = append(lines, line[offset:i]) offset = i } } From 219f0b01a9af27e3df703353f2d7b41db3e2899d Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Mon, 11 Feb 2019 21:59:59 +1100 Subject: [PATCH 27/73] use go-errors package to give stack traces to new errors --- _examples/widgets.go | 2 +- edit.go | 2 +- escape.go | 2 +- gui.go | 2 +- view.go | 3 ++- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/_examples/widgets.go b/_examples/widgets.go index 57e8b6cb..f043b856 100644 --- a/_examples/widgets.go +++ b/_examples/widgets.go @@ -5,7 +5,7 @@ package main import ( - "errors" + "github.com/go-errors/errors" "fmt" "log" "strings" diff --git a/edit.go b/edit.go index c339d75f..b99f74f9 100644 --- a/edit.go +++ b/edit.go @@ -5,7 +5,7 @@ package gocui import ( - "errors" + "github.com/go-errors/errors" "github.com/mattn/go-runewidth" ) diff --git a/escape.go b/escape.go index ec31bbe0..c88309b0 100644 --- a/escape.go +++ b/escape.go @@ -5,7 +5,7 @@ package gocui import ( - "errors" + "github.com/go-errors/errors" "strconv" ) diff --git a/gui.go b/gui.go index cb163e06..a50d0c52 100644 --- a/gui.go +++ b/gui.go @@ -5,7 +5,7 @@ package gocui import ( - "errors" + "github.com/go-errors/errors" "github.com/jesseduffield/termbox-go" ) diff --git a/view.go b/view.go index 73df1dbc..f691960b 100644 --- a/view.go +++ b/view.go @@ -6,10 +6,11 @@ package gocui import ( "bytes" - "errors" "io" "strings" + "github.com/go-errors/errors" + "github.com/jesseduffield/termbox-go" "github.com/mattn/go-runewidth" ) From 53e7c0b69db90cc2ed5244a7d98d9ab3042e90b8 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Fri, 15 Feb 2019 20:50:55 +1100 Subject: [PATCH 28/73] use standard errors when defining package level errors and wrap when percolating up --- gui.go | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/gui.go b/gui.go index a50d0c52..31f8593c 100644 --- a/gui.go +++ b/gui.go @@ -5,6 +5,9 @@ package gocui import ( + standardErrors "errors" + "time" + "github.com/go-errors/errors" "github.com/jesseduffield/termbox-go" @@ -12,10 +15,10 @@ import ( var ( // ErrQuit is used to decide if the MainLoop finished successfully. - ErrQuit = errors.New("quit") + ErrQuit = standardErrors.New("quit") // ErrUnknownView allows to assert if a View must be initialized. - ErrUnknownView = errors.New("unknown view") + ErrUnknownView = standardErrors.New("unknown view") ) // OutputMode represents the terminal's output mode (8 or 256 colors). @@ -163,7 +166,7 @@ func (g *Gui) SetView(name string, x0, y0, x1, y1 int, overlaps byte) (*View, er 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) } // SetViewOnTop sets the given view on top of the existing ones. @@ -175,7 +178,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. @@ -187,7 +190,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. @@ -203,7 +206,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 @@ -216,7 +219,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 @@ -227,7 +230,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. @@ -238,7 +241,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. @@ -249,7 +252,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 From 605f01c165f4366cd561c8408408e29c5381fec0 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Fri, 15 Feb 2019 20:51:46 +1100 Subject: [PATCH 29/73] add loader animations to views with the HasLoader bool set --- gui.go | 14 ++++++++++++++ view.go | 43 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/gui.go b/gui.go index 31f8593c..41599a9b 100644 --- a/gui.go +++ b/gui.go @@ -367,6 +367,7 @@ 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 } @@ -708,3 +709,16 @@ func (g *Gui) execKeybinding(v *View, kb *keybinding) (bool, error) { } return true, nil } + +func (g *Gui) loaderTick() { + go func() { + for range time.Tick(time.Millisecond) { + for _, view := range g.Views() { + if view.HasLoader { + g.userEvents <- userEvent{func(g *Gui) error { return nil }} + break + } + } + } + }() +} diff --git a/view.go b/view.go index f691960b..7cf632c8 100644 --- a/view.go +++ b/view.go @@ -8,6 +8,7 @@ import ( "bytes" "io" "strings" + "time" "github.com/go-errors/errors" @@ -87,6 +88,9 @@ type View struct { // 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 { @@ -322,7 +326,11 @@ 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 { wrap = maxX @@ -334,7 +342,9 @@ func (v *View) draw() error { v.viewLines = append(v.viewLines, vline) } } - v.tainted = false + if !v.HasLoader { + v.tainted = false + } } if v.Autoscroll && len(v.viewLines) > maxY { @@ -564,3 +574,32 @@ func linesToString(lines [][]cell) string { return strings.Join(str, "\n") } + +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 +} + +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, + } +} From 569bfa3f56aca110de263e753f1f973a83f8ab9a Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 3 Mar 2019 14:18:04 +1100 Subject: [PATCH 30/73] stop polling termbox events after closing the gui --- gui.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/gui.go b/gui.go index 41599a9b..1dcb237b 100644 --- a/gui.go +++ b/gui.go @@ -49,6 +49,7 @@ type Gui struct { keybindings []*keybinding maxX, maxY int outputMode OutputMode + stop chan struct{} // BgColor and FgColor allow to configure the background and foreground // colors of the GUI. @@ -92,6 +93,8 @@ func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) { g.outputMode = mode termbox.SetOutputMode(termbox.OutputMode(mode)) + g.stop = make(chan struct{}, 0) + g.tbEvents = make(chan termbox.Event, 20) g.userEvents = make(chan userEvent, 20) @@ -110,6 +113,9 @@ func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) { // 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() } @@ -371,9 +377,15 @@ func (g *Gui) MainLoop() error { 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() + } } }() From 106cbe4a191b5f57719979582867801ce0857195 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 5 Mar 2019 21:24:56 +1100 Subject: [PATCH 31/73] fix performance issue causing high cpu drain --- gui.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui.go b/gui.go index 1dcb237b..223e48c2 100644 --- a/gui.go +++ b/gui.go @@ -724,7 +724,7 @@ func (g *Gui) execKeybinding(v *View, kb *keybinding) (bool, error) { func (g *Gui) loaderTick() { go func() { - for range time.Tick(time.Millisecond) { + for range time.Tick(time.Millisecond * 50) { for _, view := range g.Views() { if view.HasLoader { g.userEvents <- userEvent{func(g *Gui) error { return nil }} From b2f96ac1e3765808ec56dd84006924e3b8517600 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 10 Apr 2019 09:57:31 +1000 Subject: [PATCH 32/73] don't write subtitle outside of view's bounds --- gui.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gui.go b/gui.go index 223e48c2..63025072 100644 --- a/gui.go +++ b/gui.go @@ -610,6 +610,9 @@ func (g *Gui) drawSubtitle(v *View, fgColor, bgColor Attribute) error { } 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 { From 95d85f5b32691e371a45c5469a76b0ace0418a8a Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Fri, 26 Apr 2019 13:40:05 +1000 Subject: [PATCH 33/73] support views of height 1 or even negative height --- gui.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/gui.go b/gui.go index 63025072..d607f1e3 100644 --- a/gui.go +++ b/gui.go @@ -151,7 +151,7 @@ func (g *Gui) Rune(x, y int) (rune, error) { // ErrUnknownView is returned, which allows to assert if the View must // be initialized. It checks if the position is valid. func (g *Gui) SetView(name string, x0, y0, x1, y1 int, overlaps byte) (*View, error) { - if x0 >= x1 || y0 >= y1 { + if x0 >= x1 { return nil, errors.New("invalid dimensions") } if name == "" { @@ -471,6 +471,9 @@ func (g *Gui) flush() error { } } for _, v := range g.views { + if v.y1 < v.y0 { + continue + } if v.Frame { var fgColor, bgColor Attribute if g.Highlight && v == g.currentView { @@ -557,6 +560,10 @@ func corner(v *View, directions byte) rune { // drawFrameCorners draws the corners of the view. func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error { + if v.y0 == v.y1 { + return nil + } + runeTL, runeTR, runeBL, runeBR := '┌', '┐', '└', '┘' if g.SupportOverlaps { runeTL = corner(v, BOTTOM|RIGHT) From 14f6d588ae26a16258da5822e5841627387530d2 Mon Sep 17 00:00:00 2001 From: mjarkk Date: Sat, 27 Apr 2019 22:32:44 +0200 Subject: [PATCH 34/73] Changed the user imports to the organizations it's repo --- attribute.go | 2 +- gui.go | 2 +- keybinding.go | 2 +- view.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/attribute.go b/attribute.go index 5b46b142..3d986a71 100644 --- a/attribute.go +++ b/attribute.go @@ -4,7 +4,7 @@ package gocui -import "github.com/jesseduffield/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 diff --git a/gui.go b/gui.go index d607f1e3..8c91c3d1 100644 --- a/gui.go +++ b/gui.go @@ -10,7 +10,7 @@ import ( "github.com/go-errors/errors" - "github.com/jesseduffield/termbox-go" + "github.com/awesome-gocui/termbox-go" ) var ( diff --git a/keybinding.go b/keybinding.go index 65d9ec6c..2c11bda3 100644 --- a/keybinding.go +++ b/keybinding.go @@ -4,7 +4,7 @@ package gocui -import "github.com/jesseduffield/termbox-go" +import "github.com/awesome-gocui/termbox-go" // Keybidings are used to link a given key-press event with a handler. type keybinding struct { diff --git a/view.go b/view.go index 7cf632c8..da680944 100644 --- a/view.go +++ b/view.go @@ -12,7 +12,7 @@ import ( "github.com/go-errors/errors" - "github.com/jesseduffield/termbox-go" + "github.com/awesome-gocui/termbox-go" "github.com/mattn/go-runewidth" ) From fb41ca56d6f691bb0a9c8f7c56c722f3ceb7b9f3 Mon Sep 17 00:00:00 2001 From: mjarkk Date: Sat, 27 Apr 2019 22:49:03 +0200 Subject: [PATCH 35/73] Made examples compilable --- _examples/active.go | 10 +++++----- _examples/bufs.go | 4 ++-- _examples/colors.go | 4 ++-- _examples/colors256.go | 4 ++-- _examples/demo.go | 8 ++++---- _examples/dynamic.go | 8 ++++---- _examples/flow_layout.go | 6 +++--- _examples/goroutine.go | 4 ++-- _examples/hello.go | 4 ++-- _examples/layout.go | 8 ++++---- _examples/mask.go | 6 +++--- _examples/mouse.go | 8 ++++---- _examples/ontop.go | 8 ++++---- _examples/overlap.go | 20 ++++++++++---------- _examples/size.go | 4 ++-- _examples/stdin.go | 6 +++--- _examples/title.go | 38 +++++++++++++++++++------------------- _examples/widgets.go | 11 ++++++----- _examples/wrap.go | 4 ++-- 19 files changed, 83 insertions(+), 82 deletions(-) diff --git a/_examples/active.go b/_examples/active.go index dea1dad1..4a485330 100644 --- a/_examples/active.go +++ b/_examples/active.go @@ -49,7 +49,7 @@ func nextView(g *gocui.Gui, v *gocui.View) error { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() - if v, err := g.SetView("v1", 0, 0, maxX/2-1, maxY/2-1); err != nil { + if v, err := g.SetView("v1", 0, 0, maxX/2-1, maxY/2-1, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -62,7 +62,7 @@ func layout(g *gocui.Gui) error { } } - if v, err := g.SetView("v2", maxX/2-1, 0, maxX-1, maxY/2-1); err != nil { + if v, err := g.SetView("v2", maxX/2-1, 0, maxX-1, maxY/2-1, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -70,7 +70,7 @@ func layout(g *gocui.Gui) error { v.Wrap = true v.Autoscroll = true } - if v, err := g.SetView("v3", 0, maxY/2-1, maxX/2-1, maxY-1); err != nil { + if v, err := g.SetView("v3", 0, maxY/2-1, maxX/2-1, maxY-1, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -79,7 +79,7 @@ func layout(g *gocui.Gui) error { v.Autoscroll = true fmt.Fprint(v, "Press TAB to change current view") } - if v, err := g.SetView("v4", maxX/2, maxY/2, maxX-1, maxY-1); err != nil { + if v, err := g.SetView("v4", maxX/2, maxY/2, maxX-1, maxY-1, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -94,7 +94,7 @@ func quit(g *gocui.Gui, v *gocui.View) error { } func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Panicln(err) } diff --git a/_examples/bufs.go b/_examples/bufs.go index b0754f81..145ee59d 100644 --- a/_examples/bufs.go +++ b/_examples/bufs.go @@ -28,7 +28,7 @@ func overwrite(g *gocui.Gui, v *gocui.View) error { func layout(g *gocui.Gui) error { _, maxY := g.Size() - if v, err := g.SetView("main", 0, 0, 20, maxY-1); err != nil { + if v, err := g.SetView("main", 0, 0, 20, maxY-1, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -42,7 +42,7 @@ func layout(g *gocui.Gui) error { } func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Panicln(err) } diff --git a/_examples/colors.go b/_examples/colors.go index b01f3ca7..349996cd 100644 --- a/_examples/colors.go +++ b/_examples/colors.go @@ -12,7 +12,7 @@ import ( ) func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Panicln(err) } @@ -31,7 +31,7 @@ func main() { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() - if v, err := g.SetView("colors", maxX/2-7, maxY/2-12, maxX/2+7, maxY/2+13); err != nil { + if v, err := g.SetView("colors", maxX/2-7, maxY/2-12, maxX/2+7, maxY/2+13, 0); err != nil { if err != gocui.ErrUnknownView { return err } diff --git a/_examples/colors256.go b/_examples/colors256.go index 7379552d..f5630077 100644 --- a/_examples/colors256.go +++ b/_examples/colors256.go @@ -12,7 +12,7 @@ import ( ) func main() { - g, err := gocui.NewGui(gocui.Output256) + g, err := gocui.NewGui(gocui.Output256, true) if err != nil { log.Panicln(err) @@ -32,7 +32,7 @@ func main() { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() - if v, err := g.SetView("colors", -1, -1, maxX, maxY); err != nil { + if v, err := g.SetView("colors", -1, -1, maxX, maxY, 0); err != nil { if err != gocui.ErrUnknownView { return err } diff --git a/_examples/demo.go b/_examples/demo.go index 361152ce..1b9022de 100644 --- a/_examples/demo.go +++ b/_examples/demo.go @@ -59,7 +59,7 @@ func getLine(g *gocui.Gui, v *gocui.View) error { } maxX, maxY := g.Size() - if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil { + if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -159,7 +159,7 @@ func saveVisualMain(g *gocui.Gui, v *gocui.View) error { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() - if v, err := g.SetView("side", -1, -1, 30, maxY); err != nil { + if v, err := g.SetView("side", -1, -1, 30, maxY, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -172,7 +172,7 @@ func layout(g *gocui.Gui) error { fmt.Fprint(v, "\rWill be") fmt.Fprint(v, "deleted\rItem 4\nItem 5") } - if v, err := g.SetView("main", 30, -1, maxX, maxY); err != nil { + if v, err := g.SetView("main", 30, -1, maxX, maxY, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -191,7 +191,7 @@ func layout(g *gocui.Gui) error { } func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Panicln(err) } diff --git a/_examples/dynamic.go b/_examples/dynamic.go index 8efad338..259be075 100644 --- a/_examples/dynamic.go +++ b/_examples/dynamic.go @@ -21,7 +21,7 @@ var ( ) func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Panicln(err) } @@ -46,7 +46,7 @@ func main() { func layout(g *gocui.Gui) error { maxX, _ := g.Size() - v, err := g.SetView("help", maxX-25, 0, maxX-1, 9) + v, err := g.SetView("help", maxX-25, 0, maxX-1, 9, 0) if err != nil { if err != gocui.ErrUnknownView { return err @@ -132,7 +132,7 @@ func initKeybindings(g *gocui.Gui) error { func newView(g *gocui.Gui) error { maxX, maxY := g.Size() name := fmt.Sprintf("v%v", idxView) - v, err := g.SetView(name, maxX/2-5, maxY/2-5, maxX/2+5, maxY/2+5) + v, err := g.SetView(name, maxX/2-5, maxY/2-5, maxX/2+5, maxY/2+5, 0) if err != nil { if err != gocui.ErrUnknownView { return err @@ -183,7 +183,7 @@ func moveView(g *gocui.Gui, v *gocui.View, dx, dy int) error { if err != nil { return err } - if _, err := g.SetView(name, x0+dx, y0+dy, x1+dx, y1+dy); err != nil { + if _, err := g.SetView(name, x0+dx, y0+dy, x1+dx, y1+dy, 0); err != nil { return err } return nil diff --git a/_examples/flow_layout.go b/_examples/flow_layout.go index afe69187..89662d63 100644 --- a/_examples/flow_layout.go +++ b/_examples/flow_layout.go @@ -34,7 +34,7 @@ func NewLabel(name string, body string) *Label { } func (w *Label) Layout(g *gocui.Gui) error { - v, err := g.SetView(w.name, 0, 0, w.w, w.h) + v, err := g.SetView(w.name, 0, 0, w.w, w.h, 0) if err != nil { if err != gocui.ErrUnknownView { return err @@ -49,7 +49,7 @@ func flowLayout(g *gocui.Gui) error { x := 0 for _, v := range views { w, h := v.Size() - _, err := g.SetView(v.Name(), x, 0, x+w+1, h+1) + _, err := g.SetView(v.Name(), x, 0, x+w+1, h+1, 0) if err != nil && err != gocui.ErrUnknownView { return err } @@ -59,7 +59,7 @@ func flowLayout(g *gocui.Gui) error { } func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Panicln(err) } diff --git a/_examples/goroutine.go b/_examples/goroutine.go index 9a7c90b8..62ff3394 100644 --- a/_examples/goroutine.go +++ b/_examples/goroutine.go @@ -24,7 +24,7 @@ var ( ) func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Panicln(err) } @@ -49,7 +49,7 @@ func main() { } func layout(g *gocui.Gui) error { - if v, err := g.SetView("ctr", 2, 2, 12, 4); err != nil { + if v, err := g.SetView("ctr", 2, 2, 12, 4, 0); err != nil { if err != gocui.ErrUnknownView { return err } diff --git a/_examples/hello.go b/_examples/hello.go index f0e44cbf..51c9d1fa 100644 --- a/_examples/hello.go +++ b/_examples/hello.go @@ -12,7 +12,7 @@ import ( ) func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Panicln(err) } @@ -31,7 +31,7 @@ func main() { 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 v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2, 0); err != nil { if err != gocui.ErrUnknownView { return err } diff --git a/_examples/layout.go b/_examples/layout.go index eee24f7c..ef65d061 100644 --- a/_examples/layout.go +++ b/_examples/layout.go @@ -12,15 +12,15 @@ import ( func layout(g *gocui.Gui) error { maxX, maxY := g.Size() - if _, err := g.SetView("side", -1, -1, int(0.2*float32(maxX)), maxY-5); err != nil && + if _, err := g.SetView("side", -1, -1, int(0.2*float32(maxX)), maxY-5, 0); err != nil && err != gocui.ErrUnknownView { return err } - if _, err := g.SetView("main", int(0.2*float32(maxX)), -1, maxX, maxY-5); err != nil && + if _, err := g.SetView("main", int(0.2*float32(maxX)), -1, maxX, maxY-5, 0); err != nil && err != gocui.ErrUnknownView { return err } - if _, err := g.SetView("cmdline", -1, maxY-5, maxX, maxY); err != nil && + if _, err := g.SetView("cmdline", -1, maxY-5, maxX, maxY, 0); err != nil && err != gocui.ErrUnknownView { return err } @@ -32,7 +32,7 @@ func quit(g *gocui.Gui, v *gocui.View) error { } func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Panicln(err) } diff --git a/_examples/mask.go b/_examples/mask.go index 47f16283..d4a18836 100644 --- a/_examples/mask.go +++ b/_examples/mask.go @@ -12,7 +12,7 @@ import ( ) func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Fatalln(err) } @@ -34,7 +34,7 @@ func main() { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() - if v, err := g.SetView("help", maxX-23, 0, maxX-1, 3); err != nil { + if v, err := g.SetView("help", maxX-23, 0, maxX-1, 3, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -43,7 +43,7 @@ func layout(g *gocui.Gui) error { fmt.Fprintln(v, "^c: Exit") } - if v, err := g.SetView("input", 0, 0, maxX-24, maxY-1); err != nil { + if v, err := g.SetView("input", 0, 0, maxX-24, maxY-1, 0); err != nil { if err != gocui.ErrUnknownView { return err } diff --git a/_examples/mouse.go b/_examples/mouse.go index d79b0eee..a4f19429 100644 --- a/_examples/mouse.go +++ b/_examples/mouse.go @@ -12,7 +12,7 @@ import ( ) func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Panicln(err) } @@ -33,7 +33,7 @@ func main() { } func layout(g *gocui.Gui) error { - if v, err := g.SetView("but1", 2, 2, 22, 7); err != nil { + if v, err := g.SetView("but1", 2, 2, 22, 7, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -45,7 +45,7 @@ func layout(g *gocui.Gui) error { fmt.Fprintln(v, "Button 1 - line 3") fmt.Fprintln(v, "Button 1 - line 4") } - if v, err := g.SetView("but2", 24, 2, 44, 4); err != nil { + if v, err := g.SetView("but2", 24, 2, 44, 4, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -90,7 +90,7 @@ func showMsg(g *gocui.Gui, v *gocui.View) error { } maxX, maxY := g.Size() - if v, err := g.SetView("msg", maxX/2-10, maxY/2, maxX/2+10, maxY/2+2); err != nil { + if v, err := g.SetView("msg", maxX/2-10, maxY/2, maxX/2+10, maxY/2+2, 0); err != nil { if err != gocui.ErrUnknownView { return err } diff --git a/_examples/ontop.go b/_examples/ontop.go index a6197a19..478b4903 100644 --- a/_examples/ontop.go +++ b/_examples/ontop.go @@ -12,7 +12,7 @@ import ( ) func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Panicln(err) } @@ -30,19 +30,19 @@ func main() { } func layout(g *gocui.Gui) error { - if v, err := g.SetView("v1", 10, 2, 30, 6); err != nil { + if v, err := g.SetView("v1", 10, 2, 30, 6, 0); err != nil { if err != gocui.ErrUnknownView { return err } fmt.Fprintln(v, "View #1") } - if v, err := g.SetView("v2", 20, 4, 40, 8); err != nil { + if v, err := g.SetView("v2", 20, 4, 40, 8, 0); err != nil { if err != gocui.ErrUnknownView { return err } fmt.Fprintln(v, "View #2") } - if v, err := g.SetView("v3", 30, 6, 50, 10); err != nil { + if v, err := g.SetView("v3", 30, 6, 50, 10, 0); err != nil { if err != gocui.ErrUnknownView { return err } diff --git a/_examples/overlap.go b/_examples/overlap.go index 0c21f367..5560b14c 100644 --- a/_examples/overlap.go +++ b/_examples/overlap.go @@ -12,39 +12,39 @@ import ( func layout(g *gocui.Gui) error { maxX, maxY := g.Size() - if _, err := g.SetView("v1", -1, -1, 10, 10); err != nil && + if _, err := g.SetView("v1", -1, -1, 10, 10, 0); err != nil && err != gocui.ErrUnknownView { return err } - if _, err := g.SetView("v2", maxX-10, -1, maxX, 10); err != nil && + if _, err := g.SetView("v2", maxX-10, -1, maxX, 10, 0); err != nil && err != gocui.ErrUnknownView { return err } - if _, err := g.SetView("v3", maxX/2-5, -1, maxX/2+5, 10); err != nil && + if _, err := g.SetView("v3", maxX/2-5, -1, maxX/2+5, 10, 0); err != nil && err != gocui.ErrUnknownView { return err } - if _, err := g.SetView("v4", -1, maxY/2-5, 10, maxY/2+5); err != nil && + if _, err := g.SetView("v4", -1, maxY/2-5, 10, maxY/2+5, 0); err != nil && err != gocui.ErrUnknownView { return err } - if _, err := g.SetView("v5", maxX-10, maxY/2-5, maxX, maxY/2+5); err != nil && + if _, err := g.SetView("v5", maxX-10, maxY/2-5, maxX, maxY/2+5, 0); err != nil && err != gocui.ErrUnknownView { return err } - if _, err := g.SetView("v6", -1, maxY-10, 10, maxY); err != nil && + if _, err := g.SetView("v6", -1, maxY-10, 10, maxY, 0); err != nil && err != gocui.ErrUnknownView { return err } - if _, err := g.SetView("v7", maxX-10, maxY-10, maxX, maxY); err != nil && + if _, err := g.SetView("v7", maxX-10, maxY-10, maxX, maxY, 0); err != nil && err != gocui.ErrUnknownView { return err } - if _, err := g.SetView("v8", maxX/2-5, maxY-10, maxX/2+5, maxY); err != nil && + if _, err := g.SetView("v8", maxX/2-5, maxY-10, maxX/2+5, maxY, 0); err != nil && err != gocui.ErrUnknownView { return err } - if _, err := g.SetView("v9", maxX/2-5, maxY/2-5, maxX/2+5, maxY/2+5); err != nil && + if _, err := g.SetView("v9", maxX/2-5, maxY/2-5, maxX/2+5, maxY/2+5, 0); err != nil && err != gocui.ErrUnknownView { return err } @@ -56,7 +56,7 @@ func quit(g *gocui.Gui, v *gocui.View) error { } func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Panicln(err) } diff --git a/_examples/size.go b/_examples/size.go index ea5a43e2..2124122f 100644 --- a/_examples/size.go +++ b/_examples/size.go @@ -12,7 +12,7 @@ import ( ) func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Panicln(err) } @@ -31,7 +31,7 @@ func main() { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() - v, err := g.SetView("size", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2) + v, err := g.SetView("size", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2, 0) if err != nil && err != gocui.ErrUnknownView { return err } diff --git a/_examples/stdin.go b/_examples/stdin.go index e0fb9aa6..e97df68b 100644 --- a/_examples/stdin.go +++ b/_examples/stdin.go @@ -15,7 +15,7 @@ import ( ) func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Fatalln(err) } @@ -37,7 +37,7 @@ func main() { func layout(g *gocui.Gui) error { maxX, _ := g.Size() - if v, err := g.SetView("help", maxX-23, 0, maxX-1, 5); err != nil { + if v, err := g.SetView("help", maxX-23, 0, maxX-1, 5, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -47,7 +47,7 @@ func layout(g *gocui.Gui) error { fmt.Fprintln(v, "^C: Exit") } - if v, err := g.SetView("stdin", 0, 0, 80, 35); err != nil { + if v, err := g.SetView("stdin", 0, 0, 80, 35, 0); err != nil { if err != gocui.ErrUnknownView { return err } diff --git a/_examples/title.go b/_examples/title.go index 8dddd7e9..2e5981e6 100644 --- a/_examples/title.go +++ b/_examples/title.go @@ -11,7 +11,7 @@ import ( ) func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Panicln(err) } @@ -36,13 +36,13 @@ func layout(g *gocui.Gui) error { maxX, maxY := g.Size() // Overlap (front) - if v, err := g.SetView("v1", 10, 2, 30, 6); err != nil { + if v, err := g.SetView("v1", 10, 2, 30, 6, 0); err != nil { if err != gocui.ErrUnknownView { return err } v.Title = "Regular title" } - if v, err := g.SetView("v2", 20, 4, 40, 8); err != nil { + if v, err := g.SetView("v2", 20, 4, 40, 8, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -50,13 +50,13 @@ func layout(g *gocui.Gui) error { } // Overlap (back) - if v, err := g.SetView("v3", 60, 4, 80, 8); err != nil { + if v, err := g.SetView("v3", 60, 4, 80, 8, 0); err != nil { if err != gocui.ErrUnknownView { return err } v.Title = "Regular title" } - if v, err := g.SetView("v4", 50, 2, 70, 6); err != nil { + if v, err := g.SetView("v4", 50, 2, 70, 6, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -64,25 +64,25 @@ func layout(g *gocui.Gui) error { } // Overlap (frame) - if v, err := g.SetView("v15", 90, 2, 110, 5); err != nil { + if v, err := g.SetView("v15", 90, 2, 110, 5, 0); err != nil { if err != gocui.ErrUnknownView { return err } v.Title = "Regular title" } - if v, err := g.SetView("v16", 100, 5, 120, 8); err != nil { + if v, err := g.SetView("v16", 100, 5, 120, 8, 0); err != nil { if err != gocui.ErrUnknownView { return err } v.Title = "Regular title" } - if v, err := g.SetView("v17", 140, 5, 160, 8); err != nil { + if v, err := g.SetView("v17", 140, 5, 160, 8, 0); err != nil { if err != gocui.ErrUnknownView { return err } v.Title = "Regular title" } - if v, err := g.SetView("v18", 130, 2, 150, 5); err != nil { + if v, err := g.SetView("v18", 130, 2, 150, 5, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -90,7 +90,7 @@ func layout(g *gocui.Gui) error { } // Long title - if v, err := g.SetView("v5", 10, 12, 30, 16); err != nil { + if v, err := g.SetView("v5", 10, 12, 30, 16, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -98,20 +98,20 @@ func layout(g *gocui.Gui) error { } // No title - if v, err := g.SetView("v6", 35, 12, 55, 16); err != nil { + if v, err := g.SetView("v6", 35, 12, 55, 16, 0); err != nil { if err != gocui.ErrUnknownView { return err } v.Title = "" } - if _, err := g.SetView("v7", 60, 12, 80, 16); err != nil { + if _, err := g.SetView("v7", 60, 12, 80, 16, 0); err != nil { if err != gocui.ErrUnknownView { return err } } // Small view - if v, err := g.SetView("v8", 85, 12, 88, 16); err != nil { + if v, err := g.SetView("v8", 85, 12, 88, 16, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -119,13 +119,13 @@ func layout(g *gocui.Gui) error { } // Screen borders - if v, err := g.SetView("v9", -10, 20, 10, 24); err != nil { + if v, err := g.SetView("v9", -10, 20, 10, 24, 0); err != nil { if err != gocui.ErrUnknownView { return err } v.Title = "Regular title" } - if v, err := g.SetView("v10", maxX-10, 20, maxX+10, 24); err != nil { + if v, err := g.SetView("v10", maxX-10, 20, maxX+10, 24, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -133,25 +133,25 @@ func layout(g *gocui.Gui) error { } // Out of screen - if v, err := g.SetView("v11", -21, 28, -1, 32); err != nil { + if v, err := g.SetView("v11", -21, 28, -1, 32, 0); err != nil { if err != gocui.ErrUnknownView { return err } v.Title = "Regular title" } - if v, err := g.SetView("v12", maxX, 28, maxX+20, 32); err != nil { + if v, err := g.SetView("v12", maxX, 28, maxX+20, 32, 0); err != nil { if err != gocui.ErrUnknownView { return err } v.Title = "Regular title" } - if v, err := g.SetView("v13", 10, -7, 30, -1); err != nil { + if v, err := g.SetView("v13", 10, -7, 30, -1, 0); err != nil { if err != gocui.ErrUnknownView { return err } v.Title = "Regular title" } - if v, err := g.SetView("v14", 10, maxY, 30, maxY+6); err != nil { + if v, err := g.SetView("v14", 10, maxY, 30, maxY+6, 0); err != nil { if err != gocui.ErrUnknownView { return err } diff --git a/_examples/widgets.go b/_examples/widgets.go index f043b856..dea49c24 100644 --- a/_examples/widgets.go +++ b/_examples/widgets.go @@ -5,11 +5,12 @@ package main import ( - "github.com/go-errors/errors" "fmt" "log" "strings" + "github.com/go-errors/errors" + "github.com/awesome-gocui/gocui" ) @@ -38,7 +39,7 @@ func NewHelpWidget(name string, x, y int, body string) *HelpWidget { } func (w *HelpWidget) Layout(g *gocui.Gui) error { - v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+w.h) + v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+w.h, 0) if err != nil { if err != gocui.ErrUnknownView { return err @@ -72,7 +73,7 @@ func (w *StatusbarWidget) Val() float64 { } func (w *StatusbarWidget) Layout(g *gocui.Gui) error { - v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+2) + v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+2, 0) if err != nil && err != gocui.ErrUnknownView { return err } @@ -96,7 +97,7 @@ func NewButtonWidget(name string, x, y int, label string, handler func(g *gocui. } func (w *ButtonWidget) Layout(g *gocui.Gui) error { - v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+2) + v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+2, 0) if err != nil { if err != gocui.ErrUnknownView { return err @@ -113,7 +114,7 @@ func (w *ButtonWidget) Layout(g *gocui.Gui) error { } func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Panicln(err) } diff --git a/_examples/wrap.go b/_examples/wrap.go index 4319bb96..78a886ef 100644 --- a/_examples/wrap.go +++ b/_examples/wrap.go @@ -14,7 +14,7 @@ import ( func layout(g *gocui.Gui) error { maxX, maxY := g.Size() - if v, err := g.SetView("main", 1, 1, maxX-1, maxY-1); err != nil { + if v, err := g.SetView("main", 1, 1, maxX-1, maxY-1, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -32,7 +32,7 @@ func quit(g *gocui.Gui, v *gocui.View) error { } func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, true) if err != nil { log.Panicln(err) } From 563a1c8d052bc78e0bc1408d2e6a20683c340eaf Mon Sep 17 00:00:00 2001 From: mjarkk Date: Sat, 27 Apr 2019 23:41:03 +0200 Subject: [PATCH 36/73] Most examples work but there are still problems - The stdin example doesn't work - Examples panic on exit - Original unknown view error check doesn't work --- _examples/active.go | 8 ++++---- _examples/bufs.go | 2 +- _examples/colors.go | 2 +- _examples/colors256.go | 2 +- _examples/demo.go | 6 +++--- _examples/dynamic.go | 4 ++-- _examples/flow_layout.go | 4 ++-- _examples/goroutine.go | 2 +- _examples/hello.go | 4 ++-- _examples/layout.go | 6 +++--- _examples/mask.go | 4 ++-- _examples/mouse.go | 6 +++--- _examples/ontop.go | 6 +++--- _examples/overlap.go | 18 +++++++++--------- _examples/size.go | 2 +- _examples/stdin.go | 4 ++-- _examples/title.go | 36 ++++++++++++++++++------------------ _examples/widgets.go | 6 +++--- _examples/wrap.go | 2 +- 19 files changed, 62 insertions(+), 62 deletions(-) diff --git a/_examples/active.go b/_examples/active.go index 4a485330..ac88635f 100644 --- a/_examples/active.go +++ b/_examples/active.go @@ -50,7 +50,7 @@ func nextView(g *gocui.Gui, v *gocui.View) error { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() if v, err := g.SetView("v1", 0, 0, maxX/2-1, maxY/2-1, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "v1 (editable)" @@ -63,7 +63,7 @@ func layout(g *gocui.Gui) error { } if v, err := g.SetView("v2", maxX/2-1, 0, maxX-1, maxY/2-1, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "v2" @@ -71,7 +71,7 @@ func layout(g *gocui.Gui) error { v.Autoscroll = true } if v, err := g.SetView("v3", 0, maxY/2-1, maxX/2-1, maxY-1, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "v3" @@ -80,7 +80,7 @@ func layout(g *gocui.Gui) error { fmt.Fprint(v, "Press TAB to change current view") } if v, err := g.SetView("v4", maxX/2, maxY/2, maxX-1, maxY-1, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "v4 (editable)" diff --git a/_examples/bufs.go b/_examples/bufs.go index 145ee59d..d8853e12 100644 --- a/_examples/bufs.go +++ b/_examples/bufs.go @@ -29,7 +29,7 @@ func overwrite(g *gocui.Gui, v *gocui.View) error { func layout(g *gocui.Gui) error { _, maxY := g.Size() if v, err := g.SetView("main", 0, 0, 20, maxY-1, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Editable = true diff --git a/_examples/colors.go b/_examples/colors.go index 349996cd..6b27d32a 100644 --- a/_examples/colors.go +++ b/_examples/colors.go @@ -32,7 +32,7 @@ func main() { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() if v, err := g.SetView("colors", maxX/2-7, maxY/2-12, maxX/2+7, maxY/2+13, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } for i := 0; i <= 7; i++ { diff --git a/_examples/colors256.go b/_examples/colors256.go index f5630077..94fa2c90 100644 --- a/_examples/colors256.go +++ b/_examples/colors256.go @@ -33,7 +33,7 @@ func main() { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() if v, err := g.SetView("colors", -1, -1, maxX, maxY, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } diff --git a/_examples/demo.go b/_examples/demo.go index 1b9022de..7108fc4a 100644 --- a/_examples/demo.go +++ b/_examples/demo.go @@ -60,7 +60,7 @@ func getLine(g *gocui.Gui, v *gocui.View) error { maxX, maxY := g.Size() if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } fmt.Fprintln(v, l) @@ -160,7 +160,7 @@ func saveVisualMain(g *gocui.Gui, v *gocui.View) error { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() if v, err := g.SetView("side", -1, -1, 30, maxY, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Highlight = true @@ -173,7 +173,7 @@ func layout(g *gocui.Gui) error { fmt.Fprint(v, "deleted\rItem 4\nItem 5") } if v, err := g.SetView("main", 30, -1, maxX, maxY, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } b, err := ioutil.ReadFile("Mark.Twain-Tom.Sawyer.txt") diff --git a/_examples/dynamic.go b/_examples/dynamic.go index 259be075..e5d5e927 100644 --- a/_examples/dynamic.go +++ b/_examples/dynamic.go @@ -48,7 +48,7 @@ func layout(g *gocui.Gui) error { maxX, _ := g.Size() v, err := g.SetView("help", maxX-25, 0, maxX-1, 9, 0) if err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } fmt.Fprintln(v, "KEYBINDINGS") @@ -134,7 +134,7 @@ func newView(g *gocui.Gui) error { name := fmt.Sprintf("v%v", idxView) v, err := g.SetView(name, maxX/2-5, maxY/2-5, maxX/2+5, maxY/2+5, 0) if err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Wrap = true diff --git a/_examples/flow_layout.go b/_examples/flow_layout.go index 89662d63..b683e1ac 100644 --- a/_examples/flow_layout.go +++ b/_examples/flow_layout.go @@ -36,7 +36,7 @@ func NewLabel(name string, body string) *Label { func (w *Label) Layout(g *gocui.Gui) error { v, err := g.SetView(w.name, 0, 0, w.w, w.h, 0) if err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } fmt.Fprint(v, w.body) @@ -50,7 +50,7 @@ func flowLayout(g *gocui.Gui) error { for _, v := range views { w, h := v.Size() _, err := g.SetView(v.Name(), x, 0, x+w+1, h+1, 0) - if err != nil && err != gocui.ErrUnknownView { + if err != nil && err.Error() != "unknown view" { return err } x += w + 2 diff --git a/_examples/goroutine.go b/_examples/goroutine.go index 62ff3394..df9ad511 100644 --- a/_examples/goroutine.go +++ b/_examples/goroutine.go @@ -50,7 +50,7 @@ func main() { func layout(g *gocui.Gui) error { if v, err := g.SetView("ctr", 2, 2, 12, 4, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } fmt.Fprintln(v, "0") diff --git a/_examples/hello.go b/_examples/hello.go index 51c9d1fa..8679ba0d 100644 --- a/_examples/hello.go +++ b/_examples/hello.go @@ -32,10 +32,10 @@ func main() { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } - fmt.Fprintln(v, "Hello world!") + fmt.Fprintln(v, "hello") } return nil } diff --git a/_examples/layout.go b/_examples/layout.go index ef65d061..8d711955 100644 --- a/_examples/layout.go +++ b/_examples/layout.go @@ -13,15 +13,15 @@ import ( func layout(g *gocui.Gui) error { maxX, maxY := g.Size() if _, err := g.SetView("side", -1, -1, int(0.2*float32(maxX)), maxY-5, 0); err != nil && - err != gocui.ErrUnknownView { + err.Error() != "unknown view" { return err } if _, err := g.SetView("main", int(0.2*float32(maxX)), -1, maxX, maxY-5, 0); err != nil && - err != gocui.ErrUnknownView { + err.Error() != "unknown view" { return err } if _, err := g.SetView("cmdline", -1, maxY-5, maxX, maxY, 0); err != nil && - err != gocui.ErrUnknownView { + err.Error() != "unknown view" { return err } return nil diff --git a/_examples/mask.go b/_examples/mask.go index d4a18836..378e483b 100644 --- a/_examples/mask.go +++ b/_examples/mask.go @@ -35,7 +35,7 @@ func layout(g *gocui.Gui) error { maxX, maxY := g.Size() if v, err := g.SetView("help", maxX-23, 0, maxX-1, 3, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "Keybindings" @@ -44,7 +44,7 @@ func layout(g *gocui.Gui) error { } if v, err := g.SetView("input", 0, 0, maxX-24, maxY-1, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } if _, err := g.SetCurrentView("input"); err != nil { diff --git a/_examples/mouse.go b/_examples/mouse.go index a4f19429..d0dd1b63 100644 --- a/_examples/mouse.go +++ b/_examples/mouse.go @@ -34,7 +34,7 @@ func main() { func layout(g *gocui.Gui) error { if v, err := g.SetView("but1", 2, 2, 22, 7, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Highlight = true @@ -46,7 +46,7 @@ func layout(g *gocui.Gui) error { fmt.Fprintln(v, "Button 1 - line 4") } if v, err := g.SetView("but2", 24, 2, 44, 4, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Highlight = true @@ -91,7 +91,7 @@ func showMsg(g *gocui.Gui, v *gocui.View) error { maxX, maxY := g.Size() if v, err := g.SetView("msg", maxX/2-10, maxY/2, maxX/2+10, maxY/2+2, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } fmt.Fprintln(v, l) diff --git a/_examples/ontop.go b/_examples/ontop.go index 478b4903..6c72a8e0 100644 --- a/_examples/ontop.go +++ b/_examples/ontop.go @@ -31,19 +31,19 @@ func main() { func layout(g *gocui.Gui) error { if v, err := g.SetView("v1", 10, 2, 30, 6, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } fmt.Fprintln(v, "View #1") } if v, err := g.SetView("v2", 20, 4, 40, 8, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } fmt.Fprintln(v, "View #2") } if v, err := g.SetView("v3", 30, 6, 50, 10, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } fmt.Fprintln(v, "View #3") diff --git a/_examples/overlap.go b/_examples/overlap.go index 5560b14c..5306dfb6 100644 --- a/_examples/overlap.go +++ b/_examples/overlap.go @@ -13,39 +13,39 @@ import ( func layout(g *gocui.Gui) error { maxX, maxY := g.Size() if _, err := g.SetView("v1", -1, -1, 10, 10, 0); err != nil && - err != gocui.ErrUnknownView { + err.Error() != "unknown view" { return err } if _, err := g.SetView("v2", maxX-10, -1, maxX, 10, 0); err != nil && - err != gocui.ErrUnknownView { + err.Error() != "unknown view" { return err } if _, err := g.SetView("v3", maxX/2-5, -1, maxX/2+5, 10, 0); err != nil && - err != gocui.ErrUnknownView { + err.Error() != "unknown view" { return err } if _, err := g.SetView("v4", -1, maxY/2-5, 10, maxY/2+5, 0); err != nil && - err != gocui.ErrUnknownView { + err.Error() != "unknown view" { return err } if _, err := g.SetView("v5", maxX-10, maxY/2-5, maxX, maxY/2+5, 0); err != nil && - err != gocui.ErrUnknownView { + err.Error() != "unknown view" { return err } if _, err := g.SetView("v6", -1, maxY-10, 10, maxY, 0); err != nil && - err != gocui.ErrUnknownView { + err.Error() != "unknown view" { return err } if _, err := g.SetView("v7", maxX-10, maxY-10, maxX, maxY, 0); err != nil && - err != gocui.ErrUnknownView { + err.Error() != "unknown view" { return err } if _, err := g.SetView("v8", maxX/2-5, maxY-10, maxX/2+5, maxY, 0); err != nil && - err != gocui.ErrUnknownView { + err.Error() != "unknown view" { return err } if _, err := g.SetView("v9", maxX/2-5, maxY/2-5, maxX/2+5, maxY/2+5, 0); err != nil && - err != gocui.ErrUnknownView { + err.Error() != "unknown view" { return err } return nil diff --git a/_examples/size.go b/_examples/size.go index 2124122f..104f37fb 100644 --- a/_examples/size.go +++ b/_examples/size.go @@ -32,7 +32,7 @@ func main() { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() v, err := g.SetView("size", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2, 0) - if err != nil && err != gocui.ErrUnknownView { + if err != nil && err.Error() != "unknown view" { return err } v.Clear() diff --git a/_examples/stdin.go b/_examples/stdin.go index e97df68b..e733c81a 100644 --- a/_examples/stdin.go +++ b/_examples/stdin.go @@ -38,7 +38,7 @@ func layout(g *gocui.Gui) error { maxX, _ := g.Size() if v, err := g.SetView("help", maxX-23, 0, maxX-1, 5, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } fmt.Fprintln(v, "KEYBINDINGS") @@ -48,7 +48,7 @@ func layout(g *gocui.Gui) error { } if v, err := g.SetView("stdin", 0, 0, 80, 35, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } if _, err := g.SetCurrentView("stdin"); err != nil { diff --git a/_examples/title.go b/_examples/title.go index 2e5981e6..d5115a29 100644 --- a/_examples/title.go +++ b/_examples/title.go @@ -37,13 +37,13 @@ func layout(g *gocui.Gui) error { // Overlap (front) if v, err := g.SetView("v1", 10, 2, 30, 6, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "Regular title" } if v, err := g.SetView("v2", 20, 4, 40, 8, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "Regular title" @@ -51,13 +51,13 @@ func layout(g *gocui.Gui) error { // Overlap (back) if v, err := g.SetView("v3", 60, 4, 80, 8, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "Regular title" } if v, err := g.SetView("v4", 50, 2, 70, 6, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "Regular title" @@ -65,25 +65,25 @@ func layout(g *gocui.Gui) error { // Overlap (frame) if v, err := g.SetView("v15", 90, 2, 110, 5, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "Regular title" } if v, err := g.SetView("v16", 100, 5, 120, 8, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "Regular title" } if v, err := g.SetView("v17", 140, 5, 160, 8, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "Regular title" } if v, err := g.SetView("v18", 130, 2, 150, 5, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "Regular title" @@ -91,7 +91,7 @@ func layout(g *gocui.Gui) error { // Long title if v, err := g.SetView("v5", 10, 12, 30, 16, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "Long long long long title" @@ -99,20 +99,20 @@ func layout(g *gocui.Gui) error { // No title if v, err := g.SetView("v6", 35, 12, 55, 16, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "" } if _, err := g.SetView("v7", 60, 12, 80, 16, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } } // Small view if v, err := g.SetView("v8", 85, 12, 88, 16, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "Regular title" @@ -120,13 +120,13 @@ func layout(g *gocui.Gui) error { // Screen borders if v, err := g.SetView("v9", -10, 20, 10, 24, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "Regular title" } if v, err := g.SetView("v10", maxX-10, 20, maxX+10, 24, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "Regular title" @@ -134,25 +134,25 @@ func layout(g *gocui.Gui) error { // Out of screen if v, err := g.SetView("v11", -21, 28, -1, 32, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "Regular title" } if v, err := g.SetView("v12", maxX, 28, maxX+20, 32, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "Regular title" } if v, err := g.SetView("v13", 10, -7, 30, -1, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "Regular title" } if v, err := g.SetView("v14", 10, maxY, 30, maxY+6, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Title = "Regular title" diff --git a/_examples/widgets.go b/_examples/widgets.go index dea49c24..a5b112e8 100644 --- a/_examples/widgets.go +++ b/_examples/widgets.go @@ -41,7 +41,7 @@ func NewHelpWidget(name string, x, y int, body string) *HelpWidget { func (w *HelpWidget) Layout(g *gocui.Gui) error { v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+w.h, 0) if err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } fmt.Fprint(v, w.body) @@ -74,7 +74,7 @@ func (w *StatusbarWidget) Val() float64 { func (w *StatusbarWidget) Layout(g *gocui.Gui) error { v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+2, 0) - if err != nil && err != gocui.ErrUnknownView { + if err != nil && err.Error() != "unknown view" { return err } v.Clear() @@ -99,7 +99,7 @@ func NewButtonWidget(name string, x, y int, label string, handler func(g *gocui. func (w *ButtonWidget) Layout(g *gocui.Gui) error { v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+2, 0) if err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } if _, err := g.SetCurrentView(w.name); err != nil { diff --git a/_examples/wrap.go b/_examples/wrap.go index 78a886ef..bb8b71da 100644 --- a/_examples/wrap.go +++ b/_examples/wrap.go @@ -15,7 +15,7 @@ import ( func layout(g *gocui.Gui) error { maxX, maxY := g.Size() if v, err := g.SetView("main", 1, 1, maxX-1, maxY-1, 0); err != nil { - if err != gocui.ErrUnknownView { + if err.Error() != "unknown view" { return err } v.Wrap = true From 8bdd3218f58ce9c92cc075ccc6cf65a464bcc88d Mon Sep 17 00:00:00 2001 From: mjarkk Date: Sun, 28 Apr 2019 13:15:38 +0200 Subject: [PATCH 37/73] The examples now don't error on exit --- _examples/active.go | 10 +++++----- _examples/bufs.go | 4 ++-- _examples/colors.go | 7 +++++-- _examples/colors256.go | 5 +++-- _examples/demo.go | 8 ++++---- _examples/dynamic.go | 6 +++--- _examples/flow_layout.go | 9 ++++++--- _examples/goroutine.go | 7 +++++-- _examples/hello.go | 14 ++++++++++---- _examples/layout.go | 18 +++++++++-------- _examples/mask.go | 6 +++--- _examples/mouse.go | 11 +++++++---- _examples/ontop.go | 11 +++++++---- _examples/overlap.go | 28 +++++++++++++++------------ _examples/size.go | 11 ++++++++--- _examples/stdin.go | 6 +++--- _examples/title.go | 42 ++++++++++++++++++++++------------------ _examples/widgets.go | 8 ++++---- _examples/wrap.go | 8 ++++++-- 19 files changed, 130 insertions(+), 89 deletions(-) diff --git a/_examples/active.go b/_examples/active.go index ac88635f..77ca11d9 100644 --- a/_examples/active.go +++ b/_examples/active.go @@ -50,7 +50,7 @@ func nextView(g *gocui.Gui, v *gocui.View) error { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() if v, err := g.SetView("v1", 0, 0, maxX/2-1, maxY/2-1, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "v1 (editable)" @@ -63,7 +63,7 @@ func layout(g *gocui.Gui) error { } if v, err := g.SetView("v2", maxX/2-1, 0, maxX-1, maxY/2-1, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "v2" @@ -71,7 +71,7 @@ func layout(g *gocui.Gui) error { v.Autoscroll = true } if v, err := g.SetView("v3", 0, maxY/2-1, maxX/2-1, maxY-1, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "v3" @@ -80,7 +80,7 @@ func layout(g *gocui.Gui) error { fmt.Fprint(v, "Press TAB to change current view") } if v, err := g.SetView("v4", maxX/2, maxY/2, maxX-1, maxY-1, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "v4 (editable)" @@ -113,7 +113,7 @@ 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) } } diff --git a/_examples/bufs.go b/_examples/bufs.go index d8853e12..ac45cb1b 100644 --- a/_examples/bufs.go +++ b/_examples/bufs.go @@ -29,7 +29,7 @@ func overwrite(g *gocui.Gui, v *gocui.View) error { func layout(g *gocui.Gui) error { _, maxY := g.Size() if v, err := g.SetView("main", 0, 0, 20, maxY-1, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Editable = true @@ -59,7 +59,7 @@ 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) } diff --git a/_examples/colors.go b/_examples/colors.go index 6b27d32a..62dc3835 100644 --- a/_examples/colors.go +++ b/_examples/colors.go @@ -24,7 +24,7 @@ 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) } } @@ -32,7 +32,7 @@ func main() { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() if v, err := g.SetView("colors", maxX/2-7, maxY/2-12, maxX/2+7, maxY/2+13, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } for i := 0; i <= 7; i++ { @@ -40,6 +40,9 @@ func layout(g *gocui.Gui) error { fmt.Fprintf(v, "Hello \033[3%d;%dmcolors!\033[0m\n", i, j) } } + if _, err := g.SetCurrentView("colors"); err != nil { + return err + } } return nil } diff --git a/_examples/colors256.go b/_examples/colors256.go index 94fa2c90..472ee4d7 100644 --- a/_examples/colors256.go +++ b/_examples/colors256.go @@ -25,7 +25,7 @@ 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) } } @@ -33,7 +33,7 @@ func main() { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() if v, err := g.SetView("colors", -1, -1, maxX, maxY, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } @@ -65,6 +65,7 @@ func layout(g *gocui.Gui) error { ctr++ } } + g.SetCurrentView("colors") } return nil } diff --git a/_examples/demo.go b/_examples/demo.go index 7108fc4a..1abc5eed 100644 --- a/_examples/demo.go +++ b/_examples/demo.go @@ -60,7 +60,7 @@ func getLine(g *gocui.Gui, v *gocui.View) error { maxX, maxY := g.Size() if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } fmt.Fprintln(v, l) @@ -160,7 +160,7 @@ func saveVisualMain(g *gocui.Gui, v *gocui.View) error { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() if v, err := g.SetView("side", -1, -1, 30, maxY, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Highlight = true @@ -173,7 +173,7 @@ func layout(g *gocui.Gui) error { fmt.Fprint(v, "deleted\rItem 4\nItem 5") } if v, err := g.SetView("main", 30, -1, maxX, maxY, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } b, err := ioutil.ReadFile("Mark.Twain-Tom.Sawyer.txt") @@ -205,7 +205,7 @@ 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) } } diff --git a/_examples/dynamic.go b/_examples/dynamic.go index e5d5e927..d73d1178 100644 --- a/_examples/dynamic.go +++ b/_examples/dynamic.go @@ -39,7 +39,7 @@ 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) } } @@ -48,7 +48,7 @@ func layout(g *gocui.Gui) error { maxX, _ := g.Size() v, err := g.SetView("help", maxX-25, 0, maxX-1, 9, 0) if err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } fmt.Fprintln(v, "KEYBINDINGS") @@ -134,7 +134,7 @@ func newView(g *gocui.Gui) error { name := fmt.Sprintf("v%v", idxView) v, err := g.SetView(name, maxX/2-5, maxY/2-5, maxX/2+5, maxY/2+5, 0) if err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Wrap = true diff --git a/_examples/flow_layout.go b/_examples/flow_layout.go index b683e1ac..bf71aa0b 100644 --- a/_examples/flow_layout.go +++ b/_examples/flow_layout.go @@ -36,10 +36,13 @@ func NewLabel(name string, body string) *Label { func (w *Label) Layout(g *gocui.Gui) error { v, err := g.SetView(w.name, 0, 0, w.w, w.h, 0) if err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } fmt.Fprint(v, w.body) + if _, err := g.SetCurrentView(w.name); err != nil { + return err + } } return nil } @@ -50,7 +53,7 @@ func flowLayout(g *gocui.Gui) error { for _, v := range views { w, h := v.Size() _, err := g.SetView(v.Name(), x, 0, x+w+1, h+1, 0) - if err != nil && err.Error() != "unknown view" { + if err != nil && !gocui.IsUnknownView(err) { return err } x += w + 2 @@ -77,7 +80,7 @@ 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) } } diff --git a/_examples/goroutine.go b/_examples/goroutine.go index df9ad511..f164e416 100644 --- a/_examples/goroutine.go +++ b/_examples/goroutine.go @@ -41,7 +41,7 @@ func main() { go counter(g) } - if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { + if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) { log.Panicln(err) } @@ -50,10 +50,13 @@ func main() { func layout(g *gocui.Gui) error { if v, err := g.SetView("ctr", 2, 2, 12, 4, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } fmt.Fprintln(v, "0") + if _, err := g.SetCurrentView("ctr"); err != nil { + return err + } } return nil } diff --git a/_examples/hello.go b/_examples/hello.go index 8679ba0d..8e30bbf8 100644 --- a/_examples/hello.go +++ b/_examples/hello.go @@ -24,19 +24,25 @@ func main() { log.Panicln(err) } - if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { - log.Panicln(err) + if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) { + log.Panicln(err.Error()) } } func layout(g *gocui.Gui) error { maxX, maxY := g.Size() if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { + return err + } + + if _, err := g.SetCurrentView("hello"); err != nil { return err } - fmt.Fprintln(v, "hello") + + fmt.Fprintln(v, "Hello world!") } + return nil } diff --git a/_examples/layout.go b/_examples/layout.go index 8d711955..da81aa8d 100644 --- a/_examples/layout.go +++ b/_examples/layout.go @@ -12,18 +12,20 @@ import ( func layout(g *gocui.Gui) error { maxX, maxY := g.Size() - if _, err := g.SetView("side", -1, -1, int(0.2*float32(maxX)), maxY-5, 0); err != nil && - err.Error() != "unknown view" { + if _, err := g.SetView("side", -1, -1, int(0.2*float32(maxX)), maxY-5, 0); err != nil && !gocui.IsUnknownView(err) { return err } - if _, err := g.SetView("main", int(0.2*float32(maxX)), -1, maxX, maxY-5, 0); err != nil && - err.Error() != "unknown view" { - return err + if _, err := g.SetView("main", int(0.2*float32(maxX)), -1, maxX, maxY-5, 0); err != nil { + if !gocui.IsUnknownView(err) { + return err + } + + g.SetCurrentView("main") } - if _, err := g.SetView("cmdline", -1, maxY-5, maxX, maxY, 0); err != nil && - err.Error() != "unknown view" { + if _, err := g.SetView("cmdline", -1, maxY-5, maxX, maxY, 0); err != nil && !gocui.IsUnknownView(err) { return err } + return nil } @@ -44,7 +46,7 @@ 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) } } diff --git a/_examples/mask.go b/_examples/mask.go index 378e483b..01a7a5f7 100644 --- a/_examples/mask.go +++ b/_examples/mask.go @@ -26,7 +26,7 @@ func main() { log.Fatalln(err) } - if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { + if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) { log.Fatalln(err) } } @@ -35,7 +35,7 @@ func layout(g *gocui.Gui) error { maxX, maxY := g.Size() if v, err := g.SetView("help", maxX-23, 0, maxX-1, 3, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "Keybindings" @@ -44,7 +44,7 @@ func layout(g *gocui.Gui) error { } if v, err := g.SetView("input", 0, 0, maxX-24, maxY-1, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } if _, err := g.SetCurrentView("input"); err != nil { diff --git a/_examples/mouse.go b/_examples/mouse.go index d0dd1b63..7226281d 100644 --- a/_examples/mouse.go +++ b/_examples/mouse.go @@ -27,14 +27,14 @@ 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 { if v, err := g.SetView("but1", 2, 2, 22, 7, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Highlight = true @@ -44,9 +44,12 @@ func layout(g *gocui.Gui) error { fmt.Fprintln(v, "Button 1 - line 2") fmt.Fprintln(v, "Button 1 - line 3") fmt.Fprintln(v, "Button 1 - line 4") + if _, err := g.SetCurrentView("but1"); err != nil { + return err + } } if v, err := g.SetView("but2", 24, 2, 44, 4, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Highlight = true @@ -91,7 +94,7 @@ func showMsg(g *gocui.Gui, v *gocui.View) error { maxX, maxY := g.Size() if v, err := g.SetView("msg", maxX/2-10, maxY/2, maxX/2+10, maxY/2+2, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } fmt.Fprintln(v, l) diff --git a/_examples/ontop.go b/_examples/ontop.go index 6c72a8e0..3f000208 100644 --- a/_examples/ontop.go +++ b/_examples/ontop.go @@ -24,29 +24,32 @@ 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 { if v, err := g.SetView("v1", 10, 2, 30, 6, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } fmt.Fprintln(v, "View #1") } if v, err := g.SetView("v2", 20, 4, 40, 8, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } fmt.Fprintln(v, "View #2") } if v, err := g.SetView("v3", 30, 6, 50, 10, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } fmt.Fprintln(v, "View #3") + if _, err := g.SetCurrentView("v3"); err != nil { + return err + } } return nil diff --git a/_examples/overlap.go b/_examples/overlap.go index 5306dfb6..e6dc672f 100644 --- a/_examples/overlap.go +++ b/_examples/overlap.go @@ -13,40 +13,44 @@ import ( func layout(g *gocui.Gui) error { maxX, maxY := g.Size() if _, err := g.SetView("v1", -1, -1, 10, 10, 0); err != nil && - err.Error() != "unknown view" { + !gocui.IsUnknownView(err) { return err } if _, err := g.SetView("v2", maxX-10, -1, maxX, 10, 0); err != nil && - err.Error() != "unknown view" { + !gocui.IsUnknownView(err) { return err } if _, err := g.SetView("v3", maxX/2-5, -1, maxX/2+5, 10, 0); err != nil && - err.Error() != "unknown view" { + !gocui.IsUnknownView(err) { return err } if _, err := g.SetView("v4", -1, maxY/2-5, 10, maxY/2+5, 0); err != nil && - err.Error() != "unknown view" { + !gocui.IsUnknownView(err) { return err } if _, err := g.SetView("v5", maxX-10, maxY/2-5, maxX, maxY/2+5, 0); err != nil && - err.Error() != "unknown view" { + !gocui.IsUnknownView(err) { return err } if _, err := g.SetView("v6", -1, maxY-10, 10, maxY, 0); err != nil && - err.Error() != "unknown view" { + !gocui.IsUnknownView(err) { return err } if _, err := g.SetView("v7", maxX-10, maxY-10, maxX, maxY, 0); err != nil && - err.Error() != "unknown view" { + !gocui.IsUnknownView(err) { return err } if _, err := g.SetView("v8", maxX/2-5, maxY-10, maxX/2+5, maxY, 0); err != nil && - err.Error() != "unknown view" { + !gocui.IsUnknownView(err) { return err } - if _, err := g.SetView("v9", maxX/2-5, maxY/2-5, maxX/2+5, maxY/2+5, 0); err != nil && - err.Error() != "unknown view" { - return err + if _, err := g.SetView("v9", maxX/2-5, maxY/2-5, maxX/2+5, maxY/2+5, 0); err != nil { + if !gocui.IsUnknownView(err) { + return err + } + if _, err := g.SetCurrentView("v9"); err != nil { + return err + } } return nil } @@ -68,7 +72,7 @@ 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) } } diff --git a/_examples/size.go b/_examples/size.go index 104f37fb..baeec0a5 100644 --- a/_examples/size.go +++ b/_examples/size.go @@ -24,7 +24,7 @@ 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) } } @@ -32,8 +32,13 @@ func main() { func layout(g *gocui.Gui) error { maxX, maxY := g.Size() v, err := g.SetView("size", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2, 0) - if err != nil && err.Error() != "unknown view" { - return err + if err != nil { + if !gocui.IsUnknownView(err) { + return err + } + if _, err := g.SetCurrentView("size"); err != nil { + return err + } } v.Clear() fmt.Fprintf(v, "%d, %d", maxX, maxY) diff --git a/_examples/stdin.go b/_examples/stdin.go index e733c81a..b15b7905 100644 --- a/_examples/stdin.go +++ b/_examples/stdin.go @@ -29,7 +29,7 @@ func main() { log.Fatalln(err) } - if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { + if err := g.MainLoop(); err != nil && !gocui.IsQuit(err) { log.Fatalln(err) } } @@ -38,7 +38,7 @@ func layout(g *gocui.Gui) error { maxX, _ := g.Size() if v, err := g.SetView("help", maxX-23, 0, maxX-1, 5, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } fmt.Fprintln(v, "KEYBINDINGS") @@ -48,7 +48,7 @@ func layout(g *gocui.Gui) error { } if v, err := g.SetView("stdin", 0, 0, 80, 35, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } if _, err := g.SetCurrentView("stdin"); err != nil { diff --git a/_examples/title.go b/_examples/title.go index d5115a29..3080b010 100644 --- a/_examples/title.go +++ b/_examples/title.go @@ -23,7 +23,7 @@ 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) } } @@ -37,13 +37,13 @@ func layout(g *gocui.Gui) error { // Overlap (front) if v, err := g.SetView("v1", 10, 2, 30, 6, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "Regular title" } if v, err := g.SetView("v2", 20, 4, 40, 8, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "Regular title" @@ -51,13 +51,13 @@ func layout(g *gocui.Gui) error { // Overlap (back) if v, err := g.SetView("v3", 60, 4, 80, 8, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "Regular title" } if v, err := g.SetView("v4", 50, 2, 70, 6, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "Regular title" @@ -65,25 +65,25 @@ func layout(g *gocui.Gui) error { // Overlap (frame) if v, err := g.SetView("v15", 90, 2, 110, 5, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "Regular title" } if v, err := g.SetView("v16", 100, 5, 120, 8, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "Regular title" } if v, err := g.SetView("v17", 140, 5, 160, 8, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "Regular title" } if v, err := g.SetView("v18", 130, 2, 150, 5, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "Regular title" @@ -91,7 +91,7 @@ func layout(g *gocui.Gui) error { // Long title if v, err := g.SetView("v5", 10, 12, 30, 16, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "Long long long long title" @@ -99,20 +99,20 @@ func layout(g *gocui.Gui) error { // No title if v, err := g.SetView("v6", 35, 12, 55, 16, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "" } if _, err := g.SetView("v7", 60, 12, 80, 16, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } } // Small view if v, err := g.SetView("v8", 85, 12, 88, 16, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "Regular title" @@ -120,13 +120,13 @@ func layout(g *gocui.Gui) error { // Screen borders if v, err := g.SetView("v9", -10, 20, 10, 24, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "Regular title" } if v, err := g.SetView("v10", maxX-10, 20, maxX+10, 24, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "Regular title" @@ -134,28 +134,32 @@ func layout(g *gocui.Gui) error { // Out of screen if v, err := g.SetView("v11", -21, 28, -1, 32, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "Regular title" } if v, err := g.SetView("v12", maxX, 28, maxX+20, 32, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "Regular title" } if v, err := g.SetView("v13", 10, -7, 30, -1, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "Regular title" } if v, err := g.SetView("v14", 10, maxY, 30, maxY+6, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Title = "Regular title" + + if _, err := g.SetCurrentView("v14"); err != nil { + return err + } } return nil diff --git a/_examples/widgets.go b/_examples/widgets.go index a5b112e8..331bfc28 100644 --- a/_examples/widgets.go +++ b/_examples/widgets.go @@ -41,7 +41,7 @@ func NewHelpWidget(name string, x, y int, body string) *HelpWidget { func (w *HelpWidget) Layout(g *gocui.Gui) error { v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+w.h, 0) if err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } fmt.Fprint(v, w.body) @@ -74,7 +74,7 @@ func (w *StatusbarWidget) Val() float64 { func (w *StatusbarWidget) Layout(g *gocui.Gui) error { v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+2, 0) - if err != nil && err.Error() != "unknown view" { + if err != nil && !gocui.IsUnknownView(err) { return err } v.Clear() @@ -99,7 +99,7 @@ func NewButtonWidget(name string, x, y int, label string, handler func(g *gocui. func (w *ButtonWidget) Layout(g *gocui.Gui) error { v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+2, 0) if err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } if _, err := g.SetCurrentView(w.name); err != nil { @@ -136,7 +136,7 @@ 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) } } diff --git a/_examples/wrap.go b/_examples/wrap.go index bb8b71da..1f86e663 100644 --- a/_examples/wrap.go +++ b/_examples/wrap.go @@ -15,7 +15,7 @@ import ( func layout(g *gocui.Gui) error { maxX, maxY := g.Size() if v, err := g.SetView("main", 1, 1, maxX-1, maxY-1, 0); err != nil { - if err.Error() != "unknown view" { + if !gocui.IsUnknownView(err) { return err } v.Wrap = true @@ -23,6 +23,10 @@ func layout(g *gocui.Gui) error { line := strings.Repeat("This is a long line -- ", 10) fmt.Fprintf(v, "%s\n\n", line) fmt.Fprintln(v, "Short") + + if _, err := g.SetCurrentView("main"); err != nil { + return err + } } return nil } @@ -44,7 +48,7 @@ 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) } } From 76fa8778eb1888377a7881f379e3d74ff33c490f Mon Sep 17 00:00:00 2001 From: mjarkk Date: Sun, 28 Apr 2019 14:14:03 +0200 Subject: [PATCH 38/73] The examples now don't error on exit --- README.md | 4 ++-- _examples/colors256.go | 4 +++- doc.go | 4 ++-- gui.go | 16 +++++++++++++--- keybinding.go | 2 +- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d497a875..51cc49f4 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ 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) } } @@ -68,7 +68,7 @@ func main() { 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 !gocui.IsUnknownView(err) { return err } fmt.Fprintln(v, "Hello world!") diff --git a/_examples/colors256.go b/_examples/colors256.go index 472ee4d7..245b7f7e 100644 --- a/_examples/colors256.go +++ b/_examples/colors256.go @@ -65,7 +65,9 @@ func layout(g *gocui.Gui) error { ctr++ } } - g.SetCurrentView("colors") + if _, err := g.SetCurrentView("colors"); err != nil { + return err + } } return nil } diff --git a/doc.go b/doc.go index fe128afb..2e022a2f 100644 --- a/doc.go +++ b/doc.go @@ -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") diff --git a/gui.go b/gui.go index 8c91c3d1..04aa1a12 100644 --- a/gui.go +++ b/gui.go @@ -13,6 +13,9 @@ import ( "github.com/awesome-gocui/termbox-go" ) +// OutputMode represents the terminal's output mode (8 or 256 colors). +type OutputMode termbox.OutputMode + var ( // ErrQuit is used to decide if the MainLoop finished successfully. ErrQuit = standardErrors.New("quit") @@ -21,9 +24,6 @@ var ( ErrUnknownView = standardErrors.New("unknown view") ) -// OutputMode represents the terminal's output mode (8 or 256 colors). -type OutputMode termbox.OutputMode - const ( // OutputNormal provides 8-colors terminal mode. OutputNormal = OutputMode(termbox.OutputNormal) @@ -744,3 +744,13 @@ func (g *Gui) loaderTick() { } }() } + +// IsUnknownView return true if the contents of an error is "unknown view" +func IsUnknownView(err error) bool { + return err.Error() == "unknown view" +} + +// IsQuit return true if the contents of an error is "quit" +func IsQuit(err error) bool { + return err.Error() == "quit" +} diff --git a/keybinding.go b/keybinding.go index 2c11bda3..e4e23403 100644 --- a/keybinding.go +++ b/keybinding.go @@ -38,7 +38,7 @@ func (kb *keybinding) matchView(v *View) bool { if v.Editable == true && kb.ch != 0 { return false } - return v != nil && kb.viewName == v.name + return kb.viewName == v.name } // Key represents special keys or keys combinations. From 059fa9891de3bce6fe81d00905a457507044bde1 Mon Sep 17 00:00:00 2001 From: mjarkk Date: Sun, 28 Apr 2019 17:00:11 +0200 Subject: [PATCH 39/73] Made the gobot happy --- README.md | 4 ++++ _examples/stdin.go | 11 +++++++---- gui.go | 2 +- keybinding.go | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 51cc49f4..eb8606fc 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ Or visit [godoc.org](https://godoc.org/github.com/awesome-gocui/gocui) to read i online. ## Example +See the [_example](./_example/) folder for more examples ```go package main @@ -72,6 +73,9 @@ func layout(g *gocui.Gui) error { return err } fmt.Fprintln(v, "Hello world!") + if _, err := g.SetCurrentView("hello"); err != nil { + return err + } } return nil } diff --git a/_examples/stdin.go b/_examples/stdin.go index b15b7905..17a900c4 100644 --- a/_examples/stdin.go +++ b/_examples/stdin.go @@ -51,14 +51,17 @@ func layout(g *gocui.Gui) error { if !gocui.IsUnknownView(err) { return err } - if _, err := g.SetCurrentView("stdin"); err != nil { + v.Wrap = true + + // TODO: Fix the program breaking at this point + // If this line is uncommented the program runs as it is suposed to do althow with no functionality + if _, err := io.Copy(hex.Dumper(v), os.Stdin); err != nil { return err } - dumper := hex.Dumper(v) - if _, err := io.Copy(dumper, os.Stdin); err != nil { + + if _, err := g.SetCurrentView("stdin"); err != nil { return err } - v.Wrap = true } return nil diff --git a/gui.go b/gui.go index 04aa1a12..426fbd98 100644 --- a/gui.go +++ b/gui.go @@ -93,7 +93,7 @@ func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) { g.outputMode = mode termbox.SetOutputMode(termbox.OutputMode(mode)) - g.stop = make(chan struct{}, 0) + g.stop = make(chan struct{}) g.tbEvents = make(chan termbox.Event, 20) g.userEvents = make(chan userEvent, 20) diff --git a/keybinding.go b/keybinding.go index e4e23403..2f9082ab 100644 --- a/keybinding.go +++ b/keybinding.go @@ -35,7 +35,7 @@ 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 the user is typing in a field, ignore char keys - if v.Editable == true && kb.ch != 0 { + if v.Editable && kb.ch != 0 { return false } return kb.viewName == v.name From 9a62783b54db14bb3790fd6a34d5a510ccb9bbe9 Mon Sep 17 00:00:00 2001 From: mjarkk Date: Sun, 28 Apr 2019 17:29:44 +0200 Subject: [PATCH 40/73] Added note at the top of the stdin example --- _examples/stdin.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_examples/stdin.go b/_examples/stdin.go index 17a900c4..c0236e1c 100644 --- a/_examples/stdin.go +++ b/_examples/stdin.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// This example doesn't work when running `go run stdin.go`, you are suposed to pipe someting to this like: `/bin/ls | go run stdin.go` + package main import ( @@ -53,8 +55,6 @@ func layout(g *gocui.Gui) error { } v.Wrap = true - // TODO: Fix the program breaking at this point - // If this line is uncommented the program runs as it is suposed to do althow with no functionality if _, err := io.Copy(hex.Dumper(v), os.Stdin); err != nil { return err } From c62d9807b57cab557d9fce6fe4e1c5b952526bde Mon Sep 17 00:00:00 2001 From: mjarkk Date: Mon, 29 Apr 2019 07:46:52 +0200 Subject: [PATCH 41/73] Moved to loader to component Also changed the suggestion from skanehira --- gui.go | 18 ++---------------- loader.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ view.go | 30 ------------------------------ 3 files changed, 48 insertions(+), 46 deletions(-) create mode 100644 loader.go diff --git a/gui.go b/gui.go index 426fbd98..4be6119e 100644 --- a/gui.go +++ b/gui.go @@ -6,7 +6,6 @@ package gocui import ( standardErrors "errors" - "time" "github.com/go-errors/errors" @@ -732,25 +731,12 @@ func (g *Gui) execKeybinding(v *View, kb *keybinding) (bool, error) { return true, nil } -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 - } - } - } - }() -} - // IsUnknownView return true if the contents of an error is "unknown view" func IsUnknownView(err error) bool { - return err.Error() == "unknown view" + return err.Error() == ErrUnknownView.Error() } // IsQuit return true if the contents of an error is "quit" func IsQuit(err error) bool { - return err.Error() == "quit" + return err.Error() == ErrQuit.Error() } diff --git a/loader.go b/loader.go new file mode 100644 index 00000000..d6715ac6 --- /dev/null +++ b/loader.go @@ -0,0 +1,46 @@ +package gocui + +import "time" + +func (g *Gui) loaderTick() { + go func() { + for range time.Tick(time.Millisecond * 50) { + for _, view := range g.Views() { + if view.HasLoader { + g.userEvents <- userEvent{func(g *Gui) error { return nil }} + break + } + } + } + }() +} + +func (v *View) loaderLines() [][]cell { + duplicate := make([][]cell, len(v.lines)) + for i := range v.lines { + if i < len(v.lines)-1 { + duplicate[i] = make([]cell, len(v.lines[i])) + copy(duplicate[i], v.lines[i]) + } else { + duplicate[i] = make([]cell, len(v.lines[i])+2) + copy(duplicate[i], v.lines[i]) + duplicate[i][len(duplicate[i])-2] = cell{chr: ' '} + duplicate[i][len(duplicate[i])-1] = Loader() + } + } + + return duplicate +} + +// Loader can show a loading animation +func Loader() cell { + characters := "|/-\\" + now := time.Now() + nanos := now.UnixNano() + index := nanos / 50000000 % int64(len(characters)) + str := characters[index : index+1] + chr := []rune(str)[0] + return cell{ + chr: chr, + } +} diff --git a/view.go b/view.go index da680944..d01f19cc 100644 --- a/view.go +++ b/view.go @@ -8,7 +8,6 @@ import ( "bytes" "io" "strings" - "time" "github.com/go-errors/errors" @@ -574,32 +573,3 @@ func linesToString(lines [][]cell) string { return strings.Join(str, "\n") } - -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 -} - -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, - } -} From 8668cab243710fe412c008b6f77f0efca116b89b Mon Sep 17 00:00:00 2001 From: skanehira Date: Sun, 28 Apr 2019 21:24:26 +0900 Subject: [PATCH 42/73] Improve get terminal window size in the docker container #15 --- gui.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/gui.go b/gui.go index 4be6119e..913375fd 100644 --- a/gui.go +++ b/gui.go @@ -6,6 +6,10 @@ package gocui import ( standardErrors "errors" + "os" + "os/signal" + "syscall" + "unsafe" "github.com/go-errors/errors" @@ -83,7 +87,8 @@ type Gui struct { // NewGui returns a new Gui object with a given output mode. func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) { - if err := termbox.Init(); err != nil { + err := termbox.Init() + if err != nil { return nil, err } @@ -97,7 +102,10 @@ func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) { g.tbEvents = make(chan termbox.Event, 20) g.userEvents = make(chan userEvent, 20) - g.maxX, g.maxY = termbox.Size() + g.maxX, g.maxY, err = g.getTermSize() + if err != nil { + return nil, err + } g.BgColor, g.FgColor = ColorDefault, ColorDefault g.SelBgColor, g.SelFgColor = ColorDefault, ColorDefault @@ -740,3 +748,44 @@ func IsUnknownView(err error) bool { func IsQuit(err error) bool { return err.Error() == ErrQuit.Error() } + +func (g *Gui) getTermSize() (int, int, error) { + var sz struct { + rows uint16 + cols uint16 + } + + 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") + } + } + } +} From 5b7b2afd738782750a96f8d21b995bdf29d52dd6 Mon Sep 17 00:00:00 2001 From: skanehira Date: Mon, 29 Apr 2019 08:18:11 +0900 Subject: [PATCH 43/73] fixed some - Add documents for new function - Only inside the container to use original get terminal window function --- gui.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/gui.go b/gui.go index 913375fd..209686da 100644 --- a/gui.go +++ b/gui.go @@ -8,6 +8,7 @@ import ( standardErrors "errors" "os" "os/signal" + "runtime" "syscall" "unsafe" @@ -102,9 +103,13 @@ func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) { g.tbEvents = make(chan termbox.Event, 20) g.userEvents = make(chan userEvent, 20) - g.maxX, g.maxY, err = g.getTermSize() - if err != nil { - return nil, err + 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 @@ -749,7 +754,9 @@ func IsQuit(err error) bool { return err.Error() == ErrQuit.Error() } -func (g *Gui) getTermSize() (int, int, error) { +// 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 From fd0c7298bd1c94793ec7de8f7f94ed698def0402 Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Sun, 28 Apr 2019 01:15:16 +0200 Subject: [PATCH 44/73] Added go mod files Signed-off-by: Glenn Vriesman --- go.mod | 8 ++++++++ go.sum | 4 ++++ 2 files changed, 12 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..d080c172 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/glvr182/gocui + +go 1.12 + +require ( + github.com/mattn/go-runewidth v0.0.4 // indirect + github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..8288bd33 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +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= +github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e h1:Vbib8wJAaMEF9jusI/kMSYMr/LtRzM7+F9MJgt/nH8k= +github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= From ad0ed37e71fae1131b37e1a2dec253e17f9e9932 Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Mon, 29 Apr 2019 19:22:45 +0200 Subject: [PATCH 45/73] Updated go mod file --- go.mod | 5 +++-- go.sum | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index d080c172..7e621b63 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/glvr182/gocui go 1.12 require ( - github.com/mattn/go-runewidth v0.0.4 // indirect - github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e + github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc + github.com/go-errors/errors v1.0.1 + github.com/mattn/go-runewidth v0.0.4 ) diff --git a/go.sum b/go.sum index 8288bd33..25f1c037 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +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= -github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e h1:Vbib8wJAaMEF9jusI/kMSYMr/LtRzM7+F9MJgt/nH8k= -github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= From f0af9339e94ac26e56f8e75f898d63a1c44ab233 Mon Sep 17 00:00:00 2001 From: mjarkk Date: Sat, 18 May 2019 21:11:36 +0200 Subject: [PATCH 46/73] Added changes --- gui.go | 10 +++++++++- keybinding.go | 3 +++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/gui.go b/gui.go index 209686da..1b3d8d77 100644 --- a/gui.go +++ b/gui.go @@ -573,6 +573,14 @@ func corner(v *View, directions byte) rune { // 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 } @@ -726,7 +734,7 @@ func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err err if kb.matchView(v) { return g.execKeybinding(v, kb) } - if kb.viewName == "" && (!v.Editable || kb.ch == 0) { + if kb.viewName == "" && ((v != nil && !v.Editable) || kb.ch == 0) { globalKb = kb } } diff --git a/keybinding.go b/keybinding.go index 2f9082ab..d1e739cf 100644 --- a/keybinding.go +++ b/keybinding.go @@ -35,6 +35,9 @@ 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 the user is typing in a field, ignore char keys + if v == nil { + return false + } if v.Editable && kb.ch != 0 { return false } From 68c8f545361763d41ea0dc3be71e5b5140a84a37 Mon Sep 17 00:00:00 2001 From: mjarkk Date: Sat, 18 May 2019 22:29:40 +0200 Subject: [PATCH 47/73] Fixed a sugestion --- keybinding.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/keybinding.go b/keybinding.go index d1e739cf..fb8aff3d 100644 --- a/keybinding.go +++ b/keybinding.go @@ -35,10 +35,7 @@ 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 the user is typing in a field, ignore char keys - if v == nil { - return false - } - if v.Editable && kb.ch != 0 { + if v == nil || (v.Editable && kb.ch != 0) { return false } return kb.viewName == v.name From 3afa0009fab20b80914e9ad2dbb2c174e2c4a7e7 Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Tue, 28 May 2019 20:18:25 +0200 Subject: [PATCH 48/73] Fixed go mod Signed-off-by: Glenn Vriesman --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 7e621b63..5791b4e4 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/glvr182/gocui +module github.com/awesome-gocui/gocui go 1.12 From 8134275081d13a77c8663421d7704f8a09ccc6a5 Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Tue, 28 May 2019 20:18:42 +0200 Subject: [PATCH 49/73] Added circle-ci config Signed-off-by: Glenn Vriesman --- .circleci/config.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..89d5f0ad --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,34 @@ +version: 2 +jobs: + build: + working_directory: /go/src/github.com/glvr182/git-profile + + docker: + - image: circleci/golang:1.12 + + steps: + - checkout + - run: + name: go mod + command: go mod tidy + - run: + name: go fmt + command: | + if [ $(find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \;|wc -l) -gt 0 ]; then + find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \; + exit 1; + fi + - run: + name: go build + command: | + cd _examples/ + for file in *.go + do + go build $file + done + +workflows: + version: 2 + build: + jobs: + - build \ No newline at end of file From dc479abcf4b19bce49a8a9eb34d4cdf2b371b440 Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Tue, 28 May 2019 20:19:29 +0200 Subject: [PATCH 50/73] Added badges to readme * Circle-ci * Codecov * Go Report * GolangCI * Github tag Signed-off-by: Glenn Vriesman --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index eb8606fc..b1f54b88 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # GOCUI - Go Console User Interface - +[![CircleCI](https://circleci.com/gh/awesome-gocui/gocui/tree/master.svg?style=svg)](https://circleci.com/gh/awesome-gocui/gocui/tree/master) +[![CodeCov](https://codecov.io/gh/awesome-gocui/gocui/branch/master/graph/badge.svg)](https://codecov.io/gh/awesome-gocui/gocui) +[![Go Report Card](https://goreportcard.com/badge/github.com/awesome-gocui/gocui)](https://goreportcard.com/report/github.com/awesome-gocui/gocui) +[![GolangCI](https://golangci.com/badges/github.com/awesome-gocui/gocui.svg)](https://golangci.com/badges/github.com/awesome-gocui/gocui.svg) [![GoDoc](https://godoc.org/github.com/awesome-gocui/gocui?status.svg)](https://godoc.org/github.com/awesome-gocui/gocui) +![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/awesome-gocui/gocui.svg) Minimalist Go package aimed at creating Console User Interfaces. From 9646e38179314f219b836e32da0020da9faba657 Mon Sep 17 00:00:00 2001 From: Richard Dzenis Date: Mon, 24 Jun 2019 20:52:03 +0200 Subject: [PATCH 51/73] Merge branch 'community_merges' from RIscRIpt * Added SetLinee() convenience function. * Added SetLine() convenience function. * Added SetHighlight() for multi-line highlighting. * simpler shutdown logic * Support zero parameteter 'CSI n m' in parseOne According to https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_codes, a zero-parameter 'CSI n m' code (for setting SGR parameters) should be treated as the same as 'CSI 0 m'. This PR changes escapeInterpreter.parseOne to support this shorthand. * Add View.MaxLines. * Check for collisions in reverse view order * Didn't work with a single view - off by 1 error.. * adds framecolors * Adds visibility flag for views * Fix misleading comment * Make View visible by default * Add reading and writing offsets. Re-write Read and Write methods. * Call Rewind with Clear to reset offsets * Don't display NUL characters * Modify goroutine example to demonstrate new Read/Write methods * Add WriteRunes, WriteString methods to View * Add example: a table * Add Flush method to Gui * fix golangcibot warnings * change _examples/table.go import to awesome-gocui * fix goroutine example * fix table example * add explanation comment to goroutine example * fix view.draw bug introduced with wide char support * expose visible attribute in view * remove Flush function from gui --- _examples/goroutine.go | 15 ++- _examples/table.go | 98 ++++++++++++++ gui.go | 14 +- view.go | 298 ++++++++++++++++++++++++++++++++++------- 4 files changed, 367 insertions(+), 58 deletions(-) create mode 100644 _examples/table.go diff --git a/_examples/goroutine.go b/_examples/goroutine.go index f164e416..1c8214b6 100644 --- a/_examples/goroutine.go +++ b/_examples/goroutine.go @@ -13,7 +13,7 @@ import ( "github.com/awesome-gocui/gocui" ) -const NumGoroutines = 10 +const NumGoroutines = 20 var ( done = make(chan struct{}) @@ -49,11 +49,11 @@ func main() { } func layout(g *gocui.Gui) error { - if v, err := g.SetView("ctr", 2, 2, 12, 4, 0); err != nil { + if v, err := g.SetView("ctr", 2, 2, 22, 2+NumGoroutines+1, 0); err != nil { if !gocui.IsUnknownView(err) { return err } - fmt.Fprintln(v, "0") + v.Clear() if _, err := g.SetCurrentView("ctr"); err != nil { return err } @@ -91,7 +91,14 @@ func counter(g *gocui.Gui) { if err != nil { return err } - v.Clear() + // use ctr to make it more chaotic + // "pseudo-randomly" print in one of two columns (x = 0, and x = 10) + x := (ctr / NumGoroutines) & 1 + if x != 0 { + x = 10 + } + y := ctr % NumGoroutines + v.SetWritePos(x, y) fmt.Fprintln(v, n) return nil }) diff --git a/_examples/table.go b/_examples/table.go new file mode 100644 index 00000000..32a21c1d --- /dev/null +++ b/_examples/table.go @@ -0,0 +1,98 @@ +// Copyright 2017 The gocui Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "log" + + "github.com/awesome-gocui/gocui" +) + +type Column struct { + Title string + Size float32 +} + +type Table struct { + name string + Left, Top int + Right, Bottom int + Columns []Column + Data [][]string +} + +func NewTable(name string, left, top, right, bottom int) *Table { + return &Table{ + name: name, + Left: left, + Top: top, + Right: right, + Bottom: bottom, + } +} + +func (t *Table) Layout(g *gocui.Gui) error { + view, err := g.SetView(t.name, t.Left, t.Top, t.Right, t.Bottom, 0) + if err != nil && !gocui.IsUnknownView(err) { + return err + } + + width, height := view.Size() + hOffset := 0 + for cid, column := range t.Columns { + size := int(float32(width) * column.Size) + + view.SetWritePos(hOffset, 0) + view.WriteString(column.Title) + + for rid := 0; rid < height; rid++ { + if rid < len(t.Data[cid]) { + view.SetWritePos(hOffset, rid+1) + view.WriteString(t.Data[cid][rid]) + } + view.SetWritePos(hOffset+size-3, rid) + view.WriteRunes([]rune{'│'}) + } + + hOffset += size + } + + return nil +} + +func main() { + g, err := gocui.NewGui(gocui.OutputNormal, false) + if err != nil { + log.Panicln(err) + } + defer g.Close() + + table := NewTable("t", 1, 2, 80, 10) + table.Columns = []Column{ + Column{"Column1", 0.25}, + Column{"Column2", 0.25}, + Column{"Column3", 0.25}, + Column{"Column4", 0.25}, + } + table.Data = [][]string{ + []string{"00", "01", "02", "03"}, + []string{"10", "11", "12", "13"}, + []string{"20", "21", "22", "23"}, + []string{"30", "31", "32", "33"}, + } + g.SetManager(table) + + if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { + log.Panicln(err) + } + + if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { + log.Panicln(err) + } +} + +func quit(g *gocui.Gui, v *gocui.View) error { + return gocui.ErrQuit +} diff --git a/gui.go b/gui.go index 1b3d8d77..cd62717f 100644 --- a/gui.go +++ b/gui.go @@ -57,11 +57,11 @@ type Gui struct { // 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. @@ -483,23 +483,25 @@ func (g *Gui) flush() error { } } for _, v := range g.views { - if v.y1 < v.y0 { + 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 != "" { diff --git a/view.go b/view.go index d01f19cc..75389333 100644 --- a/view.go +++ b/view.go @@ -8,6 +8,7 @@ import ( "bytes" "io" "strings" + "unicode/utf8" "github.com/go-errors/errors" @@ -23,22 +24,33 @@ const ( 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 + 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 - readOffset int - readCache string - tainted bool // marks if the viewBuffer must be updated + readBuffer []byte // used for storing unreaded bytes + + tainted bool // true if the viewLines must be updated viewLines []viewLine // internal representation of the view's buffer ei *escapeInterpreter // used to decode ESC sequences on Write + // Visible specifies whether the view is visible. + Visible bool + // BgColor and FgColor allow to configure the background and foreground // colors of the View. BgColor, FgColor Attribute @@ -121,6 +133,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, @@ -150,7 +163,7 @@ 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 @@ -175,6 +188,11 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error { fgColor = fgColor | AttrBold } + // Don't display NUL characters + if ch == 0 { + ch = ' ' + } + termbox.SetCell(v.x0+x+1, v.y0+y+1, ch, termbox.Attribute(fgColor), termbox.Attribute(bgColor)) @@ -186,7 +204,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 @@ -205,7 +223,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 @@ -217,6 +235,91 @@ 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 @@ -224,32 +327,49 @@ func (v *View) Origin() (x, y int) { func (v *View) Write(p []byte) (n int, err error) { v.tainted = true - for _, ch := range bytes.Runes(p) { - switch ch { + // Fill with empty cells, if writing outside current view buffer + v.makeWriteable(v.wx, v.wy) + v.writeRunes(bytes.Runes(p)) + + 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 @@ -291,30 +411,62 @@ func (v *View) parseInput(ch rune) []cell { 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() - } - if v.readOffset < len(v.readCache) { - n = copy(p, v.readCache[v.readOffset:]) - v.readOffset += n - } else { - err = io.EOF + buffer := make([]byte, utf8.UTFMax) + offset := 0 + if v.readBuffer != nil { + copy(p, v.readBuffer) + if len(v.readBuffer) >= len(p) { + if len(v.readBuffer) > len(p) { + v.readBuffer = v.readBuffer[len(p):] + } + return len(p), nil + } + v.readBuffer = nil + } + for v.ry < len(v.lines) { + for v.rx < len(v.lines[v.ry]) { + count := utf8.EncodeRune(buffer, v.lines[v.ry][v.rx].chr) + copy(p[offset:], buffer[:count]) + v.rx++ + newOffset := offset + count + if newOffset >= len(p) { + if newOffset > len(p) { + v.readBuffer = buffer[newOffset-len(p):] + } + return len(p), nil + } + offset += count + } + v.rx = 0 + v.ry++ } - return + 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) + } } // draw re-draws the view's contents. func (v *View) draw() error { + if !v.Visible { + return nil + } + maxX, maxY := v.Size() if v.Wrap { @@ -378,7 +530,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++ } @@ -392,7 +552,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 { @@ -413,12 +573,12 @@ 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.Rewind() v.tainted = true - v.lines = nil v.viewLines = nil - v.readOffset = 0 v.clearRunes() } @@ -487,7 +647,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 @@ -502,7 +662,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() @@ -528,6 +688,48 @@ 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 > 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 > 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) From 188c59e998e2b8ce4591c8cfa3be6fabad4f39f3 Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Mon, 24 Jun 2019 21:03:17 +0200 Subject: [PATCH 52/73] Fixed formatting * Fixed formatting in _examples/table.go * Fixed formatting in view.go Signed-off-by: Glenn Vriesman --- _examples/table.go | 16 ++++++++-------- view.go | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/_examples/table.go b/_examples/table.go index 32a21c1d..95687366 100644 --- a/_examples/table.go +++ b/_examples/table.go @@ -71,16 +71,16 @@ func main() { table := NewTable("t", 1, 2, 80, 10) table.Columns = []Column{ - Column{"Column1", 0.25}, - Column{"Column2", 0.25}, - Column{"Column3", 0.25}, - Column{"Column4", 0.25}, + {"Column1", 0.25}, + {"Column2", 0.25}, + {"Column3", 0.25}, + {"Column4", 0.25}, } table.Data = [][]string{ - []string{"00", "01", "02", "03"}, - []string{"10", "11", "12", "13"}, - []string{"20", "21", "22", "23"}, - []string{"30", "31", "32", "33"}, + {"00", "01", "02", "03"}, + {"10", "11", "12", "13"}, + {"20", "21", "22", "23"}, + {"30", "31", "32", "33"}, } g.SetManager(table) diff --git a/view.go b/view.go index 75389333..de4626c3 100644 --- a/view.go +++ b/view.go @@ -49,7 +49,7 @@ type View struct { ei *escapeInterpreter // used to decode ESC sequences on Write // Visible specifies whether the view is visible. - Visible bool + Visible bool // BgColor and FgColor allow to configure the background and foreground // colors of the View. From 17da2ee2ac873a65b4faf275c0ffa68958a1b7f6 Mon Sep 17 00:00:00 2001 From: mjarkk Date: Tue, 25 Jun 2019 10:22:28 +0200 Subject: [PATCH 53/73] Added some information about this fork --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b1f54b88..357fd3fd 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ [![GoDoc](https://godoc.org/github.com/awesome-gocui/gocui?status.svg)](https://godoc.org/github.com/awesome-gocui/gocui) ![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/awesome-gocui/gocui.svg) -Minimalist Go package aimed at creating Console User Interfaces. +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 @@ -20,6 +21,20 @@ Minimalist Go package aimed at creating Console User Interfaces. * Customizable edition mode. * Easy to build reusable widgets, complex layouts... +## About fork + +This fork has meany 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 +* Meany small improvements +* Change Visibility of views + +For information about this org see: [awesome-gocui/about](https://github.com/awesome-gocui/about). + ## Installation Execute: From eabbdda83fc2938b00350afa320af8054e4384cf Mon Sep 17 00:00:00 2001 From: Justin Clift Date: Sun, 30 Jun 2019 22:09:52 +1000 Subject: [PATCH 54/73] Trivial typo fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 357fd3fd..7b759b25 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ This fork has meany improvements over the original work from [jroimartin](https: * Better support for running in docker container * Customize frame colors * Improved code comments and quality -* Meany small improvements +* Many small improvements * Change Visibility of views For information about this org see: [awesome-gocui/about](https://github.com/awesome-gocui/about). From 3b583fb03ef187c73c35767ce2bc4fcfe014fc37 Mon Sep 17 00:00:00 2001 From: Sawood Alam Date: Mon, 1 Jul 2019 22:27:07 -0400 Subject: [PATCH 55/73] Change edition mode to editing mode in documentation --- README.md | 2 +- doc.go | 2 +- view.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d7b55a3b..a6ceb093 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ 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... ## Installation diff --git a/doc.go b/doc.go index fe128afb..74143bd8 100644 --- a/doc.go +++ b/doc.go @@ -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 { diff --git a/view.go b/view.go index 42082f8c..ccadb6e0 100644 --- a/view.go +++ b/view.go @@ -41,7 +41,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 From c6f7a7b6e37d843436d9c0455d4d05a51a492bd0 Mon Sep 17 00:00:00 2001 From: Jevgenijs Levconoks Date: Fri, 12 Jul 2019 15:46:30 +0100 Subject: [PATCH 56/73] Fixed readme code example --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7b759b25..cc3c17e5 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ import ( ) func main() { - g, err := gocui.NewGui(gocui.OutputNormal) + g, err := gocui.NewGui(gocui.OutputNormal, false) if err != nil { log.Panicln(err) } @@ -87,7 +87,7 @@ func main() { 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 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 } From ff771a76f6cd783276449f59234152072c7101b8 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Wed, 14 Aug 2019 00:46:21 +0900 Subject: [PATCH 57/73] Fix build on Windows --- gui.go | 47 ---------------------------------------- gui_others.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ gui_windows.go | 54 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 47 deletions(-) create mode 100644 gui_others.go create mode 100644 gui_windows.go diff --git a/gui.go b/gui.go index cd62717f..df5aecc6 100644 --- a/gui.go +++ b/gui.go @@ -6,11 +6,7 @@ package gocui import ( standardErrors "errors" - "os" - "os/signal" "runtime" - "syscall" - "unsafe" "github.com/go-errors/errors" @@ -763,46 +759,3 @@ func IsUnknownView(err error) bool { func IsQuit(err error) bool { return err.Error() == ErrQuit.Error() } - -// 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 - } - - var termw, termh int - - out, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) - if err != nil { - return 0, 0, err - } - defer out.Close() - - signalCh := make(chan os.Signal, 1) - signal.Notify(signalCh, syscall.SIGWINCH, syscall.SIGINT) - - for { - _, _, _ = syscall.Syscall(syscall.SYS_IOCTL, - out.Fd(), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz))) - - // check terminal window size - termw, termh = int(sz.cols), int(sz.rows) - if termw > 0 && termh > 0 { - return termw, termh, nil - } - - select { - case signal := <-signalCh: - switch signal { - // when the terminal window size is changed - case syscall.SIGWINCH: - continue - // ctrl + c to cancel - case syscall.SIGINT: - return 0, 0, errors.New("stop to get term window size") - } - } - } -} diff --git a/gui_others.go b/gui_others.go new file mode 100644 index 00000000..0ea1b2b9 --- /dev/null +++ b/gui_others.go @@ -0,0 +1,59 @@ +// 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 + } + + var termw, termh int + + out, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) + if err != nil { + return 0, 0, err + } + defer out.Close() + + signalCh := make(chan os.Signal, 1) + signal.Notify(signalCh, syscall.SIGWINCH, syscall.SIGINT) + + for { + _, _, _ = syscall.Syscall(syscall.SYS_IOCTL, + out.Fd(), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz))) + + // check terminal window size + termw, termh = int(sz.cols), int(sz.rows) + if termw > 0 && termh > 0 { + return termw, termh, nil + } + + select { + case signal := <-signalCh: + switch signal { + // when the terminal window size is changed + case syscall.SIGWINCH: + continue + // ctrl + c to cancel + case syscall.SIGINT: + return 0, 0, errors.New("stop to get term window size") + } + } + } +} diff --git a/gui_windows.go b/gui_windows.go new file mode 100644 index 00000000..05998aa6 --- /dev/null +++ b/gui_windows.go @@ -0,0 +1,54 @@ +// 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 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 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 +} From a0714e178a9dc80827060b267f2b07bda9cdb846 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Wed, 14 Aug 2019 09:59:42 +0900 Subject: [PATCH 58/73] Fix comment --- gui_windows.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gui_windows.go b/gui_windows.go index 05998aa6..db1faab7 100644 --- a/gui_windows.go +++ b/gui_windows.go @@ -42,8 +42,7 @@ var ( procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") ) -// 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. +// 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))) From bc0690d33b32f62a92648bec880a253cf1e1ae6a Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Mon, 26 Aug 2019 10:31:17 -0700 Subject: [PATCH 59/73] Make IsUnknownView(nil) and IsQuit(nil) return false This changes matches the general semantics of the methods and can simplify caller code. --- gui.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui.go b/gui.go index df5aecc6..1f992c37 100644 --- a/gui.go +++ b/gui.go @@ -752,10 +752,10 @@ func (g *Gui) execKeybinding(v *View, kb *keybinding) (bool, error) { // IsUnknownView return true if the contents of an error is "unknown view" func IsUnknownView(err error) bool { - return err.Error() == ErrUnknownView.Error() + return err != nil && err.Error() == ErrUnknownView.Error() } // IsQuit return true if the contents of an error is "quit" func IsQuit(err error) bool { - return err.Error() == ErrQuit.Error() + return err != nil && err.Error() == ErrQuit.Error() } From 4deda6be20abc8d152b7b3e21a14fbcbd8e734be Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Mon, 26 Aug 2019 10:32:58 -0700 Subject: [PATCH 60/73] Improve docs Fix a typo in the README Use "reports whether", as is common in many Go docs for functions returning a bool. --- README.md | 2 +- gui.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f2aa91af..a0d4cc6f 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ A community fork based on the amazing work of [jroimartin](https://github.com/jr ## About fork -This fork has meany improvements over the original work from [jroimartin](https://github.com/jroimartin/gocui). +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 diff --git a/gui.go b/gui.go index 1f992c37..73a0bd8c 100644 --- a/gui.go +++ b/gui.go @@ -750,12 +750,12 @@ func (g *Gui) execKeybinding(v *View, kb *keybinding) (bool, error) { return true, nil } -// IsUnknownView return true if the contents of an error is "unknown view" +// IsUnknownView reports whether the contents of an error is "unknown view". func IsUnknownView(err error) bool { return err != nil && err.Error() == ErrUnknownView.Error() } -// IsQuit return true if the contents of an error is "quit" +// IsQuit reports whether the contents of an error is "quit". func IsQuit(err error) bool { return err != nil && err.Error() == ErrQuit.Error() } From 95f3736920cf078fbdfc3c63ae87edf4bef3ebd3 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Mon, 26 Aug 2019 10:34:16 -0700 Subject: [PATCH 61/73] Add lazygit to README It's not my project, but since its own README prominently mentions that it uses gocui, it seems reasonable to backlink here. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a0d4cc6f..7c626238 100644 --- a/README.md +++ b/README.md @@ -129,5 +129,6 @@ 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. Note: if your project is not listed here, let us know! :) From 419455987cc68ab83101a3017c9b7c94525075c3 Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Mon, 26 Aug 2019 21:03:25 +0200 Subject: [PATCH 62/73] Add lazydocker to readme (#35) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7c626238..be212c58 100644 --- a/README.md +++ b/README.md @@ -130,5 +130,6 @@ func quit(g *gocui.Gui, v *gocui.View) error { * [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! :) From 0ad6ed361e6fac6618a57da7295c2a2f090e629c Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Mon, 26 Aug 2019 12:58:37 -0700 Subject: [PATCH 63/73] Pad syscall correctly in getTermWindowSize Fixes #33 --- gui_others.go | 1 + 1 file changed, 1 insertion(+) diff --git a/gui_others.go b/gui_others.go index 0ea1b2b9..5d247a19 100644 --- a/gui_others.go +++ b/gui_others.go @@ -21,6 +21,7 @@ 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 From 036856b4ece141c30af471bc50a470a47012d76f Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Fri, 6 Sep 2019 09:57:46 -0700 Subject: [PATCH 64/73] Fix manual bounds checks in View.SetLine The lower bound check was missing; the higher bound check was off by one. --- view.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view.go b/view.go index dd452c32..52cb346a 100644 --- a/view.go +++ b/view.go @@ -690,7 +690,7 @@ func indexFunc(r rune) bool { // SetLine changes the contents of an existing line. func (v *View) SetLine(y int, text string) error { - if y > len(v.lines) { + if y < 0 || y >= len(v.lines) { err := ErrInvalidPoint return err } From 210b6ef83220862a91f87413f710f35cd04db2d5 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Mon, 9 Sep 2019 09:10:03 -0700 Subject: [PATCH 65/73] Fix another manual bounds check Similar to 036856b4ece141c30af471bc50a470a47012d76f (#38). --- view.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view.go b/view.go index 52cb346a..d0e357e5 100644 --- a/view.go +++ b/view.go @@ -708,7 +708,7 @@ func (v *View) SetLine(y int, text string) error { // 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 > len(v.lines) { + if y < 0 || y >= len(v.lines) { err := ErrInvalidPoint return err } From cf624e38820f4fc071c02ea6616c884d74389571 Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Mon, 9 Sep 2019 18:59:50 +0200 Subject: [PATCH 66/73] View: Use mutex lock when writing * This prevents out-of-bounds errors panics See https://github.com/jesseduffield/gocui/commit/3cd3a4170288d55bfd3c32293325a72c16e56dc9 Co-authored-by: Jesse Duffield Signed-off-by: Glenn Vriesman --- view.go | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/view.go b/view.go index d0e357e5..b88b70d7 100644 --- a/view.go +++ b/view.go @@ -8,6 +8,7 @@ import ( "bytes" "io" "strings" + "sync" "unicode/utf8" "github.com/go-errors/errors" @@ -34,19 +35,27 @@ var ( // position. type View struct { name string - x0, y0, x1, y1 int // left top right bottom - ox, oy int // view offsets - cx, cy int // cursor position - rx, ry int // Read() offsets - wx, wy int // Write() offsets - lines [][]cell + x0, y0, x1, y1 int // left top right bottom + ox, oy int // view offsets + cx, cy int // cursor position + rx, ry int // Read() offsets + wx, wy int // Write() offsets + lines [][]cell // All the data - readBuffer []byte // used for storing unreaded bytes + // readBuffer is used for storing unread bytes + readBuffer []byte - tainted bool // true if the viewLines must be updated - viewLines []viewLine // internal representation of the view's buffer + // tained is true if the viewLines must be updated + tainted bool - ei *escapeInterpreter // used to decode ESC sequences on Write + // 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 @@ -326,10 +335,10 @@ func (v *View) writeCells(x, y int, cells []cell) { // be called to clear the view's buffer. func (v *View) Write(p []byte) (n int, err error) { v.tainted = true - - // Fill with empty cells, if writing outside current view buffer + v.writeMutex.Lock() v.makeWriteable(v.wx, v.wy) v.writeRunes(bytes.Runes(p)) + v.writeMutex.Unlock() return len(p), nil } From e40d63c6f9c1209bcfd6a9768483364d9ad036d0 Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Mon, 9 Sep 2019 19:00:13 +0200 Subject: [PATCH 67/73] View: Use mutex when clearing * This is just to be safe See https://github.com/jesseduffield/gocui/commit/efab7b041c46d3ae956e6b89046087b65f2e1e01 Co-authored-by: Jesse Duffield Signed-off-by: Glenn Vriesman --- view.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/view.go b/view.go index b88b70d7..9c7eb3eb 100644 --- a/view.go +++ b/view.go @@ -584,11 +584,14 @@ 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.clearRunes() + v.writeMutex.Unlock() } // clearRunes erases all the cells in the view. From da1361c0a7a586792734c928489b4953b74465f6 Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Mon, 9 Sep 2019 19:29:20 +0200 Subject: [PATCH 68/73] View: Added ViewLinesHeight() * This might be usefull for people who want to check that See https://github.com/jesseduffield/gocui/commit/e030e03faca4edb742120c0b739cb2114a4b3358 Co-authored-by: Jesse Duffield Signed-off-by: Glenn Vriesman --- view.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/view.go b/view.go index 9c7eb3eb..9257e010 100644 --- a/view.go +++ b/view.go @@ -635,10 +635,16 @@ 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 { From 8c371336b50d299f0bdebf1e62b16c435a62ebc4 Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Mon, 9 Sep 2019 19:32:55 +0200 Subject: [PATCH 69/73] View: Added IsTained() * This might be usefull for people who want to check that See https://github.com/jesseduffield/gocui/commit/77d76180000321e6c543d0879c36016a15537682 Co-authored-by: Jesse Duffield Signed-off-by: Glenn Vriesman --- view.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/view.go b/view.go index 9257e010..81f90603 100644 --- a/view.go +++ b/view.go @@ -470,6 +470,11 @@ func (v *View) Rewind() { } } +// 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 { From 6dc74ecc12d0b61f0d940777ed4644bb31d7de25 Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Mon, 9 Sep 2019 19:38:37 +0200 Subject: [PATCH 70/73] Edit: Added editor functions * Added a handler for tab * Added a handler for space * Added a handler for Insert * Added a handler for Ctrl+U * Added a handler for Ctrl+A * Added a handler for Ctrl+E * Defaults to EditWrite() See https://github.com/jesseduffield/gocui/commit/77d76180000321e6c543d0879c36016a15537682 Co-authored-by: Jesse Duffield Signed-off-by: Glenn Vriesman --- edit.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ gui.go | 8 +++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/edit.go b/edit.go index b99f74f9..62ac19fb 100644 --- a/edit.go +++ b/edit.go @@ -53,6 +53,20 @@ 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.EditNewLine() + case key == KeySpace: + v.EditWrite(' ') + case key == KeyInsert: + v.Overwrite = !v.Overwrite + case key == KeyCtrlU: + v.EditDeleteToStartOfLine() + case key == KeyCtrlA: + v.EditGotoToStartOfLine() + case key == KeyCtrlE: + v.EditGotoToEndOfLine() + default: + v.EditWrite(ch) } } @@ -63,6 +77,48 @@ func (v *View) EditWrite(ch rune) { v.moveCursor(w, 0, true) } +// EditDeleteToStartOfLine is the equivalent of pressing ctrl+U in your terminal, it deletes to the end of the line. Or if you are already at the start of the line, it deletes the newline character +func (v *View) EditDeleteToStartOfLine() { + x, _ := v.Cursor() + if x == 0 { + v.EditDelete(true) + } else { + // delete characters until we are the start of the line + for x > 0 { + v.EditDelete(true) + x, _ = v.Cursor() + } + } +} + +// EditGotoToStartOfLine takes you to the start of the current line +func (v *View) EditGotoToStartOfLine() { + x, _ := v.Cursor() + for x > 0 { + v.MoveCursor(-1, 0, false) + x, _ = v.Cursor() + } +} + +// EditGotoToEndOfLine takes you to the end of the line +func (v *View) EditGotoToEndOfLine() { + _, y := v.Cursor() + _ = v.SetCursor(0, y+1) + x, newY := v.Cursor() + if newY == y { + // we must be on the last line, so lets move to the very end + prevX := -1 + for prevX != x { + prevX = x + v.MoveCursor(1, 0, false) + x, _ = v.Cursor() + } + } else { + // most left so now we're at the end of the original line + v.MoveCursor(-1, 0, false) + } +} + // EditDelete deletes a rune at the cursor position. back determines the // direction. func (v *View) EditDelete(back bool) { diff --git a/gui.go b/gui.go index 73a0bd8c..1d1773f4 100644 --- a/gui.go +++ b/gui.go @@ -722,23 +722,29 @@ func (g *Gui) onKey(ev *termbox.Event) error { // and event. The value of matched is true if there is a match and no errors. func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err error) { var globalKb *keybinding + for _, kb := range g.keybindings { if kb.handler == nil { continue } + if !kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) { continue } + if kb.matchView(v) { return g.execKeybinding(v, kb) } - if kb.viewName == "" && ((v != nil && !v.Editable) || kb.ch == 0) { + + if kb.viewName == "" && ((v != nil && !v.Editable) || (kb.ch == 0 && kb.key != KeyCtrlU && kb.key != KeyCtrlA && kb.key != KeyCtrlE)) { globalKb = kb } } + if globalKb != nil { return g.execKeybinding(v, globalKb) } + return false, nil } From 57f012571e9d969c62eed2f82b50f1d99b44040c Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Mon, 9 Sep 2019 19:44:19 +0200 Subject: [PATCH 71/73] Gui: Added SetViewBeneath() * This might be useful for some projects See https://github.com/jesseduffield/gocui/commit/bac774b26e2192aca1d8b70506c346ad867553a2 Co-authored-by: Jesse Duffield Signed-off-by: Glenn Vriesman --- gui.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/gui.go b/gui.go index 1d1773f4..946028de 100644 --- a/gui.go +++ b/gui.go @@ -183,6 +183,17 @@ func (g *Gui) SetView(name string, x0, y0, x1, y1 int, overlaps byte) (*View, er return v, errors.Wrap(ErrUnknownView, 0) } +// SetViewBeneath sets a view stacked beneath another view +func (g *Gui) SetViewBeneath(name string, aboveViewName string, height int) (*View, error) { + aboveView, err := g.View(aboveViewName) + if err != nil { + return nil, err + } + + viewTop := aboveView.y1 + 1 + return g.SetView(name, aboveView.x0, viewTop, aboveView.x1, viewTop+height-1, 0) +} + // SetViewOnTop sets the given view on top of the existing ones. func (g *Gui) SetViewOnTop(name string) (*View, error) { for i, v := range g.views { From ce41128562bd72b11a068d4ff3765cb95d7589df Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Fri, 13 Sep 2019 00:53:42 +0200 Subject: [PATCH 72/73] Keybinding: Added keybind parsing * Added Parse() to parse a string to a keybind * Added ParseAll() to parse multiple strings to keybinds * Added MustParse() to parse a string at all costs * Added MustParseAll() to parse multiple strings at all costs * Added an example Co-authored-by: Alex Goodman Signed-off-by: Glenn Vriesman --- _examples/keybinds.go | 69 ++++++++++++++++++ keybinding.go | 158 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 220 insertions(+), 7 deletions(-) create mode 100644 _examples/keybinds.go diff --git a/_examples/keybinds.go b/_examples/keybinds.go new file mode 100644 index 00000000..13bf7256 --- /dev/null +++ b/_examples/keybinds.go @@ -0,0 +1,69 @@ +package main + +import ( + "log" + + "github.com/awesome-gocui/gocui" +) + +// layout generates the view +func layout(g *gocui.Gui) error { + maxX, maxY := g.Size() + if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2, 0); err != nil { + if !gocui.IsUnknownView(err) { + return err + } + + v.Write([]byte("Hello")) + + if _, err := g.SetCurrentView("hello"); err != nil { + return err + } + } + + return nil +} + +// quit stops the gui +func quit(_ *gocui.Gui, _ *gocui.View) error { + return gocui.ErrQuit +} + +func main() { + // Create a gui + g, err := gocui.NewGui(gocui.OutputNormal, false) + if err != nil { + log.Panicln(err) + } + defer g.Close() + + // Add a manager function + g.SetManagerFunc(layout) + + // This will set up the recovery for MustParse + defer func() { + if r := recover(); r != nil { + log.Panicln("Error caught: ", r) + } + }() + + // The MustParse can panic, but only returns 2 values instead of 3 + keyForced, modForced := gocui.MustParse("q") + if err := g.SetKeybinding("", keyForced, modForced, quit); err != nil { + log.Panicln(err) + } + + // The normal parse returns an key, a modifier and an error + keyNormal, modNormal, err := gocui.Parse("Ctrl+c") + if err != nil { + log.Panicln(err) + } + if err = g.SetKeybinding("", keyNormal, modNormal, quit); err != nil { + log.Panicln(err) + } + + // Now just start a mainloop for the demo + if err = g.MainLoop(); err != nil && err != gocui.ErrQuit { + log.Panicln(err) + } +} diff --git a/keybinding.go b/keybinding.go index fb8aff3d..8d396f4e 100644 --- a/keybinding.go +++ b/keybinding.go @@ -4,7 +4,19 @@ package gocui -import "github.com/awesome-gocui/termbox-go" +import ( + "errors" + "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 +27,68 @@ type keybinding struct { handler func(*Gui, *View) error } +var ( + ErrorNotExist = errors.New("no such keybind") +) + +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, ErrorNotExist + } + + return key, modifier, nil +} + +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 +} + +func MustParse(input string) (interface{}, Modifier) { + k, m, err := Parse(input) + if err != nil { + panic(err) + } + return k, m +} + +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{ @@ -41,8 +115,82 @@ func (kb *keybinding) matchView(v *View) bool { return kb.viewName == v.name } -// Key represents special keys or keys combinations. -type Key termbox.Key +var translate = map[string]Key{ + "F1": KeyF1, + "F2": KeyF2, + "F3": KeyF3, + "F4": KeyF4, + "F5": KeyF5, + "F6": KeyF6, + "F7": KeyF7, + "F8": KeyF8, + "F9": KeyF9, + "F10": KeyF10, + "F11": KeyF11, + "F12": KeyF12, + "Insert": KeyInsert, + "Delete": KeyDelete, + "Home": KeyHome, + "End": KeyEnd, + "Pgup": KeyPgup, + "Pgdn": KeyPgdn, + "ArrowUp": KeyArrowUp, + "ArrowDown": KeyArrowDown, + "ArrowLeft": KeyArrowLeft, + "ArrowRight": KeyArrowRight, + "CtrlTilde": KeyCtrlTilde, + "Ctrl2": KeyCtrl2, + "CtrlSpace": KeyCtrlSpace, + "CtrlA": KeyCtrlA, + "CtrlB": KeyCtrlB, + "CtrlC": KeyCtrlC, + "CtrlD": KeyCtrlD, + "CtrlE": KeyCtrlE, + "CtrlF": KeyCtrlF, + "CtrlG": KeyCtrlG, + "Backspace": KeyBackspace, + "CtrlH": KeyCtrlH, + "Tab": KeyTab, + "CtrlI": KeyCtrlI, + "CtrlJ": KeyCtrlJ, + "CtrlK": KeyCtrlK, + "CtrlL": KeyCtrlL, + "Enter": KeyEnter, + "CtrlM": KeyCtrlM, + "CtrlN": KeyCtrlN, + "CtrlO": KeyCtrlO, + "CtrlP": KeyCtrlP, + "CtrlQ": KeyCtrlQ, + "CtrlR": KeyCtrlR, + "CtrlS": KeyCtrlS, + "CtrlT": KeyCtrlT, + "CtrlU": KeyCtrlU, + "CtrlV": KeyCtrlV, + "CtrlW": KeyCtrlW, + "CtrlX": KeyCtrlX, + "CtrlY": KeyCtrlY, + "CtrlZ": KeyCtrlZ, + "Esc": KeyEsc, + "CtrlLsqBracket": KeyCtrlLsqBracket, + "Ctrl3": KeyCtrl3, + "Ctrl4": KeyCtrl4, + "CtrlBackslash": KeyCtrlBackslash, + "Ctrl5": KeyCtrl5, + "CtrlRsqBracket": KeyCtrlRsqBracket, + "Ctrl6": KeyCtrl6, + "Ctrl7": KeyCtrl7, + "CtrlSlash": KeyCtrlSlash, + "CtrlUnderscore": KeyCtrlUnderscore, + "Space": KeySpace, + "Backspace2": KeyBackspace2, + "Ctrl8": KeyCtrl8, + "Mouseleft": MouseLeft, + "Mousemiddle": MouseMiddle, + "Mouseright": MouseRight, + "Mouserelease": MouseRelease, + "MousewheelUp": MouseWheelUp, + "MousewheelDown": MouseWheelDown, +} // Special keys. const ( @@ -127,10 +275,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) From a7ae619b03f562f45cfc6ca68eab4b82cc94e92b Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Sat, 12 Oct 2019 02:02:49 +0200 Subject: [PATCH 73/73] Keybinding: Added blacklist / whitelist support * Allows developers to add / remove keybindings from the blacklist * Added more precise errors * Made the simpleEditor simple again * Added some missing comments Signed-off-by: Glenn Vriesman --- _examples/keybinds.go | 22 +++++++++++++++- edit.go | 10 ++------ gui.go | 60 ++++++++++++++++++++++++++++++++++++++++--- keybinding.go | 15 ++++++----- 4 files changed, 89 insertions(+), 18 deletions(-) diff --git a/_examples/keybinds.go b/_examples/keybinds.go index 13bf7256..ccd900d0 100644 --- a/_examples/keybinds.go +++ b/_examples/keybinds.go @@ -53,15 +53,35 @@ func main() { log.Panicln(err) } + // We can blacklist a keybinding. + // This allows us to prevent setting the keybinding. + if err := g.BlacklistKeybinding(gocui.KeyCtrlC); err != nil { + log.Panic(err) + } + + // If for some reason you want to whitelist the keybinding, + // you can allow it again by calling g.WhitelistKeybinding. + if err := g.WhitelistKeybinding(gocui.KeyCtrlC); err != nil { + log.Panic(err) + } + // The normal parse returns an key, a modifier and an error - keyNormal, modNormal, err := gocui.Parse("Ctrl+c") + keyNormal, modNormal, err := gocui.Parse("Ctrl+C") if err != nil { log.Panicln(err) } + if err = g.SetKeybinding("", keyNormal, modNormal, quit); err != nil { log.Panicln(err) } + // You can still block it when it is set, just blacklist it again, this will not throw + // an error at parsing, since it is already parsed above, + // but it will prevent it from being executed + //if err := g.BlacklistKeybinding(gocui.KeyCtrlC); err != nil { + // log.Panicln(err) + //} + // Now just start a mainloop for the demo if err = g.MainLoop(); err != nil && err != gocui.ErrQuit { log.Panicln(err) diff --git a/edit.go b/edit.go index 62ac19fb..b5630df3 100644 --- a/edit.go +++ b/edit.go @@ -54,17 +54,11 @@ func simpleEditor(v *View, key Key, ch rune, mod Modifier) { case key == KeyArrowRight: v.MoveCursor(1, 0, false) case key == KeyTab: - v.EditNewLine() + v.EditWrite('\t') case key == KeySpace: v.EditWrite(' ') case key == KeyInsert: v.Overwrite = !v.Overwrite - case key == KeyCtrlU: - v.EditDeleteToStartOfLine() - case key == KeyCtrlA: - v.EditGotoToStartOfLine() - case key == KeyCtrlE: - v.EditGotoToEndOfLine() default: v.EditWrite(ch) } @@ -77,7 +71,7 @@ func (v *View) EditWrite(ch rune) { v.moveCursor(w, 0, true) } -// EditDeleteToStartOfLine is the equivalent of pressing ctrl+U in your terminal, it deletes to the end of the line. Or if you are already at the start of the line, it deletes the newline character +// 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 { diff --git a/gui.go b/gui.go index 946028de..6fe0d5d8 100644 --- a/gui.go +++ b/gui.go @@ -17,11 +17,23 @@ import ( type OutputMode termbox.OutputMode var ( - // ErrQuit is used to decide if the MainLoop finished successfully. - ErrQuit = standardErrors.New("quit") + // 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 ( @@ -50,6 +62,7 @@ type Gui struct { maxX, maxY int outputMode OutputMode stop chan struct{} + blacklist []Key // BgColor and FgColor allow to configure the background and foreground // colors of the GUI. @@ -296,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 @@ -328,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) { @@ -747,7 +787,7 @@ func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err err return g.execKeybinding(v, kb) } - if kb.viewName == "" && ((v != nil && !v.Editable) || (kb.ch == 0 && kb.key != KeyCtrlU && kb.key != KeyCtrlA && kb.key != KeyCtrlE)) { + if kb.viewName == "" && ((v != nil && !v.Editable) || kb.ch == 0) { globalKb = kb } } @@ -761,12 +801,26 @@ func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err err // 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() diff --git a/keybinding.go b/keybinding.go index 8d396f4e..d294e70d 100644 --- a/keybinding.go +++ b/keybinding.go @@ -5,7 +5,6 @@ package gocui import ( - "errors" "strings" "github.com/awesome-gocui/termbox-go" @@ -27,10 +26,8 @@ type keybinding struct { handler func(*Gui, *View) error } -var ( - ErrorNotExist = errors.New("no such keybind") -) - +// 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])) @@ -55,12 +52,13 @@ func Parse(input string) (interface{}, Modifier, error) { key, exist := translate[strings.Join(cleaned, "")] if !exist { - return nil, ModNone, ErrorNotExist + 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 { @@ -73,6 +71,8 @@ func ParseAll(input []string) (map[interface{}]Modifier, error) { 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 { @@ -81,6 +81,8 @@ func MustParse(input string) (interface{}, Modifier) { 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 { @@ -115,6 +117,7 @@ func (kb *keybinding) matchView(v *View) bool { return kb.viewName == v.name } +// translations for strings to keys var translate = map[string]Key{ "F1": KeyF1, "F2": KeyF2,