gui.go

  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}