Compare commits

...

117 Commits

Author SHA1 Message Date
Roi Martin
fc12c654f2
Merge pull request #253 from tbouasli/patch-1
docs: removed dollar sign from scripts
2024-09-09 07:08:19 +02:00
Thomas Bouasli
031962f8d6
docs: removed dollar sign from scripts 2024-09-01 15:19:34 -03:00
Roi Martin
de100503e6 doc: update links to pkg.go.dev 2021-08-14 19:15:20 +02:00
Roi Martin
0214e0e872 all: go modules 2021-08-14 19:15:08 +02:00
gulyasm
c055c87ae8 adds ViewBufferLines to View 2018-04-11 17:57:51 +02:00
gulyasm
2cda4f9f05 adds BufferLines func to View 2018-04-11 17:57:51 +02:00
Miguel Mota
491cd051cf Add cointop to "Projects using gocui" 2018-04-11 12:23:13 +02:00
gulyasm
f2f6a1fab8 Adds jsonui to projects using gocui 2018-04-11 12:15:48 +02:00
Mike JS. Choi
df0b3dc8df Add fac to list of projects using gocui 2018-04-11 12:08:39 +02:00
Roi Martin
247e437c8e Merge branch 'kcli-readme' of https://github.com/cswank/gocui into cswank-kcli-readme 2018-04-11 12:04:34 +02:00
Roi Martin
1ec4d5dd73
Merge pull request #149 from alitari/master
Add kubexp to "Projects using gocui"
2018-04-11 11:59:40 +02:00
Alexander Krieg
dc8ca8416c
added kubexp to "Projects using gocui" 2018-03-11 18:15:46 +01:00
Craig Swank
25c0c27f2e Add cswank/kcli to README's list of projects using gocui. 2017-12-29 08:16:18 -07:00
Julien Breux
4f518eddb0 Add Pody project to Readme 2017-08-27 21:50:11 +02:00
Roi Martin
eb73455bec Add diagram project to README 2017-08-20 15:22:33 +02:00
Roi Martin
a251da5a9f Minor syntax clean-ups 2017-08-20 00:28:06 +02:00
Roi Martin
70497a1d13 Fix typo 2017-08-20 00:17:11 +02:00
Roi Martin
881edc4380 Add terminews project to README 2017-08-19 20:43:14 +02:00
Roi Martin
6564cfcacb Rename *Gui.Execute() to *Gui.Update() 2017-08-19 00:13:30 +02:00
Roi Martin
75a7ad2750 Do not move the cursor beyond EOF 2017-08-19 00:06:54 +02:00
Roi Martin
a4d0b30476 Fix warning in _examples/bufs.go 2017-08-18 10:15:20 +02:00
Roi Martin
ad91aa2b83 Refactoring of *View.writeRune 2017-08-18 09:21:00 +02:00
Roi Martin
2677ad0445 Fix trailing \x00 bug in edition mode 2017-08-18 01:00:47 +02:00
Roi Martin
c64aff6dc2 Add claws project to README 2017-08-17 00:04:36 +02:00
Roi Martin
7ba3ea9d2c Use ColorDefault as default bg and fg color 2017-08-16 19:28:26 +02:00
Roi Martin
7e8dafc560 Remove empty line 2017-08-16 00:58:08 +02:00
Roi Martin
e18bedd405 Add _examples/size.go 2017-08-15 22:37:31 +02:00
Roi Martin
70a276872a Add gotime project in README 2017-08-15 21:52:03 +02:00
Roi Martin
0f8c8e3c9e Remove unneeded parentheses 2017-08-15 21:13:34 +02:00
Roman Lisagor
d1b6db5a49 Bug fix: v.MoveCursor with dx < -1 or dx > 1 does not work if origin needs to be adjusted
The existing logic moves the origin by 1 in either direction. The new logic
adjusts the origin by the appropriate amount, and moves the cursor to the
beginning or end of the line.
2017-08-15 21:11:04 +02:00
telecoda
4316bb79d4 Check for collisions in reverse view order 2017-08-15 20:52:01 +02:00
Roi Martin
c48e902a26 Update dynamic.go to add SetViewOnBottom 2017-08-15 20:34:30 +02:00
Roi Martin
a984617410 Simplify SetViewOnBottom 2017-08-15 20:34:06 +02:00
Julien Breux
93acb816a8 Add set view on bottom feature 2017-08-15 20:19:15 +02:00
Roi Martin
612b0b2987 Update README 2017-03-07 08:52:14 +01:00
Roi Martin
5424b93b3b Add httplab and domainr to README 2017-03-07 08:45:14 +01:00
Roi Martin
ed41d1bd2c Fix error handling in *escapeInterpreter.parseOne() 2017-02-12 15:50:38 +01:00
Roi Martin
139487f578 Remove unneeded check in *escapeInterpreter.parseOne() 2017-02-12 15:40:49 +01:00
Niels Widger
e27f247a3e 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.
2017-02-12 14:56:43 +01:00
Roi Martin
88d2b471d4 Rename *Gui.Ascii to *Gui.ASCII 2017-02-11 02:06:03 +01:00
Roi Martin
cfc4e0298f Add *Gui.Ascii to enable ASCII line art 2017-02-10 23:17:26 +01:00
Roi Martin
aff177f6e3 Add wuzz as project using gocui 2017-02-10 22:59:00 +01:00
Harmen
c690b943b6 _examples/goroutine.go: simpler shutdown logic 2017-02-06 00:30:29 +01:00
Dustin Willis Webber
7ac95c981b
Add MouseRelease, MouseUp and MouseDown 2016-11-24 16:23:44 -05:00
Roi Martin
78f1aee25c Set viewLines to nil on *View.Clear() 2016-11-14 21:56:17 +01:00
Roi Martin
1ad1e27a52 Minor fixes in escapeInterpreter 2016-11-14 21:50:15 +01:00
Roi Martin
aacdc2698f Rewind on *View.Clear() 2016-11-14 01:12:37 +01:00
Roi Martin
ba396278de Update README 2016-11-13 21:44:48 +01:00
Roi Martin
fced1c672f Update docs 2016-11-13 21:42:03 +01:00
Roi Martin
f95667af1a Fix typo in doc 2016-11-13 21:40:01 +01:00
Roi Martin
64e8d44eee Fix typo in doc 2016-11-13 21:35:54 +01:00
Roi Martin
6b3dc12c32 Update AUTHORS 2016-11-13 20:46:12 +01:00
Roi Martin
adef0057a9 Add more tests to _examples/colors256.go 2016-11-13 20:46:02 +01:00
Roi Martin
56a1633c76 Minor style changes 2016-11-13 20:45:04 +01:00
Dustin Willis Webber
ff841413b6
256 OutputMode support 2016-11-13 14:06:04 -05:00
Roi Martin
0975ddb2a8 SetManager and SetManagerFunc delete keybindings 2016-10-30 13:20:05 +01:00
Roi Martin
e41f0e8947 _examples/flow_layout.go: refactoring 2016-10-27 23:13:45 +02:00
Roi Martin
308833a2cf Add *Gui.Views(). Add flow layout example 2016-10-27 23:09:13 +02:00
Roi Martin
aa4ac778d3 Move Editor into View. Update docs 2016-10-27 00:43:28 +02:00
Roi Martin
717fd3eaae _examples/active.go: minor fixes 2016-10-27 00:41:50 +02:00
Roi Martin
19125a0a67 Do not edit the view if a keybinding matches the event 2016-10-26 23:22:51 +02:00
Roi Martin
357a541add Update doc and README 2016-10-25 11:41:17 +02:00
Roi Martin
fb08594c69 _examples/widgets.go: Minor changes 2016-10-24 23:34:32 +02:00
Roi Martin
6a590932dd _examples/widgets.go: Improve help message 2016-10-24 19:33:15 +02:00
Roi Martin
89ecdb537d _examples/widgets.go: Minor refactoring 2016-10-24 16:00:18 +02:00
Roi Martin
e5517b913e Minor change in _examples/widgets.go 2016-10-24 15:45:34 +02:00
Roi Martin
df63f19bd6 Add example "widgets.go" 2016-10-24 15:34:14 +02:00
Roi Martin
e38ba07224 Minor refactoring. Avoid shadow vars. 2016-10-24 08:50:51 +02:00
Roi Martin
40dbad569f Introduce GUI managers to replace layout functions 2016-10-24 08:36:23 +02:00
Roi Martin
fc121d98fd Update docs 2016-10-24 07:26:59 +02:00
Roi Martin
ddbc9be671 Merge *Gui.Init() with *Gui.NewGui() 2016-10-24 02:24:41 +02:00
Roi Martin
550f04e523 Simplify how frames are drawn 2016-10-19 00:15:47 +02:00
Roi Martin
d5cb1ac216 Minor fixes in doc 2016-10-19 00:12:01 +02:00
Roi Martin
a7019c8547 Add *Gui.Highlight to enable/disable current view's custom color 2016-10-18 23:30:43 +02:00
Roi Martin
c0ae071931 Use *Gui.{SelFgColor,SelBgColor} to set current view's color
- Use *Gui.{FgColor,BgColor} to set GUI's color.
- Use *Gui.{SelFgColor,SelBgColor} to set current view's color.
- Drop *Gui.ActiveColor and *View.ActiveColor.
2016-10-18 22:49:31 +02:00
Roi Martin
16db12db96 Update AUTHORS 2016-10-18 00:56:53 +02:00
Roi Martin
a109a3641c Use ActiveColor in dynamic.go. Fix bug in active.go 2016-10-18 00:36:26 +02:00
Roi Martin
463428abda Minor refactoring. Update examples 2016-10-18 00:24:16 +02:00
Henri Koski
7779534f95 Add attribute ActiveColor to configure the color of the current view 2016-10-17 23:25:56 +02:00
Roi Martin
ec14132e67 Rearrange screenshots 2016-10-16 18:54:00 +02:00
Roi Martin
1724fbebc8 Update README with projects using gocui and more screenshots
Related to issue #61.
2016-10-16 18:30:11 +02:00
Roi Martin
4e9ce9a8e2 Revert changes in *View.MoveCursor 2016-10-11 10:18:38 +02:00
Roi Martin
d822523f8c Make golint happy. Fix typo. 2016-10-11 08:52:03 +02:00
Roi Martin
cea263b118 Remove unnecessary error in onKey() 2016-10-11 08:41:22 +02:00
Roi Martin
10d2bb60ea Fix mouse mode 2016-10-11 08:34:27 +02:00
Roi Martin
76554e4f84 Execute keybind handlers before edition
Fix issue #54
2016-10-11 08:27:26 +02:00
Roi Martin
8d16527c1d Fix edition mode when Editable = true
Fix issue #53
2016-10-11 07:37:42 +02:00
Roi Martin
6314568953 Fix frames drawing.
Fix issue #57
2016-10-04 00:10:07 +02:00
Roi Martin
2e62b6ba19 Respect view's default background and foreground colors
Fix issue #58
2016-10-03 19:38:40 +02:00
Roi Martin
375270bca0 Replace "godoc" by "go doc" in README 2016-10-03 11:15:42 +02:00
Roi Martin
13f0442ee4 Minor style changes 2016-10-03 11:10:50 +02:00
govlas
f1f9c0fa53 Add *Gui.DeleteKeybindings
*Gui.DeleteKeybindings Deletes all keybindings of view
2016-10-03 11:03:11 +02:00
Roi Martin
30f7d65597 Refactoring 2016-08-30 23:29:55 +02:00
Roi Martin
2a0623774f Add *Gui.DeleteKeybinding 2016-08-30 23:10:07 +02:00
Roi Martin
5f143ef3de Fix typo 2016-08-30 22:15:03 +02:00
Gustavo Bittencourt
873bc2c17d Enable ESCKey binding
Permit the binding of the ESCKey correctly. The default setup is the
same (inputMode := termbox.InputAlt) if user doesn't set the
InputESC value explictly.
2016-08-28 00:29:12 -03:00
Roi Martin
aa556cf135 Add example save.go 2016-07-31 16:47:00 +02:00
Roi Martin
41745b2a15 Fix typo in doc 2016-07-20 21:35:02 +02:00
Roi Martin
2dcda558bf Update AUTHORS file 2016-05-23 00:52:20 +02:00
Roi Martin
132f267b70 Fix minor typo 2016-05-23 00:21:03 +02:00
Roi Martin
6f73260433 Update _examples/colors.go with @deweerdt's code from #39 2016-05-23 00:16:59 +02:00
Roi Martin
a67a34cd60 Use View's default colors in View.writeRune 2016-05-23 00:16:59 +02:00
Roi Martin
3758266eac Update documentation and README 2016-05-23 00:16:59 +02:00
Roi Martin
1b35b7cd26 escape: remove unnecessary type interpreterReturn 2016-05-23 00:16:59 +02:00
Roi Martin
40dec91023 Initial support for colored text
- View contents are stored as cells (rune + colors) instead of runes.
- Uses the escape interpreter coded by @deweerdt in #39.
2016-05-23 00:16:59 +02:00
Roi Martin
8c49240a03 Remove unreachable code 2016-05-17 22:42:36 +02:00
Roi Martin
0707386452 Fix #50: highlight for wrapped lines 2016-05-17 22:40:24 +02:00
Roi Martin
490199421a Show cursor in _examples/mask.go 2016-04-20 14:35:33 +02:00
Roi Martin
7ffb37ef13 Add example for mask 2016-04-20 14:34:09 +02:00
Danny Tylman
65dfdbf77a Add support for masked views 2016-04-20 14:33:14 +02:00
James Monger
336b3337d1 Update delete.go to dynamic.go in readme 2016-04-19 10:35:13 +01:00
Roi Martin
a8eba6db38 Rename _examples/delete.go to _examples/dynamic.go 2016-02-07 17:39:19 +01:00
Roi Martin
248e4f4437 Do not show cursor when it's out of screen 2016-02-07 16:58:15 +01:00
Roi Martin
a09a166064 Extend _examples/delete.go to test *Gui.SetViewOnTop 2016-02-07 16:52:31 +01:00
Roi Martin
28abcdb8e4 Support set views on top
- Add method *Gui.SetViewOnTop
- Add example _examples/ontop.go to test this feature
2016-02-07 16:52:31 +01:00
Roi Martin
e669e2b30d Add more test cases to _examples/title.go 2016-02-06 15:21:38 +01:00
Roi Martin
bb01d13a95 Add view title (based on the implementation of @conejoninja) 2016-02-06 14:50:30 +01:00
29 changed files with 1989 additions and 510 deletions

