diff options
author | Michael Muré <batolettre@gmail.com> | 2018-08-01 02:15:40 +0200 |
---|---|---|
committer | Michael Muré <batolettre@gmail.com> | 2018-08-01 02:17:06 +0200 |
commit | c875d40e631f83507b602807480be96dae05fc85 (patch) | |
tree | a81b0909eda009acb395d936d1e2bf09e03cff3a | |
parent | 2f88c28c59ce0480e64dfba6d820dc6f7589a6cf (diff) | |
download | git-bug-c875d40e631f83507b602807480be96dae05fc85.tar.gz |
termui: add a view to display a bug
-rw-r--r-- | bug/comment.go | 4 | ||||
-rw-r--r-- | bug/operations/create.go | 2 | ||||
-rw-r--r-- | bug/operations/create_test.go | 2 | ||||
-rw-r--r-- | bug/person.go | 4 | ||||
-rw-r--r-- | bug/snapshot.go | 10 | ||||
-rw-r--r-- | doc/man/git-bug-close.3 | 2 | ||||
-rw-r--r-- | doc/man/git-bug-commands.3 | 2 | ||||
-rw-r--r-- | doc/man/git-bug-comment.3 | 2 | ||||
-rw-r--r-- | doc/man/git-bug-label.3 | 2 | ||||
-rw-r--r-- | doc/man/git-bug-ls.3 | 2 | ||||
-rw-r--r-- | doc/man/git-bug-new.3 | 2 | ||||
-rw-r--r-- | doc/man/git-bug-open.3 | 2 | ||||
-rw-r--r-- | doc/man/git-bug-pull.3 | 2 | ||||
-rw-r--r-- | doc/man/git-bug-push.3 | 2 | ||||
-rw-r--r-- | doc/man/git-bug-show.3 | 2 | ||||
-rw-r--r-- | doc/man/git-bug-termui.3 | 2 | ||||
-rw-r--r-- | doc/man/git-bug-webui.3 | 2 | ||||
-rw-r--r-- | doc/man/git-bug.3 | 2 | ||||
-rw-r--r-- | termui/bug_table.go | 81 | ||||
-rw-r--r-- | termui/error_popup.go | 3 | ||||
-rw-r--r-- | termui/show_bug.go | 158 | ||||
-rw-r--r-- | termui/termui.go | 24 |
22 files changed, 265 insertions, 49 deletions
diff --git a/bug/comment.go b/bug/comment.go index 9cc38738..c0c07076 100644 --- a/bug/comment.go +++ b/bug/comment.go @@ -6,8 +6,8 @@ import ( ) type Comment struct { - Author Person `json:"author"` - Message string `json:"message"` + Author Person + Message string // Creation time of the comment. // Should be used only for human display, never for ordering as we can't rely on it in a distributed system. diff --git a/bug/operations/create.go b/bug/operations/create.go index 4c1dd32e..0ee7e857 100644 --- a/bug/operations/create.go +++ b/bug/operations/create.go @@ -23,6 +23,8 @@ func (op CreateOperation) Apply(snapshot bug.Snapshot) bug.Snapshot { UnixTime: op.UnixTime, }, } + snapshot.Author = op.Author + snapshot.CreatedAt = op.Time() return snapshot } diff --git a/bug/operations/create_test.go b/bug/operations/create_test.go index 8aade05f..11b907c8 100644 --- a/bug/operations/create_test.go +++ b/bug/operations/create_test.go @@ -23,6 +23,8 @@ func TestCreate(t *testing.T) { Comments: []bug.Comment{ {Author: rene, Message: "message", UnixTime: create.UnixTime}, }, + Author: rene, + CreatedAt: create.Time(), } if !reflect.DeepEqual(snapshot, expected) { diff --git a/bug/person.go b/bug/person.go index 6a70fa9e..5c9dcc4c 100644 --- a/bug/person.go +++ b/bug/person.go @@ -6,8 +6,8 @@ import ( ) type Person struct { - Name string `json:"name"` - Email string `json:"email"` + Name string + Email string } // GetUser will query the repository for user detail and build the corresponding Person diff --git a/bug/snapshot.go b/bug/snapshot.go index 5058b3f7..9001d2c7 100644 --- a/bug/snapshot.go +++ b/bug/snapshot.go @@ -10,10 +10,12 @@ import ( type Snapshot struct { id string - Status Status `json:"status"` - Title string `json:"title"` - Comments []Comment `json:"comments"` - Labels []Label `json:"labels"` + Status Status + Title string + Comments []Comment + Labels []Label + Author Person + CreatedAt time.Time Operations []Operation } diff --git a/doc/man/git-bug-close.3 b/doc/man/git-bug-close.3 index cf20b27a..7a3b5946 100644 --- a/doc/man/git-bug-close.3 +++ b/doc/man/git-bug-close.3 @@ -1,4 +1,4 @@ -.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" +.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" .nh .ad l diff --git a/doc/man/git-bug-commands.3 b/doc/man/git-bug-commands.3 index e35fcd66..0c3c25f8 100644 --- a/doc/man/git-bug-commands.3 +++ b/doc/man/git-bug-commands.3 @@ -1,4 +1,4 @@ -.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" +.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" .nh .ad l diff --git a/doc/man/git-bug-comment.3 b/doc/man/git-bug-comment.3 index 72b7c2c3..b5dca2b6 100644 --- a/doc/man/git-bug-comment.3 +++ b/doc/man/git-bug-comment.3 @@ -1,4 +1,4 @@ -.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" +.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" .nh .ad l diff --git a/doc/man/git-bug-label.3 b/doc/man/git-bug-label.3 index 90c26fee..57e7344b 100644 --- a/doc/man/git-bug-label.3 +++ b/doc/man/git-bug-label.3 @@ -1,4 +1,4 @@ -.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" +.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" .nh .ad l diff --git a/doc/man/git-bug-ls.3 b/doc/man/git-bug-ls.3 index 1a9aec79..44c21217 100644 --- a/doc/man/git-bug-ls.3 +++ b/doc/man/git-bug-ls.3 @@ -1,4 +1,4 @@ -.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" +.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" .nh .ad l diff --git a/doc/man/git-bug-new.3 b/doc/man/git-bug-new.3 index e184c34a..5684e67e 100644 --- a/doc/man/git-bug-new.3 +++ b/doc/man/git-bug-new.3 @@ -1,4 +1,4 @@ -.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" +.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" .nh .ad l diff --git a/doc/man/git-bug-open.3 b/doc/man/git-bug-open.3 index d7d50346..250751e9 100644 --- a/doc/man/git-bug-open.3 +++ b/doc/man/git-bug-open.3 @@ -1,4 +1,4 @@ -.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" +.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" .nh .ad l diff --git a/doc/man/git-bug-pull.3 b/doc/man/git-bug-pull.3 index 1bb2ef13..734b5f0f 100644 --- a/doc/man/git-bug-pull.3 +++ b/doc/man/git-bug-pull.3 @@ -1,4 +1,4 @@ -.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" +.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" .nh .ad l diff --git a/doc/man/git-bug-push.3 b/doc/man/git-bug-push.3 index 40377dd0..f543e60e 100644 --- a/doc/man/git-bug-push.3 +++ b/doc/man/git-bug-push.3 @@ -1,4 +1,4 @@ -.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" +.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" .nh .ad l diff --git a/doc/man/git-bug-show.3 b/doc/man/git-bug-show.3 index 03bdbcda..fa26c44a 100644 --- a/doc/man/git-bug-show.3 +++ b/doc/man/git-bug-show.3 @@ -1,4 +1,4 @@ -.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" +.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" .nh .ad l diff --git a/doc/man/git-bug-termui.3 b/doc/man/git-bug-termui.3 index dd6eb0a9..74a8d69c 100644 --- a/doc/man/git-bug-termui.3 +++ b/doc/man/git-bug-termui.3 @@ -1,4 +1,4 @@ -.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" +.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" .nh .ad l diff --git a/doc/man/git-bug-webui.3 b/doc/man/git-bug-webui.3 index ea182ff2..2a616971 100644 --- a/doc/man/git-bug-webui.3 +++ b/doc/man/git-bug-webui.3 @@ -1,4 +1,4 @@ -.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" +.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" .nh .ad l diff --git a/doc/man/git-bug.3 b/doc/man/git-bug.3 index 08aefa5b..db4aab88 100644 --- a/doc/man/git-bug.3 +++ b/doc/man/git-bug.3 @@ -1,4 +1,4 @@ -.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" +.TH "MINE" "3" "Aug 2018" "Auto generated by spf13/cobra" "" .nh .ad l diff --git a/termui/bug_table.go b/termui/bug_table.go index a6eb569d..ae67c289 100644 --- a/termui/bug_table.go +++ b/termui/bug_table.go @@ -9,18 +9,23 @@ import ( ) const bugTableView = "bugTableView" +const bugTableHeaderView = "bugTableHeaderView" +const bugTableFooterView = "bugTableFooterView" +const bugTableInstructionView = "bugTableInstructionView" type bugTable struct { - cache cache.RepoCacher - allIds []string - bugs []*bug.Snapshot - cursor int + cache cache.RepoCacher + allIds []string + bugs []*bug.Snapshot + pageCursor int + selectCursor int } func newBugTable(cache cache.RepoCacher) *bugTable { return &bugTable{ - cache: cache, - cursor: 0, + cache: cache, + pageCursor: 0, + selectCursor: 0, } } @@ -32,7 +37,7 @@ func (bt *bugTable) layout(g *gocui.Gui) error { return nil } - v, err := g.SetView("header", -1, -1, maxX, 3) + v, err := g.SetView(bugTableHeaderView, -1, -1, maxX, 3) if err != nil { if err != gocui.ErrUnknownView { @@ -56,6 +61,10 @@ func (bt *bugTable) layout(g *gocui.Gui) error { v.Highlight = true v.SelBgColor = gocui.ColorWhite v.SelFgColor = gocui.ColorBlack + + // restore the cursor + // window is too small to set the cursor properly, ignoring the error + _ = v.SetCursor(0, bt.selectCursor) } _, viewHeight := v.Size() @@ -72,7 +81,7 @@ func (bt *bugTable) layout(g *gocui.Gui) error { v.Clear() bt.render(v, maxX) - v, err = g.SetView("footer", -1, maxY-4, maxX, maxY) + v, err = g.SetView(bugTableFooterView, -1, maxY-4, maxX, maxY) if err != nil { if err != gocui.ErrUnknownView { @@ -85,7 +94,7 @@ func (bt *bugTable) layout(g *gocui.Gui) error { v.Clear() bt.renderFooter(v, maxX) - v, err = g.SetView("instructions", -1, maxY-2, maxX, maxY) + v, err = g.SetView(bugTableInstructionView, -1, maxY-2, maxX, maxY) if err != nil { if err != gocui.ErrUnknownView { @@ -99,12 +108,7 @@ func (bt *bugTable) layout(g *gocui.Gui) error { } _, err = g.SetCurrentView(bugTableView) - - if err != nil { - return err - } - - return nil + return err } func (bt *bugTable) keybindings(g *gocui.Gui) error { @@ -165,6 +169,28 @@ func (bt *bugTable) keybindings(g *gocui.Gui) error { return err } + // Open bug + if err := g.SetKeybinding(bugTableView, gocui.KeyEnter, gocui.ModNone, + bt.openBug); err != nil { + return err + } + + return nil +} + +func (bt *bugTable) disable(g *gocui.Gui) error { + if err := g.DeleteView(bugTableView); err != nil { + return err + } + if err := g.DeleteView(bugTableHeaderView); err != nil { + return err + } + if err := g.DeleteView(bugTableFooterView); err != nil { + return err + } + if err := g.DeleteView(bugTableInstructionView); err != nil { + return err + } return nil } @@ -181,10 +207,10 @@ func (bt *bugTable) paginate(max int) error { func (bt *bugTable) doPaginate(allIds []string, max int) error { // clamp the cursor - bt.cursor = maxInt(bt.cursor, 0) - bt.cursor = minInt(bt.cursor, len(allIds)-1) + bt.pageCursor = maxInt(bt.pageCursor, 0) + bt.pageCursor = minInt(bt.pageCursor, len(allIds)-1) - nb := minInt(len(allIds)-bt.cursor, max) + nb := minInt(len(allIds)-bt.pageCursor, max) if nb < 0 { bt.bugs = []*bug.Snapshot{} @@ -192,7 +218,7 @@ func (bt *bugTable) doPaginate(allIds []string, max int) error { } // slice the data - ids := allIds[bt.cursor : bt.cursor+nb] + ids := allIds[bt.pageCursor : bt.pageCursor+nb] bt.bugs = make([]*bug.Snapshot, len(ids)) @@ -217,7 +243,7 @@ func (bt *bugTable) getColumnWidths(maxX int) map[string]int { m["id"] = 10 m["status"] = 8 - left := maxX - m["id"] - m["status"] + left := maxX - 4 - m["id"] - m["status"] m["summary"] = maxInt(30, left/3) left -= m["summary"] @@ -272,6 +298,7 @@ func (bt *bugTable) cursorDown(g *gocui.Gui, v *gocui.View) error { // window is too small to set the cursor properly, ignoring the error _ = v.SetCursor(0, y) + bt.selectCursor = y return nil } @@ -282,6 +309,7 @@ func (bt *bugTable) cursorUp(g *gocui.Gui, v *gocui.View) error { // window is too small to set the cursor properly, ignoring the error _ = v.SetCursor(0, y) + bt.selectCursor = y return nil } @@ -294,6 +322,7 @@ func (bt *bugTable) cursorClamp(v *gocui.View) error { // window is too small to set the cursor properly, ignoring the error _ = v.SetCursor(0, y) + bt.selectCursor = y return nil } @@ -308,11 +337,11 @@ func (bt *bugTable) nextPage(g *gocui.Gui, v *gocui.View) error { bt.allIds = allIds - if bt.cursor+max >= len(allIds) { + if bt.pageCursor+max >= len(allIds) { return nil } - bt.cursor += max + bt.pageCursor += max return bt.doPaginate(allIds, max) } @@ -326,7 +355,13 @@ func (bt *bugTable) previousPage(g *gocui.Gui, v *gocui.View) error { bt.allIds = allIds - bt.cursor = maxInt(0, bt.cursor-max) + bt.pageCursor = maxInt(0, bt.pageCursor-max) return bt.doPaginate(allIds, max) } + +func (bt *bugTable) openBug(g *gocui.Gui, v *gocui.View) error { + _, y := v.Cursor() + ui.showBug.bug = bt.bugs[bt.pageCursor+y] + return ui.activateWindow(ui.showBug) +} diff --git a/termui/error_popup.go b/termui/error_popup.go index f0af5a93..7ec6b783 100644 --- a/termui/error_popup.go +++ b/termui/error_popup.go @@ -62,8 +62,7 @@ func (ep *errorPopup) layout(g *gocui.Gui) error { func (ep *errorPopup) close(g *gocui.Gui, v *gocui.View) error { ep.err = "" - g.DeleteView(errorPopupView) - return nil + return g.DeleteView(errorPopupView) } func (ep *errorPopup) isActive() bool { diff --git a/termui/show_bug.go b/termui/show_bug.go new file mode 100644 index 00000000..9d1f5b76 --- /dev/null +++ b/termui/show_bug.go @@ -0,0 +1,158 @@ +package termui + +import ( + "fmt" + "github.com/MichaelMure/git-bug/bug" + "github.com/MichaelMure/git-bug/bug/operations" + "github.com/MichaelMure/git-bug/cache" + "github.com/MichaelMure/git-bug/util" + "github.com/jroimartin/gocui" +) + +const showBugView = "showBugView" +const showBugSidebarView = "showBugSidebarView" +const showBugInstructionView = "showBugInstructionView" + +const timeLayout = "Jan _2 2006" + +type showBug struct { + cache cache.RepoCacher + bug *bug.Snapshot +} + +func newShowBug(cache cache.RepoCacher) *showBug { + return &showBug{ + cache: cache, + } +} + +func (sb *showBug) layout(g *gocui.Gui) error { + maxX, maxY := g.Size() + + v, err := g.SetView(showBugView, 0, 0, maxX*2/3, maxY-2) + + if err != nil { + if err != gocui.ErrUnknownView { + return err + } + + v.Frame = false + } + + v.Clear() + sb.renderMain(v) + + v, err = g.SetView(showBugSidebarView, maxX*2/3+1, 0, maxX-1, maxY-2) + + if err != nil { + if err != gocui.ErrUnknownView { + return err + } + + v.Frame = false + } + + v.Clear() + sb.renderSidebar(v) + + v, err = g.SetView(showBugInstructionView, -1, maxY-2, maxX, maxY) + + if err != nil { + if err != gocui.ErrUnknownView { + return err + } + + v.Frame = false + v.BgColor = gocui.ColorBlue + + fmt.Fprintf(v, "[q] Return") + } + + _, err = g.SetCurrentView(showBugView) + return err +} + +func (sb *showBug) keybindings(g *gocui.Gui) error { + // Return + if err := g.SetKeybinding(showBugView, 'q', gocui.ModNone, sb.back); err != nil { + return err + } + + if err := g.SetKeybinding(showBugView, gocui.KeyPgup, gocui.ModNone, + sb.scrollUp); err != nil { + return err + } + if err := g.SetKeybinding(showBugView, gocui.KeyPgdn, gocui.ModNone, + sb.scrollDown); err != nil { + return err + } + + return nil +} + +func (sb *showBug) disable(g *gocui.Gui) error { + if err := g.DeleteView(showBugView); err != nil { + return err + } + if err := g.DeleteView(showBugSidebarView); err != nil { + return err + } + if err := g.DeleteView(showBugInstructionView); err != nil { + return err + } + return nil +} + +func (sb *showBug) renderMain(v *gocui.View) { + maxX, _ := v.Size() + + header1 := fmt.Sprintf("[%s] %s", sb.bug.HumanId(), sb.bug.Title) + fmt.Fprintf(v, util.LeftPaddedString(header1, maxX, 2)+"\n\n") + + header2 := fmt.Sprintf("[%s] %s opened this bug on %s", + sb.bug.Status, sb.bug.Author.Name, sb.bug.CreatedAt.Format(timeLayout)) + fmt.Fprintf(v, util.LeftPaddedString(header2, maxX, 2)+"\n\n") + + for _, op := range sb.bug.Operations { + switch op.(type) { + + case operations.CreateOperation: + create := op.(operations.CreateOperation) + fmt.Fprintf(v, util.LeftPaddedString(create.Message, maxX, 6)+"\n\n\n") + + case operations.AddCommentOperation: + comment := op.(operations.AddCommentOperation) + header := fmt.Sprintf("%s commented on %s", + comment.Author.Name, comment.Time().Format(timeLayout)) + fmt.Fprintf(v, util.LeftPaddedString(header, maxX, 6)+"\n\n") + fmt.Fprintf(v, util.LeftPaddedString(comment.Message, maxX, 6)+"\n\n\n") + } + } + +} + +func (sb *showBug) renderSidebar(v *gocui.View) { + maxX, _ := v.Size() + + title := util.LeftPaddedString("LABEL", maxX, 2) + fmt.Fprintf(v, title+"\n\n") + + for _, label := range sb.bug.Labels { + fmt.Fprintf(v, util.LeftPaddedString(label.String(), maxX, 2)) + fmt.Fprintln(v) + } +} + +func (sb *showBug) back(g *gocui.Gui, v *gocui.View) error { + sb.bug = nil + ui.activateWindow(ui.bugTable) + return nil +} + +func (sb *showBug) scrollUp(g *gocui.Gui, v *gocui.View) error { + return nil +} + +func (sb *showBug) scrollDown(g *gocui.Gui, v *gocui.View) error { + return nil +} diff --git a/termui/termui.go b/termui/termui.go index f4a4fb26..727b9f3d 100644 --- a/termui/termui.go +++ b/termui/termui.go @@ -11,20 +11,33 @@ import ( var errTerminateMainloop = errors.New("terminate gocui mainloop") type termUI struct { - g *gocui.Gui - gError chan error - cache cache.RepoCacher + g *gocui.Gui + gError chan error + cache cache.RepoCacher + activeWindow window bugTable *bugTable + showBug *showBug errorPopup *errorPopup } +func (tui *termUI) activateWindow(window window) error { + if err := tui.activeWindow.disable(tui.g); err != nil { + return err + } + + tui.activeWindow = window + + return nil +} + var ui *termUI type window interface { keybindings(g *gocui.Gui) error layout(g *gocui.Gui) error + disable(g *gocui.Gui) error } func Run(repo repository.Repo) error { @@ -34,6 +47,7 @@ func Run(repo repository.Repo) error { gError: make(chan error, 1), cache: c, bugTable: newBugTable(c), + showBug: newShowBug(c), errorPopup: newErrorPopup(), } @@ -107,6 +121,10 @@ func keybindings(g *gocui.Gui) error { return err } + if err := ui.showBug.keybindings(g); err != nil { + return err + } + if err := ui.errorPopup.keybindings(g); err != nil { return err } |