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}