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