use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ManagedNodeLs {
pub ls_id: &'static str,
pub tool_dir: &'static str,
pub packages: &'static [(&'static str, &'static str)],
pub entry_rel: &'static [&'static str],
pub required_rel: &'static [&'static [&'static str]],
}
impl ManagedNodeLs {
pub fn pin(&self) -> &'static str {
self.packages[0].1
}
pub fn dir(&self, tools_dir: &Path) -> PathBuf {
tools_dir.join(self.tool_dir).join(self.pin())
}
pub fn required(&self, tools_dir: &Path) -> impl Iterator<Item = PathBuf> + '_ {
let dir = self.dir(tools_dir);
self.required_rel.iter().map(move |rel| {
let mut p = dir.clone();
p.extend(*rel);
p
})
}
pub fn entry(&self, tools_dir: &Path) -> PathBuf {
let mut p = self.dir(tools_dir);
p.extend(self.entry_rel);
p
}
}
pub const PHP_LS_ID: &str = "php";
pub const INTELEPHENSE_TOOL_DIR: &str = "intelephense";
pub const INTELEPHENSE_PACKAGE: &str = "intelephense";
pub const INTELEPHENSE_PIN: &str = "1.14.4";
pub const INTELEPHENSE: ManagedNodeLs = ManagedNodeLs {
ls_id: PHP_LS_ID,
tool_dir: INTELEPHENSE_TOOL_DIR,
packages: &[(INTELEPHENSE_PACKAGE, INTELEPHENSE_PIN)],
entry_rel: &[
"node_modules",
INTELEPHENSE_PACKAGE,
"lib",
"intelephense.js",
],
required_rel: &[],
};
pub const TS_LS_ID: &str = "typescript";
pub const TS_LS_PACKAGE: &str = "typescript-language-server";
pub const TS_LS_PIN: &str = "5.1.3";
pub const TYPESCRIPT_PIN: &str = "5.9.3";
pub const TYPESCRIPT: ManagedNodeLs = ManagedNodeLs {
ls_id: TS_LS_ID,
tool_dir: TS_LS_PACKAGE,
packages: &[(TS_LS_PACKAGE, TS_LS_PIN), ("typescript", TYPESCRIPT_PIN)],
entry_rel: &["node_modules", TS_LS_PACKAGE, "lib", "cli.mjs"],
required_rel: &[],
};
pub const VUE_LS_ID: &str = "vue";
pub const VUE_TOOL_DIR: &str = "vue-language-server";
pub const VUE_LS_PACKAGE: &str = "@vue/language-server";
pub const VUE_LS_PIN: &str = "3.1.5";
pub const VUE: ManagedNodeLs = ManagedNodeLs {
ls_id: VUE_LS_ID,
tool_dir: VUE_TOOL_DIR,
packages: &[
(VUE_LS_PACKAGE, VUE_LS_PIN),
("typescript", TYPESCRIPT_PIN),
(TS_LS_PACKAGE, TS_LS_PIN),
],
entry_rel: &[
"node_modules",
"@vue",
"language-server",
"bin",
"vue-language-server.js",
],
required_rel: &[
&crate::serena_vue::VUE_SHIM_REL,
&crate::serena_vue::TS_SHIM_REL,
],
};
pub const MANAGED: [ManagedNodeLs; 2] = [INTELEPHENSE, TYPESCRIPT];
pub fn managed(ls_id: &str) -> Option<&'static ManagedNodeLs> {
MANAGED.iter().find(|m| m.ls_id == ls_id)
}
pub const NODE: &str = "node";
pub fn intelephense_dir(tools_dir: &Path) -> PathBuf {
INTELEPHENSE.dir(tools_dir)
}
pub fn intelephense_entry(tools_dir: &Path) -> PathBuf {
INTELEPHENSE.entry(tools_dir)
}
pub fn canonical_base_cmd(ls_id: &str, tools_dir: &Path) -> Option<Vec<String>> {
let entry = managed(ls_id)?.entry(tools_dir);
Some(vec![NODE.to_owned(), entry.to_str()?.to_owned()])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn php_gets_node_plus_entry_without_stdio() {
let tools = Path::new("/h/.mur/tools");
let cmd = canonical_base_cmd(PHP_LS_ID, tools).unwrap();
assert_eq!(cmd.len(), 2, "serena appends --stdio itself: {cmd:?}");
assert_eq!(cmd[0], NODE);
assert_eq!(Path::new(&cmd[1]), intelephense_entry(tools));
assert!(cmd[1].ends_with("intelephense.js"));
assert!(Path::new(&cmd[1]).starts_with(intelephense_dir(tools)));
}
#[test]
fn typescript_gets_node_plus_cli_mjs_without_stdio() {
let tools = Path::new("/h/.mur/tools");
let cmd = canonical_base_cmd(TS_LS_ID, tools).unwrap();
assert_eq!(cmd.len(), 2, "serena appends --stdio itself: {cmd:?}");
assert_eq!(cmd[0], NODE);
assert_eq!(
Path::new(&cmd[1]),
Path::new(
"/h/.mur/tools/typescript-language-server/5.1.3/node_modules/typescript-language-server/lib/cli.mjs"
)
);
}
#[test]
fn typescript_installs_both_pinned_packages() {
assert_eq!(
TYPESCRIPT.packages,
[
("typescript-language-server", "5.1.3"),
("typescript", "5.9.3")
]
);
}
#[test]
fn every_managed_server_is_found_by_its_ls_id() {
for ls in MANAGED {
assert_eq!(managed(ls.ls_id).map(|m| m.tool_dir), Some(ls.tool_dir));
}
}
#[test]
fn other_servers_have_no_canonical_base_cmd() {
for id in [
"python",
"cpp",
"rust",
"typescript_vts",
"php_phpactor",
VUE_LS_ID,
"",
] {
assert!(canonical_base_cmd(id, Path::new("/t")).is_none(), "{id}");
}
}
}