From 70eb603449de15c4daed4f41f95f8c7db4f36f8d Mon Sep 17 00:00:00 2001 From: Anand Sudhir Prayaga Date: Thu, 2 Aug 2018 17:13:06 +0200 Subject: [PATCH] Make the gerrit module interactive * Add ability to select and open reviews * Add more keyboard shortcuts * Fix issue - focus shortcut letter wasn't displayed on the title area * Cleanup some code --- _site/content/posts/modules/gerrit.md | 60 +++++++++++++----- docs/index.xml | 6 +- docs/posts/index.xml | 6 +- docs/posts/modules/gerrit/index.html | 60 +++++++++++++----- gerrit/display.go | 34 +++++----- gerrit/gerrit_repo.go | 54 ++++++++-------- gerrit/widget.go | 90 +++++++++++++++++++++++---- 7 files changed, 224 insertions(+), 86 deletions(-) diff --git a/_site/content/posts/modules/gerrit.md b/_site/content/posts/modules/gerrit.md index 1b9fd1be..26d912a5 100644 --- a/_site/content/posts/modules/gerrit.md +++ b/_site/content/posts/modules/gerrit.md @@ -33,55 +33,87 @@ wtf/gerrit/ Key: `l`
Action: Show the next project. +Key: `j`
+Action: Select the next review in the list. + +Key: `k`
+Action: Select the previous review in the list. + +Key: `r`
+Action: Refresh the data. + Key: `←`
Action: Show the previous project. Key: `→`
Action: Show the next project. +Key: `↓`
+Action: Select the next review in the list. + +Key: `↑`
+Action: Select the previous review in the list. + +Key: `[return]`
+Action: Open the selected review in the browser. + ## Configuration ```yaml gerrit: + colors: + rows: + even: "lightblue" + odd: "white" + domain: https://gerrit-review.googlesource.com enabled: true + password: "mypassword" position: top: 2 left: 3 height: 2 width: 2 - refreshInterval: 300 - domain: https://gerrit-review.googlesource.com projects: - org/test-project" - dotfiles - password: "mypassword" + refreshInterval: 300 username: "myname" verifyServerCertificate: false ``` ### Attributes -`enabled`
-Determines whether or not this module is executed and if its data displayed onscreen.
-Values: `true`, `false`. +`colors.rows.even`
+Define the foreground color for even-numbered rows.
+Values: Any X11 +color name. -`position`
-Defines where in the grid this module's widget will be displayed.
- -`refreshInterval`
-How often, in seconds, this module will update its data.
-Values: A positive integer, `0..n`. +`colors.rows.odd`
+Define the foreground color for odd-numbered rows.
+Values: Any X11 +color name. `domain`
Your Gerrit corporate domain.
Values: A valid URI. -`projects`
-A list of Gerrit project names to fetch data for.
+`enabled`
+Determines whether or not this module is executed and if its data displayed onscreen.
+Values: `true`, `false`. `password`
Value: Your Gerrit HTTP Password. +`position`
+Defines where in the grid this module's widget will be displayed.
+ +`projects`
+A list of Gerrit project names to fetch data for.
+ +`refreshInterval`
+How often, in seconds, this module will update its data.
+Values: A positive integer, `0..n`. + `username`
Your Gerrit username. diff --git a/docs/index.xml b/docs/index.xml index acee43b8..db6f2adf 100644 --- a/docs/index.xml +++ b/docs/index.xml @@ -85,8 +85,8 @@ My Outgoing Reviews All open reviews created by you. Source Code wtf/gerrit/ Keyboard Commands Key: / Action: Open/close the widget’s help window. Key: h Action: Show the previous project. Key: l Action: Show the next project. -Key: ← Action: Show the previous project. -Key: → Action: Show the next project. +Key: j Action: Select the next review in the list. +Key: k Action: Select the previous review in the list. @@ -550,4 +550,4 @@ Example Configuration Files A couple of example config files are provided in the - \ No newline at end of file + diff --git a/docs/posts/index.xml b/docs/posts/index.xml index d81cca19..be12bff6 100644 --- a/docs/posts/index.xml +++ b/docs/posts/index.xml @@ -85,8 +85,8 @@ My Outgoing Reviews All open reviews created by you. Source Code wtf/gerrit/ Keyboard Commands Key: / Action: Open/close the widget’s help window. Key: h Action: Show the previous project. Key: l Action: Show the next project. -Key: ← Action: Show the previous project. -Key: → Action: Show the next project. +Key: j Action: Select the next review in the list. +Key: k Action: Select the previous review in the list. @@ -550,4 +550,4 @@ Example Configuration Files A couple of example config files are provided in the - \ No newline at end of file + diff --git a/docs/posts/modules/gerrit/index.html b/docs/posts/modules/gerrit/index.html index e13b2612..5bd7d9a0 100644 --- a/docs/posts/modules/gerrit/index.html +++ b/docs/posts/modules/gerrit/index.html @@ -162,51 +162,83 @@ height="0" width="0" style="display:none;visibility:hidden">

