From bb12309c7c834f8b0b12d7942cfee0d071242ad0 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 26 May 2018 13:23:39 +1000 Subject: [PATCH] Breaking into different files --- .gitignore | 1 + commit_panel.go | 67 +++++++ confirmation_panel.go | 62 +++++++ files_panel.go | 136 ++++++++++++++ gitcommands.go | 57 +++++- gui.go | 407 ++++++------------------------------------ logs_panel.go | 30 ++++ view_helpers.go | 120 +++++++++++++ 8 files changed, 523 insertions(+), 357 deletions(-) create mode 100644 commit_panel.go create mode 100644 confirmation_panel.go create mode 100644 files_panel.go create mode 100644 logs_panel.go create mode 100644 view_helpers.go diff --git a/.gitignore b/.gitignore index 1032e298f..48dffc2a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ development.log +commands.log diff --git a/commit_panel.go b/commit_panel.go new file mode 100644 index 000000000..17f54882a --- /dev/null +++ b/commit_panel.go @@ -0,0 +1,67 @@ +// lots of this has been directly ported from one of the example files, will brush up later + +// Copyright 2014 The gocui Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + + "github.com/jroimartin/gocui" +) + +func handleCommitPress(g *gocui.Gui, currentView *gocui.View) error { + devLog(stagedFiles(state.GitFiles)) + if len(stagedFiles(state.GitFiles)) == 0 { + return nil + } + maxX, maxY := g.Size() + // var v *gocui.View + if v, err := g.SetView("commit", maxX/2-30, maxY/2-1, maxX/2+30, maxY/2+1); err != nil { + if err != gocui.ErrUnknownView { + return err + } + v.Title = "Commit Message" + v.Editable = true + if _, err := g.SetCurrentView("commit"); err != nil { + return err + } + switchFocus(g, currentView, v) + } + return nil +} + +func handleCommitSubmit(g *gocui.Gui, v *gocui.View) error { + if len(v.BufferLines()) == 0 { + return closeCommitPrompt(g, v) + } + message := fmt.Sprint(v.BufferLines()[0]) + // for whatever reason, a successful commit returns an error, so we're not + // going to check for an error here + if err := gitCommit(message); err != nil { + devLog(err) + panic(err) + } + refreshFiles(g) + refreshLogs(g) + return closeCommitPrompt(g, v) +} + +func closeCommitPrompt(g *gocui.Gui, v *gocui.View) error { + filesView, _ := g.View("files") + switchFocus(g, v, filesView) + devLog("test prompt close") + if err := g.DeleteView("commit"); err != nil { + return err + } + if _, err := g.SetCurrentView(state.PreviousView); err != nil { + return err + } + return nil +} + +func handleCommitPromptFocus(g *gocui.Gui, v *gocui.View) error { + return renderString(g, "options", "esc: close, enter: commit") +} diff --git a/confirmation_panel.go b/confirmation_panel.go new file mode 100644 index 000000000..b63534987 --- /dev/null +++ b/confirmation_panel.go @@ -0,0 +1,62 @@ +// lots of this has been directly ported from one of the example files, will brush up later + +// Copyright 2014 The gocui Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + + // "io" + // "io/ioutil" + + "math" + // "strings" + + "github.com/jroimartin/gocui" +) + +func wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.View) error) func(*gocui.Gui, *gocui.View) error { + return func(g *gocui.Gui, v *gocui.View) error { + if function != nil { + if err := function(g, v); err != nil { + panic(err) + } + } + if err := returnFocus(g, v); err != nil { + panic(err) + } + g.DeleteKeybindings("confirmation") + return g.DeleteView("confirmation") + } +} + +func getConfirmationPanelDimensions(g *gocui.Gui, prompt string) (int, int, int, int) { + width, height := g.Size() + panelWidth := 60 + panelHeight := int(math.Ceil(float64(len(prompt)) / float64(panelWidth))) + return width/2 - panelWidth/2, + height/2 - panelHeight/2 - panelHeight%2 - 1, + width/2 + panelWidth/2, + height/2 + panelHeight/2 +} + +func createConfirmationPanel(g *gocui.Gui, sourceView *gocui.View, title, prompt string, handleYes, handleNo func(*gocui.Gui, *gocui.View) error) error { + x0, y0, x1, y1 := getConfirmationPanelDimensions(g, prompt) + if v, err := g.SetView("confirmation", x0, y0, x1, y1); err != nil { + if err != gocui.ErrUnknownView { + return err + } + v.Title = title + renderString(g, "confirmation", prompt+" (y/n)") + switchFocus(g, sourceView, v) + if err := g.SetKeybinding("confirmation", 'n', gocui.ModNone, wrappedConfirmationFunction(handleNo)); err != nil { + return err + } + if err := g.SetKeybinding("confirmation", 'y', gocui.ModNone, wrappedConfirmationFunction(handleYes)); err != nil { + return err + } + } + return nil +} diff --git a/files_panel.go b/files_panel.go new file mode 100644 index 000000000..40c983481 --- /dev/null +++ b/files_panel.go @@ -0,0 +1,136 @@ +// lots of this has been directly ported from one of the example files, will brush up later + +// Copyright 2014 The gocui Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + + // "io" + // "io/ioutil" + + // "strings" + + "strings" + + "github.com/fatih/color" + "github.com/jroimartin/gocui" +) + +func stagedFiles(files []GitFile) []GitFile { + result := make([]GitFile, 0) + for _, file := range files { + if file.HasStagedChanges { + result = append(result, file) + } + } + return result +} + +func handleFilePress(g *gocui.Gui, v *gocui.View) error { + file := getSelectedFile(v) + + if file.HasUnstagedChanges { + stageFile(file.Name) + } else { + unStageFile(file.Name) + } + + if err := refreshFiles(g); err != nil { + return err + } + if err := handleFileSelect(g, v); err != nil { + return err + } + + return nil +} + +func getSelectedFile(v *gocui.View) GitFile { + lineNumber := getItemPosition(v) + if len(state.GitFiles) == 0 { + return GitFile{ + Name: "noFile", + DisplayString: "none", + HasStagedChanges: false, + HasUnstagedChanges: false, + Tracked: false, + Deleted: false, + } + } + return state.GitFiles[lineNumber] +} + +func handleFileRemove(g *gocui.Gui, v *gocui.View) error { + file := getSelectedFile(v) + var deleteVerb string + if file.Tracked { + deleteVerb = "checkout" + } else { + deleteVerb = "delete" + } + return createConfirmationPanel(g, v, strings.Title(deleteVerb)+" file", "Are you sure you want to "+deleteVerb+" "+file.Name+" (you will lose your changes)?", func(g *gocui.Gui, v *gocui.View) error { + if err := removeFile(file); err != nil { + panic(err) + } + return refreshFiles(g) + }, nil) +} + +func handleFileSelect(g *gocui.Gui, v *gocui.View) error { + item := getSelectedFile(v) + var optionsString string + baseString := "space: toggle staged, c: commit changes, option+o: open" + if item.Tracked { + optionsString = baseString + ", option+d: checkout" + } else { + optionsString = baseString + ", option+d: delete" + } + renderString(g, "options", optionsString) + diff := getDiff(item) + return renderString(g, "main", diff) +} + +func handleFileOpen(g *gocui.Gui, v *gocui.View) error { + file := getSelectedFile(v) + _, err := openFile(file.Name) + return err +} + +func handleSublimeFileOpen(g *gocui.Gui, v *gocui.View) error { + file := getSelectedFile(v) + _, err := sublimeOpenFile(file.Name) + return err +} + +func refreshFiles(g *gocui.Gui) error { + filesView, err := g.View("files") + if err != nil { + return err + } + + // get files to stage + gitFiles := getGitStatusFiles() + state.GitFiles = mergeGitStatusFiles(state.GitFiles, gitFiles) + + filesView.Clear() + red := color.New(color.FgRed) + green := color.New(color.FgGreen) + for _, gitFile := range state.GitFiles { + if !gitFile.Tracked { + red.Fprintln(filesView, gitFile.DisplayString) + continue + } + green.Fprint(filesView, gitFile.DisplayString[0:1]) + red.Fprint(filesView, gitFile.DisplayString[1:3]) + if gitFile.HasUnstagedChanges { + red.Fprintln(filesView, gitFile.Name) + } else { + green.Fprintln(filesView, gitFile.Name) + } + } + correctCursor(filesView) + return nil +} diff --git a/gitcommands.go b/gitcommands.go index 87fd425c2..fbfded157 100644 --- a/gitcommands.go +++ b/gitcommands.go @@ -8,6 +8,7 @@ import ( // "log" "fmt" + "os" "os/exec" "strings" ) @@ -30,6 +31,23 @@ type Branch struct { BaseBranch string } +func devLog(objects ...interface{}) { + localLog("/Users/jesseduffieldduffield/go/src/github.com/jesseduffield/gitgot/development.log", objects...) +} + +func commandLog(objects ...interface{}) { + localLog("/Users/jesseduffieldduffield/go/src/github.com/jesseduffield/gitgot/commands.log", objects...) + localLog("/Users/jesseduffieldduffield/go/src/github.com/jesseduffield/gitgot/development.log", objects...) +} + +func localLog(path string, objects ...interface{}) { + f, _ := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644) + defer f.Close() + for _, object := range objects { + f.WriteString(fmt.Sprint(object) + "\n") + } +} + // Map (from https://gobyexample.com/collection-functions) func Map(vs []string, f func(string) string) []string { vsm := make([]string, len(vs)) @@ -39,6 +57,15 @@ func Map(vs []string, f func(string) string) []string { return vsm } +func includes(list []string, a string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + func mergeGitStatusFiles(oldGitFiles, newGitFiles []GitFile) []GitFile { if len(oldGitFiles) == 0 { return newGitFiles @@ -57,9 +84,10 @@ func mergeGitStatusFiles(oldGitFiles, newGitFiles []GitFile) []GitFile { } func runDirectCommand(command string) (string, error) { + commandLog(command) cmdOut, err := exec.Command("bash", "-c", command).Output() devLog(string(cmdOut)) - devLog(fmt.Sprint(err)) + devLog(err) return string(cmdOut), err } @@ -74,7 +102,7 @@ func getGitBranches() []Branch { branches := make([]Branch, 0) rawString, _ := runDirectCommand(getBranchesCommand) branchLines := splitLines(rawString) - for _, line := range branchLines { + for i, line := range branchLines { name := branchNameFromString(line) var branchType string var baseBranch string @@ -91,16 +119,19 @@ func getGitBranches() []Branch { branchType = "other" baseBranch = name } + if i == 0 { + line = line[:2] + "\t*" + line[2:] + } branches = append(branches, Branch{name, line, branchType, baseBranch}) } - devLog(fmt.Sprint(branches)) + devLog(branches) return branches } func getGitStatusFiles() []GitFile { statusOutput, _ := getGitStatus() statusStrings := splitLines(statusOutput) - devLog(fmt.Sprint(statusStrings)) + devLog(statusStrings) // a file can have both staged and unstaged changes // I'll probably end up ignoring the unstaged flag for now but might revisit // tracked, staged, unstaged @@ -135,17 +166,33 @@ func gitCheckout(branch string, force bool) error { } func runCommand(cmd string) (string, error) { + commandLog(cmd) splitCmd := strings.Split(cmd, " ") cmdOut, err := exec.Command(splitCmd[0], splitCmd[1:]...).Output() - devLog(cmd) devLog(string(cmdOut[:])) return string(cmdOut), err } +func openFile(filename string) (string, error) { + return runCommand("open " + filename) +} + +func sublimeOpenFile(filename string) (string, error) { + return runCommand("subl " + filename) +} + func getBranchDiff(branch string, baseBranch string) (string, error) { return runCommand("git diff --color " + baseBranch + "..." + branch) } +func getLog() string { + result, err := runDirectCommand("git log --color --oneline") + if err != nil { + panic(err) + } + return result +} + func getDiff(file GitFile) string { cachedArg := "" if file.HasStagedChanges { diff --git a/gui.go b/gui.go index 289caf2d7..62e77ff26 100644 --- a/gui.go +++ b/gui.go @@ -7,37 +7,29 @@ package main import ( - "fmt" - "strings" + // "io" // "io/ioutil" + "log" // "strings" - "os" - "github.com/fatih/color" "github.com/jroimartin/gocui" ) type stateType struct { - GitFiles []GitFile - Branches []Branch + GitFiles []GitFile + Branches []Branch + PreviousView string } -var state = stateType{GitFiles: make([]GitFile, 0)} +var state = stateType{ + GitFiles: make([]GitFile, 0), + PreviousView: "files", +} var cyclableViews = []string{"files", "branches"} -func stagedFiles(files []GitFile) []GitFile { - result := make([]GitFile, 0) - for _, file := range files { - if file.HasStagedChanges { - result = append(result, file) - } - } - return result -} - func nextView(g *gocui.Gui, v *gocui.View) error { var focusedViewName string if v == nil || v.Name() == cyclableViews[len(cyclableViews)-1] { @@ -61,86 +53,7 @@ func nextView(g *gocui.Gui, v *gocui.View) error { return switchFocus(g, v, focusedView) } -func switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error { - if oldView != nil { - oldView.Highlight = false - } - newView.Highlight = true - devLog(newView.Name()) - _, err := g.SetCurrentView(newView.Name()) // not mega proud of the delayed - // return of err - itemSelected(g, newView) - showViewOptions(g, newView.Name()) - return err -} - -func showViewOptions(g *gocui.Gui, viewName string) error { - optionsMap := map[string]string{ - "files": "space: toggle staged, c: commit changes, shift+d: remove", - "branches": "space: checkout", - "prompt": "esc: cancel, enter: commit", - } - g.Update(func(*gocui.Gui) error { - v, err := g.View("options") - if err != nil { - panic(err) - } - v.Clear() - fmt.Fprint(v, optionsMap[viewName]) - return nil - }) - return nil -} - -func getItemPosition(v *gocui.View) int { - _, cy := v.Cursor() - _, oy := v.Origin() - return oy + cy -} - -func cursorUp(g *gocui.Gui, v *gocui.View) error { - if v == nil { - 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 - } - } - - itemSelected(g, v) - return nil -} - -func resetOrigin(v *gocui.View) error { - if err := v.SetCursor(0, 0); err != nil { - return err - } - return v.SetOrigin(0, 0) -} - -func cursorDown(g *gocui.Gui, v *gocui.View) error { - if v != 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 - } - } - } - - itemSelected(g, v) - return nil -} - -func itemSelected(g *gocui.Gui, v *gocui.View) error { +func newLineFocused(g *gocui.Gui, v *gocui.View) error { mainView, _ := g.View("main") mainView.SetOrigin(0, 0) @@ -149,14 +62,18 @@ func itemSelected(g *gocui.Gui, v *gocui.View) error { return handleFileSelect(g, v) case "branches": return handleBranchSelect(g, v) - case "prompt": + case "commit": + return handleCommitPromptFocus(g, v) + case "confirmation": + return nil + case "main": return nil default: - panic("No view matching itemSelected switch statement") + panic("No view matching newLineFocused switch statement") } } -func scrollUp(g *gocui.Gui, v *gocui.View) error { +func scrollUpMain(g *gocui.Gui, v *gocui.View) error { mainView, _ := g.View("main") ox, oy := mainView.Origin() if oy >= 1 { @@ -165,7 +82,7 @@ func scrollUp(g *gocui.Gui, v *gocui.View) error { return nil } -func scrollDown(g *gocui.Gui, v *gocui.View) error { +func scrollDownMain(g *gocui.Gui, v *gocui.View) error { mainView, _ := g.View("main") ox, oy := mainView.Origin() if oy < len(mainView.BufferLines()) { @@ -174,144 +91,6 @@ func scrollDown(g *gocui.Gui, v *gocui.View) error { return nil } -func devLog(s string) { - f, _ := os.OpenFile("/Users/jesseduffieldduffield/go/src/github.com/jesseduffield/gitgot/development.log", os.O_APPEND|os.O_WRONLY, 0644) - defer f.Close() - - f.WriteString(s + "\n") -} - -func handleBranchPress(g *gocui.Gui, v *gocui.View) error { - branch := getSelectedBranch(v) - if err := gitCheckout(branch.Name, false); err != nil { - panic(err) - } - refreshBranches(v) - refreshFiles(g) - return nil -} - -func handleFilePress(g *gocui.Gui, v *gocui.View) error { - file := getSelectedFile(v) - - if file.HasUnstagedChanges { - stageFile(file.Name) - } else { - unStageFile(file.Name) - } - - if err := refreshFiles(g); err != nil { - return err - } - if err := handleFileSelect(g, v); err != nil { - return err - } - - return nil -} - -func handleCommitPrompt(g *gocui.Gui, currentView *gocui.View) error { - devLog(fmt.Sprint(stagedFiles(state.GitFiles))) - if len(stagedFiles(state.GitFiles)) == 0 { - return nil - } - maxX, maxY := g.Size() - // var v *gocui.View - if v, err := g.SetView("prompt", maxX/2-30, maxY/2-1, maxX/2+30, maxY/2+1); err != nil { - if err != gocui.ErrUnknownView { - return err - } - v.Title = "Commit Message: " - v.Editable = true - v.Highlight = true - v.Autoscroll = true - v.Wrap = true - v.Overwrite = true - v.Caret = true - // fmt.Fprintln(v, "commit message: ") - if _, err := g.SetCurrentView("prompt"); err != nil { - return err - } - switchFocus(g, currentView, v) - } - return nil -} - -func handleCommitSubmit(g *gocui.Gui, v *gocui.View) error { - if len(v.BufferLines()) == 0 { - return closePrompt(g, v) - } - message := fmt.Sprint(v.BufferLines()[0]) - // for whatever reason, a successful commit returns an error, so we're not - // going to check for an error here - if err := gitCommit(message); err != nil { - devLog(fmt.Sprint(err)) - panic(err) - } - refreshFiles(g) - return closePrompt(g, v) -} - -func handleFileRemove(g *gocui.Gui, v *gocui.View) error { - file := getSelectedFile(v) - removeFile(file) - refreshFiles(g) - return nil -} - -func getSelectedFile(v *gocui.View) GitFile { - lineNumber := getItemPosition(v) - if len(state.GitFiles) == 0 { - return GitFile{ - Name: "noFile", - DisplayString: "none", - HasStagedChanges: false, - HasUnstagedChanges: false, - Tracked: false, - Deleted: false, - } - } - return state.GitFiles[lineNumber] -} - -func getSelectedBranch(v *gocui.View) Branch { - lineNumber := getItemPosition(v) - return state.Branches[lineNumber] -} - -func handleBranchSelect(g *gocui.Gui, v *gocui.View) error { - lineNumber := getItemPosition(v) - branch := state.Branches[lineNumber] - diff, _ := getBranchDiff(branch.Name, branch.BaseBranch) - if err := renderString(g, diff); err != nil { - return err - } - return nil -} - -func handleFileSelect(g *gocui.Gui, v *gocui.View) error { - item := getSelectedFile(v) - diff := getDiff(item) - return renderString(g, diff) -} - -func closePrompt(g *gocui.Gui, v *gocui.View) error { - filesView, _ := g.View("files") - switchFocus(g, v, filesView) - devLog("test prompt close") - if err := g.DeleteView("prompt"); err != nil { - return err - } - if _, err := g.SetCurrentView("files"); err != nil { - return err - } - return nil -} - -func quit(g *gocui.Gui, v *gocui.View) error { - return gocui.ErrQuit -} - func keybindings(g *gocui.Gui) error { if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, nextView); err != nil { return err @@ -328,25 +107,31 @@ func keybindings(g *gocui.Gui) error { if err := g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil { return err } - if err := g.SetKeybinding("", gocui.KeyPgup, gocui.ModNone, scrollUp); err != nil { + if err := g.SetKeybinding("", gocui.KeyPgup, gocui.ModNone, scrollUpMain); err != nil { return err } - if err := g.SetKeybinding("", gocui.KeyPgdn, gocui.ModNone, scrollDown); err != nil { + if err := g.SetKeybinding("", gocui.KeyPgdn, gocui.ModNone, scrollDownMain); err != nil { return err } - if err := g.SetKeybinding("", 'C', gocui.ModNone, handleCommitPrompt); err != nil { + if err := g.SetKeybinding("", 'ç', gocui.ModNone, handleCommitPress); err != nil { return err } if err := g.SetKeybinding("files", gocui.KeySpace, gocui.ModNone, handleFilePress); err != nil { return err } - if err := g.SetKeybinding("files", 'D', gocui.ModNone, handleFileRemove); err != nil { + if err := g.SetKeybinding("files", '®', gocui.ModNone, handleFileRemove); err != nil { return err } - if err := g.SetKeybinding("prompt", gocui.KeyEsc, gocui.ModNone, closePrompt); err != nil { + if err := g.SetKeybinding("files", 'ø', gocui.ModNone, handleFileOpen); err != nil { return err } - if err := g.SetKeybinding("prompt", gocui.KeyEnter, gocui.ModNone, handleCommitSubmit); err != nil { + if err := g.SetKeybinding("files", 'ß', gocui.ModNone, handleSublimeFileOpen); err != nil { + return err + } + if err := g.SetKeybinding("commit", gocui.KeyEsc, gocui.ModNone, closeCommitPrompt); err != nil { + return err + } + if err := g.SetKeybinding("commit", gocui.KeyEnter, gocui.ModNone, handleCommitSubmit); err != nil { return err } if err := g.SetKeybinding("branches", gocui.KeySpace, gocui.ModNone, handleBranchPress); err != nil { @@ -355,94 +140,16 @@ func keybindings(g *gocui.Gui) error { return nil } -func splitLines(multilineString string) []string { - if multilineString == "" || multilineString == "\n" { - return make([]string, 0) - } - lines := strings.Split(multilineString, "\n") - if lines[len(lines)-1] == "" { - return lines[:len(lines)-1] - } - return lines -} - -func refreshBranches(v *gocui.View) error { - state.Branches = getGitBranches() - yellow := color.New(color.FgYellow) - red := color.New(color.FgRed) - white := color.New(color.FgWhite) - green := color.New(color.FgGreen) - - v.Clear() - for _, branch := range state.Branches { - if branch.Type == "feature" { - green.Fprintln(v, branch.DisplayString) - continue - } - if branch.Type == "bugfix" { - yellow.Fprintln(v, branch.DisplayString) - continue - } - if branch.Type == "hotfix" { - red.Fprintln(v, branch.DisplayString) - continue - } - white.Fprintln(v, branch.DisplayString) - } - resetOrigin(v) - return nil -} - -// if the cursor down past the last item, move it up one -func correctCursor(v *gocui.View) error { - cx, cy := v.Cursor() - _, oy := v.Origin() - lineCount := len(v.BufferLines()) - 2 - if cy >= lineCount-oy { - return v.SetCursor(cx, lineCount-oy) - } - return nil -} - -func refreshFiles(g *gocui.Gui) error { - filesView, err := g.View("files") - if err != nil { - return err - } - - // get files to stage - gitFiles := getGitStatusFiles() - state.GitFiles = mergeGitStatusFiles(state.GitFiles, gitFiles) - - filesView.Clear() - red := color.New(color.FgRed) - green := color.New(color.FgGreen) - for _, gitFile := range state.GitFiles { - if !gitFile.Tracked { - red.Fprintln(filesView, gitFile.DisplayString) - continue - } - green.Fprint(filesView, gitFile.DisplayString[0:1]) - red.Fprint(filesView, gitFile.DisplayString[1:3]) - if gitFile.HasUnstagedChanges { - red.Fprintln(filesView, gitFile.Name) - } else { - green.Fprintln(filesView, gitFile.Name) - } - } - correctCursor(filesView) - return nil -} - func layout(g *gocui.Gui) error { - maxX, maxY := g.Size() - leftSideWidth := maxX / 3 - filesBranchesBoundary := maxY - 10 + width, height := g.Size() + leftSideWidth := width / 3 + logsBranchesBoundary := height - 10 + filesBranchesBoundary := height - 20 - optionsTop := maxY - 3 + optionsTop := height - 3 // hiding options if there's not enough space - if maxY < 30 { - optionsTop = maxY + if height < 30 { + optionsTop = height } sideView, err := g.SetView("files", 0, 0, leftSideWidth, filesBranchesBoundary-1) @@ -455,19 +162,27 @@ func layout(g *gocui.Gui) error { refreshFiles(g) } - if v, err := g.SetView("main", leftSideWidth+2, 0, maxX-1, optionsTop-1); err != nil { + if v, err := g.SetView("main", leftSideWidth+2, 0, width-1, optionsTop-1); err != nil { if err != gocui.ErrUnknownView { return err } v.Title = "Diff" v.Wrap = true - if _, err := g.SetCurrentView("files"); err != nil { - return err - } + switchFocus(g, nil, v) handleFileSelect(g, sideView) } - if v, err := g.SetView("branches", 0, filesBranchesBoundary, leftSideWidth, optionsTop-1); err != nil { + if v, err := g.SetView("logs", 0, logsBranchesBoundary, leftSideWidth, optionsTop-1); err != nil { + if err != gocui.ErrUnknownView { + return err + } + v.Title = "Log" + + // these are only called once + refreshLogs(g) + } + + if v, err := g.SetView("branches", 0, filesBranchesBoundary, leftSideWidth, logsBranchesBoundary-1); err != nil { if err != gocui.ErrUnknownView { return err } @@ -478,7 +193,7 @@ func layout(g *gocui.Gui) error { nextView(g, nil) } - if v, err := g.SetView("options", 0, optionsTop, maxX-1, optionsTop+2); err != nil { + if v, err := g.SetView("options", 0, optionsTop, width-1, optionsTop+2); err != nil { if err != gocui.ErrUnknownView { return err } @@ -488,20 +203,6 @@ func layout(g *gocui.Gui) error { return nil } -func renderString(g *gocui.Gui, s string) error { - g.Update(func(*gocui.Gui) error { - v, err := g.View("main") - if err != nil { - panic(err) - } - v.Clear() - fmt.Fprint(v, s) - v.Wrap = true - return nil - }) - return nil -} - func run() { g, err := gocui.NewGui(gocui.OutputNormal) if err != nil { @@ -509,8 +210,6 @@ func run() { } defer g.Close() - // g.Cursor = true - g.SetManagerFunc(layout) if err := keybindings(g); err != nil { @@ -522,6 +221,10 @@ func run() { } } +func quit(g *gocui.Gui, v *gocui.View) error { + return gocui.ErrQuit +} + // const mcRide = " // `.-::-` // -/o+oossys+:. diff --git a/logs_panel.go b/logs_panel.go new file mode 100644 index 000000000..aa6f1ce48 --- /dev/null +++ b/logs_panel.go @@ -0,0 +1,30 @@ +// lots of this has been directly ported from one of the example files, will brush up later + +// Copyright 2014 The gocui Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + + "github.com/jroimartin/gocui" +) + +func refreshLogs(g *gocui.Gui) error { + // here is where you want to pickup from + // state.Logs = getGitLogs(nil) + s := getLog() + g.Update(func(*gocui.Gui) error { + v, err := g.View("logs") + v.Clear() + if err != nil { + panic(err) + } + v.Clear() + fmt.Fprint(v, s) + return nil + }) + return nil +} diff --git a/view_helpers.go b/view_helpers.go new file mode 100644 index 000000000..2306dd2f7 --- /dev/null +++ b/view_helpers.go @@ -0,0 +1,120 @@ +// lots of this has been directly ported from one of the example files, will brush up later + +// Copyright 2014 The gocui Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "strings" + + "github.com/jroimartin/gocui" +) + +func returnFocus(g *gocui.Gui, v *gocui.View) error { + previousView, err := g.View(state.PreviousView) + if err != nil { + panic(err) + } + return switchFocus(g, v, previousView) +} + +func switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error { + if oldView != nil { + oldView.Highlight = false + state.PreviousView = oldView.Name() + } + newView.Highlight = true + devLog(newView.Name()) + if _, err := g.SetCurrentView(newView.Name()); err != nil { + return err + } + g.Cursor = newView.Name() == "commit" + return newLineFocused(g, newView) +} + +func getItemPosition(v *gocui.View) int { + _, cy := v.Cursor() + _, oy := v.Origin() + return oy + cy +} + +func cursorUp(g *gocui.Gui, v *gocui.View) error { + if v == nil { + 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 +} + +func resetOrigin(v *gocui.View) error { + if err := v.SetCursor(0, 0); err != nil { + return err + } + return v.SetOrigin(0, 0) +} + +func cursorDown(g *gocui.Gui, v *gocui.View) error { + if v != 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 +} + +// if the cursor down past the last item, move it up one +func correctCursor(v *gocui.View) error { + cx, cy := v.Cursor() + _, oy := v.Origin() + lineCount := len(v.BufferLines()) - 2 + if cy >= lineCount-oy { + return v.SetCursor(cx, lineCount-oy) + } + return nil +} + +func renderString(g *gocui.Gui, viewName, s string) error { + g.Update(func(*gocui.Gui) error { + v, err := g.View(viewName) + if err != nil { + panic(err) + } + v.Clear() + fmt.Fprint(v, s) + v.Wrap = true + return nil + }) + return nil +} + +func splitLines(multilineString string) []string { + if multilineString == "" || multilineString == "\n" { + return make([]string, 0) + } + lines := strings.Split(multilineString, "\n") + if lines[len(lines)-1] == "" { + return lines[:len(lines)-1] + } + return lines +}