Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / language-server / src / messages.rs
12 kB 294 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2024 The Gleam contributors 3 4use camino::Utf8PathBuf; 5use lsp::{DefinitionRequest, DidChangeWatchedFilesNotification, DidOpenTextDocumentNotification}; 6use lsp_types::{ 7 self as lsp, CodeActionRequest, CompletionRequest, DidChangeTextDocumentNotification, 8 DidCloseTextDocumentNotification, DidSaveTextDocumentNotification, DocumentFormattingRequest, 9 DocumentHighlightRequest, DocumentSymbolRequest, FoldingRangeRequest, HoverRequest, 10 PrepareRenameRequest, ReferencesRequest, RenameRequest, SignatureHelpRequest, 11 TextDocumentContentChangeEvent, TypeDefinitionRequest, WillRenameFilesRequest, 12}; 13use std::time::Duration; 14 15#[derive(Debug)] 16pub enum Message { 17 Request(lsp_server::RequestId, Request), 18 Notification(Notification), 19} 20 21#[derive(Debug)] 22pub enum Request { 23 Format(lsp::DocumentFormattingParams), 24 Hover(lsp::HoverParams), 25 GoToDefinition(lsp::DefinitionParams), 26 GoToTypeDefinition(lsp::TypeDefinitionParams), 27 Completion(lsp::CompletionParams), 28 CodeAction(lsp::CodeActionParams), 29 SignatureHelp(lsp::SignatureHelpParams), 30 DocumentSymbol(lsp::DocumentSymbolParams), 31 FoldingRange(lsp::FoldingRangeParams), 32 PrepareRename(lsp::PrepareRenameParams), 33 Rename(lsp::RenameParams), 34 FindReferences(lsp::ReferenceParams), 35 DocumentHighlight(lsp::DocumentHighlightParams), 36 RenameFiles(lsp::RenameFilesParams), 37} 38 39impl Request { 40 fn extract(request: lsp_server::Request) -> Option<Message> { 41 let id = request.id.clone(); 42 match request.method.as_str() { 43 "textDocument/formatting" => { 44 let params = cast_request::<DocumentFormattingRequest>(request); 45 Some(Message::Request(id, Request::Format(params))) 46 } 47 "textDocument/hover" => { 48 let params = cast_request::<HoverRequest>(request); 49 Some(Message::Request(id, Request::Hover(params))) 50 } 51 "textDocument/definition" => { 52 let params = cast_request::<DefinitionRequest>(request); 53 Some(Message::Request(id, Request::GoToDefinition(params))) 54 } 55 "textDocument/completion" => { 56 let params = cast_request::<CompletionRequest>(request); 57 Some(Message::Request(id, Request::Completion(params))) 58 } 59 "textDocument/codeAction" => { 60 let params = cast_request::<CodeActionRequest>(request); 61 Some(Message::Request(id, Request::CodeAction(params))) 62 } 63 "textDocument/signatureHelp" => { 64 let params = cast_request::<SignatureHelpRequest>(request); 65 Some(Message::Request(id, Request::SignatureHelp(params))) 66 } 67 "textDocument/documentSymbol" => { 68 let params = cast_request::<DocumentSymbolRequest>(request); 69 Some(Message::Request(id, Request::DocumentSymbol(params))) 70 } 71 "textDocument/foldingRange" => { 72 let params = cast_request::<FoldingRangeRequest>(request); 73 Some(Message::Request(id, Request::FoldingRange(params))) 74 } 75 "textDocument/rename" => { 76 let params = cast_request::<RenameRequest>(request); 77 Some(Message::Request(id, Request::Rename(params))) 78 } 79 "textDocument/prepareRename" => { 80 let params = cast_request::<PrepareRenameRequest>(request); 81 Some(Message::Request(id, Request::PrepareRename(params))) 82 } 83 "textDocument/typeDefinition" => { 84 let params = cast_request::<TypeDefinitionRequest>(request); 85 Some(Message::Request(id, Request::GoToTypeDefinition(params))) 86 } 87 "textDocument/references" => { 88 let params = cast_request::<ReferencesRequest>(request); 89 Some(Message::Request(id, Request::FindReferences(params))) 90 } 91 "textDocument/documentHighlight" => { 92 let params = cast_request::<DocumentHighlightRequest>(request); 93 Some(Message::Request(id, Request::DocumentHighlight(params))) 94 } 95 "workspace/willRenameFiles" => { 96 let params = cast_request::<WillRenameFilesRequest>(request); 97 Some(Message::Request(id, Request::RenameFiles(params))) 98 } 99 _ => None, 100 } 101 } 102} 103 104#[derive(Debug)] 105pub enum Notification { 106 /// A Gleam file has been modified in memory, and the new text is provided. 107 SourceFileChangedInMemory { path: Utf8PathBuf, text: String }, 108 /// A Gleam file has been opened in the editor. 109 SourceFileOpened { path: Utf8PathBuf, text: String }, 110 /// A Gleam file has been closed in the editor. 111 SourceFileClosed { path: Utf8PathBuf }, 112 /// A gleam file has been saved to disk 113 SourceFileSaved { path: Utf8PathBuf }, 114 /// gleam.toml has changed. 115 ConfigFileChanged { path: Utf8PathBuf }, 116 /// It's time to compile all open projects. 117 CompilePlease, 118} 119 120impl Notification { 121 fn extract(notification: lsp_server::Notification) -> Option<Message> { 122 match notification.method.as_str() { 123 "textDocument/didOpen" => { 124 let params = cast_notification::<DidOpenTextDocumentNotification>(notification); 125 let notification = Notification::SourceFileOpened { 126 path: super::path(&params.text_document.uri), 127 text: params.text_document.text, 128 }; 129 Some(Message::Notification(notification)) 130 } 131 "textDocument/didChange" => { 132 let params = cast_notification::<DidChangeTextDocumentNotification>(notification); 133 let TextDocumentContentChangeEvent::TextDocumentContentChangeWholeDocument(change) = 134 params.content_changes.into_iter().next_back()? 135 else { 136 return None; 137 }; 138 let notification = Notification::SourceFileChangedInMemory { 139 path: super::path(&params.text_document.text_document_identifier.uri), 140 text: change.text, 141 }; 142 Some(Message::Notification(notification)) 143 } 144 "textDocument/didSave" => { 145 let params = cast_notification::<DidSaveTextDocumentNotification>(notification); 146 let notification = Notification::SourceFileSaved { 147 path: super::path(&params.text_document.uri), 148 }; 149 Some(Message::Notification(notification)) 150 } 151 "textDocument/didClose" => { 152 let params = cast_notification::<DidCloseTextDocumentNotification>(notification); 153 let notification = Notification::SourceFileClosed { 154 path: super::path(&params.text_document.uri), 155 }; 156 Some(Message::Notification(notification)) 157 } 158 "workspace/didChangeWatchedFiles" => { 159 let params = cast_notification::<DidChangeWatchedFilesNotification>(notification); 160 let notification = Notification::ConfigFileChanged { 161 path: super::path(&params.changes.into_iter().next_back()?.uri), 162 }; 163 Some(Message::Notification(notification)) 164 } 165 _ => None, 166 } 167 } 168} 169 170pub enum Next { 171 MorePlease, 172 Handle(Vec<Message>), 173 Stop, 174} 175 176/// The message buffer pulls messages from the client until one of the following 177/// happens: 178/// - A shutdown request is received. 179/// - A short pause in messages is detected, indicating the programmer has 180/// stopped typing for a moment and would benefit from feedback. 181/// - A request type message is received, which requires an immediate response. 182/// 183pub struct MessageBuffer { 184 messages: Vec<Message>, 185} 186 187impl MessageBuffer { 188 pub fn new() -> Self { 189 Self { 190 messages: Vec::new(), 191 } 192 } 193 194 pub fn receive(&mut self, conn: &lsp_server::Connection) -> Next { 195 let pause = Duration::from_millis(100); 196 197 // If the buffer is empty, wait indefinitely for the first message. 198 // If the buffer is not empty, wait for a short time to see if more messages are 199 // coming before processing the ones we have. 200 let message = if self.messages.is_empty() { 201 Some(conn.receiver.recv().expect("Receiving LSP message")) 202 } else { 203 conn.receiver.recv_timeout(pause).ok() 204 }; 205 206 // If have have not received a message then it means there is a pause in the 207 // messages from the client, implying the programmer has stopped typing. Process 208 // the currently enqueued messages. 209 let message = match message { 210 Some(message) => message, 211 None => { 212 // A compile please message it added in the instance of this 213 // pause of activity so that the client gets feedback on the 214 // state of the code as it is now. 215 self.push_compile_please_message(); 216 return Next::Handle(self.take_messages()); 217 } 218 }; 219 220 match message { 221 lsp_server::Message::Request(r) if self.shutdown(conn, &r) => Next::Stop, 222 lsp_server::Message::Request(r) => self.request(r), 223 lsp_server::Message::Response(r) => self.response(r), 224 lsp_server::Message::Notification(n) => self.notification(n), 225 } 226 } 227 228 fn request(&mut self, r: lsp_server::Request) -> Next { 229 let Some(message) = Request::extract(r) else { 230 return Next::MorePlease; 231 }; 232 233 // Compile the code prior to attempting to process the response, to 234 // ensure that the response is based on the latest code. 235 self.push_compile_please_message(); 236 self.messages.push(message); 237 Next::Handle(self.take_messages()) 238 } 239 240 fn notification(&mut self, n: lsp_server::Notification) -> Next { 241 // A new notification telling us that an edit has been made, or 242 // something along those lines. 243 if let Some(message) = Notification::extract(n) { 244 self.messages.push(message); 245 } 246 // Ask for more messages (or a pause), at which point we'll start processing. 247 Next::MorePlease 248 } 249 250 fn response(&mut self, _: lsp_server::Response) -> Next { 251 // We do not use or expect responses from the client currently. 252 Next::MorePlease 253 } 254 255 /// Add a `CompilePlease` message which will prompt the engine to compile 256 /// the projects. 257 /// 258 fn push_compile_please_message(&mut self) { 259 let message = Notification::CompilePlease; 260 let value = Message::Notification(message); 261 self.messages.push(value); 262 } 263 264 fn take_messages(&mut self) -> Vec<Message> { 265 std::mem::take(&mut self.messages) 266 } 267 268 fn shutdown( 269 &mut self, 270 connection: &lsp_server::Connection, 271 request: &lsp_server::Request, 272 ) -> bool { 273 connection.handle_shutdown(request).expect("LSP shutdown") 274 } 275} 276 277fn cast_request<R>(request: lsp_server::Request) -> R::Params 278where 279 R: lsp::Request, 280 R::Params: serde::de::DeserializeOwned, 281{ 282 let (_, params) = request.extract(R::METHOD.as_str()).expect("cast request"); 283 params 284} 285 286fn cast_notification<N>(notification: lsp_server::Notification) -> N::Params 287where 288 N: lsp::Notification, 289 N::Params: serde::de::DeserializeOwned, 290{ 291 notification 292 .extract::<N::Params>(N::METHOD.as_str()) 293 .expect("cast notification") 294}