diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2019-11-04 19:47:25 +1100 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2019-11-05 19:22:01 +1100 |
commit | d5e443e8e3609fe38586aed942a3dae3343dbe47 (patch) | |
tree | 6d7465b9abd8df3ae903e6d95898054ac3a6d8b4 /pkg/gui | |
parent | a3c84296bf2fbc8b132d5b2285eedba09813fbee (diff) |
Support building and moving patches
WIP
Diffstat (limited to 'pkg/gui')
-rw-r--r-- | pkg/gui/branches_panel.go | 3 | ||||
-rw-r--r-- | pkg/gui/commit_files_panel.go | 89 | ||||
-rw-r--r-- | pkg/gui/commits_panel.go | 24 | ||||
-rw-r--r-- | pkg/gui/gui.go | 20 | ||||
-rw-r--r-- | pkg/gui/keybindings.go | 19 | ||||
-rw-r--r-- | pkg/gui/patch_options_panel.go | 89 | ||||
-rw-r--r-- | pkg/gui/staging_panel.go | 155 | ||||
-rw-r--r-- | pkg/gui/view_helpers.go | 4 |
8 files changed, 336 insertions, 67 deletions
diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index f79dce905..ce9ac4daa 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -6,7 +6,6 @@ import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" - "github.com/jesseduffield/lazygit/pkg/git" ) // list panel functions @@ -67,7 +66,7 @@ func (gui *Gui) RenderSelectedBranchUpstreamDifferences() error { // be sure there is a state.Branches array to pick the current branch from func (gui *Gui) refreshBranches(g *gocui.Gui) error { g.Update(func(g *gocui.Gui) error { - builder, err := git.NewBranchListBuilder(gui.Log, gui.GitCommand) + builder, err := commands.NewBranchListBuilder(gui.Log, gui.GitCommand) if err != nil { return err } diff --git a/pkg/gui/commit_files_panel.go b/pkg/gui/commit_files_panel.go index 2cdda6815..a8e5b2f4f 100644 --- a/pkg/gui/commit_files_panel.go +++ b/pkg/gui/commit_files_panel.go @@ -1,6 +1,7 @@ package gui import ( + "github.com/go-errors/errors" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" ) @@ -23,7 +24,7 @@ func (gui *Gui) handleCommitFileSelect(g *gocui.Gui, v *gocui.View) error { if err := gui.focusPoint(0, gui.State.Panels.CommitFiles.SelectedLine, len(gui.State.CommitFiles), v); err != nil { return err } - commitText, err := gui.GitCommand.ShowCommitFile(commitFile.Sha, commitFile.Name) + commitText, err := gui.GitCommand.ShowCommitFile(commitFile.Sha, commitFile.Name, false) if err != nil { return err } @@ -79,16 +80,19 @@ func (gui *Gui) handleDiscardOldFileChange(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) refreshCommitFilesView() error { + if err := gui.refreshPatchPanel(); err != nil { + return err + } + commit := gui.getSelectedCommit(gui.g) if commit == nil { return nil } - files, err := gui.GitCommand.GetCommitFiles(commit.Sha) + files, err := gui.GitCommand.GetCommitFiles(commit.Sha, gui.State.PatchManager) if err != nil { return gui.createErrorPanel(gui.g, err.Error()) } - gui.State.CommitFiles = files gui.refreshSelectedLine(&gui.State.Panels.CommitFiles.SelectedLine, len(gui.State.CommitFiles)) @@ -104,3 +108,82 @@ func (gui *Gui) handleOpenOldCommitFile(g *gocui.Gui, v *gocui.View) error { file := gui.getSelectedCommitFile(g) return gui.openFile(file.Name) } + +func (gui *Gui) handleToggleFileForPatch(g *gocui.Gui, v *gocui.View) error { + commitFile := gui.getSelectedCommitFile(g) + if commitFile == nil { + return gui.renderString(g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles")) + } + + toggleTheFile := func() error { + if gui.State.PatchManager == nil { + if err := gui.createPatchManager(); err != nil { + return err + } + } + + gui.State.PatchManager.ToggleFileWhole(commitFile.Name) + + return gui.refreshCommitFilesView() + } + + if gui.State.PatchManager != nil && gui.State.PatchManager.CommitSha != commitFile.Sha { + return gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("DiscardPatch"), gui.Tr.SLocalize("DiscardPatchConfirm"), func(g *gocui.Gui, v *gocui.View) error { + gui.State.PatchManager = nil + return toggleTheFile() + }, nil) + } + + return toggleTheFile() +} + +func (gui *Gui) createPatchManager() error { + diffMap := map[string]string{} + for _, commitFile := range gui.State.CommitFiles { + commitText, err := gui.GitCommand.ShowCommitFile(commitFile.Sha, commitFile.Name, true) + if err != nil { + return err + } + diffMap[commitFile.Name] = commitText + } + + commit := gui.getSelectedCommit(gui.g) + if commit == nil { + return errors.New("No commit selected") + } + + gui.State.PatchManager = commands.NewPatchManager(gui.Log, gui.GitCommand.ApplyPatch, commit.Sha, diffMap) + return nil +} + +func (gui *Gui) handleEnterCommitFile(g *gocui.Gui, v *gocui.View) error { + commitFile := gui.getSelectedCommitFile(g) + if commitFile == nil { + return gui.renderString(g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles")) + } + + enterTheFile := func() error { + if gui.State.PatchManager == nil { + if err := gui.createPatchManager(); err != nil { + return err + } + } + + if err := gui.changeContext("main", "staging"); err != nil { + return err + } + if err := gui.switchFocus(g, v, gui.getMainView()); err != nil { + return err + } + return gui.refreshStagingPanel() + } + + if gui.State.PatchManager != nil && gui.State.PatchManager.CommitSha != commitFile.Sha { + return gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("DiscardPatch"), gui.Tr.SLocalize("DiscardPatchConfirm"), func(g *gocui.Gui, v *gocui.View) error { + gui.State.PatchManager = nil + return enterTheFile() + }, nil) + } + + return enterTheFile() +} diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index c35b4adee..b22e6084e 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -9,7 +9,6 @@ import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" - "github.com/jesseduffield/lazygit/pkg/git" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -53,9 +52,26 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error { return gui.renderString(g, "main", commitText) } +func (gui *Gui) refreshPatchPanel() error { + if gui.State.PatchManager != nil { + gui.State.SplitMainPanel = true + secondaryView := gui.getSecondaryView() + secondaryView.Highlight = true + secondaryView.Wrap = false + + gui.g.Update(func(*gocui.Gui) error { + return gui.setViewContent(gui.g, gui.getSecondaryView(), gui.State.PatchManager.RenderAggregatedPatchColored(false)) + }) + } else { + gui.State.SplitMainPanel = false + } + + return nil +} + func (gui *Gui) refreshCommits(g *gocui.Gui) error { g.Update(func(*gocui.Gui) error { - builder, err := git.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr, gui.State.CherryPickedCommits, gui.State.DiffEntries) + builder, err := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr, gui.State.CherryPickedCommits, gui.State.DiffEntries) if err != nil { return err } @@ -65,6 +81,10 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error { } gui.State.Commits = commits + if err := gui.refreshPatchPanel(); err != nil { + return err + } + gui.refreshSelectedLine(&gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits)) isFocused := gui.g.CurrentView().Name() == "commits" diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index e66df31d8..ea71d9dca 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -23,7 +23,6 @@ import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/git" "github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/theme" "github.com/jesseduffield/lazygit/pkg/updates" @@ -84,13 +83,13 @@ type Gui struct { // non-mutative, so that we don't accidentally end up // with mismatches of data. We might change this in the future type stagingPanelState struct { - SelectedLineIdx int - FirstLineIdx int - LastLineIdx int - Diff string - PatchParser *git.PatchParser - SelectMode int // one of LINE, HUNK, or RANGE - IndexFocused bool // this is for if we show the left or right panel + SelectedLineIdx int + FirstLineIdx int + LastLineIdx int + Diff string + PatchParser *commands.PatchParser + SelectMode int // one of LINE, HUNK, or RANGE + SecondaryFocused bool // this is for if we show the left or right panel } type mergingPanelState struct { @@ -152,8 +151,11 @@ type guiState struct { Contexts map[string]string CherryPickedCommits []*commands.Commit SplitMainPanel bool + PatchManager *commands.PatchManager } +// for now the split view will always be on + // NewGui builds a new gui handler func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater) (*Gui, error) { @@ -390,7 +392,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { main := "main" secondary := "secondary" - swappingMainPanels := gui.State.Panels.Staging != nil && gui.State.Panels.Staging.IndexFocused + swappingMainPanels := gui.State.Panels.Staging != nil && gui.State.Panels.Staging.SecondaryFocused if swappingMainPanels { main = "secondary" secondary = "main" diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 77d295d33..7718535ed 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -143,6 +143,11 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { Modifier: gocui.ModNone, Handler: gui.handleCreateOptionsMenu, }, { + ViewName: "", + Key: gocui.KeyCtrlP, + Modifier: gocui.ModNone, + Handler: gui.handleCreatePatchOptionsMenu, + }, { ViewName: "status", Key: 'e', Modifier: gocui.ModNone, @@ -523,6 +528,20 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { Handler: gui.handleOpenOldCommitFile, Description: gui.Tr.SLocalize("openFile"), }, + { + ViewName: "commitFiles", + Key: gocui.KeySpace, + Modifier: gocui.ModNone, + Handler: gui.handleToggleFileForPatch, + Description: gui.Tr.SLocalize("toggleAddToPatch"), + }, + { + ViewName: "commitFiles", + Key: gocui.KeyEnter, + Modifier: gocui.ModNone, + Handler: gui.handleEnterCommitFile, + Description: gui.Tr.SLocalize("enterFile"), + }, } for _, viewName := range []string{"status", "branches", "files", "commits", "commitFiles", "stash", "menu"} { diff --git a/pkg/gui/patch_options_panel.go b/pkg/gui/patch_options_panel.go new file mode 100644 index 000000000..14d13cd82 --- /dev/null +++ b/pkg/gui/patch_options_panel.go @@ -0,0 +1,89 @@ +package gui + +import ( + "fmt" + + "github.com/jesseduffield/gocui" +) + +type patchMenuOption struct { + displayName string + function func() error +} + +// GetDisplayStrings is a function. +func (o *patchMenuOption) GetDisplayStrings(isFocused bool) []string { + return []string{o.displayName} +} + +func (gui *Gui) handleCreatePatchOptionsMenu(g *gocui.Gui, v *gocui.View) error { + m := gui.State.PatchManager + if m == nil { + return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NoPatchError")) + } + + options := []*patchMenuOption{ + {displayName: "discard patch", function: gui.handleDeletePatchFromCommit}, + {displayName: "pull patch out into index", function: gui.handlePullPatchIntoWorkingTree}, + {displayName: "save patch to file"}, + {displayName: "clear patch", function: gui.handleClearPatch}, + } + + selectedCommit := gui.getSelectedCommit(gui.g) + if selectedCommit != nil && gui.State.PatchManager.CommitSha != selectedCommit.Sha { + options = append(options, &patchMenuOption{ + displayName: fmt.Sprintf("move patch to selected commit (%s)", selectedCommit.Sha), + function: gui.handleMovePatchToSelectedCommit, + }) + } + + handleMenuPress := func(index int) error { + return options[index].function() + } + + return gui.createMenu(gui.Tr.SLocalize("PatchOptionsTitle"), options, len(options), handleMenuPress) +} + +func (gui *Gui) getPatchCommitIndex() int { + for index, commit := range gui.State.Commits { + if commit.Sha == gui.State.PatchManager.CommitSha { + return index + } + } + return -1 +} + +func (gui *Gui) handleDeletePatchFromCommit() error { + // TODO: deal with when we're already rebasing + + return gui.WithWaitingStatus(gui.Tr.SLocalize("RebasingStatus"), func() error { + commitIndex := gui.getPatchCommitIndex() + err := gui.GitCommand.DeletePatchesFromCommit(gui.State.Commits, commitIndex, gui.State.PatchManager) + return gui.handleGenericMergeCommandResult(err) + }) +} + +func (gui *Gui) handleMovePatchToSelectedCommit() error { + // TODO: deal with when we're already rebasing + + return gui.WithWaitingStatus(gui.Tr.SLocalize("RebasingStatus"), func() error { + commitIndex := gui.getPatchCommitIndex() + err := gui.GitCommand.MovePatchToSelectedCommit(gui.State.Commits, commitIndex, gui.State.Panels.Commits.SelectedLine, gui.State.PatchManager) + return gui.handleGenericMergeCommandResult(err) + }) +} + +func (gui *Gui) handlePullPatchIntoWorkingTree() error { + // TODO: deal with when we're already rebasing + + return gui.WithWaitingStatus(gui.Tr.SLocalize("RebasingStatus"), func() error { + commitIndex := gui.getPatchCommitIndex() + err := gui.GitCommand.PullPatchIntoIndex(gui.State.Commits, commitIndex, gui.State.PatchManager) + return gui.handleGenericMergeCommandResult(err) + }) +} + +func (gui *Gui) handleClearPatch() error { + gui.State.PatchManager = nil + return gui.refreshCommitFilesView() +} diff --git a/pkg/gui/staging_panel.go b/pkg/gui/staging_panel.go index 93e1d46c5..0a47d0a76 100644 --- a/pkg/gui/staging_panel.go +++ b/pkg/gui/staging_panel.go @@ -1,12 +1,11 @@ package gui import ( - "strings" - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/git" + "github.com/jesseduffield/lazygit/pkg/commands" ) +// these represent what select mode we're in const ( LINE = iota RANGE @@ -16,49 +15,65 @@ const ( func (gui *Gui) refreshStagingPanel() error { state := gui.State.Panels.Staging - file, err := gui.getSelectedFile(gui.g) - if err != nil { - if err != gui.Errors.ErrNoFiles { - return err - } - return gui.handleStagingEscape(gui.g, nil) - } + // file, err := gui.getSelectedFile(gui.g) + // if err != nil { + // if err != gui.Errors.ErrNoFiles { + // return err + // } + // return gui.handleStagingEscape(gui.g, nil) + // } gui.State.SplitMainPanel = true - indexFocused := false + secondaryFocused := false if state != nil { - indexFocused = state.IndexFocused - } - - if !file.HasUnstagedChanges && !file.HasStagedChanges { - return gui.handleStagingEscape(gui.g, nil) + secondaryFocused = state.SecondaryFocused } - if (indexFocused && !file.HasStagedChanges) || (!indexFocused && !file.HasUnstagedChanges) { - indexFocused = !indexFocused + // if !file.HasUnstagedChanges && !file.HasStagedChanges { + // return gui.handleStagingEscape(gui.g, nil) + // } + + // if (secondaryFocused && !file.HasStagedChanges) || (!secondaryFocused && !file.HasUnstagedChanges) { + // secondaryFocused = !secondaryFocused + // } + + // getDiffs := func() (string, string) { + // // note for custom diffs, we'll need to send a flag here saying not to use the custom diff + // diff := gui.GitCommand.Diff(file, true, secondaryFocused) + // secondaryColorDiff := gui.GitCommand.Diff(file, false, !secondaryFocused) + // return diff, secondaryColorDiff + // } + + // diff, secondaryColorDiff := getDiffs() + + // // if we have e.g. a deleted file with nothing else to the diff will have only + // // 4-5 lines in which case we'll swap panels + // if len(strings.Split(diff, "\n")) < 5 { + // if len(strings.Split(secondaryColorDiff, "\n")) < 5 { + // return gui.handleStagingEscape(gui.g, nil) + // } + // secondaryFocused = !secondaryFocused + // diff, secondaryColorDiff = getDiffs() + // } + + // get diff from commit file that's currently selected + commitFile := gui.getSelectedCommitFile(gui.g) + if commitFile == nil { + return gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles")) } - getDiffs := func() (string, string) { - // note for custom diffs, we'll need to send a flag here saying not to use the custom diff - diff := gui.GitCommand.Diff(file, true, indexFocused) - secondaryColorDiff := gui.GitCommand.Diff(file, false, !indexFocused) - return diff, secondaryColorDiff + diff, err := gui.GitCommand.ShowCommitFile(commitFile.Sha, commitFile.Name, true) + if err != nil { + return err } - diff, secondaryColorDiff := getDiffs() - - // if we have e.g. a deleted file with nothing else to the diff will have only - // 4-5 lines in which case we'll swap panels - if len(strings.Split(diff, "\n")) < 5 { - if len(strings.Split(secondaryColorDiff, "\n")) < 5 { - return gui.handleStagingEscape(gui.g, nil) - } - indexFocused = !indexFocused - diff, secondaryColorDiff = getDiffs() + secondaryColorDiff := gui.State.PatchManager.RenderPatchForFile(commitFile.Name, false, false) + if err != nil { + return err } - patchParser, err := git.NewPatchParser(gui.Log, diff) + patchParser, err := commands.NewPatchParser(gui.Log, diff) if err != nil { return nil } @@ -92,13 +107,13 @@ func (gui *Gui) refreshStagingPanel() error { } gui.State.Panels.Staging = &stagingPanelState{ - PatchParser: patchParser, - SelectedLineIdx: selectedLineIdx, - SelectMode: selectMode, - FirstLineIdx: firstLineIdx, - LastLineIdx: lastLineIdx, - Diff: diff, - IndexFocused: indexFocused, + PatchParser: patchParser, + SelectedLineIdx: selectedLineIdx, + SelectMode: selectMode, + FirstLineIdx: firstLineIdx, + LastLineIdx: lastLineIdx, + Diff: diff, + SecondaryFocused: secondaryFocused, } if err := gui.refreshView(); err != nil { @@ -123,14 +138,14 @@ func (gui *Gui) refreshStagingPanel() error { func (gui *Gui) handleTogglePanel(g *gocui.Gui, v *gocui.View) error { state := gui.State.Panels.Staging - state.IndexFocused = !state.IndexFocused + state.SecondaryFocused = !state.SecondaryFocused return gui.refreshStagingPanel() } func (gui *Gui) handleStagingEscape(g *gocui.Gui, v *gocui.View) error { gui.State.Panels.Staging = nil - return gui.switchFocus(gui.g, nil, gui.getFilesView()) + return gui.switchFocus(gui.g, nil, gui.getCommitFilesView()) } func (gui *Gui) handleStagingPrevLine(g *gocui.Gui, v *gocui.View) error { @@ -203,7 +218,9 @@ func (gui *Gui) handleCycleLine(change int) error { func (gui *Gui) refreshView() error { state := gui.State.Panels.Staging - colorDiff := state.PatchParser.Render(state.FirstLineIdx, state.LastLineIdx) + filename := gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLine].Name + + colorDiff := state.PatchParser.Render(state.FirstLineIdx, state.LastLineIdx, gui.State.PatchManager.GetFileIncLineIndices(filename)) mainView := gui.getMainView() mainView.Highlight = true @@ -258,17 +275,57 @@ func (gui *Gui) focusSelection(includeCurrentHunk bool) error { } func (gui *Gui) handleStageSelection(g *gocui.Gui, v *gocui.View) error { - return gui.applySelection(false) + state := gui.State.Panels.Staging + + // add range of lines to those set for the file + commitFile := gui.getSelectedCommitFile(gui.g) + if commitFile == nil { + return gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles")) + } + + gui.State.PatchManager.AddFileLineRange(commitFile.Name, state.FirstLineIdx, state.LastLineIdx) + + if err := gui.refreshCommitFilesView(); err != nil { + return err + } + + if err := gui.refreshStagingPanel(); err != nil { + return err + } + + return nil + + // return gui.applySelection(false) } func (gui *Gui) handleResetSelection(g *gocui.Gui, v *gocui.View) error { - return gui.applySelection(true) + state := gui.State.Panels.Staging + + // add range of lines to those set for the file + commitFile := gui.getSelectedCommitFile(gui.g) + if commitFile == nil { + return gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles")) + } + + gui.State.PatchManager.RemoveFileLineRange(commitFile.Name, state.FirstLineIdx, state.LastLineIdx) + + if err := gui.refreshCommitFilesView(); err != nil { + return err + } + + if err := gui.refreshStagingPanel(); err != nil { + return err + } + + return nil + + // return gui.applySelection(true) } func (gui *Gui) applySelection(reverse bool) error { state := gui.State.Panels.Staging - if !reverse && state.IndexFocused { + if !reverse && state.SecondaryFocused { return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("CantStageStaged")) } @@ -277,7 +334,7 @@ func (gui *Gui) applySelection(reverse bool) error { return err } - patch := git.ModifiedPatch(gui.Log, file.Name, state.Diff, state.FirstLineIdx, state.LastLineIdx, reverse) + patch := commands.ModifiedPatchForRange(gui.Log, file.Name, state.Diff, state.FirstLineIdx, state.LastLineIdx, reverse, false) if patch == "" { return nil @@ -285,7 +342,7 @@ func (gui *Gui) applySelection(reverse bool) error { // apply the patch then refresh this panel // create a new temp file with the patch, then call git apply with that patch - _, err = gui.GitCommand.ApplyPatch(patch, false, !reverse || state.IndexFocused) + err = gui.GitCommand.ApplyPatch(patch, false, !reverse || state.SecondaryFocused, "") if err != nil { return err } diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 5c5678a14..719285be3 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -368,13 +368,13 @@ func (gui *Gui) changeSelectedLine(line *int, total int, up bool) { return } - *line -= 1 + *line-- } else { if *line == -1 || *line == total-1 { return } - *line += 1 + *line++ } } |