aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cache/repo_cache.go7
-rw-r--r--commands/root.go6
-rw-r--r--input/input.go32
-rw-r--r--termui/bug_table.go37
-rw-r--r--termui/input_popup.go14
-rw-r--r--termui/termui.go34
6 files changed, 113 insertions, 17 deletions
diff --git a/cache/repo_cache.go b/cache/repo_cache.go
index a46e3a75..cd743e92 100644
--- a/cache/repo_cache.go
+++ b/cache/repo_cache.go
@@ -19,9 +19,12 @@ import (
)
type RepoCache struct {
- repo repository.Repo
+ // the underlying repo
+ repo repository.Repo
+ // excerpt of bugs data for all bugs
excerpts map[string]*BugExcerpt
- bugs map[string]*BugCache
+ // bug loaded in memory
+ bugs map[string]*BugCache
}
func NewRepoCache(r repository.Repo) (*RepoCache, error) {
diff --git a/commands/root.go b/commands/root.go
index 77d0fcef..83722781 100644
--- a/commands/root.go
+++ b/commands/root.go
@@ -24,7 +24,9 @@ var RootCmd = &cobra.Command{
It use the same internal storage so it doesn't pollute your project. As you would do with commits and branches, you can push your bugs to the same git remote your are already using to collaborate with other peoples.`,
- // Force the execution of the PreRun while still displaying the help
+ // For the root command, force the execution of the PreRun
+ // even if we just display the help. This is to make sure that we check
+ // the repository and give the user early feedback.
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
@@ -35,6 +37,8 @@ It use the same internal storage so it doesn't pollute your project. As you woul
DisableAutoGenTag: true,
+ // Custom bash code to connect the git completion for "git bug" to the
+ // git-bug completion for "git-bug"
BashCompletionFunction: `
_git_bug() {
__start_git-bug "$@"
diff --git a/input/input.go b/input/input.go
index 8e7f3d97..16264efc 100644
--- a/input/input.go
+++ b/input/input.go
@@ -149,6 +149,38 @@ func BugTitleEditorInput(repo repository.Repo, preTitle string) (string, error)
return title, nil
}
+const queryTemplate = `%s
+
+# Please edit the bug query.
+# Lines starting with '#' will be ignored, and an empty query aborts the operation.
+`
+
+// QueryEditorInput will open the default editor in the terminal with a
+// template for the user to fill. The file is then processed to extract a query.
+func QueryEditorInput(repo repository.Repo, preQuery string) (string, error) {
+ template := fmt.Sprintf(queryTemplate, preQuery)
+ raw, err := launchEditorWithTemplate(repo, messageFilename, template)
+
+ if err != nil {
+ return "", err
+ }
+
+ lines := strings.Split(raw, "\n")
+
+ for _, line := range lines {
+ if strings.HasPrefix(line, "#") {
+ continue
+ }
+ trimmed := strings.TrimSpace(line)
+ if trimmed == "" {
+ continue
+ }
+ return trimmed, nil
+ }
+
+ return "", nil
+}
+
// launchEditorWithTemplate will launch an editor as launchEditor do, but with a
// provided template.
func launchEditorWithTemplate(repo repository.Repo, fileName string, template string) (string, error) {
diff --git a/termui/bug_table.go b/termui/bug_table.go
index 6ee44b42..59252b48 100644
--- a/termui/bug_table.go
+++ b/termui/bug_table.go
@@ -16,10 +16,12 @@ const bugTableHeaderView = "bugTableHeaderView"
const bugTableFooterView = "bugTableFooterView"
const bugTableInstructionView = "bugTableInstructionView"
-const remote = "origin"
+const defaultRemote = "origin"
+const defaultQuery = "status:open"
type bugTable struct {
repo *cache.RepoCache
+ queryStr string
query *cache.Query
allIds []string
bugs []*cache.BugCache
@@ -28,12 +30,15 @@ type bugTable struct {
}
func newBugTable(c *cache.RepoCache) *bugTable {
+ query, err := cache.ParseQuery(defaultQuery)
+ if err != nil {
+ panic(err)
+ }
+
return &bugTable{
- repo: c,
- query: &cache.Query{
- OrderBy: cache.OrderByCreation,
- OrderDirection: cache.OrderAscending,
- },
+ repo: c,
+ query: query,
+ queryStr: defaultQuery,
pageCursor: 0,
selectCursor: 0,
}
@@ -197,6 +202,12 @@ func (bt *bugTable) keybindings(g *gocui.Gui) error {
return err
}
+ // Query
+ if err := g.SetKeybinding(bugTableView, 'q', gocui.ModNone,
+ bt.changeQuery); err != nil {
+ return err
+ }
+
return nil
}
@@ -390,10 +401,10 @@ func (bt *bugTable) openBug(g *gocui.Gui, v *gocui.View) error {
func (bt *bugTable) pull(g *gocui.Gui, v *gocui.View) error {
// Note: this is very hacky
- ui.msgPopup.Activate("Pull from remote "+remote, "...")
+ ui.msgPopup.Activate("Pull from remote "+defaultRemote, "...")
go func() {
- stdout, err := bt.repo.Fetch(remote)
+ stdout, err := bt.repo.Fetch(defaultRemote)
if err != nil {
g.Update(func(gui *gocui.Gui) error {
@@ -410,7 +421,7 @@ func (bt *bugTable) pull(g *gocui.Gui, v *gocui.View) error {
var buffer bytes.Buffer
beginLine := ""
- for merge := range bt.repo.MergeAll(remote) {
+ for merge := range bt.repo.MergeAll(defaultRemote) {
if merge.Status == bug.MsgMergeNothing {
continue
}
@@ -447,11 +458,11 @@ func (bt *bugTable) pull(g *gocui.Gui, v *gocui.View) error {
}
func (bt *bugTable) push(g *gocui.Gui, v *gocui.View) error {
- ui.msgPopup.Activate("Push to remote "+remote, "...")
+ ui.msgPopup.Activate("Push to remote "+defaultRemote, "...")
go func() {
// TODO: make the remote configurable
- stdout, err := bt.repo.Push(remote)
+ stdout, err := bt.repo.Push(defaultRemote)
if err != nil {
g.Update(func(gui *gocui.Gui) error {
@@ -468,3 +479,7 @@ func (bt *bugTable) push(g *gocui.Gui, v *gocui.View) error {
return nil
}
+
+func (bt *bugTable) changeQuery(g *gocui.Gui, v *gocui.View) error {
+ return editQueryWithEditor(bt)
+}
diff --git a/termui/input_popup.go b/termui/input_popup.go
index 00e602e5..c8299d2a 100644
--- a/termui/input_popup.go
+++ b/termui/input_popup.go
@@ -8,10 +8,12 @@ import (
const inputPopupView = "inputPopupView"
+// inputPopup is a simple popup with an input field
type inputPopup struct {
- active bool
- title string
- c chan string
+ active bool
+ title string
+ preload string
+ c chan string
}
func newInputPopup() *inputPopup {
@@ -53,6 +55,7 @@ func (ip *inputPopup) layout(g *gocui.Gui) error {
v.Frame = true
v.Title = ip.title
v.Editable = true
+ v.Write([]byte(ip.preload))
}
if _, err := g.SetCurrentView(inputPopupView); err != nil {
@@ -88,6 +91,11 @@ func (ip *inputPopup) validate(g *gocui.Gui, v *gocui.View) error {
return nil
}
+func (ip *inputPopup) ActivateWithContent(title string, content string) <-chan string {
+ ip.preload = content
+ return ip.Activate(title)
+}
+
func (ip *inputPopup) Activate(title string) <-chan string {
ip.title = title
ip.active = true
diff --git a/termui/termui.go b/termui/termui.go
index 879ec2ea..54324c61 100644
--- a/termui/termui.go
+++ b/termui/termui.go
@@ -262,6 +262,40 @@ func setTitleWithEditor(bug *cache.BugCache) error {
return errTerminateMainloop
}
+func editQueryWithEditor(bt *bugTable) error {
+ // This is somewhat hacky.
+ // As there is no way to pause gocui, run the editor and 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.
+
+ ui.g.Close()
+ ui.g = nil
+
+ queryStr, err := input.QueryEditorInput(bt.repo.Repository(), bt.queryStr)
+
+ if err != nil {
+ return err
+ }
+
+ bt.queryStr = queryStr
+
+ query, err := cache.ParseQuery(queryStr)
+
+ if err != nil {
+ ui.msgPopup.Activate(msgPopupErrorTitle, err.Error())
+ } else {
+ bt.query = query
+ }
+
+ initGui(nil)
+
+ return errTerminateMainloop
+}
+
func maxInt(a, b int) int {
if a > b {
return a