1#![allow(clippy::cognitive_complexity)]
2
3use std::{
4 io::Write,
5 ops::ControlFlow,
6 path::{Path, PathBuf},
7};
8
9use crate::{
10 ClientExt, NewArchiveParams, ProgressCallbackServer, StandaloneExportParams, UriParams,
11 annotations::{into_diagnostic, to_diagnostic},
12 documents::LSPDocument,
13 state::{LSPState, UrlOrFile},
14};
15
16use super::{FLAMSLSPServer, ServerWrapper};
17use async_lsp::{
18 ClientSocket, LanguageClient, LanguageServer, ResponseError,
19 lsp_types::{self as lsp},
20};
21use flams_math_archives::{
22 MathArchive,
23 backend::{GlobalBackend, LocalBackend},
24 formats::FormatOrTargets,
25 utils::path_ext::RelPath,
26};
27use flams_stex::quickparse::stex::{AnnotIter, STeXAnnot, STeXDiagnostic};
28use flams_system::{TokioEngine, backend::backend};
29use flams_utils::{
30 parsing::{Needle, SourceParser, StrParser},
31 prelude::TreeChildIter,
32 sourcerefs::{ByteOffset, LSPLineCol, PositionConverter, StringPosition, StringRange},
33 unwrap,
34};
35use ftml_ontology::{
36 narrative::{
37 DataRef,
38 elements::{
39 DocumentElementRef,
40 problems::{GradingNote, Solutions},
41 },
42 },
43 utils::{Css, RefTree},
44};
45use ftml_uris::{DocumentUri, IsDomainUri, IsNarrativeUri, SymbolUri, UriWithArchive, UriWithPath};
46use futures::{FutureExt, TryFutureExt, future::BoxFuture};
47
48macro_rules! impl_request {
49 ($name:ident = $struct:ident) => {
50 fn $name(
51 &mut self,
52 params: <lsp::request::$struct as lsp::request::Request>::Params,
53 ) -> Res<<lsp::request::$struct as lsp::request::Request>::Result> {
54 tracing::info!("LSP: {params:?}");
55 Box::pin(std::future::ready(Err(ResponseError::new(
56 async_lsp::ErrorCode::METHOD_NOT_FOUND,
57 format!(
58 "No such method: {}",
59 <lsp::request::$struct as lsp::request::Request>::METHOD
60 ),
61 ))))
62 }
63 };
64 (? $name:ident = $struct:ident => ($default:expr)) => {
65 fn $name(
66 &mut self,
67 params: <lsp::request::$struct as lsp::request::Request>::Params,
68 ) -> Res<<lsp::request::$struct as lsp::request::Request>::Result> {
69 tracing::info!("LSP: {params:?}");
70 Box::pin(std::future::ready(Ok($default)))
71 }
72 };
73 (! $name:ident = $struct:ident => ($default:expr)) => {
74 fn $name(
75 &mut self,
76 params: <lsp::request::$struct as lsp::request::Request>::Params,
77 ) -> Res<<lsp::request::$struct as lsp::request::Request>::Result> {
78 tracing::debug!("LSP: {params:?}");
79 Box::pin(std::future::ready(Ok($default)))
80 }
81 };
82}
83
84macro_rules! impl_notification {
85 (! $name:ident = $struct:ident) => {
86 fn $name(
87 &mut self,
88 params: <lsp::notification::$struct as lsp::notification::Notification>::Params,
89 ) -> Self::NotifyResult {
90 tracing::debug!("LSP: {params:?}");
91 ControlFlow::Continue(())
92 }
93 };
94 ($name:ident = $struct:ident) => {
95 fn $name(
96 &mut self,
97 params: <lsp::notification::$struct as lsp::notification::Notification>::Params,
98 ) -> Self::NotifyResult {
99 tracing::info!("LSP: {params:?}");
100 ControlFlow::Break(Err(async_lsp::Error::Routing(format!(
101 "Unhandled notification: {}",
102 <lsp::notification::$struct as lsp::notification::Notification>::METHOD,
103 ))))
104 }
105 };
106}
107
108#[inline]
109fn fut<T: Send + 'static>(f: impl FnOnce() -> Result<T, String> + Send + 'static) -> Res<T> {
110 Box::pin(tokio::task::spawn_blocking(f).map_ok_or_else(
111 |e| {
112 Err(ResponseError::new(
113 async_lsp::ErrorCode::REQUEST_FAILED,
114 e.to_string(),
115 ))
116 },
117 |r| r.map_err(|e| ResponseError::new(async_lsp::ErrorCode::REQUEST_FAILED, e)),
118 ))
119}
120fn block<T: Send + 'static>(
121 f: impl FnOnce() -> Result<T, String> + Send + 'static,
122) -> impl std::future::Future<Output = Result<T, String>> {
123 tokio::task::spawn_blocking(f).map_ok_or_else(|e| Err(e.to_string()), |r| r)
124}
125fn wrap_fut<T: Send + 'static>(
126 f: impl std::future::Future<Output = Result<T, String>> + Send + 'static,
127) -> Res<T> {
128 Box::pin(f.map_err(|e| ResponseError::new(async_lsp::ErrorCode::REQUEST_FAILED, e)))
129}
130
131impl<T: FLAMSLSPServer> ServerWrapper<T> {
132 pub(crate) fn html_request(&mut self, params: UriParams) -> Res<Option<String>> {
133 let mut client = self.inner.client().clone();
134 let state = self.inner.state().clone();
135 Box::pin(
136 tokio::task::spawn_blocking(move || {
137 state
138 .build_html(¶ms.uri.into(), &mut client)
139 .map(|d| d.to_string())
140 })
141 .map_err(|e| ResponseError::new(async_lsp::ErrorCode::REQUEST_FAILED, e.to_string())),
142 )
143 }
144
145 pub(crate) fn new_archive(
146 &mut self,
147 NewArchiveParams { archive, urlbase }: NewArchiveParams,
148 ) -> <Self as LanguageServer>::NotifyResult {
149 let mut client = self.inner.client().clone();
150 tokio::task::spawn_blocking(move || {
151 match GlobalBackend.new_archive(
152 &archive,
153 &urlbase,
154 flams_stex::STEX.id(),
155 "helloworld.tex",
156 include_str!("stex_default.txt"),
157 ) {
158 Ok(mut path) => {
159 let _ = client.show_message(lsp::ShowMessageParams {
160 typ: lsp::MessageType::INFO,
161 message: format!("Created new archive {archive}"),
162 });
163 path.push("source");
164 path.push("helloworld.tex");
165 client.open_file(&path);
166 }
167 Err(e) => {
168 let _ = client.show_message(lsp::ShowMessageParams {
169 typ: lsp::MessageType::ERROR,
170 message: format!("Error creating new archive {archive}: {e:#}"),
171 });
172 }
173 }
174 });
175 ControlFlow::Continue(())
176 }
177
178 pub(crate) fn export_html(
179 &mut self,
180 params: StandaloneExportParams,
181 ) -> <Self as LanguageServer>::NotifyResult {
182 let StandaloneExportParams { uri, target } = params;
183 let uri: UrlOrFile = uri.into();
184 let state = self.inner.state().clone();
185 let mut client = self.inner.client().clone();
186 tokio::task::spawn_blocking(move || {
187 let Some(doc) = state.get(&uri) else {
188 let _ = client.show_message(lsp::ShowMessageParams {
189 typ: lsp::MessageType::ERROR,
190 message: format!("Not a valid file path: {uri}"),
191 });
192 return;
193 };
194 let Some(doc_uri) = doc.document_uri() else {
195 let _ = client.show_message(lsp::ShowMessageParams {
196 typ: lsp::MessageType::ERROR,
197 message: format!("Document for {uri} not found"),
198 });
199 return;
200 };
201
202 let progress = ProgressCallbackServer::new(
203 client.clone(),
204 format!("Exporting {}", doc_uri.document_name()),
205 None,
206 );
207 let _ = if let Err(e) = backend().export_html(doc_uri, &target) {
208 client.show_message(lsp::ShowMessageParams {
209 typ: lsp::MessageType::ERROR,
210 message: e,
211 })
212 } else {
213 client.show_message(lsp::ShowMessageParams {
214 typ: lsp::MessageType::INFO,
215 message: "Exported HTML successfully".to_string(),
216 })
217 };
218 drop(progress);
219 });
220 ControlFlow::Continue(())
221 }
222
223 pub(crate) fn export_standalone(
224 &mut self,
225 params: StandaloneExportParams,
226 ) -> <Self as LanguageServer>::NotifyResult {
227 let StandaloneExportParams { uri, target } = params;
228 let uri: UrlOrFile = uri.into();
229 let state = self.inner.state().clone();
230 let mut client = self.inner.client().clone();
231 tokio::task::spawn_blocking(move || {
232 let Some(doc) = state.get(&uri) else {
233 let _ = client.show_message(lsp::ShowMessageParams {
234 typ: lsp::MessageType::ERROR,
235 message: format!("Not a valid file path: {uri}"),
236 });
237 return;
238 };
239 let Some(doc_uri) = doc.document_uri() else {
240 let _ = client.show_message(lsp::ShowMessageParams {
241 typ: lsp::MessageType::ERROR,
242 message: format!("Document for {uri} not found"),
243 });
244 return;
245 };
246 let Some(file) = doc.path() else {
247 let _ = client.show_message(lsp::ShowMessageParams {
248 typ: lsp::MessageType::ERROR,
249 message: format!("File for {uri} not found"),
250 });
251 return;
252 };
253 let progress = ProgressCallbackServer::new(
254 client.clone(),
255 format!("Exporting {}", doc_uri.document_name()),
256 None,
257 );
258 if let Err(e) = flams_stex::export_standalone(doc_uri, file, &target) {
259 let _ = client.show_message(lsp::ShowMessageParams {
260 typ: lsp::MessageType::ERROR,
261 message: format!(
262 "Error exporting {} to {}: {e:#}",
263 file.display(),
264 target.display()
265 ),
266 });
267 } else {
268 let _ = client.show_message(lsp::ShowMessageParams {
269 typ: lsp::MessageType::INFO,
270 message: format!(
271 "Finished exporting {} to {}\n\nYou may want to verify the exported file actually compiles",
272 file.display(),
273 target.display()
274 ),
275 });
276 }
277 drop(progress);
278 });
279 ControlFlow::Continue(())
280 }
281
282 pub(crate) fn quiz_request(&mut self, params: UriParams) -> Res<String> {
283 use flams_system::backend::backend;
284 fn get_res(url: UrlOrFile, state: LSPState) -> Result<String, String> {
285 let doc = state
286 .get(&url)
287 .ok_or_else(|| "Document not found".to_string())?;
288 let uri = doc
289 .document_uri()
290 .ok_or_else(|| "Document URI not found".to_string())?;
291 let doc = backend().get_document(uri).map_err(|e| e.to_string())?;
292 let quiz = doc
293 .as_quiz(
294 &|d| backend().get_document(d).ok(),
295 &|d, r| backend().get_html_fragment(d, r).ok(),
296 &|d, r: DataRef<Solutions>| {
297 backend().get_reference(&r.with_doc(d.clone())).ok()
298 },
299 &|d, r: DataRef<GradingNote>| {
300 backend().get_reference(&r.with_doc(d.clone())).ok()
301 },
302 )
303 .map_err(|e| format!("{e:#}"))?;
304 serde_json::to_string(&quiz).map_err(|e| format!("{e:#}"))
305 }
306 let state = self.inner.state().clone();
307 let mut client = self.inner.client().clone();
308 Box::pin(async move {
309 let url: UrlOrFile = params.uri.into();
310 tokio::task::spawn_blocking(move || match get_res(url, state) {
311 Err(e) => {
312 let _ = client.show_message(lsp::ShowMessageParams {
313 typ: lsp::MessageType::ERROR,
314 message: e.clone(),
315 });
316 Err(ResponseError::new(async_lsp::ErrorCode::REQUEST_FAILED, e))
317 }
318 Ok(r) => Ok(r),
319 })
320 .map_err(|e| ResponseError::new(async_lsp::ErrorCode::REQUEST_FAILED, e.to_string()))
321 .await?
322 })
323 }
324
325 pub(crate) fn snify(
326 &mut self,
327 UriParams { uri }: UriParams,
328 ) -> <Self as LanguageServer>::NotifyResult {
329 let url: UrlOrFile = uri.into();
330 let Some(doc) = self.inner.state().get(&url) else {
331 let _ = self
332 .inner
333 .client_mut()
334 .show_message(async_lsp::lsp_types::ShowMessageParams {
335 typ: lsp::MessageType::ERROR,
336 message: format!("Could not find file {url}"),
337 });
338 return ControlFlow::Continue(());
339 };
340 doc.up_to_date
341 .store(false, std::sync::atomic::Ordering::SeqCst);
342 doc.load_annotations_and(self.inner.state().clone(), true, |data| {
343 let mut diagnostics = Vec::new();
344 let iter: AnnotIter = data.annotations.iter().into();
345 for e in <AnnotIter as TreeChildIter<STeXAnnot>>::dfs(iter) {
346 if let STeXAnnot::SnifySuggestion { range, symbols } = e {
347 diagnostics.push(into_diagnostic(STeXDiagnostic {
348 level: flams_stex::quickparse::stex::DiagnosticLevel::Info,
349 range: *range,
350 message: "snify suggestion".to_string(),
351 }));
352 }
353 }
354 if !diagnostics.is_empty() {
355 let _ = self.inner.client_mut().publish_diagnostics(
356 async_lsp::lsp_types::PublishDiagnosticsParams {
357 uri: url.into(),
358 diagnostics,
359 version: None,
360 },
361 );
362 }
363 });
364
365 ControlFlow::Continue(())
366 }
367
368 fn build(
369 doc: &LSPDocument,
370 uri: &impl std::fmt::Display,
371 stale_only: bool,
372 ) -> Result<(), String> {
373 let Some(id) = doc.archive().map(|a| a.archive_id()) else {
374 return Err(format!("Containing archive for {uri} not found"));
375 };
376 let Some(rel_path) = doc.relative_path() else {
377 return Err(format!("relative path for {uri} not found"));
378 };
379 flams_system::building::queue_manager::QueueManager::get().with_global(move |queue| {
381 queue.enqueue_archive(
382 id,
383 FormatOrTargets::Format(flams_stex::STEX.id()),
384 stale_only,
385 Some(RelPath::new(rel_path)),
386 false,
387 )
388 });
389 Ok(())
390 }
391
392 pub(crate) fn build_one(&mut self, params: UriParams) -> Res<()> {
393 let state = self.inner.state().clone();
394 fut(move || {
395 let url: UrlOrFile = params.uri.into();
396 let Some(doc) = state.get(&url) else {
397 return Err(format!("Document not found: {url}"));
398 };
399 Self::build(&doc, &url, false)
400 })
401 }
402 pub(crate) fn build_all(&mut self, params: UriParams) -> Res<()> {
403 let state = self.inner.state().clone();
404 let client = self.inner.client().clone();
405 wrap_fut(async move {
406 let istate = state.clone();
407 let url = block(move || {
408 let url: UrlOrFile = params.uri.into();
409 let Some(doc) = istate.get(&url) else {
410 return Err(format!("Document not found: {url}"));
411 };
412 Self::build(&doc, &url, false)?;
413 Ok(url)
414 })
415 .await?;
416 let deps = triomphe::Arc::new(parking_lot::Mutex::new(vec![url]));
417 let mut curr = 0;
418 loop {
419 let url = {
420 let dps = deps.lock();
421 if curr == dps.len() {
422 break;
423 }
424 let d = dps[curr].clone();
425 drop(dps);
426 d
427 };
428 curr += 1;
429 let Some(d) = state.get(&url) else {
430 continue;
432 };
433 let iclient = client.clone();
436 let d_archive = d.archive().cloned();
437 let Some(vec) = d
438 .with_annots_block(state.clone(), false, move |annots| {
439 let mut client = iclient;
440 <AnnotIter as TreeChildIter<STeXAnnot>>::dfs(AnnotIter::from(
441 annots.annotations.iter(),
442 ))
443 .filter_map(|a| match a {
444 STeXAnnot::Inputref {
445 archive, filepath, ..
446 }
447 | STeXAnnot::MHInput {
448 archive, filepath, ..
449 }
450 | STeXAnnot::IncludeProblem {
451 archive, filepath, ..
452 } => {
453 let archive = archive
454 .as_ref()
455 .map(|a| &a.0)
456 .unwrap_or_else(|| unwrap!(d_archive.as_ref()).archive_id());
457 let Some(uri) = crate::annotations::uri_from_archive_relpath(
458 &archive,
459 &filepath.0,
460 ) else {
461 let _ = client.show_message(lsp::ShowMessageParams {
462 typ: lsp::MessageType::ERROR,
463 message: format!(
464 "Could not find file [{archive}]{{{}}}",
465 &filepath.0
466 ),
467 });
468 return None;
469 };
470 let url: UrlOrFile = uri.into();
471 Some(url)
472 }
473 STeXAnnot::ImportModule { module, .. }
474 | STeXAnnot::UseModule { module, .. } => {
475 let Some(uri) = module
476 .full_path
477 .as_ref()
478 .and_then(|e| lsp::Url::from_file_path(e).ok())
479 else {
480 let _ = client.show_message(lsp::ShowMessageParams {
481 typ: lsp::MessageType::ERROR,
482 message: format!(
483 "Could not find module file {}",
484 &module.uri
485 ),
486 });
487 return None;
488 };
489 let url: UrlOrFile = uri.into();
490 Some(url)
491 }
492 _ => None,
493 })
494 .collect::<Vec<_>>()
495 })
496 .await
497 else {
498 continue;
499 };
500 for url in vec {
501 {
502 let mut dep_lock = deps.lock();
503 if dep_lock.contains(&url) {
504 continue;
505 }
506 dep_lock.push(url.clone());
507 }
508 let state = state.clone();
509 let mut client = client.clone();
510 block(move || {
511 let Some(doc) = state.force_get(&url) else {
512 let _ = client.show_message(lsp::ShowMessageParams {
513 typ: lsp::MessageType::ERROR,
514 message: format!("Could not find document {url}"),
515 });
516 return Ok(());
517 };
518 if let Err(e) = Self::build(&doc, &url, true) {
519 let _ = client.show_message(lsp::ShowMessageParams {
520 typ: lsp::MessageType::ERROR,
521 message: format!("Could not queue document {url}: {e}"),
522 });
523 }
524 Ok(())
525 })
526 .await?
527 }
528 }
529 Ok(())
530 })
531 }
532
533 pub(crate) fn reload(
534 &mut self,
535 _: crate::ReloadParams,
536 ) -> <Self as LanguageServer>::NotifyResult {
537 let state = self.inner.state().clone();
538 let client = self.inner.client().clone();
539 tracing::info!("LSP: reload");
540 state.backend().reset::<TokioEngine>();
541 let _ = tokio::task::spawn_blocking(|| {
542 for e in flams_system::iter::<flams_math_archives::FlamsExtension>() {
543 (e.on_reload)();
544 }
545 });
546 let _ = tokio::task::spawn_blocking(move || {
547 state.load_mathhubs(client.clone());
548 client.update_mathhub();
549 });
550 ControlFlow::Continue(())
551 }
552
553 pub(crate) fn install(
554 &mut self,
555 params: crate::InstallParams,
556 ) -> <Self as LanguageServer>::NotifyResult {
557 let state = self.inner.state().clone();
558 let client = self.inner.client().clone();
559 let mut progress = ProgressCallbackServer::new(client, "Installing".to_string(), None);
560 let _ = tokio::task::spawn(async move {
561 let crate::InstallParams {
562 archives,
563 remote_url,
564 } = params;
565 let mut rescan = false;
566 let archives = {
567 let mut ret = Vec::new();
568 let exis = GlobalBackend.all_archives();
569 for a in archives {
570 if exis.iter().any(|e| *e.id() == a) || ret.contains(&a) {
571 continue;
572 }
573 ret.push(a);
574 }
575 ret
576 };
577 let len = archives.len();
578 for (i, a) in archives.into_iter().enumerate() {
579 let url = format!("{remote_url}/api/backend/download?id={a}");
580 let prefix = format!("{}/{len}: {a}", i + 1);
581 progress.update(prefix.clone(), None);
582 if flams_system::zip::unzip_from_remote(a.clone(), &url, |p| {
583 progress.update(format!("{prefix}: {}", p.display()), None)
584 })
585 .await
586 .is_err()
587 {
588 let _ = progress.client_mut().show_message(lsp::ShowMessageParams {
589 message: format!("Failed to install archive {a}"),
590 typ: lsp::MessageType::ERROR,
591 });
592 } else {
593 rescan = true;
594 }
595 }
596 let client = progress.client();
597 drop(progress);
598 if rescan {
599 state.backend().reset::<TokioEngine>();
600 let _ = tokio::task::spawn_blocking(|| {
601 for e in flams_system::iter::<flams_math_archives::FlamsExtension>() {
602 (e.on_reload)();
603 }
604 });
605 let _ = tokio::task::spawn_blocking(move || {
606 state.load_mathhubs(client.clone());
608 client.update_mathhub();
609 });
610 } else {
611 client.update_mathhub();
612 }
613 });
614 ControlFlow::Continue(())
615 }
616
617 fn update_backend(
618 files: impl Iterator<Item = PathBuf>,
619 ) -> <Self as LanguageServer>::NotifyResult {
620 let backend = GlobalBackend.get();
621 let mut dones = Vec::new();
622 let mut missing = false;
623 for f in files {
624 if backend
625 .archive_of(&f, |a, _| {
626 if !dones.contains(a.id()) {
627 dones.push(a.id().clone());
628 a.update_sources();
629 }
630 })
631 .is_none()
632 {
633 missing = true;
634 }
635 }
636 if missing {
637 backend.load(flams_system::settings::Settings::get().mathhubs());
638 }
639 ControlFlow::Continue(())
640 }
641}
642
643type Res<T> = BoxFuture<'static, Result<T, ResponseError>>;
644
645impl<T: FLAMSLSPServer> LanguageServer for ServerWrapper<T> {
646 type Error = ResponseError;
647 type NotifyResult = ControlFlow<async_lsp::Result<()>>;
648
649 fn initialize(&mut self, params: lsp::InitializeParams) -> Res<lsp::InitializeResult> {
650 tracing::info!("LSP: initialize");
651 self.inner.initialize(
652 params
653 .workspace_folders
654 .unwrap_or_default()
655 .into_iter()
656 .map(|f| (f.name, f.uri)),
657 );
658 Box::pin(std::future::ready({
659 Ok(lsp::InitializeResult {
660 capabilities: super::capabilities::capabilities(),
661 server_info: None,
662 })
663 }))
664 }
665
666 fn shutdown(&mut self, (): ()) -> Res<()> {
667 tracing::info!("LSP: shutdown");
668 Box::pin(std::future::ready(Ok(())))
669 }
670
671 fn initialized(&mut self, _params: lsp::InitializedParams) -> Self::NotifyResult {
675 tracing::info!("LSP: initialized");
676 self.inner.initialized();
677 ControlFlow::Continue(())
680 }
681
682 impl_notification!(!exit = Exit);
683
684 impl_notification!(!did_change_workspace_folders = DidChangeWorkspaceFolders);
686 impl_notification!(!did_change_configuration = DidChangeConfiguration);
687 impl_notification!(!did_change_watched_files = DidChangeWatchedFiles);
688
689 fn did_open(&mut self, params: lsp::DidOpenTextDocumentParams) -> Self::NotifyResult {
692 let document = params.text_document;
693 tracing::trace!(
694 "URI: {}, language: {}, version: {}, text: \n{}",
695 document.uri,
696 document.language_id,
697 document.version,
698 document.text
699 );
700 self.inner
701 .state()
702 .insert(document.uri.into(), document.text);
703 ControlFlow::Continue(())
704 }
705
706 #[allow(clippy::let_underscore_future)]
707 fn did_change(&mut self, params: lsp::DidChangeTextDocumentParams) -> Self::NotifyResult {
709 let document = params.text_document;
710 let uri = document.uri.clone().into();
711 if let Some(d) = self.inner.state().get(&uri) {
712 for change in params.content_changes {
713 tracing::trace!(
714 "URI: {},version: {}, text: \"{}\", range: {:?}",
715 document.uri,
716 document.version,
717 change.text,
718 change.range
719 );
720 d.delta(change.text, change.range);
721 }
722 let mut client = self.inner.client().clone();
723 let _ = tokio::spawn(d.with_annots(self.inner.state().clone(), false, move |a| {
724 let r = lsp::PublishDiagnosticsParams {
725 uri: document.uri,
726 diagnostics: a.diagnostics.iter().map(to_diagnostic).collect(),
727 version: Some(document.version),
728 };
729 let _ = client.publish_diagnostics(r);
730 }));
731 } else {
732 tracing::warn!("document not found: {}", document.uri);
733 }
734 ControlFlow::Continue(())
735 }
736
737 #[allow(clippy::let_underscore_future)]
738 fn did_save(&mut self, params: lsp::DidSaveTextDocumentParams) -> Self::NotifyResult {
740 tracing::trace!("did_save: {}", params.text_document.uri);
741 let state = self.inner.state().clone();
742 let client = self.inner.client().clone();
743 let uri = params.text_document.uri.into();
744 let _ = tokio::task::spawn_blocking(move || {
745 state.build_html_and_notify(&uri, client);
746 });
747 ControlFlow::Continue(())
748 }
749
750 impl_notification!(!will_save = WillSaveTextDocument);
751
752 fn did_close(&mut self, params: lsp::DidCloseTextDocumentParams) -> Self::NotifyResult {
754 tracing::trace!("did_close: {}", params.text_document.uri);
755 ControlFlow::Continue(())
756 }
757
758 impl_notification!(work_done_progress_cancel = WorkDoneProgressCancel);
761
762 impl_notification!(!set_trace = SetTrace);
764 impl_notification!(!cancel_request = Cancel);
765 impl_notification!(!progress = Progress);
766
767 fn document_symbol(
773 &mut self,
774 params: lsp::DocumentSymbolParams,
775 ) -> Res<Option<lsp::DocumentSymbolResponse>> {
776 tracing::trace_span!("document_symbol").in_scope(move || {
777 tracing::trace!(
778 "uri: {},work_done_progress_params: {:?}, partial_results: {:?}",
779 params.text_document.uri,
780 params.work_done_progress_params,
781 params.partial_result_params
782 );
783 let p = params
784 .work_done_progress_params
785 .work_done_token
786 .map(|tk| self.get_progress(tk));
787 self.inner
788 .state()
789 .get_symbols(¶ms.text_document.uri.into(), p)
790 .map_or_else(
791 || Box::pin(std::future::ready(Ok(None))) as _,
792 |f| Box::pin(f.map(Result::Ok)) as _,
793 )
794 })
795 }
796
797 fn document_diagnostic(
799 &mut self,
800 params: lsp::DocumentDiagnosticParams,
801 ) -> Res<lsp::DocumentDiagnosticReportResult> {
802 fn default() -> lsp::DocumentDiagnosticReportResult {
803 lsp::DocumentDiagnosticReportResult::Report(lsp::DocumentDiagnosticReport::Full(
804 lsp::RelatedFullDocumentDiagnosticReport::default(),
805 ))
806 }
807 tracing::trace_span!("document_diagnostics").in_scope(move || {
808 tracing::trace!("work_done_progress_params: {:?}, partial_results: {:?}, position: {:?}, context: {:?}",
809 params.work_done_progress_params,
810 params.partial_result_params,
811 params.text_document,
812 params.identifier
813 );
814
815 let p = params.work_done_progress_params.work_done_token.map(
816 |tk| self.get_progress(tk)
817 );
818 self.inner.state().get_diagnostics(¶ms.text_document.uri.into(),p)
819 .map_or_else(|| Box::pin(std::future::ready(Ok(default()))) as _,
820 |f| Box::pin(f.map(Result::Ok)) as _
821 )
822 })
823 }
824
825 fn references(&mut self, params: lsp::ReferenceParams) -> Res<Option<Vec<lsp::Location>>> {
827 tracing::trace_span!("references").in_scope(move || {
828 tracing::trace!("work_done_progress_params: {:?}, partial_results: {:?}, position: {:?}, context: {:?}",
829 params.work_done_progress_params,
830 params.partial_result_params,
831 params.text_document_position,
832 params.context
833 );
834 let p = params.work_done_progress_params.work_done_token.map(
835 |tk| self.get_progress(tk)
836 );
837 self.inner.state().get_references(
838 params.text_document_position.text_document.uri.into(),
839 params.text_document_position.position,p
840 ).map_or_else(|| Box::pin(std::future::ready(Ok(None))) as _,
841 |f| Box::pin(f.map(Result::Ok)) as _
842 )
843 })
844 }
845
846 fn document_link(
848 &mut self,
849 params: lsp::DocumentLinkParams,
850 ) -> Res<Option<Vec<lsp::DocumentLink>>> {
851 tracing::trace_span!("document_link").in_scope(move || {
852 tracing::trace!(
853 "uri: {},work_done_progress_params: {:?}, partial_results: {:?}",
854 params.text_document.uri,
855 params.work_done_progress_params,
856 params.partial_result_params
857 );
858 let p = params
859 .work_done_progress_params
860 .work_done_token
861 .map(|tk| self.get_progress(tk));
862 self.inner
863 .state()
864 .get_links(¶ms.text_document.uri.into(), p)
865 .map_or_else(
866 || Box::pin(std::future::ready(Ok(None))) as _,
867 |f| Box::pin(f.map(Result::Ok)) as _,
868 )
869 })
870 }
871
872 fn hover(&mut self, params: lsp::HoverParams) -> Res<Option<lsp::Hover>> {
874 tracing::trace_span!("hover").in_scope(move || {
875 tracing::trace!(
876 "uri: {},work_done_progress_params: {:?}, position: {:?}",
877 params.text_document_position_params.text_document.uri,
878 params.work_done_progress_params,
879 params.text_document_position_params.position
880 );
881 let p = params
882 .work_done_progress_params
883 .work_done_token
884 .map(|tk| self.get_progress(tk));
885 self.inner
886 .state()
887 .get_hover(
888 ¶ms
889 .text_document_position_params
890 .text_document
891 .uri
892 .into(),
893 params.text_document_position_params.position,
894 p,
895 )
896 .map_or_else(
897 || Box::pin(std::future::ready(Ok(None))) as _,
898 |f| Box::pin(f.map(Result::Ok)) as _,
899 )
900 })
901 }
902
903 fn definition(
905 &mut self,
906 params: lsp::GotoDefinitionParams,
907 ) -> Res<Option<lsp::GotoDefinitionResponse>> {
908 tracing::trace_span!("definition").in_scope(move || {
909 tracing::trace!(
910 "uri: {},work_done_progress_params: {:?}, position: {:?}",
911 params.text_document_position_params.text_document.uri,
912 params.work_done_progress_params,
913 params.text_document_position_params.position
914 );
915 let p = params
916 .work_done_progress_params
917 .work_done_token
918 .map(|tk| self.get_progress(tk));
919 self.inner
920 .state()
921 .get_goto_definition(
922 params
923 .text_document_position_params
924 .text_document
925 .uri
926 .into(),
927 params.text_document_position_params.position,
928 p,
929 )
930 .map_or_else(
931 || Box::pin(std::future::ready(Ok(None))) as _,
932 |f| Box::pin(f.map(Result::Ok)) as _,
933 )
934 })
935 }
936
937 impl_request!(! code_lens = CodeLensRequest => (None));
938
939 impl_request!(! declaration = GotoDefinition => (None));
940
941 impl_request!(! workspace_diagnostic = WorkspaceDiagnosticRequest => (lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {items:Vec::new()})));
942 fn inlay_hint(&mut self, params: lsp::InlayHintParams) -> Res<Option<Vec<lsp::InlayHint>>> {
989 tracing::trace_span!("inlay hint").in_scope(move || {
990 tracing::trace!(
991 "uri: {},work_done_progress_params: {:?}",
992 params.text_document.uri,
993 params.work_done_progress_params,
994 );
995 let p = params
996 .work_done_progress_params
997 .work_done_token
998 .map(|tk| self.get_progress(tk));
999 self.inner
1000 .state()
1001 .get_inlay_hints(¶ms.text_document.uri.into(), p)
1002 .map_or_else(
1003 || Box::pin(std::future::ready(Ok(None))) as _,
1004 |f| Box::pin(f.map(Result::Ok)) as _,
1005 )
1006 })
1007 }
1008 impl_request!(inlay_hint_resolve = InlayHintResolveRequest);
1010
1011 fn code_action(
1013 &mut self,
1014 params: lsp::CodeActionParams,
1015 ) -> Res<Option<lsp::CodeActionResponse>> {
1016 tracing::trace_span!("code_action").in_scope(move || {
1017 tracing::trace!(
1018 "uri: {},work_done_progress_params: {:?}; range: {:?}; context:{:?}",
1019 params.text_document.uri, params.work_done_progress_params,
1021 params.range,
1022 params.context
1023 );
1024 let p = params
1025 .work_done_progress_params
1026 .work_done_token
1027 .map(|tk| self.get_progress(tk));
1028 self.inner
1029 .state()
1030 .get_codeaction(
1031 params.text_document.uri.into(),
1032 params.range,
1033 params.context,
1034 p,
1035 )
1036 .map_or_else(
1037 || Box::pin(std::future::ready(Ok(None))) as _,
1038 |f| Box::pin(f.map(Result::Ok)) as _,
1039 )
1040 })
1041 }
1042
1043 fn prepare_call_hierarchy(
1045 &mut self,
1046 params: lsp::CallHierarchyPrepareParams,
1047 ) -> Res<Option<Vec<lsp::CallHierarchyItem>>> {
1048 tracing::trace_span!("prepare_call_hierarchy").in_scope(move || {
1049 tracing::trace!(
1050 "uri: {},work_done_progress_params: {:?}; position: {:?}",
1051 params.text_document_position_params.text_document.uri,
1052 params.work_done_progress_params,
1053 params.text_document_position_params.position
1054 );
1055 let p = params
1056 .work_done_progress_params
1057 .work_done_token
1058 .map(|tk| self.get_progress(tk));
1059 self.inner
1060 .state()
1061 .prepare_module_hierarchy(
1062 params
1063 .text_document_position_params
1064 .text_document
1065 .uri
1066 .into(),
1067 p,
1068 )
1069 .map_or_else(
1070 || Box::pin(std::future::ready(Ok(None))) as _,
1071 |f| Box::pin(f.map(Result::Ok)) as _,
1072 )
1073 })
1074 }
1075
1076 fn incoming_calls(
1079 &mut self,
1080 params: lsp::CallHierarchyIncomingCallsParams,
1081 ) -> Res<Option<Vec<lsp::CallHierarchyIncomingCall>>> {
1082 tracing::trace_span!("incoming_call_hierarchy").in_scope(move || {
1083 tracing::trace!(
1084 "uri: {},work_done_progress_params: {:?};",
1085 params.item.uri,
1086 params.work_done_progress_params,
1087 );
1088 let p = params
1089 .work_done_progress_params
1090 .work_done_token
1091 .map(|tk| self.get_progress(tk));
1092 if let Some(d) = params
1093 .item
1094 .data
1095 .and_then(|d| d.as_str().and_then(|d| d.parse().ok()))
1096 {
1097 self.inner
1098 .state()
1099 .module_hierarchy_imports(params.item.uri, params.item.kind, d, p)
1100 .map_or_else(
1101 || Box::pin(std::future::ready(Ok(None))) as _,
1102 |f| Box::pin(f.map(Result::Ok)) as _,
1103 )
1104 } else {
1105 Box::pin(std::future::ready(Ok(None))) as _
1106 }
1107 })
1108 }
1109 impl_request!(outgoing_calls = CallHierarchyOutgoingCalls);
1110
1111 impl_request!(! document_highlight = DocumentHighlightRequest => (None));
1112 impl_request!(! folding_range = FoldingRangeRequest => (None));
1113
1114 impl_request!(implementation = GotoImplementation);
1115 impl_request!(type_definition = GotoTypeDefinition);
1116 impl_request!(document_color = DocumentColor);
1117 impl_request!(color_presentation = ColorPresentationRequest);
1118 impl_request!(selection_range = SelectionRangeRequest);
1119 impl_request!(moniker = MonikerRequest);
1120 impl_request!(inline_value = InlineValueRequest);
1121 impl_request!(on_type_formatting = OnTypeFormatting);
1122 impl_request!(range_formatting = RangeFormatting);
1123 impl_request!(formatting = Formatting);
1124 impl_request!(prepare_type_hierarchy = TypeHierarchyPrepare);
1125 impl_request!(will_save_wait_until = WillSaveWaitUntil);
1126
1127 impl_request!(!completion = Completion => (None));
1128
1129 impl_request!(signature_help = SignatureHelpRequest);
1130 impl_request!(linked_editing_range = LinkedEditingRange);
1131
1132 fn semantic_tokens_full(
1135 &mut self,
1136 params: lsp::SemanticTokensParams,
1137 ) -> Res<Option<lsp::SemanticTokensResult>> {
1138 tracing::trace_span!("semantic_tokens_full").in_scope(|| {
1139 tracing::trace!(
1140 "work_done_progress_params: {:?}, partial_results: {:?}, uri: {}",
1141 params.work_done_progress_params,
1142 params.partial_result_params,
1143 params.text_document.uri
1144 );
1145 let p = params
1146 .work_done_progress_params
1147 .work_done_token
1148 .map(|tk| self.get_progress(tk));
1149 self.inner
1150 .state()
1151 .get_semantic_tokens(¶ms.text_document.uri.into(), p, None)
1152 .map_or_else(
1153 || Box::pin(std::future::ready(Ok(None))) as _,
1154 |f| Box::pin(f.map(|r| Ok(r.map(lsp::SemanticTokensResult::Tokens)))) as _,
1155 )
1156 })
1157 }
1158
1159 fn semantic_tokens_range(
1161 &mut self,
1162 params: lsp::SemanticTokensRangeParams,
1163 ) -> Res<Option<lsp::SemanticTokensRangeResult>> {
1164 tracing::trace_span!("semantic_tokens_range").in_scope(|| {
1165 tracing::trace!(
1166 "work_done_progress_params: {:?}, partial_results: {:?}, range: {:?}, uri:{}",
1167 params.work_done_progress_params,
1168 params.partial_result_params,
1169 params.range,
1170 params.text_document.uri
1171 );
1172 let p = params
1173 .work_done_progress_params
1174 .work_done_token
1175 .map(|tk| self.get_progress(tk));
1176 self.inner
1177 .state()
1178 .get_semantic_tokens(¶ms.text_document.uri.into(), p, Some(params.range))
1179 .map_or_else(
1180 || Box::pin(std::future::ready(Ok(None))) as _,
1181 |f| Box::pin(f.map(|r| Ok(r.map(lsp::SemanticTokensRangeResult::Tokens)))) as _,
1182 )
1183 })
1184 }
1185
1186 fn semantic_tokens_full_delta(
1188 &mut self,
1189 params: lsp::SemanticTokensDeltaParams,
1190 ) -> Res<Option<lsp::SemanticTokensFullDeltaResult>> {
1191 tracing::info_span!("semantic_tokens_full_delta").in_scope(|| {
1192 tracing::info!("work_done_progress_params: {:?}, partial_results: {:?}, previous_result_id: {:?}, uri:{}",
1193 params.work_done_progress_params,
1194 params.partial_result_params,
1195 params.previous_result_id,
1196 params.text_document.uri
1197 );
1198 Box::pin(std::future::ready(Ok(None)))
1199 })
1200 }
1201
1202 fn did_create_files(&mut self, params: lsp::CreateFilesParams) -> Self::NotifyResult {
1204 let files = params.files.into_iter().map(|f| PathBuf::from(f.uri));
1205 Self::update_backend(files)
1206 }
1207
1208 fn did_rename_files(&mut self, params: lsp::RenameFilesParams) -> Self::NotifyResult {
1210 let files = params
1211 .files
1212 .into_iter()
1213 .flat_map(|f| vec![PathBuf::from(f.old_uri), PathBuf::from(f.new_uri)]);
1214 Self::update_backend(files)
1215 }
1216
1217 fn did_delete_files(&mut self, params: lsp::DeleteFilesParams) -> Self::NotifyResult {
1219 let files = params.files.into_iter().map(|f| PathBuf::from(f.uri));
1220 Self::update_backend(files)
1221 }
1222
1223 impl_request!(prepare_rename = PrepareRenameRequest);
1224 impl_request!(rename = Rename);
1225
1226 impl_request!(will_create_files = WillCreateFiles);
1228 impl_request!(will_rename_files = WillRenameFiles);
1229 impl_request!(will_delete_files = WillDeleteFiles);
1230 impl_request!(symbol = WorkspaceSymbolRequest);
1231
1232 fn execute_command(
1234 &mut self,
1235 params: lsp::ExecuteCommandParams,
1236 ) -> Res<Option<serde_json::Value>> {
1237 tracing::trace_span!("executing command {}", params.command).in_scope(move || {
1238 tracing::trace!("{:?}", params.arguments);
1239 match &*params.command {
1240 "snify/annotate" => {
1241 let state = self.inner.state().clone();
1242 let client = self.inner.client().clone();
1243 tokio::task::spawn_blocking(move || {
1244 snify_annotate(state, client, params.arguments);
1245 });
1246 }
1247 c => {
1248 let _ = self
1249 .inner
1250 .client_mut()
1251 .show_message(lsp::ShowMessageParams {
1252 message: format!("Unknown command {c:?}"),
1253 typ: lsp::MessageType::ERROR,
1254 });
1255 }
1256 }
1257 Box::pin(std::future::ready(Ok(None))) as _
1258 })
1259 }
1260
1261 impl_request!(supertypes = TypeHierarchySupertypes);
1263 impl_request!(subtypes = TypeHierarchySubtypes);
1264
1265 impl_request!(completion_item_resolve = ResolveCompletionItem);
1267
1268 fn code_action_resolve(&mut self, params: lsp::CodeAction) -> Res<lsp::CodeAction> {
1271 tracing::trace_span!("executing command").in_scope(move || {
1272 tracing::trace!("{params:?}");
1273 Box::pin(std::future::ready(Ok(lsp::CodeAction {
1274 title: "None".to_string(),
1275 kind: None,
1276 diagnostics: None,
1277 edit: None,
1278 command: None,
1279 is_preferred: None,
1280 disabled: None,
1281 data: None,
1282 }))) as _
1283 })
1284 }
1285
1286 impl_request!(workspace_symbol_resolve = WorkspaceSymbolResolve);
1288
1289 impl_request!(code_lens_resolve = CodeLensResolve);
1291
1292 impl_request!(document_link_resolve = DocumentLinkResolve);
1294}
1295
1296fn snify_annotate(
1297 state: LSPState,
1298 mut client: ClientSocket,
1299 mut arguments: Vec<serde_json::Value>,
1300) {
1301 fn err_num(num: usize, mut client: ClientSocket) {
1302 let _ = client.show_message(lsp::ShowMessageParams {
1303 message: format!(
1304 "invalid number of arguments for snify/annotate; expected: 4; got: {num}"
1305 ),
1306 typ: lsp::MessageType::ERROR,
1307 });
1308 }
1309 let Some(range) = arguments.pop() else {
1310 err_num(0, client);
1311 return;
1312 };
1313 let Some(doc) = arguments.pop() else {
1314 err_num(1, client);
1315 return;
1316 };
1317 let Some(needs_usemodule) = arguments.pop() else {
1318 err_num(2, client);
1319 return;
1320 };
1321 let Some(uri) = arguments.pop() else {
1322 err_num(3, client);
1323 return;
1324 };
1325 if !arguments.is_empty() {
1326 err_num(arguments.len() + 4, client);
1327 return;
1328 }
1329
1330 let Ok(range) = serde_json::from_value::<StringRange<LSPLineCol>>(range) else {
1331 let _ = client.show_message(lsp::ShowMessageParams {
1332 message: "Expected document range in argument 4".to_string(),
1333 typ: lsp::MessageType::ERROR,
1334 });
1335 return;
1336 };
1337 let Ok(needs_usemodule) = serde_json::from_value::<bool>(needs_usemodule) else {
1338 let _ = client.show_message(lsp::ShowMessageParams {
1339 message: "Expected boolean in argument 3".to_string(),
1340 typ: lsp::MessageType::ERROR,
1341 });
1342 return;
1343 };
1344 let Ok(url) = serde_json::from_value::<lsp::Url>(doc) else {
1345 let _ = client.show_message(lsp::ShowMessageParams {
1346 message: "Expected lsp uri in argument 2".to_string(),
1347 typ: lsp::MessageType::ERROR,
1348 });
1349 return;
1350 };
1351 let uri = if let serde_json::Value::String(s) = uri {
1352 if s.is_empty() {
1353 None
1354 } else {
1355 let Ok(uri) = s.parse::<SymbolUri>() else {
1356 let _ = client.show_message(lsp::ShowMessageParams {
1357 message: "Expected symbol uri in argument 1".to_string(),
1358 typ: lsp::MessageType::ERROR,
1359 });
1360 return;
1361 };
1362 Some(uri)
1363 }
1364 } else {
1365 let _ = client.show_message(lsp::ShowMessageParams {
1366 message: "Expected symbol uri in argument 1".to_string(),
1367 typ: lsp::MessageType::ERROR,
1368 });
1369 return;
1370 };
1371
1372 tracing::trace!(
1373 "Executing snify/annotate on {url}@{range} with {uri:?}; Needs usemodule:{needs_usemodule}"
1374 );
1375
1376 let urlfile = UrlOrFile::from(url.clone());
1377 let Some(doc) = state.get(&urlfile) else {
1378 let _ = client.show_message(lsp::ShowMessageParams {
1379 message: format!("Document {urlfile} not found"),
1380 typ: lsp::MessageType::ERROR,
1381 });
1382 return;
1383 };
1384 let edits = doc.with_text(|txt| {
1385 use crate::IsLSPRange;
1386 thread_local! {
1387 static NEEDLE:Needle<'static> = Needle::new("% srskip ");
1388 }
1389 let mut ret = Vec::new();
1390 let Some(uri) = uri else {
1391 let Some(text) = LSPLineCol::get_range(range.start, range.end, txt) else {
1392 return Vec::new();
1393 };
1394 let mut parser = StrParser::<LSPLineCol>::new(txt);
1395 let _ = NEEDLE.with(|n| parser.read_until_needle(n));
1396 if parser.rest().is_empty() {
1397 return vec![lsp::TextEdit {
1398 range: StringRange::into_range(StringRange {
1399 start: parser.pos,
1400 end: parser.pos,
1401 }),
1402 new_text: format!("\n% srskip l:{text}"),
1403 }];
1404 }
1405 let _ = parser.drop_prefix("% srskip ");
1406 return vec![lsp::TextEdit {
1407 range: StringRange::into_range(StringRange {
1408 start: parser.pos,
1409 end: parser.pos,
1410 }),
1411 new_text: format!("l:{text}, "),
1412 }];
1413 };
1414 if needs_usemodule {
1415 use std::fmt::Write;
1416 let mut parser = StrParser::<LSPLineCol>::new(txt);
1417 parser.read_until_str("\\begin{document}");
1418 parser.drop_prefix("\\begin{document}");
1419 let mut insertion = format!("\n \\usemodule[{}]{{", uri.archive_id());
1420 if let Some(path) = uri.path() {
1421 let _ = write!(insertion, "{path}?{}}}", uri.module_name());
1422 } else {
1423 let _ = write!(insertion, "{}}}", uri.module_name());
1424 }
1425 ret.push(lsp::TextEdit {
1426 range: StringRange::into_range(StringRange {
1427 start: parser.pos,
1428 end: parser.pos,
1429 }),
1430 new_text: insertion,
1431 });
1432 }
1433 ret.push(lsp::TextEdit {
1434 range: StringRange::into_range(StringRange {
1435 start: range.start,
1436 end: range.start,
1437 }),
1438 new_text: format!("\\sr{{{}}}{{", uri.name()),
1439 });
1440 ret.push(lsp::TextEdit {
1441 range: StringRange::into_range(StringRange {
1442 start: range.end,
1443 end: range.end,
1444 }),
1445 new_text: "}".to_string(),
1446 });
1447 ret
1448 });
1460 let mut eds = std::collections::HashMap::new();
1461 eds.insert(url, edits);
1462 doc.force_snify
1463 .store(true, std::sync::atomic::Ordering::Relaxed);
1464 std::mem::drop(tokio::task::spawn(client.apply_edit(
1465 lsp::ApplyWorkspaceEditParams {
1466 label: None,
1467 edit: lsp::WorkspaceEdit {
1468 document_changes: None,
1469 change_annotations: None,
1470 changes: Some(eds),
1471 },
1472 },
1473 )));
1474}