termui.go

  1package termui
  2
  3import (
  4	"github.com/MichaelMure/git-bug/cache"
  5	"github.com/MichaelMure/git-bug/input"
  6	"github.com/MichaelMure/git-bug/repository"
  7	"github.com/jroimartin/gocui"
  8	"github.com/pkg/errors"
  9)
 10
 11var errTerminateMainloop = errors.New("terminate gocui mainloop")
 12
 13type termUI struct {
 14	g            *gocui.Gui
 15	gError       chan error
 16	cache        cache.RepoCacher
 17	activeWindow window
 18
 19	bugTable   *bugTable
 20	errorPopup *errorPopup
 21}
 22
 23var ui *termUI
 24
 25type window interface {
 26	keybindings(g *gocui.Gui) error
 27	layout(g *gocui.Gui) error
 28}
 29
 30func Run(repo repository.Repo) error {
 31	c := cache.NewRepoCache(repo)
 32
 33	ui = &termUI{
 34		gError:     make(chan error, 1),
 35		cache:      c,
 36		bugTable:   newBugTable(c),
 37		errorPopup: newErrorPopup(),
 38	}
 39
 40	ui.activeWindow = ui.bugTable
 41
 42	initGui()
 43
 44	err := <-ui.gError
 45
 46	if err != nil && err != gocui.ErrQuit {
 47		return err
 48	}
 49
 50	return nil
 51}
 52
 53func initGui() {
 54	g, err := gocui.NewGui(gocui.OutputNormal)
 55
 56	if err != nil {
 57		ui.gError <- err
 58		return
 59	}
 60
 61	ui.g = g
 62
 63	ui.g.SetManagerFunc(layout)
 64
 65	err = keybindings(ui.g)
 66
 67	if err != nil {
 68		ui.g.Close()
 69		ui.g = nil
 70		ui.gError <- err
 71		return
 72	}
 73
 74	err = g.MainLoop()
 75
 76	if err != nil && err != errTerminateMainloop {
 77		if ui.g != nil {
 78			ui.g.Close()
 79		}
 80		ui.gError <- err
 81	}
 82
 83	return
 84}
 85
 86func layout(g *gocui.Gui) error {
 87	g.Cursor = false
 88
 89	if err := ui.activeWindow.layout(g); err != nil {
 90		return err
 91	}
 92
 93	if err := ui.errorPopup.layout(g); err != nil {
 94		return err
 95	}
 96
 97	return nil
 98}
 99
100func keybindings(g *gocui.Gui) error {
101	// Quit
102	if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
103		return err
104	}
105
106	if err := ui.bugTable.keybindings(g); err != nil {
107		return err
108	}
109
110	if err := ui.errorPopup.keybindings(g); err != nil {
111		return err
112	}
113
114	return nil
115}
116
117func quit(g *gocui.Gui, v *gocui.View) error {
118	return gocui.ErrQuit
119}
120
121func newBugWithEditor(g *gocui.Gui, v *gocui.View) error {
122	// This is somewhat hacky.
123	// As there is no way to pause gocui, run the editor, restart gocui,
124	// we have to stop it entirely and start a new one later.
125	//
126	// - an error channel is used to route the returned error of this new
127	// 		instance into the original launch function
128	// - a custom error (errTerminateMainloop) is used to terminate the original
129	//		instance's mainLoop. This error is then filtered.
130
131	ui.g.Close()
132	ui.g = nil
133
134	title, message, err := input.BugCreateEditorInput(ui.cache.Repository(), "", "")
135
136	if err != nil && err != input.ErrEmptyTitle {
137		return err
138	}
139
140	if err == input.ErrEmptyTitle {
141		ui.errorPopup.err = err.Error()
142	} else {
143		_, err = ui.cache.NewBug(title, message)
144		if err != nil {
145			return err
146		}
147	}
148
149	initGui()
150
151	return errTerminateMainloop
152}
153
154func maxInt(a, b int) int {
155	if a > b {
156		return a
157	}
158	return b
159}
160
161func minInt(a, b int) int {
162	if a > b {
163		return b
164	}
165	return a
166}