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