use crate::{
config::{LspServerConfig, LspSettings},
lsp::DocumentVersion,
};
use std::{collections::BTreeMap, path::Path};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct LanguageRoute {
pub(crate) server_id: String,
pub(crate) language_id: String,
pub(crate) config: LspServerConfig,
}
#[derive(Debug, Default, Clone)]
pub(crate) struct DocumentVersions {
by_path: BTreeMap<std::path::PathBuf, DocumentVersion>,
}
impl DocumentVersions {
pub(crate) fn next_for(&mut self, path: &Path) -> DocumentVersion {
let entry = self
.by_path
.entry(path.to_path_buf())
.or_insert(DocumentVersion(0));
entry.0 += 1;
*entry
}
}
pub(crate) fn default_servers() -> BTreeMap<String, LspServerConfig> {
BTreeMap::from([
(
"rust-analyzer".to_string(),
LspServerConfig {
command: "rust-analyzer".to_string(),
args: Vec::new(),
enabled: true,
},
),
(
"typescript-language-server".to_string(),
LspServerConfig {
command: "typescript-language-server".to_string(),
args: vec!["--stdio".to_string()],
enabled: true,
},
),
(
"pyright-langserver".to_string(),
LspServerConfig {
command: "pyright-langserver".to_string(),
args: vec!["--stdio".to_string()],
enabled: true,
},
),
(
"gopls".to_string(),
LspServerConfig {
command: "gopls".to_string(),
args: Vec::new(),
enabled: true,
},
),
])
}
pub(crate) fn route_for_path(path: &Path, settings: &LspSettings) -> Option<LanguageRoute> {
let language_id = language_id_for_path(path)?.to_string();
let server_id = server_id_for_language(&language_id)?.to_string();
let mut servers = default_servers();
for (key, config) in &settings.servers {
if servers.contains_key(key) {
servers.insert(key.clone(), config.clone());
}
}
let config = servers.remove(&server_id)?;
config.enabled.then_some(LanguageRoute {
server_id,
language_id,
config,
})
}
pub(crate) fn language_id_for_path(path: &Path) -> Option<&'static str> {
match path.extension().and_then(|extension| extension.to_str())? {
"rs" => Some("rust"),
"ts" | "tsx" => Some("typescript"),
"js" | "jsx" => Some("javascript"),
"py" => Some("python"),
"go" => Some("go"),
_ => None,
}
}
fn server_id_for_language(language_id: &str) -> Option<&'static str> {
match language_id {
"rust" => Some("rust-analyzer"),
"typescript" | "javascript" => Some("typescript-language-server"),
"python" => Some("pyright-langserver"),
"go" => Some("gopls"),
_ => None,
}
}
pub(crate) fn read_text_for_sync(path: &Path) -> anyhow::Result<String> {
Ok(std::fs::read_to_string(path)?)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn routes_supported_extensions() {
let settings = LspSettings::default();
assert_eq!(
route_for_path(Path::new("a.rs"), &settings)
.unwrap()
.server_id,
"rust-analyzer"
);
assert_eq!(
route_for_path(Path::new("a.ts"), &settings)
.unwrap()
.language_id,
"typescript"
);
assert!(route_for_path(Path::new("a.txt"), &settings).is_none());
}
#[test]
fn route_uses_configured_default_server_override() {
let mut settings = LspSettings::default();
settings.servers.insert(
"rust-analyzer".to_string(),
LspServerConfig {
command: "custom-ra".to_string(),
args: vec!["--x".to_string()],
enabled: true,
},
);
let route = route_for_path(Path::new("a.rs"), &settings).unwrap();
assert_eq!(route.config.command, "custom-ra");
assert_eq!(route.config.args, ["--x"]);
}
#[test]
fn route_ignores_non_default_server_override_and_disabled_default() {
let mut settings = LspSettings::default();
settings.servers.insert(
"custom-server".to_string(),
LspServerConfig {
command: "custom".to_string(),
args: Vec::new(),
enabled: true,
},
);
assert_eq!(
route_for_path(Path::new("a.rs"), &settings)
.unwrap()
.config
.command,
"rust-analyzer"
);
settings.servers.insert(
"rust-analyzer".to_string(),
LspServerConfig {
command: "rust-analyzer".to_string(),
args: Vec::new(),
enabled: false,
},
);
assert!(route_for_path(Path::new("a.rs"), &settings).is_none());
}
#[test]
fn language_ids_cover_defaults() {
assert_eq!(language_id_for_path(Path::new("x.tsx")), Some("typescript"));
assert_eq!(language_id_for_path(Path::new("x.jsx")), Some("javascript"));
assert_eq!(language_id_for_path(Path::new("x.py")), Some("python"));
assert_eq!(language_id_for_path(Path::new("x.go")), Some("go"));
assert_eq!(language_id_for_path(Path::new("x.md")), None);
}
}