mise 2026.2.24

The front-end to your dev env
#![allow(unknown_lints)]
#![allow(clippy::literal_string_with_formatting_args)]
use std::fmt::Display;

use crate::shell::{self, ActivateOptions, Shell};
use indoc::formatdoc;
use shell_escape::unix::escape;

#[derive(Default)]
pub struct Elvish {}

impl Shell for Elvish {
    fn activate(&self, opts: ActivateOptions) -> String {
        let exe = opts.exe;
        let flags = opts.flags;
        let exe = escape(exe.to_string_lossy());

        let mut out = String::new();
        out.push_str(&shell::build_deactivation_script(self));
        out.push_str(&self.format_activate_prelude(&opts.prelude));
        out.push_str(&formatdoc! {r#"
            var hook-enabled = $false

            fn hook-env {{
              if $hook-enabled {{
                eval ((external {exe}) hook-env{flags} -s elvish | slurp)
              }}
            }}

            set after-chdir = (conj $after-chdir {{|_| hook-env }})
            set edit:before-readline = (conj $edit:before-readline $hook-env~)

            fn activate {{
              set-env MISE_SHELL elvish
              set hook-enabled = ${hook_enabled}
              hook-env
            }}

            fn deactivate {{
              set hook-enabled = $false
              eval ((external {exe}) deactivate | slurp)
            }}

            fn mise {{|@a|
              if (== (count $a) 0) {{
                (external {exe})
                return
              }}

              if (not (or (has-value $a -h) (has-value $a --help))) {{
                var command = $a[0]
                if (==s $command shell) {{
                  try {{ eval ((external {exe}) $@a) }} catch {{ }}
                  return
                }} elif (==s $command deactivate) {{
                  deactivate
                  return
                }} elif (==s $command activate) {{
                  activate
                  return
                }}
              }}
              (external {exe}) $@a
            }}
            "#, hook_enabled = !opts.no_hook_env});
        out
    }

    fn deactivate(&self) -> String {
        formatdoc! {r#"
            unset-env MISE_SHELL
            unset-env __MISE_DIFF
            unset-env __MISE_SESSION
        "#}
    }

    fn set_env(&self, k: &str, v: &str) -> String {
        let k = shell_escape::unix::escape(k.into());
        let v = shell_escape::unix::escape(v.into());
        let v = v.replace("\\n", "\n");
        format!("set-env {k} {v}\n")
    }

    fn prepend_env(&self, k: &str, v: &str) -> String {
        let k = shell_escape::unix::escape(k.into());
        let v = shell_escape::unix::escape(v.into());
        format!("set-env {k} {v}(get-env {k})\n")
    }

    fn unset_env(&self, k: &str) -> String {
        format!("unset-env {k}\n", k = shell_escape::unix::escape(k.into()))
    }
}

impl Display for Elvish {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "elvish")
    }
}

#[cfg(test)]
mod tests {
    use insta::assert_snapshot;
    use std::path::Path;
    use test_log::test;

    use crate::test::replace_path;

    use super::*;

    #[test]
    fn test_hook_init() {
        let elvish = Elvish::default();
        let exe = Path::new("/some/dir/mise");
        let opts = ActivateOptions {
            exe: exe.to_path_buf(),
            flags: " --status".into(),
            no_hook_env: false,
            prelude: vec![],
        };
        assert_snapshot!(elvish.activate(opts));
    }

    #[test]
    fn test_set_env() {
        assert_snapshot!(Elvish::default().set_env("FOO", "1"));
    }

    #[test]
    fn test_prepend_env() {
        let sh = Elvish::default();
        assert_snapshot!(replace_path(&sh.prepend_env("PATH", "/some/dir:/2/dir")));
    }

    #[test]
    fn test_unset_env() {
        assert_snapshot!(Elvish::default().unset_env("FOO"));
    }

    #[test]
    fn test_deactivate() {
        let deactivate = Elvish::default().deactivate();
        assert_snapshot!(replace_path(&deactivate));
    }
}