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