Skip to main content

idt/installers/
alacritty.rs

1use std::path::Path;
2
3use rootcause::prelude::ResultExt;
4
5use crate::installers::Installer;
6
7pub struct Alacritty<'a> {
8    pub dev_tools_dir: &'a Path,
9    pub bin_dir: &'a Path,
10}
11
12impl Installer for Alacritty<'_> {
13    fn bin_name(&self) -> &'static str {
14        "alacritty"
15    }
16
17    /// Builds Alacritty from source, symlinks the binary into `bin_dir`, and
18    /// copies `Alacritty.app` into `/Applications` (atomic swap).
19    fn install(&self) -> rootcause::Result<()> {
20        let source_dir = self.dev_tools_dir.join(self.bin_name()).join("source");
21
22        ytil_cmd::silent_cmd("sh")
23            .args([
24                "-c",
25                &format!(
26                    r#"
27                        ([ ! -d "{0}" ] && \
28                            git clone --depth=1 https://github.com/alacritty/alacritty.git {0} || true) && \
29                        cd {0} && \
30                        git fetch origin master --depth=1 && \
31                        git checkout origin/master && \
32                        rustup toolchain install stable --profile default && \
33                        rustup override set stable && \
34                        make app
35                    "#,
36                    source_dir.display(),
37                ),
38            ])
39            .status()
40            .context("failed to spawn build command")?
41            .exit_ok()
42            .context("build failed")
43            .attach_with(|| format!("tool={}", self.bin_name()))
44            .attach_with(|| format!("source_dir={}", source_dir.display()))?;
45
46        let app = source_dir
47            .join("target")
48            .join("release")
49            .join("osx")
50            .join("Alacritty.app");
51
52        crate::installers::install_macos_app(&app, self.bin_dir, self.bin_name())?;
53
54        // Alacritty sets TERM=alacritty, but programs need a matching terminfo entry to
55        // know the terminal's capabilities. Without it the system falls back to TERM=dumb,
56        // which breaks tools that depend on a capable terminal (e.g. starship, neovim).
57        // The app bundle ships pre-compiled entries; copy them to ~/.terminfo/ (no sudo,
58        // bypasses macOS SIP on /usr/share, avoids tic compatibility issues).
59        let home = std::env::var("HOME").context("error reading HOME env var")?;
60        let terminfo_dest = Path::new(&home).join(".terminfo").join("61");
61        std::fs::create_dir_all(&terminfo_dest)
62            .context("error creating ~/.terminfo/61")
63            .attach_with(|| format!("path={}", terminfo_dest.display()))?;
64
65        let bundled = app.join("Contents").join("Resources").join("61");
66        for entry in &["alacritty", "alacritty-direct"] {
67            let src = bundled.join(entry);
68            let dst = terminfo_dest.join(entry);
69            std::fs::copy(&src, &dst)
70                .context("error copying terminfo entry")
71                .attach_with(|| format!("src={}", src.display()))
72                .attach_with(|| format!("dst={}", dst.display()))?;
73        }
74
75        Ok(())
76    }
77
78    fn health_check_args(&self) -> Option<&[&str]> {
79        Some(&["--version"])
80    }
81
82    fn should_verify_checksum(&self) -> bool {
83        false
84    }
85}