mod completion;
mod configuration;
pub mod index_worker;
mod linting;
mod navigation;
mod position;
mod relint;
pub mod server;
mod symbols;
pub mod types;
pub use server::RumdlLanguageServer;
pub use types::{RumdlLspConfig, warning_to_code_actions, warning_to_diagnostic};
use std::path::{Path, PathBuf};
use tokio::net::TcpListener;
use tower_lsp::{LspService, Server};
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
pub(crate) fn resolve_workspace_root(path: &Path) -> PathBuf {
crate::discovery::canonicalize_for_matching(path).unwrap_or_else(|| path.to_path_buf())
}
pub(crate) fn resolve_document_path(path: &Path) -> PathBuf {
let (Some(parent), Some(name)) = (path.parent(), path.file_name()) else {
return resolve_workspace_root(path);
};
match crate::discovery::canonicalize_for_matching(parent) {
Some(dir) => dir.join(name),
None => path.to_path_buf(),
}
}
pub(crate) fn resolve_uri(uri: &tower_lsp::lsp_types::Url) -> Option<PathBuf> {
uri.to_file_path().ok().map(|path| resolve_document_path(&path))
}
pub(crate) fn resolve_uri_spelling(uri: &tower_lsp::lsp_types::Url) -> tower_lsp::lsp_types::Url {
resolve_uri(uri)
.and_then(|path| tower_lsp::lsp_types::Url::from_file_path(path).ok())
.unwrap_or_else(|| uri.clone())
}
pub async fn start_server(config_path: Option<&str>) -> Result<()> {
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();
let (service, socket) = LspService::new(|client| RumdlLanguageServer::new(client, config_path));
log::info!("Starting rumdl Language Server Protocol server");
Server::new(stdin, stdout, socket).serve(service).await;
Ok(())
}
pub async fn start_tcp_server(port: u16, config_path: Option<&str>) -> Result<()> {
let listener = TcpListener::bind(format!("127.0.0.1:{port}")).await?;
log::info!("rumdl LSP server listening on 127.0.0.1:{port}");
let config_path_owned = config_path.map(std::string::ToString::to_string);
loop {
let (stream, _) = listener.accept().await?;
let config_path_clone = config_path_owned.clone();
let (service, socket) =
LspService::new(move |client| RumdlLanguageServer::new(client, config_path_clone.as_deref()));
tokio::spawn(async move {
let (read, write) = tokio::io::split(stream);
Server::new(read, write, socket).serve(service).await;
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_module_exports() {
fn _check_exports() {
let _server_type: RumdlLanguageServer;
let _config_type: RumdlLspConfig;
let _func1: fn(&crate::rule::LintWarning, &str) -> tower_lsp::lsp_types::Diagnostic = warning_to_diagnostic;
let _func2: fn(
&crate::rule::LintWarning,
&tower_lsp::lsp_types::Url,
&str,
) -> Vec<tower_lsp::lsp_types::CodeAction> = warning_to_code_actions;
}
}
#[tokio::test]
async fn test_tcp_server_bind() {
use std::net::TcpListener as StdTcpListener;
let listener = StdTcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
drop(listener);
let server_handle = tokio::spawn(async move {
match tokio::time::timeout(std::time::Duration::from_millis(100), start_tcp_server(port, None)).await {
Ok(Ok(())) => {} Ok(Err(_)) => {} Err(_) => {} }
});
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
match tokio::time::timeout(
std::time::Duration::from_millis(50),
tokio::net::TcpStream::connect(format!("127.0.0.1:{port}")),
)
.await
{
Ok(Ok(_)) => {
}
_ => {
}
}
server_handle.abort();
}
#[tokio::test]
async fn test_tcp_server_invalid_port() {
let result = tokio::time::timeout(std::time::Duration::from_millis(100), start_tcp_server(80, None)).await;
match result {
Ok(Err(_)) => {
}
Ok(Ok(())) => {
panic!("Should not be able to bind to port 80 without privileges");
}
Err(_) => {
}
}
}
#[tokio::test]
async fn test_service_creation() {
let (service, _socket) = LspService::new(|client| RumdlLanguageServer::new(client, None));
drop(service);
}
#[tokio::test]
async fn test_multiple_tcp_connections() {
use std::net::TcpListener as StdTcpListener;
let listener = StdTcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
drop(listener);
let server_handle = tokio::spawn(async move {
let _ = tokio::time::timeout(std::time::Duration::from_millis(500), start_tcp_server(port, None)).await;
});
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
let mut handles = vec![];
for _ in 0..3 {
let handle = tokio::spawn(async move {
match tokio::time::timeout(
std::time::Duration::from_millis(100),
tokio::net::TcpStream::connect(format!("127.0.0.1:{port}")),
)
.await
{
Ok(Ok(_stream)) => {
true
}
_ => false,
}
});
handles.push(handle);
}
for handle in handles {
let _ = handle.await;
}
server_handle.abort();
}
#[test]
fn test_logging_initialization() {
let _info_level = log::Level::Info;
}
}