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))
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("view%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.BgColor = gocui.ColorBlue
148 }
149 v.Clear()
150 fmt.Fprint(v, "[q] Save and close [↓↑,jk] Nav [a] Add item")
151 if _, err = g.SetViewOnTop(labelSelectInstructionsView); err != nil {
152 return err
153 }
154 if _, err := g.SetCurrentView(labelSelectView); err != nil {
155 return err
156 }
157 return nil
158}
159
160func (ls *labelSelect) disable(g *gocui.Gui) error {
161 for _, view := range ls.childViews {
162 if err := g.DeleteView(view); err != nil && !gocui.IsUnknownView(err) {
163 return err
164 }
165 }
166 return nil
167}
168
169func (ls *labelSelect) focusView(g *gocui.Gui) error {
170 if ls.selected < 0 {
171 return nil
172 }
173
174 _, lsy0, _, lsy1, err := g.ViewPosition(labelSelectView)
175 if err != nil {
176 return err
177 }
178
179 _, vy0, _, vy1, err := g.ViewPosition(fmt.Sprintf("view%d", ls.selected))
180 if err != nil {
181 return err
182 }
183
184 // Below bottom of frame
185 if vy1 > lsy1 {
186 ls.scroll += vy1 - lsy1
187 return nil
188 }
189
190 // Above top of frame
191 if vy0 < lsy0 {
192 ls.scroll -= lsy0 - vy0
193 }
194
195 return nil
196}
197
198func (ls *labelSelect) selectPrevious(g *gocui.Gui, v *gocui.View) error {
199 if ls.selected < 0 {
200 return nil
201 }
202
203 ls.selected = maxInt(0, ls.selected-1)
204 return ls.focusView(g)
205}
206
207func (ls *labelSelect) selectNext(g *gocui.Gui, v *gocui.View) error {
208 if ls.selected < 0 {
209 return nil
210 }
211
212 ls.selected = minInt(len(ls.labels)-1, ls.selected+1)
213 return ls.focusView(g)
214}
215
216func (ls *labelSelect) selectItem(g *gocui.Gui, v *gocui.View) error {
217 if ls.selected < 0 {
218 return nil
219 }
220
221 ls.labelSelect[ls.selected] = !ls.labelSelect[ls.selected]
222 return nil
223}
224
225func (ls *labelSelect) addItem(g *gocui.Gui, v *gocui.View) error {
226 c := ui.inputPopup.Activate("Add a new label")
227
228 go func() {
229 input := <-c
230
231 // Standardize label format
232 input = strings.TrimSuffix(input, "\n")
233 input = strings.Replace(input, " ", "-", -1)
234
235 // Check if label already exists
236 for i, label := range ls.labels {
237 if input == label.String() {
238 ls.labelSelect[i] = true
239 ls.selected = i
240
241 g.Update(func(gui *gocui.Gui) error {
242 return ls.focusView(g)
243 })
244
245 return
246 }
247 }
248
249 // Add new label, make it selected, and focus
250 ls.labels = append(ls.labels, bug.Label(input))
251 ls.labelSelect = append(ls.labelSelect, true)
252 ls.selected = len(ls.labels) - 1
253
254 g.Update(func(g *gocui.Gui) error {
255 return nil
256 })
257 }()
258
259 return nil
260}
261
262func (ls *labelSelect) abort(g *gocui.Gui, v *gocui.View) error {
263 return ui.activateWindow(ui.showBug)
264}
265
266func (ls *labelSelect) saveAndReturn(g *gocui.Gui, v *gocui.View) error {
267 bugLabels := ls.bug.Snapshot().Labels
268 var selectedLabels []bug.Label
269 for i, label := range ls.labels {
270 if ls.labelSelect[i] {
271 selectedLabels = append(selectedLabels, label)
272 }
273 }
274
275 // Find the new and removed labels. This could be implemented more efficiently...
276 var newLabels []string
277 var rmLabels []string
278
279 for _, selectedLabel := range selectedLabels {
280 found := false
281 for _, bugLabel := range bugLabels {
282 if selectedLabel == bugLabel {
283 found = true
284 }
285 }
286
287 if !found {
288 newLabels = append(newLabels, string(selectedLabel))
289 }
290 }
291
292 for _, bugLabel := range bugLabels {
293 found := false
294 for _, selectedLabel := range selectedLabels {
295 if bugLabel == selectedLabel {
296 found = true
297 }
298 }
299
300 if !found {
301 rmLabels = append(rmLabels, string(bugLabel))
302 }
303 }
304
305 if _, _, err := ls.bug.ChangeLabels(newLabels, rmLabels); err != nil {
306 ui.msgPopup.Activate(msgPopupErrorTitle, err.Error())
307 }
308
309 return ui.activateWindow(ui.showBug)
310}