1#![allow(clippy::too_many_arguments)]
4
5#[cfg(test)]
6mod tests;
7
8mod completion;
9mod connection;
10pub mod envelope;
11mod fs;
12mod state;
13mod url;
14
15use anyhow::Context as _;
16use lsp::notification::Notification;
17use lsp::request::Request;
18use serde::Deserialize;
19#[cfg(feature = "std")]
20use tokio::io::{self, Stdin, Stdout};
21use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt as _};
22use tokio::sync::Notify;
23
24use crate::alloc::String;
25use crate::languageserver::envelope::Code;
26use crate::languageserver::state::State;
27use crate::support::Result;
28use crate::workspace::MANIFEST_FILE;
29use crate::{Context, Options};
30
31use self::connection::Input;
32use self::state::StateEncoding;
33
34pub fn builder() -> Builder<Unset, Unset> {
60 Builder {
61 input: Unset,
62 output: Unset,
63 context: None,
64 options: None,
65 }
66}
67
68pub struct Builder<I, O> {
72 input: I,
73 output: O,
74 context: Option<Context>,
75 options: Option<Options>,
76}
77
78pub struct Unset;
84
85impl<I, O> Builder<I, O> {
86 pub fn with_input<T>(self, input: T) -> Builder<T, O>
88 where
89 T: Unpin + AsyncRead,
90 {
91 Builder {
92 input,
93 output: self.output,
94 context: self.context,
95 options: self.options,
96 }
97 }
98
99 pub fn with_output<T>(self, output: T) -> Builder<I, T>
101 where
102 T: Unpin + AsyncWrite,
103 {
104 Builder {
105 input: self.input,
106 output,
107 context: self.context,
108 options: self.options,
109 }
110 }
111
112 #[cfg(feature = "std")]
115 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
116 pub fn with_stdio(self) -> Builder<Stdin, Stdout> {
117 self.with_input(io::stdin()).with_output(io::stdout())
118 }
119
120 pub fn with_context(self, context: Context) -> Self {
124 Self {
125 input: self.input,
126 output: self.output,
127 context: Some(context),
128 options: self.options,
129 }
130 }
131
132 pub fn with_options(self, options: Options) -> Self {
134 Self {
135 input: self.input,
136 output: self.output,
137 context: self.context,
138 options: Some(options),
139 }
140 }
141
142 pub fn build(self) -> Result<LanguageServer<I, O>>
144 where
145 I: Unpin + AsyncRead,
146 O: Unpin + AsyncWrite,
147 {
148 let context = match self.context {
149 Some(context) => context,
150 None => Context::with_default_modules()?,
151 };
152
153 let options = match self.options {
154 Some(options) => options,
155 None => Options::from_default_env()?,
156 };
157
158 Ok(LanguageServer {
159 input: self.input,
160 output: self.output,
161 context,
162 options,
163 })
164 }
165}
166
167enum Language {
168 Rune,
169 Other,
170}
171
172pub struct LanguageServer<I, O> {
174 input: I,
175 output: O,
176 context: Context,
177 options: Options,
178}
179
180impl<I, O> LanguageServer<I, O>
181where
182 I: Unpin + AsyncRead,
183 O: Unpin + AsyncWrite,
184{
185 pub async fn run(mut self) -> Result<()> {
187 let mut input = Input::new(self.input);
188
189 let rebuild_notify = Notify::new();
190
191 let rebuild = rebuild_notify.notified();
192 tokio::pin!(rebuild);
193
194 let mut state = State::new(&rebuild_notify, self.context, self.options);
195 tracing::info!("Starting server");
196 state.rebuild()?;
197
198 let mut content = rust_alloc::vec::Vec::new();
199
200 while !state.is_stopped() {
201 tokio::select! {
202 _ = rebuild.as_mut() => {
203 tracing::info!("Rebuilding project");
204 state.rebuild()?;
205 rebuild.set(rebuild_notify.notified());
206 },
207 len = self.output.write(state.out.readable()), if !state.out.is_empty() => {
208 let len = len.context("writing output")?;
209 state.out.advance(len);
210
211 if state.out.is_empty() {
212 self.output.flush().await.context("flushing output")?;
213 }
214 },
215 frame = input.next(&mut content) => {
216 if !frame? {
217 break;
218 };
219
220 let incoming: envelope::IncomingMessage<'_> = serde_json::from_slice(&content)?;
221 tracing::trace!(?incoming);
222
223 if !state.is_initialized() && incoming.method != lsp::request::Initialize::METHOD {
225 state.out
226 .error(
227 incoming.id,
228 Code::InvalidRequest,
229 "Server not initialized",
230 None::<()>,
231 )?;
232
233 continue;
234 }
235
236 macro_rules! handle {
237 ($(req($req_ty:ty, $req_handle:ident)),* $(, notif($notif_ty:ty, $notif_handle:ident))* $(,)?) => {
238 match incoming.method {
239 $(<$req_ty>::METHOD => {
240 let params = <$req_ty as Request>::Params::deserialize(incoming.params)?;
241 let result = $req_handle(&mut state, params)?;
242 state.out.response(incoming.id, result)?;
243 })*
244 $(<$notif_ty>::METHOD => {
245 let params = <$notif_ty as Notification>::Params::deserialize(incoming.params)?;
246 let () = $notif_handle(&mut state, params)?;
247 })*
248 _ => {
249 state.out.log(
250 lsp::MessageType::INFO,
251 format!("Unhandled method `{}`", incoming.method),
252 )?;
253 state.out.method_not_found(incoming.id)?;
254 }
255 }
256 }
257 }
258
259 handle! {
260 req(lsp::request::Initialize, initialize),
261 req(lsp::request::Shutdown, shutdown),
262 req(lsp::request::GotoDefinition, goto_definition),
263 req(lsp::request::Completion, completion),
264 req(lsp::request::Formatting, formatting),
265 req(lsp::request::RangeFormatting, range_formatting),
266 notif(lsp::notification::DidOpenTextDocument, did_open_text_document),
267 notif(lsp::notification::DidChangeTextDocument, did_change_text_document),
268 notif(lsp::notification::DidCloseTextDocument, did_close_text_document),
269 notif(lsp::notification::DidSaveTextDocument, did_save_text_document),
270 notif(lsp::notification::Initialized, initialized),
271 }
272
273 content.clear();
274 },
275 }
276 }
277
278 while !state.out.is_empty() {
279 let len = self.output.write(state.out.readable()).await?;
280 state.out.advance(len);
281 }
282
283 Ok(())
284 }
285}
286
287fn is_utf8(params: &lsp::InitializeParams) -> bool {
288 let Some(general) = ¶ms.capabilities.general else {
289 return false;
290 };
291
292 let Some(encodings) = &general.position_encodings else {
293 return false;
294 };
295
296 for encoding in encodings {
297 if *encoding == lsp::PositionEncodingKind::UTF8 {
298 return true;
299 }
300 }
301
302 false
303}
304
305fn initialize(s: &mut State<'_>, params: lsp::InitializeParams) -> Result<lsp::InitializeResult> {
307 s.initialize();
308
309 s.out
310 .log(lsp::MessageType::INFO, "Starting language server")?;
311
312 let position_encoding;
313
314 if is_utf8(¶ms) {
315 s.encoding = StateEncoding::Utf8;
316 position_encoding = Some(lsp::PositionEncodingKind::UTF8);
317 } else {
318 position_encoding = None;
319 }
320
321 s.out.log(
322 lsp::MessageType::INFO,
323 format_args!("Using {} position encoding", s.encoding),
324 )?;
325
326 let capabilities = lsp::ServerCapabilities {
327 position_encoding,
328 text_document_sync: Some(lsp::TextDocumentSyncCapability::Kind(
329 lsp::TextDocumentSyncKind::INCREMENTAL,
330 )),
331 definition_provider: Some(lsp::OneOf::Left(true)),
332 completion_provider: Some(lsp::CompletionOptions {
333 all_commit_characters: None,
334 resolve_provider: Some(false),
335 trigger_characters: Some(vec![".".into(), "::".into()]),
336 work_done_progress_options: lsp::WorkDoneProgressOptions {
337 work_done_progress: None,
338 },
339 completion_item: Some(lsp::CompletionOptionsCompletionItem {
340 label_details_support: Some(true),
341 }),
342 }),
343 document_formatting_provider: Some(lsp::OneOf::Left(true)),
344 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
345 ..Default::default()
346 };
347
348 let server_info = lsp::ServerInfo {
349 name: String::try_from("Rune Language Server")?.into_std(),
350 version: None,
351 };
352
353 let mut rebuild = false;
354
355 #[allow(deprecated)]
356 if let Some(root_uri) = ¶ms.root_uri {
357 let mut manifest_uri = root_uri.clone();
358
359 if let Ok(mut path) = manifest_uri.path_segments_mut() {
360 path.push(MANIFEST_FILE);
361 }
362
363 if let Ok(manifest_path) = manifest_uri.to_file_path() {
364 if fs::is_file(&manifest_path)? {
365 tracing::trace!(?manifest_uri, ?manifest_path, "Activating workspace");
366 s.workspace.manifest_path = Some((manifest_uri, manifest_path));
367 rebuild = true;
368 }
369 }
370 }
371
372 if rebuild {
373 s.rebuild_interest();
374 }
375
376 Ok(lsp::InitializeResult {
377 capabilities,
378 server_info: Some(server_info),
379 })
380}
381
382fn shutdown(s: &mut State<'_>, _: ()) -> Result<()> {
383 s.stop();
384 Ok(())
385}
386
387fn initialized(_: &mut State<'_>, _: lsp::InitializedParams) -> Result<()> {
389 tracing::info!("Initialized");
390 Ok(())
391}
392
393fn goto_definition(
395 s: &mut State<'_>,
396 params: lsp::GotoDefinitionParams,
397) -> Result<Option<lsp::GotoDefinitionResponse>> {
398 let position = s.goto_definition(
399 ¶ms.text_document_position_params.text_document.uri,
400 params.text_document_position_params.position,
401 )?;
402
403 Ok(position.map(lsp::GotoDefinitionResponse::Scalar))
404}
405
406fn completion(
408 state: &mut State<'_>,
409 params: lsp::CompletionParams,
410) -> Result<Option<lsp::CompletionResponse>> {
411 let Some(results) = state.complete(
412 ¶ms.text_document_position.text_document.uri,
413 params.text_document_position.position,
414 )?
415 else {
416 return Ok(None);
417 };
418
419 Ok(Some(lsp::CompletionResponse::Array(results.into_std())))
420}
421
422fn formatting(
424 state: &mut State<'_>,
425 params: lsp::DocumentFormattingParams,
426) -> Result<Option<rust_alloc::vec::Vec<lsp::TextEdit>>> {
427 state
428 .format(¶ms.text_document.uri)
429 .map(|option| option.map(|formatted| vec![formatted]))
430}
431
432fn range_formatting(
434 state: &mut State<'_>,
435 params: lsp::DocumentRangeFormattingParams,
436) -> Result<Option<rust_alloc::vec::Vec<lsp::TextEdit>>> {
437 state
438 .range_format(¶ms.text_document.uri, ¶ms.range)
439 .map(|option| option.map(|formatted| vec![formatted]))
440}
441
442fn did_open_text_document(s: &mut State<'_>, params: lsp::DidOpenTextDocumentParams) -> Result<()> {
444 let lagnuage = match params.text_document.language_id.as_str() {
445 "rune" => Language::Rune,
446 _ => Language::Other,
447 };
448
449 if s.workspace
450 .insert_source(
451 params.text_document.uri.clone(),
452 params.text_document.text.try_into()?,
453 lagnuage,
454 )?
455 .is_some()
456 {
457 tracing::warn!(
458 "opened text document `{}`, but it was already open!",
459 params.text_document.uri
460 );
461 }
462
463 s.rebuild_interest();
464 Ok(())
465}
466
467fn did_change_text_document(
469 s: &mut State<'_>,
470 params: lsp::DidChangeTextDocumentParams,
471) -> Result<()> {
472 let mut interest = false;
473
474 if let Some(source) = s.workspace.get_mut(¶ms.text_document.uri) {
475 for change in params.content_changes {
476 if let Some(range) = change.range {
477 source.modify_lsp_range(&s.encoding, range, &change.text)?;
478 } else {
479 source.modify_lsp_full_range(&change.text)?;
480 }
481 interest = true;
482 }
483 } else {
484 tracing::warn!(
485 "tried to modify `{}`, but it was not open!",
486 params.text_document.uri
487 );
488 }
489
490 if interest {
491 s.rebuild_interest();
492 }
493
494 Ok(())
495}
496
497fn did_close_text_document(
499 s: &mut State<'_>,
500 params: lsp::DidCloseTextDocumentParams,
501) -> Result<()> {
502 s.workspace.remove(¶ms.text_document.uri)?;
503 s.rebuild_interest();
504 Ok(())
505}
506
507fn did_save_text_document(s: &mut State<'_>, _: lsp::DidSaveTextDocumentParams) -> Result<()> {
509 s.rebuild_interest();
510 Ok(())
511}