1mod completion;
10mod configuration;
11pub mod index_worker;
12mod linting;
13mod navigation;
14mod position;
15pub mod server;
16mod symbols;
17pub mod types;
18
19pub use server::RumdlLanguageServer;
20pub use types::{RumdlLspConfig, warning_to_code_actions, warning_to_diagnostic};
21
22use anyhow::Result;
23use std::path::{Path, PathBuf};
24use tokio::net::TcpListener;
25use tower_lsp::{LspService, Server};
26
27pub(crate) fn resolve_workspace_root(path: &Path) -> PathBuf {
34 crate::discovery::canonicalize_for_matching(path).unwrap_or_else(|| path.to_path_buf())
35}
36
37pub(crate) fn resolve_document_path(path: &Path) -> PathBuf {
50 let (Some(parent), Some(name)) = (path.parent(), path.file_name()) else {
51 return resolve_workspace_root(path);
52 };
53 match crate::discovery::canonicalize_for_matching(parent) {
54 Some(dir) => dir.join(name),
55 None => path.to_path_buf(),
56 }
57}
58
59pub(crate) fn resolve_uri(uri: &tower_lsp::lsp_types::Url) -> Option<PathBuf> {
63 uri.to_file_path().ok().map(|path| resolve_document_path(&path))
64}
65
66pub(crate) fn resolve_uri_spelling(uri: &tower_lsp::lsp_types::Url) -> tower_lsp::lsp_types::Url {
73 resolve_uri(uri)
74 .and_then(|path| tower_lsp::lsp_types::Url::from_file_path(path).ok())
75 .unwrap_or_else(|| uri.clone())
76}
77
78pub async fn start_server(config_path: Option<&str>) -> Result<()> {
81 let stdin = tokio::io::stdin();
82 let stdout = tokio::io::stdout();
83
84 let (service, socket) = LspService::new(|client| RumdlLanguageServer::new(client, config_path));
85
86 log::info!("Starting rumdl Language Server Protocol server");
87
88 Server::new(stdin, stdout, socket).serve(service).await;
89
90 Ok(())
91}
92
93pub async fn start_tcp_server(port: u16, config_path: Option<&str>) -> Result<()> {
95 let listener = TcpListener::bind(format!("127.0.0.1:{port}")).await?;
96 log::info!("rumdl LSP server listening on 127.0.0.1:{port}");
97
98 let config_path_owned = config_path.map(std::string::ToString::to_string);
100
101 loop {
102 let (stream, _) = listener.accept().await?;
103 let config_path_clone = config_path_owned.clone();
104 let (service, socket) =
105 LspService::new(move |client| RumdlLanguageServer::new(client, config_path_clone.as_deref()));
106
107 tokio::spawn(async move {
108 let (read, write) = tokio::io::split(stream);
109 Server::new(read, write, socket).serve(service).await;
110 });
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn test_module_exports() {
120 fn _check_exports() {
123 let _server_type: RumdlLanguageServer;
125 let _config_type: RumdlLspConfig;
126 let _func1: fn(&crate::rule::LintWarning, &str) -> tower_lsp::lsp_types::Diagnostic = warning_to_diagnostic;
127 let _func2: fn(
128 &crate::rule::LintWarning,
129 &tower_lsp::lsp_types::Url,
130 &str,
131 ) -> Vec<tower_lsp::lsp_types::CodeAction> = warning_to_code_actions;
132 }
133 }
134
135 #[tokio::test]
136 async fn test_tcp_server_bind() {
137 use std::net::TcpListener as StdTcpListener;
138
139 let listener = StdTcpListener::bind("127.0.0.1:0").unwrap();
141 let port = listener.local_addr().unwrap().port();
142 drop(listener);
143
144 let server_handle = tokio::spawn(async move {
146 match tokio::time::timeout(std::time::Duration::from_millis(100), start_tcp_server(port, None)).await {
148 Ok(Ok(())) => {} Ok(Err(_)) => {} Err(_) => {} }
152 });
153
154 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
156
157 match tokio::time::timeout(
159 std::time::Duration::from_millis(50),
160 tokio::net::TcpStream::connect(format!("127.0.0.1:{port}")),
161 )
162 .await
163 {
164 Ok(Ok(_)) => {
165 }
167 _ => {
168 }
170 }
171
172 server_handle.abort();
174 }
175
176 #[tokio::test]
177 async fn test_tcp_server_invalid_port() {
178 let result = tokio::time::timeout(std::time::Duration::from_millis(100), start_tcp_server(80, None)).await;
181
182 match result {
183 Ok(Err(_)) => {
184 }
186 Ok(Ok(())) => {
187 panic!("Should not be able to bind to port 80 without privileges");
188 }
189 Err(_) => {
190 }
193 }
194 }
195
196 #[tokio::test]
197 async fn test_service_creation() {
198 let (service, _socket) = LspService::new(|client| RumdlLanguageServer::new(client, None));
200
201 drop(service);
204 }
205
206 #[tokio::test]
207 async fn test_multiple_tcp_connections() {
208 use std::net::TcpListener as StdTcpListener;
209
210 let listener = StdTcpListener::bind("127.0.0.1:0").unwrap();
212 let port = listener.local_addr().unwrap().port();
213 drop(listener);
214
215 let server_handle = tokio::spawn(async move {
217 let _ = tokio::time::timeout(std::time::Duration::from_millis(500), start_tcp_server(port, None)).await;
218 });
219
220 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
222
223 let mut handles = vec![];
225 for _ in 0..3 {
226 let handle = tokio::spawn(async move {
227 match tokio::time::timeout(
228 std::time::Duration::from_millis(100),
229 tokio::net::TcpStream::connect(format!("127.0.0.1:{port}")),
230 )
231 .await
232 {
233 Ok(Ok(_stream)) => {
234 true
236 }
237 _ => false,
238 }
239 });
240 handles.push(handle);
241 }
242
243 for handle in handles {
245 let _ = handle.await;
246 }
247
248 server_handle.abort();
250 }
251
252 #[test]
253 fn test_logging_initialization() {
254 let _info_level = log::Level::Info;
260 }
261}