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