mod completion;
mod configuration;
pub mod index_worker;
mod linting;
mod navigation;
pub mod server;
pub mod types;
pub use server::RumdlLanguageServer;
pub use types::{RumdlLspConfig, warning_to_code_actions, warning_to_diagnostic};
use anyhow::Result;
use tokio::net::TcpListener;
use tower_lsp::{LspService, Server};
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(|s| s.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) -> 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;
}
}