Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion internal/nix/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,25 @@ import (
"go.jetpack.io/devbox/internal/boxcli/featureflag"
"go.jetpack.io/devbox/internal/boxcli/usererr"
"go.jetpack.io/devbox/internal/debug"
"go.jetpack.io/devbox/internal/xdg"
)

//go:embed shellrc.tmpl
var shellrcText string
var shellrcTmpl = template.Must(template.New("shellrc").Parse(shellrcText))

//go:embed shellrc_fish.tmpl
var fishrcText string
var fishrcTmpl = template.Must(template.New("shellrc_fish").Parse(fishrcText))

type name string

const (
shUnknown name = ""
shBash name = "bash"
shZsh name = "zsh"
shKsh name = "ksh"
shFish name = "fish"
shPosix name = "posix"
)

Expand Down Expand Up @@ -85,6 +91,9 @@ func DetectShell(opts ...ShellOption) (*Shell, error) {
case "ksh":
sh.name = shKsh
sh.userShellrcPath = rcfilePath(".kshrc")
case "fish":
sh.name = shFish
sh.userShellrcPath = fishConfig()
case "dash", "ash", "sh":
sh.name = shPosix
sh.userShellrcPath = os.Getenv("ENV")
Expand Down Expand Up @@ -172,6 +181,10 @@ func rcfilePath(basename string) string {
return filepath.Join(home, basename)
}

func fishConfig() string {
return filepath.Join(xdg.ConfigDir(), "fish", "config.fish")
}

func (s *Shell) Run(nixShellFilePath, nixFlakesFilePath string) error {
// Copy the current PATH into nix-shell, but clean and remove some
// directories that are incompatible.
Expand Down Expand Up @@ -342,6 +355,13 @@ func (s *Shell) shellRCOverrides(shellrc string) (extraEnv []string, extraArgs [
extraEnv = []string{fmt.Sprintf(`ZDOTDIR=%s`, shellescape.Quote(filepath.Dir(shellrc)))}
case shKsh, shPosix:
extraEnv = []string{fmt.Sprintf(`ENV=%s`, shellescape.Quote(shellrc))}
case shFish:
if featureflag.UnifiedEnv.Enabled() {
extraArgs = []string{"-C", ". " + shellrc}
} else {
// Needs quotes because it's wrapped inside the nix-shell command
extraArgs = []string{"-C", shellescape.Quote(". " + shellrc)}
}
}
return extraEnv, extraArgs
}
Expand Down Expand Up @@ -394,7 +414,12 @@ func (s *Shell) writeDevboxShellrc() (path string, err error) {
pathPrepend = s.pkgConfigDir + ":" + pathPrepend
}

err = shellrcTmpl.Execute(shellrcf, struct {
tmpl := shellrcTmpl
if s.name == shFish {
tmpl = fishrcTmpl
}

err = tmpl.Execute(shellrcf, struct {
ProjectDir string
OriginalInit string
OriginalInitPath string
Expand Down
107 changes: 107 additions & 0 deletions internal/nix/shellrc_fish.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
{{- /*

This template defines the shellrc file that the devbox shell will run at
startup when using the fish shell.

It does _not_ include the user's original fish config, because unlike other
shells, fish has multiple files as part of its config, and it's difficult
to start a fish shell with a custom fish config. Instead, we let fish read
the user's original config directly, and run these commands next.

Devbox needs to ensure that the shell's PATH, prompt, and a few other things are
set correctly after the user's shellrc runs. The commands to do this are in
the "Devbox Post-init Hook" section.

This file is useful for debugging shell errors, so try to keep the generated
content readable.

*/ -}}

{{- if .UnifiedEnv -}}
# Run the shell hook defined in shell.nix or flake.nix
eval $shellHook

{{ end -}}

# Begin Devbox Post-init Hook

{{- /*
NOTE: fish_add_path doesn't play nicely with colon:separated:paths, and I'd rather not
add string-splitting logic here nor parametrize computeNixEnv based on the shell being
used. So here we (ab)use the fact that using "export" ahead of the variable definition
makes fish do exactly what we want and behave in the same way as other shells.
*/ -}}
{{ if .UnifiedEnv -}}
export PATH="$DEVBOX_PATH_PREPEND:$PATH"
{{- else -}}
export PATH="{{ .PathPrepend }}:$PATH"
{{- end }}

{{- /*
Set the history file by setting fish_history. This is not exactly the same as with other
shells, because we're not setting the file, but rather the session name, but it's a good
enough approximation for now.
*/ -}}
{{- if .HistoryFile }}
set fish_history devbox
{{- end }}

# Prepend to the prompt to make it clear we're in a devbox shell.
functions -c fish_prompt __devbox_fish_prompt_orig
function fish_prompt
echo "(devbox)" (__devbox_fish_prompt_orig)
end

{{- if .ShellStartTime }}
# log that the shell is ready now!
devbox log shell-ready {{ .ShellStartTime }}
{{ end }}

# End Devbox Post-init Hook

# Switch to the directory where devbox.json config is
set workingDir $(pwd)
cd {{ .ProjectDir }}

{{- if .PluginInitHook }}

# Begin Plugin Init Hook

{{ .PluginInitHook }}

# End Plugin Init Hook

{{- end }}

{{- if .UserHook }}

# Begin Devbox User Hook

{{ .UserHook }}

# End Devbox User Hook

{{- end }}

cd $workingDir

{{- if .ShellStartTime }}
# log that the shell is interactive now!
devbox log shell-interactive {{ .ShellStartTime }}
{{ end }}

# Begin Script command

{{- if .ScriptCommand }}

function run_script
set workingDir $(pwd)
cd {{ .ProjectDir }}

{{ .ScriptCommand }}

cd $workingDir
end
{{- end }}

# End Script command