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