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