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.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("labeledit%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}