dev_kit/command/json/
difftool.rs

1use crate::command::json::{DiffTool, JetbrainsIDE};
2use anyhow::anyhow;
3use itertools::Itertools;
4use std::path::Path;
5use std::process::{exit, Command};
6use std::str::FromStr;
7use strum::IntoEnumIterator;
8use which::which;
9
10impl FromStr for DiffTool {
11    type Err = anyhow::Error;
12
13    fn from_str(s: &str) -> Result<Self, Self::Err> {
14        let s = s.to_lowercase();
15        match s.as_str() {
16            "zed" => Ok(DiffTool::Zed),
17            "vscode" | "code" => Ok(DiffTool::VSCode),
18            val => JetbrainsIDE::iter().find(|it|
19                it.to_string().eq(val)
20            ).map(DiffTool::JetbrainsIDE).ok_or_else(||
21                anyhow!("invalid diff tool: {}", val)
22            )
23        }
24    }
25}
26
27impl Default for DiffTool {
28    fn default() -> Self {
29        let difftool = Self::list_available_diff_tools().into_iter()
30            .filter(|it| it.is_available())
31            .next();
32        if let Some(difftool) = difftool {
33            difftool
34        } else {
35            eprintln!("no diff tool available, you can install one of the following:");
36            for it in Self::iter() {
37                println!("{}", it.how_to_install())
38            }
39            exit(0)
40        }
41    }
42}
43
44impl DiffTool {
45    pub fn diff<L: AsRef<Path>, R: AsRef<Path>>(&self, left: L, right: R) -> crate::Result<()> {
46        let left = left.as_ref();
47        let right = right.as_ref();
48        match self {
49            DiffTool::JetbrainsIDE(ide) => {
50                let program = which(ide.to_string()).map_err(|_| anyhow!("JetbrainsIDE {} not found", ide))?;
51                let status = Command::new(program)
52                    .arg("diff")
53                    .arg(left.display().to_string())
54                    .arg(right.display().to_string())
55                    .status()
56                    .map_err(|err| anyhow!(
57r#"
58failed to execute idea diff {} {}
59error: {}"#,
60                        left.display(), right.display(), err
61                    ))?;
62                if status.success() {
63                    Ok(())
64                } else {
65                    Err(anyhow!("idea diff command failed with status: {}", status))
66                }
67            }
68            DiffTool::Zed => {
69                let program = which("zed").map_err(|_| anyhow!("zed not found"))?;
70                let status = Command::new(program)
71                    .arg("--diff")
72                    .arg(left.display().to_string())
73                    .arg(right.display().to_string())
74                    .status()
75                    .map_err(|err| anyhow!(
76r#"
77failed to execute zed --diff {} {}
78error: {}"#,
79                        left.display(), right.display(), err
80                    ))?;
81                if status.success() {
82                    Ok(())
83                } else {
84                    Err(anyhow!("zed --diff command failed with status: {}", status))
85                }
86            }
87            &DiffTool::VSCode => {
88                let program = which("code").or_else(|_|
89                    which("vscode")
90                ).map_err(|_|
91                    anyhow!("code/vscode not found")
92                )?;
93                let status = Command::new(program)
94                    .arg("--diff")
95                    .arg(left.display().to_string())
96                    .arg(right.display().to_string())
97                    .status()
98                    .map_err(|err| anyhow!(
99r#"
100failed to execute code --diff {} {}
101error: {}"#,
102                        left.display(), right.display(), err
103                    ))?;
104                if status.success() {
105                    Ok(())
106                } else {
107                    Err(anyhow!("code --diff command failed with status: {}", status))
108                }
109            }
110        }
111    }
112
113    pub fn is_available(&self) -> bool {
114        match self {
115            DiffTool::JetbrainsIDE(ide) => which(ide.to_string()).is_ok(),
116            DiffTool::Zed => which("zed").is_ok(),
117            DiffTool::VSCode => which("code").is_ok() || which("vscode").is_ok(),
118        }
119    }
120
121    pub fn how_to_install(&self) -> String {
122        match self {
123            DiffTool::JetbrainsIDE(ide) => format!("https://www.jetbrains.com/help/{}/working-with-the-ide-features-from-command-line.html", ide.to_string()),
124            DiffTool::Zed => "https://zed.dev/docs/command-line-interface".to_string(),
125            DiffTool::VSCode => "https://code.visualstudio.com/docs/configure/command-line".to_string(),
126        }
127    }
128
129    pub fn list_available_diff_tools() -> Vec<DiffTool> {
130        vec![
131            JetbrainsIDE::iter().map(|it| Self::JetbrainsIDE(it)).collect_vec(),
132            vec![DiffTool::Zed, DiffTool::VSCode]
133        ].into_iter().flatten().filter(|it| it.is_available()).collect_vec()
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_list_available_diff_tools() {
143        let available_diff_tools = DiffTool::list_available_diff_tools();
144        for available_diff_tool in available_diff_tools {
145            println!("{}", available_diff_tool);
146        }
147    }
148}