1package termui
2
3import (
4 "fmt"
5
6 "github.com/MichaelMure/git-bug/bug/operations"
7 "github.com/MichaelMure/git-bug/cache"
8 "github.com/MichaelMure/git-bug/util"
9 "github.com/jroimartin/gocui"
10)
11
12const showBugView = "showBugView"
13const showBugSidebarView = "showBugSidebarView"
14const showBugInstructionView = "showBugInstructionView"
15
16const timeLayout = "Jan _2 2006"
17
18type showBug struct {
19 cache cache.RepoCacher
20 bug cache.BugCacher
21 childViews []string
22 selectableView []string
23 selected string
24}
25
26func newShowBug(cache cache.RepoCacher) *showBug {
27 return &showBug{
28 cache: cache,
29 }
30}
31
32func (sb *showBug) layout(g *gocui.Gui) error {
33 maxX, maxY := g.Size()
34
35 v, err := g.SetView(showBugView, 0, 0, maxX*2/3, maxY-2)
36
37 if err != nil {
38 if err != gocui.ErrUnknownView {
39 return err
40 }
41
42 sb.childViews = append(sb.childViews, showBugView)
43 v.Frame = false
44 }
45
46 v.Clear()
47 err = sb.renderMain(g, v)
48 if err != nil {
49 return err
50 }
51
52 v, err = g.SetView(showBugSidebarView, maxX*2/3+1, 0, maxX-1, maxY-2)
53
54 if err != nil {
55 if err != gocui.ErrUnknownView {
56 return err
57 }
58
59 sb.childViews = append(sb.childViews, showBugSidebarView)
60 v.Frame = true
61 }
62
63 v.Clear()
64 sb.renderSidebar(v)
65
66 v, err = g.SetView(showBugInstructionView, -1, maxY-2, maxX, maxY)
67
68 if err != nil {
69 if err != gocui.ErrUnknownView {
70 return err
71 }
72
73 sb.childViews = append(sb.childViews, showBugInstructionView)
74 v.Frame = false
75 v.BgColor = gocui.ColorBlue
76
77 fmt.Fprintf(v, "[q] Return [c] Comment [t] Change title [↓,j] Down [↑,k] Up")
78 }
79
80 _, err = g.SetCurrentView(showBugView)
81 return err
82}
83
84func (sb *showBug) keybindings(g *gocui.Gui) error {
85 // Return
86 if err := g.SetKeybinding(showBugView, 'q', gocui.ModNone, sb.back); err != nil {
87 return err
88 }
89
90 // Scrolling
91 if err := g.SetKeybinding(showBugView, gocui.KeyPgup, gocui.ModNone,
92 sb.scrollUp); err != nil {
93 return err
94 }
95 if err := g.SetKeybinding(showBugView, gocui.KeyPgdn, gocui.ModNone,
96 sb.scrollDown); err != nil {
97 return err
98 }
99
100 // Down
101 if err := g.SetKeybinding(showBugView, 'j', gocui.ModNone,
102 sb.selectNext); err != nil {
103 return err
104 }
105 if err := g.SetKeybinding(showBugView, gocui.KeyArrowDown, gocui.ModNone,
106 sb.selectNext); err != nil {
107 return err
108 }
109 // Up
110 if err := g.SetKeybinding(showBugView, 'k', gocui.ModNone,
111 sb.selectPrevious); err != nil {
112 return err
113 }
114 if err := g.SetKeybinding(showBugView, gocui.KeyArrowUp, gocui.ModNone,
115 sb.selectPrevious); err != nil {
116 return err
117 }
118
119 // Comment
120 if err := g.SetKeybinding(showBugView, 'c', gocui.ModNone,
121 sb.comment); err != nil {
122 return err
123 }
124
125 // Title
126 if err := g.SetKeybinding(showBugView, 't', gocui.ModNone,
127 sb.title); err != nil {
128 return err
129 }
130
131 // Labels
132
133 return nil
134}
135
136func (sb *showBug) disable(g *gocui.Gui) error {
137 for _, view := range sb.childViews {
138 if err := g.DeleteView(view); err != nil {
139 return err
140 }
141 }
142 return nil
143}
144
145func (sb *showBug) renderMain(g *gocui.Gui, mainView *gocui.View) error {
146 maxX, _ := mainView.Size()
147 x0, y0, _, _, _ := g.ViewPosition(mainView.Name())
148 snap := sb.bug.Snapshot()
149
150 v, err := g.SetView("showBugHeader", x0, y0, maxX+1, y0+4)
151
152 if err != nil {
153 if err != gocui.ErrUnknownView {
154 return err
155 }
156
157 sb.childViews = append(sb.childViews, "showBugHeader")
158 v.Frame = false
159 }
160 y0 += 4
161
162 v.Clear()
163 header1 := fmt.Sprintf("[%s] %s", snap.HumanId(), snap.Title)
164 fmt.Fprintf(v, util.LeftPaddedString(header1, maxX-1, 0)+"\n\n")
165
166 header2 := fmt.Sprintf("[%s] %s opened this bug on %s",
167 snap.Status, snap.Author.Name, snap.CreatedAt.Format(timeLayout))
168 fmt.Fprintf(v, util.LeftPaddedString(header2, maxX-1, 0))
169
170 for i, op := range snap.Operations {
171 viewName := fmt.Sprintf("op%d", i)
172
173 switch op.(type) {
174
175 case operations.CreateOperation:
176 create := op.(operations.CreateOperation)
177 content, lines := util.TextWrap(create.Message, maxX)
178
179 v, err := sb.createOpView(g, viewName, x0, y0, maxX+1, lines, true)
180 if err != nil {
181 return err
182 }
183 fmt.Fprint(v, content)
184 y0 += lines + 2
185
186 case operations.AddCommentOperation:
187 comment := op.(operations.AddCommentOperation)
188
189 header := fmt.Sprintf("%s commented on %s",
190 comment.Author.Name, comment.Time().Format(timeLayout))
191 header = util.LeftPaddedString(header, maxX, 6)
192 message, lines := util.TextWrap(comment.Message, maxX)
193
194 v, err := sb.createOpView(g, viewName, x0, y0, maxX+1, lines+2, true)
195 if err != nil {
196 return err
197 }
198 fmt.Fprint(v, header, "\n\n", message)
199 y0 += lines + 3
200 }
201 }
202
203 return nil
204}
205
206func (sb *showBug) createOpView(g *gocui.Gui, name string, x0 int, y0 int, maxX int, height int, selectable bool) (*gocui.View, error) {
207 v, err := g.SetView(name, x0, y0, maxX, y0+height+1)
208
209 if err != nil {
210 if err != gocui.ErrUnknownView {
211 return nil, err
212 }
213
214 sb.childViews = append(sb.childViews, name)
215
216 if selectable {
217 sb.selectableView = append(sb.selectableView, name)
218 }
219 }
220
221 v.Frame = sb.selected == name
222
223 v.Clear()
224
225 return v, nil
226}
227
228func (sb *showBug) renderSidebar(v *gocui.View) {
229 maxX, _ := v.Size()
230 snap := sb.bug.Snapshot()
231
232 title := util.LeftPaddedString("LABEL", maxX, 2)
233 fmt.Fprintf(v, title+"\n\n")
234
235 for _, label := range snap.Labels {
236 fmt.Fprintf(v, util.LeftPaddedString(label.String(), maxX, 2))
237 fmt.Fprintln(v)
238 }
239}
240
241func (sb *showBug) back(g *gocui.Gui, v *gocui.View) error {
242 ui.activateWindow(ui.bugTable)
243 return nil
244}
245
246func (sb *showBug) scrollUp(g *gocui.Gui, v *gocui.View) error {
247 return nil
248}
249
250func (sb *showBug) scrollDown(g *gocui.Gui, v *gocui.View) error {
251 return nil
252}
253
254func (sb *showBug) selectPrevious(g *gocui.Gui, v *gocui.View) error {
255 if len(sb.selectableView) == 0 {
256 return nil
257 }
258
259 for i, name := range sb.selectableView {
260 if name == sb.selected {
261 sb.selected = sb.selectableView[maxInt(i-1, 0)]
262 return nil
263 }
264 }
265
266 if sb.selected == "" {
267 sb.selected = sb.selectableView[0]
268 }
269
270 return nil
271}
272
273func (sb *showBug) selectNext(g *gocui.Gui, v *gocui.View) error {
274 if len(sb.selectableView) == 0 {
275 return nil
276 }
277
278 for i, name := range sb.selectableView {
279 if name == sb.selected {
280 sb.selected = sb.selectableView[minInt(i+1, len(sb.childViews)-1)]
281 return nil
282 }
283 }
284
285 if sb.selected == "" {
286 sb.selected = sb.selectableView[0]
287 }
288
289 return nil
290}
291
292func (sb *showBug) comment(g *gocui.Gui, v *gocui.View) error {
293 return addCommentWithEditor(sb.bug)
294}
295
296func (sb *showBug) title(g *gocui.Gui, v *gocui.View) error {
297 return setTitleWithEditor(sb.bug)
298}