zoi/cmd/
shell.rs

1use crate::cli::{Cli, SetupScope};
2use crate::pkg::types::Scope;
3use crate::utils;
4use anyhow::Result;
5use clap::CommandFactory;
6use clap_complete::{Shell, generate};
7use colored::*;
8use std::fs;
9use std::io::{Error, ErrorKind};
10use std::path::PathBuf;
11
12fn get_completion_path(shell: Shell, scope: SetupScope) -> Result<PathBuf, Error> {
13    if scope == SetupScope::System {
14        if !utils::is_admin() {
15            return Err(Error::new(
16                ErrorKind::PermissionDenied,
17                "System-wide installation requires root privileges. Please run with sudo or as an administrator.",
18            ));
19        }
20        Ok(match shell {
21            Shell::Bash => PathBuf::from("/usr/share/bash-completion/completions/zoi"),
22            Shell::Elvish => PathBuf::from("/usr/share/elvish/lib/zoi.elv"),
23            Shell::Fish => PathBuf::from("/usr/share/fish/vendor_completions.d/zoi.fish"),
24            Shell::Zsh => PathBuf::from("/usr/share/zsh/site-functions/_zoi"),
25            _ => {
26                return Err(Error::new(
27                    ErrorKind::InvalidInput,
28                    "System-wide completion installation not supported for this shell.",
29                ));
30            }
31        })
32    } else {
33        let home = dirs::home_dir()
34            .ok_or_else(|| Error::new(ErrorKind::NotFound, "Home directory not found"))?;
35        Ok(match shell {
36            Shell::Bash => home.join(".local/share/bash-completion/completions/zoi"),
37            Shell::Zsh => home.join(".zsh/completions/_zoi"),
38            Shell::Fish => home.join(".config/fish/completions/zoi.fish"),
39            Shell::Elvish => home.join(".config/elvish/completions/zoi.elv"),
40            Shell::PowerShell => home.join("Documents/PowerShell/Microsoft.PowerShell_profile.ps1"),
41            _ => {
42                return Err(Error::new(
43                    ErrorKind::InvalidInput,
44                    "User-level completion installation not supported for this shell.",
45                ));
46            }
47        })
48    }
49}
50
51fn install_completions(
52    shell: Shell,
53    scope: SetupScope,
54    cmd: &mut clap::Command,
55) -> Result<(), Error> {
56    if cfg!(windows) && scope == SetupScope::System {
57        return Err(Error::new(
58            ErrorKind::Unsupported,
59            "System-wide shell setup is not supported on Windows.",
60        ));
61    }
62
63    let path = get_completion_path(shell, scope)?;
64    if let Some(parent) = path.parent() {
65        fs::create_dir_all(parent)?;
66    }
67
68    if shell == Shell::PowerShell {
69        let mut file = fs::OpenOptions::new()
70            .append(true)
71            .create(true)
72            .open(&path)?;
73        use std::io::Write;
74        writeln!(file)?;
75        let mut script_buf = Vec::new();
76        generate(shell, cmd, "zoi", &mut script_buf);
77        file.write_all(&script_buf)?;
78        println!(
79            "PowerShell completion script appended to your profile: {:?}",
80            path
81        );
82        println!("Please restart your shell or run '. $PROFILE' to activate it.");
83    } else {
84        let mut file = fs::File::create(&path)?;
85        generate(shell, cmd, "zoi", &mut file);
86        println!("{} completions installed in: {:?}", shell, path);
87    }
88
89    if shell == Shell::Zsh && scope == SetupScope::User {
90        println!("Ensure the directory is in your $fpath. Add this to your .zshrc if it's not:");
91        println!("  fpath=({:?} $fpath)", path.parent().unwrap());
92    }
93
94    Ok(())
95}
96
97pub fn run(shell: Shell, scope: SetupScope) -> Result<()> {
98    println!("--- Setting up shell: {} ---", shell.to_string().cyan());
99
100    let mut cmd = Cli::command();
101    install_completions(shell, scope, &mut cmd)?;
102
103    println!();
104
105    let scope_to_pass = match scope {
106        SetupScope::User => Scope::User,
107        SetupScope::System => Scope::System,
108    };
109    utils::setup_path(scope_to_pass)?;
110    Ok(())
111}