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 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
35pub 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 !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) = ¶ms.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
141async 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(¶ms) {
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) = ¶ms.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
229async fn initialized(_: &mut State<'_>, _: lsp::InitializedParams) -> Result<()> {
231 tracing::info!("Initialized");
232 Ok(())
233}
234
235async fn goto_definition(
237 s: &mut State<'_>,
238 params: lsp::GotoDefinitionParams,
239) -> Result<Option<lsp::GotoDefinitionResponse>> {
240 let position = s
241 .goto_definition(
242 ¶ms.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
250async fn completion(
252 state: &mut State<'_>,
253 params: lsp::CompletionParams,
254) -> Result<Option<lsp::CompletionResponse>> {
255 let Some(results) = state.complete(
256 ¶ms.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
266async fn formatting(
268 state: &mut State<'_>,
269 params: lsp::DocumentFormattingParams,
270) -> Result<Option<::rust_alloc::vec::Vec<lsp::TextEdit>>> {
271 state
272 .format(¶ms.text_document.uri)
273 .map(|option| option.map(|formatted| vec![formatted]))
274}
275
276async 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(¶ms.text_document.uri, ¶ms.range)
283 .map(|option| option.map(|formatted| vec![formatted]))
284}
285
286async 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
314async 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(¶ms.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
342async fn did_close_text_document(
344 s: &mut State<'_>,
345 params: lsp::DidCloseTextDocumentParams,
346) -> Result<()> {
347 s.workspace.remove(¶ms.text_document.uri)?;
348 s.rebuild_interest();
349 Ok(())
350}
351
352async fn did_save_text_document(
354 s: &mut State<'_>,
355 _: lsp::DidSaveTextDocumentParams,
356) -> Result<()> {
357 s.rebuild_interest();
358 Ok(())
359}