Skip to content

Commit

Permalink
Allow using shell aliases in interactive custom commands (jesseduffie…
Browse files Browse the repository at this point in the history
…ld#3793)

- **PR Description**

When executing an interactive custom command, use the user's shell
rather than "bash", and pass the -i flag. This makes it possible to use
shell aliases or shell functions which are not available in
non-interactive shells.

In previous attempts to solve this, concerns were brought up: [this
one](jesseduffield#2096 (comment))
is addressed by using the interactive shell only for custom commands but
not anything else. [This
one](jesseduffield#2096 (comment))
is a little dubious and unconfirmed, so I'm not very worried about it.

Supersedes jesseduffield#2096 and jesseduffield#3299. Fixes jesseduffield#770, jesseduffield#899, and jesseduffield#1642.
  • Loading branch information
stefanhaller authored Aug 17, 2024
2 parents da94ee7 + 5a30494 commit c72be6c
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 26 deletions.
4 changes: 4 additions & 0 deletions pkg/commands/git_cmd_obj_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ func (self *gitCmdObjBuilder) NewShell(cmdStr string) oscommands.ICmdObj {
return self.innerBuilder.NewShell(cmdStr).AddEnvVars(defaultEnvVar)
}

func (self *gitCmdObjBuilder) NewInteractiveShell(cmdStr string) oscommands.ICmdObj {
return self.innerBuilder.NewInteractiveShell(cmdStr).AddEnvVars(defaultEnvVar)
}

func (self *gitCmdObjBuilder) Quote(str string) string {
return self.innerBuilder.Quote(str)
}
25 changes: 18 additions & 7 deletions pkg/commands/oscommands/cmd_obj_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ type ICmdObjBuilder interface {
New(args []string) ICmdObj
// NewShell takes a string like `git commit` and returns an executable shell command for it e.g. `sh -c 'git commit'`
NewShell(commandStr string) ICmdObj
// Like NewShell, but uses the user's shell rather than "bash", and passes -i to it
NewInteractiveShell(commandStr string) ICmdObj
// Quote wraps a string in quotes with any necessary escaping applied. The reason for bundling this up with the other methods in this interface is that we basically always need to make use of this when creating new command objects.
Quote(str string) string
}
Expand Down Expand Up @@ -43,24 +45,33 @@ func (self *CmdObjBuilder) NewWithEnviron(args []string, env []string) ICmdObj {
}

func (self *CmdObjBuilder) NewShell(commandStr string) ICmdObj {
var quotedCommand string
quotedCommand := self.quotedCommandString(commandStr)
cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s", self.platform.Shell, self.platform.ShellArg, quotedCommand))

return self.New(cmdArgs)
}

func (self *CmdObjBuilder) NewInteractiveShell(commandStr string) ICmdObj {
quotedCommand := self.quotedCommandString(commandStr)
cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s %s", self.platform.InteractiveShell, self.platform.InteractiveShellArg, self.platform.ShellArg, quotedCommand))

return self.New(cmdArgs)
}

func (self *CmdObjBuilder) quotedCommandString(commandStr string) string {
// Windows does not seem to like quotes around the command
if self.platform.OS == "windows" {
quotedCommand = strings.NewReplacer(
return strings.NewReplacer(
"^", "^^",
"&", "^&",
"|", "^|",
"<", "^<",
">", "^>",
"%", "^%",
).Replace(commandStr)
} else {
quotedCommand = self.Quote(commandStr)
}

cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s", self.platform.Shell, self.platform.ShellArg, quotedCommand))

return self.New(cmdArgs)
return self.Quote(commandStr)
}

func (self *CmdObjBuilder) CloneWithNewRunner(decorate func(ICmdObjRunner) ICmdObjRunner) *CmdObjBuilder {
Expand Down
12 changes: 7 additions & 5 deletions pkg/commands/oscommands/dummies.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,13 @@ func NewDummyCmdObjBuilder(runner ICmdObjRunner) *CmdObjBuilder {
}

var dummyPlatform = &Platform{
OS: "darwin",
Shell: "bash",
ShellArg: "-c",
OpenCommand: "open {{filename}}",
OpenLinkCommand: "open {{link}}",
OS: "darwin",
Shell: "bash",
InteractiveShell: "bash",
ShellArg: "-c",
InteractiveShellArg: "-i",
OpenCommand: "open {{filename}}",
OpenLinkCommand: "open {{link}}",
}

func NewDummyOSCommandWithRunner(runner *FakeCmdObjRunner) *OSCommand {
Expand Down
12 changes: 7 additions & 5 deletions pkg/commands/oscommands/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ type OSCommand struct {

// Platform stores the os state
type Platform struct {
OS string
Shell string
ShellArg string
OpenCommand string
OpenLinkCommand string
OS string
Shell string
InteractiveShell string
ShellArg string
InteractiveShellArg string
OpenCommand string
OpenLinkCommand string
}

// NewOSCommand os command runner
Expand Down
21 changes: 16 additions & 5 deletions pkg/commands/oscommands/os_default_platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,26 @@
package oscommands

import (
"os"
"runtime"
)

func GetPlatform() *Platform {
return &Platform{
OS: runtime.GOOS,
Shell: "bash",
ShellArg: "-c",
OpenCommand: "open {{filename}}",
OpenLinkCommand: "open {{link}}",
OS: runtime.GOOS,
Shell: "bash",
InteractiveShell: getUserShell(),
ShellArg: "-c",
InteractiveShellArg: "-i",
OpenCommand: "open {{filename}}",
OpenLinkCommand: "open {{link}}",
}
}

func getUserShell() string {
if shell := os.Getenv("SHELL"); shell != "" {
return shell
}

return "bash"
}
8 changes: 5 additions & 3 deletions pkg/commands/oscommands/os_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package oscommands

func GetPlatform() *Platform {
return &Platform{
OS: "windows",
Shell: "cmd",
ShellArg: "/c",
OS: "windows",
Shell: "cmd",
InteractiveShell: "cmd",
ShellArg: "/c",
InteractiveShellArg: "",
}
}
2 changes: 1 addition & 1 deletion pkg/gui/controllers/custom_command_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (self *CustomCommandAction) Call() error {

self.c.LogAction(self.c.Tr.Actions.CustomCommand)
return self.c.RunSubprocessAndRefresh(
self.c.OS().Cmd.NewShell(command),
self.c.OS().Cmd.NewInteractiveShell(command),
)
},
HandleDeleteSuggestion: func(index int) error {
Expand Down

0 comments on commit c72be6c

Please sign in to comment.