rune/languageserver/
mod.rs

1//! Utility for building a language server.
2
3#![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
34/// Construct a new empty builder without any configured I/O.
35///
36/// In order to actually call build, the input and output streams must be
37/// configured using [`with_input`], and [`with_output`], or a method such as
38/// [`with_stdio`].
39///
40/// [`with_input`]: Builder::with_input
41/// [`with_output`]: Builder::with_output
42/// [`with_stdio`]: Builder::with_stdio
43///
44/// # Examples
45///
46/// ```no_run
47/// use rune::Context;
48/// use rune::languageserver;
49///
50/// let context = Context::with_default_modules()?;
51///
52/// let languageserver = languageserver::builder()
53///     .with_context(context)
54///     .with_stdio()
55///     .build()?;
56///
57/// # Ok::<_, rune::support::Error>(())
58/// ```
59pub fn builder() -> Builder<Unset, Unset> {
60    Builder {
61        input: Unset,
62        output: Unset,
63        context: None,
64        options: None,
65    }
66}
67
68/// A builder for a language server.
69///
70/// See [`builder()`] for more details.
71pub struct Builder<I, O> {
72    input: I,
73    output: O,
74    context: Option<Context>,
75    options: Option<Options>,
76}
77
78/// Unset placeholder I/O types for language server.
79///
80/// These must be replaced in order to actually construct a language server.
81///
82/// See [`builder()`] for more details.
83pub struct Unset;
84
85impl<I, O> Builder<I, O> {
86    /// Associate the specified input with the builder.
87    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    /// Associate the specified output with the builder.
100    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    /// Associate [`Stdin`] and [`Stdout`] as the input and output of the
113    /// builder.
114    #[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    /// Associate the specified context with the builder.
121    ///
122    /// If none is specified, a default context will be constructed.
123    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    /// Associate the specified options with the builder.
133    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    /// Build a new language server using the provided options.
143    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
172/// The instance of a language server, as constructed through [`builder()`].
173pub 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    /// Run a language server.
186    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 server is not initialized, reject incoming requests.
224                    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) = &params.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
305/// Initialize the language state.
306fn 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(&params) {
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) = &params.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
387/// Handle initialized notification.
388fn initialized(_: &mut State<'_>, _: lsp::InitializedParams) -> Result<()> {
389    tracing::info!("Initialized");
390    Ok(())
391}
392
393/// Handle initialized notification.
394fn goto_definition(
395    s: &mut State<'_>,
396    params: lsp::GotoDefinitionParams,
397) -> Result<Option<lsp::GotoDefinitionResponse>> {
398    let position = s.goto_definition(
399        &params.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
406/// Handle initialized notification.
407fn completion(
408    state: &mut State<'_>,
409    params: lsp::CompletionParams,
410) -> Result<Option<lsp::CompletionResponse>> {
411    let Some(results) = state.complete(
412        &params.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
422/// Handle formatting request.
423fn formatting(
424    state: &mut State<'_>,
425    params: lsp::DocumentFormattingParams,
426) -> Result<Option<rust_alloc::vec::Vec<lsp::TextEdit>>> {
427    state
428        .format(&params.text_document.uri)
429        .map(|option| option.map(|formatted| vec![formatted]))
430}
431
432/// Handle formatting request.
433fn range_formatting(
434    state: &mut State<'_>,
435    params: lsp::DocumentRangeFormattingParams,
436) -> Result<Option<rust_alloc::vec::Vec<lsp::TextEdit>>> {
437    state
438        .range_format(&params.text_document.uri, &params.range)
439        .map(|option| option.map(|formatted| vec![formatted]))
440}
441
442/// Handle open text document.
443fn 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
467/// Handle open text document.
468fn 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(&params.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
497/// Handle open text document.
498fn did_close_text_document(
499    s: &mut State<'_>,
500    params: lsp::DidCloseTextDocumentParams,
501) -> Result<()> {
502    s.workspace.remove(&params.text_document.uri)?;
503    s.rebuild_interest();
504    Ok(())
505}
506
507/// Handle saving of text documents.
508fn did_save_text_document(s: &mut State<'_>, _: lsp::DidSaveTextDocumentParams) -> Result<()> {
509    s.rebuild_interest();
510    Ok(())
511}