From 4aed924ceba8ab9dc849acae7ac0e2f2ca06684a Mon Sep 17 00:00:00 2001 From: Roi Martin Date: Sat, 30 Jan 2016 02:36:10 +0100 Subject: [PATCH] Editor refactoring. Add doc. Simplify _examples. --- _examples/delete.go | 5 +- _examples/demo.go | 5 +- _examples/goroutine.go | 12 ++--- _examples/layout.go | 5 +- _examples/mouse.go | 5 +- _examples/overlap.go | 5 +- _examples/stdin.go | 3 +- _examples/wrap.go | 5 +- doc.go | 118 ++++++++++++++++++++++++++++++++--------- edit.go | 12 ++--- gui.go | 14 +++-- view.go | 4 +- 12 files changed, 123 insertions(+), 70 deletions(-) diff --git a/_examples/delete.go b/_examples/delete.go index 025bc28..decee70 100644 --- a/_examples/delete.go +++ b/_examples/delete.go @@ -21,8 +21,6 @@ var ( ) func main() { - var err error - g := gocui.NewGui() if err := g.Init(); err != nil { log.Panicln(err) @@ -37,8 +35,7 @@ func main() { log.Panicln(err) } - err = g.MainLoop() - if err != nil && err != gocui.ErrQuit { + if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { log.Panicln(err) } } diff --git a/_examples/demo.go b/_examples/demo.go index 01067c7..3786cca 100644 --- a/_examples/demo.go +++ b/_examples/demo.go @@ -187,8 +187,6 @@ func layout(g *gocui.Gui) error { } func main() { - var err error - g := gocui.NewGui() if err := g.Init(); err != nil { log.Panicln(err) @@ -203,8 +201,7 @@ func main() { g.SelFgColor = gocui.ColorBlack g.Cursor = true - err = g.MainLoop() - if err != nil && err != gocui.ErrQuit { + if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { log.Panicln(err) } } diff --git a/_examples/goroutine.go b/_examples/goroutine.go index 71462f9..c8dab36 100644 --- a/_examples/goroutine.go +++ b/_examples/goroutine.go @@ -79,20 +79,18 @@ func counter(g *gocui.Gui) { case <-done: return case <-time.After(500 * time.Millisecond): + mu.Lock() + n := ctr + ctr++ + mu.Unlock() + g.Execute(func(g *gocui.Gui) error { v, err := g.View("ctr") if err != nil { return err } - - mu.Lock() - n := ctr - ctr++ - mu.Unlock() - v.Clear() fmt.Fprintln(v, n) - return nil }) } diff --git a/_examples/layout.go b/_examples/layout.go index 62d34d4..50783bd 100644 --- a/_examples/layout.go +++ b/_examples/layout.go @@ -32,8 +32,6 @@ func quit(g *gocui.Gui, v *gocui.View) error { } func main() { - var err error - g := gocui.NewGui() if err := g.Init(); err != nil { log.Panicln(err) @@ -46,8 +44,7 @@ func main() { log.Panicln(err) } - err = g.MainLoop() - if err != nil && err != gocui.ErrQuit { + if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { log.Panicln(err) } } diff --git a/_examples/mouse.go b/_examples/mouse.go index 9eddfed..07f7359 100644 --- a/_examples/mouse.go +++ b/_examples/mouse.go @@ -12,8 +12,6 @@ import ( ) func main() { - var err error - g := gocui.NewGui() if err := g.Init(); err != nil { log.Panicln(err) @@ -29,8 +27,7 @@ func main() { g.Cursor = true g.Mouse = true - err = g.MainLoop() - if err != nil && err != gocui.ErrQuit { + if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { log.Panicln(err) } } diff --git a/_examples/overlap.go b/_examples/overlap.go index f2b8f0f..3ec364c 100644 --- a/_examples/overlap.go +++ b/_examples/overlap.go @@ -56,8 +56,6 @@ func quit(g *gocui.Gui, v *gocui.View) error { } func main() { - var err error - g := gocui.NewGui() if err := g.Init(); err != nil { log.Panicln(err) @@ -69,8 +67,7 @@ func main() { log.Panicln(err) } - err = g.MainLoop() - if err != nil && err != gocui.ErrQuit { + if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { log.Panicln(err) } } diff --git a/_examples/stdin.go b/_examples/stdin.go index 7bee2f2..c517d53 100644 --- a/_examples/stdin.go +++ b/_examples/stdin.go @@ -27,8 +27,7 @@ func main() { } g.Cursor = true - err := g.MainLoop() - if err != nil && err != gocui.ErrQuit { + if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { log.Fatalln(err) } } diff --git a/_examples/wrap.go b/_examples/wrap.go index b9b4055..24f78ce 100644 --- a/_examples/wrap.go +++ b/_examples/wrap.go @@ -32,8 +32,6 @@ func quit(g *gocui.Gui, v *gocui.View) error { } func main() { - var err error - g := gocui.NewGui() if err := g.Init(); err != nil { log.Panicln(err) @@ -45,8 +43,7 @@ func main() { log.Panicln(err) } - err = g.MainLoop() - if err != nil && err != gocui.ErrQuit { + if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { log.Panicln(err) } } diff --git a/doc.go b/doc.go index 2be9865..009b227 100644 --- a/doc.go +++ b/doc.go @@ -5,36 +5,106 @@ /* Package gocui allows to create console user interfaces. -Example: +Create a new GUI: - func layout(g *gocui.Gui) error { - maxX, maxY := g.Size() - if v, err := g.SetView("center", maxX/2-10, maxY/2, maxX/2+10, maxY/2+2); err != nil { - if err != gocui.ErrUnknownView { - return err - } - fmt.Fprintln(v, "This is an example") + g := gocui.NewGui() + if err := g.Init(); err != nil { + // handle error + } + defer g.Close() + + // Set layout and key bindings + // ... + + if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { + // handle error + } + +Set the layout function: + + g.SetLayout(fcn) + +On each iteration of the GUI's main loop, the "layout function" will be +executed. These layout functions can be used to set-up and update the main +application 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). + +GUIs are composed by Views, you can think of it as free text buffers. Views +implement the io.ReadWriter interface, so you can just write to them if you +want to modify their content and the same is valid for reading. + +Create and intialize a view with absolute coordinates: + + if v, err := g.SetView("viewname", 2, 2, 22, 7); err != nil { + if err != gocui.ErrUnknownView { + // handle error } + fmt.Fprintln(v, "This is a new view") + // ... + } + +Views can also being created using relative coordinates: + + maxX, maxY := g.Size() + if v, err := g.SetView("viewname", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil { + // ... + } + +IMPORTANT: Views can only be created, destroyed or updated in two ways: from a +layout funcion 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: + + g.Execute(func(g *gocui.Gui) error { + v, err := g.View("viewname") + if err != nil { + // handle error + } + v.Clear() + fmt.Fprintln(v, "Writing from different goroutines") return nil + }) + +Configure keybindings: + + if err := g.SetKeybinding("viewname", gocui.KeyEnter, gocui.ModNone, fcn); err != nil { + // handle error } - func quit(g *gocui.Gui, v *gocui.View) error { - return gocui.ErrQuit + +gocui implements full mouse support than can be enabled with: + + g.Mouse = true + +Mouse events are handled like any other keybinding: + + if err := g.SetKeybinding("viewname", gocui.MouseLeft, gocui.ModNone, fcn); err != nil { + // handle error } - func main() { - var err error - g := gocui.NewGui() - if err := g.Init(); err != nil { - log.Panicln(err) - } - defer g.Close() - g.SetLayout(layout) - if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { - log.Panicln(err) - } - err = g.MainLoop() - if err != nil && err != gocui.ErrQuit { - log.Panicln(err) + +By default, gocui provides a basic edition mode. This mode can be extended +and customized creating a new Editor and asigning it to *Gui.Editor: + + type Editor interface { + Edit(v *View, key Key, ch rune, mod Modifier) + } + +DefaultEditor can be taken as example to create your own custom Editor: + + var DefaultEditor = EditorFunc(simpleEditor) + + func simpleEditor(v *View, key Key, ch rune, mod Modifier) { + switch { + case ch != 0 && mod == 0: + v.EditWrite(ch) + case key == KeySpace: + v.EditWrite(' ') + case key == KeyBackspace || key == KeyBackspace2: + v.EditDelete(true) + // ... } } + +For more information, see the examples in folder "_examples/". */ package gocui diff --git a/edit.go b/edit.go index a49bfda..eb88819 100644 --- a/edit.go +++ b/edit.go @@ -6,11 +6,6 @@ package gocui const maxInt = int(^uint(0) >> 1) -// Edit allows to define the editor that manages the edition mode, -// including keybindings or cursor behaviour. DefaultEditor is used by -// default. -var Edit = EditorFunc(DefaultEditor) - // Editor interface must be satisfied by gocui editors. type Editor interface { Edit(v *View, key Key, ch rune, mod Modifier) @@ -26,8 +21,11 @@ func (f EditorFunc) Edit(v *View, key Key, ch rune, mod Modifier) { f(v, key, ch, mod) } -// DefaultEditor is used as the default gocui editor. -func DefaultEditor(v *View, key Key, ch rune, mod Modifier) { +// DefaultEditor is the default editor. +var DefaultEditor Editor = EditorFunc(simpleEditor) + +// simpleEditor is used as the default gocui editor. +func simpleEditor(v *View, key Key, ch rune, mod Modifier) { switch { case ch != 0 && mod == 0: v.EditWrite(ch) diff --git a/gui.go b/gui.go index eb379e1..14ae8f6 100644 --- a/gui.go +++ b/gui.go @@ -19,7 +19,7 @@ type userEvent struct { } var ( - // ErrQuit is used to decide if the MainLoop finished succesfully. + // ErrQuit is used to decide if the MainLoop finished successfully. ErrQuit = errors.New("quit") // ErrUnknownView allows to assert if a View must be initialized. @@ -50,6 +50,11 @@ type Gui struct { // 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 } // NewGui returns a new Gui object. @@ -68,6 +73,7 @@ func (g *Gui) Init() error { g.maxX, g.maxY = termbox.Size() g.BgColor = ColorBlack g.FgColor = ColorWhite + g.Editor = DefaultEditor return nil } @@ -220,7 +226,7 @@ func (g *Gui) Execute(h Handler) { } // SetLayout sets the current layout. A layout is a function that -// will be called everytime the gui is re-drawed, it must contain +// 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 @@ -493,8 +499,8 @@ func (g *Gui) onKey(ev *termbox.Event) error { switch ev.Type { case termbox.EventKey: - if g.currentView != nil && g.currentView.Editable && Edit != nil { - Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod)) + if g.currentView != nil && g.currentView.Editable && g.Editor != nil { + g.Editor.Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod)) } curView = g.currentView case termbox.EventMouse: diff --git a/view.go b/view.go index 2c1842c..1cdb169 100644 --- a/view.go +++ b/view.go @@ -419,7 +419,7 @@ func (v *View) breakLine(x, y int) error { } // Buffer returns a string with the contents of the view's internal -// buffer +// buffer. func (v *View) Buffer() string { str := "" for _, l := range v.lines { @@ -429,7 +429,7 @@ func (v *View) Buffer() string { } // ViewBuffer returns a string with the contents of the view's buffer that is -// showed to the user +// shown to the user. func (v *View) ViewBuffer() string { str := "" for _, l := range v.viewLines {