From 9cbd5b4ee113c660377ffe9c01ca374d6addfef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Tue, 11 Sep 2018 19:28:32 +0200 Subject: [PATCH] termui: allow to change the bug query --- cache/repo_cache.go | 7 +++++-- commands/root.go | 6 +++++- input/input.go | 32 ++++++++++++++++++++++++++++++++ termui/bug_table.go | 37 ++++++++++++++++++++++++++----------- termui/input_popup.go | 14 +++++++++++--- termui/termui.go | 34 ++++++++++++++++++++++++++++++++++ 6 files changed, 113 insertions(+), 17 deletions(-) diff --git a/cache/repo_cache.go b/cache/repo_cache.go index a46e3a750f2b5f50ed3de1cdb23df6c385dd101e..cd743e92bd478c6f0597c69be4867a2e43324b1d 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 77d0fcef57927a83b757846433e2c374d8c2c966..8372278181bb8ccfc70b7dc8d63a557d05b5a98e 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 8e7f3d97b950408ba3b5eeffc0714afccf447d58..16264efcd8cdcb7d86a1640ac46e6f7f8c472f38 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 6ee44b42c27b678d4e17903adbc2db90fc72add8..59252b4891636497fc164c6cda253e8f5feb3183 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 00e602e501a6d451e27cc89ef9b2bbb1b6ff9d79..c8299d2ad43040ba315f536dd9351f6f590811e7 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 879ec2ea2409e60d5585322723d423f8efd8e04d..54324c6133040260ec2f9b861cc66a206e5fe625 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