Skip to main content

forge_agent/env/
lsp_path.rs

1//! Whether a declared LSP server binary resolves on PATH.
2//!
3//! Lifted from the terminal's Extensions pane when the web view grew the
4//! same read (rule 24): the check is a fact about the machine, not a way of
5//! drawing it, so views ask it here.
6
7/// Whether `server_binary` resolves on `path_var`: some directory on the
8/// path holds it as an executable file. `path_var` is injected so tests
9/// need not touch the process environment.
10pub fn lsp_binary_on_path(server_binary: &str, path_var: Option<&str>) -> bool {
11    use std::os::unix::fs::PermissionsExt;
12    let Some(path_var) = path_var else { return false };
13    path_var.split(':').any(|dir| {
14        let candidate = std::path::Path::new(dir).join(server_binary);
15        candidate
16            .metadata()
17            .is_ok_and(|meta| meta.is_file() && meta.permissions().mode() & 0o111 != 0)
18    })
19}
20
21#[cfg(test)]
22mod tests {
23    use super::*;
24
25    /// Catches a check that answers "yes" for a binary that is not there:
26    /// the row would draw the reassuring half of the pair for every server.
27    #[test]
28    fn a_missing_binary_is_not_on_the_path() {
29        let dir = tempfile::tempdir().expect("tempdir");
30        let path = dir.path().to_string_lossy().into_owned();
31        assert!(!lsp_binary_on_path("definitely-not-here-xyz", Some(&path)));
32        assert!(!lsp_binary_on_path("definitely-not-here-xyz", None));
33    }
34
35    /// The declared command is what gets probed, not the server key, and a
36    /// file that exists but is not executable does not count.
37    #[test]
38    fn the_declared_command_is_probed_as_an_executable_file() {
39        use std::os::unix::fs::PermissionsExt;
40        let dir = tempfile::tempdir().expect("tempdir");
41        let binary = dir.path().join("probe-ls");
42        std::fs::write(&binary, "#!/bin/sh\n").expect("write");
43        std::fs::set_permissions(&binary, std::fs::Permissions::from_mode(0o644))
44            .expect("chmod 644");
45        let path = dir.path().to_string_lossy().into_owned();
46        assert!(
47            !lsp_binary_on_path("probe-ls", Some(&path)),
48            "a non-executable file must not read as on PATH"
49        );
50        std::fs::set_permissions(&binary, std::fs::Permissions::from_mode(0o755))
51            .expect("chmod 755");
52        assert!(lsp_binary_on_path("probe-ls", Some(&path)));
53    }
54
55    /// The walk covers every `:`-separated directory, not just the first: a
56    /// binary living in a later entry is on the path exactly as one in the
57    /// first is.
58    #[test]
59    fn the_walk_covers_every_directory_on_the_path() {
60        use std::os::unix::fs::PermissionsExt;
61        let first = tempfile::tempdir().expect("tempdir");
62        let second = tempfile::tempdir().expect("tempdir");
63        let binary = second.path().join("late-binary");
64        std::fs::write(&binary, "#!/bin/sh\n").expect("write");
65        std::fs::set_permissions(&binary, std::fs::Permissions::from_mode(0o755)).expect("chmod");
66        let path = format!("{}:{}", first.path().display(), second.path().display());
67        assert!(
68            lsp_binary_on_path("late-binary", Some(&path)),
69            "a directory after the first is walked like the first"
70        );
71    }
72}