diff options
-rw-r--r-- | commands/termui.go | 21 | ||||
-rw-r--r-- | doc/bash_completion/git-bug | 21 | ||||
-rw-r--r-- | doc/man/git-bug-termui.3 | 29 | ||||
-rw-r--r-- | doc/man/git-bug.3 | 2 | ||||
-rw-r--r-- | doc/md/git-bug.md | 1 | ||||
-rw-r--r-- | doc/md/git-bug_termui.md | 22 | ||||
-rw-r--r-- | doc/zsh_completion/git-bug | 2 | ||||
-rw-r--r-- | termui/bug_table.go | 186 | ||||
-rw-r--r-- | termui/termui.go | 132 |
9 files changed, 414 insertions, 2 deletions
diff --git a/commands/termui.go b/commands/termui.go new file mode 100644 index 00000000..6f3ba5ef --- /dev/null +++ b/commands/termui.go @@ -0,0 +1,21 @@ +package commands + +import ( + "github.com/MichaelMure/git-bug/termui" + "github.com/spf13/cobra" +) + +func runTermUI(cmd *cobra.Command, args []string) error { + //time.Sleep(10 * time.Second) + return termui.Run(repo) +} + +var termUICmd = &cobra.Command{ + Use: "termui", + Short: "Launch the terminal UI", + RunE: runTermUI, +} + +func init() { + RootCmd.AddCommand(termUICmd) +} diff --git a/doc/bash_completion/git-bug b/doc/bash_completion/git-bug index 7ab85fca..0be168de 100644 --- a/doc/bash_completion/git-bug +++ b/doc/bash_completion/git-bug @@ -461,6 +461,26 @@ _git-bug_show() noun_aliases=() } +_git-bug_termui() +{ + last_command="git-bug_termui" + + command_aliases=() + + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + _git-bug_webui() { last_command="git-bug_webui" @@ -501,6 +521,7 @@ _git-bug_root_command() commands+=("pull") commands+=("push") commands+=("show") + commands+=("termui") commands+=("webui") flags=() diff --git a/doc/man/git-bug-termui.3 b/doc/man/git-bug-termui.3 new file mode 100644 index 00000000..dd6eb0a9 --- /dev/null +++ b/doc/man/git-bug-termui.3 @@ -0,0 +1,29 @@ +.TH "MINE" "3" "Jul 2018" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +git\-bug\-termui \- Launch the terminal UI + + +.SH SYNOPSIS +.PP +\fBgit\-bug termui [flags]\fP + + +.SH DESCRIPTION +.PP +Launch the terminal UI + + +.SH OPTIONS +.PP +\fB\-h\fP, \fB\-\-help\fP[=false] + help for termui + + +.SH SEE ALSO +.PP +\fBgit\-bug(3)\fP diff --git a/doc/man/git-bug.3 b/doc/man/git-bug.3 index 066c1d1a..08aefa5b 100644 --- a/doc/man/git-bug.3 +++ b/doc/man/git-bug.3 @@ -29,4 +29,4 @@ It use the same internal storage so it doesn't pollute your project. As you woul .SH SEE ALSO .PP -\fBgit\-bug\-close(3)\fP, \fBgit\-bug\-commands(3)\fP, \fBgit\-bug\-comment(3)\fP, \fBgit\-bug\-label(3)\fP, \fBgit\-bug\-ls(3)\fP, \fBgit\-bug\-new(3)\fP, \fBgit\-bug\-open(3)\fP, \fBgit\-bug\-pull(3)\fP, \fBgit\-bug\-push(3)\fP, \fBgit\-bug\-show(3)\fP, \fBgit\-bug\-webui(3)\fP +\fBgit\-bug\-close(3)\fP, \fBgit\-bug\-commands(3)\fP, \fBgit\-bug\-comment(3)\fP, \fBgit\-bug\-label(3)\fP, \fBgit\-bug\-ls(3)\fP, \fBgit\-bug\-new(3)\fP, \fBgit\-bug\-open(3)\fP, \fBgit\-bug\-pull(3)\fP, \fBgit\-bug\-push(3)\fP, \fBgit\-bug\-show(3)\fP, \fBgit\-bug\-termui(3)\fP, \fBgit\-bug\-webui(3)\fP diff --git a/doc/md/git-bug.md b/doc/md/git-bug.md index eeef22f1..1e97e74e 100644 --- a/doc/md/git-bug.md +++ b/doc/md/git-bug.md @@ -30,5 +30,6 @@ git-bug [flags] * [git-bug pull](git-bug_pull.md) - Pull bugs update from a git remote * [git-bug push](git-bug_push.md) - Push bugs update to a git remote * [git-bug show](git-bug_show.md) - Display the details of a bug +* [git-bug termui](git-bug_termui.md) - Launch the terminal UI * [git-bug webui](git-bug_webui.md) - Launch the web UI diff --git a/doc/md/git-bug_termui.md b/doc/md/git-bug_termui.md new file mode 100644 index 00000000..8bbca0fb --- /dev/null +++ b/doc/md/git-bug_termui.md @@ -0,0 +1,22 @@ +## git-bug termui + +Launch the terminal UI + +### Synopsis + +Launch the terminal UI + +``` +git-bug termui [flags] +``` + +### Options + +``` + -h, --help help for termui +``` + +### SEE ALSO + +* [git-bug](git-bug.md) - A bugtracker embedded in Git + diff --git a/doc/zsh_completion/git-bug b/doc/zsh_completion/git-bug index ce85f435..dcd83163 100644 --- a/doc/zsh_completion/git-bug +++ b/doc/zsh_completion/git-bug @@ -7,7 +7,7 @@ case $state in level1) case $words[1] in git-bug) - _arguments '1: :(close commands comment label ls new open pull push show webui)' + _arguments '1: :(close commands comment label ls new open pull push show termui webui)' ;; *) _arguments '*: :_files' diff --git a/termui/bug_table.go b/termui/bug_table.go new file mode 100644 index 00000000..42b7f645 --- /dev/null +++ b/termui/bug_table.go @@ -0,0 +1,186 @@ +package termui + +import ( + "fmt" + "github.com/MichaelMure/git-bug/bug" + "github.com/MichaelMure/git-bug/cache" + "github.com/jroimartin/gocui" +) + +type bugTable struct { + cache cache.RepoCacher + bugs []*bug.Snapshot + cursor int +} + +func newBugTable(cache cache.RepoCacher) *bugTable { + return &bugTable{ + cache: cache, + cursor: 0, + } +} + +func (bt *bugTable) paginate(max int) error { + allIds, err := bt.cache.AllBugIds() + if err != nil { + return err + } + + return bt.doPaginate(allIds, max) +} + +func (bt *bugTable) nextPage(max int) error { + allIds, err := bt.cache.AllBugIds() + if err != nil { + return err + } + + if bt.cursor+max > len(allIds) { + return nil + } + + bt.cursor += max + + return bt.doPaginate(allIds, max) +} + +func (bt *bugTable) previousPage(max int) error { + allIds, err := bt.cache.AllBugIds() + if err != nil { + return err + } + + bt.cursor = maxInt(0, bt.cursor-max) + + return bt.doPaginate(allIds, max) +} + +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) + + // slice the data + nb := minInt(len(allIds)-bt.cursor, max) + + ids := allIds[bt.cursor:nb] + + bt.bugs = make([]*bug.Snapshot, len(ids)) + + for i, id := range ids { + b, err := bt.cache.ResolveBug(id) + if err != nil { + return err + } + + bt.bugs[i] = b.Snapshot() + } + + return nil +} + +func (bt *bugTable) layout(g *gocui.Gui) error { + maxX, maxY := g.Size() + + v, err := g.SetView("header", -1, -1, maxX, 3) + + if err != nil { + if err != gocui.ErrUnknownView { + return err + } + + v.Frame = false + } + + v.Clear() + ui.bugTable.renderHeader(v, maxX) + + v, err = g.SetView("table", -1, 1, maxX, maxY-2) + + if err != nil { + if err != gocui.ErrUnknownView { + return err + } + + v.Frame = false + v.Highlight = true + v.SelBgColor = gocui.ColorWhite + v.SelFgColor = gocui.ColorBlack + + _, err = g.SetCurrentView("table") + + if err != nil { + return err + } + } + + _, tableHeight := v.Size() + err = bt.paginate(tableHeight) + if err != nil { + return err + } + + v.Clear() + ui.bugTable.render(v, maxX) + + v, err = g.SetView("footer", -1, maxY-3, maxX, maxY) + + if err != nil { + if err != gocui.ErrUnknownView { + return err + } + + v.Frame = false + } + + v.Clear() + ui.bugTable.renderFooter(v, maxX) + + v, err = g.SetView("instructions", -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] Quit [h] Go back [j] Down [k] Up [l] Go forward [m] Load Additional [p] Play [enter] Play and Exit") + } + + return nil +} + +func (bt *bugTable) getTableLength() int { + return len(bt.bugs) +} + +func (bt *bugTable) render(v *gocui.View, maxX int) { + for _, b := range bt.bugs { + fmt.Fprintln(v, b.Title) + } +} + +func (bt *bugTable) renderHeader(v *gocui.View, maxX int) { + fmt.Fprintf(v, "header") + +} + +func (bt *bugTable) renderFooter(v *gocui.View, maxX int) { + fmt.Fprintf(v, "footer") +} + +func maxInt(a, b int) int { + if a > b { + return a + } + return b +} + +func minInt(a, b int) int { + if a > b { + return b + } + return a +} diff --git a/termui/termui.go b/termui/termui.go new file mode 100644 index 00000000..a8248442 --- /dev/null +++ b/termui/termui.go @@ -0,0 +1,132 @@ +package termui + +import ( + "github.com/MichaelMure/git-bug/cache" + "github.com/MichaelMure/git-bug/repository" + "github.com/jroimartin/gocui" +) + +type termUI struct { + cache cache.RepoCacher + bugTable *bugTable +} + +var ui *termUI + +func Run(repo repository.Repo) error { + c := cache.NewRepoCache(repo) + + ui = &termUI{ + cache: c, + bugTable: newBugTable(c), + } + + g, err := gocui.NewGui(gocui.OutputNormal) + + if err != nil { + return err + } + + defer g.Close() + + g.SetManagerFunc(layout) + + err = keybindings(g) + + if err != nil { + return err + } + + err = g.MainLoop() + + if err != nil && err != gocui.ErrQuit { + return err + } + + return nil +} + +func layout(g *gocui.Gui) error { + //maxX, maxY := g.Size() + + ui.bugTable.layout(g) + + v, err := g.View("table") + if err != nil { + return err + } + + cursorClamp(v) + + return nil +} + +func keybindings(g *gocui.Gui) error { + if err := g.SetKeybinding("", 'q', gocui.ModNone, quit); err != nil { + return err + } + if err := g.SetKeybinding("table", 'j', gocui.ModNone, cursorDown); err != nil { + return err + } + if err := g.SetKeybinding("table", gocui.KeyArrowDown, gocui.ModNone, cursorDown); err != nil { + return err + } + if err := g.SetKeybinding("table", 'k', gocui.ModNone, cursorUp); err != nil { + return err + } + if err := g.SetKeybinding("table", gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil { + return err + } + + //err = g.SetKeybinding("table", 'h', gocui.ModNone, popTable) + //err = g.SetKeybinding("table", gocui.KeyArrowLeft, gocui.ModNone, popTable) + //err = g.SetKeybinding("table", 'l', gocui.ModNone, pushTable) + //err = g.SetKeybinding("table", gocui.KeyArrowRight, gocui.ModNone, pushTable) + //err = g.SetKeybinding("table", 'p', gocui.ModNone, playSelected) + //err = g.SetKeybinding("table", gocui.KeyEnter, gocui.ModNone, playSelectedAndExit) + //err = g.SetKeybinding("table", 'm', gocui.ModNone, loadNextRecords) + + return nil +} + +func quit(g *gocui.Gui, v *gocui.View) error { + return gocui.ErrQuit +} + +func cursorDown(g *gocui.Gui, v *gocui.View) error { + _, y := v.Cursor() + y = minInt(y+1, ui.bugTable.getTableLength()-1) + + err := v.SetCursor(0, y) + if err != nil { + return err + } + + return nil +} + +func cursorUp(g *gocui.Gui, v *gocui.View) error { + _, y := v.Cursor() + y = maxInt(y-1, 0) + + err := v.SetCursor(0, y) + if err != nil { + return err + } + + return nil +} + +func cursorClamp(v *gocui.View) error { + _, y := v.Cursor() + + y = minInt(y, ui.bugTable.getTableLength()-1) + y = maxInt(y, 0) + + err := v.SetCursor(0, y) + if err != nil { + return err + } + + return nil +} |