mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-12 08:36:25 -04:00
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:
parent
37855b59e9
commit
a32ed507d3
|
|
@ -114,6 +114,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 {
|
||||
|
|
|
|||
|
|
@ -1653,6 +1653,20 @@ func (v *View) Word(x, y int) (string, bool) {
|
|||
return str[nl:nr], true
|
||||
}
|
||||
|
||||
func (v *View) HyperLinkInLine(y int, urlScheme string) (string, bool) {
|
||||
if y < 0 || y >= len(v.viewLines) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
for _, c := range v.lines[v.viewLines[y].linesY].cells {
|
||||
if strings.HasPrefix(c.hyperlink, urlScheme) {
|
||||
return c.hyperlink, true
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// indexFunc allows to split lines by words taking into account spaces
|
||||
// and 0.
|
||||
func indexFunc(r rune) bool {
|
||||
|
|
|
|||
|
|
@ -52,8 +52,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)
|
||||
|
||||
|
|
@ -73,7 +74,6 @@ func (gui *Gui) resetHelpersAndControllers() {
|
|||
rebaseHelper,
|
||||
)
|
||||
bisectHelper := helpers.NewBisectHelper(helperCommon)
|
||||
windowHelper := helpers.NewWindowHelper(helperCommon, viewHelper)
|
||||
modeHelper := helpers.NewModeHelper(
|
||||
helperCommon,
|
||||
diffHelper,
|
||||
|
|
|
|||
|
|
@ -537,7 +537,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 {
|
||||
|
|
@ -608,11 +608,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.WorktreePath(), 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), false)
|
||||
node = self.context().GetSelected()
|
||||
}
|
||||
}
|
||||
|
||||
return self.enterCommitFile(node, types.OnFocusOpts{ClickedWindowName: "main", ClickedViewLineIdx: line, ClickedViewRealLineIdx: line})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -409,11 +409,34 @@ func (self *FilesController) GetOnDoubleClick() 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.WorktreePath(), 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), false)
|
||||
}
|
||||
}
|
||||
|
||||
return self.EnterFile(types.OnFocusOpts{ClickedWindowName: mainViewName, ClickedViewLineIdx: line, ClickedViewRealLineIdx: line})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -701,7 +724,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 {
|
||||
|
|
|
|||
|
|
@ -56,8 +56,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() {
|
||||
|
|
@ -90,7 +92,7 @@ func (self *PatchBuildingHelper) RefreshPatchBuildingPanel(opts types.OnFocusOpt
|
|||
|
||||
oldState := context.GetState()
|
||||
|
||||
state := patch_exploring.NewState(diff, selectedLineIdx, context.GetView(), oldState, self.c.UserConfig().Gui.UseHunkModeInStagingView)
|
||||
state := patch_exploring.NewState(diff, selectedLineIdx, selectedRealLineIdx, context.GetView(), oldState, self.c.UserConfig().Gui.UseHunkModeInStagingView)
|
||||
context.SetState(state)
|
||||
if state == nil {
|
||||
self.Escape()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -64,11 +74,11 @@ func (self *StagingHelper) RefreshStagingPanel(focusOpts types.OnFocusOpts) {
|
|||
|
||||
hunkMode := self.c.UserConfig().Gui.UseHunkModeInStagingView
|
||||
mainContext.SetState(
|
||||
patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainContext.GetView(), mainContext.GetState(), hunkMode),
|
||||
patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainSelectedRealLineIdx, mainContext.GetView(), mainContext.GetState(), hunkMode),
|
||||
)
|
||||
|
||||
secondaryContext.SetState(
|
||||
patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondaryContext.GetView(), secondaryContext.GetState(), hunkMode),
|
||||
patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondarySelectedRealLineIdx, secondaryContext.GetView(), secondaryContext.GetState(), hunkMode),
|
||||
)
|
||||
|
||||
mainState := mainContext.GetState()
|
||||
|
|
@ -125,3 +135,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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,9 +151,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
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ const (
|
|||
HUNK
|
||||
)
|
||||
|
||||
func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *State, useHunkModeByDefault bool) *State {
|
||||
func NewState(diff string, selectedLineIdx int, selectedRealLineIdx int, view *gocui.View, oldState *State, useHunkModeByDefault bool) *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
|
||||
|
|
@ -61,6 +61,14 @@ func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *Stat
|
|||
|
||||
viewLineIndices, patchLineIndices := wrapPatchLines(diff, view)
|
||||
|
||||
if selectedRealLineIdx != -1 {
|
||||
// PatchLineForLineNumber returns a patch line index, but selectedLineIdx
|
||||
// is in view-line (wrapped) space, so convert it. Without this the
|
||||
// landing line is off by the number of wrapped lines above it.
|
||||
patchLineIdx := patch.PatchLineForLineNumber(selectedRealLineIdx)
|
||||
selectedLineIdx = viewLineIndices[lo.Clamp(patchLineIdx, 0, len(viewLineIndices)-1)]
|
||||
}
|
||||
|
||||
rangeStartLineIdx := 0
|
||||
if oldState != nil {
|
||||
rangeStartLineIdx = oldState.rangeStartLineIdx
|
||||
|
|
@ -80,7 +88,7 @@ func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *Stat
|
|||
if selectedLineIdx >= 0 {
|
||||
// Clamp to the number of wrapped view lines; index might be out of
|
||||
// bounds if a custom diff renderer is being used which produces more lines
|
||||
selectedLineIdx = min(selectedLineIdx, len(viewLineIndices)-1)
|
||||
selectedLineIdx = min(selectedLineIdx, len(patchLineIndices)-1)
|
||||
|
||||
selectMode = RANGE
|
||||
rangeStartLineIdx = selectedLineIdx
|
||||
|
|
|
|||
|
|
@ -227,8 +227,12 @@ type IViewTrait interface {
|
|||
}
|
||||
|
||||
type OnFocusOpts struct {
|
||||
ClickedWindowName string
|
||||
ClickedViewLineIdx int
|
||||
ClickedWindowName string
|
||||
ClickedViewLineIdx int
|
||||
|
||||
// If not -1, takes precedence over ClickedViewLineIdx.
|
||||
ClickedViewRealLineIdx int
|
||||
|
||||
ScrollSelectionIntoView bool
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue