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}