Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/utils #41

Merged
merged 6 commits into from
Oct 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
glvr182 marked this conversation as resolved.
Show resolved Hide resolved
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