Press enter in main view of files/commitFiles to enter staging/patch-building

This was already possible, but only when a file was selected, and it woudln't
always land on the right line when a pager was used. Now it's also possible to
do this for directories, and it jumps to the right line.

At the moment this is a hack that relies on delta's hyperlinks, so it only works
on lines that have hyperlinks (added and context).

The implementation is very hacky for other reasons too (e.g. the addition of the
weirdly named ClickedViewRealLineIdx to OnFocusOpts).
This commit is contained in:
Stefan Haller 2024-09-16 20:38:22 +02:00
parent 4cf8d11783
commit f0b49eba71
10 changed files with 155 additions and 20 deletions

View file

@ -104,6 +104,38 @@ func (self *Patch) LineNumberOfLine(idx int) int {
return hunk.newStart + offset
}
// Takes a line number in the new file and returns the line index in the patch.
// This is the opposite of LineNumberOfLine.
// If the line number is not contained in any of the hunks, it returns the
// closest position.
func (self *Patch) PatchLineForLineNumber(lineNumber int) int {
if len(self.hunks) == 0 {
return len(self.header)
}
for hunkIdx, hunk := range self.hunks {
if lineNumber <= hunk.newStart {
return self.HunkStartIdx(hunkIdx)
}
if lineNumber < hunk.newStart+hunk.newLength() {
lines := hunk.bodyLines
offset := lineNumber - hunk.newStart
for i, line := range lines {
if offset == 0 {
return self.HunkStartIdx(hunkIdx) + i + 1
}
if line.Kind == ADDITION || line.Kind == CONTEXT {
offset--
}
}
}
}
return self.LineCount() - 1
}
// Returns hunk index containing the line at the given patch line index
func (self *Patch) HunkContainingLine(idx int) int {
for hunkIdx, hunk := range self.hunks {

View file

@ -54,8 +54,9 @@ func (gui *Gui) resetHelpersAndControllers() {
gpgHelper := helpers.NewGpgHelper(helperCommon)
viewHelper := helpers.NewViewHelper(helperCommon, gui.State.Contexts)
windowHelper := helpers.NewWindowHelper(helperCommon, viewHelper)
patchBuildingHelper := helpers.NewPatchBuildingHelper(helperCommon)
stagingHelper := helpers.NewStagingHelper(helperCommon)
stagingHelper := helpers.NewStagingHelper(helperCommon, windowHelper)
mergeConflictsHelper := helpers.NewMergeConflictsHelper(helperCommon)
searchHelper := helpers.NewSearchHelper(helperCommon)
@ -75,7 +76,6 @@ func (gui *Gui) resetHelpersAndControllers() {
rebaseHelper,
)
bisectHelper := helpers.NewBisectHelper(helperCommon)
windowHelper := helpers.NewWindowHelper(helperCommon, viewHelper)
modeHelper := helpers.NewModeHelper(
helperCommon,
diffHelper,

View file

@ -470,7 +470,7 @@ func (self *CommitFilesController) currentFromToReverseForPatchBuilding() (strin
}
func (self *CommitFilesController) enter(node *filetree.CommitFileNode) error {
return self.enterCommitFile(node, types.OnFocusOpts{ClickedWindowName: "", ClickedViewLineIdx: -1})
return self.enterCommitFile(node, types.OnFocusOpts{ClickedWindowName: "", ClickedViewLineIdx: -1, ClickedViewRealLineIdx: -1})
}
func (self *CommitFilesController) enterCommitFile(node *filetree.CommitFileNode, opts types.OnFocusOpts) error {
@ -545,11 +545,35 @@ func (self *CommitFilesController) expandAll() error {
func (self *CommitFilesController) GetOnClickFocusedMainView() func(mainViewName string, clickedLineIdx int) error {
return func(mainViewName string, clickedLineIdx int) error {
node := self.getSelectedItem()
if node != nil && node.File != nil {
return self.enterCommitFile(node, types.OnFocusOpts{ClickedWindowName: mainViewName, ClickedViewLineIdx: clickedLineIdx})
clickedFile, line, ok := self.c.Helpers().Staging.GetFileAndLineForClickedDiffLine(mainViewName, clickedLineIdx)
if !ok {
line = -1
}
return nil
node := self.getSelectedItem()
if node == nil {
return nil
}
if !node.IsFile() && ok {
relativePath, err := filepath.Rel(self.c.Git().RepoPaths.RepoPath(), clickedFile)
if err != nil {
return err
}
relativePath = "./" + relativePath
self.context().CommitFileTreeViewModel.ExpandToPath(relativePath)
self.c.PostRefreshUpdate(self.context())
idx, ok := self.context().CommitFileTreeViewModel.GetIndexForPath(relativePath)
if ok {
self.context().SetSelectedLineIdx(idx)
self.context().GetViewTrait().FocusPoint(
self.context().ModelIndexToViewIndex(idx))
node = self.context().GetSelected()
}
}
return self.enterCommitFile(node, types.OnFocusOpts{ClickedWindowName: "main", ClickedViewLineIdx: line, ClickedViewRealLineIdx: line})
}
}

View file

@ -325,11 +325,34 @@ func (self *FilesController) GetOnClick() func() error {
func (self *FilesController) GetOnClickFocusedMainView() func(mainViewName string, clickedLineIdx int) error {
return func(mainViewName string, clickedLineIdx int) error {
node := self.getSelectedItem()
if node != nil && node.File != nil {
return self.EnterFile(types.OnFocusOpts{ClickedWindowName: mainViewName, ClickedViewLineIdx: clickedLineIdx})
clickedFile, line, ok := self.c.Helpers().Staging.GetFileAndLineForClickedDiffLine(mainViewName, clickedLineIdx)
if !ok {
line = -1
}
return nil
node := self.context().GetSelected()
if node == nil {
return nil
}
if !node.IsFile() && ok {
relativePath, err := filepath.Rel(self.c.Git().RepoPaths.RepoPath(), clickedFile)
if err != nil {
return err
}
relativePath = "./" + relativePath
self.context().FileTreeViewModel.ExpandToPath(relativePath)
self.c.PostRefreshUpdate(self.context())
idx, ok := self.context().FileTreeViewModel.GetIndexForPath(relativePath)
if ok {
self.context().SetSelectedLineIdx(idx)
self.context().GetViewTrait().FocusPoint(
self.context().ModelIndexToViewIndex(idx))
}
}
return self.EnterFile(types.OnFocusOpts{ClickedWindowName: mainViewName, ClickedViewLineIdx: line, ClickedViewRealLineIdx: line})
}
}
@ -511,7 +534,7 @@ func (self *FilesController) getSelectedFile() *models.File {
}
func (self *FilesController) enter() error {
return self.EnterFile(types.OnFocusOpts{ClickedWindowName: "", ClickedViewLineIdx: -1})
return self.EnterFile(types.OnFocusOpts{ClickedWindowName: "", ClickedViewLineIdx: -1, ClickedViewRealLineIdx: -1})
}
func (self *FilesController) collapseAll() error {

View file

@ -53,8 +53,10 @@ func (self *PatchBuildingHelper) Reset() error {
func (self *PatchBuildingHelper) RefreshPatchBuildingPanel(opts types.OnFocusOpts) {
selectedLineIdx := -1
selectedRealLineIdx := -1
if opts.ClickedWindowName == "main" {
selectedLineIdx = opts.ClickedViewLineIdx
selectedRealLineIdx = opts.ClickedViewRealLineIdx
}
if !self.c.Git().Patch.PatchBuilder.Active() {
@ -86,7 +88,7 @@ func (self *PatchBuildingHelper) RefreshPatchBuildingPanel(opts types.OnFocusOpt
oldState := context.GetState()
state := patch_exploring.NewState(diff, selectedLineIdx, context.GetView(), oldState)
state := patch_exploring.NewState(diff, selectedLineIdx, selectedRealLineIdx, context.GetView(), oldState)
context.SetState(state)
if state == nil {
self.Escape()

View file

@ -1,20 +1,26 @@
package helpers
import (
"regexp"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type StagingHelper struct {
c *HelperCommon
c *HelperCommon
windowHelper *WindowHelper
}
func NewStagingHelper(
c *HelperCommon,
windowHelper *WindowHelper,
) *StagingHelper {
return &StagingHelper{
c: c,
c: c,
windowHelper: windowHelper,
}
}
@ -30,12 +36,16 @@ func (self *StagingHelper) RefreshStagingPanel(focusOpts types.OnFocusOpts) {
}
mainSelectedLineIdx := -1
mainSelectedRealLineIdx := -1
secondarySelectedLineIdx := -1
secondarySelectedRealLineIdx := -1
if focusOpts.ClickedViewLineIdx > 0 {
if secondaryFocused {
secondarySelectedLineIdx = focusOpts.ClickedViewLineIdx
secondarySelectedRealLineIdx = focusOpts.ClickedViewRealLineIdx
} else {
mainSelectedLineIdx = focusOpts.ClickedViewLineIdx
mainSelectedRealLineIdx = focusOpts.ClickedViewRealLineIdx
}
}
@ -63,11 +73,11 @@ func (self *StagingHelper) RefreshStagingPanel(focusOpts types.OnFocusOpts) {
secondaryContext.GetMutex().Lock()
mainContext.SetState(
patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainContext.GetView(), mainContext.GetState()),
patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainSelectedRealLineIdx, mainContext.GetView(), mainContext.GetState()),
)
secondaryContext.SetState(
patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondaryContext.GetView(), secondaryContext.GetState()),
patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondarySelectedRealLineIdx, secondaryContext.GetView(), secondaryContext.GetState()),
)
mainState := mainContext.GetState()
@ -124,3 +134,20 @@ func (self *StagingHelper) secondaryStagingFocused() bool {
func (self *StagingHelper) mainStagingFocused() bool {
return self.c.Context().CurrentStatic().GetKey() == self.c.Contexts().Staging.GetKey()
}
func (self *StagingHelper) GetFileAndLineForClickedDiffLine(windowName string, lineIdx int) (string, int, bool) {
v, _ := self.c.GocuiGui().View(self.windowHelper.GetViewNameForWindow(windowName))
hyperlink, ok := v.HyperLinkInLine(lineIdx, "lazygit-edit:")
if !ok {
return "", 0, false
}
re := regexp.MustCompile(`^lazygit-edit://(.+?):(\d+)$`)
matches := re.FindStringSubmatch(hyperlink)
if matches == nil {
return "", 0, false
}
filepath := matches[1]
lineNumber := utils.MustConvertToInt(matches[2])
return filepath, lineNumber, true
}

View file

@ -163,9 +163,15 @@ func (self *PatchExplorerController) GetMouseKeybindings(opts types.KeybindingsO
return self.withRenderAndFocus(self.HandleMouseDown)()
}
_, line, ok := self.c.Helpers().Staging.GetFileAndLineForClickedDiffLine(self.context.GetWindowName(), opts.Y)
if !ok {
line = -1
}
self.c.Context().Push(self.context, types.OnFocusOpts{
ClickedWindowName: self.context.GetWindowName(),
ClickedViewLineIdx: opts.Y,
ClickedWindowName: self.context.GetWindowName(),
ClickedViewLineIdx: opts.Y,
ClickedViewRealLineIdx: line,
})
return nil

View file

@ -39,7 +39,7 @@ const (
HUNK
)
func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *State) *State {
func NewState(diff string, selectedLineIdx int, selectedRealLineIdx int, view *gocui.View, oldState *State) *State {
if oldState != nil && diff == oldState.diff && selectedLineIdx == -1 {
// if we're here then we can return the old state. If selectedLineIdx was not -1
// then that would mean we were trying to click and potentially drag a range, which
@ -55,6 +55,10 @@ func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *Stat
viewLineIndices, patchLineIndices := wrapPatchLines(diff, view)
if selectedRealLineIdx != -1 {
selectedLineIdx = patch.PatchLineForLineNumber(selectedRealLineIdx)
}
rangeStartLineIdx := 0
if oldState != nil {
rangeStartLineIdx = oldState.rangeStartLineIdx

View file

@ -220,6 +220,9 @@ type IViewTrait interface {
type OnFocusOpts struct {
ClickedWindowName string
ClickedViewLineIdx int
// If not -1, takes precedence over ClickedViewLineIdx.
ClickedViewRealLineIdx int
}
type OnFocusLostOpts struct {