1package termui
2import (
3 "fmt"
4 "strings"
5 "github.com/jroimartin/gocui"
6 "github.com/MichaelMure/git-bug/bug"
7 "github.com/MichaelMure/git-bug/cache"
8)
9const labelSelectView = "labelSelectView"
10const labelSelectInstructionsView = "labelSelectInstructionsView"
11
12type labelSelect struct {
13 cache *cache.RepoCache
14 bug *cache.BugCache
15 labels []bug.Label
16 labelSelect []bool
17 selected int
18 childViews []string
19}
20
21func newLabelSelect() *labelSelect {
22 return &labelSelect{}
23}
24
25func (ls *labelSelect) SetBug(cache *cache.RepoCache, bug *cache.BugCache) {
26 ls.cache = cache
27 ls.bug = bug
28 ls.labels = cache.ValidLabels()
29
30 // Find which labels are currently applied to the bug
31 bugLabels := bug.Snapshot().Labels
32 labelSelect := make([]bool, len(ls.labels))
33 for i, label := range ls.labels {
34 for _, bugLabel := range bugLabels {
35 if label == bugLabel {
36 labelSelect[i] = true
37 break
38 }
39 }
40 }
41
42 ls.labelSelect = labelSelect
43 ls.selected = 0
44}
45
46func (ls *labelSelect) keybindings(g *gocui.Gui) error {
47 // Abort
48 if err := g.SetKeybinding(labelSelectView, gocui.KeyEsc, gocui.ModNone, ls.abort); err != nil {
49 return err
50 }
51 // Save and return
52 if err := g.SetKeybinding(labelSelectView, 'q', gocui.ModNone, ls.saveAndReturn); err != nil {
53 return err
54 }
55 // Up
56 if err := g.SetKeybinding(labelSelectView, gocui.KeyArrowUp, gocui.ModNone, ls.selectPrevious); err != nil {
57 return err
58 }
59 if err := g.SetKeybinding(labelSelectView, 'k', gocui.ModNone, ls.selectPrevious); err != nil {
60 return err
61 }
62 // Down
63 if err := g.SetKeybinding(labelSelectView, gocui.KeyArrowDown, gocui.ModNone, ls.selectNext); err != nil {
64 return err
65 }
66 if err := g.SetKeybinding(labelSelectView, 'j', gocui.ModNone, ls.selectNext); err != nil {
67 return err
68 }
69 // Select
70 if err := g.SetKeybinding(labelSelectView, gocui.KeySpace, gocui.ModNone, ls.selectItem); err != nil {
71 return err
72 }
73 if err := g.SetKeybinding(labelSelectView, 'x', gocui.ModNone, ls.selectItem); err != nil {
74 return err
75 }
76 if err := g.SetKeybinding(labelSelectView, gocui.KeyEnter, gocui.ModNone, ls.selectItem); err != nil {
77 return err
78 }
79 // Add
80 if err := g.SetKeybinding(labelSelectView, 'a', gocui.ModNone, ls.addItem); err != nil {
81 return err
82 }
83 return nil
84}
85
86func (ls *labelSelect) layout(g *gocui.Gui) error {
87 maxX, maxY := g.Size()
88 ls.childViews = nil
89
90 // TODO: Make width adaptive
91 width := 30
92 height := 2*len(ls.labels) + 3
93 x0 := 2
94 y0 := 2
95
96 v, err := g.SetView(labelSelectView, x0, y0, x0+width, y0+height)
97 if err != nil {
98 if err != gocui.ErrUnknownView {
99 return err
100 }
101
102 v.Frame = false
103 }
104 y0 += 1
105
106 for i, label := range ls.labels {
107 viewname := fmt.Sprintf("view%d", i)
108 v, err := g.SetView(viewname, x0+2, y0, x0+width-2, y0+2)
109 if err != nil && err != gocui.ErrUnknownView {
110 return err
111 }
112 ls.childViews = append(ls.childViews, viewname)
113 v.Frame = i == ls.selected
114 v.Clear()
115 selectBox := " [ ] "
116 if ls.labelSelect[i] {
117 selectBox = " [x] "
118 }
119 fmt.Fprint(v, selectBox, label)
120 y0 += 2
121 }
122
123 v, err = g.SetView(labelSelectInstructionsView, -1, maxY-2, maxX, maxY)
124 ls.childViews = append(ls.childViews, labelSelectInstructionsView)
125 if err != nil {
126 if err != gocui.ErrUnknownView{
127 return err
128 }
129 v.Frame = false
130 v.BgColor = gocui.ColorBlue
131 }
132 v.Clear()
133 fmt.Fprint(v, "[↓↑,jk] Nav [a] Add item [q] Save and close")
134 if _, err = g.SetViewOnTop(labelSelectInstructionsView); err != nil {
135 return err
136 }
137 if _, err := g.SetCurrentView(labelSelectView); err != nil {
138 return err
139 }
140 return nil
141}
142
143func (ls *labelSelect) disable(g *gocui.Gui) error {
144 for _, view := range ls.childViews {
145 if err := g.DeleteView(view); err != nil && err != gocui.ErrUnknownView {
146 return err
147 }
148 }
149 return nil
150}
151
152func(ls *labelSelect) selectPrevious(g *gocui.Gui, v*gocui.View) error {
153 ls.selected = maxInt(0, ls.selected-1)
154 return nil
155}
156
157func(ls *labelSelect) selectNext(g *gocui.Gui, v*gocui.View) error {
158 ls.selected = minInt(len(ls.labels)-1, ls.selected+1)
159 return nil
160}
161
162func(ls *labelSelect) selectItem(g *gocui.Gui, v*gocui.View) error {
163 ls.labelSelect[ls.selected] = !ls.labelSelect[ls.selected]
164 return nil
165}
166
167func (ls *labelSelect) addItem(g *gocui.Gui, v *gocui.View) error {
168 c := ui.inputPopup.Activate("Add a new label")
169
170 go func() {
171 input := <-c
172
173 // Standardize label format
174 input = strings.TrimSuffix(input, "\n")
175 input = strings.Replace(input, " ", "-", -1)
176
177 // Check if label already exists
178 for i, label := range ls.labels {
179 if input == label.String() {
180 ls.labelSelect[i] = true
181 ls.selected = i
182 return
183 }
184 }
185
186 // Add new label, make it selected, and move frame
187 ls.labels = append(ls.labels, bug.Label(input))
188 ls.labelSelect = append(ls.labelSelect, true)
189 ls.selected = len(ls.labels) - 1
190 }()
191 return nil
192}
193
194func (ls *labelSelect) abort(g *gocui.Gui, v *gocui.View) error {
195 return ui.activateWindow(ui.showBug)
196}
197
198func (ls *labelSelect) saveAndReturn(g *gocui.Gui, v *gocui.View) error {
199 bugLabels := ls.bug.Snapshot().Labels
200 selectedLabels := []bug.Label{}
201 for i, label := range ls.labels {
202 if ls.labelSelect[i] {
203 selectedLabels = append(selectedLabels, label)
204 }
205 }
206
207 // Find the new and removed labels. This makes use of the fact that the first elements
208 // of selectedLabels are the not-removed labels in bugLabels
209 newLabels := []string{}
210 rmLabels := []string{}
211 i := 0 // Index for bugLabels
212 j := 0 // Index for selectedLabels
213 for {
214 if j == len(selectedLabels) {
215 // No more labels to consider
216 break
217 } else if i == len(bugLabels) {
218 // Remaining labels are all new
219 newLabels = append(newLabels, selectedLabels[j].String())
220 j += 1
221 } else if bugLabels[i] == selectedLabels[j] {
222 // Labels match. Move to next pair
223 i += 1
224 j += 1
225 } else {
226 // Labels don't match. Prelabel must have been removed
227 rmLabels = append(rmLabels, bugLabels[i].String())
228 i += 1
229 }
230 }
231
232 if _, err := ls.bug.ChangeLabels(newLabels, rmLabels); err != nil {
233 ui.msgPopup.Activate(msgPopupErrorTitle, err.Error())
234 }
235
236 return ui.activateWindow(ui.showBug)
237}
238
239// func (ls *labelSelect) Activate(labels []bug.Label, sel []bool) <-chan []bug.Label {
240// ls.labels = labels
241// ls.labelSelect = sel
242// ls.selected = 0
243// ls.c = make(chan []bug.Label)
244// return ls.c
245// }