27
AUTHORS
View File

@ -1,9 +1,30 @@
# This is the official list of gocui authors for copyright purposes.
# Names should be added to this file as
# Name or Organization <email address>
# Name or Organization <email address> contribution
# Contribution
# The email address is not required for organizations.
# Please keep the list sorted.
Roi Martin <jroi.martin@gmail.com>
Main developer
Roi Martin (@nibble_ds) <jroi.martin@gmail.com>
Ryan Sullivan <kayoticsully@gmail.com>
Toggleable view frames
Matthieu Rakotojaona <matthieu.rakotojaona@gmail.com>
Wrapped views
Harry Lawrence <hazbo@gmx.com>
Basic mouse support
Danny Tylman <dtylman@gmail.com>
Masked views
Frederik Deweerdt <frederik.deweerdt@gmail.com>
Colored fonts
Henri Koski <henri.t.koski@gmail.com>
Custom current view color
Dustin Willis Webber <dustin.webber@gmail.com>
256-colors output mode support

View File

@ -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)
[![Go Reference](https://pkg.go.dev/badge/github.com/jroimartin/gocui.svg)](https://pkg.go.dev/github.com/jroimartin/gocui)
Minimalist Go package aimed at creating Console User Interfaces.
@ -12,26 +12,28 @@ Minimalist Go package aimed at creating Console User Interfaces.
* The GUI can be modified at runtime (concurrent-safe).
* Global and view-level keybindings.
* Mouse support.
* Colored text.
* Customizable edition mode.
* Easy to build reusable widgets, complex layouts...
## Installation
Execute:
```
$ go get github.com/jroimartin/gocui
```sh
go get github.com/jroimartin/gocui
```
## Documentation
Execute:
```
$ godoc github.com/jroimartin/gocui
```sh
go doc github.com/jroimartin/gocui
```
Or visit [godoc.org](https://godoc.org/github.com/jroimartin/gocui) to read it
online.
Or visit [pkg.go.dev](https://pkg.go.dev/github.com/jroimartin/gocui) to read
it online.
## Example
@ -46,13 +48,13 @@ import (
)
func main() {
g := gocui.NewGui()
if err := g.Init(); err != nil {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.SetLayout(layout)
g.SetManagerFunc(layout)
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)
@ -81,10 +83,28 @@ func quit(g *gocui.Gui, v *gocui.View) error {
## Screenshots
_examples/demo.go:
![r2cui](https://cloud.githubusercontent.com/assets/1223476/19418932/63645052-93ce-11e6-867c-da5e97e37237.png)
![_examples/demo.go](https://cloud.githubusercontent.com/assets/1223476/5992750/720b84f0-aa36-11e4-88ec-296fa3247b52.png)
_examples/delete.go:
![_examples/dynamic.go](https://cloud.githubusercontent.com/assets/1223476/5992751/76ad5cc2-aa36-11e4-8204-6a90269db827.png)
![_examples/delete.go](https://cloud.githubusercontent.com/assets/1223476/5992751/76ad5cc2-aa36-11e4-8204-6a90269db827.png)
## Projects using gocui
* [komanda-cli](https://github.com/mephux/komanda-cli): IRC Client For Developers.
* [vuls](https://github.com/future-architect/vuls): Agentless vulnerability scanner for Linux/FreeBSD.
* [wuzz](https://github.com/asciimoo/wuzz): Interactive cli tool for HTTP inspection.
* [httplab](https://github.com/gchaincl/httplab): Interactive web server.
* [domainr](https://github.com/MichaelThessel/domainr): Tool that checks the availability of domains based on keywords.
* [gotime](https://github.com/nanohard/gotime): Time tracker for projects and tasks.
* [claws](https://github.com/thehowl/claws): Interactive command line client for testing websockets.
* [terminews](http://github.com/antavelos/terminews): Terminal based RSS reader.
* [diagram](https://github.com/esimov/diagram): Tool to convert ascii arts into hand drawn diagrams.
* [pody](https://github.com/JulienBreux/pody): CLI app to manage Pods in a Kubernetes cluster.
* [kubexp](https://github.com/alitari/kubexp): Kubernetes client.
* [kcli](https://github.com/cswank/kcli): Tool for inspecting kafka topics/partitions/messages.
* [fac](https://github.com/mkchoi212/fac): git merge conflict resolver
* [jsonui](https://github.com/gulyasm/jsonui): Interactive JSON explorer for your terminal.
* [cointop](https://github.com/miguelmota/cointop): Interactive terminal based UI application for tracking cryptocurrencies.
Note: if your project is not listed here, let us know! :)

119
_examples/active.go Normal file
View File

@ -0,0 +1,119 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"log"
"github.com/jroimartin/gocui"
)
var (
viewArr = []string{"v1", "v2", "v3", "v4"}
active = 0
)
func setCurrentViewOnTop(g *gocui.Gui, name string) (*gocui.View, error) {
if _, err := g.SetCurrentView(name); err != nil {
return nil, err
}
return g.SetViewOnTop(name)
}
func nextView(g *gocui.Gui, v *gocui.View) error {
nextIndex := (active + 1) % len(viewArr)
name := viewArr[nextIndex]
out, err := g.View("v2")
if err != nil {
return err
}
fmt.Fprintln(out, "Going from view "+v.Name()+" to "+name)
if _, err := setCurrentViewOnTop(g, name); err != nil {
return err
}
if nextIndex == 0 || nextIndex == 3 {
g.Cursor = true
} else {
g.Cursor = false
}
active = nextIndex
return nil
}
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 err != gocui.ErrUnknownView {
return err
}
v.Title = "v1 (editable)"
v.Editable = true
v.Wrap = true
if _, err = setCurrentViewOnTop(g, "v1"); err != nil {
return err
}
}
if v, err := g.SetView("v2", maxX/2-1, 0, maxX-1, maxY/2-1); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = "v2"
v.Wrap = true
v.Autoscroll = true
}
if v, err := g.SetView("v3", 0, maxY/2-1, maxX/2-1, maxY-1); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = "v3"
v.Wrap = true
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 err != gocui.ErrUnknownView {
return err
}
v.Title = "v4 (editable)"
v.Editable = true
}
return nil
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}
func main() {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.Highlight = true
g.Cursor = true
g.SelFgColor = gocui.ColorGreen
g.SetManagerFunc(layout)
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, nextView); err != nil {
log.Panicln(err)
}
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
}

70
_examples/bufs.go Normal file
View File

@ -0,0 +1,70 @@
// 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.
// WARNING: tricky code just for testing purposes, do not use as reference.
package main
import (
"fmt"
"log"
"github.com/jroimartin/gocui"
)
var vbuf, buf string
func quit(g *gocui.Gui, v *gocui.View) error {
vbuf = v.ViewBuffer()
buf = v.Buffer()
return gocui.ErrQuit
}
func overwrite(g *gocui.Gui, v *gocui.View) error {
v.Overwrite = !v.Overwrite
return nil
}
func layout(g *gocui.Gui) error {
_, maxY := g.Size()
if v, err := g.SetView("main", 0, 0, 20, maxY-1); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Editable = true
v.Wrap = true
if _, err := g.SetCurrentView("main"); err != nil {
return err
}
}
return nil
}
func main() {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
g.Cursor = true
g.Mouse = true
g.SetManagerFunc(layout)
if err := g.SetKeybinding("main", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("main", gocui.KeyCtrlI, gocui.ModNone, overwrite); err != nil {
log.Panicln(err)
}
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
g.Close()
fmt.Printf("VBUF:\n%s\n", vbuf)
fmt.Printf("BUF:\n%s\n", buf)
}

49
_examples/colors.go Normal file
View File

@ -0,0 +1,49 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"log"
"github.com/jroimartin/gocui"
)
func main() {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.SetManagerFunc(layout)
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)
}
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
}
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 err != gocui.ErrUnknownView {
return err
}
for i := 0; i <= 7; i++ {
for _, j := range []int{1, 4, 7} {
fmt.Fprintf(v, "Hello \033[3%d;%dmcolors!\033[0m\n", i, j)
}
}
}
return nil
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}

74
_examples/colors256.go Normal file
View File

@ -0,0 +1,74 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"log"
"github.com/jroimartin/gocui"
)
func main() {
g, err := gocui.NewGui(gocui.Output256)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.SetManagerFunc(layout)
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)
}
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
}
func layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
if v, err := g.SetView("colors", -1, -1, maxX, maxY); err != nil {
if err != gocui.ErrUnknownView {
return err
}
// 256-colors escape codes
for i := 0; i < 256; i++ {
str := fmt.Sprintf("\x1b[48;5;%dm\x1b[30m%3d\x1b[0m ", i, i)
str += fmt.Sprintf("\x1b[38;5;%dm%3d\x1b[0m ", i, i)
if (i+1)%10 == 0 {
str += "\n"
}
fmt.Fprint(v, str)
}
fmt.Fprint(v, "\n\n")
// 8-colors escape codes
ctr := 0
for i := 0; i <= 7; i++ {
for _, j := range []int{1, 4, 7} {
str := fmt.Sprintf("\x1b[3%d;%dm%d:%d\x1b[0m ", i, j, i, j)
if (ctr+1)%20 == 0 {
str += "\n"
}
fmt.Fprint(v, str)
ctr++
}
}
}
return nil
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}

View File

@ -16,9 +16,11 @@ import (
func nextView(g *gocui.Gui, v *gocui.View) error {
if v == nil || v.Name() == "side" {
return g.SetCurrentView("main")
_, err := g.SetCurrentView("main")
return err
}
return g.SetCurrentView("side")
_, err := g.SetCurrentView("side")
return err
}
func cursorDown(g *gocui.Gui, v *gocui.View) error {
@ -62,7 +64,7 @@ func getLine(g *gocui.Gui, v *gocui.View) error {
return err
}
fmt.Fprintln(v, l)
if err := g.SetCurrentView("msg"); err != nil {
if _, err := g.SetCurrentView("msg"); err != nil {
return err
}
}
@ -73,7 +75,7 @@ func delMsg(g *gocui.Gui, v *gocui.View) error {
if err := g.DeleteView("msg"); err != nil {
return err
}
if err := g.SetCurrentView("side"); err != nil {
if _, err := g.SetCurrentView("side"); err != nil {
return err
}
return nil
@ -162,6 +164,8 @@ func layout(g *gocui.Gui) error {
return err
}
v.Highlight = true
v.SelBgColor = gocui.ColorGreen
v.SelFgColor = gocui.ColorBlack
fmt.Fprintln(v, "Item 1")
fmt.Fprintln(v, "Item 2")
fmt.Fprintln(v, "Item 3")
@ -179,7 +183,7 @@ func layout(g *gocui.Gui) error {
fmt.Fprintf(v, "%s", b)
v.Editable = true
v.Wrap = true
if err := g.SetCurrentView("main"); err != nil {
if _, err := g.SetCurrentView("main"); err != nil {
return err
}
}
@ -187,19 +191,19 @@ func layout(g *gocui.Gui) error {
}
func main() {
g := gocui.NewGui()
if err := g.Init(); err != nil {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.SetLayout(layout)
g.Cursor = true
g.SetManagerFunc(layout)
if err := keybindings(g); err != nil {
log.Panicln(err)
}
g.SelBgColor = gocui.ColorGreen
g.SelFgColor = gocui.ColorBlack
g.Cursor = true
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Panicln(err)

View File

@ -12,7 +12,7 @@ import (
"github.com/jroimartin/gocui"
)
const delta = 2
const delta = 1
var (
views = []string{}
@ -21,13 +21,17 @@ var (
)
func main() {
g := gocui.NewGui()
if err := g.Init(); err != nil {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.SetLayout(layout)
g.Highlight = true
g.SelFgColor = gocui.ColorRed
g.SetManagerFunc(layout)
if err := initKeybindings(g); err != nil {
log.Panicln(err)
}
@ -42,7 +46,7 @@ func main() {
func layout(g *gocui.Gui) error {
maxX, _ := g.Size()
v, err := g.SetView("legend", maxX-25, 0, maxX-1, 7)
v, err := g.SetView("help", maxX-25, 0, maxX-1, 9)
if err != nil {
if err != gocui.ErrUnknownView {
return err
@ -52,13 +56,18 @@ func layout(g *gocui.Gui) error {
fmt.Fprintln(v, "Tab: Next View")
fmt.Fprintln(v, "← ↑ → ↓: Move View")
fmt.Fprintln(v, "Backspace: Delete View")
fmt.Fprintln(v, "t: Set view on top")
fmt.Fprintln(v, "b: Set view on bottom")
fmt.Fprintln(v, "^C: Exit")
}
return nil
}
func initKeybindings(g *gocui.Gui) error {
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}); err != nil {
return err
}
if err := g.SetKeybinding("", gocui.KeySpace, gocui.ModNone,
@ -103,13 +112,23 @@ func initKeybindings(g *gocui.Gui) error {
}); err != nil {
return err
}
if err := g.SetKeybinding("", 't', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
_, err := g.SetViewOnTop(views[curView])
return err
}); err != nil {
return err
}
if err := g.SetKeybinding("", 'b', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
_, err := g.SetViewOnBottom(views[curView])
return err
}); err != nil {
return err
}
return nil
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}
func newView(g *gocui.Gui) error {
maxX, maxY := g.Size()
name := fmt.Sprintf("v%v", idxView)
@ -121,18 +140,9 @@ func newView(g *gocui.Gui) error {
v.Wrap = true
fmt.Fprintln(v, strings.Repeat(name+" ", 30))
}
if err := g.SetCurrentView(name); err != nil {
if _, err := g.SetCurrentView(name); err != nil {
return err
}
v.BgColor = gocui.ColorRed
if curView >= 0 {
cv, err := g.View(views[curView])
if err != nil {
return err
}
cv.BgColor = g.BgColor
}
views = append(views, name)
curView = len(views) - 1
@ -159,22 +169,9 @@ func nextView(g *gocui.Gui, disableCurrent bool) error {
next = 0
}
nv, err := g.View(views[next])
if err != nil {
if _, err := g.SetCurrentView(views[next]); err != nil {
return err
}
if err := g.SetCurrentView(views[next]); err != nil {
return err
}
nv.BgColor = gocui.ColorRed
if disableCurrent && len(views) > 1 {
cv, err := g.View(views[curView])
if err != nil {
return err
}
cv.BgColor = g.BgColor
}
curView = next
return nil

87
_examples/flow_layout.go Normal file
View File

@ -0,0 +1,87 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"log"
"strings"
"github.com/jroimartin/gocui"
)
type Label struct {
name string
w, h int
body string
}
func NewLabel(name string, body string) *Label {
lines := strings.Split(body, "\n")
w := 0
for _, l := range lines {
if len(l) > w {
w = len(l)
}
}
h := len(lines) + 1
w = w + 1
return &Label{name: name, w: w, h: h, body: body}
}
func (w *Label) Layout(g *gocui.Gui) error {
v, err := g.SetView(w.name, 0, 0, w.w, w.h)
if err != nil {
if err != gocui.ErrUnknownView {
return err
}
fmt.Fprint(v, w.body)
}
return nil
}
func flowLayout(g *gocui.Gui) error {
views := g.Views()
x := 0
for _, v := range views {
w, h := v.Size()
_, err := g.SetView(v.Name(), x, 0, x+w+1, h+1)
if err != nil && err != gocui.ErrUnknownView {
return err
}
x += w + 2
}
return nil
}
func main() {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
l1 := NewLabel("l1", "This")
l2 := NewLabel("l2", "is")
l3 := NewLabel("l3", "a")
l4 := NewLabel("l4", "flow\nlayout")
l5 := NewLabel("l5", "!")
fl := gocui.ManagerFunc(flowLayout)
g.SetManager(l1, l2, l3, l4, l5, fl)
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
}

View File

@ -16,7 +16,7 @@ import (
const NumGoroutines = 10
var (
done = make(chan bool)
done = make(chan struct{})
wg sync.WaitGroup
mu sync.Mutex // protects ctr
@ -24,13 +24,14 @@ var (
)
func main() {
g := gocui.NewGui()
if err := g.Init(); err != nil {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.SetLayout(layout)
g.SetManagerFunc(layout)
if err := keybindings(g); err != nil {
log.Panicln(err)
}
@ -65,9 +66,7 @@ func keybindings(g *gocui.Gui) error {
}
func quit(g *gocui.Gui, v *gocui.View) error {
for i := 0; i < NumGoroutines; i++ {
done <- true
}
close(done)
return gocui.ErrQuit
}
@ -84,7 +83,7 @@ func counter(g *gocui.Gui) {
ctr++
mu.Unlock()
g.Execute(func(g *gocui.Gui) error {
g.Update(func(g *gocui.Gui) error {
v, err := g.View("ctr")
if err != nil {
return err

View File

@ -12,13 +12,13 @@ import (
)
func main() {
g := gocui.NewGui()
if err := g.Init(); err != nil {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.SetLayout(layout)
g.SetManagerFunc(layout)
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)

View File

@ -32,13 +32,13 @@ func quit(g *gocui.Gui, v *gocui.View) error {
}
func main() {
g := gocui.NewGui()
if err := g.Init(); err != nil {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.SetLayout(layout)
g.SetManagerFunc(layout)
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)

75
_examples/mask.go Normal file
View File

@ -0,0 +1,75 @@
// Copyright 2015 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 (
"fmt"
"log"
"github.com/jroimartin/gocui"
)
func main() {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Fatalln(err)
}
defer g.Close()
g.Cursor = true
g.SetManagerFunc(layout)
if err := initKeybindings(g); err != nil {
log.Fatalln(err)
}
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Fatalln(err)
}
}
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 err != gocui.ErrUnknownView {
return err
}
v.Title = "Keybindings"
fmt.Fprintln(v, "^a: Set mask")
fmt.Fprintln(v, "^c: Exit")
}
if v, err := g.SetView("input", 0, 0, maxX-24, maxY-1); err != nil {
if err != gocui.ErrUnknownView {
return err
}
if _, err := g.SetCurrentView("input"); err != nil {
return err
}
v.Editable = true
v.Wrap = true
}
return nil
}
func initKeybindings(g *gocui.Gui) error {
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}); err != nil {
return err
}
if err := g.SetKeybinding("input", gocui.KeyCtrlA, gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
v.Mask ^= '*'
return nil
}); err != nil {
return err
}
return nil
}

View File

@ -12,20 +12,20 @@ import (
)
func main() {
g := gocui.NewGui()
if err := g.Init(); err != nil {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.SetLayout(layout)
g.Cursor = true
g.Mouse = true
g.SetManagerFunc(layout)
if err := keybindings(g); err != nil {
log.Panicln(err)
}
g.SelBgColor = gocui.ColorGreen
g.SelFgColor = gocui.ColorBlack
g.Cursor = true
g.Mouse = true
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Panicln(err)
@ -38,6 +38,8 @@ func layout(g *gocui.Gui) error {
return err
}
v.Highlight = true
v.SelBgColor = gocui.ColorGreen
v.SelFgColor = gocui.ColorBlack
fmt.Fprintln(v, "Button 1 - line 1")
fmt.Fprintln(v, "Button 1 - line 2")
fmt.Fprintln(v, "Button 1 - line 3")
@ -48,6 +50,8 @@ func layout(g *gocui.Gui) error {
return err
}
v.Highlight = true
v.SelBgColor = gocui.ColorGreen
v.SelFgColor = gocui.ColorBlack
fmt.Fprintln(v, "Button 2 - line 1")
}
return nil
@ -76,7 +80,7 @@ func showMsg(g *gocui.Gui, v *gocui.View) error {
var l string
var err error
if err := g.SetCurrentView(v.Name()); err != nil {
if _, err := g.SetCurrentView(v.Name()); err != nil {
return err
}

88
_examples/ontop.go Normal file
View File

@ -0,0 +1,88 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"log"
"github.com/jroimartin/gocui"
)
func main() {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.SetManagerFunc(layout)
if err := keybindings(g); err != nil {
log.Panicln(err)
}
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
}
func layout(g *gocui.Gui) error {
if v, err := g.SetView("v1", 10, 2, 30, 6); 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 err != gocui.ErrUnknownView {
return err
}
fmt.Fprintln(v, "View #2")
}
if v, err := g.SetView("v3", 30, 6, 50, 10); err != nil {
if err != gocui.ErrUnknownView {
return err
}
fmt.Fprintln(v, "View #3")
}
return nil
}
func keybindings(g *gocui.Gui) error {
err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
})
if err != nil {
return err
}
err = g.SetKeybinding("", '1', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
_, err := g.SetViewOnTop("v1")
return err
})
if err != nil {
return err
}
err = g.SetKeybinding("", '2', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
_, err := g.SetViewOnTop("v2")
return err
})
if err != nil {
return err
}
err = g.SetKeybinding("", '3', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
_, err := g.SetViewOnTop("v3")
return err
})
if err != nil {
return err
}
return nil
}

View File

@ -56,13 +56,14 @@ func quit(g *gocui.Gui, v *gocui.View) error {
}
func main() {
g := gocui.NewGui()
if err := g.Init(); err != nil {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.SetLayout(layout)
g.SetManagerFunc(layout)
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)
}

45
_examples/size.go Normal file
View File

@ -0,0 +1,45 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"log"
"github.com/jroimartin/gocui"
)
func main() {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.SetManagerFunc(layout)
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)
}
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
}
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)
if err != nil && err != gocui.ErrUnknownView {
return err
}
v.Clear()
fmt.Fprintf(v, "%d, %d", maxX, maxY)
return nil
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}

View File

@ -15,17 +15,19 @@ import (
)
func main() {
g := gocui.NewGui()
if err := g.Init(); err != nil {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Fatalln(err)
}
defer g.Close()
g.SetLayout(layout)
g.Cursor = true
g.SetManagerFunc(layout)
if err := initKeybindings(g); err != nil {
log.Fatalln(err)
}
g.Cursor = true
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Fatalln(err)
@ -35,13 +37,13 @@ func main() {
func layout(g *gocui.Gui) error {
maxX, _ := g.Size()
if v, err := g.SetView("legend", maxX-23, 0, maxX-1, 5); err != nil {
if v, err := g.SetView("help", maxX-23, 0, maxX-1, 5); err != nil {
if err != gocui.ErrUnknownView {
return err
}
fmt.Fprintln(v, "KEYBINDINGS")
fmt.Fprintln(v, "↑ ↓: Seek input")
fmt.Fprintln(v, "A: Enable autoscroll")
fmt.Fprintln(v, "a: Enable autoscroll")
fmt.Fprintln(v, "^C: Exit")
}
@ -49,7 +51,7 @@ func layout(g *gocui.Gui) error {
if err != gocui.ErrUnknownView {
return err
}
if err := g.SetCurrentView("stdin"); err != nil {
if _, err := g.SetCurrentView("stdin"); err != nil {
return err
}
dumper := hex.Dumper(v)

162
_examples/title.go Normal file
View File

@ -0,0 +1,162 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"log"
"github.com/jroimartin/gocui"
)
func main() {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.SetManagerFunc(layout)
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)
}
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}
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 err != gocui.ErrUnknownView {
return err
}
v.Title = "Regular title"
}
if v, err := g.SetView("v2", 20, 4, 40, 8); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = "Regular title"
}
// Overlap (back)
if v, err := g.SetView("v3", 60, 4, 80, 8); 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 err != gocui.ErrUnknownView {
return err
}
v.Title = "Regular title"
}
// Overlap (frame)
if v, err := g.SetView("v15", 90, 2, 110, 5); 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 err != gocui.ErrUnknownView {
return err
}
v.Title = "Regular title"
}
if v, err := g.SetView("v17", 140, 5, 160, 8); 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 err != gocui.ErrUnknownView {
return err
}
v.Title = "Regular title"
}
// Long title
if v, err := g.SetView("v5", 10, 12, 30, 16); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = "Long long long long title"
}
// No title
if v, err := g.SetView("v6", 35, 12, 55, 16); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = ""
}
if _, err := g.SetView("v7", 60, 12, 80, 16); err != nil {
if err != gocui.ErrUnknownView {
return err
}
}
// Small view
if v, err := g.SetView("v8", 85, 12, 88, 16); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = "Regular title"
}
// Screen borders
if v, err := g.SetView("v9", -10, 20, 10, 24); 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 err != gocui.ErrUnknownView {
return err
}
v.Title = "Regular title"
}
// Out of screen
if v, err := g.SetView("v11", -21, 28, -1, 32); 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 err != gocui.ErrUnknownView {
return err
}
v.Title = "Regular title"
}
if v, err := g.SetView("v13", 10, -7, 30, -1); 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 err != gocui.ErrUnknownView {
return err
}
v.Title = "Regular title"
}
return nil
}

179
_examples/widgets.go Normal file
View File

@ -0,0 +1,179 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"errors"
"fmt"
"log"
"strings"
"github.com/jroimartin/gocui"
)
const delta = 0.2
type HelpWidget struct {
name string
x, y int
w, h int
body string
}
func NewHelpWidget(name string, x, y int, body string) *HelpWidget {
lines := strings.Split(body, "\n")
w := 0
for _, l := range lines {
if len(l) > w {
w = len(l)
}
}
h := len(lines) + 1
w = w + 1
return &HelpWidget{name: name, x: x, y: y, w: w, h: h, body: body}
}
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)
if err != nil {
if err != gocui.ErrUnknownView {
return err
}
fmt.Fprint(v, w.body)
}
return nil
}
type StatusbarWidget struct {
name string
x, y int
w int
val float64
}
func NewStatusbarWidget(name string, x, y, w int) *StatusbarWidget {
return &StatusbarWidget{name: name, x: x, y: y, w: w}
}
func (w *StatusbarWidget) SetVal(val float64) error {
if val < 0 || val > 1 {
return errors.New("invalid value")
}
w.val = val
return nil
}
func (w *StatusbarWidget) Val() float64 {
return w.val
}
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)
if err != nil && err != gocui.ErrUnknownView {
return err
}
v.Clear()
rep := int(w.val * float64(w.w-1))
fmt.Fprint(v, strings.Repeat("▒", rep))
return nil
}
type ButtonWidget struct {
name string
x, y int
w int
label string
handler func(g *gocui.Gui, v *gocui.View) error
}
func NewButtonWidget(name string, x, y int, label string, handler func(g *gocui.Gui, v *gocui.View) error) *ButtonWidget {
return &ButtonWidget{name: name, x: x, y: y, w: len(label) + 1, label: label, handler: handler}
}
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)
if err != nil {
if err != gocui.ErrUnknownView {
return err
}
if _, err := g.SetCurrentView(w.name); err != nil {
return err
}
if err := g.SetKeybinding(w.name, gocui.KeyEnter, gocui.ModNone, w.handler); err != nil {
return err
}
fmt.Fprint(v, w.label)
}
return nil
}
func main() {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.Highlight = true
g.SelFgColor = gocui.ColorRed
help := NewHelpWidget("help", 1, 1, helpText)
status := NewStatusbarWidget("status", 1, 7, 50)
butdown := NewButtonWidget("butdown", 52, 7, "DOWN", statusDown(status))
butup := NewButtonWidget("butup", 58, 7, "UP", statusUp(status))
g.SetManager(help, status, butdown, butup)
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, toggleButton); 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
}
func toggleButton(g *gocui.Gui, v *gocui.View) error {
nextview := "butdown"
if v != nil && v.Name() == "butdown" {
nextview = "butup"
}
_, err := g.SetCurrentView(nextview)
return err
}
func statusUp(status *StatusbarWidget) func(g *gocui.Gui, v *gocui.View) error {
return func(g *gocui.Gui, v *gocui.View) error {
return statusSet(status, delta)
}
}
func statusDown(status *StatusbarWidget) func(g *gocui.Gui, v *gocui.View) error {
return func(g *gocui.Gui, v *gocui.View) error {
return statusSet(status, -delta)
}
}
func statusSet(sw *StatusbarWidget, inc float64) error {
val := sw.Val() + inc
if val < 0 || val > 1 {
return nil
}
return sw.SetVal(val)
}
const helpText = `KEYBINDINGS
Tab: Move between buttons
Enter: Push button
^C: Exit`

