1use std::collections::HashMap;
6use std::sync::Arc;
7
8use fml_check::Definitions;
9use fml_syntax::{FormatError, FormatOptions, Severity};
10use lsp_types::{
11 CompletionOptions, CompletionParams, DiagnosticSeverity, DidChangeTextDocumentParams,
12 DidCloseTextDocumentParams, DidOpenTextDocumentParams, DocumentFormattingParams,
13 DocumentSymbolParams, DocumentSymbolResponse, GotoDefinitionParams, GotoDefinitionResponse,
14 HoverParams, HoverProviderCapability, InitializeResult, Location, OneOf, Position,
15 PrepareRenameResponse, PublishDiagnosticsParams, ReferenceParams, RenameOptions, RenameParams,
16 SemanticTokensFullOptions, SemanticTokensOptions, SemanticTokensParams,
17 SemanticTokensServerCapabilities, ServerCapabilities, ServerInfo, SignatureHelpOptions,
18 SignatureHelpParams, TextDocumentPositionParams, TextDocumentSyncCapability,
19 TextDocumentSyncKind, TextEdit, Url, WorkDoneProgressOptions,
20};
21use serde::de::DeserializeOwned;
22use serde_json::{Value, json};
23
24use crate::document::Document;
25use crate::{completion, hover, navigate, semantic, signature, symbols};
26
27const METHOD_NOT_FOUND: i64 = -32601;
29const INVALID_PARAMS: i64 = -32602;
30const REQUEST_FAILED: i64 = -32803;
31const SERVER_NOT_INITIALIZED: i64 = -32002;
32
33pub type SharedDefinitions = Arc<dyn Definitions + Send + Sync>;
35
36pub struct Server {
38 definitions: SharedDefinitions,
39 documents: HashMap<Url, Document>,
40 initialized: bool,
41 shutdown: bool,
42 exited: bool,
43}
44
45impl Server {
46 pub fn new(definitions: SharedDefinitions) -> Self {
48 Self {
49 definitions,
50 documents: HashMap::new(),
51 initialized: false,
52 shutdown: false,
53 exited: false,
54 }
55 }
56
57 #[cfg(feature = "fhirschema")]
59 pub fn with_release(release: fml_check::Release) -> Self {
60 Self::new(Arc::new(fml_check::CoreDefinitions::new(release)))
61 }
62
63 pub fn set_definitions(&mut self, definitions: SharedDefinitions) -> Vec<Value> {
66 self.definitions = definitions;
67 let uris: Vec<Url> = self.documents.keys().cloned().collect();
68 uris.into_iter()
69 .filter_map(|uri| {
70 let document = self.documents.remove(&uri)?;
71 let document =
72 Document::new(document.text, document.version, self.definitions.as_ref());
73 let message = diagnostics(&uri, &document);
74 self.documents.insert(uri, document);
75 Some(message)
76 })
77 .collect()
78 }
79
80 pub fn exited(&self) -> bool {
82 self.exited
83 }
84
85 pub fn exit_code(&self) -> i32 {
87 if self.shutdown { 0 } else { 1 }
88 }
89
90 pub fn capabilities() -> ServerCapabilities {
92 ServerCapabilities {
93 text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::FULL)),
94 hover_provider: Some(HoverProviderCapability::Simple(true)),
95 completion_provider: Some(CompletionOptions {
96 trigger_characters: Some(vec![".".into(), ":".into()]),
97 ..CompletionOptions::default()
98 }),
99 signature_help_provider: Some(SignatureHelpOptions {
100 trigger_characters: Some(vec!["(".into(), ",".into()]),
101 retrigger_characters: None,
102 work_done_progress_options: WorkDoneProgressOptions::default(),
103 }),
104 definition_provider: Some(OneOf::Left(true)),
105 references_provider: Some(OneOf::Left(true)),
106 rename_provider: Some(OneOf::Right(RenameOptions {
107 prepare_provider: Some(true),
108 work_done_progress_options: WorkDoneProgressOptions::default(),
109 })),
110 document_symbol_provider: Some(OneOf::Left(true)),
111 document_formatting_provider: Some(OneOf::Left(true)),
112 semantic_tokens_provider: Some(
113 SemanticTokensServerCapabilities::SemanticTokensOptions(SemanticTokensOptions {
114 legend: semantic::legend(),
115 full: Some(SemanticTokensFullOptions::Bool(true)),
116 range: None,
117 work_done_progress_options: WorkDoneProgressOptions::default(),
118 }),
119 ),
120 ..ServerCapabilities::default()
121 }
122 }
123
124 pub fn handle(&mut self, message: &Value) -> Vec<Value> {
127 let Some(method) = message.get("method").and_then(Value::as_str) else {
128 return Vec::new();
130 };
131 let params = message.get("params").cloned().unwrap_or(Value::Null);
132 match message.get("id").cloned() {
133 Some(id) => vec![self.request(id, method, params)],
134 None => self.notification(method, params),
135 }
136 }
137
138 fn request(&mut self, id: Value, method: &str, params: Value) -> Value {
139 if method == "initialize" {
140 self.initialized = true;
141 let result = InitializeResult {
142 capabilities: Self::capabilities(),
143 server_info: Some(ServerInfo {
144 name: "fml-lsp".to_owned(),
145 version: Some(env!("CARGO_PKG_VERSION").to_owned()),
146 }),
147 };
148 return response(id, json!(result));
149 }
150 if !self.initialized {
151 return error(id, SERVER_NOT_INITIALIZED, "the server is not initialized");
152 }
153 let result = match method {
154 "shutdown" => {
155 self.shutdown = true;
156 Ok(Value::Null)
157 }
158 "textDocument/hover" => self.with::<HoverParams>(params, |s, p| {
159 let (document, offset) = s.at(&p.text_document_position_params)?;
160 Some(json!(hover::hover(document, offset)?))
161 }),
162 "textDocument/completion" => self.with::<CompletionParams>(params, |s, p| {
163 let (document, offset) = s.at(&p.text_document_position)?;
164 let list = completion::completion(document, s.definitions.as_ref(), offset)?;
165 Some(json!(list))
166 }),
167 "textDocument/signatureHelp" => self.with::<SignatureHelpParams>(params, |s, p| {
168 let (document, offset) = s.at(&p.text_document_position_params)?;
169 Some(json!(signature::signature_help(document, offset)?))
170 }),
171 "textDocument/definition" => self.with::<GotoDefinitionParams>(params, |s, p| {
172 let position = &p.text_document_position_params;
173 let (document, offset) = s.at(position)?;
174 let range = navigate::definition(document, offset)?;
175 Some(json!(GotoDefinitionResponse::Scalar(Location::new(
176 position.text_document.uri.clone(),
177 document.range(range),
178 ))))
179 }),
180 "textDocument/references" => self.with::<ReferenceParams>(params, |s, p| {
181 let position = &p.text_document_position;
182 let (document, offset) = s.at(position)?;
183 let (symbol, _) = navigate::symbol_at(document, offset)?;
184 let mut ranges = navigate::occurrences(document, &symbol);
185 if !p.context.include_declaration && !ranges.is_empty() {
186 ranges.remove(0);
187 }
188 let locations: Vec<Location> = ranges
189 .into_iter()
190 .map(|r| Location::new(position.text_document.uri.clone(), document.range(r)))
191 .collect();
192 Some(json!(locations))
193 }),
194 "textDocument/prepareRename" => {
195 self.with::<TextDocumentPositionParams>(params, |s, p| {
196 let (document, offset) = s.at(&p)?;
197 let (_, range) = navigate::symbol_at(document, offset)?;
198 Some(json!(PrepareRenameResponse::Range(document.range(range))))
199 })
200 }
201 "textDocument/rename" => match serde_json::from_value::<RenameParams>(params) {
202 Err(e) => Err((INVALID_PARAMS, e.to_string())),
203 Ok(p) => {
204 let position = &p.text_document_position;
205 match self.at(position) {
206 None => Ok(Value::Null),
207 Some((document, offset)) => navigate::rename(
208 document,
209 &position.text_document.uri,
210 offset,
211 &p.new_name,
212 )
213 .map(|edit| json!(edit))
214 .map_err(|problem| (REQUEST_FAILED, problem)),
215 }
216 }
217 },
218 "textDocument/documentSymbol" => self.with::<DocumentSymbolParams>(params, |s, p| {
219 let document = s.documents.get(&p.text_document.uri)?;
220 Some(json!(DocumentSymbolResponse::Nested(
221 symbols::document_symbols(document)
222 )))
223 }),
224 "textDocument/formatting" => self.with::<DocumentFormattingParams>(params, |s, p| {
225 let document = s.documents.get(&p.text_document.uri)?;
226 let options = FormatOptions {
227 indent_width: p.options.tab_size as usize,
228 use_tabs: !p.options.insert_spaces,
229 };
230 match fml_syntax::format(&document.text, &options) {
231 Ok(formatted) if formatted == document.text => Some(json!([])),
232 Ok(formatted) => {
233 let end = document.position(fml_syntax::TextSize::of(&document.text));
234 let range = lsp_types::Range::new(Position::new(0, 0), end);
235 Some(json!([TextEdit::new(range, formatted)]))
236 }
237 Err(FormatError::Syntax(_) | FormatError::Unstable(_)) => None,
239 }
240 }),
241 "textDocument/semanticTokens/full" => {
242 self.with::<SemanticTokensParams>(params, |s, p| {
243 let document = s.documents.get(&p.text_document.uri)?;
244 Some(json!(semantic::semantic_tokens(document)))
245 })
246 }
247 _ => Err((METHOD_NOT_FOUND, format!("unknown method `{method}`"))),
248 };
249 match result {
250 Ok(value) => response(id, value),
251 Err((code, message)) => error(id, code, &message),
252 }
253 }
254
255 fn with<P: DeserializeOwned>(
257 &mut self,
258 params: Value,
259 f: impl FnOnce(&Self, P) -> Option<Value>,
260 ) -> Result<Value, (i64, String)> {
261 let params: P =
262 serde_json::from_value(params).map_err(|e| (INVALID_PARAMS, e.to_string()))?;
263 Ok(f(self, params).unwrap_or(Value::Null))
264 }
265
266 fn at(
267 &self,
268 position: &TextDocumentPositionParams,
269 ) -> Option<(&Document, fml_syntax::TextSize)> {
270 let document = self.documents.get(&position.text_document.uri)?;
271 Some((document, document.offset(position.position)))
272 }
273
274 fn notification(&mut self, method: &str, params: Value) -> Vec<Value> {
275 match method {
276 "exit" => {
277 self.exited = true;
278 Vec::new()
279 }
280 "textDocument/didOpen" => {
281 let Ok(p) = serde_json::from_value::<DidOpenTextDocumentParams>(params) else {
282 return Vec::new();
283 };
284 self.open(
285 p.text_document.uri,
286 p.text_document.text,
287 p.text_document.version,
288 )
289 }
290 "textDocument/didChange" => {
291 let Ok(p) = serde_json::from_value::<DidChangeTextDocumentParams>(params) else {
292 return Vec::new();
293 };
294 let Some(change) = p.content_changes.into_iter().last() else {
296 return Vec::new();
297 };
298 self.open(p.text_document.uri, change.text, p.text_document.version)
299 }
300 "textDocument/didClose" => {
301 let Ok(p) = serde_json::from_value::<DidCloseTextDocumentParams>(params) else {
302 return Vec::new();
303 };
304 self.documents.remove(&p.text_document.uri);
305 vec![notification(
306 "textDocument/publishDiagnostics",
307 json!(PublishDiagnosticsParams::new(
308 p.text_document.uri,
309 Vec::new(),
310 None
311 )),
312 )]
313 }
314 _ => Vec::new(),
315 }
316 }
317
318 fn open(&mut self, uri: Url, text: String, version: i32) -> Vec<Value> {
319 let document = Document::new(text, version, self.definitions.as_ref());
320 let message = diagnostics(&uri, &document);
321 self.documents.insert(uri, document);
322 vec![message]
323 }
324}
325
326fn diagnostics(uri: &Url, document: &Document) -> Value {
327 let diagnostics = document
328 .diagnostics
329 .iter()
330 .map(|d| lsp_types::Diagnostic {
331 range: document.range(d.range),
332 severity: Some(match d.severity {
333 Severity::Error => DiagnosticSeverity::ERROR,
334 Severity::Warning => DiagnosticSeverity::WARNING,
335 }),
336 source: Some("fml".to_owned()),
337 message: d.message.clone(),
338 ..lsp_types::Diagnostic::default()
339 })
340 .collect();
341 notification(
342 "textDocument/publishDiagnostics",
343 json!(PublishDiagnosticsParams::new(
344 uri.clone(),
345 diagnostics,
346 Some(document.version)
347 )),
348 )
349}
350
351fn response(id: Value, result: Value) -> Value {
352 json!({ "jsonrpc": "2.0", "id": id, "result": result })
353}
354
355fn error(id: Value, code: i64, message: &str) -> Value {
356 json!({ "jsonrpc": "2.0", "id": id, "error": { "code": code, "message": message } })
357}
358
359fn notification(method: &str, params: Value) -> Value {
360 json!({ "jsonrpc": "2.0", "method": method, "params": params })
361}