diff --git a/docs-master/Config.md b/docs-master/Config.md index 9f79218217f..8736a7ee798 100644 --- a/docs-master/Config.md +++ b/docs-master/Config.md @@ -552,6 +552,9 @@ os: # "{{filename}}", but doesn't support "{{line}}". open: "" + # Whether lazygit suspends until an open process returns + openInTerminal: false + # Command for opening a link. Should contain "{{link}}". openLink: "" diff --git a/docs/Config.md b/docs/Config.md index 9931fda61fe..bfcfc37eef2 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -551,6 +551,9 @@ os: # "{{filename}}", but doesn't support "{{line}}". open: "" + # Whether lazygit suspends until an open process returns + openInTerminal: false + # Command for opening a link. Should contain "{{link}}". openLink: "" diff --git a/pkg/commands/git_commands/file.go b/pkg/commands/git_commands/file.go index 1b5f5b2ddd9..5f99f688ebe 100644 --- a/pkg/commands/git_commands/file.go +++ b/pkg/commands/git_commands/file.go @@ -65,6 +65,17 @@ func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber return cmdStr } +func (self *FileCommands) GetOpenCmdStr(filename string) (string, bool) { + template, suspend := config.GetOpenTemplate(&self.UserConfig().OS, config.GetPlatformDefaultConfig().Open) + + templateValues := map[string]string{ + "filename": self.cmd.Quote(filename), + } + + cmdStr := utils.ResolvePlaceholderString(template, templateValues) + return cmdStr, suspend +} + func (self *FileCommands) GetOpenDirInEditorCmdStr(path string) (string, bool) { template, suspend := config.GetOpenDirInEditorTemplate(self.os.Platform.Shell, &self.UserConfig().OS, self.guessDefaultEditor) diff --git a/pkg/commands/git_commands/file_test.go b/pkg/commands/git_commands/file_test.go index 5dd3d133c4f..483e8ce50c1 100644 --- a/pkg/commands/git_commands/file_test.go +++ b/pkg/commands/git_commands/file_test.go @@ -5,6 +5,7 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands/git_config" "github.com/jesseduffield/lazygit/pkg/config" + "github.com/samber/lo" "github.com/stretchr/testify/assert" ) @@ -152,6 +153,59 @@ func TestEditFileAtLineAndWaitCmd(t *testing.T) { } } +func TestOpenFileCmd(t *testing.T) { + type scenario struct { + name string + filename string + osConfig config.OSConfig + expectedCmdStr string + suspend bool + } + + scenarios := []scenario{ + { + name: "uses platform default open command", + filename: "test", + osConfig: config.OSConfig{}, + expectedCmdStr: `open -- "test"`, + suspend: false, + }, + { + name: "uses explicit open command", + filename: "file/with space", + osConfig: config.OSConfig{ + Open: "custom-open {{filename}}", + }, + expectedCmdStr: `custom-open "file/with space"`, + suspend: false, + }, + { + name: "suspends when open is configured to run in terminal", + filename: "test", + osConfig: config.OSConfig{ + SuspendOnOpen: lo.ToPtr(true), + }, + expectedCmdStr: `open -- "test"`, + suspend: true, + }, + } + + for _, s := range scenarios { + t.Run(s.name, func(t *testing.T) { + userConfig := config.GetDefaultConfig() + userConfig.OS = s.osConfig + + instance := buildFileCommands(commonDeps{ + userConfig: userConfig, + }) + + cmdStr, suspend := instance.GetOpenCmdStr(s.filename) + assert.Equal(t, s.expectedCmdStr, cmdStr) + assert.Equal(t, s.suspend, suspend) + }) + } +} + func TestGuessDefaultEditor(t *testing.T) { type scenario struct { gitConfigMockResponses map[string]string diff --git a/pkg/config/editor_presets.go b/pkg/config/editor_presets.go index 5fcde97c54a..8ead00e1490 100644 --- a/pkg/config/editor_presets.go +++ b/pkg/config/editor_presets.go @@ -33,6 +33,15 @@ func GetEditAtLineAndWaitTemplate(shell string, osConfig *OSConfig, guessDefault return template } +func GetOpenTemplate(osConfig *OSConfig, defaultTemplate string) (string, bool) { + template := osConfig.Open + if template == "" { + template = defaultTemplate + } + + return template, getOpenInTerminal(osConfig) +} + func GetOpenDirInEditorTemplate(shell string, osConfig *OSConfig, guessDefaultEditor func() string) (string, bool) { preset := getPreset(shell, osConfig, guessDefaultEditor) template := osConfig.OpenDirInEditor @@ -196,3 +205,11 @@ func getEditInTerminal(osConfig *OSConfig, preset *editPreset) bool { } return preset.suspend() } + +func getOpenInTerminal(osConfig *OSConfig) bool { + if osConfig.SuspendOnOpen != nil { + return *osConfig.SuspendOnOpen + } + + return false +} diff --git a/pkg/config/editor_presets_test.go b/pkg/config/editor_presets_test.go index d7c56965a46..561ce3ad60a 100644 --- a/pkg/config/editor_presets_test.go +++ b/pkg/config/editor_presets_test.go @@ -124,3 +124,49 @@ func TestGetEditTemplate(t *testing.T) { }) } } + +func TestGetOpenTemplate(t *testing.T) { + trueVal := true + + scenarios := []struct { + name string + osConfig *OSConfig + defaultTemplate string + expectedTemplate string + expectedSuspend bool + }{ + { + name: "uses platform default open command", + osConfig: &OSConfig{}, + defaultTemplate: "open -- {{filename}}", + expectedTemplate: "open -- {{filename}}", + expectedSuspend: false, + }, + { + name: "uses explicit open command", + osConfig: &OSConfig{ + Open: "myopen {{filename}}", + }, + defaultTemplate: "open -- {{filename}}", + expectedTemplate: "myopen {{filename}}", + expectedSuspend: false, + }, + { + name: "uses explicit open suspend setting", + osConfig: &OSConfig{ + SuspendOnOpen: &trueVal, + }, + defaultTemplate: "open -- {{filename}}", + expectedTemplate: "open -- {{filename}}", + expectedSuspend: true, + }, + } + + for _, s := range scenarios { + t.Run(s.name, func(t *testing.T) { + template, suspend := GetOpenTemplate(s.osConfig, s.defaultTemplate) + assert.Equal(t, s.expectedTemplate, template) + assert.Equal(t, s.expectedSuspend, suspend) + }) + } +} diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index cac87ec9157..0f5a1faf4fb 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -663,6 +663,11 @@ type OSConfig struct { // 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"` + // Whether lazygit suspends until an open process returns + // [dev] Pointer to bool so that we can distinguish unset (nil) from false. + // [dev] We're naming this `openInTerminal` so it aligns with editInTerminal + SuspendOnOpen *bool `yaml:"openInTerminal,omitempty"` + // Command for opening a link. Should contain "{{link}}". OpenLink string `yaml:"openLink,omitempty"` diff --git a/pkg/gui/controllers/helpers/files_helper.go b/pkg/gui/controllers/helpers/files_helper.go index 81c9b272e18..96d8067d558 100644 --- a/pkg/gui/controllers/helpers/files_helper.go +++ b/pkg/gui/controllers/helpers/files_helper.go @@ -76,8 +76,9 @@ func (self *FilesHelper) OpenFile(filename string) error { return err } self.c.LogAction(self.c.Tr.Actions.OpenFile) - if err := self.c.OS().OpenFile(absPath); err != nil { - return err + cmdStr, suspend := self.c.Git().File.GetOpenCmdStr(absPath) + if !suspend { + return self.c.OS().OpenFile(absPath) } - return nil + return self.callEditor(cmdStr, suspend) } diff --git a/schema-master/config.json b/schema-master/config.json index 2e968ba8f95..6dba83d7e06 100644 --- a/schema-master/config.json +++ b/schema-master/config.json @@ -3465,6 +3465,10 @@ "type": "string", "description": "Command for opening a file, as if the file is double-clicked. Should contain \"{{filename}}\", but doesn't support \"{{line}}\"." }, + "openInTerminal": { + "type": "boolean", + "description": "Whether lazygit suspends until an open process returns" + }, "openLink": { "type": "string", "description": "Command for opening a link. Should contain \"{{link}}\"."