1package termui
2
3import (
4 "fmt"
5 "strings"
6
7 "github.com/awesome-gocui/gocui"
8
9 "github.com/MichaelMure/git-bug/bug"
10 "github.com/MichaelMure/git-bug/cache"
11)
12
13const labelSelectView = "labelSelectView"
14const labelSelectInstructionsView = "labelSelectInstructionsView"
15
16var labelSelectHelp = helpBar{
17 {"q", "Save and close"},
18 {"↓↑,jk", "Nav"},
19 {"a", "Add item"},
20}
21
22type labelSelect struct {
23 cache *cache.RepoCache
24 bug *cache.BugCache
25 labels []bug.Label
26 labelSelect []bool
27 selected int
28 scroll int
29 childViews []string
30}
31
32func newLabelSelect() *labelSelect {
33 return &labelSelect{}
34}
35
36func (ls *labelSelect) SetBug(cache *cache.RepoCache, bug *cache.BugCache) {
37 ls.cache = cache
38 ls.bug = bug
39 ls.labels = cache.ValidLabels()
40
41 // Find which labels are currently applied to the bug
42 bugLabels := bug.Snapshot().Labels
43 labelSelect := make([]bool, len(ls.labels))
44 for i, label := range ls.labels {
45 for _, bugLabel := range bugLabels {
46 if label == bugLabel {
47 labelSelect[i] = true
48 break
49 }
50 }
51 }
52
53 ls.labelSelect = labelSelect
54 if len(labelSelect) > 0 {
55 ls.selected = 0
56 } else {
57 ls.selected = -1
58 }
59
60 ls.scroll = 0
61}
62
63func (ls *labelSelect) keybindings(g *gocui.Gui) error {
64 // Abort
65 if err := g.SetKeybinding(labelSelectView, gocui.KeyEsc, gocui.ModNone, ls.abort); err != nil {
66 return err
67 }
68 // Save and return
69 if err := g.SetKeybinding(labelSelectView, 'q', gocui.ModNone, ls.saveAndReturn); err != nil {
70 return err
71 }
72 // Up
73 if err := g.SetKeybinding(labelSelectView, gocui.KeyArrowUp, gocui.ModNone, ls.selectPrevious); err != nil {
74 return err
75 }
76 if err := g.SetKeybinding(labelSelectView, 'k', gocui.ModNone, ls.selectPrevious); err != nil {
77 return err
78 }
79 // Down
80 if err := g.SetKeybinding(labelSelectView, gocui.KeyArrowDown, gocui.ModNone, ls.selectNext); err != nil {
81 return err
82 }
83 if err := g.SetKeybinding(labelSelectView, 'j', gocui.ModNone, ls.selectNext); err != nil {
84 return err
85 }
86 // Select
87 if err := g.SetKeybinding(labelSelectView, gocui.KeySpace, gocui.ModNone, ls.selectItem); err != nil {
88 return err
89 }
90 if err := g.SetKeybinding(labelSelectView, 'x', gocui.ModNone, ls.selectItem); err != nil {
91 return err
92 }
93 if err := g.SetKeybinding(labelSelectView, gocui.KeyEnter, gocui.ModNone, ls.selectItem); err != nil {
94 return err
95 }
96 // Add
97 if err := g.SetKeybinding(labelSelectView, 'a', gocui.ModNone, ls.addItem); err != nil {
98 return err
99 }
100 return nil
101}
102
103func (ls *labelSelect) layout(g *gocui.Gui) error {
104 maxX, maxY := g.Size()
105 ls.childViews = nil
106
107 width := 5
108 for _, label := range ls.labels {
109 width = maxInt(width, len(label.String()))
110 }
111 width += 10
112 x0 := 1
113 y0 := 0 - ls.scroll
114
115 v, err := g.SetView(labelSelectView, x0, 0, x0+width, maxY-2, 0)
116 if err != nil {
117 if !gocui.IsUnknownView(err) {
118 return err
119 }
120
121 v.Frame = false
122 }
123
124 for i, label := range ls.labels {
125 viewname := fmt.Sprintf("labeledit%d", i)
126 v, err := g.SetView(viewname, x0+2, y0, x0+width+2, y0+2, 0)
127 if err != nil && !gocui.IsUnknownView(err) {
128 return err
129 }
130 ls.childViews = append(ls.childViews, viewname)
131 v.Frame = i == ls.selected
132 v.Clear()
133 selectBox := " [ ] "
134 if ls.labelSelect[i] {
135 selectBox = " [x] "
136 }
137
138 lc := label.Color()
139 lc256 := lc.Term256()
140 labelStr := lc256.Escape() + "◼ " + lc256.Unescape() + label.String()
141 _, _ = fmt.Fprint(v, selectBox, labelStr)
142
143 y0 += 2
144 }
145
146 v, err = g.SetView(labelSelectInstructionsView, -1, maxY-2, maxX, maxY, 0)
147 ls.childViews = append(ls.childViews, labelSelectInstructionsView)
148 if err != nil {
149 if !gocui.IsUnknownView(err) {
150 return err
151 }
152 v.Frame = false
153 v.FgColor = gocui.ColorWhite
154 }
155 v.Clear()
156 _, _ = fmt.Fprint(v, labelSelectHelp.Render())
157 if _, err = g.SetViewOnTop(labelSelectInstructionsView); err != nil {
158 return err
159 }
160 if _, err := g.SetCurrentView(labelSelectView); err != nil {
161 return err
162 }
163 return nil
164}
165
166func (ls *labelSelect) disable(g *gocui.Gui) error {
167 for _, view := range ls.childViews {
168 if err := g.DeleteView(view); err != nil && !gocui.IsUnknownView(err) {
169 return err
170 }
171 }
172 return nil
173}
174
175func (ls *labelSelect) focusView(g *gocui.Gui) error {
176 if ls.selected < 0 {
177 return nil
178 }
179
180 _, lsy0, _, lsy1, err := g.ViewPosition(labelSelectView)
181 if err != nil {
182 return err
183 }
184
185 _, vy0, _, vy1, err := g.ViewPosition(fmt.Sprintf("labeledit%d", ls.selected))
186 if err != nil {
187 return err
188 }
189
190 // Below bottom of frame
191 if vy1 > lsy1 {
192 ls.scroll += vy1 - lsy1
193 return nil
194 }
195
196 // Above top of frame
197 if vy0 < lsy0 {
198 ls.scroll -= lsy0 - vy0
199 }
200
201 return nil
202}
203
204func (ls *labelSelect) selectPrevious(g *gocui.Gui, v *gocui.View) error {
205 if ls.selected < 0 {
206 return nil
207 }
208
209 ls.selected = maxInt(0, ls.selected-1)
210 return ls.focusView(g)
211}
212
213func (ls *labelSelect) selectNext(g *gocui.Gui, v *gocui.View) error {
214 if ls.selected < 0 {
215 return nil
216 }
217
218 ls.selected = minInt(len(ls.labels)-1, ls.selected+1)
219 return ls.focusView(g)
220}
221
222func (ls *labelSelect) selectItem(g *gocui.Gui, v *gocui.View) error {
223 if ls.selected < 0 {
224 return nil
225 }
226
227 ls.labelSelect[ls.selected] = !ls.labelSelect[ls.selected]
228 return nil
229}
230
231func (ls *labelSelect) addItem(g *gocui.Gui, v *gocui.View) error {
232 c := ui.inputPopup.Activate("Add a new label")
233
234 go func() {
235 input := <-c
236
237 // Standardize label format
238 input = strings.TrimSuffix(input, "\n")
239 input = strings.Replace(input, " ", "-", -1)
240
241 // Check if label already exists
242 for i, label := range ls.labels {
243 if input == label.String() {
244 ls.labelSelect[i] = true
245 ls.selected = i
246
247 g.Update(func(gui *gocui.Gui) error {
248 return ls.focusView(g)
249 })
250
251 return
252 }
253 }
254
255 // Add new label, make it selected, and focus
256 ls.labels = append(ls.labels, bug.Label(input))
257 ls.labelSelect = append(ls.labelSelect, true)
258 ls.selected = len(ls.labels) - 1
259
260 g.Update(func(g *gocui.Gui) error {
261 return nil
262 })
263 }()
264
265 return nil
266}
267
268func (ls *labelSelect) abort(g *gocui.Gui, v *gocui.View) error {
269 return ui.activateWindow(ui.showBug)
270}
271
272func (ls *labelSelect) saveAndReturn(g *gocui.Gui, v *gocui.View) error {
273 bugLabels := ls.bug.Snapshot().Labels
274 var selectedLabels []bug.Label
275 for i, label := range ls.labels {
276 if ls.labelSelect[i] {
277 selectedLabels = append(selectedLabels, label)
278 }
279 }
280
281 // Find the new and removed labels. This could be implemented more efficiently...
282 var newLabels []string
283 var rmLabels []string
284
285 for _, selectedLabel := range selectedLabels {
286 found := false
287 for _, bugLabel := range bugLabels {
288 if selectedLabel == bugLabel {
289 found = true
290 }
291 }
292
293 if !found {
294 newLabels = append(newLabels, string(selectedLabel))
295 }
296 }
297
298 for _, bugLabel := range bugLabels {
299 found := false
300 for _, selectedLabel := range selectedLabels {
301 if bugLabel == selectedLabel {
302 found = true
303 }
304 }
305
306 if !found {
307 rmLabels = append(rmLabels, string(bugLabel))
308 }
309 }
310
311 if _, _, err := ls.bug.ChangeLabels(newLabels, rmLabels); err != nil {
312 ui.msgPopup.Activate(msgPopupErrorTitle, err.Error())
313 }
314
315 return ui.activateWindow(ui.showBug)
316}