Compare commits

...
14 Commits
Author SHA1 Message Date
Jesse Duffield ed6f21ee74 bump version to v0.1.48 2018-08-09 23:35:06 +10:00
Jesse Duffield 75a186614a add scroll keybinding to displayed files panel options 2018-08-09 23:34:50 +10:00
Jesse Duffield 18aa5b1bbe bump version to v0.1.47 2018-08-09 23:34:16 +10:00
Jesse Duffield 89209d1253 bump version to v0.1.46 2018-08-09 23:27:11 +10:00
Jesse Duffield bcad80250a add basic vim keybindings
add keybinding for the tab key to begin a newline when writing a commit message
it would have been shift+enter but currently that is not supported by gocui
2018-08-09 23:26:31 +10:00
Jesse Duffield 44eaccfd14 switch to using the log package rather than directly writing to file 2018-08-09 20:29:58 +10:00
Jesse Duffield 9537645d0c dynamic width for confirmation panel and better handling of a squashed terminal 2018-08-09 19:49:36 +10:00
Jesse Duffield d08e3a55a1 WIP hard reset 2018-08-09 19:33:35 +10:00
Jesse Duffield 2cd0bd8125 Merge branch 'master' of https://github.com/jesseduffield/lazygit 2018-08-09 19:02:19 +10:00
Jesse Duffield cc31cb1abe remove double quote escapes in commit message for auto version bumper 2018-08-09 19:02:16 +10:00
Jesse Duffield a56643fe64 refactor repeater functions and refresh file panel every ten seconds 2018-08-09 19:01:42 +10:00
Jesse Duffield 2386283e45 Update README.md 2018-08-09 16:07:25 +10:00
Jesse Duffield 58977ed7f3 remove the dist folder after goreleaser runs 2018-08-09 15:47:55 +10:00
Jesse Duffield a068548bcb remove speed monitoring 2018-08-09 15:44:28 +10:00
14 changed files with 147 additions and 76 deletions
+1 -1
View File
@@ -61,7 +61,7 @@ Basic tutorial [Here](https://www.youtube.com/watch?v=VDXvbHZYeKY)
![Viewing Commit Diffs](https://image.ibb.co/gPD02o/capture.png)
## Milestones
- [ ] Easy Installation (homebrew, release binaries)
- [x] Easy Installation (homebrew, release binaries)
- [ ] Configurable Keybindings
- [ ] Configurable Color Themes
- [ ] Spawning Subprocesses (help needed - have a look at https://github.com/jesseduffield/lazygit/pull/18)
+1 -1
View File
@@ -1 +1 @@
v0.1.45
v0.1.48
+2 -1
View File
@@ -37,11 +37,12 @@ func main() {
}
runCommand("git", "add", "VERSION")
runCommand("git", "commit", "-m", "\"bump version to "+newVersion+"\"", "--", "VERSION")
runCommand("git", "commit", "-m", "bump version to "+newVersion, "--", "VERSION")
runCommand("git", "push")
runCommand("git", "tag", newVersion)
runCommand("git", "push", "origin", newVersion)
runCommand("goreleaser", "--rm-dist")
runCommand("rm", "-rf", "dist")
}
func runCommand(args ...string) {
+1 -1
View File
@@ -131,7 +131,7 @@ func getSelectedCommit(g *gocui.Gui) (Commit, error) {
}
lineNumber := getItemPosition(v)
if lineNumber > len(state.Commits)-1 {
colorLog(color.FgRed, "potential error in getSelected Commit (mismatched ui and state)", state.Commits, lineNumber)
devLog("potential error in getSelected Commit (mismatched ui and state)", state.Commits, lineNumber)
return state.Commits[len(state.Commits)-1], nil
}
return state.Commits[lineNumber], nil
+46 -10
View File
@@ -47,7 +47,7 @@ func getMessageHeight(message string, width int) int {
func getConfirmationPanelDimensions(g *gocui.Gui, prompt string) (int, int, int, int) {
width, height := g.Size()
panelWidth := 60
panelWidth := width / 2
panelHeight := getMessageHeight(prompt, panelWidth)
return width/2 - panelWidth/2,
height/2 - panelHeight/2 - panelHeight%2 - 1,
@@ -55,7 +55,7 @@ func getConfirmationPanelDimensions(g *gocui.Gui, prompt string) (int, int, int,
height/2 + panelHeight/2
}
func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, handleYes func(*gocui.Gui, *gocui.View) error) error {
func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, handleConfirm func(*gocui.Gui, *gocui.View) error) error {
// only need to fit one line
x0, y0, x1, y1 := getConfirmationPanelDimensions(g, "")
if confirmationView, err := g.SetView("confirmation", x0, y0, x1, y1, 0); err != nil {
@@ -69,12 +69,12 @@ func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, hand
confirmationView.Title = title
confirmationView.FgColor = gocui.ColorWhite
switchFocus(g, currentView, confirmationView)
return setKeyBindings(g, handleYes, nil)
return setKeyBindings(g, handleConfirm, nil)
}
return nil
}
func createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, handleYes, handleNo func(*gocui.Gui, *gocui.View) error) error {
func createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
g.Update(func(g *gocui.Gui) error {
// delete the existing confirmation panel if it exists
if view, _ := g.View("confirmation"); view != nil {
@@ -91,20 +91,36 @@ func createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, promp
confirmationView.FgColor = gocui.ColorWhite
renderString(g, "confirmation", prompt)
switchFocus(g, currentView, confirmationView)
return setKeyBindings(g, handleYes, handleNo)
return setKeyBindings(g, handleConfirm, handleClose)
}
return nil
})
return nil
}
func setKeyBindings(g *gocui.Gui, handleYes, handleNo func(*gocui.Gui, *gocui.View) error) error {
renderString(g, "options", "esc: close, enter: confirm")
if err := g.SetKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone, wrappedConfirmationFunction(handleYes)); err != nil {
return err
func handleNewline(g *gocui.Gui, v *gocui.View) error {
// resising ahead of time so that the top line doesn't get hidden to make
// room for the cursor on the second line
x0, y0, x1, y1 := getConfirmationPanelDimensions(g, v.Buffer())
if _, err := g.SetView("confirmation", x0, y0, x1, y1+1, 0); err != nil {
if err != gocui.ErrUnknownView {
return err
}
}
return g.SetKeybinding("confirmation", gocui.KeyEsc, gocui.ModNone, wrappedConfirmationFunction(handleNo))
v.EditNewLine()
return nil
}
func setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
renderString(g, "options", "esc: close, enter: confirm")
if err := g.SetKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone, wrappedConfirmationFunction(handleConfirm)); err != nil {
return err
}
if err := g.SetKeybinding("confirmation", gocui.KeyTab, gocui.ModNone, handleNewline); err != nil {
return err
}
return g.SetKeybinding("confirmation", gocui.KeyEsc, gocui.ModNone, wrappedConfirmationFunction(handleClose))
}
func createMessagePanel(g *gocui.Gui, currentView *gocui.View, title, prompt string) error {
@@ -117,3 +133,23 @@ func createErrorPanel(g *gocui.Gui, message string) error {
coloredMessage := colorFunction(strings.TrimSpace(message))
return createConfirmationPanel(g, currentView, "Error", coloredMessage, nil, nil)
}
func trimTrailingNewline(str string) string {
if strings.HasSuffix(str, "\n") {
return str[:len(str)-1]
}
return str
}
func resizeConfirmationPanel(g *gocui.Gui) error {
// If the confirmation panel is already displayed, just resize the width,
// otherwise continue
if v, err := g.View("confirmation"); err == nil {
content := trimTrailingNewline(v.Buffer())
x0, y0, x1, y1 := getConfirmationPanelDimensions(g, content)
if _, err = g.SetView("confirmation", x0, y0, x1, y1, 0); err != nil {
return err
}
}
return nil
}
+11 -11
View File
@@ -2,11 +2,11 @@
## Global:
← → ↑ ↓: navigate
PgUp/PgDn: scroll diff panel (use fn+up/down on osx)
q: quit
p: pull
shift+P: push
← → ↑ ↓/h j k l: navigate
PgUp/PgDn: scroll diff panel (use fn+up/down on osx)
q: quit
p: pull
shift+P: push
## Files Panel:
@@ -39,7 +39,7 @@
## Stash Panel:
space: apply
k: pop
g: pop
d: drop
## Popup Panel:
@@ -49,8 +49,8 @@
## Resolving Merge Conflicts (Diff Panel):
← →: navigate conflicts
↑ ↓: select hunk
space: pick hunk
b: pick both hunks
z: undo (only available while still inside diff panel)
← →/h l: navigate conflicts
↑ ↓/ k j: select hunk
space: pick hunk
b: pick both hunks
z: undo (only available while still inside diff panel)
+20 -10
View File
@@ -129,16 +129,17 @@ func handleIgnoreFile(g *gocui.Gui, v *gocui.View) error {
func renderfilesOptions(g *gocui.Gui, gitFile *GitFile) error {
optionsMap := map[string]string{
"← → ↑ ↓": "navigate",
"S": "stash files",
"c": "commit changes",
"o": "open",
"i": "ignore",
"d": "delete",
"space": "toggle staged",
"R": "refresh",
"t": "add patch",
"e": "edit",
"← → ↑ ↓": "navigate",
"S": "stash files",
"c": "commit changes",
"o": "open",
"i": "ignore",
"d": "delete",
"space": "toggle staged",
"R": "refresh",
"t": "add patch",
"e": "edit",
"PgUp/PgDn": "scroll",
}
if state.HasMergeConflicts {
optionsMap["a"] = "abort merge"
@@ -354,3 +355,12 @@ func handleAbortMerge(g *gocui.Gui, v *gocui.View) error {
refreshStatus(g)
return refreshFiles(g)
}
func handleResetHard(g *gocui.Gui, v *gocui.View) error {
return createConfirmationPanel(g, v, "Clear file panel", "Are you sure you want `reset --hard HEAD`? You may lose changes", func(g *gocui.Gui, v *gocui.View) error {
if err := gitResetHard(); err != nil {
createErrorPanel(g, err.Error())
}
return refreshFiles(g)
}, nil)
}
+4
View File
@@ -652,3 +652,7 @@ func getAndMergeFetchedBranches(branches []Branch) []Branch {
}
return branches
}
func gitResetHard() error {
return w.Reset(&git.ResetOptions{Mode: git.HardReset})
}
+30 -16
View File
@@ -98,6 +98,14 @@ func handleRefresh(g *gocui.Gui, v *gocui.View) error {
return refreshSidePanels(g)
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
// layout is called for every screen re-render e.g. when the screen is resized
func layout(g *gocui.Gui) error {
g.Highlight = true
g.SelFgColor = gocui.ColorWhite | gocui.AttrBold
@@ -108,14 +116,15 @@ func layout(g *gocui.Gui) error {
commitsBranchesBoundary := 3 * height / 5 // height - 10
commitsStashBoundary := height - 5 // height - 5
minimumHeight := 16
minimumWidth := 10
panelSpacing := 1
if OverlappingEdges {
panelSpacing = 0
}
if height < minimumHeight {
v, err := g.SetView("limit", 0, 0, width-1, height-1, 0)
if height < minimumHeight || width < minimumWidth {
v, err := g.SetView("limit", 0, 0, max(width-1, 2), max(height-1, 2), 0)
if err != nil {
if err != gocui.ErrUnknownView {
return err
@@ -195,6 +204,10 @@ func layout(g *gocui.Gui) error {
v.Frame = false
}
if err = resizeConfirmationPanel(g); err != nil {
return err
}
if v, err := g.SetView("version", width-len(version)-1, optionsTop, width, optionsTop+2, 0); err != nil {
if err != gocui.ErrUnknownView {
return err
@@ -216,12 +229,13 @@ func layout(g *gocui.Gui) error {
return nil
}
func fetch(g *gocui.Gui) {
func fetch(g *gocui.Gui) error {
gitFetch()
refreshStatus(g)
return nil
}
func updateLoader(g *gocui.Gui) {
func updateLoader(g *gocui.Gui) error {
if confirmationView, _ := g.View("confirmation"); confirmationView != nil {
content := trimmedContent(confirmationView)
if strings.Contains(content, "...") {
@@ -229,6 +243,15 @@ func updateLoader(g *gocui.Gui) {
renderString(g, "confirmation", staticContent+" "+loader())
}
}
return nil
}
func goEvery(g *gocui.Gui, interval time.Duration, function func(*gocui.Gui) error) {
go func() {
for range time.Tick(interval) {
function(g)
}
}()
}
func run() (err error) {
@@ -238,18 +261,9 @@ func run() (err error) {
}
defer g.Close()
// periodically fetching to check for upstream differences
go func() {
for range time.Tick(time.Second * 60) {
fetch(g)
}
}()
go func() {
for range time.Tick(time.Millisecond * 10) {
updateLoader(g)
}
}()
goEvery(g, time.Second*60, fetch)
goEvery(g, time.Second*10, refreshFiles)
goEvery(g, time.Millisecond*10, updateLoader)
g.SetManagerFunc(layout)
+25 -8
View File
@@ -14,13 +14,8 @@ type Binding struct {
func keybindings(g *gocui.Gui) error {
bindings := []Binding{
Binding{ViewName: "", Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: previousView},
Binding{ViewName: "", Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: nextView},
Binding{ViewName: "", Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: nextView},
Binding{ViewName: "", Key: 'q', Modifier: gocui.ModNone, Handler: quit},
Binding{ViewName: "", Key: gocui.KeyCtrlC, Modifier: gocui.ModNone, Handler: quit},
Binding{ViewName: "", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: cursorDown},
Binding{ViewName: "", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: cursorUp},
Binding{ViewName: "", Key: gocui.KeyPgup, Modifier: gocui.ModNone, Handler: scrollUpMain},
Binding{ViewName: "", Key: gocui.KeyPgdn, Modifier: gocui.ModNone, Handler: scrollDownMain},
Binding{ViewName: "", Key: 'P', Modifier: gocui.ModNone, Handler: pushFiles},
@@ -39,13 +34,18 @@ func keybindings(g *gocui.Gui) error {
Binding{ViewName: "files", Key: 'S', Modifier: gocui.ModNone, Handler: handleStashSave},
Binding{ViewName: "files", Key: 'a', Modifier: gocui.ModNone, Handler: handleAbortMerge},
Binding{ViewName: "files", Key: 't', Modifier: gocui.ModNone, Handler: handleAddPatch},
Binding{ViewName: "main", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: handleSelectTop},
Binding{ViewName: "main", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: handleSelectBottom},
Binding{ViewName: "files", Key: 'D', Modifier: gocui.ModNone, Handler: handleResetHard},
Binding{ViewName: "main", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: handleEscapeMerge},
Binding{ViewName: "main", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handlePickHunk},
Binding{ViewName: "main", Key: 'b', Modifier: gocui.ModNone, Handler: handlePickBothHunks},
Binding{ViewName: "main", Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: handleSelectPrevConflict},
Binding{ViewName: "main", Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: handleSelectNextConflict},
Binding{ViewName: "main", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: handleSelectTop},
Binding{ViewName: "main", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: handleSelectBottom},
Binding{ViewName: "main", Key: 'h', Modifier: gocui.ModNone, Handler: handleSelectPrevConflict},
Binding{ViewName: "main", Key: 'l', Modifier: gocui.ModNone, Handler: handleSelectNextConflict},
Binding{ViewName: "main", Key: 'k', Modifier: gocui.ModNone, Handler: handleSelectTop},
Binding{ViewName: "main", Key: 'j', Modifier: gocui.ModNone, Handler: handleSelectBottom},
Binding{ViewName: "main", Key: 'z', Modifier: gocui.ModNone, Handler: handlePopFileSnapshot},
Binding{ViewName: "branches", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleBranchPress},
Binding{ViewName: "branches", Key: 'c', Modifier: gocui.ModNone, Handler: handleCheckoutByName},
@@ -56,9 +56,26 @@ func keybindings(g *gocui.Gui) error {
Binding{ViewName: "commits", Key: 'r', Modifier: gocui.ModNone, Handler: handleRenameCommit},
Binding{ViewName: "commits", Key: 'g', Modifier: gocui.ModNone, Handler: handleResetToCommit},
Binding{ViewName: "stash", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleStashApply},
Binding{ViewName: "stash", Key: 'k', Modifier: gocui.ModNone, Handler: handleStashPop},
Binding{ViewName: "stash", Key: 'g', Modifier: gocui.ModNone, Handler: handleStashPop},
Binding{ViewName: "stash", Key: 'd', Modifier: gocui.ModNone, Handler: handleStashDrop},
}
// Would make these keybindings global but that interferes with editing
// input in the confirmation panel
for _, viewName := range []string{"files", "branches", "commits", "stash"} {
bindings = append(bindings, []Binding{
Binding{ViewName: viewName, Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: nextView},
Binding{ViewName: viewName, Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: previousView},
Binding{ViewName: viewName, Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: nextView},
Binding{ViewName: viewName, Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: cursorUp},
Binding{ViewName: viewName, Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: cursorDown},
Binding{ViewName: viewName, Key: 'h', Modifier: gocui.ModNone, Handler: previousView},
Binding{ViewName: viewName, Key: 'l', Modifier: gocui.ModNone, Handler: nextView},
Binding{ViewName: viewName, Key: 'k', Modifier: gocui.ModNone, Handler: cursorUp},
Binding{ViewName: viewName, Key: 'j', Modifier: gocui.ModNone, Handler: cursorDown},
}...)
}
for _, binding := range bindings {
if err := g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
return err
+5 -13
View File
@@ -10,9 +10,7 @@ import (
"os/exec"
"os/user"
"path/filepath"
"time"
"github.com/fatih/color"
"github.com/jesseduffield/gocui"
git "gopkg.in/src-d/go-git.v4"
)
@@ -21,7 +19,6 @@ import (
var (
ErrSubprocess = errors.New("running subprocess")
subprocess *exec.Cmd
startTime time.Time
commit string
version = "unversioned"
@@ -47,18 +44,14 @@ func projectPath(path string) string {
}
func devLog(objects ...interface{}) {
localLog(color.FgWhite, "development.log", objects...)
}
func colorLog(colour color.Attribute, objects ...interface{}) {
localLog(colour, "development.log", objects...)
localLog("development.log", objects...)
}
func commandLog(objects ...interface{}) {
localLog(color.FgWhite, "commands.log", objects...)
localLog("commands.log", objects...)
}
func localLog(colour color.Attribute, path string, objects ...interface{}) {
func localLog(path string, objects ...interface{}) {
if !*debuggingFlag {
return
}
@@ -67,9 +60,9 @@ func localLog(colour color.Attribute, path string, objects ...interface{}) {
panic(err.Error())
}
defer f.Close()
log.SetOutput(f)
for _, object := range objects {
colorFunction := color.New(colour).SprintFunc()
f.WriteString(colorFunction(fmt.Sprint(object)) + "\n")
log.Println(fmt.Sprint(object))
}
}
@@ -107,7 +100,6 @@ func setupWorktree() {
}
func main() {
startTime = time.Now()
devLog("\n\n\n\n\n\n\n\n\n\n")
flag.Parse()
if version == "unversioned" {
-1
View File
@@ -136,7 +136,6 @@ func pushFileSnapshot(g *gocui.Gui) error {
}
func handlePopFileSnapshot(g *gocui.Gui, v *gocui.View) error {
colorLog(color.FgCyan, "IM HERE")
if state.EditHistory.Len() == 0 {
return nil
}
+1 -1
View File
@@ -33,7 +33,7 @@ func getSelectedStashEntry(v *gocui.View) *StashEntry {
func renderStashOptions(g *gocui.Gui) error {
return renderOptionsMap(g, map[string]string{
"space": "apply",
"k": "pop",
"g": "pop",
"d": "drop",
"← → ↑ ↓": "navigate",
})
-2
View File
@@ -2,7 +2,6 @@ package main
import (
"fmt"
"time"
"github.com/fatih/color"
"github.com/jesseduffield/gocui"
@@ -35,7 +34,6 @@ func refreshStatus(g *gocui.Gui) error {
// utilising the fact these all have padding to only grab the name
// from the display string with the existing coloring applied
fmt.Fprint(v, " "+branch.DisplayString[4:])
colorLog(color.FgCyan, time.Now().Sub(startTime))
return nil
})