label_select.go

  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
183				g.Update(func(gui *gocui.Gui) error {
184					return nil
185				})
186				return
187			}
188		}
189
190		// Add new label, make it selected, and move frame
191		ls.labels = append(ls.labels, bug.Label(input))
192		ls.labelSelect = append(ls.labelSelect, true)
193		ls.selected = len(ls.labels) - 1
194
195		g.Update(func(gui *gocui.Gui) error {
196			return nil
197		})
198	}()
199	return nil
200}
201
202func (ls *labelSelect) abort(g *gocui.Gui, v *gocui.View) error {
203	return ui.activateWindow(ui.showBug)
204}
205
206func (ls *labelSelect) saveAndReturn(g *gocui.Gui, v *gocui.View) error {
207	bugLabels := ls.bug.Snapshot().Labels
208	selectedLabels := []bug.Label{}
209	for i, label := range ls.labels {
210		if ls.labelSelect[i] {
211			selectedLabels = append(selectedLabels, label)
212		}
213	}
214
215	// Find the new and removed labels. This makes use of the fact that the first elements
216	// of selectedLabels are the not-removed labels in bugLabels
217	newLabels := []string{}
218	rmLabels := []string{}
219	i := 0	// Index for bugLabels
220	j := 0	// Index for selectedLabels
221	for {
222		if j == len(selectedLabels) {
223			// No more labels to consider
224			break
225		} else if i == len(bugLabels) {
226			// Remaining labels are all new
227			newLabels = append(newLabels, selectedLabels[j].String())
228			j += 1
229		} else if bugLabels[i] == selectedLabels[j] {
230			// Labels match. Move to next pair
231			i += 1
232			j += 1
233		} else {
234			// Labels don't match. Prelabel must have been removed
235			rmLabels = append(rmLabels, bugLabels[i].String())
236			i += 1
237		}
238	}
239
240	if _, err := ls.bug.ChangeLabels(newLabels, rmLabels); err != nil {
241		ui.msgPopup.Activate(msgPopupErrorTitle, err.Error())
242	}
243
244	return ui.activateWindow(ui.showBug)
245}