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.err = err.Error()
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}