2018-05-26 05:23:39 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2018-07-21 07:51:18 +02:00
|
|
|
"fmt"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
2018-07-22 04:07:36 +02:00
|
|
|
"time"
|
2018-05-26 05:23:39 +02:00
|
|
|
|
2018-07-21 07:51:18 +02:00
|
|
|
"github.com/jesseduffield/gocui"
|
2018-05-26 05:23:39 +02:00
|
|
|
)
|
|
|
|
|
2018-06-06 04:17:49 +02:00
|
|
|
var cyclableViews = []string{"files", "branches", "commits", "stash"}
|
|
|
|
|
2018-06-09 11:06:33 +02:00
|
|
|
func refreshSidePanels(g *gocui.Gui) error {
|
2018-07-21 07:51:18 +02:00
|
|
|
refreshBranches(g)
|
|
|
|
refreshFiles(g)
|
|
|
|
refreshCommits(g)
|
|
|
|
return nil
|
2018-06-06 04:17:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func nextView(g *gocui.Gui, v *gocui.View) error {
|
2018-07-21 07:51:18 +02:00
|
|
|
var focusedViewName string
|
|
|
|
if v == nil || v.Name() == cyclableViews[len(cyclableViews)-1] {
|
|
|
|
focusedViewName = cyclableViews[0]
|
|
|
|
} else {
|
|
|
|
for i := range cyclableViews {
|
|
|
|
if v.Name() == cyclableViews[i] {
|
|
|
|
focusedViewName = cyclableViews[i+1]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if i == len(cyclableViews)-1 {
|
|
|
|
devLog(v.Name() + " is not in the list of views")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
focusedView, err := g.View(focusedViewName)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return switchFocus(g, v, focusedView)
|
2018-06-06 04:17:49 +02:00
|
|
|
}
|
|
|
|
|
2018-07-22 04:07:36 +02:00
|
|
|
func previousView(g *gocui.Gui, v *gocui.View) error {
|
|
|
|
var focusedViewName string
|
2018-07-22 04:58:39 +02:00
|
|
|
if v == nil || v.Name() == cyclableViews[0] {
|
|
|
|
focusedViewName = cyclableViews[len(cyclableViews)-1]
|
2018-07-22 04:07:36 +02:00
|
|
|
} else {
|
|
|
|
for i := range cyclableViews {
|
|
|
|
if v.Name() == cyclableViews[i] {
|
|
|
|
focusedViewName = cyclableViews[i-1] // TODO: make this work properly
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if i == len(cyclableViews)-1 {
|
|
|
|
devLog(v.Name() + " is not in the list of views")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
focusedView, err := g.View(focusedViewName)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return switchFocus(g, v, focusedView)
|
|
|
|
}
|
|
|
|
|
2018-06-06 04:17:49 +02:00
|
|
|
func newLineFocused(g *gocui.Gui, v *gocui.View) error {
|
2018-07-21 07:51:18 +02:00
|
|
|
mainView, _ := g.View("main")
|
|
|
|
mainView.SetOrigin(0, 0)
|
|
|
|
|
|
|
|
switch v.Name() {
|
|
|
|
case "files":
|
|
|
|
return handleFileSelect(g, v)
|
|
|
|
case "branches":
|
|
|
|
return handleBranchSelect(g, v)
|
|
|
|
case "confirmation":
|
|
|
|
return nil
|
2018-08-11 07:09:37 +02:00
|
|
|
case "commitMessage":
|
|
|
|
return handleCommitFocused(g, v)
|
2018-07-21 07:51:18 +02:00
|
|
|
case "main":
|
|
|
|
// TODO: pull this out into a 'view focused' function
|
|
|
|
refreshMergePanel(g)
|
|
|
|
v.Highlight = false
|
|
|
|
return nil
|
|
|
|
case "commits":
|
|
|
|
return handleCommitSelect(g, v)
|
|
|
|
case "stash":
|
|
|
|
return handleStashEntrySelect(g, v)
|
|
|
|
default:
|
|
|
|
panic("No view matching newLineFocused switch statement")
|
|
|
|
}
|
2018-06-06 04:17:49 +02:00
|
|
|
}
|
|
|
|
|
2018-05-26 05:23:39 +02:00
|
|
|
func returnFocus(g *gocui.Gui, v *gocui.View) error {
|
2018-07-21 07:51:18 +02:00
|
|
|
previousView, err := g.View(state.PreviousView)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return switchFocus(g, v, previousView)
|
2018-05-26 05:23:39 +02:00
|
|
|
}
|
|
|
|
|
2018-06-09 11:06:33 +02:00
|
|
|
// pass in oldView = nil if you don't want to be able to return to your old view
|
2018-05-26 05:23:39 +02:00
|
|
|
func switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error {
|
2018-07-21 07:51:18 +02:00
|
|
|
// we assume we'll never want to return focus to a confirmation panel i.e.
|
|
|
|
// we should never stack confirmation panels
|
|
|
|
if oldView != nil && oldView.Name() != "confirmation" {
|
|
|
|
oldView.Highlight = false
|
|
|
|
devLog("setting previous view to:", oldView.Name())
|
|
|
|
state.PreviousView = oldView.Name()
|
|
|
|
}
|
|
|
|
newView.Highlight = true
|
2018-08-11 07:04:02 +02:00
|
|
|
devLog("new focused view is " + newView.Name())
|
2018-07-21 07:51:18 +02:00
|
|
|
if _, err := g.SetCurrentView(newView.Name()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
g.Cursor = newView.Editable
|
|
|
|
return newLineFocused(g, newView)
|
2018-05-26 05:23:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func getItemPosition(v *gocui.View) int {
|
2018-07-21 07:51:18 +02:00
|
|
|
_, cy := v.Cursor()
|
|
|
|
_, oy := v.Origin()
|
|
|
|
return oy + cy
|
2018-05-26 05:23:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func cursorUp(g *gocui.Gui, v *gocui.View) error {
|
2018-07-21 07:51:18 +02:00
|
|
|
// swallowing cursor movements in main
|
|
|
|
// TODO: pull this out
|
|
|
|
if v == nil || v.Name() == "main" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ox, oy := v.Origin()
|
|
|
|
cx, cy := v.Cursor()
|
|
|
|
if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
|
|
|
|
if err := v.SetOrigin(ox, oy-1); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
newLineFocused(g, v)
|
|
|
|
return nil
|
2018-05-26 05:23:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func cursorDown(g *gocui.Gui, v *gocui.View) error {
|
2018-07-21 07:51:18 +02:00
|
|
|
// swallowing cursor movements in main
|
|
|
|
// TODO: pull this out
|
|
|
|
if v == nil || v.Name() == "main" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
cx, cy := v.Cursor()
|
|
|
|
ox, oy := v.Origin()
|
|
|
|
if cy+oy >= len(v.BufferLines())-2 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if err := v.SetCursor(cx, cy+1); err != nil {
|
|
|
|
if err := v.SetOrigin(ox, oy+1); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
newLineFocused(g, v)
|
|
|
|
return nil
|
2018-05-26 05:23:39 +02:00
|
|
|
}
|
|
|
|
|
2018-06-09 11:06:33 +02:00
|
|
|
func resetOrigin(v *gocui.View) error {
|
2018-07-21 07:51:18 +02:00
|
|
|
if err := v.SetCursor(0, 0); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return v.SetOrigin(0, 0)
|
2018-06-09 11:06:33 +02:00
|
|
|
}
|
|
|
|
|
2018-05-26 05:23:39 +02:00
|
|
|
// if the cursor down past the last item, move it up one
|
|
|
|
func correctCursor(v *gocui.View) error {
|
2018-07-21 07:51:18 +02:00
|
|
|
cx, cy := v.Cursor()
|
|
|
|
_, oy := v.Origin()
|
|
|
|
lineCount := len(v.BufferLines()) - 2
|
|
|
|
if cy >= lineCount-oy {
|
|
|
|
return v.SetCursor(cx, lineCount-oy)
|
|
|
|
}
|
|
|
|
return nil
|
2018-05-26 05:23:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func renderString(g *gocui.Gui, viewName, s string) error {
|
2018-07-21 07:51:18 +02:00
|
|
|
g.Update(func(*gocui.Gui) error {
|
|
|
|
v, err := g.View(viewName)
|
2018-07-21 10:37:00 +02:00
|
|
|
// just in case the view disappeared as this function was called, we'll
|
|
|
|
// silently return if it's not found
|
2018-07-21 07:51:18 +02:00
|
|
|
if err != nil {
|
2018-07-21 10:37:00 +02:00
|
|
|
return nil
|
2018-07-21 07:51:18 +02:00
|
|
|
}
|
|
|
|
v.Clear()
|
|
|
|
fmt.Fprint(v, s)
|
|
|
|
v.Wrap = true
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return nil
|
2018-05-26 05:23:39 +02:00
|
|
|
}
|
|
|
|
|
2018-06-09 11:06:33 +02:00
|
|
|
func optionsMapToString(optionsMap map[string]string) string {
|
2018-07-21 07:51:18 +02:00
|
|
|
optionsArray := make([]string, 0)
|
|
|
|
for key, description := range optionsMap {
|
|
|
|
optionsArray = append(optionsArray, key+": "+description)
|
|
|
|
}
|
|
|
|
sort.Strings(optionsArray)
|
|
|
|
return strings.Join(optionsArray, ", ")
|
2018-06-09 11:06:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func renderOptionsMap(g *gocui.Gui, optionsMap map[string]string) error {
|
2018-07-21 07:51:18 +02:00
|
|
|
return renderString(g, "options", optionsMapToString(optionsMap))
|
2018-06-09 11:06:33 +02:00
|
|
|
}
|
2018-07-22 04:07:36 +02:00
|
|
|
|
|
|
|
func loader() string {
|
|
|
|
characters := "|/-\\"
|
|
|
|
now := time.Now()
|
|
|
|
nanos := now.UnixNano()
|
|
|
|
index := nanos / 50000000 % int64(len(characters))
|
|
|
|
return characters[index : index+1]
|
|
|
|
}
|
2018-08-11 07:09:37 +02:00
|
|
|
|
|
|
|
// TODO: refactor properly
|
|
|
|
func getFilesView(g *gocui.Gui) *gocui.View {
|
|
|
|
v, _ := g.View("files")
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
|
|
|
func getCommitsView(g *gocui.Gui) *gocui.View {
|
|
|
|
v, _ := g.View("commits")
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
|
|
|
func getCommitMessageView(g *gocui.Gui) *gocui.View {
|
|
|
|
v, _ := g.View("commitMessage")
|
|
|
|
return v
|
|
|
|
}
|