diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index 185278f47..155980507 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -301,6 +301,7 @@ declare global { "term:fontfamily"?: string; "term:mode"?: string; "term:theme"?: string; + "term:localshellpath"?: string; count?: number; }; @@ -417,6 +418,7 @@ declare global { "term:fontsize"?: number; "term:fontfamily"?: string; "term:disablewebgl"?: boolean; + "term:localshellpath"?: string; "editor:minimapenabled"?: boolean; "editor:stickyscrollenabled"?: boolean; "web:*"?: boolean; diff --git a/pkg/blockcontroller/blockcontroller.go b/pkg/blockcontroller/blockcontroller.go index 917734e25..e17d04cac 100644 --- a/pkg/blockcontroller/blockcontroller.go +++ b/pkg/blockcontroller/blockcontroller.go @@ -20,6 +20,7 @@ import ( "github.com/wavetermdev/waveterm/pkg/shellexec" "github.com/wavetermdev/waveterm/pkg/wavebase" "github.com/wavetermdev/waveterm/pkg/waveobj" + "github.com/wavetermdev/waveterm/pkg/wconfig" "github.com/wavetermdev/waveterm/pkg/wps" "github.com/wavetermdev/waveterm/pkg/wshrpc" "github.com/wavetermdev/waveterm/pkg/wshutil" @@ -286,6 +287,7 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj return err } } else { + // local terminal if !blockMeta.GetBool(waveobj.MetaKey_CmdNoWsh, false) { jwtStr, err := wshutil.MakeClientJWTToken(wshrpc.RpcContext{TabId: bc.TabId, BlockId: bc.BlockId}, wavebase.GetDomainSocketName()) if err != nil { @@ -293,6 +295,13 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj } cmdOpts.Env[wshutil.WaveJwtTokenVarName] = jwtStr } + settings := wconfig.GetWatcher().GetFullConfig().Settings + if settings.TermLocalShellPath != "" { + cmdOpts.ShellPath = settings.TermLocalShellPath + } + if blockMeta.GetString(waveobj.MetaKey_TermLocalShellPath, "") != "" { + cmdOpts.ShellPath = blockMeta.GetString(waveobj.MetaKey_TermLocalShellPath, "") + } shellProc, err = shellexec.StartShellProc(rc.TermSize, cmdStr, cmdOpts) if err != nil { return err diff --git a/pkg/shellexec/shellexec.go b/pkg/shellexec/shellexec.go index 63a65ce14..a9ed061d4 100644 --- a/pkg/shellexec/shellexec.go +++ b/pkg/shellexec/shellexec.go @@ -21,6 +21,7 @@ import ( "github.com/wavetermdev/waveterm/pkg/remote" "github.com/wavetermdev/waveterm/pkg/remote/conncontroller" "github.com/wavetermdev/waveterm/pkg/util/shellutil" + "github.com/wavetermdev/waveterm/pkg/util/utilfn" "github.com/wavetermdev/waveterm/pkg/wavebase" "github.com/wavetermdev/waveterm/pkg/waveobj" "github.com/wavetermdev/waveterm/pkg/wshutil" @@ -33,6 +34,7 @@ type CommandOptsType struct { Login bool `json:"login,omitempty"` Cwd string `json:"cwd,omitempty"` Env map[string]string `json:"env,omitempty"` + ShellPath string `json:"shellPath,omitempty"` } type ShellProc struct { @@ -140,15 +142,19 @@ func (pp *PipePty) WriteString(s string) (n int, err error) { func StartRemoteShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType, conn *conncontroller.SSHConn) (*ShellProc, error) { client := conn.GetClient() - shellPath, err := remote.DetectShell(client) - if err != nil { - return nil, err + shellPath := cmdOpts.ShellPath + if shellPath == "" { + remoteShellPath, err := remote.DetectShell(client) + if err != nil { + return nil, err + } + shellPath = remoteShellPath } var shellOpts []string var cmdCombined string log.Printf("detected shell: %s", shellPath) - err = remote.InstallClientRcFiles(client) + err := remote.InstallClientRcFiles(client) if err != nil { log.Printf("error installing rc files: %v", err) return nil, err @@ -163,6 +169,9 @@ func StartRemoteShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts Comm // add --rcfile // cant set -l or -i with --rcfile shellOpts = append(shellOpts, "--rcfile", fmt.Sprintf(`"%s"/.waveterm/%s/.bashrc`, homeDir, shellutil.BashIntegrationDir)) + } else if isFishShell(shellPath) { + carg := fmt.Sprintf(`"set -x PATH \"%s\"/.waveterm/%s $PATH"`, homeDir, shellutil.WaveHomeBinDir) + shellOpts = append(shellOpts, "-C", carg) } else if remote.IsPowershell(shellPath) { // powershell is weird about quoted path executables and requires an ampersand first shellPath = "& " + shellPath @@ -264,17 +273,29 @@ func isBashShell(shellPath string) bool { return strings.Contains(shellBase, "bash") } +func isFishShell(shellPath string) bool { + // get the base path, and then check contains + shellBase := filepath.Base(shellPath) + return strings.Contains(shellBase, "fish") +} + func StartShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType) (*ShellProc, error) { shellutil.InitCustomShellStartupFiles() var ecmd *exec.Cmd var shellOpts []string - - shellPath := shellutil.DetectLocalShellPath() + shellPath := cmdOpts.ShellPath + if shellPath == "" { + shellPath = shellutil.DetectLocalShellPath() + } if cmdStr == "" { if isBashShell(shellPath) { // add --rcfile // cant set -l or -i with --rcfile shellOpts = append(shellOpts, "--rcfile", shellutil.GetBashRcFileOverride()) + } else if isFishShell(shellPath) { + wshBinDir := filepath.Join(wavebase.GetWaveHomeDir(), shellutil.WaveHomeBinDir) + quotedWshBinDir := utilfn.ShellQuote(wshBinDir, false, 300) + shellOpts = append(shellOpts, "-C", fmt.Sprintf("set -x PATH %s $PATH", quotedWshBinDir)) } else if remote.IsPowershell(shellPath) { shellOpts = append(shellOpts, "-ExecutionPolicy", "Bypass", "-NoExit", "-File", shellutil.GetWavePowershellEnv()) } else { @@ -322,7 +343,6 @@ func StartShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOpt } cmdPty, err := pty.StartWithSize(ecmd, &pty.Winsize{Rows: uint16(termSize.Rows), Cols: uint16(termSize.Cols)}) if err != nil { - cmdPty.Close() return nil, err } return &ShellProc{Cmd: CmdWrap{ecmd, cmdPty}, CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil diff --git a/pkg/waveobj/metaconsts.go b/pkg/waveobj/metaconsts.go index d6d18f468..40de3c165 100644 --- a/pkg/waveobj/metaconsts.go +++ b/pkg/waveobj/metaconsts.go @@ -59,6 +59,7 @@ const ( MetaKey_TermFontFamily = "term:fontfamily" MetaKey_TermMode = "term:mode" MetaKey_TermTheme = "term:theme" + MetaKey_TermLocalShellPath = "term:localshellpath" MetaKey_Count = "count" ) diff --git a/pkg/waveobj/wtypemeta.go b/pkg/waveobj/wtypemeta.go index d964c2062..e9d4e95d7 100644 --- a/pkg/waveobj/wtypemeta.go +++ b/pkg/waveobj/wtypemeta.go @@ -54,12 +54,14 @@ type MetaTSType struct { BgOpacity float64 `json:"bg:opacity,omitempty"` BgBlendMode string `json:"bg:blendmode,omitempty"` - TermClear bool `json:"term:*,omitempty"` - TermFontSize int `json:"term:fontsize,omitempty"` - TermFontFamily string `json:"term:fontfamily,omitempty"` - TermMode string `json:"term:mode,omitempty"` - TermTheme string `json:"term:theme,omitempty"` - Count int `json:"count,omitempty"` // temp for cpu plot. will remove later + TermClear bool `json:"term:*,omitempty"` + TermFontSize int `json:"term:fontsize,omitempty"` + TermFontFamily string `json:"term:fontfamily,omitempty"` + TermMode string `json:"term:mode,omitempty"` + TermTheme string `json:"term:theme,omitempty"` + TermLocalShellPath string `json:"term:localshellpath,omitempty"` // matches settings + + Count int `json:"count,omitempty"` // temp for cpu plot. will remove later } type MetaDataDecl struct { diff --git a/pkg/wconfig/metaconsts.go b/pkg/wconfig/metaconsts.go index a9669a419..847a7df60 100644 --- a/pkg/wconfig/metaconsts.go +++ b/pkg/wconfig/metaconsts.go @@ -18,6 +18,7 @@ const ( ConfigKey_TermFontSize = "term:fontsize" ConfigKey_TermFontFamily = "term:fontfamily" ConfigKey_TermDisableWebGl = "term:disablewebgl" + ConfigKey_TermLocalShellPath = "term:localshellpath" ConfigKey_EditorMinimapEnabled = "editor:minimapenabled" ConfigKey_EditorStickyScrollEnabled = "editor:stickyscrollenabled" diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 6be7ee044..a8a01f7b5 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -48,10 +48,11 @@ type SettingsType struct { AiMaxTokens float64 `json:"ai:maxtokens,omitempty"` AiTimeoutMs float64 `json:"ai:timeoutms,omitempty"` - TermClear bool `json:"term:*,omitempty"` - TermFontSize float64 `json:"term:fontsize,omitempty"` - TermFontFamily string `json:"term:fontfamily,omitempty"` - TermDisableWebGl bool `json:"term:disablewebgl,omitempty"` + TermClear bool `json:"term:*,omitempty"` + TermFontSize float64 `json:"term:fontsize,omitempty"` + TermFontFamily string `json:"term:fontfamily,omitempty"` + TermDisableWebGl bool `json:"term:disablewebgl,omitempty"` + TermLocalShellPath string `json:"term:localshellpath,omitempty"` EditorMinimapEnabled bool `json:"editor:minimapenabled,omitempty"` EditorStickyScrollEnabled bool `json:"editor:stickyscrollenabled,omitempty"`