Key: l
Action: Show the next project.

+

Key: j
+Action: Select the next review in the list.

+ +

Key: k
+Action: Select the previous review in the list.

+ +

Key: r
+Action: Refresh the data.

+

Key:
Action: Show the previous project.

Key:
Action: Show the next project.

+

Key:
+Action: Select the next review in the list.

+ +

Key:
+Action: Select the previous review in the list.

+ +

Key: [return]
+Action: Open the selected review in the browser.

+

Configuration

gerrit:
+  colors:
+    rows:
+      even: "lightblue"
+      odd: "white"
+  domain: https://gerrit-review.googlesource.com
   enabled: true
+  password: "mypassword"
   position:
     top: 2
     left: 3
     height: 2
     width: 2
-  refreshInterval: 300
-  domain: https://gerrit-review.googlesource.com
   projects:
   - org/test-project"
   - dotfiles
-  password: "mypassword"
+  refreshInterval: 300
   username: "myname"
   verifyServerCertificate: false

Attributes

-

enabled
-Determines whether or not this module is executed and if its data displayed onscreen.
-Values: true, false.

+

colors.rows.even
+Define the foreground color for even-numbered rows.
+Values: Any X11 +color name.

-

position
-Defines where in the grid this module’s widget will be displayed.

- -

refreshInterval
-How often, in seconds, this module will update its data.
-Values: A positive integer, 0..n.

+

colors.rows.odd
+Define the foreground color for odd-numbered rows.
+Values: Any X11 +color name.

domain
Your Gerrit corporate domain.
Values: A valid URI.

-

projects
-A list of Gerrit project names to fetch data for.

+

enabled
+Determines whether or not this module is executed and if its data displayed onscreen.
+Values: true, false.

password
Value: Your Gerrit HTTP Password.

+

position
+Defines where in the grid this module’s widget will be displayed.

+ +

projects
+A list of Gerrit project names to fetch data for.

+ +

refreshInterval
+How often, in seconds, this module will update its data.
+Values: A positive integer, 0..n.

+

username
Your Gerrit username.