View File

@ -32,13 +32,14 @@ func quit(g *gocui.Gui, v *gocui.View) error {
}
func main() {
g := gocui.NewGui()
if err := g.Init(); err != nil {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.SetLayout(layout)
g.SetManagerFunc(layout)
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)
}

38
doc.go
View File

@ -7,28 +7,29 @@ Package gocui allows to create console user interfaces.
Create a new GUI:
g := gocui.NewGui()
if err := g.Init(); err != nil {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
// handle error
}
defer g.Close()
// Set layout and key bindings
// Set GUI managers and key bindings
// ...
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
// handle error
}
Set the layout function:
Set GUI managers:
g.SetLayout(fcn)
g.SetManager(mgr1, mgr2)
On each iteration of the GUI's main loop, the "layout function" is executed.
These layout functions can be used to set-up and update the application's main
views, being possible to freely switch between them. Also, it is important to
mention that a main loop iteration is executed on each reported event
(key-press, mouse event, window resize, etc).
Managers are in charge of GUI's layout and can be used to build widgets. On
each iteration of the GUI's main loop, the Layout function of each configured
manager is executed. Managers are used to set-up and update the application's
main views, being possible to freely change them during execution. Also, it is
important to mention that a main loop iteration is executed on each reported
event (key-press, mouse event, window resize, etc).
GUIs are composed by Views, you can think of it as buffers. Views implement the
io.ReadWriter interface, so you can just write to them if you want to modify
@ -68,11 +69,12 @@ Mouse events are handled like any other keybinding:
}
IMPORTANT: Views can only be created, destroyed or updated in three ways: from
layout funcions, from keybinding callbacks or via *Gui.Execute(). The reason
for this is that it allows gocui to be conccurent-safe. So, if you want to
update your GUI from a goroutine, you must use *Gui.Execute(). For example:
the Layout function within managers, from keybinding callbacks or via
*Gui.Update(). The reason for this is that it allows gocui to be
concurrent-safe. So, if you want to update your GUI from a goroutine, you must
use *Gui.Update(). For example:
g.Execute(func(g *gocui.Gui) error {
g.Update(func(g *gocui.Gui) error {
v, err := g.View("viewname")
if err != nil {
// handle error
@ -83,7 +85,7 @@ update your GUI from a goroutine, you must use *Gui.Execute(). For example:
})
By default, gocui provides a basic edition mode. This mode can be extended
and customized creating a new Editor and assigning it to *Gui.Editor:
and customized creating a new Editor and assigning it to *View.Editor:
type Editor interface {
Edit(v *View, key Key, ch rune, mod Modifier)
@ -105,6 +107,12 @@ DefaultEditor can be taken as example to create your own custom Editor:
}
}
Colored text:
Views allow to add colored text using ANSI colors. For example:
fmt.Fprintln(v, "\x1b[0;31mHello world")
For more information, see the examples in folder "_examples/".
*/
package gocui

166
edit.go
View File

@ -4,6 +4,8 @@
package gocui
import "errors"
const maxInt = int(^uint(0) >> 1)
// Editor interface must be satisfied by gocui editors.
@ -106,16 +108,9 @@ func (v *View) EditDelete(back bool) {
// EditNewLine inserts a new line under the cursor.
func (v *View) EditNewLine() {
v.breakLine(v.cx, v.cy)
y := v.oy + v.cy
if y >= len(v.viewLines) || (y >= 0 && y < len(v.viewLines) &&
!(v.Wrap && v.cx == 0 && v.viewLines[y].linesX > 0)) {
// new line at the end of the buffer or
// cursor is not at the beginning of a wrapped line
v.ox = 0
v.cx = 0
v.MoveCursor(0, 1, true)
}
v.ox = 0
v.cx = 0
v.MoveCursor(0, 1, true)
}
// MoveCursor moves the cursor taking into account the width of the line/view,
@ -153,11 +148,13 @@ func (v *View) MoveCursor(dx, dy int, writeMode bool) {
// adjust cursor's x position and view's x origin
if x > curLineWidth { // move to next line
if dx > 0 { // horizontal movement
if !v.Wrap {
v.ox = 0
}
v.cx = 0
cy++
if writeMode || v.oy+cy < len(v.viewLines) {
if !v.Wrap {
v.ox = 0
}
v.cx = 0
}
} else { // vertical movement
if curLineWidth > 0 { // move cursor to the EOL
if v.Wrap {
@ -175,16 +172,20 @@ func (v *View) MoveCursor(dx, dy int, writeMode bool) {
}
}
} else {
if !v.Wrap {
v.ox = 0
if writeMode || v.oy+cy < len(v.viewLines) {
if !v.Wrap {
v.ox = 0
}
v.cx = 0
}
v.cx = 0
}
}
} else if cx < 0 {
if !v.Wrap && v.ox > 0 { // move origin to the left
v.ox--
v.ox += cx
v.cx = 0
} else { // move to previous line
cy--
if prevLineWidth > 0 {
if !v.Wrap { // set origin so the EOL is visible
nox := prevLineWidth - maxX + 1
@ -201,14 +202,14 @@ func (v *View) MoveCursor(dx, dy int, writeMode bool) {
}
v.cx = 0
}
cy--
}
} else { // stay on the same line
if v.Wrap {
v.cx = cx
} else {
if cx >= maxX {
v.ox++
v.ox += cx - maxX + 1
v.cx = maxX
} else {
v.cx = cx
}
@ -216,13 +217,128 @@ func (v *View) MoveCursor(dx, dy int, writeMode bool) {
}
// adjust cursor's y position and view's y origin
if cy >= maxY {
v.oy++
} else if cy < 0 {
if cy < 0 {
if v.oy > 0 {
v.oy--
}
} else {
v.cy = cy
} else if writeMode || v.oy+cy < len(v.viewLines) {
if cy >= maxY {
v.oy++
} else {
v.cy = cy
}
}
}
// writeRune writes a rune into the view's internal buffer, at the
// position corresponding to the point (x, y). The length of the internal
// buffer is increased if the point is out of bounds. Overwrite mode is
// governed by the value of View.overwrite.
func (v *View) writeRune(x, y int, ch rune) error {
v.tainted = true
x, y, err := v.realPosition(x, y)
if err != nil {
return err
}
if x < 0 || y < 0 {
return errors.New("invalid point")
}
if y >= len(v.lines) {
s := make([][]cell, y-len(v.lines)+1)
v.lines = append(v.lines, s...)
}
olen := len(v.lines[y])
var s []cell
if x >= len(v.lines[y]) {
s = make([]cell, x-len(v.lines[y])+1)
} else if !v.Overwrite {
s = make([]cell, 1)
}
v.lines[y] = append(v.lines[y], s...)
if !v.Overwrite || (v.Overwrite && x >= olen-1) {
copy(v.lines[y][x+1:], v.lines[y][x:])
}
v.lines[y][x] = cell{
fgColor: v.FgColor,
bgColor: v.BgColor,
chr: ch,
}
return nil
}
// deleteRune removes a rune from the view's internal buffer, at the
// position corresponding to the point (x, y).
func (v *View) deleteRune(x, y int) error {
v.tainted = true
x, y, err := v.realPosition(x, y)
if err != nil {
return err
}
if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
return errors.New("invalid point")
}
v.lines[y] = append(v.lines[y][:x], v.lines[y][x+1:]...)
return nil
}
// mergeLines merges the lines "y" and "y+1" if possible.
func (v *View) mergeLines(y int) error {
v.tainted = true
_, y, err := v.realPosition(0, y)
if err != nil {
return err
}
if y < 0 || y >= len(v.lines) {
return errors.New("invalid point")
}
if y < len(v.lines)-1 { // otherwise we don't need to merge anything
v.lines[y] = append(v.lines[y], v.lines[y+1]...)
v.lines = append(v.lines[:y+1], v.lines[y+2:]...)
}
return nil
}
// breakLine breaks a line of the internal buffer at the position corresponding
// to the point (x, y).
func (v *View) breakLine(x, y int) error {
v.tainted = true
x, y, err := v.realPosition(x, y)
if err != nil {
return err
}
if y < 0 || y >= len(v.lines) {
return errors.New("invalid point")
}
var left, right []cell
if x < len(v.lines[y]) { // break line
left = make([]cell, len(v.lines[y][:x]))
copy(left, v.lines[y][:x])
right = make([]cell, len(v.lines[y][x:]))
copy(right, v.lines[y][x:])
} else { // new empty line
left = v.lines[y]
}
lines := make([][]cell, len(v.lines)+1)
lines[y] = left
lines[y+1] = right
copy(lines, v.lines[:y])
copy(lines[y+2:], v.lines[y+1:])
v.lines = lines
return nil
}

229
escape.go Normal file
View File

@ -0,0 +1,229 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gocui
import (
"errors"
"strconv"
)
type escapeInterpreter struct {
state escapeState
curch rune
csiParam []string
curFgColor, curBgColor Attribute
mode OutputMode
}
type escapeState int
const (
stateNone escapeState = iota
stateEscape
stateCSI
stateParams
)
var (
errNotCSI = errors.New("Not a CSI escape sequence")
errCSIParseError = errors.New("CSI escape sequence parsing error")
errCSITooLong = errors.New("CSI escape sequence is too long")
)
// runes in case of error will output the non-parsed runes as a string.
func (ei *escapeInterpreter) runes() []rune {
switch ei.state {
case stateNone:
return []rune{0x1b}
case stateEscape:
return []rune{0x1b, ei.curch}
case stateCSI:
return []rune{0x1b, '[', ei.curch}
case stateParams:
ret := []rune{0x1b, '['}
for _, s := range ei.csiParam {
ret = append(ret, []rune(s)...)
ret = append(ret, ';')
}
return append(ret, ei.curch)
}
return nil
}
// newEscapeInterpreter returns an escapeInterpreter that will be able to parse
// terminal escape sequences.
func newEscapeInterpreter(mode OutputMode) *escapeInterpreter {
ei := &escapeInterpreter{
state: stateNone,
curFgColor: ColorDefault,
curBgColor: ColorDefault,
mode: mode,
}
return ei
}
// reset sets the escapeInterpreter in initial state.
func (ei *escapeInterpreter) reset() {
ei.state = stateNone
ei.curFgColor = ColorDefault
ei.curBgColor = ColorDefault
ei.csiParam = nil
}
// parseOne parses a rune. If isEscape is true, it means that the rune is part
// of an escape sequence, and as such should not be printed verbatim. Otherwise,
// it's not an escape sequence.
func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
// Sanity checks
if len(ei.csiParam) > 20 {
return false, errCSITooLong
}
if len(ei.csiParam) > 0 && len(ei.csiParam[len(ei.csiParam)-1]) > 255 {
return false, errCSITooLong
}
ei.curch = ch
switch ei.state {
case stateNone:
if ch == 0x1b {
ei.state = stateEscape
return true, nil
}
return false, nil
case stateEscape:
if ch == '[' {
ei.state = stateCSI
return true, nil
}
return false, errNotCSI
case stateCSI:
switch {
case ch >= '0' && ch <= '9':
ei.csiParam = append(ei.csiParam, "")
case ch == 'm':
ei.csiParam = append(ei.csiParam, "0")
default:
return false, errCSIParseError
}
ei.state = stateParams
fallthrough
case stateParams:
switch {
case ch >= '0' && ch <= '9':
ei.csiParam[len(ei.csiParam)-1] += string(ch)
return true, nil
case ch == ';':
ei.csiParam = append(ei.csiParam, "")
return true, nil
case ch == 'm':
var err error
switch ei.mode {
case OutputNormal:
err = ei.outputNormal()
case Output256:
err = ei.output256()
}
if err != nil {
return false, errCSIParseError
}
ei.state = stateNone
ei.csiParam = nil
return true, nil
default:
return false, errCSIParseError
}
}
return false, nil
}
// outputNormal provides 8 different colors:
// black, red, green, yellow, blue, magenta, cyan, white
func (ei *escapeInterpreter) outputNormal() error {
for _, param := range ei.csiParam {
p, err := strconv.Atoi(param)
if err != nil {
return errCSIParseError
}
switch {
case p >= 30 && p <= 37:
ei.curFgColor = Attribute(p - 30 + 1)
case p == 39:
ei.curFgColor = ColorDefault
case p >= 40 && p <= 47:
ei.curBgColor = Attribute(p - 40 + 1)
case p == 49:
ei.curBgColor = ColorDefault
case p == 1:
ei.curFgColor |= AttrBold
case p == 4:
ei.curFgColor |= AttrUnderline
case p == 7:
ei.curFgColor |= AttrReverse
case p == 0:
ei.curFgColor = ColorDefault
ei.curBgColor = ColorDefault
}
}
return nil
}
// output256 allows you to leverage the 256-colors terminal mode:
// 0x01 - 0x08: the 8 colors as in OutputNormal
// 0x09 - 0x10: Color* | AttrBold
// 0x11 - 0xe8: 216 different colors
// 0xe9 - 0x1ff: 24 different shades of grey
func (ei *escapeInterpreter) output256() error {
if len(ei.csiParam) < 3 {
return ei.outputNormal()
}
mode, err := strconv.Atoi(ei.csiParam[1])
if err != nil {
return errCSIParseError
}
if mode != 5 {
return ei.outputNormal()
}
fgbg, err := strconv.Atoi(ei.csiParam[0])
if err != nil {
return errCSIParseError
}
color, err := strconv.Atoi(ei.csiParam[2])
if err != nil {
return errCSIParseError
}
switch fgbg {
case 38:
ei.curFgColor = Attribute(color + 1)
for _, param := range ei.csiParam[3:] {
p, err := strconv.Atoi(param)
if err != nil {
return errCSIParseError
}
switch {
case p == 1:
ei.curFgColor |= AttrBold
case p == 4:
ei.curFgColor |= AttrUnderline
case p == 7:
ei.curFgColor |= AttrReverse
}
}
case 48:
ei.curBgColor = Attribute(color + 1)
default:
return errCSIParseError
}
return nil
}

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module github.com/jroimartin/gocui
go 1.16
require github.com/nsf/termbox-go v1.1.1

4
go.sum Normal file
View File

@ -0,0 +1,4 @@
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=
github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=

481
gui.go
View File

@ -10,14 +10,6 @@ import (
"github.com/nsf/termbox-go"
)
// Handler represents a handler that can be used to update or modify the GUI.
type Handler func(*Gui) error
// userEvent represents an event triggered by the user.
type userEvent struct {
h Handler
}
var (
// ErrQuit is used to decide if the MainLoop finished successfully.
ErrQuit = errors.New("quit")
@ -26,6 +18,17 @@ var (
ErrUnknownView = errors.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)
// Output256 provides 256-colors terminal mode.
Output256 = OutputMode(termbox.Output256)
)
// Gui represents the whole User Interface, including the views, layouts
// and keybindings.
type Gui struct {
@ -33,48 +36,58 @@ type Gui struct {
userEvents chan userEvent
views []*View
currentView *View
layout Handler
managers []Manager
keybindings []*keybinding
maxX, maxY int
outputMode OutputMode
// BgColor and FgColor allow to configure the background and foreground
// colors of the GUI.
BgColor, FgColor Attribute
// SelBgColor and SelFgColor are used to configure the background and
// foreground colors of the selected line, when it is highlighted.
// SelBgColor and SelFgColor allow to configure the background and
// foreground colors of the frame of the current view.
SelBgColor, SelFgColor Attribute
// If Highlight is true, Sel{Bg,Fg}Colors will be used to draw the
// frame of the current view.
Highlight bool
// If Cursor is true then the cursor is enabled.
Cursor bool
// If Mouse is true then mouse events will be enabled.
Mouse bool
// Editor allows to define the editor that manages the edition mode,
// including keybindings or cursor behaviour. DefaultEditor is used by
// default.
Editor Editor
// If InputEsc is true, when ESC sequence is in the buffer and it doesn't
// match any known sequence, ESC means KeyEsc.
InputEsc bool
// If ASCII is true then use ASCII instead of unicode to draw the
// interface. Using ASCII is more portable.
ASCII bool
}
// NewGui returns a new Gui object.
func NewGui() *Gui {
return &Gui{}
}
// Init initializes the library. This function must be called before
// any other functions.
func (g *Gui) Init() error {
// NewGui returns a new Gui object with a given output mode.
func NewGui(mode OutputMode) (*Gui, error) {
if err := termbox.Init(); err != nil {
return err
return nil, err
}
g := &Gui{}
g.outputMode = mode
termbox.SetOutputMode(termbox.OutputMode(mode))
g.tbEvents = make(chan termbox.Event, 20)
g.userEvents = make(chan userEvent, 20)
g.maxX, g.maxY = termbox.Size()
g.BgColor = ColorBlack
g.FgColor = ColorWhite
g.Editor = DefaultEditor
return nil
g.BgColor, g.FgColor = ColorDefault, ColorDefault
g.SelBgColor, g.SelFgColor = ColorDefault, ColorDefault
return g, nil
}
// Close finalizes the library. It should be called after a successful
@ -90,12 +103,12 @@ func (g *Gui) Size() (x, y int) {
// SetRune writes a rune at the given point, relative to the top-left
// corner of the terminal. It checks if the position is valid and applies
// the gui's colors.
func (g *Gui) SetRune(x, y int, ch rune) error {
// the given colors.
func (g *Gui) SetRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
return errors.New("invalid point")
}
termbox.SetCell(x, y, ch, termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor))
termbox.SetCell(x, y, ch, termbox.Attribute(fgColor), termbox.Attribute(bgColor))
return nil
}
@ -131,13 +144,42 @@ func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) {
return v, nil
}
v := newView(name, x0, y0, x1, y1)
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
g.views = append(g.views, v)
return v, ErrUnknownView
}
// SetViewOnTop sets the given view on top of the existing ones.
func (g *Gui) SetViewOnTop(name string) (*View, error) {
for i, v := range g.views {
if v.name == name {
s := append(g.views[:i], g.views[i+1:]...)
g.views = append(s, v)
return v, nil
}
}
return nil, ErrUnknownView
}
// SetViewOnBottom sets the given view on bottom of the existing ones.
func (g *Gui) SetViewOnBottom(name string) (*View, error) {
for i, v := range g.views {
if v.name == name {
s := append(g.views[:i], g.views[i+1:]...)
g.views = append([]*View{v}, s...)
return v, nil
}
}
return nil, ErrUnknownView
}
// Views returns all the views in the GUI.
func (g *Gui) Views() []*View {
return g.views
}
// View returns a pointer to the view with the given name, or error
// ErrUnknownView if a view with that name does not exist.
func (g *Gui) View(name string) (*View, error) {
@ -152,7 +194,9 @@ func (g *Gui) View(name string) (*View, error) {
// ViewByPosition returns a pointer to a view matching the given position, or
// error ErrUnknownView if a view in that position does not exist.
func (g *Gui) ViewByPosition(x, y int) (*View, error) {
for _, v := range g.views {
// traverse views in reverse order checking top views first
for i := len(g.views); i > 0; i-- {
v := g.views[i-1]
if x > v.x0 && x < v.x1 && y > v.y0 && y < v.y1 {
return v, nil
}
@ -183,14 +227,14 @@ func (g *Gui) DeleteView(name string) error {
}
// SetCurrentView gives the focus to a given view.
func (g *Gui) SetCurrentView(name string) error {
func (g *Gui) SetCurrentView(name string) (*View, error) {
for _, v := range g.views {
if v.name == name {
g.currentView = v
return nil
return v, nil
}
}
return ErrUnknownView
return nil, ErrUnknownView
}
// CurrentView returns the currently focused view, or nil if no view
@ -202,39 +246,106 @@ func (g *Gui) CurrentView() *View {
// SetKeybinding creates a new keybinding. If viewname equals to ""
// (empty string) then the keybinding will apply to all views. key must
// be a rune or a Key.
func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, h KeybindingHandler) error {
func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, handler func(*Gui, *View) error) error {
var kb *keybinding
switch k := key.(type) {
case Key:
kb = newKeybinding(viewname, k, 0, mod, h)
case rune:
kb = newKeybinding(viewname, 0, k, mod, h)
default:
return errors.New("unknown type")
k, ch, err := getKey(key)
if err != nil {
return err
}
kb = newKeybinding(viewname, k, ch, mod, handler)
g.keybindings = append(g.keybindings, kb)
return nil
}
// Execute executes the given handler. This function can be called safely from
// a goroutine in order to update the GUI. It is important to note that it
// won't be executed immediately, instead it will be added to the user events
// queue.
func (g *Gui) Execute(h Handler) {
go func() { g.userEvents <- userEvent{h: h} }()
// DeleteKeybinding deletes a keybinding.
func (g *Gui) DeleteKeybinding(viewname string, key interface{}, mod Modifier) error {
k, ch, err := getKey(key)
if err != nil {
return err
}
for i, kb := range g.keybindings {
if kb.viewName == viewname && kb.ch == ch && kb.key == k && kb.mod == mod {
g.keybindings = append(g.keybindings[:i], g.keybindings[i+1:]...)
return nil
}
}
return errors.New("keybinding not found")
}
// SetLayout sets the current layout. A layout is a function that
// will be called every time the gui is redrawn, it must contain
// the base views and its initializations.
func (g *Gui) SetLayout(layout Handler) {
g.layout = layout
// DeleteKeybindings deletes all keybindings of view.
func (g *Gui) DeleteKeybindings(viewname string) {
var s []*keybinding
for _, kb := range g.keybindings {
if kb.viewName != viewname {
s = append(s, kb)
}
}
g.keybindings = s
}
// getKey takes an empty interface with a key and returns the corresponding
// typed Key or rune.
func getKey(key interface{}) (Key, rune, error) {
switch t := key.(type) {
case Key:
return t, 0, nil
case rune:
return 0, t, nil
default:
return 0, 0, errors.New("unknown type")
}
}
// userEvent represents an event triggered by the user.
type userEvent struct {
f func(*Gui) error
}
// Update executes the passed function. This method can be called safely from a
// goroutine in order to update the GUI. It is important to note that the
// passed function won't be executed immediately, instead it will be added to
// the user events queue. Given that Update spawns a goroutine, the order in
// which the user events will be handled is not guaranteed.
func (g *Gui) Update(f func(*Gui) error) {
go func() { g.userEvents <- userEvent{f: f} }()
}
// A Manager is in charge of GUI's layout and can be used to build widgets.
type Manager interface {
// Layout is called every time the GUI is redrawn, it must contain the
// base views and its initializations.
Layout(*Gui) error
}
// The ManagerFunc type is an adapter to allow the use of ordinary functions as
// Managers. If f is a function with the appropriate signature, ManagerFunc(f)
// is an Manager object that calls f.
type ManagerFunc func(*Gui) error
// Layout calls f(g)
func (f ManagerFunc) Layout(g *Gui) error {
return f(g)
}
// SetManager sets the given GUI managers. It deletes all views and
// keybindings.
func (g *Gui) SetManager(managers ...Manager) {
g.managers = managers
g.currentView = nil
g.views = nil
g.keybindings = nil
go func() { g.tbEvents <- termbox.Event{Type: termbox.EventResize} }()
}
// SetManagerFunc sets the given manager function. It deletes all views and
// keybindings.
func (g *Gui) SetManagerFunc(manager func(*Gui) error) {
g.SetManager(ManagerFunc(manager))
}
// MainLoop runs the main loop until an error is returned. A successful
// finish should return ErrQuit.
func (g *Gui) MainLoop() error {
@ -245,6 +356,9 @@ func (g *Gui) MainLoop() error {
}()
inputMode := termbox.InputAlt
if g.InputEsc {
inputMode = termbox.InputEsc
}
if g.Mouse {
inputMode |= termbox.InputMouse
}
@ -260,7 +374,7 @@ func (g *Gui) MainLoop() error {
return err
}
case ev := <-g.userEvents:
if err := ev.h(g); err != nil {
if err := ev.f(g); err != nil {
return err
}
}
@ -271,7 +385,6 @@ func (g *Gui) MainLoop() error {
return err
}
}
return nil
}
// consumeevents handles the remaining events in the events pool.
@ -283,7 +396,7 @@ func (g *Gui) consumeevents() error {
return err
}
case ev := <-g.userEvents:
if err := ev.h(g); err != nil {
if err := ev.f(g); err != nil {
return err
}
default:
@ -307,10 +420,6 @@ func (g *Gui) handleEvent(ev *termbox.Event) error {
// flush updates the gui, re-drawing frames and buffers.
func (g *Gui) flush() error {
if g.layout == nil {
return errors.New("Null layout")
}
termbox.Clear(termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor))
maxX, maxY := termbox.Size()
@ -322,41 +431,60 @@ func (g *Gui) flush() error {
}
g.maxX, g.maxY = maxX, maxY
if err := g.layout(g); err != nil {
return err
for _, m := range g.managers {
if err := m.Layout(g); err != nil {
return err
}
}
for _, v := range g.views {
if v.Frame {
if err := g.drawFrame(v); err != nil {
var fgColor, bgColor Attribute
if g.Highlight && v == g.currentView {
fgColor = g.SelFgColor
bgColor = g.SelBgColor
} else {
fgColor = g.FgColor
bgColor = g.BgColor
}
if err := g.drawFrameEdges(v, fgColor, bgColor); err != nil {
return err
}
if err := g.drawFrameCorners(v, fgColor, bgColor); err != nil {
return err
}
if v.Title != "" {
if err := g.drawTitle(v, fgColor, bgColor); err != nil {
return err
}
}
}
if err := g.draw(v); err != nil {
return err
}
}
if err := g.drawIntersections(); err != nil {
return err
}
termbox.Flush()
return nil
}
// drawFrame draws the horizontal and vertical edges of a view.
func (g *Gui) drawFrame(v *View) error {
// drawFrameEdges draws the horizontal and vertical edges of a view.
func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error {
runeH, runeV := '─', '│'
if g.ASCII {
runeH, runeV = '-', '|'
}
for x := v.x0 + 1; x < v.x1 && x < g.maxX; x++ {
if x < 0 {
continue
}
if v.y0 > -1 && v.y0 < g.maxY {
if err := g.SetRune(x, v.y0, '─'); err != nil {
if err := g.SetRune(x, v.y0, runeH, fgColor, bgColor); err != nil {
return err
}
}
if v.y1 > -1 && v.y1 < g.maxY {
if err := g.SetRune(x, v.y1, '─'); err != nil {
if err := g.SetRune(x, v.y1, runeH, fgColor, bgColor); err != nil {
return err
}
}
@ -366,12 +494,12 @@ func (g *Gui) drawFrame(v *View) error {
continue
}
if v.x0 > -1 && v.x0 < g.maxX {
if err := g.SetRune(v.x0, y, '│'); err != nil {
if err := g.SetRune(v.x0, y, runeV, fgColor, bgColor); err != nil {
return err
}
}
if v.x1 > -1 && v.x1 < g.maxX {
if err := g.SetRune(v.x1, y, '│'); err != nil {
if err := g.SetRune(v.x1, y, runeV, fgColor, bgColor); err != nil {
return err
}
}
@ -379,20 +507,71 @@ func (g *Gui) drawFrame(v *View) error {
return nil
}
// drawFrameCorners draws the corners of the view.
func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error {
runeTL, runeTR, runeBL, runeBR := '┌', '┐', '└', '┘'
if g.ASCII {
runeTL, runeTR, runeBL, runeBR = '+', '+', '+', '+'
}
corners := []struct {
x, y int
ch rune
}{{v.x0, v.y0, runeTL}, {v.x1, v.y0, runeTR}, {v.x0, v.y1, runeBL}, {v.x1, v.y1, runeBR}}
for _, c := range corners {
if c.x >= 0 && c.y >= 0 && c.x < g.maxX && c.y < g.maxY {
if err := g.SetRune(c.x, c.y, c.ch, fgColor, bgColor); err != nil {
return err
}
}
}
return nil
}
// drawTitle draws the title of the view.
func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) error {
if v.y0 < 0 || v.y0 >= g.maxY {
return nil
}
for i, ch := range v.Title {
x := v.x0 + i + 2
if x < 0 {
continue
} else if x > v.x1-2 || x >= g.maxX {
break
}
if err := g.SetRune(x, v.y0, ch, fgColor, bgColor); err != nil {
return err
}
}
return nil
}
// draw manages the cursor and calls the draw function of a view.
func (g *Gui) draw(v *View) error {
if g.Cursor {
if v := g.currentView; v != nil {
maxX, maxY := v.Size()
cx, cy := v.cx, v.cy
if v.cx >= maxX {
cx = maxX - 1
if curview := g.currentView; curview != nil {
vMaxX, vMaxY := curview.Size()
if curview.cx < 0 {
curview.cx = 0
} else if curview.cx >= vMaxX {
curview.cx = vMaxX - 1
}
if v.cy >= maxY {
cy = maxY - 1
if curview.cy < 0 {
curview.cy = 0
} else if curview.cy >= vMaxY {
curview.cy = vMaxY - 1
}
gMaxX, gMaxY := g.Size()
cx, cy := curview.x0+curview.cx+1, curview.y0+curview.cy+1
if cx >= 0 && cx < gMaxX && cy >= 0 && cy < gMaxY {
termbox.SetCursor(cx, cy)
} else {
termbox.HideCursor()
}
v.cx, v.cy = cx, cy
termbox.SetCursor(v.x0+v.cx+1, v.y0+v.cy+1)
}
} else {
termbox.HideCursor()
@ -405,104 +584,22 @@ func (g *Gui) draw(v *View) error {
return nil
}
// drawIntersections draws the corners of each view, based on the type
// of the edges that converge at these points.
func (g *Gui) drawIntersections() error {
for _, v := range g.views {
if ch, ok := g.intersectionRune(v.x0, v.y0); ok {
if err := g.SetRune(v.x0, v.y0, ch); err != nil {
return err
}
}
if ch, ok := g.intersectionRune(v.x0, v.y1); ok {
if err := g.SetRune(v.x0, v.y1, ch); err != nil {
return err
}
}
if ch, ok := g.intersectionRune(v.x1, v.y0); ok {
if err := g.SetRune(v.x1, v.y0, ch); err != nil {
return err
}
}
if ch, ok := g.intersectionRune(v.x1, v.y1); ok {
if err := g.SetRune(v.x1, v.y1, ch); err != nil {
return err
}
}
}
return nil
}
// intersectionRune returns the correct intersection rune at a given
// point.
func (g *Gui) intersectionRune(x, y int) (rune, bool) {
if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
return ' ', false
}
chTop, _ := g.Rune(x, y-1)
top := verticalRune(chTop)
chBottom, _ := g.Rune(x, y+1)
bottom := verticalRune(chBottom)
chLeft, _ := g.Rune(x-1, y)
left := horizontalRune(chLeft)
chRight, _ := g.Rune(x+1, y)
right := horizontalRune(chRight)
var ch rune
switch {
case !top && bottom && !left && right:
ch = '┌'
case !top && bottom && left && !right:
ch = '┐'
case top && !bottom && !left && right:
ch = '└'
case top && !bottom && left && !right:
ch = '┘'
case top && bottom && left && right:
ch = '┼'
case top && bottom && !left && right:
ch = '├'
case top && bottom && left && !right:
ch = '┤'
case !top && bottom && left && right:
ch = '┬'
case top && !bottom && left && right:
ch = '┴'
default:
return ' ', false
}
return ch, true
}
// verticalRune returns if the given character is a vertical rune.
func verticalRune(ch rune) bool {
if ch == '│' || ch == '┼' || ch == '├' || ch == '┤' {
return true
}
return false
}
// verticalRune returns if the given character is a horizontal rune.
func horizontalRune(ch rune) bool {
if ch == '─' || ch == '┼' || ch == '┬' || ch == '┴' {
return true
}
return false
}
// onKey manages key-press events. A keybinding handler is called when
// a key-press or mouse event satisfies a configured keybinding. Furthermore,
// currentView's internal buffer is modified if currentView.Editable is true.
func (g *Gui) onKey(ev *termbox.Event) error {
var curView *View
switch ev.Type {
case termbox.EventKey:
if g.currentView != nil && g.currentView.Editable && g.Editor != nil {
g.Editor.Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod))
matched, err := g.execKeybindings(g.currentView, ev)
if err != nil {
return err
}
if matched {
break
}
if g.currentView != nil && g.currentView.Editable && g.currentView.Editor != nil {
g.currentView.Editor.Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod))
}
curView = g.currentView
case termbox.EventMouse:
mx, my := ev.MouseX, ev.MouseY
v, err := g.ViewByPosition(mx, my)
@ -512,18 +609,28 @@ func (g *Gui) onKey(ev *termbox.Event) error {
if err := v.SetCursor(mx-v.x0-1, my-v.y0-1); err != nil {
return err
}
curView = v
if _, err := g.execKeybindings(v, ev); err != nil {
return err
}
}
for _, kb := range g.keybindings {
if kb.h == nil {
continue
}
if kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) && kb.matchView(curView) {
if err := kb.h(g, curView); err != nil {
return err
}
}
}
return nil
}
// execKeybindings executes the keybinding handlers that match the passed view
// and event. The value of matched is true if there is a match and no errors.
func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err error) {
matched = false
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
}
}
return matched, nil
}

View File

@ -6,19 +6,42 @@ package gocui
import "github.com/nsf/termbox-go"
type (
// Key represents special keys or keys combinations.
Key termbox.Key
// Keybidings are used to link a given key-press event with a handler.
type keybinding struct {
viewName string
key Key
ch rune
mod Modifier
handler func(*Gui, *View) error
}
// Modifier allows to define special keys combinations. They can be used
// in combination with Keys or Runes when a new keybinding is defined.
Modifier termbox.Modifier
// newKeybinding returns a new Keybinding object.
func newKeybinding(viewname string, key Key, ch rune, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) {
kb = &keybinding{
viewName: viewname,
key: key,
ch: ch,
mod: mod,
handler: handler,
}
return kb
}
// KeybindingHandler represents the handler linked to a specific
// keybindings. The handler is called when a key-press event satisfies a
// configured keybinding.
KeybindingHandler func(*Gui, *View) error
)
// matchKeypress returns if the keybinding matches the keypress.
func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
return kb.key == key && kb.ch == ch && kb.mod == mod
}
// matchView returns if the keybinding matches the current view.
func (kb *keybinding) matchView(v *View) bool {
if kb.viewName == "" {
return true
}
return v != nil && kb.viewName == v.name
}
// Key represents special keys or keys combinations.
type Key termbox.Key
// Special keys.
const (
@ -45,9 +68,12 @@ const (
KeyArrowLeft = Key(termbox.KeyArrowLeft)
KeyArrowRight = Key(termbox.KeyArrowRight)
MouseLeft = Key(termbox.MouseLeft)
MouseMiddle = Key(termbox.MouseMiddle)
MouseRight = Key(termbox.MouseRight)
MouseLeft = Key(termbox.MouseLeft)
MouseMiddle = Key(termbox.MouseMiddle)
MouseRight = Key(termbox.MouseRight)
MouseRelease = Key(termbox.MouseRelease)
MouseWheelUp = Key(termbox.MouseWheelUp)
MouseWheelDown = Key(termbox.MouseWheelDown)
)
// Keys combinations.
@ -100,42 +126,12 @@ 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)
ModAlt = Modifier(termbox.ModAlt)
)
// Keybidings are used to link a given key-press event with a handler.
type keybinding struct {
viewName string
key Key
ch rune
mod Modifier
h KeybindingHandler
}
// newKeybinding returns a new Keybinding object.
func newKeybinding(viewname string, key Key, ch rune, mod Modifier, h KeybindingHandler) (kb *keybinding) {
kb = &keybinding{
viewName: viewname,
key: key,
ch: ch,
mod: mod,
h: h,
}
return kb
}
// matchKeypress returns if the keybinding matches the keypress.
func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
return kb.key == key && kb.ch == ch && kb.mod == mod
}
// matchView returns if the keybinding matches the current view.
func (kb *keybinding) matchView(v *View) bool {
if kb.viewName == "" {
return true
}
return v != nil && kb.viewName == v.name
}

295
view.go
View File

@ -20,13 +20,15 @@ type View struct {
x0, y0, x1, y1 int
ox, oy int
cx, cy int
lines [][]rune
lines [][]cell
readOffset int
readCache string
tainted bool // marks if the viewBuffer must be updated
viewLines []viewLine // internal representation of the view's buffer
ei *escapeInterpreter // used to decode ESC sequences on Write
// BgColor and FgColor allow to configure the background and foreground
// colors of the View.
BgColor, FgColor Attribute
@ -39,6 +41,11 @@ type View struct {
// buffer at the cursor position.
Editable bool
// Editor allows to define the editor that manages the edition mode,
// including keybindings or cursor behaviour. DefaultEditor is used by
// default.
Editor Editor
// Overwrite enables or disables the overwrite mode of the view.
Overwrite bool
@ -57,15 +64,38 @@ type View struct {
// If Autoscroll is true, the View will automatically scroll down when the
// text overflows. If true the view's y-origin will be ignored.
Autoscroll bool
// If Frame is true, Title allows to configure a title for the view.
Title string
// If Mask is true, the View will display the mask instead of the real
// content
Mask rune
}
type viewLine struct {
linesX, linesY int // coordinates relative to v.lines
line []rune
line []cell
}
type cell struct {
chr rune
bgColor, fgColor Attribute
}
type lineType []cell
// String returns a string from a given cell slice.
func (l lineType) String() string {
str := ""
for _, c := range l {
str += string(c.chr)
}
return str
}
// newView returns a new View object.
func newView(name string, x0, y0, x1, y1 int) *View {
func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
v := &View{
name: name,
x0: x0,
@ -73,7 +103,9 @@ func newView(name string, x0, y0, x1, y1 int) *View {
x1: x1,
y1: y1,
Frame: true,
Editor: DefaultEditor,
tainted: true,
ei: newEscapeInterpreter(mode),
}
return v
}
@ -88,25 +120,42 @@ func (v *View) Name() string {
return v.name
}
// setRune writes a rune at the given point, relative to the view. It
// checks if the position is valid and applies the view's colors, taking
// into account if the cell must be highlighted.
func (v *View) setRune(x, y int, ch rune) error {
// setRune sets a rune at the given point relative to the view. It applies the
// specified colors, taking into account if the cell must be highlighted. Also,
// it checks if the position is valid.
func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
maxX, maxY := v.Size()
if x < 0 || x >= maxX || y < 0 || y >= maxY {
return errors.New("invalid point")
}
var fgColor, bgColor Attribute
if v.Highlight && y == v.cy {
fgColor = v.SelFgColor
bgColor = v.SelBgColor
} else {
var (
ry, rcy int
err error
)
if v.Highlight {
_, ry, err = v.realPosition(x, y)
if err != nil {
return err
}
_, rcy, err = v.realPosition(v.cx, v.cy)
if err != nil {
return err
}
}
if v.Mask != 0 {
fgColor = v.FgColor
bgColor = v.BgColor
ch = v.Mask
} else if v.Highlight && ry == rcy {
fgColor = v.SelFgColor
bgColor = v.SelBgColor
}
termbox.SetCell(v.x0+x+1, v.y0+y+1, ch,
termbox.Attribute(fgColor), termbox.Attribute(bgColor))
return nil
}
@ -162,20 +211,57 @@ func (v *View) Write(p []byte) (n int, err error) {
if nl > 0 {
v.lines[nl-1] = nil
} else {
v.lines = make([][]rune, 1)
v.lines = make([][]cell, 1)
}
default:
cells := v.parseInput(ch)
if cells == nil {
continue
}
nl := len(v.lines)
if nl > 0 {
v.lines[nl-1] = append(v.lines[nl-1], ch)
v.lines[nl-1] = append(v.lines[nl-1], cells...)
} else {
v.lines = append(v.lines, []rune{ch})
v.lines = append(v.lines, cells)
}
}
}
return len(p), nil
}
// parseInput parses char by char the input written to the View. It returns nil
// while processing ESC sequences. Otherwise, it returns a cell slice that
// contains the processed data.
func (v *View) parseInput(ch rune) []cell {
cells := []cell{}
isEscape, err := v.ei.parseOne(ch)
if err != nil {
for _, r := range v.ei.runes() {
c := cell{
fgColor: v.FgColor,
bgColor: v.BgColor,
chr: r,
}
cells = append(cells, c)
}
v.ei.reset()
} else {
if isEscape {
return nil
}
c := cell{
fgColor: v.ei.curFgColor,
bgColor: v.ei.curBgColor,
chr: ch,
}
cells = append(cells, c)
}
return cells
}
// Read reads data into p. It returns the number of bytes read into p.
// At EOF, err will be io.EOF. Calling Read() after Rewind() makes the
// cache to be refreshed with the contents of the view.
@ -212,22 +298,19 @@ func (v *View) draw() error {
v.viewLines = nil
for i, line := range v.lines {
if v.Wrap {
if len(line) <= maxX {
if len(line) < maxX {
vline := viewLine{linesX: 0, linesY: i, line: line}
v.viewLines = append(v.viewLines, vline)
continue
} else {
vline := viewLine{linesX: 0, linesY: i, line: line[:maxX]}
v.viewLines = append(v.viewLines, vline)
}
// Append remaining lines
for n := maxX; 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)
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 {
@ -250,14 +333,24 @@ func (v *View) draw() error {
break
}
x := 0
for j, ch := range vline.line {
for j, c := range vline.line {
if j < v.ox {
continue
}
if x >= maxX {
break
}
if err := v.setRune(x, y, ch); err != nil {
fgColor := c.fgColor
if fgColor == ColorDefault {
fgColor = v.FgColor
}
bgColor := c.bgColor
if bgColor == ColorDefault {
bgColor = v.BgColor
}
if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil {
return err
}
x++
@ -299,6 +392,8 @@ func (v *View) Clear() {
v.tainted = true
v.lines = nil
v.viewLines = nil
v.readOffset = 0
v.clearRunes()
}
@ -313,109 +408,16 @@ func (v *View) clearRunes() {
}
}
// writeRune writes a rune into the view's internal buffer, at the
// position corresponding to the point (x, y). The length of the internal
// buffer is increased if the point is out of bounds. Overwrite mode is
// governed by the value of View.overwrite.
func (v *View) writeRune(x, y int, ch rune) error {
v.tainted = true
x, y, err := v.realPosition(x, y)
if err != nil {
return err
// BufferLines returns the lines in the view's internal
// buffer.
func (v *View) BufferLines() []string {
lines := make([]string, len(v.lines))
for i, l := range v.lines {
str := lineType(l).String()
str = strings.Replace(str, "\x00", " ", -1)
lines[i] = str
}
if x < 0 || y < 0 {
return errors.New("invalid point")
}
if y >= len(v.lines) {
s := make([][]rune, y-len(v.lines)+1)
v.lines = append(v.lines, s...)
}
olen := len(v.lines[y])
if x >= len(v.lines[y]) {
s := make([]rune, x-len(v.lines[y])+1)
v.lines[y] = append(v.lines[y], s...)
}
if !v.Overwrite && x < olen {
v.lines[y] = append(v.lines[y], '\x00')
copy(v.lines[y][x+1:], v.lines[y][x:])
}
v.lines[y][x] = ch
return nil
}
// 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 {
v.tainted = true
x, y, err := v.realPosition(x, y)
if err != nil {
return err
}
if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
return errors.New("invalid point")
}
v.lines[y] = append(v.lines[y][:x], v.lines[y][x+1:]...)
return nil
}
// mergeLines merges the lines "y" and "y+1" if possible.
func (v *View) mergeLines(y int) error {
v.tainted = true
_, y, err := v.realPosition(0, y)
if err != nil {
return err
}
if y < 0 || y >= len(v.lines) {
return errors.New("invalid point")
}
if y < len(v.lines)-1 { // otherwise we don't need to merge anything
v.lines[y] = append(v.lines[y], v.lines[y+1]...)
v.lines = append(v.lines[:y+1], v.lines[y+2:]...)
}
return nil
}
// breakLine breaks a line of the internal buffer at the position corresponding
// to the point (x, y).
func (v *View) breakLine(x, y int) error {
v.tainted = true
x, y, err := v.realPosition(x, y)
if err != nil {
return err
}
if y < 0 || y >= len(v.lines) {
return errors.New("invalid point")
}
var left, right []rune
if x < len(v.lines[y]) { // break line
left = make([]rune, len(v.lines[y][:x]))
copy(left, v.lines[y][:x])
right = make([]rune, len(v.lines[y][x:]))
copy(right, v.lines[y][x:])
} else { // new empty line
left = v.lines[y]
}
lines := make([][]rune, len(v.lines)+1)
lines[y] = left
lines[y+1] = right
copy(lines, v.lines[:y])
copy(lines[y+2:], v.lines[y+1:])
v.lines = lines
return nil
return lines
}
// Buffer returns a string with the contents of the view's internal
@ -423,17 +425,29 @@ func (v *View) breakLine(x, y int) error {
func (v *View) Buffer() string {
str := ""
for _, l := range v.lines {
str += string(l) + "\n"
str += lineType(l).String() + "\n"
}
return strings.Replace(str, "\x00", " ", -1)
}
// ViewBufferLines returns the lines in the view's internal
// buffer that is shown to the user.
func (v *View) ViewBufferLines() []string {
lines := make([]string, len(v.viewLines))
for i, l := range v.viewLines {
str := lineType(l.line).String()
str = strings.Replace(str, "\x00", " ", -1)
lines[i] = str
}
return lines
}
// 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 += string(l.line) + "\n"
str += lineType(l.line).String() + "\n"
}
return strings.Replace(str, "\x00", " ", -1)
}
@ -449,7 +463,8 @@ func (v *View) Line(y int) (string, error) {
if y < 0 || y >= len(v.lines) {
return "", errors.New("invalid point")
}
return string(v.lines[y]), nil
return lineType(v.lines[y]).String(), nil
}
// Word returns a string with the word of the view's internal buffer
@ -463,20 +478,22 @@ 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")
}
l := string(v.lines[y])
nl := strings.LastIndexFunc(l[:x], indexFunc)
str := lineType(v.lines[y]).String()
nl := strings.LastIndexFunc(str[:x], indexFunc)
if nl == -1 {
nl = 0
} else {
nl = nl + 1
}
nr := strings.IndexFunc(l[x:], indexFunc)
nr := strings.IndexFunc(str[x:], indexFunc)
if nr == -1 {
nr = len(l)
nr = len(str)
} else {
nr = nr + x
}
return string(l[nl:nr]), nil
return string(str[nl:nr]), nil
}
// indexFunc allows to split lines by words taking into account spaces