bug_table.go

  1package termui
  2
  3import (
  4	"fmt"
  5	"github.com/MichaelMure/git-bug/bug"
  6	"github.com/MichaelMure/git-bug/cache"
  7	"github.com/MichaelMure/git-bug/util"
  8	"github.com/jroimartin/gocui"
  9)
 10
 11const bugTableView = "bugTableView"
 12
 13type bugTable struct {
 14	cache  cache.RepoCacher
 15	allIds []string
 16	bugs   []*bug.Snapshot
 17	cursor int
 18}
 19
 20func newBugTable(cache cache.RepoCacher) *bugTable {
 21	return &bugTable{
 22		cache:  cache,
 23		cursor: 0,
 24	}
 25}
 26
 27func (bt *bugTable) layout(g *gocui.Gui) error {
 28	maxX, maxY := g.Size()
 29
 30	v, err := g.SetView("header", -1, -1, maxX, 3)
 31
 32	if err != nil {
 33		if err != gocui.ErrUnknownView {
 34			return err
 35		}
 36
 37		v.Frame = false
 38	}
 39
 40	v.Clear()
 41	bt.renderHeader(v, maxX)
 42
 43	v, err = g.SetView(bugTableView, -1, 1, maxX, maxY-2)
 44
 45	if err != nil {
 46		if err != gocui.ErrUnknownView {
 47			return err
 48		}
 49
 50		v.Frame = false
 51		v.Highlight = true
 52		v.SelBgColor = gocui.ColorWhite
 53		v.SelFgColor = gocui.ColorBlack
 54
 55		_, err = g.SetCurrentView(bugTableView)
 56
 57		if err != nil {
 58			return err
 59		}
 60	}
 61
 62	_, viewHeight := v.Size()
 63	err = bt.paginate(viewHeight - 1)
 64	if err != nil {
 65		return err
 66	}
 67
 68	err = bt.cursorClamp(v)
 69	if err != nil {
 70		return err
 71	}
 72
 73	v.Clear()
 74	bt.render(v, maxX)
 75
 76	v, err = g.SetView("footer", -1, maxY-3, maxX, maxY)
 77
 78	if err != nil {
 79		if err != gocui.ErrUnknownView {
 80			return err
 81		}
 82
 83		v.Frame = false
 84	}
 85
 86	v.Clear()
 87	bt.renderFooter(v, maxX)
 88
 89	v, err = g.SetView("instructions", -1, maxY-2, maxX, maxY)
 90
 91	if err != nil {
 92		if err != gocui.ErrUnknownView {
 93			return err
 94		}
 95
 96		v.Frame = false
 97		v.BgColor = gocui.ColorBlue
 98
 99		fmt.Fprintf(v, "[q] Quit [←,h] Previous page [↓,j] Down [↑,k] Up [→,l] Next page [enter] Open bug [n] New bug")
100	}
101
102	return nil
103}
104
105func (bt *bugTable) keybindings(g *gocui.Gui) error {
106	// Quit
107	if err := g.SetKeybinding(bugTableView, 'q', gocui.ModNone, quit); err != nil {
108		return err
109	}
110
111	// Down
112	if err := g.SetKeybinding(bugTableView, 'j', gocui.ModNone,
113		bt.cursorDown); err != nil {
114		return err
115	}
116	if err := g.SetKeybinding(bugTableView, gocui.KeyArrowDown, gocui.ModNone,
117		bt.cursorDown); err != nil {
118		return err
119	}
120	// Up
121	if err := g.SetKeybinding(bugTableView, 'k', gocui.ModNone,
122		bt.cursorUp); err != nil {
123		return err
124	}
125	if err := g.SetKeybinding(bugTableView, gocui.KeyArrowUp, gocui.ModNone,
126		bt.cursorUp); err != nil {
127		return err
128	}
129
130	// Previous page
131	if err := g.SetKeybinding(bugTableView, 'h', gocui.ModNone,
132		bt.previousPage); err != nil {
133		return err
134	}
135	if err := g.SetKeybinding(bugTableView, gocui.KeyArrowLeft, gocui.ModNone,
136		bt.previousPage); err != nil {
137		return err
138	}
139	if err := g.SetKeybinding(bugTableView, gocui.KeyPgup, gocui.ModNone,
140		bt.previousPage); err != nil {
141		return err
142	}
143	// Next page
144	if err := g.SetKeybinding(bugTableView, 'l', gocui.ModNone,
145		bt.nextPage); err != nil {
146		return err
147	}
148	if err := g.SetKeybinding(bugTableView, gocui.KeyArrowRight, gocui.ModNone,
149		bt.nextPage); err != nil {
150		return err
151	}
152	if err := g.SetKeybinding(bugTableView, gocui.KeyPgdn, gocui.ModNone,
153		bt.nextPage); err != nil {
154		return err
155	}
156
157	// New bug
158	if err := g.SetKeybinding(bugTableView, 'n', gocui.ModNone,
159		newBugWithEditor); err != nil {
160		return err
161	}
162
163	return nil
164}
165
166func (bt *bugTable) paginate(max int) error {
167	allIds, err := bt.cache.AllBugIds()
168	if err != nil {
169		return err
170	}
171
172	bt.allIds = allIds
173
174	return bt.doPaginate(allIds, max)
175}
176
177func (bt *bugTable) doPaginate(allIds []string, max int) error {
178	// clamp the cursor
179	bt.cursor = maxInt(bt.cursor, 0)
180	bt.cursor = minInt(bt.cursor, len(allIds)-1)
181
182	nb := minInt(len(allIds)-bt.cursor, max)
183
184	// slice the data
185	ids := allIds[bt.cursor : bt.cursor+nb]
186
187	bt.bugs = make([]*bug.Snapshot, len(ids))
188
189	for i, id := range ids {
190		b, err := bt.cache.ResolveBug(id)
191		if err != nil {
192			return err
193		}
194
195		bt.bugs[i] = b.Snapshot()
196	}
197
198	return nil
199}
200
201func (bt *bugTable) getTableLength() int {
202	return len(bt.bugs)
203}
204
205func (bt *bugTable) getColumnWidths(maxX int) map[string]int {
206	m := make(map[string]int)
207	m["id"] = 10
208	m["status"] = 8
209
210	left := maxX - m["id"] - m["status"]
211
212	m["summary"] = maxInt(30, left/3)
213	left -= m["summary"]
214
215	m["author"] = maxInt(left*2/5, 15)
216	m["title"] = maxInt(left-m["author"], 10)
217
218	return m
219}
220
221func (bt *bugTable) render(v *gocui.View, maxX int) {
222	columnWidths := bt.getColumnWidths(maxX)
223
224	for _, b := range bt.bugs {
225		person := bug.Person{}
226		if len(b.Comments) > 0 {
227			create := b.Comments[0]
228			person = create.Author
229		}
230
231		id := util.LeftPaddedString(b.HumanId(), columnWidths["id"], 2)
232		status := util.LeftPaddedString(b.Status.String(), columnWidths["status"], 2)
233		title := util.LeftPaddedString(b.Title, columnWidths["title"], 2)
234		author := util.LeftPaddedString(person.Name, columnWidths["author"], 2)
235		summary := util.LeftPaddedString(b.Summary(), columnWidths["summary"], 2)
236
237		fmt.Fprintf(v, "%s %s %s %s %s\n", id, status, title, author, summary)
238	}
239}
240
241func (bt *bugTable) renderHeader(v *gocui.View, maxX int) {
242	columnWidths := bt.getColumnWidths(maxX)
243
244	id := util.LeftPaddedString("ID", columnWidths["id"], 2)
245	status := util.LeftPaddedString("STATUS", columnWidths["status"], 2)
246	title := util.LeftPaddedString("TITLE", columnWidths["title"], 2)
247	author := util.LeftPaddedString("AUTHOR", columnWidths["author"], 2)
248	summary := util.LeftPaddedString("SUMMARY", columnWidths["summary"], 2)
249
250	fmt.Fprintf(v, "\n")
251	fmt.Fprintf(v, "%s %s %s %s %s\n", id, status, title, author, summary)
252
253}
254
255func (bt *bugTable) renderFooter(v *gocui.View, maxX int) {
256	fmt.Fprintf(v, "Showing %d of %d bugs", len(bt.bugs), len(bt.allIds))
257}
258
259func (bt *bugTable) cursorDown(g *gocui.Gui, v *gocui.View) error {
260	_, y := v.Cursor()
261	y = minInt(y+1, bt.getTableLength()-1)
262
263	err := v.SetCursor(0, y)
264	if err != nil {
265		return err
266	}
267
268	return nil
269}
270
271func (bt *bugTable) cursorUp(g *gocui.Gui, v *gocui.View) error {
272	_, y := v.Cursor()
273	y = maxInt(y-1, 0)
274
275	err := v.SetCursor(0, y)
276	if err != nil {
277		return err
278	}
279
280	return nil
281}
282
283func (bt *bugTable) cursorClamp(v *gocui.View) error {
284	_, y := v.Cursor()
285
286	y = minInt(y, bt.getTableLength()-1)
287	y = maxInt(y, 0)
288
289	err := v.SetCursor(0, y)
290	if err != nil {
291		return err
292	}
293
294	return nil
295}
296
297func (bt *bugTable) nextPage(g *gocui.Gui, v *gocui.View) error {
298	_, max := v.Size()
299
300	allIds, err := bt.cache.AllBugIds()
301	if err != nil {
302		return err
303	}
304
305	bt.allIds = allIds
306
307	if bt.cursor+max >= len(allIds) {
308		return nil
309	}
310
311	bt.cursor += max
312
313	return bt.doPaginate(allIds, max)
314}
315
316func (bt *bugTable) previousPage(g *gocui.Gui, v *gocui.View) error {
317	_, max := v.Size()
318	allIds, err := bt.cache.AllBugIds()
319	if err != nil {
320		return err
321	}
322
323	bt.allIds = allIds
324
325	bt.cursor = maxInt(0, bt.cursor-max)
326
327	return bt.doPaginate(allIds, max)
328}