diff --git a/docs/Config.md b/docs/Config.md index c2565ae93..724f35334 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -65,6 +65,7 @@ git: parseEmoji: false os: editCommand: '' # see 'Configuring File Editing' section + editCommandTemplate: '{{editor}} {{filename}}' openCommand: '' refresher: refreshInterval: 10 # file/submodule refresh interval in seconds @@ -241,6 +242,24 @@ os: Lazygit will log an error if none of these options are set. +You can specify a line number you are currently at when in the line-by-line mode. + +```yaml +os: + editCommand: 'vim' + editCommandTemplate: '{{editor}} +{{line}} {{filename}}' +``` + +or + +```yaml +os: + editCommand: 'code' + editCommandTemplate: '{{editor}} --goto {{filename}}:{{line}}' +``` + +`{{editor}}` in `editCommandTemplate` is replaced with the value of `editCommand`. + ### Recommended Config Values for users of VSCode diff --git a/pkg/commands/files.go b/pkg/commands/files.go index 9fa5fb1bd..ac508941b 100644 --- a/pkg/commands/files.go +++ b/pkg/commands/files.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "strconv" "time" "github.com/go-errors/errors" @@ -321,7 +322,7 @@ func (c *GitCommand) ResetAndClean() error { return c.RemoveUntrackedFiles() } -func (c *GitCommand) EditFileCmdStr(filename string) (string, error) { +func (c *GitCommand) EditFileCmdStr(filename string, lineNumber int) (string, error) { editor := c.Config.GetUserConfig().OS.EditCommand if editor == "" { @@ -346,5 +347,12 @@ func (c *GitCommand) EditFileCmdStr(filename string) (string, error) { return "", errors.New("No editor defined in config file, $GIT_EDITOR, $VISUAL, $EDITOR, or git config") } - return fmt.Sprintf("%s %s", editor, c.OSCommand.Quote(filename)), nil + templateValues := map[string]string{ + "editor": editor, + "filename": c.OSCommand.Quote(filename), + "line": strconv.Itoa(lineNumber), + } + + editCmdTemplate := c.Config.GetUserConfig().OS.EditCommandTemplate + return utils.ResolvePlaceholderString(editCmdTemplate, templateValues), nil } diff --git a/pkg/commands/files_test.go b/pkg/commands/files_test.go index 01c1a6793..0410c95c2 100644 --- a/pkg/commands/files_test.go +++ b/pkg/commands/files_test.go @@ -741,18 +741,20 @@ func TestGitCommandRemoveUntrackedFiles(t *testing.T) { // TestEditFileCmdStr is a function. func TestEditFileCmdStr(t *testing.T) { type scenario struct { - filename string - configEditCommand string - command func(string, ...string) *exec.Cmd - getenv func(string) string - getGitConfigValue func(string) (string, error) - test func(string, error) + filename string + configEditCommand string + configEditCommandTemplate string + command func(string, ...string) *exec.Cmd + getenv func(string) string + getGitConfigValue func(string) (string, error) + test func(string, error) } scenarios := []scenario{ { "test", "", + "{{editor}} {{filename}}", func(name string, arg ...string) *exec.Cmd { return secureexec.Command("exit", "1") }, @@ -769,6 +771,7 @@ func TestEditFileCmdStr(t *testing.T) { { "test", "nano", + "{{editor}} {{filename}}", func(name string, args ...string) *exec.Cmd { assert.Equal(t, "which", name) return secureexec.Command("echo") @@ -787,6 +790,7 @@ func TestEditFileCmdStr(t *testing.T) { { "test", "", + "{{editor}} {{filename}}", func(name string, arg ...string) *exec.Cmd { assert.Equal(t, "which", name) return secureexec.Command("exit", "1") @@ -805,6 +809,7 @@ func TestEditFileCmdStr(t *testing.T) { { "test", "", + "{{editor}} {{filename}}", func(name string, arg ...string) *exec.Cmd { assert.Equal(t, "which", name) return secureexec.Command("exit", "1") @@ -826,6 +831,7 @@ func TestEditFileCmdStr(t *testing.T) { { "test", "", + "{{editor}} {{filename}}", func(name string, arg ...string) *exec.Cmd { assert.Equal(t, "which", name) return secureexec.Command("exit", "1") @@ -848,6 +854,7 @@ func TestEditFileCmdStr(t *testing.T) { { "test", "", + "{{editor}} {{filename}}", func(name string, arg ...string) *exec.Cmd { assert.Equal(t, "which", name) return secureexec.Command("echo") @@ -866,6 +873,7 @@ func TestEditFileCmdStr(t *testing.T) { { "file/with space", "", + "{{editor}} {{filename}}", func(name string, args ...string) *exec.Cmd { assert.Equal(t, "which", name) return secureexec.Command("echo") @@ -881,14 +889,34 @@ func TestEditFileCmdStr(t *testing.T) { assert.Equal(t, "vi \"file/with space\"", cmdStr) }, }, + { + "open file/at line", + "vim", + "{{editor}} +{{line}} {{filename}}", + func(name string, args ...string) *exec.Cmd { + assert.Equal(t, "which", name) + return secureexec.Command("echo") + }, + func(env string) string { + return "" + }, + func(cf string) (string, error) { + return "", nil + }, + func(cmdStr string, err error) { + assert.NoError(t, err) + assert.Equal(t, "vim +1 \"open file/at line\"", cmdStr) + }, + }, } for _, s := range scenarios { gitCmd := NewDummyGitCommand() gitCmd.Config.GetUserConfig().OS.EditCommand = s.configEditCommand + gitCmd.Config.GetUserConfig().OS.EditCommandTemplate = s.configEditCommandTemplate gitCmd.OSCommand.Command = s.command gitCmd.OSCommand.Getenv = s.getenv gitCmd.getGitConfigValue = s.getGitConfigValue - s.test(gitCmd.EditFileCmdStr(s.filename)) + s.test(gitCmd.EditFileCmdStr(s.filename, 1)) } } diff --git a/pkg/commands/patch/patch_modifier.go b/pkg/commands/patch/patch_modifier.go index c2bbc60f6..3ab13931b 100644 --- a/pkg/commands/patch/patch_modifier.go +++ b/pkg/commands/patch/patch_modifier.go @@ -138,7 +138,14 @@ func ModifiedPatchForLines(log *logrus.Entry, filename string, diffText string, // I want to know, given a hunk, what line a given index is on func (hunk *PatchHunk) LineNumberOfLine(idx int) int { - lines := hunk.bodyLines[0 : idx-hunk.FirstLineIdx-1] + n := idx - hunk.FirstLineIdx - 1 + if n < 0 { + n = 0 + } else if n >= len(hunk.bodyLines) { + n = len(hunk.bodyLines) - 1 + } + + lines := hunk.bodyLines[0:n] offset := nLinesWithPrefix(lines, []string{"+", " "}) diff --git a/pkg/config/config_default_platform.go b/pkg/config/config_default_platform.go index fabdf8a88..0f56297d3 100644 --- a/pkg/config/config_default_platform.go +++ b/pkg/config/config_default_platform.go @@ -5,8 +5,9 @@ package config // GetPlatformDefaultConfig gets the defaults for the platform func GetPlatformDefaultConfig() OSConfig { return OSConfig{ - EditCommand: ``, - OpenCommand: "open {{filename}}", - OpenLinkCommand: "open {{link}}", + EditCommand: ``, + EditCommandTemplate: `{{editor}} {{filename}}`, + OpenCommand: "open {{filename}}", + OpenLinkCommand: "open {{link}}", } } diff --git a/pkg/config/config_linux.go b/pkg/config/config_linux.go index 8399e0e47..fe75b7322 100644 --- a/pkg/config/config_linux.go +++ b/pkg/config/config_linux.go @@ -3,8 +3,9 @@ package config // GetPlatformDefaultConfig gets the defaults for the platform func GetPlatformDefaultConfig() OSConfig { return OSConfig{ - EditCommand: ``, - OpenCommand: `sh -c "xdg-open {{filename}} >/dev/null"`, - OpenLinkCommand: `sh -c "xdg-open {{link}} >/dev/null"`, + EditCommand: ``, + EditCommandTemplate: `{{editor}} {{filename}}`, + OpenCommand: `sh -c "xdg-open {{filename}} >/dev/null"`, + OpenLinkCommand: `sh -c "xdg-open {{link}} >/dev/null"`, } } diff --git a/pkg/config/config_windows.go b/pkg/config/config_windows.go index f6780eb6b..71124808b 100644 --- a/pkg/config/config_windows.go +++ b/pkg/config/config_windows.go @@ -3,8 +3,9 @@ package config // GetPlatformDefaultConfig gets the defaults for the platform func GetPlatformDefaultConfig() OSConfig { return OSConfig{ - EditCommand: ``, - OpenCommand: `cmd /c "start "" {{filename}}"`, - OpenLinkCommand: `cmd /c "start "" {{link}}"`, + EditCommand: ``, + EditCommandTemplate: `{{editor}} {{filename}}`, + OpenCommand: `cmd /c "start "" {{filename}}"`, + OpenLinkCommand: `cmd /c "start "" {{link}}"`, } } diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index 2f435f066..d2d98ac52 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -255,6 +255,9 @@ type OSConfig struct { // EditCommand is the command for editing a file EditCommand string `yaml:"editCommand,omitempty"` + // EditCommandTemplate is the command template for editing a file + EditCommandTemplate string `yaml:"editCommandTemplate,omitempty"` + // OpenCommand is the command for opening a file OpenCommand string `yaml:"openCommand,omitempty"` diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 48c0621cb..f652c3f05 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -474,13 +474,17 @@ func (gui *Gui) handleCommitEditorPress() error { } func (gui *Gui) editFile(filename string) error { - cmdStr, err := gui.GitCommand.EditFileCmdStr(filename) + return gui.editFileAtLine(filename, 1) +} + +func (gui *Gui) editFileAtLine(filename string, lineNumber int) error { + cmdStr, err := gui.GitCommand.EditFileCmdStr(filename, lineNumber) if err != nil { return gui.surfaceError(err) } return gui.runSubprocessWithSuspenseAndRefresh( - gui.OSCommand.WithSpan(gui.Tr.Spans.EditFile).PrepareShellSubProcess(cmdStr), + gui.OSCommand.WithSpan(gui.Tr.Spans.EditFile).ShellCommandFromString(cmdStr), ) } diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index f9537e755..4b20bab95 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -1301,7 +1301,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { ViewName: "main", Contexts: []string{string(MAIN_STAGING_CONTEXT_KEY)}, Key: gui.getKey(config.Universal.Edit), - Handler: gui.handleFileEdit, + Handler: gui.handleLineByLineEdit, Description: gui.Tr.LcEditFile, }, { diff --git a/pkg/gui/line_by_line_panel.go b/pkg/gui/line_by_line_panel.go index d7a5380b2..8121b6ed3 100644 --- a/pkg/gui/line_by_line_panel.go +++ b/pkg/gui/line_by_line_panel.go @@ -272,3 +272,13 @@ func (gui *Gui) withLBLActiveCheck(f func(*LblPanelState) error) error { return f(state) } + +func (gui *Gui) handleLineByLineEdit() error { + file := gui.getSelectedFile() + if file == nil { + return nil + } + + lineNumber := gui.State.Panels.LineByLine.CurrentLineNumber() + return gui.editFileAtLine(file.Name, lineNumber) +}