aboutsummaryrefslogtreecommitdiffstats
path: root/termui
diff options
context:
space:
mode:
Diffstat (limited to 'termui')
-rw-r--r--termui/bug_table.go257
-rw-r--r--termui/termui.go153
2 files changed, 251 insertions, 159 deletions
diff --git a/termui/bug_table.go b/termui/bug_table.go
index 89f43c87..264dff2d 100644
--- a/termui/bug_table.go
+++ b/termui/bug_table.go
@@ -8,6 +8,8 @@ import (
"github.com/jroimartin/gocui"
)
+const bugTableView = "bugTableView"
+
type bugTable struct {
cache cache.RepoCacher
allIds []string
@@ -22,71 +24,6 @@ func newBugTable(cache cache.RepoCacher) *bugTable {
}
}
-func (bt *bugTable) paginate(max int) error {
- allIds, err := bt.cache.AllBugIds()
- if err != nil {
- return err
- }
-
- bt.allIds = allIds
-
- return bt.doPaginate(allIds, max)
-}
-
-func (bt *bugTable) nextPage(max int) error {
- allIds, err := bt.cache.AllBugIds()
- if err != nil {
- return err
- }
-
- bt.allIds = allIds
-
- 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.allIds = allIds
-
- 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)
-
- nb := minInt(len(allIds)-bt.cursor, max)
-
- // slice the data
- ids := allIds[bt.cursor : 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()
@@ -101,9 +38,9 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
}
v.Clear()
- ui.bugTable.renderHeader(v, maxX)
+ bt.renderHeader(v, maxX)
- v, err = g.SetView("bugTable", -1, 1, maxX, maxY-2)
+ v, err = g.SetView(bugTableView, -1, 1, maxX, maxY-2)
if err != nil {
if err != gocui.ErrUnknownView {
@@ -115,21 +52,26 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
v.SelBgColor = gocui.ColorWhite
v.SelFgColor = gocui.ColorBlack
- _, err = g.SetCurrentView("bugTable")
+ _, err = g.SetCurrentView(bugTableView)
if err != nil {
return err
}
}
- _, tableHeight := v.Size()
- err = bt.paginate(tableHeight)
+ _, viewHeight := v.Size()
+ err = bt.paginate(viewHeight - 1)
+ if err != nil {
+ return err
+ }
+
+ err = bt.cursorClamp(v)
if err != nil {
return err
}
v.Clear()
- ui.bugTable.render(v, maxX)
+ bt.render(v, maxX)
v, err = g.SetView("footer", -1, maxY-3, maxX, maxY)
@@ -142,7 +84,7 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
}
v.Clear()
- ui.bugTable.renderFooter(v, maxX)
+ bt.renderFooter(v, maxX)
v, err = g.SetView("instructions", -1, maxY-2, maxX, maxY)
@@ -154,7 +96,103 @@ func (bt *bugTable) layout(g *gocui.Gui) error {
v.Frame = false
v.BgColor = gocui.ColorBlue
- fmt.Fprintf(v, "[q] Quit [h] Previous page [j] Down [k] Up [l] Next page [enter] Open bug")
+ fmt.Fprintf(v, "[q] Quit [←,h] Previous page [↓,j] Down [↑,k] Up [→,l] Next page [enter] Open bug [n] New bug")
+ }
+
+ return nil
+}
+
+func (bt *bugTable) keybindings(g *gocui.Gui) error {
+ // Quit
+ if err := g.SetKeybinding(bugTableView, 'q', gocui.ModNone, quit); err != nil {
+ return err
+ }
+
+ // Down
+ if err := g.SetKeybinding(bugTableView, 'j', gocui.ModNone,
+ bt.cursorDown); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding(bugTableView, gocui.KeyArrowDown, gocui.ModNone,
+ bt.cursorDown); err != nil {
+ return err
+ }
+ // Up
+ if err := g.SetKeybinding(bugTableView, 'k', gocui.ModNone,
+ bt.cursorUp); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding(bugTableView, gocui.KeyArrowUp, gocui.ModNone,
+ bt.cursorUp); err != nil {
+ return err
+ }
+
+ // Previous page
+ if err := g.SetKeybinding(bugTableView, 'h', gocui.ModNone,
+ bt.previousPage); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding(bugTableView, gocui.KeyArrowLeft, gocui.ModNone,
+ bt.previousPage); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding(bugTableView, gocui.KeyPgup, gocui.ModNone,
+ bt.previousPage); err != nil {
+ return err
+ }
+ // Next page
+ if err := g.SetKeybinding(bugTableView, 'l', gocui.ModNone,
+ bt.nextPage); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding(bugTableView, gocui.KeyArrowRight, gocui.ModNone,
+ bt.nextPage); err != nil {
+ return err
+ }
+ if err := g.SetKeybinding(bugTableView, gocui.KeyPgdn, gocui.ModNone,
+ bt.nextPage); err != nil {
+ return err
+ }
+
+ // New bug
+ if err := g.SetKeybinding(bugTableView, 'n', gocui.ModNone,
+ newBugWithEditor); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (bt *bugTable) paginate(max int) error {
+ allIds, err := bt.cache.AllBugIds()
+ if err != nil {
+ return err
+ }
+
+ bt.allIds = allIds
+
+ 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)
+
+ nb := minInt(len(allIds)-bt.cursor, max)
+
+ // slice the data
+ ids := allIds[bt.cursor : 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
@@ -218,16 +256,73 @@ func (bt *bugTable) renderFooter(v *gocui.View, maxX int) {
fmt.Fprintf(v, "Showing %d of %d bugs", len(bt.bugs), len(bt.allIds))
}
-func maxInt(a, b int) int {
- if a > b {
- return a
+func (bt *bugTable) cursorDown(g *gocui.Gui, v *gocui.View) error {
+ _, y := v.Cursor()
+ y = minInt(y+1, bt.getTableLength()-1)
+
+ err := v.SetCursor(0, y)
+ if err != nil {
+ return err
}
- return b
+
+ return nil
+}
+
+func (bt *bugTable) 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 minInt(a, b int) int {
- if a > b {
- return b
+func (bt *bugTable) cursorClamp(v *gocui.View) error {
+ _, y := v.Cursor()
+
+ y = minInt(y, bt.getTableLength()-1)
+ y = maxInt(y, 0)
+
+ err := v.SetCursor(0, y)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (bt *bugTable) nextPage(g *gocui.Gui, v *gocui.View) error {
+ _, max := v.Size()
+
+ allIds, err := bt.cache.AllBugIds()
+ if err != nil {
+ return err
}
- return a
+
+ bt.allIds = allIds
+
+ if bt.cursor+max >= len(allIds) {
+ return nil
+ }
+
+ bt.cursor += max
+
+ return bt.doPaginate(allIds, max)
+}
+
+func (bt *bugTable) previousPage(g *gocui.Gui, v *gocui.View) error {
+ _, max := v.Size()
+ allIds, err := bt.cache.AllBugIds()
+ if err != nil {
+ return err
+ }
+
+ bt.allIds = allIds
+
+ bt.cursor = maxInt(0, bt.cursor-max)
+
+ return bt.doPaginate(allIds, max)
}
diff --git a/termui/termui.go b/termui/termui.go
index e2e5ae24..9ac82fd3 100644
--- a/termui/termui.go
+++ b/termui/termui.go
@@ -2,104 +2,103 @@ package termui
import (
"github.com/MichaelMure/git-bug/cache"
+ "github.com/MichaelMure/git-bug/input"
"github.com/MichaelMure/git-bug/repository"
"github.com/jroimartin/gocui"
+ "github.com/pkg/errors"
)
+var errTerminateMainloop = errors.New("terminate gocui mainloop")
+
type termUI struct {
- cache cache.RepoCacher
+ g *gocui.Gui
+ gError chan error
+ cache cache.RepoCacher
+ activeWindow window
+
bugTable *bugTable
}
var ui *termUI
+type window interface {
+ keybindings(g *gocui.Gui) error
+ layout(g *gocui.Gui) error
+}
+
func Run(repo repository.Repo) error {
c := cache.NewRepoCache(repo)
ui = &termUI{
+ gError: make(chan error, 1),
cache: c,
bugTable: newBugTable(c),
}
+ ui.activeWindow = ui.bugTable
+
+ initGui()
+
+ err := <-ui.gError
+
+ if err != nil && err != gocui.ErrQuit {
+ return err
+ }
+
+ return nil
+}
+
+func initGui() {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
- return err
+ ui.gError <- err
+ return
}
- defer g.Close()
+ ui.g = g
- g.SetManagerFunc(layout)
+ ui.g.SetManagerFunc(layout)
- err = keybindings(g)
+ err = keybindings(ui.g)
if err != nil {
- return err
+ ui.g.Close()
+ ui.gError <- err
+ return
}
err = g.MainLoop()
- if err != nil && err != gocui.ErrQuit {
- return err
+ if err != nil && err != errTerminateMainloop {
+ ui.g.Close()
+ ui.gError <- err
}
- return nil
+ return
}
func layout(g *gocui.Gui) error {
//maxX, maxY := g.Size()
- ui.bugTable.layout(g)
+ g.Cursor = false
- v, err := g.View("bugTable")
- if err != nil {
+ if err := ui.activeWindow.layout(g); 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("bugTable", 'j', gocui.ModNone, cursorDown); err != nil {
- return err
- }
- if err := g.SetKeybinding("bugTable", gocui.KeyArrowDown, gocui.ModNone, cursorDown); err != nil {
- return err
- }
- if err := g.SetKeybinding("bugTable", 'k', gocui.ModNone, cursorUp); err != nil {
- return err
- }
- if err := g.SetKeybinding("bugTable", gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil {
+ // Quit
+ if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
return err
}
- if err := g.SetKeybinding("bugTable", 'h', gocui.ModNone, previousPage); err != nil {
+ if err := ui.bugTable.keybindings(g); err != nil {
return err
}
- if err := g.SetKeybinding("bugTable", gocui.KeyArrowLeft, gocui.ModNone, previousPage); err != nil {
- return err
- }
- if err := g.SetKeybinding("bugTable", gocui.KeyPgup, gocui.ModNone, previousPage); err != nil {
- return err
- }
- if err := g.SetKeybinding("bugTable", 'l', gocui.ModNone, nextPage); err != nil {
- return err
- }
- if err := g.SetKeybinding("bugTable", gocui.KeyArrowRight, gocui.ModNone, nextPage); err != nil {
- return err
- }
- if err := g.SetKeybinding("bugTable", gocui.KeyPgdn, gocui.ModNone, nextPage); err != nil {
- return err
- }
-
- //err = g.SetKeybinding("bugTable", 'p', gocui.ModNone, playSelected)
- //err = g.SetKeybinding("bugTable", gocui.KeyEnter, gocui.ModNone, playSelectedAndExit)
- //err = g.SetKeybinding("bugTable", 'm', gocui.ModNone, loadNextRecords)
return nil
}
@@ -108,50 +107,48 @@ 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
- }
+func newBugWithEditor(g *gocui.Gui, v *gocui.View) error {
+ // This is somewhat hacky.
+ // As there is no way to pause gocui, run the editor, restart gocui,
+ // we have to stop it entirely and start a new one later.
+ //
+ // - an error channel is used to route the returned error of this new
+ // instance into the original launch function
+ // - a custom error (errTerminateMainloop) is used to terminate the original
+ // instance's mainLoop. This error is then filtered.
- return nil
-}
+ ui.g.Close()
-func cursorUp(g *gocui.Gui, v *gocui.View) error {
- _, y := v.Cursor()
- y = maxInt(y-1, 0)
+ title, message, err := input.BugCreateEditorInput(ui.cache.Repository(), "", "")
- err := v.SetCursor(0, y)
+ if err == input.ErrEmptyTitle {
+ // TODO: display proper error
+ return err
+ }
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)
+ _, err = ui.cache.NewBug(title, message)
if err != nil {
return err
}
- return nil
+ initGui()
+
+ return errTerminateMainloop
}
-func nextPage(g *gocui.Gui, v *gocui.View) error {
- _, maxY := v.Size()
- return ui.bugTable.nextPage(maxY)
+func maxInt(a, b int) int {
+ if a > b {
+ return a
+ }
+ return b
}
-func previousPage(g *gocui.Gui, v *gocui.View) error {
- _, maxY := v.Size()
- return ui.bugTable.previousPage(maxY)
+func minInt(a, b int) int {
+ if a > b {
+ return b
+ }
+ return a
}