mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-01-10 04:07:18 +02:00
Merge pull request #2523 from stefanhaller/editor-config
This commit is contained in:
commit
04e0a9bb45
@ -96,9 +96,12 @@ git:
|
||||
parseEmoji: false
|
||||
diffContextSize: 3 # how many lines of context are shown around a change in diffs
|
||||
os:
|
||||
editCommand: '' # see 'Configuring File Editing' section
|
||||
editCommandTemplate: ''
|
||||
openCommand: ''
|
||||
editPreset: '' # see 'Configuring File Editing' section
|
||||
edit: ''
|
||||
editAtLine: ''
|
||||
editAtLineAndWait: ''
|
||||
open: ''
|
||||
openLink: ''
|
||||
refresher:
|
||||
refreshInterval: 10 # File/submodule refresh interval in seconds. Auto-refresh can be disabled via option 'git.autoRefresh'.
|
||||
fetchInterval: 60 # Re-fetch interval in seconds. Auto-fetch can be disabled via option 'git.autoFetch'.
|
||||
@ -268,40 +271,41 @@ os:
|
||||
|
||||
### Configuring File Editing
|
||||
|
||||
Lazygit will edit a file with the first set editor in the following:
|
||||
There are two commands for opening files, `o` for "open" and `e` for "edit". `o`
|
||||
acts as if the file was double-clicked in the Finder/Explorer, so it also works
|
||||
for non-text files, whereas `e` opens the file in an editor. `e` can also jump
|
||||
to the right line in the file if you invoke it from the staging panel, for
|
||||
example.
|
||||
|
||||
1. config.yaml
|
||||
To tell lazygit which editor to use for the `e` command, the easiest way to do
|
||||
that is to provide an editPreset config, e.g.
|
||||
|
||||
```yaml
|
||||
os:
|
||||
editCommand: 'vim' # as an example
|
||||
editPreset: 'vscode'
|
||||
```
|
||||
|
||||
2. \$(git config core.editor)
|
||||
3. \$GIT_EDITOR
|
||||
4. \$VISUAL
|
||||
5. \$EDITOR
|
||||
6. \$(which vi)
|
||||
Supported presets are `vim`, `emacs`, `nano`, `vscode`, `sublime`, `bbedit`, and
|
||||
`xcode`. In many cases lazygit will be able to guess the right preset from your
|
||||
$(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
|
||||
|
||||
Lazygit will log an error if none of these options are set.
|
||||
|
||||
You can specify the current line number when you're in the patch explorer.
|
||||
If for some reason you are not happy with the default commands from a preset, or
|
||||
there simply is no preset for your editor, you can customize the commands by
|
||||
setting the `edit`, `editAtLine`, and `editAtLineAndWait` options, e.g.:
|
||||
|
||||
```yaml
|
||||
os:
|
||||
editCommand: 'vim'
|
||||
editCommandTemplate: '{{editor}} +{{line}} -- {{filename}}'
|
||||
edit: 'myeditor {{filename}}'
|
||||
editAtLine: 'myeditor --line={{line}} {{filename}}'
|
||||
editAtLineAndWait: 'myeditor --block --line={{line}} {{filename}}'
|
||||
editInTerminal: true
|
||||
```
|
||||
|
||||
or
|
||||
The `editInTerminal` option is used to decide whether lazygit needs to suspend
|
||||
itself to the background before calling the editor.
|
||||
|
||||
```yaml
|
||||
os:
|
||||
editCommand: 'code'
|
||||
editCommandTemplate: '{{editor}} --goto -- {{filename}}:{{line}}'
|
||||
```
|
||||
|
||||
`{{editor}}` in `editCommandTemplate` is replaced with the value of `editCommand`.
|
||||
Contributions of new editor presets are welcome; see the `getPreset` function in
|
||||
[`editor_presets.go`](https://github.com/jesseduffield/lazygit/blob/master/pkg/config/editor_presets.go).
|
||||
|
||||
### Overriding default config file location
|
||||
|
||||
@ -317,15 +321,6 @@ or
|
||||
LG_CONFIG_FILE="$HOME/.base_lg_conf,$HOME/.light_theme_lg_conf" lazygit
|
||||
```
|
||||
|
||||
### Recommended Config Values
|
||||
|
||||
for users of VSCode
|
||||
|
||||
```yaml
|
||||
os:
|
||||
openCommand: 'code -rg {{filename}}'
|
||||
```
|
||||
|
||||
## Color Attributes
|
||||
|
||||
For color attributes you can choose an array of attributes (with max one color attribute)
|
||||
|
@ -3,8 +3,10 @@ package git_commands
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
@ -27,7 +29,7 @@ func (self *FileCommands) Cat(fileName string) (string, error) {
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
func (self *FileCommands) GetEditCmdStr(filename string, lineNumber int) (string, error) {
|
||||
func (self *FileCommands) GetEditCmdStrLegacy(filename string, lineNumber int) (string, error) {
|
||||
editor := self.UserConfig.OS.EditCommand
|
||||
|
||||
if editor == "" {
|
||||
@ -72,3 +74,82 @@ func (self *FileCommands) GetEditCmdStr(filename string, lineNumber int) (string
|
||||
}
|
||||
return utils.ResolvePlaceholderString(editCmdTemplate, templateValues), nil
|
||||
}
|
||||
|
||||
func (self *FileCommands) GetEditCmdStr(filename string) (string, bool) {
|
||||
// Legacy support for old config; to be removed at some point
|
||||
if self.UserConfig.OS.Edit == "" && self.UserConfig.OS.EditCommandTemplate != "" {
|
||||
if cmdStr, err := self.GetEditCmdStrLegacy(filename, 1); err == nil {
|
||||
return cmdStr, true
|
||||
}
|
||||
}
|
||||
|
||||
template, editInTerminal := config.GetEditTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
|
||||
|
||||
templateValues := map[string]string{
|
||||
"filename": self.cmd.Quote(filename),
|
||||
}
|
||||
|
||||
cmdStr := utils.ResolvePlaceholderString(template, templateValues)
|
||||
return cmdStr, editInTerminal
|
||||
}
|
||||
|
||||
func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (string, bool) {
|
||||
// Legacy support for old config; to be removed at some point
|
||||
if self.UserConfig.OS.EditAtLine == "" && self.UserConfig.OS.EditCommandTemplate != "" {
|
||||
if cmdStr, err := self.GetEditCmdStrLegacy(filename, lineNumber); err == nil {
|
||||
return cmdStr, true
|
||||
}
|
||||
}
|
||||
|
||||
template, editInTerminal := config.GetEditAtLineTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
|
||||
|
||||
templateValues := map[string]string{
|
||||
"filename": self.cmd.Quote(filename),
|
||||
"line": strconv.Itoa(lineNumber),
|
||||
}
|
||||
|
||||
cmdStr := utils.ResolvePlaceholderString(template, templateValues)
|
||||
return cmdStr, editInTerminal
|
||||
}
|
||||
|
||||
func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber int) string {
|
||||
// Legacy support for old config; to be removed at some point
|
||||
if self.UserConfig.OS.EditAtLineAndWait == "" && self.UserConfig.OS.EditCommandTemplate != "" {
|
||||
if cmdStr, err := self.GetEditCmdStrLegacy(filename, lineNumber); err == nil {
|
||||
return cmdStr
|
||||
}
|
||||
}
|
||||
|
||||
template := config.GetEditAtLineAndWaitTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
|
||||
|
||||
templateValues := map[string]string{
|
||||
"filename": self.cmd.Quote(filename),
|
||||
"line": strconv.Itoa(lineNumber),
|
||||
}
|
||||
|
||||
cmdStr := utils.ResolvePlaceholderString(template, templateValues)
|
||||
return cmdStr
|
||||
}
|
||||
|
||||
func (self *FileCommands) guessDefaultEditor() string {
|
||||
// Try to query a few places where editors get configured
|
||||
editor := self.config.GetCoreEditor()
|
||||
if editor == "" {
|
||||
editor = self.os.Getenv("GIT_EDITOR")
|
||||
}
|
||||
if editor == "" {
|
||||
editor = self.os.Getenv("VISUAL")
|
||||
}
|
||||
if editor == "" {
|
||||
editor = self.os.Getenv("EDITOR")
|
||||
}
|
||||
|
||||
if editor != "" {
|
||||
// At this point, it might be more than just the name of the editor;
|
||||
// e.g. it might be "code -w" or "vim -u myvim.rc". So assume that
|
||||
// everything up to the first space is the editor name.
|
||||
editor = strings.Split(editor, " ")[0]
|
||||
}
|
||||
|
||||
return editor
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEditFileCmdStr(t *testing.T) {
|
||||
func TestEditFileCmdStrLegacy(t *testing.T) {
|
||||
type scenario struct {
|
||||
filename string
|
||||
configEditCommand string
|
||||
@ -172,7 +172,206 @@ func TestEditFileCmdStr(t *testing.T) {
|
||||
getenv: s.getenv,
|
||||
})
|
||||
|
||||
s.test(instance.GetEditCmdStr(s.filename, 1))
|
||||
s.test(instance.GetEditCmdStrLegacy(s.filename, 1))
|
||||
s.runner.CheckForMissingCalls()
|
||||
}
|
||||
}
|
||||
|
||||
func TestEditFileCmd(t *testing.T) {
|
||||
type scenario struct {
|
||||
filename string
|
||||
osConfig config.OSConfig
|
||||
expectedCmdStr string
|
||||
expectedEditInTerminal bool
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
filename: "test",
|
||||
osConfig: config.OSConfig{},
|
||||
expectedCmdStr: `vim -- "test"`,
|
||||
expectedEditInTerminal: true,
|
||||
},
|
||||
{
|
||||
filename: "test",
|
||||
osConfig: config.OSConfig{
|
||||
Edit: "nano {{filename}}",
|
||||
},
|
||||
expectedCmdStr: `nano "test"`,
|
||||
expectedEditInTerminal: true,
|
||||
},
|
||||
{
|
||||
filename: "file/with space",
|
||||
osConfig: config.OSConfig{
|
||||
EditPreset: "sublime",
|
||||
},
|
||||
expectedCmdStr: `subl -- "file/with space"`,
|
||||
expectedEditInTerminal: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
userConfig := config.GetDefaultConfig()
|
||||
userConfig.OS = s.osConfig
|
||||
|
||||
instance := buildFileCommands(commonDeps{
|
||||
userConfig: userConfig,
|
||||
})
|
||||
|
||||
cmdStr, editInTerminal := instance.GetEditCmdStr(s.filename)
|
||||
assert.Equal(t, s.expectedCmdStr, cmdStr)
|
||||
assert.Equal(t, s.expectedEditInTerminal, editInTerminal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEditFileAtLineCmd(t *testing.T) {
|
||||
type scenario struct {
|
||||
filename string
|
||||
lineNumber int
|
||||
osConfig config.OSConfig
|
||||
expectedCmdStr string
|
||||
expectedEditInTerminal bool
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
filename: "test",
|
||||
lineNumber: 42,
|
||||
osConfig: config.OSConfig{},
|
||||
expectedCmdStr: `vim +42 -- "test"`,
|
||||
expectedEditInTerminal: true,
|
||||
},
|
||||
{
|
||||
filename: "test",
|
||||
lineNumber: 35,
|
||||
osConfig: config.OSConfig{
|
||||
EditAtLine: "nano +{{line}} {{filename}}",
|
||||
},
|
||||
expectedCmdStr: `nano +35 "test"`,
|
||||
expectedEditInTerminal: true,
|
||||
},
|
||||
{
|
||||
filename: "file/with space",
|
||||
lineNumber: 12,
|
||||
osConfig: config.OSConfig{
|
||||
EditPreset: "sublime",
|
||||
},
|
||||
expectedCmdStr: `subl -- "file/with space":12`,
|
||||
expectedEditInTerminal: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
userConfig := config.GetDefaultConfig()
|
||||
userConfig.OS = s.osConfig
|
||||
|
||||
instance := buildFileCommands(commonDeps{
|
||||
userConfig: userConfig,
|
||||
})
|
||||
|
||||
cmdStr, editInTerminal := instance.GetEditAtLineCmdStr(s.filename, s.lineNumber)
|
||||
assert.Equal(t, s.expectedCmdStr, cmdStr)
|
||||
assert.Equal(t, s.expectedEditInTerminal, editInTerminal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEditFileAtLineAndWaitCmd(t *testing.T) {
|
||||
type scenario struct {
|
||||
filename string
|
||||
lineNumber int
|
||||
osConfig config.OSConfig
|
||||
expectedCmdStr string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
filename: "test",
|
||||
lineNumber: 42,
|
||||
osConfig: config.OSConfig{},
|
||||
expectedCmdStr: `vim +42 -- "test"`,
|
||||
},
|
||||
{
|
||||
filename: "file/with space",
|
||||
lineNumber: 12,
|
||||
osConfig: config.OSConfig{
|
||||
EditPreset: "sublime",
|
||||
},
|
||||
expectedCmdStr: `subl --wait -- "file/with space":12`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
userConfig := config.GetDefaultConfig()
|
||||
userConfig.OS = s.osConfig
|
||||
|
||||
instance := buildFileCommands(commonDeps{
|
||||
userConfig: userConfig,
|
||||
})
|
||||
|
||||
cmdStr := instance.GetEditAtLineAndWaitCmdStr(s.filename, s.lineNumber)
|
||||
assert.Equal(t, s.expectedCmdStr, cmdStr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuessDefaultEditor(t *testing.T) {
|
||||
type scenario struct {
|
||||
gitConfigMockResponses map[string]string
|
||||
getenv func(string) string
|
||||
expectedResult string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
gitConfigMockResponses: nil,
|
||||
getenv: func(env string) string {
|
||||
return ""
|
||||
},
|
||||
expectedResult: "",
|
||||
},
|
||||
{
|
||||
gitConfigMockResponses: map[string]string{"core.editor": "nano"},
|
||||
getenv: func(env string) string {
|
||||
return ""
|
||||
},
|
||||
expectedResult: "nano",
|
||||
},
|
||||
{
|
||||
gitConfigMockResponses: map[string]string{"core.editor": "code -w"},
|
||||
getenv: func(env string) string {
|
||||
return ""
|
||||
},
|
||||
expectedResult: "code",
|
||||
},
|
||||
{
|
||||
gitConfigMockResponses: nil,
|
||||
getenv: func(env string) string {
|
||||
if env == "VISUAL" {
|
||||
return "emacs"
|
||||
}
|
||||
|
||||
return ""
|
||||
},
|
||||
expectedResult: "emacs",
|
||||
},
|
||||
{
|
||||
gitConfigMockResponses: nil,
|
||||
getenv: func(env string) string {
|
||||
if env == "EDITOR" {
|
||||
return "bbedit -w"
|
||||
}
|
||||
|
||||
return ""
|
||||
},
|
||||
expectedResult: "bbedit",
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
instance := buildFileCommands(commonDeps{
|
||||
gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses),
|
||||
getenv: s.getenv,
|
||||
})
|
||||
|
||||
assert.Equal(t, s.expectedResult, instance.guessDefaultEditor())
|
||||
}
|
||||
}
|
||||
|
@ -78,21 +78,30 @@ func FileType(path string) string {
|
||||
}
|
||||
|
||||
func (c *OSCommand) OpenFile(filename string) error {
|
||||
return c.OpenFileAtLine(filename, 1)
|
||||
}
|
||||
|
||||
func (c *OSCommand) OpenFileAtLine(filename string, lineNumber int) error {
|
||||
commandTemplate := c.UserConfig.OS.OpenCommand
|
||||
commandTemplate := c.UserConfig.OS.Open
|
||||
if commandTemplate == "" {
|
||||
// Legacy support
|
||||
commandTemplate = c.UserConfig.OS.OpenCommand
|
||||
}
|
||||
if commandTemplate == "" {
|
||||
commandTemplate = config.GetPlatformDefaultConfig().Open
|
||||
}
|
||||
templateValues := map[string]string{
|
||||
"filename": c.Quote(filename),
|
||||
"line": fmt.Sprintf("%d", lineNumber),
|
||||
}
|
||||
command := utils.ResolvePlaceholderString(commandTemplate, templateValues)
|
||||
return c.Cmd.NewShell(command).Run()
|
||||
}
|
||||
|
||||
func (c *OSCommand) OpenLink(link string) error {
|
||||
commandTemplate := c.UserConfig.OS.OpenLinkCommand
|
||||
commandTemplate := c.UserConfig.OS.OpenLink
|
||||
if commandTemplate == "" {
|
||||
// Legacy support
|
||||
commandTemplate = c.UserConfig.OS.OpenLinkCommand
|
||||
}
|
||||
if commandTemplate == "" {
|
||||
commandTemplate = config.GetPlatformDefaultConfig().OpenLink
|
||||
}
|
||||
templateValues := map[string]string{
|
||||
"link": c.Quote(link),
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ func TestOSCommandOpenFileDarwin(t *testing.T) {
|
||||
for _, s := range scenarios {
|
||||
oSCmd := NewDummyOSCommandWithRunner(s.runner)
|
||||
oSCmd.Platform.OS = "darwin"
|
||||
oSCmd.UserConfig.OS.OpenCommand = "open {{filename}}"
|
||||
oSCmd.UserConfig.OS.Open = "open {{filename}}"
|
||||
|
||||
s.test(oSCmd.OpenFile(s.filename))
|
||||
}
|
||||
@ -135,7 +135,7 @@ func TestOSCommandOpenFileLinux(t *testing.T) {
|
||||
for _, s := range scenarios {
|
||||
oSCmd := NewDummyOSCommandWithRunner(s.runner)
|
||||
oSCmd.Platform.OS = "linux"
|
||||
oSCmd.UserConfig.OS.OpenCommand = `xdg-open {{filename}} > /dev/null`
|
||||
oSCmd.UserConfig.OS.Open = `xdg-open {{filename}} > /dev/null`
|
||||
|
||||
s.test(oSCmd.OpenFile(s.filename))
|
||||
}
|
@ -6,6 +6,7 @@ package oscommands
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cli/safeexec"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -19,11 +20,13 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
|
||||
test func(error)
|
||||
}
|
||||
|
||||
fullCmdPath, _ := safeexec.LookPath("cmd")
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
filename: "test",
|
||||
runner: NewFakeRunner(t).
|
||||
ExpectArgs([]string{"cmd", "/c", "start", "", "test"}, "", errors.New("error")),
|
||||
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "test"}, "", errors.New("error")),
|
||||
test: func(err error) {
|
||||
assert.Error(t, err)
|
||||
},
|
||||
@ -31,7 +34,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
|
||||
{
|
||||
filename: "test",
|
||||
runner: NewFakeRunner(t).
|
||||
ExpectArgs([]string{"cmd", "/c", "start", "", "test"}, "", nil),
|
||||
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "test"}, "", nil),
|
||||
test: func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
@ -39,7 +42,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
|
||||
{
|
||||
filename: "filename with spaces",
|
||||
runner: NewFakeRunner(t).
|
||||
ExpectArgs([]string{"cmd", "/c", "start", "", "filename with spaces"}, "", nil),
|
||||
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "filename with spaces"}, "", nil),
|
||||
test: func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
@ -47,7 +50,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
|
||||
{
|
||||
filename: "let's_test_with_single_quote",
|
||||
runner: NewFakeRunner(t).
|
||||
ExpectArgs([]string{"cmd", "/c", "start", "", "let's_test_with_single_quote"}, "", nil),
|
||||
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "let's_test_with_single_quote"}, "", nil),
|
||||
test: func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
@ -55,7 +58,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
|
||||
{
|
||||
filename: "$USER.txt",
|
||||
runner: NewFakeRunner(t).
|
||||
ExpectArgs([]string{"cmd", "/c", "start", "", "$USER.txt"}, "", nil),
|
||||
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "$USER.txt"}, "", nil),
|
||||
test: func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
@ -6,9 +6,7 @@ package config
|
||||
// GetPlatformDefaultConfig gets the defaults for the platform
|
||||
func GetPlatformDefaultConfig() OSConfig {
|
||||
return OSConfig{
|
||||
EditCommand: ``,
|
||||
EditCommandTemplate: "",
|
||||
OpenCommand: "open -- {{filename}}",
|
||||
OpenLinkCommand: "open {{link}}",
|
||||
Open: "open -- {{filename}}",
|
||||
OpenLink: "open {{link}}",
|
||||
}
|
||||
}
|
||||
|
@ -29,17 +29,13 @@ func isContainer() bool {
|
||||
func GetPlatformDefaultConfig() OSConfig {
|
||||
if isWSL() && !isContainer() {
|
||||
return OSConfig{
|
||||
EditCommand: ``,
|
||||
EditCommandTemplate: "",
|
||||
OpenCommand: `powershell.exe start explorer.exe {{filename}} >/dev/null`,
|
||||
OpenLinkCommand: `powershell.exe start {{link}} >/dev/null`,
|
||||
Open: `powershell.exe start explorer.exe {{filename}} >/dev/null`,
|
||||
OpenLink: `powershell.exe start {{link}} >/dev/null`,
|
||||
}
|
||||
}
|
||||
|
||||
return OSConfig{
|
||||
EditCommand: ``,
|
||||
EditCommandTemplate: "",
|
||||
OpenCommand: `xdg-open {{filename}} >/dev/null`,
|
||||
OpenLinkCommand: `xdg-open {{link}} >/dev/null`,
|
||||
Open: `xdg-open {{filename}} >/dev/null`,
|
||||
OpenLink: `xdg-open {{link}} >/dev/null`,
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,7 @@ package config
|
||||
// GetPlatformDefaultConfig gets the defaults for the platform
|
||||
func GetPlatformDefaultConfig() OSConfig {
|
||||
return OSConfig{
|
||||
EditCommand: ``,
|
||||
EditCommandTemplate: "",
|
||||
OpenCommand: `start "" {{filename}}`,
|
||||
OpenLinkCommand: `start "" {{link}}`,
|
||||
Open: `start "" {{filename}}`,
|
||||
OpenLink: `start "" {{link}}`,
|
||||
}
|
||||
}
|
||||
|
109
pkg/config/editor_presets.go
Normal file
109
pkg/config/editor_presets.go
Normal file
@ -0,0 +1,109 @@
|
||||
package config
|
||||
|
||||
func GetEditTemplate(osConfig *OSConfig, guessDefaultEditor func() string) (string, bool) {
|
||||
preset := getPreset(osConfig, guessDefaultEditor)
|
||||
template := osConfig.Edit
|
||||
if template == "" {
|
||||
template = preset.editTemplate
|
||||
}
|
||||
|
||||
return template, getEditInTerminal(osConfig, preset)
|
||||
}
|
||||
|
||||
func GetEditAtLineTemplate(osConfig *OSConfig, guessDefaultEditor func() string) (string, bool) {
|
||||
preset := getPreset(osConfig, guessDefaultEditor)
|
||||
template := osConfig.EditAtLine
|
||||
if template == "" {
|
||||
template = preset.editAtLineTemplate
|
||||
}
|
||||
return template, getEditInTerminal(osConfig, preset)
|
||||
}
|
||||
|
||||
func GetEditAtLineAndWaitTemplate(osConfig *OSConfig, guessDefaultEditor func() string) string {
|
||||
preset := getPreset(osConfig, guessDefaultEditor)
|
||||
template := osConfig.EditAtLineAndWait
|
||||
if template == "" {
|
||||
template = preset.editAtLineAndWaitTemplate
|
||||
}
|
||||
return template
|
||||
}
|
||||
|
||||
type editPreset struct {
|
||||
editTemplate string
|
||||
editAtLineTemplate string
|
||||
editAtLineAndWaitTemplate string
|
||||
editInTerminal bool
|
||||
}
|
||||
|
||||
func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset {
|
||||
presets := map[string]*editPreset{
|
||||
"vi": standardTerminalEditorPreset("vi"),
|
||||
"vim": standardTerminalEditorPreset("vim"),
|
||||
"nvim": standardTerminalEditorPreset("nvim"),
|
||||
"emacs": standardTerminalEditorPreset("emacs"),
|
||||
"nano": standardTerminalEditorPreset("nano"),
|
||||
"vscode": {
|
||||
editTemplate: "code --reuse-window -- {{filename}}",
|
||||
editAtLineTemplate: "code --reuse-window --goto -- {{filename}}:{{line}}",
|
||||
editAtLineAndWaitTemplate: "code --reuse-window --goto --wait -- {{filename}}:{{line}}",
|
||||
editInTerminal: false,
|
||||
},
|
||||
"sublime": {
|
||||
editTemplate: "subl -- {{filename}}",
|
||||
editAtLineTemplate: "subl -- {{filename}}:{{line}}",
|
||||
editAtLineAndWaitTemplate: "subl --wait -- {{filename}}:{{line}}",
|
||||
editInTerminal: false,
|
||||
},
|
||||
"bbedit": {
|
||||
editTemplate: "bbedit -- {{filename}}",
|
||||
editAtLineTemplate: "bbedit +{{line}} -- {{filename}}",
|
||||
editAtLineAndWaitTemplate: "bbedit +{{line}} --wait -- {{filename}}",
|
||||
editInTerminal: false,
|
||||
},
|
||||
"xcode": {
|
||||
editTemplate: "xed -- {{filename}}",
|
||||
editAtLineTemplate: "xed --line {{line}} -- {{filename}}",
|
||||
editAtLineAndWaitTemplate: "xed --line {{line}} --wait -- {{filename}}",
|
||||
editInTerminal: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Some of our presets have a different name than the editor they are using.
|
||||
editorToPreset := map[string]string{
|
||||
"code": "vscode",
|
||||
"subl": "sublime",
|
||||
"xed": "xcode",
|
||||
}
|
||||
|
||||
presetName := osConfig.EditPreset
|
||||
if presetName == "" {
|
||||
defaultEditor := guessDefaultEditor()
|
||||
if presets[defaultEditor] != nil {
|
||||
presetName = defaultEditor
|
||||
} else if p := editorToPreset[defaultEditor]; p != "" {
|
||||
presetName = p
|
||||
}
|
||||
}
|
||||
|
||||
if presetName == "" || presets[presetName] == nil {
|
||||
presetName = "vim"
|
||||
}
|
||||
|
||||
return presets[presetName]
|
||||
}
|
||||
|
||||
func standardTerminalEditorPreset(editor string) *editPreset {
|
||||
return &editPreset{
|
||||
editTemplate: editor + " -- {{filename}}",
|
||||
editAtLineTemplate: editor + " +{{line}} -- {{filename}}",
|
||||
editAtLineAndWaitTemplate: editor + " +{{line}} -- {{filename}}",
|
||||
editInTerminal: true,
|
||||
}
|
||||
}
|
||||
|
||||
func getEditInTerminal(osConfig *OSConfig, preset *editPreset) bool {
|
||||
if osConfig.EditInTerminal != nil {
|
||||
return *osConfig.EditInTerminal
|
||||
}
|
||||
return preset.editInTerminal
|
||||
}
|
126
pkg/config/editor_presets_test.go
Normal file
126
pkg/config/editor_presets_test.go
Normal file
@ -0,0 +1,126 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetEditTemplate(t *testing.T) {
|
||||
trueVal := true
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
osConfig *OSConfig
|
||||
guessDefaultEditor func() string
|
||||
expectedEditTemplate string
|
||||
expectedEditAtLineTemplate string
|
||||
expectedEditAtLineAndWaitTemplate string
|
||||
expectedEditInTerminal bool
|
||||
}{
|
||||
{
|
||||
"Default template is vim",
|
||||
&OSConfig{},
|
||||
func() string { return "" },
|
||||
"vim -- {{filename}}",
|
||||
"vim +{{line}} -- {{filename}}",
|
||||
"vim +{{line}} -- {{filename}}",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Setting a preset",
|
||||
&OSConfig{
|
||||
EditPreset: "vscode",
|
||||
},
|
||||
func() string { return "" },
|
||||
"code --reuse-window -- {{filename}}",
|
||||
"code --reuse-window --goto -- {{filename}}:{{line}}",
|
||||
"code --reuse-window --goto --wait -- {{filename}}:{{line}}",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Setting a preset wins over guessed editor",
|
||||
&OSConfig{
|
||||
EditPreset: "vscode",
|
||||
},
|
||||
func() string { return "nano" },
|
||||
"code --reuse-window -- {{filename}}",
|
||||
"code --reuse-window --goto -- {{filename}}:{{line}}",
|
||||
"code --reuse-window --goto --wait -- {{filename}}:{{line}}",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Overriding a preset with explicit config (edit)",
|
||||
&OSConfig{
|
||||
EditPreset: "vscode",
|
||||
Edit: "myeditor {{filename}}",
|
||||
EditInTerminal: &trueVal,
|
||||
},
|
||||
func() string { return "" },
|
||||
"myeditor {{filename}}",
|
||||
"code --reuse-window --goto -- {{filename}}:{{line}}",
|
||||
"code --reuse-window --goto --wait -- {{filename}}:{{line}}",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Overriding a preset with explicit config (edit at line)",
|
||||
&OSConfig{
|
||||
EditPreset: "vscode",
|
||||
EditAtLine: "myeditor --line={{line}} {{filename}}",
|
||||
EditInTerminal: &trueVal,
|
||||
},
|
||||
func() string { return "" },
|
||||
"code --reuse-window -- {{filename}}",
|
||||
"myeditor --line={{line}} {{filename}}",
|
||||
"code --reuse-window --goto --wait -- {{filename}}:{{line}}",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Overriding a preset with explicit config (edit at line and wait)",
|
||||
&OSConfig{
|
||||
EditPreset: "vscode",
|
||||
EditAtLineAndWait: "myeditor --line={{line}} -w {{filename}}",
|
||||
EditInTerminal: &trueVal,
|
||||
},
|
||||
func() string { return "" },
|
||||
"code --reuse-window -- {{filename}}",
|
||||
"code --reuse-window --goto -- {{filename}}:{{line}}",
|
||||
"myeditor --line={{line}} -w {{filename}}",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Unknown preset name",
|
||||
&OSConfig{
|
||||
EditPreset: "thisPresetDoesNotExist",
|
||||
},
|
||||
func() string { return "" },
|
||||
"vim -- {{filename}}",
|
||||
"vim +{{line}} -- {{filename}}",
|
||||
"vim +{{line}} -- {{filename}}",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Guessing a preset from guessed editor",
|
||||
&OSConfig{},
|
||||
func() string { return "emacs" },
|
||||
"emacs -- {{filename}}",
|
||||
"emacs +{{line}} -- {{filename}}",
|
||||
"emacs +{{line}} -- {{filename}}",
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
template, editInTerminal := GetEditTemplate(s.osConfig, s.guessDefaultEditor)
|
||||
assert.Equal(t, s.expectedEditTemplate, template)
|
||||
assert.Equal(t, s.expectedEditInTerminal, editInTerminal)
|
||||
|
||||
template, editInTerminal = GetEditAtLineTemplate(s.osConfig, s.guessDefaultEditor)
|
||||
assert.Equal(t, s.expectedEditAtLineTemplate, template)
|
||||
assert.Equal(t, s.expectedEditInTerminal, editInTerminal)
|
||||
|
||||
template = GetEditAtLineAndWaitTemplate(s.osConfig, s.guessDefaultEditor)
|
||||
assert.Equal(t, s.expectedEditAtLineAndWaitTemplate, template)
|
||||
})
|
||||
}
|
||||
}
|
@ -291,16 +291,54 @@ type KeybindingSubmodulesConfig struct {
|
||||
|
||||
// OSConfig contains config on the level of the os
|
||||
type OSConfig struct {
|
||||
// EditCommand is the command for editing a file
|
||||
// Command for editing a file. Should contain "{{filename}}".
|
||||
Edit string `yaml:"edit,omitempty"`
|
||||
|
||||
// Command for editing a file at a given line number. Should contain
|
||||
// "{{filename}}", and may optionally contain "{{line}}".
|
||||
EditAtLine string `yaml:"editAtLine,omitempty"`
|
||||
|
||||
// Same as EditAtLine, except that the command needs to wait until the
|
||||
// window is closed.
|
||||
EditAtLineAndWait string `yaml:"editAtLineAndWait,omitempty"`
|
||||
|
||||
// Whether the given edit commands use the terminal. Used to decide whether
|
||||
// lazygit needs to suspend to the background before calling the editor.
|
||||
// Pointer to bool so that we can distinguish unset (nil) from false.
|
||||
EditInTerminal *bool `yaml:"editInTerminal,omitempty"`
|
||||
|
||||
// A built-in preset that sets all of the above settings. Supported presets
|
||||
// are defined in the getPreset function in editor_presets.go.
|
||||
EditPreset string `yaml:"editPreset,omitempty"`
|
||||
|
||||
// Command for opening a file, as if the file is double-clicked. Should
|
||||
// contain "{{filename}}", but doesn't support "{{line}}".
|
||||
Open string `yaml:"open,omitempty"`
|
||||
|
||||
// Command for opening a link. Should contain "{{link}}".
|
||||
OpenLink string `yaml:"openLink,omitempty"`
|
||||
|
||||
// --------
|
||||
|
||||
// The following configs are all deprecated and kept for backward
|
||||
// compatibility. They will be removed in the future.
|
||||
|
||||
// EditCommand is the command for editing a file.
|
||||
// Deprecated: use Edit instead. Note that semantics are different:
|
||||
// EditCommand is just the command itself, whereas Edit contains a
|
||||
// "{{filename}}" variable.
|
||||
EditCommand string `yaml:"editCommand,omitempty"`
|
||||
|
||||
// EditCommandTemplate is the command template for editing a file
|
||||
// Deprecated: use EditAtLine instead.
|
||||
EditCommandTemplate string `yaml:"editCommandTemplate,omitempty"`
|
||||
|
||||
// OpenCommand is the command for opening a file
|
||||
// Deprecated: use Open instead.
|
||||
OpenCommand string `yaml:"openCommand,omitempty"`
|
||||
|
||||
// OpenCommand is the command for opening a link
|
||||
// OpenLinkCommand is the command for opening a link
|
||||
// Deprecated: use OpenLink instead.
|
||||
OpenLinkCommand string `yaml:"openLinkCommand,omitempty"`
|
||||
}
|
||||
|
||||
@ -570,7 +608,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
BulkMenu: "b",
|
||||
},
|
||||
},
|
||||
OS: GetPlatformDefaultConfig(),
|
||||
OS: OSConfig{},
|
||||
DisableStartupPopups: false,
|
||||
CustomCommands: []CustomCommand(nil),
|
||||
Services: map[string]string(nil),
|
||||
|
@ -10,7 +10,6 @@ type IFilesHelper interface {
|
||||
EditFile(filename string) error
|
||||
EditFileAtLine(filename string, lineNumber int) error
|
||||
OpenFile(filename string) error
|
||||
OpenFileAtLine(filename string, lineNumber int) error
|
||||
}
|
||||
|
||||
type FilesHelper struct {
|
||||
@ -34,28 +33,37 @@ func NewFilesHelper(
|
||||
var _ IFilesHelper = &FilesHelper{}
|
||||
|
||||
func (self *FilesHelper) EditFile(filename string) error {
|
||||
return self.EditFileAtLine(filename, 1)
|
||||
cmdStr, editInTerminal := self.git.File.GetEditCmdStr(filename)
|
||||
return self.callEditor(cmdStr, editInTerminal)
|
||||
}
|
||||
|
||||
func (self *FilesHelper) EditFileAtLine(filename string, lineNumber int) error {
|
||||
cmdStr, err := self.git.File.GetEditCmdStr(filename, lineNumber)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
cmdStr, editInTerminal := self.git.File.GetEditAtLineCmdStr(filename, lineNumber)
|
||||
return self.callEditor(cmdStr, editInTerminal)
|
||||
}
|
||||
|
||||
func (self *FilesHelper) EditFileAtLineAndWait(filename string, lineNumber int) error {
|
||||
cmdStr := self.git.File.GetEditAtLineAndWaitCmdStr(filename, lineNumber)
|
||||
|
||||
// Always suspend, regardless of the value of the editInTerminal config,
|
||||
// since we want to prevent interacting with the UI until the editor
|
||||
// returns, even if the editor doesn't use the terminal
|
||||
return self.callEditor(cmdStr, true)
|
||||
}
|
||||
|
||||
func (self *FilesHelper) callEditor(cmdStr string, editInTerminal bool) error {
|
||||
if editInTerminal {
|
||||
return self.c.RunSubprocessAndRefresh(
|
||||
self.os.Cmd.NewShell(cmdStr),
|
||||
)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.EditFile)
|
||||
return self.c.RunSubprocessAndRefresh(
|
||||
self.os.Cmd.NewShell(cmdStr),
|
||||
)
|
||||
return self.os.Cmd.NewShell(cmdStr).Run()
|
||||
}
|
||||
|
||||
func (self *FilesHelper) OpenFile(filename string) error {
|
||||
return self.OpenFileAtLine(filename, 1)
|
||||
}
|
||||
|
||||
func (self *FilesHelper) OpenFileAtLine(filename string, lineNumber int) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.OpenFile)
|
||||
if err := self.os.OpenFileAtLine(filename, lineNumber); err != nil {
|
||||
if err := self.os.OpenFile(filename); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return nil
|
||||
|
@ -166,8 +166,7 @@ func (self *MergeConflictsController) HandleEditFile() error {
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) HandleOpenFile() error {
|
||||
lineNumber := self.context().GetState().GetSelectedLine()
|
||||
return self.helpers.Files.OpenFileAtLine(self.context().GetState().GetPath(), lineNumber)
|
||||
return self.helpers.Files.OpenFile(self.context().GetState().GetPath())
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) HandleScrollLeft() error {
|
||||
|
@ -69,8 +69,7 @@ func (self *PatchBuildingController) OpenFile() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
lineNumber := self.context().GetState().CurrentLineNumber()
|
||||
return self.helpers.Files.OpenFileAtLine(path, lineNumber)
|
||||
return self.helpers.Files.OpenFile(path)
|
||||
}
|
||||
|
||||
func (self *PatchBuildingController) EditFile() error {
|
||||
|
@ -109,8 +109,7 @@ func (self *StagingController) OpenFile() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
lineNumber := self.context.GetState().CurrentLineNumber()
|
||||
return self.helpers.Files.OpenFileAtLine(path, lineNumber)
|
||||
return self.helpers.Files.OpenFile(path)
|
||||
}
|
||||
|
||||
func (self *StagingController) EditFile() error {
|
||||
@ -247,7 +246,7 @@ func (self *StagingController) editHunk() error {
|
||||
|
||||
lineOffset := 3
|
||||
lineIdxInHunk := state.GetSelectedLineIdx() - hunkStartIdx
|
||||
if err := self.helpers.Files.EditFileAtLine(patchFilepath, lineIdxInHunk+lineOffset); err != nil {
|
||||
if err := self.helpers.Files.EditFileAtLineAndWait(patchFilepath, lineIdxInHunk+lineOffset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -495,6 +495,8 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer gui.checkForDeprecatedEditConfigs()
|
||||
|
||||
gui.g = g
|
||||
defer gui.g.Close()
|
||||
|
||||
@ -583,6 +585,37 @@ func (gui *Gui) RunAndHandleError(startArgs appTypes.StartArgs) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) checkForDeprecatedEditConfigs() {
|
||||
osConfig := &gui.UserConfig.OS
|
||||
deprecatedConfigs := []struct {
|
||||
config string
|
||||
oldName string
|
||||
newName string
|
||||
}{
|
||||
{osConfig.EditCommand, "EditCommand", "Edit"},
|
||||
{osConfig.EditCommandTemplate, "EditCommandTemplate", "Edit,EditAtLine"},
|
||||
{osConfig.OpenCommand, "OpenCommand", "Open"},
|
||||
{osConfig.OpenLinkCommand, "OpenLinkCommand", "OpenLink"},
|
||||
}
|
||||
deprecatedConfigStrings := []string{}
|
||||
|
||||
for _, dc := range deprecatedConfigs {
|
||||
if dc.config != "" {
|
||||
deprecatedConfigStrings = append(deprecatedConfigStrings, fmt.Sprintf(" OS.%s -> OS.%s", dc.oldName, dc.newName))
|
||||
}
|
||||
}
|
||||
if len(deprecatedConfigStrings) != 0 {
|
||||
warningMessage := utils.ResolvePlaceholderString(
|
||||
gui.c.Tr.DeprecatedEditConfigWarning,
|
||||
map[string]string{
|
||||
"configs": strings.Join(deprecatedConfigStrings, "\n"),
|
||||
},
|
||||
)
|
||||
|
||||
os.Stdout.Write([]byte(warningMessage))
|
||||
}
|
||||
}
|
||||
|
||||
// returns whether command exited without error or not
|
||||
func (gui *Gui) runSubprocessWithSuspenseAndRefresh(subprocess oscommands.ICmdObj) error {
|
||||
_, err := gui.runSubprocessWithSuspense(subprocess)
|
||||
|
@ -155,6 +155,7 @@ type TranslationSet struct {
|
||||
MergeToolTitle string
|
||||
MergeToolPrompt string
|
||||
IntroPopupMessage string
|
||||
DeprecatedEditConfigWarning string
|
||||
GitconfigParseErr string
|
||||
LcEditFile string
|
||||
LcOpenFile string
|
||||
@ -659,6 +660,21 @@ Thanks for using lazygit! Seriously you rock. Three things to share with you:
|
||||
Or even just star the repo to share the love!
|
||||
`
|
||||
|
||||
const englishDeprecatedEditConfigWarning = `
|
||||
### Deprecated config warning ###
|
||||
|
||||
The following config settings are deprecated and will be removed in a future
|
||||
version:
|
||||
{{configs}}
|
||||
|
||||
Please refer to
|
||||
|
||||
https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#configuring-file-editing
|
||||
|
||||
for up-to-date information how to configure your editor.
|
||||
|
||||
`
|
||||
|
||||
// exporting this so we can use it in tests
|
||||
func EnglishTranslationSet() TranslationSet {
|
||||
return TranslationSet{
|
||||
@ -805,6 +821,7 @@ func EnglishTranslationSet() TranslationSet {
|
||||
MergeToolTitle: "Merge tool",
|
||||
MergeToolPrompt: "Are you sure you want to open `git mergetool`?",
|
||||
IntroPopupMessage: englishIntroPopupMessage,
|
||||
DeprecatedEditConfigWarning: englishDeprecatedEditConfigWarning,
|
||||
GitconfigParseErr: `Gogit failed to parse your gitconfig file due to the presence of unquoted '\' characters. Removing these should fix the issue.`,
|
||||
LcEditFile: `edit file`,
|
||||
LcOpenFile: `open file`,
|
||||
|
Loading…
Reference in New Issue
Block a user