1// Copyright 2014 The gocui Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package gocui
6
7import (
8 standardErrors "errors"
9 "runtime"
10
11 "github.com/go-errors/errors"
12
13 "github.com/awesome-gocui/termbox-go"
14)
15
16// OutputMode represents the terminal's output mode (8 or 256 colors).
17type OutputMode termbox.OutputMode
18
19var (
20 // ErrAlreadyBlacklisted is returned when the keybinding is already blacklisted.
21 ErrAlreadyBlacklisted = standardErrors.New("keybind already blacklisted")
22
23 // ErrBlacklisted is returned when the keybinding being parsed / used is blacklisted.
24 ErrBlacklisted = standardErrors.New("keybind blacklisted")
25
26 // ErrNotBlacklisted is returned when a keybinding being whitelisted is not blacklisted.
27 ErrNotBlacklisted = standardErrors.New("keybind not blacklisted")
28
29 // ErrNoSuchKeybind is returned when the keybinding being parsed does not exist.
30 ErrNoSuchKeybind = standardErrors.New("no such keybind")
31
32 // ErrUnknownView allows to assert if a View must be initialized.
33 ErrUnknownView = standardErrors.New("unknown view")
34
35 // ErrQuit is used to decide if the MainLoop finished successfully.
36 ErrQuit = standardErrors.New("quit")
37)
38
39const (
40 // OutputNormal provides 8-colors terminal mode.
41 OutputNormal = OutputMode(termbox.OutputNormal)
42
43 // Output256 provides 256-colors terminal mode.
44 Output256 = OutputMode(termbox.Output256)
45
46 // OutputGrayScale provides greyscale terminal mode.
47 OutputGrayScale = OutputMode(termbox.OutputGrayscale)
48
49 // Output216 provides greyscale terminal mode.
50 Output216 = OutputMode(termbox.Output216)
51)
52
53// Gui represents the whole User Interface, including the views, layouts
54// and keybindings.
55type Gui struct {
56 tbEvents chan termbox.Event
57 userEvents chan userEvent
58 views []*View
59 currentView *View
60 managers []Manager
61 keybindings []*keybinding
62 maxX, maxY int
63 outputMode OutputMode
64 stop chan struct{}
65 blacklist []Key
66
67 // BgColor and FgColor allow to configure the background and foreground
68 // colors of the GUI.
69 BgColor, FgColor, FrameColor Attribute
70
71 // SelBgColor and SelFgColor allow to configure the background and
72 // foreground colors of the frame of the current view.
73 SelBgColor, SelFgColor, SelFrameColor Attribute
74
75 // If Highlight is true, Sel{Bg,Fg}Colors will be used to draw the
76 // frame of the current view.
77 Highlight bool
78
79 // If Cursor is true then the cursor is enabled.
80 Cursor bool
81
82 // If Mouse is true then mouse events will be enabled.
83 Mouse bool
84
85 // If InputEsc is true, when ESC sequence is in the buffer and it doesn't
86 // match any known sequence, ESC means KeyEsc.
87 InputEsc bool
88
89 // If ASCII is true then use ASCII instead of unicode to draw the
90 // interface. Using ASCII is more portable.
91 ASCII bool
92
93 // SupportOverlaps is true when we allow for view edges to overlap with other
94 // view edges
95 SupportOverlaps bool
96}
97
98// NewGui returns a new Gui object with a given output mode.
99func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) {
100 err := termbox.Init()
101 if err != nil {
102 return nil, err
103 }
104
105 g := &Gui{}
106
107 g.outputMode = mode
108 termbox.SetOutputMode(termbox.OutputMode(mode))
109
110 g.stop = make(chan struct{})
111
112 g.tbEvents = make(chan termbox.Event, 20)
113 g.userEvents = make(chan userEvent, 20)
114
115 if runtime.GOOS != "windows" {
116 g.maxX, g.maxY, err = g.getTermWindowSize()
117 if err != nil {
118 return nil, err
119 }
120 } else {
121 g.maxX, g.maxY = termbox.Size()
122 }
123
124 g.BgColor, g.FgColor = ColorDefault, ColorDefault
125 g.SelBgColor, g.SelFgColor = ColorDefault, ColorDefault
126
127 // SupportOverlaps is true when we allow for view edges to overlap with other
128 // view edges
129 g.SupportOverlaps = supportOverlaps
130
131 return g, nil
132}
133
134// Close finalizes the library. It should be called after a successful
135// initialization and when gocui is not needed anymore.
136func (g *Gui) Close() {
137 go func() {
138 g.stop <- struct{}{}
139 }()
140 termbox.Close()
141}
142
143// Size returns the terminal's size.
144func (g *Gui) Size() (x, y int) {
145 return g.maxX, g.maxY
146}
147
148// SetRune writes a rune at the given point, relative to the top-left
149// corner of the terminal. It checks if the position is valid and applies
150// the given colors.
151func (g *Gui) SetRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
152 if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
153 return errors.New("invalid point")
154 }
155 termbox.SetCell(x, y, ch, termbox.Attribute(fgColor), termbox.Attribute(bgColor))
156 return nil
157}
158
159// Rune returns the rune contained in the cell at the given position.
160// It checks if the position is valid.
161func (g *Gui) Rune(x, y int) (rune, error) {
162 if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
163 return ' ', errors.New("invalid point")
164 }
165 c := termbox.CellBuffer()[y*g.maxX+x]
166 return c.Ch, nil
167}
168
169// SetView creates a new view with its top-left corner at (x0, y0)
170// and the bottom-right one at (x1, y1). If a view with the same name
171// already exists, its dimensions are updated; otherwise, the error
172// ErrUnknownView is returned, which allows to assert if the View must
173// be initialized. It checks if the position is valid.
174func (g *Gui) SetView(name string, x0, y0, x1, y1 int, overlaps byte) (*View, error) {
175 if x0 >= x1 {
176 return nil, errors.New("invalid dimensions")
177 }
178 if name == "" {
179 return nil, errors.New("invalid name")
180 }
181
182 if v, err := g.View(name); err == nil {
183 v.x0 = x0
184 v.y0 = y0
185 v.x1 = x1
186 v.y1 = y1
187 v.tainted = true
188 return v, nil
189 }
190
191 v := newView(name, x0, y0, x1, y1, g.outputMode)
192 v.BgColor, v.FgColor = g.BgColor, g.FgColor
193 v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor
194 v.Overlaps = overlaps
195 g.views = append(g.views, v)
196 return v, errors.Wrap(ErrUnknownView, 0)
197}
198
199// SetViewBeneath sets a view stacked beneath another view
200func (g *Gui) SetViewBeneath(name string, aboveViewName string, height int) (*View, error) {
201 aboveView, err := g.View(aboveViewName)
202 if err != nil {
203 return nil, err
204 }
205
206 viewTop := aboveView.y1 + 1
207 return g.SetView(name, aboveView.x0, viewTop, aboveView.x1, viewTop+height-1, 0)
208}
209
210// SetViewOnTop sets the given view on top of the existing ones.
211func (g *Gui) SetViewOnTop(name string) (*View, error) {
212 for i, v := range g.views {
213 if v.name == name {
214 s := append(g.views[:i], g.views[i+1:]...)
215 g.views = append(s, v)
216 return v, nil
217 }
218 }
219 return nil, errors.Wrap(ErrUnknownView, 0)
220}
221
222// SetViewOnBottom sets the given view on bottom of the existing ones.
223func (g *Gui) SetViewOnBottom(name string) (*View, error) {
224 for i, v := range g.views {
225 if v.name == name {
226 s := append(g.views[:i], g.views[i+1:]...)
227 g.views = append([]*View{v}, s...)
228 return v, nil
229 }
230 }
231 return nil, errors.Wrap(ErrUnknownView, 0)
232}
233
234// Views returns all the views in the GUI.
235func (g *Gui) Views() []*View {
236 return g.views
237}
238
239// View returns a pointer to the view with the given name, or error
240// ErrUnknownView if a view with that name does not exist.
241func (g *Gui) View(name string) (*View, error) {
242 for _, v := range g.views {
243 if v.name == name {
244 return v, nil
245 }
246 }
247 return nil, errors.Wrap(ErrUnknownView, 0)
248}
249
250// ViewByPosition returns a pointer to a view matching the given position, or
251// error ErrUnknownView if a view in that position does not exist.
252func (g *Gui) ViewByPosition(x, y int) (*View, error) {
253 // traverse views in reverse order checking top views first
254 for i := len(g.views); i > 0; i-- {
255 v := g.views[i-1]
256 if x > v.x0 && x < v.x1 && y > v.y0 && y < v.y1 {
257 return v, nil
258 }
259 }
260 return nil, errors.Wrap(ErrUnknownView, 0)
261}
262
263// ViewPosition returns the coordinates of the view with the given name, or
264// error ErrUnknownView if a view with that name does not exist.
265func (g *Gui) ViewPosition(name string) (x0, y0, x1, y1 int, err error) {
266 for _, v := range g.views {
267 if v.name == name {
268 return v.x0, v.y0, v.x1, v.y1, nil
269 }
270 }
271 return 0, 0, 0, 0, errors.Wrap(ErrUnknownView, 0)
272}
273
274// DeleteView deletes a view by name.
275func (g *Gui) DeleteView(name string) error {
276 for i, v := range g.views {
277 if v.name == name {
278 g.views = append(g.views[:i], g.views[i+1:]...)
279 return nil
280 }
281 }
282 return errors.Wrap(ErrUnknownView, 0)
283}
284
285// SetCurrentView gives the focus to a given view.
286func (g *Gui) SetCurrentView(name string) (*View, error) {
287 for _, v := range g.views {
288 if v.name == name {
289 g.currentView = v
290 return v, nil
291 }
292 }
293 return nil, errors.Wrap(ErrUnknownView, 0)
294}
295
296// CurrentView returns the currently focused view, or nil if no view
297// owns the focus.
298func (g *Gui) CurrentView() *View {
299 return g.currentView
300}
301
302// SetKeybinding creates a new keybinding. If viewname equals to ""
303// (empty string) then the keybinding will apply to all views. key must
304// be a rune or a Key.
305func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, handler func(*Gui, *View) error) error {
306 var kb *keybinding
307
308 k, ch, err := getKey(key)
309 if err != nil {
310 return err
311 }
312
313 if g.isBlacklisted(k) {
314 return ErrBlacklisted
315 }
316
317 kb = newKeybinding(viewname, k, ch, mod, handler)
318 g.keybindings = append(g.keybindings, kb)
319 return nil
320}
321
322// DeleteKeybinding deletes a keybinding.
323func (g *Gui) DeleteKeybinding(viewname string, key interface{}, mod Modifier) error {
324 k, ch, err := getKey(key)
325 if err != nil {
326 return err
327 }
328
329 for i, kb := range g.keybindings {
330 if kb.viewName == viewname && kb.ch == ch && kb.key == k && kb.mod == mod {
331 g.keybindings = append(g.keybindings[:i], g.keybindings[i+1:]...)
332 return nil
333 }
334 }
335 return errors.New("keybinding not found")
336}
337
338// DeleteKeybindings deletes all keybindings of view.
339func (g *Gui) DeleteKeybindings(viewname string) {
340 var s []*keybinding
341 for _, kb := range g.keybindings {
342 if kb.viewName != viewname {
343 s = append(s, kb)
344 }
345 }
346 g.keybindings = s
347}
348
349// BlackListKeybinding adds a keybinding to the blacklist
350func (g *Gui) BlacklistKeybinding(k Key) error {
351 for _, j := range g.blacklist {
352 if j == k {
353 return ErrAlreadyBlacklisted
354 }
355 }
356 g.blacklist = append(g.blacklist, k)
357 return nil
358}
359
360// WhiteListKeybinding removes a keybinding from the blacklist
361func (g *Gui) WhitelistKeybinding(k Key) error {
362 for i, j := range g.blacklist {
363 if j == k {
364 g.blacklist = append(g.blacklist[:i], g.blacklist[i+1:]...)
365 return nil
366 }
367 }
368 return ErrNotBlacklisted
369}
370
371// getKey takes an empty interface with a key and returns the corresponding
372// typed Key or rune.
373func getKey(key interface{}) (Key, rune, error) {
374 switch t := key.(type) {
375 case Key:
376 return t, 0, nil
377 case rune:
378 return 0, t, nil
379 default:
380 return 0, 0, errors.New("unknown type")
381 }
382}
383
384// userEvent represents an event triggered by the user.
385type userEvent struct {
386 f func(*Gui) error
387}
388
389// Update executes the passed function. This method can be called safely from a
390// goroutine in order to update the GUI. It is important to note that the
391// passed function won't be executed immediately, instead it will be added to
392// the user events queue. Given that Update spawns a goroutine, the order in
393// which the user events will be handled is not guaranteed.
394func (g *Gui) Update(f func(*Gui) error) {
395 go func() { g.userEvents <- userEvent{f: f} }()
396}
397
398// A Manager is in charge of GUI's layout and can be used to build widgets.
399type Manager interface {
400 // Layout is called every time the GUI is redrawn, it must contain the
401 // base views and its initializations.
402 Layout(*Gui) error
403}
404
405// The ManagerFunc type is an adapter to allow the use of ordinary functions as
406// Managers. If f is a function with the appropriate signature, ManagerFunc(f)
407// is an Manager object that calls f.
408type ManagerFunc func(*Gui) error
409
410// Layout calls f(g)
411func (f ManagerFunc) Layout(g *Gui) error {
412 return f(g)
413}
414
415// SetManager sets the given GUI managers. It deletes all views and
416// keybindings.
417func (g *Gui) SetManager(managers ...Manager) {
418 g.managers = managers
419 g.currentView = nil
420 g.views = nil
421 g.keybindings = nil
422
423 go func() { g.tbEvents <- termbox.Event{Type: termbox.EventResize} }()
424}
425
426// SetManagerFunc sets the given manager function. It deletes all views and
427// keybindings.
428func (g *Gui) SetManagerFunc(manager func(*Gui) error) {
429 g.SetManager(ManagerFunc(manager))
430}
431
432// MainLoop runs the main loop until an error is returned. A successful
433// finish should return ErrQuit.
434func (g *Gui) MainLoop() error {
435 g.loaderTick()
436 if err := g.flush(); err != nil {
437 return err
438 }
439
440 go func() {
441 for {
442 select {
443 case <-g.stop:
444 return
445 default:
446 g.tbEvents <- termbox.PollEvent()
447 }
448 }
449 }()
450
451 inputMode := termbox.InputAlt
452 if true { // previously g.InputEsc, but didn't seem to work
453 inputMode = termbox.InputEsc
454 }
455 if g.Mouse {
456 inputMode |= termbox.InputMouse
457 }
458 termbox.SetInputMode(inputMode)
459
460 if err := g.flush(); err != nil {
461 return err
462 }
463 for {
464 select {
465 case ev := <-g.tbEvents:
466 if err := g.handleEvent(&ev); err != nil {
467 return err
468 }
469 case ev := <-g.userEvents:
470 if err := ev.f(g); err != nil {
471 return err
472 }
473 }
474 if err := g.consumeevents(); err != nil {
475 return err
476 }
477 if err := g.flush(); err != nil {
478 return err
479 }
480 }
481}
482
483// consumeevents handles the remaining events in the events pool.
484func (g *Gui) consumeevents() error {
485 for {
486 select {
487 case ev := <-g.tbEvents:
488 if err := g.handleEvent(&ev); err != nil {
489 return err
490 }
491 case ev := <-g.userEvents:
492 if err := ev.f(g); err != nil {
493 return err
494 }
495 default:
496 return nil
497 }
498 }
499}
500
501// handleEvent handles an event, based on its type (key-press, error,
502// etc.)
503func (g *Gui) handleEvent(ev *termbox.Event) error {
504 switch ev.Type {
505 case termbox.EventKey, termbox.EventMouse:
506 return g.onKey(ev)
507 case termbox.EventError:
508 return ev.Err
509 default:
510 return nil
511 }
512}
513
514// flush updates the gui, re-drawing frames and buffers.
515func (g *Gui) flush() error {
516 termbox.Clear(termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor))
517
518 maxX, maxY := termbox.Size()
519 // if GUI's size has changed, we need to redraw all views
520 if maxX != g.maxX || maxY != g.maxY {
521 for _, v := range g.views {
522 v.tainted = true
523 }
524 }
525 g.maxX, g.maxY = maxX, maxY
526
527 for _, m := range g.managers {
528 if err := m.Layout(g); err != nil {
529 return err
530 }
531 }
532 for _, v := range g.views {
533 if !v.Visible || v.y1 < v.y0 {
534 continue
535 }
536 if v.Frame {
537 var fgColor, bgColor, frameColor Attribute
538 if g.Highlight && v == g.currentView {
539 fgColor = g.SelFgColor
540 bgColor = g.SelBgColor
541 frameColor = g.SelFrameColor
542 } else {
543 fgColor = g.FgColor
544 bgColor = g.BgColor
545 frameColor = g.FrameColor
546 }
547
548 if err := g.drawFrameEdges(v, frameColor, bgColor); err != nil {
549 return err
550 }
551 if err := g.drawFrameCorners(v, frameColor, bgColor); err != nil {
552 return err
553 }
554 if v.Title != "" {
555 if err := g.drawTitle(v, fgColor, bgColor); err != nil {
556 return err
557 }
558 }
559 if v.Subtitle != "" {
560 if err := g.drawSubtitle(v, fgColor, bgColor); err != nil {
561 return err
562 }
563 }
564 }
565 if err := g.draw(v); err != nil {
566 return err
567 }
568 }
569 termbox.Flush()
570 return nil
571}
572
573// drawFrameEdges draws the horizontal and vertical edges of a view.
574func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error {
575 runeH, runeV := '─', '│'
576 if g.ASCII {
577 runeH, runeV = '-', '|'
578 }
579
580 for x := v.x0 + 1; x < v.x1 && x < g.maxX; x++ {
581 if x < 0 {
582 continue
583 }
584 if v.y0 > -1 && v.y0 < g.maxY {
585 if err := g.SetRune(x, v.y0, runeH, fgColor, bgColor); err != nil {
586 return err
587 }
588 }
589 if v.y1 > -1 && v.y1 < g.maxY {
590 if err := g.SetRune(x, v.y1, runeH, fgColor, bgColor); err != nil {
591 return err
592 }
593 }
594 }
595 for y := v.y0 + 1; y < v.y1 && y < g.maxY; y++ {
596 if y < 0 {
597 continue
598 }
599 if v.x0 > -1 && v.x0 < g.maxX {
600 if err := g.SetRune(v.x0, y, runeV, fgColor, bgColor); err != nil {
601 return err
602 }
603 }
604 if v.x1 > -1 && v.x1 < g.maxX {
605 if err := g.SetRune(v.x1, y, runeV, fgColor, bgColor); err != nil {
606 return err
607 }
608 }
609 }
610 return nil
611}
612
613func cornerRune(index byte) rune {
614 return []rune{' ', '│', '│', '│', '─', '┘', '┐', '┤', '─', '└', '┌', '├', '├', '┴', '┬', '┼'}[index]
615}
616
617func corner(v *View, directions byte) rune {
618 index := v.Overlaps | directions
619 return cornerRune(index)
620}
621
622// drawFrameCorners draws the corners of the view.
623func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error {
624 if v.y0 == v.y1 {
625 if !g.SupportOverlaps && v.x0 >= 0 && v.x1 >= 0 && v.y0 >= 0 && v.x0 < g.maxX && v.x1 < g.maxX && v.y0 < g.maxY {
626 if err := g.SetRune(v.x0, v.y0, '╶', fgColor, bgColor); err != nil {
627 return err
628 }
629 if err := g.SetRune(v.x1, v.y0, '╴', fgColor, bgColor); err != nil {
630 return err
631 }
632 }
633 return nil
634 }
635
636 runeTL, runeTR, runeBL, runeBR := '┌', '┐', '└', '┘'
637 if g.SupportOverlaps {
638 runeTL = corner(v, BOTTOM|RIGHT)
639 runeTR = corner(v, BOTTOM|LEFT)
640 runeBL = corner(v, TOP|RIGHT)
641 runeBR = corner(v, TOP|LEFT)
642 }
643 if g.ASCII {
644 runeTL, runeTR, runeBL, runeBR = '+', '+', '+', '+'
645 }
646
647 corners := []struct {
648 x, y int
649 ch rune
650 }{{v.x0, v.y0, runeTL}, {v.x1, v.y0, runeTR}, {v.x0, v.y1, runeBL}, {v.x1, v.y1, runeBR}}
651
652 for _, c := range corners {
653 if c.x >= 0 && c.y >= 0 && c.x < g.maxX && c.y < g.maxY {
654 if err := g.SetRune(c.x, c.y, c.ch, fgColor, bgColor); err != nil {
655 return err
656 }
657 }
658 }
659 return nil
660}
661
662// drawTitle draws the title of the view.
663func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) error {
664 if v.y0 < 0 || v.y0 >= g.maxY {
665 return nil
666 }
667
668 for i, ch := range v.Title {
669 x := v.x0 + i + 2
670 if x < 0 {
671 continue
672 } else if x > v.x1-2 || x >= g.maxX {
673 break
674 }
675 if err := g.SetRune(x, v.y0, ch, fgColor, bgColor); err != nil {
676 return err
677 }
678 }
679 return nil
680}
681
682// drawSubtitle draws the subtitle of the view.
683func (g *Gui) drawSubtitle(v *View, fgColor, bgColor Attribute) error {
684 if v.y0 < 0 || v.y0 >= g.maxY {
685 return nil
686 }
687
688 start := v.x1 - 5 - len(v.Subtitle)
689 if start < v.x0 {
690 return nil
691 }
692 for i, ch := range v.Subtitle {
693 x := start + i
694 if x >= v.x1 {
695 break
696 }
697 if err := g.SetRune(x, v.y0, ch, fgColor, bgColor); err != nil {
698 return err
699 }
700 }
701 return nil
702}
703
704// draw manages the cursor and calls the draw function of a view.
705func (g *Gui) draw(v *View) error {
706 if g.Cursor {
707 if curview := g.currentView; curview != nil {
708 vMaxX, vMaxY := curview.Size()
709 if curview.cx < 0 {
710 curview.cx = 0
711 } else if curview.cx >= vMaxX {
712 curview.cx = vMaxX - 1
713 }
714 if curview.cy < 0 {
715 curview.cy = 0
716 } else if curview.cy >= vMaxY {
717 curview.cy = vMaxY - 1
718 }
719
720 gMaxX, gMaxY := g.Size()
721 cx, cy := curview.x0+curview.cx+1, curview.y0+curview.cy+1
722 if cx >= 0 && cx < gMaxX && cy >= 0 && cy < gMaxY {
723 termbox.SetCursor(cx, cy)
724 } else {
725 termbox.HideCursor()
726 }
727 }
728 } else {
729 termbox.HideCursor()
730 }
731
732 v.clearRunes()
733 if err := v.draw(); err != nil {
734 return err
735 }
736 return nil
737}
738
739// onKey manages key-press events. A keybinding handler is called when
740// a key-press or mouse event satisfies a configured keybinding. Furthermore,
741// currentView's internal buffer is modified if currentView.Editable is true.
742func (g *Gui) onKey(ev *termbox.Event) error {
743 switch ev.Type {
744 case termbox.EventKey:
745 matched, err := g.execKeybindings(g.currentView, ev)
746 if err != nil {
747 return err
748 }
749 if matched {
750 break
751 }
752 if g.currentView != nil && g.currentView.Editable && g.currentView.Editor != nil {
753 g.currentView.Editor.Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod))
754 }
755 case termbox.EventMouse:
756 mx, my := ev.MouseX, ev.MouseY
757 v, err := g.ViewByPosition(mx, my)
758 if err != nil {
759 break
760 }
761 if err := v.SetCursor(mx-v.x0-1, my-v.y0-1); err != nil {
762 return err
763 }
764 if _, err := g.execKeybindings(v, ev); err != nil {
765 return err
766 }
767 }
768
769 return nil
770}
771
772// execKeybindings executes the keybinding handlers that match the passed view
773// and event. The value of matched is true if there is a match and no errors.
774func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err error) {
775 var globalKb *keybinding
776
777 for _, kb := range g.keybindings {
778 if kb.handler == nil {
779 continue
780 }
781
782 if !kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) {
783 continue
784 }
785
786 if kb.matchView(v) {
787 return g.execKeybinding(v, kb)
788 }
789
790 if kb.viewName == "" && ((v != nil && !v.Editable) || kb.ch == 0) {
791 globalKb = kb
792 }
793 }
794
795 if globalKb != nil {
796 return g.execKeybinding(v, globalKb)
797 }
798
799 return false, nil
800}
801
802// execKeybinding executes a given keybinding
803func (g *Gui) execKeybinding(v *View, kb *keybinding) (bool, error) {
804 if g.isBlacklisted(kb.key) {
805 return true, nil
806 }
807
808 if err := kb.handler(g, v); err != nil {
809 return false, err
810 }
811 return true, nil
812}
813
814// isBlacklisted reports whether the key is blacklisted
815func (g *Gui) isBlacklisted(k Key) bool {
816 for _, j := range g.blacklist {
817 if j == k {
818 return true
819 }
820 }
821 return false
822}
823
824// IsUnknownView reports whether the contents of an error is "unknown view".
825func IsUnknownView(err error) bool {
826 return err != nil && err.Error() == ErrUnknownView.Error()
827}
828
829// IsQuit reports whether the contents of an error is "quit".
830func IsQuit(err error) bool {
831 return err != nil && err.Error() == ErrQuit.Error()
832}