1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
//! The goal of this mod is to ensure the launcher shell function
//! is available for fish i.e. the `br` shell function can
//! be used to launch broot (and thus make it possible to execute
//! some commands, like `cd`, from the starting shell.
//!
//!
//! In a correct installation, we have:
//! - a function declaration script in ~/.local/share/broot/launcher/fish/br.fish
//! - a link to that script in ~/.config/fish/functions/br.fish
//! (exact paths depend on XDG variables)
//!
//! fish stores functions in FISH_CONFIG_DIR/functions (for example,
//! ~/.config/fish/functions) and lazily loads (or reloads) them as
//! needed.

use {
    super::ShellInstall,
    crate::{conf, errors::ProgramError},
    directories::BaseDirs,
    directories::ProjectDirs,
    std::path::PathBuf,
};

const NAME: &str = "fish";
const SCRIPT_FILENAME: &str = "br.fish";

const FISH_FUNC: &str = r#"
# This script was automatically generated by the broot program
# More information can be found in https://github.com/Canop/broot
# This function starts broot and executes the command
# it produces, if any.
# It's needed because some shell commands, like `cd`,
# have no useful effect if executed in a subshell.
function br
    set -l cmd_file (mktemp)
    if broot --outcmd $cmd_file $argv
        read --local --null cmd < $cmd_file
        rm -f $cmd_file
        eval $cmd
    else
        set -l code $status
        rm -f $cmd_file
        return $code
    end
end
"#;

pub fn get_script() -> &'static str {
    FISH_FUNC
}

/// return the root of fish's config
fn get_fish_dir() -> PathBuf {
    if let Some(base_dirs) = BaseDirs::new() {
        let fish_dir = base_dirs.home_dir().join(".config/fish");
        if fish_dir.exists() {
            return fish_dir;
        }
    }
    ProjectDirs::from("fish", "fish", "fish") // hem...
        .expect("Unable to find configuration directories")
        .config_dir()
        .to_path_buf()
}

/// return the fish functions directory
fn get_fish_functions_dir() -> PathBuf {
    get_fish_dir().join("functions")
}

/// return the path to the link to the function script
///
/// At version 0.10.4 we change the location of the script:
/// It was previously with the link, but it's now in
/// ~/.config/fish/functions/br.fish
fn get_link_path() -> PathBuf {
    get_fish_functions_dir().join("br.fish")
}

/// return the path to the script containing the function.
///
/// At version 0.10.4 we change the location of the script:
/// It was previously with the link, but it's now in
/// ~/.local/share/broot/launcher/fish/br.fish
fn get_script_path() -> PathBuf {
    conf::app_dirs()
        .data_dir()
        .join("launcher")
        .join(NAME)
        .join(SCRIPT_FILENAME)
}

/// check for fish shell
///
/// As fish isn't frequently used, we first check that it seems
/// to be installed. If not, we just do nothing.
pub fn install(si: &mut ShellInstall) -> Result<(), ProgramError> {
    let fish_dir = get_fish_dir();
    if !fish_dir.exists() {
        debug!("no fish config directory. Assuming fish isn't used.");
        return Ok(());
    }
    info!("fish seems to be installed");
    let script_path = get_script_path();
    si.write_script(&script_path, FISH_FUNC)?;
    let link_path = get_link_path();
    // creating the link may create the fish/conf.d directory
    si.create_link(&link_path, &script_path)?;
    si.done = true;
    Ok(())
}