mod client;
mod diagnostics;
mod manager;
mod stdio;
mod sync;
pub(crate) use manager::{EditDiagnosticsRequest, LspManager};
pub(crate) use diagnostics::{DiagnosticsStore, LspDiagnostic};
pub(crate) use sync::DocumentVersions;
use anyhow::Context;
use serde::{Deserialize, Serialize};
use std::path::Path;
pub(crate) const MAX_INJECTED_DIAGNOSTICS: usize = 20;
pub(crate) const MAX_INJECTED_DIAGNOSTICS_BYTES: usize = 2 * 1024;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub(crate) struct DocumentUri(String);
impl DocumentUri {
pub(crate) fn from_path(path: &Path) -> anyhow::Result<Self> {
let absolute = if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir()?.join(path)
};
let path = absolute
.canonicalize()
.with_context(|| format!("canonicalizing {}", absolute.display()))?;
Ok(Self(format!("file://{}", percent_encode_path(&path))))
}
pub(crate) fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[repr(u8)]
pub(crate) enum DiagnosticSeverity {
Error = 1,
Warning = 2,
Information = 3,
Hint = 4,
}
impl<'de> Deserialize<'de> for DiagnosticSeverity {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
match u8::deserialize(deserializer)? {
1 => Ok(Self::Error),
2 => Ok(Self::Warning),
3 => Ok(Self::Information),
4 => Ok(Self::Hint),
other => Err(serde::de::Error::custom(format!(
"unknown diagnostic severity {other}"
))),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct LspRange {
pub(crate) start: LspPosition,
pub(crate) end: LspPosition,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct LspPosition {
pub(crate) line: u32,
pub(crate) character: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct DocumentVersion(pub(crate) i32);
fn percent_encode_path(path: &Path) -> String {
path.to_string_lossy()
.bytes()
.flat_map(|byte| match byte {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'/' | b'-' | b'_' | b'.' | b'~' => {
vec![byte as char]
}
_ => format!("%{byte:02X}").chars().collect(),
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn document_uri_round_trips_path_with_spaces() {
let temp = tempfile::TempDir::new().unwrap();
let path = temp.path().join("file with spaces.rs");
std::fs::write(&path, "fn main() {}\n").unwrap();
let uri = DocumentUri::from_path(&path).unwrap();
assert!(uri.as_str().starts_with("file://"));
assert!(uri.as_str().contains("%20"));
}
}