1use std::str::FromStr;
2use std::sync::{Arc, Mutex};
3
4use dada_compiler::{Compiler, Fork, RealFs};
5use dada_ir_ast::diagnostic::{Diagnostic, DiagnosticLabel, Level};
6use dada_ir_ast::inputs::SourceFile;
7use dada_ir_ast::span::{AbsoluteOffset, AbsoluteSpan};
8use dada_util::{Fallible, Map, Set, bail};
9use lsp::{Editor, Lsp, LspFork};
10use lsp_types::{
11 DidChangeTextDocumentParams, DidOpenTextDocumentParams, HoverProviderCapability, MessageType,
12 OneOf, PublishDiagnosticsParams, TextDocumentContentChangeEvent, TextDocumentItem,
13 TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, Uri,
14 VersionedTextDocumentIdentifier,
15};
16use lsp_types::{InitializeParams, ServerCapabilities};
17
18use salsa::{Database, Setter};
19use url::Url;
20
21mod lsp;
22
23fn main() -> Fallible<()> {
24 Server::run()
25}
26
27struct Server {
28 db: Compiler,
29 diagnostics: Arc<Mutex<EditorDiagnostics>>,
30}
31
32#[derive(Default)]
34struct EditorDiagnostics {
35 has_published_diagnostics: Set<SourceFile>,
37}
38
39impl lsp::Lsp for Server {
40 type Fork = ServerFork;
41
42 fn new(_params: InitializeParams) -> Fallible<Self> {
43 Ok(Server {
44 db: Compiler::new(RealFs::new(), None),
45 diagnostics: Default::default(),
46 })
47 }
48
49 fn server_capabilities(&mut self) -> Fallible<ServerCapabilities> {
50 Ok(ServerCapabilities {
51 hover_provider: Some(HoverProviderCapability::Simple(true)),
52 text_document_sync: Some(TextDocumentSyncCapability::Options(
53 TextDocumentSyncOptions {
54 open_close: Some(true),
55 change: Some(TextDocumentSyncKind::FULL),
56 will_save: None,
57 will_save_wait_until: None,
58 save: None,
59 },
60 )),
61 definition_provider: Some(OneOf::Left(true)),
62 ..ServerCapabilities::default()
63 })
64 }
65
66 fn server_info(&mut self) -> Fallible<Option<lsp_types::ServerInfo>> {
67 Ok(None)
68 }
69
70 fn fork(&mut self) -> Self::Fork {
71 ServerFork {
72 db: self.db.fork(),
73 diagnostics: self.diagnostics.clone(),
74 }
75 }
76
77 fn did_open(
78 &mut self,
79 editor: &mut dyn Editor<Self>,
80 params: DidOpenTextDocumentParams,
81 ) -> Fallible<()> {
82 let DidOpenTextDocumentParams { text_document } = params;
83 let TextDocumentItem {
84 uri,
85 language_id: _,
86 version: _,
87 text,
88 } = text_document;
89
90 let source_file = self.db.open_source_file(uri.as_str(), Ok(text))?;
91
92 editor.show_message(MessageType::INFO, format!("did open {}", uri.as_str()))?;
93
94 editor.spawn(ServerFork::check_all_task(source_file));
95
96 Ok(())
97 }
98
99 fn did_change(
100 &mut self,
101 editor: &mut dyn Editor<Self>,
102 params: DidChangeTextDocumentParams,
103 ) -> Fallible<()> {
104 let DidChangeTextDocumentParams {
105 text_document: VersionedTextDocumentIdentifier { uri, version: _ },
106 content_changes,
107 } = params;
108 let uri_str = uri.as_str();
109
110 let source_file = self.db.get_previously_opened_source_file(uri_str)?;
111
112 for TextDocumentContentChangeEvent {
113 range,
114 range_length: _,
115 text,
116 } in content_changes
117 {
118 match range {
119 Some(_) => {
120 bail!("we requested full content change events");
123 }
124 None => {
125 let _old_contents = source_file.set_contents(&mut self.db).to(Ok(text));
126 }
127 }
128 }
129
130 editor.show_message(MessageType::INFO, format!("did change {uri_str}"))?;
131
132 editor.spawn(ServerFork::check_all_task(source_file));
133
134 Ok(())
135 }
136
137 fn hover(
138 &mut self,
139 _editor: &mut dyn Editor<Self>,
140 params: lsp_types::HoverParams,
141 ) -> Fallible<Option<lsp_types::Hover>> {
142 let lsp_types::HoverParams {
143 text_document_position_params:
144 lsp_types::TextDocumentPositionParams {
145 text_document: lsp_types::TextDocumentIdentifier { uri },
146 position,
147 },
148 work_done_progress_params: _,
149 } = params;
150
151 let source_file = self.db.get_previously_opened_source_file(uri.as_str())?;
153
154 let line = position.line as usize;
156 let character = position.character as usize;
157
158 let line_starts = source_file.line_starts(&self.db);
160
161 if line >= line_starts.len() - 1 {
163 return Ok(None);
164 }
165
166 let line_start = line_starts[line];
168
169 let offset = AbsoluteOffset::from(line_start.as_usize() + character);
171
172 let span = AbsoluteSpan {
174 source_file,
175 start: offset,
176 end: offset,
177 };
178
179 self.db.attach(|db| {
181 if let Some(type_str) = dada_probe::probe_expression_type(db, span) {
182 return Ok(Some(lsp_types::Hover {
184 contents: lsp_types::HoverContents::Markup(lsp_types::MarkupContent {
185 kind: lsp_types::MarkupKind::Markdown,
186 value: format!("Type: `{type_str}`"),
187 }),
188 range: None,
189 }));
190 }
191
192 Ok(None)
193 })
194 }
195}
196
197struct ServerFork {
198 db: Fork<Compiler>,
199 diagnostics: Arc<Mutex<EditorDiagnostics>>,
200}
201
202impl LspFork for ServerFork {
203 fn fork(&self) -> Self {
204 ServerFork {
205 db: self.db.fork(),
206 diagnostics: self.diagnostics.clone(),
207 }
208 }
209}
210
211type CheckAllTask = Box<dyn FnOnce(&ServerFork, &mut dyn Editor<Server>) -> Fallible<()> + Send>;
212
213impl ServerFork {
214 fn check_all_task(source_file: SourceFile) -> CheckAllTask {
215 Box::new(move |this, editor| this.check_all(editor, source_file))
216 }
217
218 fn check_all(&self, editor: &mut dyn Editor<Server>, source_file: SourceFile) -> Fallible<()> {
219 let new_diagnostics = self.db.check_all(source_file);
220 self.diagnostics.lock().unwrap().reconcile_diagnostics(
221 &self.db,
222 editor,
223 new_diagnostics,
224 )?;
225 Ok(())
226 }
227}
228
229impl EditorDiagnostics {
230 fn reconcile_diagnostics(
231 &mut self,
232 db: &Compiler,
233 editor: &mut dyn Editor<Server>,
234 diagnostics: Vec<&Diagnostic>,
235 ) -> Fallible<()> {
236 let mut new_diagnostics: Map<SourceFile, Vec<Diagnostic>> = Map::default();
237
238 for diagnostic in diagnostics {
240 new_diagnostics
241 .entry(diagnostic.span.source_file)
242 .or_default()
243 .push(diagnostic.clone());
244 }
245
246 for (&source_file, diagnostics) in &new_diagnostics {
248 editor.publish_diagnostics(PublishDiagnosticsParams {
249 uri: Self::lsp_uri(source_file.url(db)),
250 diagnostics: diagnostics
251 .iter()
252 .map(|d| Self::lsp_diagnostic(db, d))
253 .collect(),
254 version: None,
255 })?;
256
257 self.has_published_diagnostics.insert(source_file);
259 }
260
261 let no_longer_have_diagnostics: Vec<SourceFile> = self
263 .has_published_diagnostics
264 .iter()
265 .filter(|source| !new_diagnostics.contains_key(source))
266 .copied()
267 .collect();
268 for source_file in no_longer_have_diagnostics {
269 editor.publish_diagnostics(PublishDiagnosticsParams {
270 uri: Self::lsp_uri(source_file.url(db)),
271 diagnostics: vec![],
272 version: None,
273 })?;
274 self.has_published_diagnostics.remove(&source_file);
275 }
276
277 Ok(())
278 }
279
280 fn lsp_diagnostic(db: &Compiler, diagnostic: &Diagnostic) -> lsp_types::Diagnostic {
281 let related_information = diagnostic
282 .labels
283 .iter()
284 .map(|label| Self::lsp_diagnostic_related_information(db, label))
285 .collect();
286
287 lsp_types::Diagnostic {
288 range: Self::lsp_range(db, diagnostic.span),
289 severity: Some(Self::lsp_severity(db, diagnostic.level)),
290 code: None,
291 code_description: None,
292 source: Some("Dada compiler".to_string()),
293 message: diagnostic.message.clone(),
294 related_information: Some(related_information),
295 tags: None,
296 data: None,
297 }
298 }
299
300 fn lsp_severity(_db: &Compiler, level: Level) -> lsp_types::DiagnosticSeverity {
301 match level {
302 Level::Note => lsp_types::DiagnosticSeverity::INFORMATION,
303 Level::Help => lsp_types::DiagnosticSeverity::HINT,
304 Level::Info => lsp_types::DiagnosticSeverity::INFORMATION,
305 Level::Warning => lsp_types::DiagnosticSeverity::WARNING,
306 Level::Error => lsp_types::DiagnosticSeverity::ERROR,
307 }
308 }
309
310 fn lsp_diagnostic_related_information(
311 db: &Compiler,
312 label: &DiagnosticLabel,
313 ) -> lsp_types::DiagnosticRelatedInformation {
314 let location = Self::lsp_location(db, label.span);
315 let message = label.message.clone();
316 lsp_types::DiagnosticRelatedInformation { location, message }
317 }
318
319 fn lsp_location(db: &Compiler, span: AbsoluteSpan) -> lsp_types::Location {
320 let uri = Self::lsp_uri(span.source_file.url(db));
321 let range = Self::lsp_range(db, span);
322 lsp_types::Location { uri, range }
323 }
324
325 fn lsp_range(db: &Compiler, span: AbsoluteSpan) -> lsp_types::Range {
326 let start = Self::lsp_position(db, span.source_file, span.start);
327 let end = Self::lsp_position(db, span.source_file, span.end);
328 lsp_types::Range { start, end }
329 }
330
331 fn lsp_position(
332 db: &Compiler,
333 source_file: SourceFile,
334 offset: AbsoluteOffset,
335 ) -> lsp_types::Position {
336 let (line, column) = source_file.line_col(db, offset);
337 lsp_types::Position {
338 line: line.as_u32(),
339 character: column.as_u32(),
340 }
341 }
342
343 fn lsp_uri(url: &Url) -> Uri {
344 Uri::from_str(url.as_str()).unwrap()
345 }
346}