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 lsp::notification::Notification;
16use lsp::request::Request;
17use serde::Deserialize;
18use tokio::sync::Notify;
19
20use crate::alloc::String;
21use crate::languageserver::connection::stdio;
22use crate::languageserver::envelope::Code;
23use crate::languageserver::state::State;
24use crate::support::Result;
25use crate::workspace::MANIFEST_FILE;
26use crate::{Context, Options};
27
28use self::state::StateEncoding;
29
30enum Language {
31    Rune,
32    Other,
33}
34
35/// Run a language server with the given options.
36pub async fn run(context: Context, options: Options) -> Result<()> {
37    let (mut input, output) = stdio()?;
38
39    let rebuild_notify = Notify::new();
40
41    let rebuild = rebuild_notify.notified();
42    tokio::pin!(rebuild);
43
44    let mut state = State::new(output, &rebuild_notify, context, options);
45    tracing::info!("Starting server");
46    state.rebuild().await?;
47
48    while !state.is_stopped() {
49        tokio::select! {
50            _ = rebuild.as_mut() => {
51                tracing::info!("rebuilding project");
52                state.rebuild().await?;
53                rebuild.set(rebuild_notify.notified());
54            },
55            frame = input.next() => {
56                let frame = match frame? {
57                    Some(frame) => frame,
58                    None => break,
59                };
60
61                let incoming: envelope::IncomingMessage = serde_json::from_slice(frame.content)?;
62                tracing::trace!(?incoming);
63
64                // If server is not initialized, reject incoming requests.
65                if !state.is_initialized() && incoming.method != lsp::request::Initialize::METHOD {
66                    state.output
67                        .error(
68                            incoming.id,
69                            Code::InvalidRequest,
70                            "Server not initialized",
71                            None::<()>,
72                        )
73                        .await?;
74
75                    continue;
76                }
77
78                macro_rules! handle {
79                    ($(req($req_ty:ty, $req_handle:ident)),* $(, notif($notif_ty:ty, $notif_handle:ident))* $(,)?) => {
80                        match incoming.method.as_str() {
81                            $(<$req_ty>::METHOD => {
82                                let params = <$req_ty as Request>::Params::deserialize(incoming.params)?;
83                                let result = $req_handle(&mut state, params).await?;
84                                state.output.response(incoming.id, result).await?;
85                            })*
86                            $(<$notif_ty>::METHOD => {
87                                let params = <$notif_ty as Notification>::Params::deserialize(incoming.params)?;
88                                let () = $notif_handle(&mut state, params).await?;
89                            })*
90                            _ => {
91                                state.output
92                                .log(
93                                    lsp::MessageType::INFO,
94                                    format!("Unhandled method `{}`", incoming.method),
95                                )
96                                .await?;
97                                state.output.method_not_found(incoming.id).await?;
98                            }
99                        }
100                    }
101                }
102
103                handle! {
104                    req(lsp::request::Initialize, initialize),
105                    req(lsp::request::Shutdown, shutdown),
106                    req(lsp::request::GotoDefinition, goto_definition),
107                    req(lsp::request::Completion, completion),
108                    req(lsp::request::Formatting, formatting),
109                    req(lsp::request::RangeFormatting, range_formatting),
110                    notif(lsp::notification::DidOpenTextDocument, did_open_text_document),
111                    notif(lsp::notification::DidChangeTextDocument, did_change_text_document),
112                    notif(lsp::notification::DidCloseTextDocument, did_close_text_document),
113                    notif(lsp::notification::DidSaveTextDocument, did_save_text_document),
114                    notif(lsp::notification::Initialized, initialized),
115                }
116            },
117        }
118    }
119
120    Ok(())
121}
122
123fn is_utf8(params: &lsp::InitializeParams) -> bool {
124    let Some(general) = &params.capabilities.general else {
125        return false;
126    };
127
128    let Some(encodings) = &general.position_encodings else {
129        return false;
130    };
131
132    for encoding in encodings {
133        if *encoding == lsp::PositionEncodingKind::UTF8 {
134            return true;
135        }
136    }
137
138    false
139}
140
141/// Initialize the language state.
142async fn initialize(
143    s: &mut State<'_>,
144    params: lsp::InitializeParams,
145) -> Result<lsp::InitializeResult> {
146    s.initialize();
147
148    s.output
149        .log(lsp::MessageType::INFO, "Starting language server")
150        .await?;
151
152    let position_encoding;
153
154    if is_utf8(&params) {
155        s.encoding = StateEncoding::Utf8;
156        position_encoding = Some(lsp::PositionEncodingKind::UTF8);
157    } else {
158        position_encoding = None;
159    }
160
161    s.output
162        .log(
163            lsp::MessageType::INFO,
164            format_args!("Using {} position encoding", s.encoding),
165        )
166        .await?;
167
168    let capabilities = lsp::ServerCapabilities {
169        position_encoding,
170        text_document_sync: Some(lsp::TextDocumentSyncCapability::Kind(
171            lsp::TextDocumentSyncKind::INCREMENTAL,
172        )),
173        definition_provider: Some(lsp::OneOf::Left(true)),
174        completion_provider: Some(lsp::CompletionOptions {
175            all_commit_characters: None,
176            resolve_provider: Some(false),
177            trigger_characters: Some(vec![".".into(), "::".into()]),
178            work_done_progress_options: lsp::WorkDoneProgressOptions {
179                work_done_progress: None,
180            },
181            completion_item: Some(lsp::CompletionOptionsCompletionItem {
182                label_details_support: Some(true),
183            }),
184        }),
185        document_formatting_provider: Some(lsp::OneOf::Left(true)),
186        document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
187        ..Default::default()
188    };
189
190    let server_info = lsp::ServerInfo {
191        name: String::try_from("Rune Language Server")?.into_std(),
192        version: None,
193    };
194
195    let mut rebuild = false;
196
197    #[allow(deprecated)]
198    if let Some(root_uri) = &params.root_uri {
199        let mut manifest_uri = root_uri.clone();
200
201        if let Ok(mut path) = manifest_uri.path_segments_mut() {
202            path.push(MANIFEST_FILE);
203        }
204
205        if let Ok(manifest_path) = manifest_uri.to_file_path() {
206            if fs::is_file(&manifest_path).await? {
207                tracing::trace!(?manifest_uri, ?manifest_path, "Activating workspace");
208                s.workspace.manifest_path = Some((manifest_uri, manifest_path));
209                rebuild = true;
210            }
211        }
212    }
213
214    if rebuild {
215        s.rebuild_interest();
216    }
217
218    Ok(lsp::InitializeResult {
219        capabilities,
220        server_info: Some(server_info),
221    })
222}
223
224async fn shutdown(s: &mut State<'_>, _: ()) -> Result<()> {
225    s.stop();
226    Ok(())
227}
228
229/// Handle initialized notification.
230async fn initialized(_: &mut State<'_>, _: lsp::InitializedParams) -> Result<()> {
231    tracing::info!("Initialized");
232    Ok(())
233}
234
235/// Handle initialized notification.
236async fn goto_definition(
237    s: &mut State<'_>,
238    params: lsp::GotoDefinitionParams,
239) -> Result<Option<lsp::GotoDefinitionResponse>> {
240    let position = s
241        .goto_definition(
242            &params.text_document_position_params.text_document.uri,
243            params.text_document_position_params.position,
244        )
245        .await?;
246
247    Ok(position.map(lsp::GotoDefinitionResponse::Scalar))
248}
249
250/// Handle initialized notification.
251async fn completion(
252    state: &mut State<'_>,
253    params: lsp::CompletionParams,
254) -> Result<Option<lsp::CompletionResponse>> {
255    let Some(results) = state.complete(
256        &params.text_document_position.text_document.uri,
257        params.text_document_position.position,
258    )?
259    else {
260        return Ok(None);
261    };
262
263    Ok(Some(lsp::CompletionResponse::Array(results.into_std())))
264}
265
266/// Handle formatting request.
267async fn formatting(
268    state: &mut State<'_>,
269    params: lsp::DocumentFormattingParams,
270) -> Result<Option<::rust_alloc::vec::Vec<lsp::TextEdit>>> {
271    state
272        .format(&params.text_document.uri)
273        .map(|option| option.map(|formatted| vec![formatted]))
274}
275
276/// Handle formatting request.
277async fn range_formatting(
278    state: &mut State<'_>,
279    params: lsp::DocumentRangeFormattingParams,
280) -> Result<Option<::rust_alloc::vec::Vec<lsp::TextEdit>>> {
281    state
282        .range_format(&params.text_document.uri, &params.range)
283        .map(|option| option.map(|formatted| vec![formatted]))
284}
285
286/// Handle open text document.
287async fn did_open_text_document(
288    s: &mut State<'_>,
289    params: lsp::DidOpenTextDocumentParams,
290) -> Result<()> {
291    let lagnuage = match params.text_document.language_id.as_str() {
292        "rune" => Language::Rune,
293        _ => Language::Other,
294    };
295
296    if s.workspace
297        .insert_source(
298            params.text_document.uri.clone(),
299            params.text_document.text.try_into()?,
300            lagnuage,
301        )?
302        .is_some()
303    {
304        tracing::warn!(
305            "opened text document `{}`, but it was already open!",
306            params.text_document.uri
307        );
308    }
309
310    s.rebuild_interest();
311    Ok(())
312}
313
314/// Handle open text document.
315async fn did_change_text_document(
316    s: &mut State<'_>,
317    params: lsp::DidChangeTextDocumentParams,
318) -> Result<()> {
319    let mut interest = false;
320
321    if let Some(source) = s.workspace.get_mut(&params.text_document.uri) {
322        for change in params.content_changes {
323            if let Some(range) = change.range {
324                source.modify_lsp_range(&s.encoding, range, &change.text)?;
325                interest = true;
326            }
327        }
328    } else {
329        tracing::warn!(
330            "tried to modify `{}`, but it was not open!",
331            params.text_document.uri
332        );
333    }
334
335    if interest {
336        s.rebuild_interest();
337    }
338
339    Ok(())
340}
341
342/// Handle open text document.
343async fn did_close_text_document(
344    s: &mut State<'_>,
345    params: lsp::DidCloseTextDocumentParams,
346) -> Result<()> {
347    s.workspace.remove(&params.text_document.uri)?;
348    s.rebuild_interest();
349    Ok(())
350}
351
352/// Handle saving of text documents.
353async fn did_save_text_document(
354    s: &mut State<'_>,
355    _: lsp::DidSaveTextDocumentParams,
356) -> Result<()> {
357    s.rebuild_interest();
358    Ok(())
359}