diff --git a/gerrit/display.go b/gerrit/display.go index f7ef3a49..895b8fda 100644 --- a/gerrit/display.go +++ b/gerrit/display.go @@ -14,7 +14,7 @@ func (widget *Widget) display() { return } - widget.View.SetTitle(fmt.Sprintf("%s- %s", widget.Name, widget.title(project))) + widget.View.SetTitle(widget.ContextualTitle(fmt.Sprintf("%s- %s", widget.Name, widget.title(project)))) str := wtf.SigilStr(len(widget.GerritProjects), widget.Idx, widget.View) + "\n" str = str + " [red]Stats[white]\n" @@ -29,31 +29,27 @@ func (widget *Widget) display() { widget.View.SetText(str) } -func (widget *Widget) displayMyOutgoingReviews(project *GerritProject, username string) string { - ors := project.myOutgoingReviews(username) - - if len(ors) == 0 { +func (widget *Widget) displayMyIncomingReviews(project *GerritProject, username string) string { + if len(project.IncomingReviews) == 0 { return " [grey]none[white]\n" } str := "" - for _, r := range ors { - str = str + fmt.Sprintf(" [green]%4s[white] %s\n", r.ChangeID, r.Subject) + for idx, r := range project.IncomingReviews { + str = str + fmt.Sprintf(" [%s] [green]%d[white] [%s] %s\n", widget.rowColor(idx), r.Number, widget.rowColor(idx), r.Subject) } return str } -func (widget *Widget) displayMyIncomingReviews(project *GerritProject, username string) string { - irs := project.myIncomingReviews(username) - - if len(irs) == 0 { +func (widget *Widget) displayMyOutgoingReviews(project *GerritProject, username string) string { + if len(project.OutgoingReviews) == 0 { return " [grey]none[white]\n" } str := "" - for _, r := range irs { - str = str + fmt.Sprintf(" [green]%4s[white] %s\n", r.ChangeID, r.Subject) + for idx, r := range project.OutgoingReviews { + str = str + fmt.Sprintf(" [%s] [green]%d[white] [%s] %s\n", widget.rowColor(idx+len(project.IncomingReviews)), r.Number, widget.rowColor(idx+len(project.IncomingReviews)), r.Subject) } return str @@ -62,12 +58,22 @@ func (widget *Widget) displayMyIncomingReviews(project *GerritProject, username func (widget *Widget) displayStats(project *GerritProject) string { str := fmt.Sprintf( " Reviews: %d\n", - project.ReviewCount(), + project.ReviewCount, ) return str } +func (widget *Widget) rowColor(index int) string { + if widget.View.HasFocus() && (index == widget.selected) { + foreColor := wtf.Config.UString("wtf.colors.highlight.fore", "black") + backColor := wtf.Config.UString("wtf.colors.highlight.back", "orange") + + return fmt.Sprintf("%s:%s", foreColor, backColor) + } + return wtf.RowColor("gerrit", index) +} + func (widget *Widget) title(project *GerritProject) string { return fmt.Sprintf("[green]%s [white]", project.Path) } diff --git a/gerrit/gerrit_repo.go b/gerrit/gerrit_repo.go index 0e6b6e78..738173fb 100644 --- a/gerrit/gerrit_repo.go +++ b/gerrit/gerrit_repo.go @@ -2,13 +2,17 @@ package gerrit import ( glb "github.com/andygrunwald/go-gerrit" + "github.com/senorprogrammer/wtf/wtf" ) type GerritProject struct { gerrit *glb.Client Path string - Changes *[]glb.ChangeInfo + Changes *[]glb.ChangeInfo + ReviewCount int + IncomingReviews []glb.ChangeInfo + OutgoingReviews []glb.ChangeInfo } func NewGerritProject(path string, gerrit *glb.Client) *GerritProject { @@ -22,67 +26,65 @@ func NewGerritProject(path string, gerrit *glb.Client) *GerritProject { // Refresh reloads the gerrit data via the Gerrit API func (project *GerritProject) Refresh() { + username := wtf.Config.UString("wtf.mods.gerrit.username") project.Changes, _ = project.loadChanges() + + project.ReviewCount = project.countReviews(project.Changes) + project.IncomingReviews = project.myIncomingReviews(project.Changes, username) + project.OutgoingReviews = project.myOutgoingReviews(project.Changes, username) + } /* -------------------- Counts -------------------- */ -func (project *GerritProject) IssueCount() int { - if project.Changes == nil { +func (project *GerritProject) countReviews(changes *[]glb.ChangeInfo) int { + if changes == nil { return 0 } - return len(*project.Changes) -} - -func (project *GerritProject) ReviewCount() int { - if project.Changes == nil { - return 0 - } - - return len(*project.Changes) + return len(*changes) } /* -------------------- Unexported Functions -------------------- */ // myOutgoingReviews returns a list of my outgoing reviews created by username on this project -func (project *GerritProject) myOutgoingReviews(username string) []glb.ChangeInfo { - changes := []glb.ChangeInfo{} +func (project *GerritProject) myOutgoingReviews(changes *[]glb.ChangeInfo, username string) []glb.ChangeInfo { + var ors []glb.ChangeInfo - if project.Changes == nil { - return changes + if changes == nil { + return ors } - for _, change := range *project.Changes { + for _, change := range *changes { user := change.Owner if user.Username == username { - changes = append(changes, change) + ors = append(ors, change) } } - return changes + return ors } // myIncomingReviews returns a list of merge requests for which username has been requested to ChangeInfo -func (project *GerritProject) myIncomingReviews(username string) []glb.ChangeInfo { - changes := []glb.ChangeInfo{} +func (project *GerritProject) myIncomingReviews(changes *[]glb.ChangeInfo, username string) []glb.ChangeInfo { + var irs []glb.ChangeInfo - if project.Changes == nil { - return changes + if changes == nil { + return irs } - for _, change := range *project.Changes { + for _, change := range *changes { reviewers := change.Reviewers for _, reviewer := range reviewers["REVIEWER"] { if reviewer.Username == username { - changes = append(changes, change) + irs = append(irs, change) } } } - return changes + return irs } func (project *GerritProject) loadChanges() (*[]glb.ChangeInfo, error) { diff --git a/gerrit/widget.go b/gerrit/widget.go index c7c2c133..1f6836ea 100644 --- a/gerrit/widget.go +++ b/gerrit/widget.go @@ -17,12 +17,18 @@ const HelpText = ` Keyboard commands for Gerrit: /: Show/hide this help window - h: Previous project - l: Next project + h: Show the previous project + l: Show the next project + j: Select the next review in the list + k: Select the previous review in the list r: Refresh the data - arrow left: Previous project - arrow right: Next project + arrow left: Show the previous project + arrow right: Show the next project + arrow down: Select the next review in the list + arrow up: Select the previous review in the list + + return: Open the selected review in a browser ` type Widget struct { @@ -33,6 +39,7 @@ type Widget struct { GerritProjects []*GerritProject Idx int + selected int } var ( @@ -85,6 +92,7 @@ func NewWidget(app *tview.Application, pages *tview.Pages) *Widget { widget.GerritProjects = widget.buildProjectCollection(wtf.Config.UList("wtf.mods.gerrit.projects")) widget.View.SetInputCapture(widget.keyboardIntercept) + widget.unselect() return &widget } @@ -100,25 +108,65 @@ func (widget *Widget) Refresh() { widget.display() } -func (widget *Widget) Next() { +/* -------------------- Unexported Functions -------------------- */ + +func (widget *Widget) nextProject() { widget.Idx = widget.Idx + 1 + widget.unselect() if widget.Idx == len(widget.GerritProjects) { widget.Idx = 0 } - widget.display() + widget.unselect() } -func (widget *Widget) Prev() { +func (widget *Widget) prevProject() { widget.Idx = widget.Idx - 1 if widget.Idx < 0 { widget.Idx = len(widget.GerritProjects) - 1 } + widget.unselect() +} + +func (widget *Widget) nextReview() { + widget.selected++ + project := widget.GerritProjects[widget.Idx] + if widget.selected >= project.ReviewCount { + widget.selected = 0 + } + widget.display() } -/* -------------------- Unexported Functions -------------------- */ +func (widget *Widget) prevReview() { + widget.selected-- + project := widget.GerritProjects[widget.Idx] + if widget.selected < 0 { + widget.selected = project.ReviewCount - 1 + } + + widget.display() +} + +func (widget *Widget) openReview() { + sel := widget.selected + project := widget.GerritProjects[widget.Idx] + if sel >= 0 && sel < project.ReviewCount { + change := glb.ChangeInfo{} + if sel < len(project.IncomingReviews) { + change = project.IncomingReviews[sel] + } else { + change = project.OutgoingReviews[sel-len(project.IncomingReviews)] + } + wtf.OpenFile(fmt.Sprintf("%s/%s/%d", wtf.Config.UString("wtf.mods.gerrit.domain"), "#/c", change.Number)) + } +} + +func (widget *Widget) unselect() { + widget.selected = -1 + widget.display() +} func (widget *Widget) buildProjectCollection(projectData []interface{}) []*GerritProject { gerritProjects := []*GerritProject{} @@ -149,10 +197,16 @@ func (widget *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey { widget.ShowHelp() return nil case "h": - widget.Prev() + widget.prevProject() return nil case "l": - widget.Next() + widget.nextProject() + return nil + case "j": + widget.nextReview() + return nil + case "k": + widget.prevReview() return nil case "r": widget.Refresh() @@ -161,11 +215,23 @@ func (widget *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey { switch event.Key() { case tcell.KeyLeft: - widget.Prev() + widget.prevProject() return nil case tcell.KeyRight: - widget.Next() + widget.nextProject() return nil + case tcell.KeyDown: + widget.nextReview() + return nil + case tcell.KeyUp: + widget.prevReview() + return nil + case tcell.KeyEnter: + widget.openReview() + return nil + case tcell.KeyEsc: + widget.unselect() + return event default: return event }