Skip to content

Commit

Permalink
Merge branch 'feature/utils'
Browse files Browse the repository at this point in the history
* feature/utils:
  Keybinding: Added blacklist / whitelist support
  Keybinding: Added keybind parsing
  Gui: Added SetViewBeneath()
  Edit: Added editor functions
  View: Added IsTained()
  View: Added ViewLinesHeight()

Signed-off-by: Glenn Vriesman <[email protected]>
  • Loading branch information
glvr182 committed Oct 20, 2019
2 parents d0b5cfa + a7ae619 commit c9d3c2b
Show file tree
Hide file tree
Showing 5 changed files with 377 additions and 9 deletions.
89 changes: 89 additions & 0 deletions _examples/keybinds.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
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)
}

// 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")
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)
}
}
50 changes: 50 additions & 0 deletions edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ func simpleEditor(v *View, key Key, ch rune, mod Modifier) {
v.MoveCursor(-1, 0, false)
case key == KeyArrowRight:
v.MoveCursor(1, 0, false)
case key == KeyTab:
v.EditWrite('\t')
case key == KeySpace:
v.EditWrite(' ')
case key == KeyInsert:
v.Overwrite = !v.Overwrite
default:
v.EditWrite(ch)
}
}

Expand All @@ -63,6 +71,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 start of the line. Or if you are already at the start of the line, it deletes the newline character
func (v *View) EditDeleteToStartOfLine() {
x, _ := v.Cursor()
if x == 0 {
v.EditDelete(true)
} else {
// delete characters until we are the start of the line
for x > 0 {
v.EditDelete(true)
x, _ = v.Cursor()
}
}
}

// EditGotoToStartOfLine takes you to the start of the current line
func (v *View) EditGotoToStartOfLine() {
x, _ := v.Cursor()
for x > 0 {
v.MoveCursor(-1, 0, false)
x, _ = v.Cursor()
}
}

// EditGotoToEndOfLine takes you to the end of the line
func (v *View) EditGotoToEndOfLine() {
_, y := v.Cursor()
_ = v.SetCursor(0, y+1)
x, newY := v.Cursor()
if newY == y {
// we must be on the last line, so lets move to the very end
prevX := -1
for prevX != x {
prevX = x
v.MoveCursor(1, 0, false)
x, _ = v.Cursor()
}
} else {
// most left so now we're at the end of the original line
v.MoveCursor(-1, 0, false)
}
}

// EditDelete deletes a rune at the cursor position. back determines the
// direction.
func (v *View) EditDelete(back bool) {
Expand Down
75 changes: 73 additions & 2 deletions gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -183,6 +196,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 {
Expand Down Expand Up @@ -285,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
Expand Down Expand Up @@ -317,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) {
Expand Down Expand Up @@ -722,34 +773,54 @@ 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) {
globalKb = kb
}
}

if globalKb != nil {
return g.execKeybinding(v, globalKb)
}

return false, nil
}

// execKeybinding executes a given keybinding
func (g *Gui) execKeybinding(v *View, kb *keybinding) (bool, error) {
if g.isBlacklisted(kb.key) {
return true, nil
}

if err := kb.handler(g, v); err != nil {
return false, err
}
return true, nil
}

// isBlacklisted reports whether the key is blacklisted
func (g *Gui) isBlacklisted(k Key) bool {
for _, j := range g.blacklist {
if j == k {
return true
}
}
return false
}

// IsUnknownView reports whether the contents of an error is "unknown view".
func IsUnknownView(err error) bool {
return err != nil && err.Error() == ErrUnknownView.Error()
Expand Down
Loading

0 comments on commit c9d3c2b

Please sign in to comment.