label_select.go

  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}