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::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};
28use flams_system::{TokioEngine, backend::backend};
29use flams_utils::{prelude::TreeChildIter, unwrap};
30use ftml_ontology::{
31 narrative::{
32 DataRef,
33 elements::{
34 DocumentElementRef,
35 problems::{GradingNote, Solutions},
36 },
37 },
38 utils::{Css, RefTree},
39};
40use ftml_uris::{DocumentUri, IsNarrativeUri, UriWithArchive};
41use futures::{FutureExt, TryFutureExt, future::BoxFuture};
42
43macro_rules! impl_request {
44 ($name:ident = $struct:ident) => {
45 fn $name(
46 &mut self,
47 params: <lsp::request::$struct as lsp::request::Request>::Params,
48 ) -> Res<<lsp::request::$struct as lsp::request::Request>::Result> {
49 tracing::info!("LSP: {params:?}");
50 Box::pin(std::future::ready(Err(ResponseError::new(
51 async_lsp::ErrorCode::METHOD_NOT_FOUND,
52 format!(
53 "No such method: {}",
54 <lsp::request::$struct as lsp::request::Request>::METHOD
55 ),
56 ))))
57 }
58 };
59 (? $name:ident = $struct:ident => ($default:expr)) => {
60 fn $name(
61 &mut self,
62 params: <lsp::request::$struct as lsp::request::Request>::Params,
63 ) -> Res<<lsp::request::$struct as lsp::request::Request>::Result> {
64 tracing::info!("LSP: {params:?}");
65 Box::pin(std::future::ready(Ok($default)))
66 }
67 };
68 (! $name:ident = $struct:ident => ($default:expr)) => {
69 fn $name(
70 &mut self,
71 params: <lsp::request::$struct as lsp::request::Request>::Params,
72 ) -> Res<<lsp::request::$struct as lsp::request::Request>::Result> {
73 tracing::debug!("LSP: {params:?}");
74 Box::pin(std::future::ready(Ok($default)))
75 }
76 };
77}
78
79macro_rules! impl_notification {
80 (! $name:ident = $struct:ident) => {
81 fn $name(
82 &mut self,
83 params: <lsp::notification::$struct as lsp::notification::Notification>::Params,
84 ) -> Self::NotifyResult {
85 tracing::debug!("LSP: {params:?}");
86 ControlFlow::Continue(())
87 }
88 };
89 ($name:ident = $struct:ident) => {
90 fn $name(
91 &mut self,
92 params: <lsp::notification::$struct as lsp::notification::Notification>::Params,
93 ) -> Self::NotifyResult {
94 tracing::info!("LSP: {params:?}");
95 ControlFlow::Break(Err(async_lsp::Error::Routing(format!(
96 "Unhandled notification: {}",
97 <lsp::notification::$struct as lsp::notification::Notification>::METHOD,
98 ))))
99 }
100 };
101}
102
103#[inline]
104fn fut<T: Send + 'static>(f: impl FnOnce() -> Result<T, String> + Send + 'static) -> Res<T> {
105 Box::pin(tokio::task::spawn_blocking(f).map_ok_or_else(
106 |e| {
107 Err(ResponseError::new(
108 async_lsp::ErrorCode::REQUEST_FAILED,
109 e.to_string(),
110 ))
111 },
112 |r| r.map_err(|e| ResponseError::new(async_lsp::ErrorCode::REQUEST_FAILED, e)),
113 ))
114}
115fn block<T: Send + 'static>(
116 f: impl FnOnce() -> Result<T, String> + Send + 'static,
117) -> impl std::future::Future<Output = Result<T, String>> {
118 tokio::task::spawn_blocking(f).map_ok_or_else(|e| Err(e.to_string()), |r| r)
119}
120fn wrap_fut<T: Send + 'static>(
121 f: impl std::future::Future<Output = Result<T, String>> + Send + 'static,
122) -> Res<T> {
123 Box::pin(f.map_err(|e| ResponseError::new(async_lsp::ErrorCode::REQUEST_FAILED, e)))
124}
125
126impl<T: FLAMSLSPServer> ServerWrapper<T> {
127 pub(crate) fn html_request(&mut self, params: UriParams) -> Res<Option<String>> {
128 let mut client = self.inner.client().clone();
129 let state = self.inner.state().clone();
130 Box::pin(
131 tokio::task::spawn_blocking(move || {
132 state
133 .build_html(¶ms.uri.into(), &mut client)
134 .map(|d| d.to_string())
135 })
136 .map_err(|e| ResponseError::new(async_lsp::ErrorCode::REQUEST_FAILED, e.to_string())),
137 )
138 }
139
140 pub(crate) fn new_archive(
141 &mut self,
142 NewArchiveParams { archive, urlbase }: NewArchiveParams,
143 ) -> <Self as LanguageServer>::NotifyResult {
144 let mut client = self.inner.client().clone();
145 tokio::task::spawn_blocking(move || {
146 match GlobalBackend.new_archive(
147 &archive,
148 &urlbase,
149 flams_stex::STEX.id(),
150 "helloworld.tex",
151 include_str!("stex_default.txt"),
152 ) {
153 Ok(path) => {
154 let _ = client.show_message(lsp::ShowMessageParams {
155 typ: lsp::MessageType::INFO,
156 message: format!("Created new archive {archive}"),
157 });
158 client.open_file(&path);
159 }
160 Err(e) => {
161 let _ = client.show_message(lsp::ShowMessageParams {
162 typ: lsp::MessageType::ERROR,
163 message: format!("Error creating new archive {archive}: {e:#}"),
164 });
165 }
166 }
167 });
168 ControlFlow::Continue(())
169 }
170
171 pub(crate) fn export_html(
172 &mut self,
173 params: StandaloneExportParams,
174 ) -> <Self as LanguageServer>::NotifyResult {
175 let StandaloneExportParams { uri, target } = params;
176 let uri: UrlOrFile = uri.into();
177 let state = self.inner.state().clone();
178 let mut client = self.inner.client().clone();
179 tokio::task::spawn_blocking(move || {
180 let Some(doc) = state.get(&uri) else {
181 let _ = client.show_message(lsp::ShowMessageParams {
182 typ: lsp::MessageType::ERROR,
183 message: format!("Not a valid file path: {uri}"),
184 });
185 return;
186 };
187 let Some(doc_uri) = doc.document_uri() else {
188 let _ = client.show_message(lsp::ShowMessageParams {
189 typ: lsp::MessageType::ERROR,
190 message: format!("Document for {uri} not found"),
191 });
192 return;
193 };
194 if let Err(e) = export_html(doc_uri, client.clone(), &target) {
195 let _ = client.show_message(lsp::ShowMessageParams {
196 typ: lsp::MessageType::ERROR,
197 message: e,
198 });
199 }
200 });
201 ControlFlow::Continue(())
202 }
203
204 pub(crate) fn export_standalone(
205 &mut self,
206 params: StandaloneExportParams,
207 ) -> <Self as LanguageServer>::NotifyResult {
208 let StandaloneExportParams { uri, target } = params;
209 let uri: UrlOrFile = uri.into();
210 let state = self.inner.state().clone();
211 let mut client = self.inner.client().clone();
212 tokio::task::spawn_blocking(move || {
213 let Some(doc) = state.get(&uri) else {
214 let _ = client.show_message(lsp::ShowMessageParams {
215 typ: lsp::MessageType::ERROR,
216 message: format!("Not a valid file path: {uri}"),
217 });
218 return;
219 };
220 let Some(doc_uri) = doc.document_uri() else {
221 let _ = client.show_message(lsp::ShowMessageParams {
222 typ: lsp::MessageType::ERROR,
223 message: format!("Document for {uri} not found"),
224 });
225 return;
226 };
227 let Some(file) = doc.path() else {
228 let _ = client.show_message(lsp::ShowMessageParams {
229 typ: lsp::MessageType::ERROR,
230 message: format!("File for {uri} not found"),
231 });
232 return;
233 };
234 let progress = ProgressCallbackServer::new(
235 client.clone(),
236 format!("Exporting {}", doc_uri.document_name()),
237 None,
238 );
239 if let Err(e) = flams_stex::export_standalone(doc_uri, file, &target) {
240 let _ = client.show_message(lsp::ShowMessageParams {
241 typ: lsp::MessageType::ERROR,
242 message: format!(
243 "Error exporting {} to {}: {e:#}",
244 file.display(),
245 target.display()
246 ),
247 });
248 } else {
249 let _ = client.show_message(lsp::ShowMessageParams {
250 typ: lsp::MessageType::INFO,
251 message: format!(
252 "Finished exporting {} to {}\n\nYou may want to verify the exported file actually compiles",
253 file.display(),
254 target.display()
255 ),
256 });
257 }
258 drop(progress);
259 });
260 ControlFlow::Continue(())
261 }
262
263 pub(crate) fn quiz_request(&mut self, params: UriParams) -> Res<String> {
264 use flams_system::backend::backend;
265 fn get_res(url: UrlOrFile, state: LSPState) -> Result<String, String> {
266 let doc = state
267 .get(&url)
268 .ok_or_else(|| "Document not found".to_string())?;
269 let uri = doc
270 .document_uri()
271 .ok_or_else(|| "Document URI not found".to_string())?;
272 let doc = backend().get_document(uri).map_err(|e| e.to_string())?;
273 let quiz = doc
274 .as_quiz(
275 &|d| backend().get_document(d).ok(),
276 &|d, r| backend().get_html_fragment(d, r).ok(),
277 &|d, r: DataRef<Solutions>| {
278 backend().get_reference(&r.with_doc(d.clone())).ok()
279 },
280 &|d, r: DataRef<GradingNote>| {
281 backend().get_reference(&r.with_doc(d.clone())).ok()
282 },
283 )
284 .map_err(|e| format!("{e:#}"))?;
285 serde_json::to_string(&quiz).map_err(|e| format!("{e:#}"))
286 }
287 let state = self.inner.state().clone();
288 let mut client = self.inner.client().clone();
289 Box::pin(async move {
290 let url: UrlOrFile = params.uri.into();
291 tokio::task::spawn_blocking(move || match get_res(url, state) {
292 Err(e) => {
293 let _ = client.show_message(lsp::ShowMessageParams {
294 typ: lsp::MessageType::ERROR,
295 message: e.clone(),
296 });
297 Err(ResponseError::new(async_lsp::ErrorCode::REQUEST_FAILED, e))
298 }
299 Ok(r) => Ok(r),
300 })
301 .map_err(|e| ResponseError::new(async_lsp::ErrorCode::REQUEST_FAILED, e.to_string()))
302 .await?
303 })
304 }
305
306 fn build(
307 doc: &LSPDocument,
308 uri: &impl std::fmt::Display,
309 stale_only: bool,
310 ) -> Result<(), String> {
311 let Some(id) = doc.archive().map(|a| a.archive_id()) else {
312 return Err(format!("Containing archive for {uri} not found"));
313 };
314 let Some(rel_path) = doc.relative_path() else {
315 return Err(format!("relative path for {uri} not found"));
316 };
317 flams_system::building::queue_manager::QueueManager::get().with_global(move |queue| {
319 queue.enqueue_archive(
320 id,
321 FormatOrTargets::Format(flams_stex::STEX.id()),
322 stale_only,
323 Some(RelPath::new(rel_path)),
324 false,
325 )
326 });
327 Ok(())
328 }
329
330 pub(crate) fn build_one(&mut self, params: UriParams) -> Res<()> {
331 let state = self.inner.state().clone();
332 fut(move || {
333 let url: UrlOrFile = params.uri.into();
334 let Some(doc) = state.get(&url) else {
335 return Err(format!("Document not found: {url}"));
336 };
337 Self::build(&doc, &url, false)
338 })
339 }
340 pub(crate) fn build_all(&mut self, params: UriParams) -> Res<()> {
341 let state = self.inner.state().clone();
342 let client = self.inner.client().clone();
343 wrap_fut(async move {
344 let istate = state.clone();
345 let url = block(move || {
346 let url: UrlOrFile = params.uri.into();
347 let Some(doc) = istate.get(&url) else {
348 return Err(format!("Document not found: {url}"));
349 };
350 Self::build(&doc, &url, false)?;
351 Ok(url)
352 })
353 .await?;
354 let deps = triomphe::Arc::new(parking_lot::Mutex::new(vec![url]));
355 let mut curr = 0;
356 loop {
357 let url = {
358 let dps = deps.lock();
359 if curr == dps.len() {
360 break;
361 }
362 let d = dps[curr].clone();
363 drop(dps);
364 d
365 };
366 curr += 1;
367 let Some(d) = state.get(&url) else {
368 continue;
370 };
371 let iclient = client.clone();
374 let d_archive = d.archive().cloned();
375 let Some(vec) = d
376 .with_annots_block(state.clone(), move |annots| {
377 let mut client = iclient;
378 <AnnotIter as TreeChildIter<STeXAnnot>>::dfs(AnnotIter::from(
379 annots.annotations.iter(),
380 ))
381 .filter_map(|a| match a {
382 STeXAnnot::Inputref {
383 archive, filepath, ..
384 }
385 | STeXAnnot::MHInput {
386 archive, filepath, ..
387 }
388 | STeXAnnot::IncludeProblem {
389 archive, filepath, ..
390 } => {
391 let archive = archive
392 .as_ref()
393 .map(|a| &a.0)
394 .unwrap_or_else(|| unwrap!(d_archive.as_ref()).archive_id());
395 let Some(uri) = crate::annotations::uri_from_archive_relpath(
396 &archive,
397 &filepath.0,
398 ) else {
399 let _ = client.show_message(lsp::ShowMessageParams {
400 typ: lsp::MessageType::ERROR,
401 message: format!(
402 "Could not find file [{archive}]{{{}}}",
403 &filepath.0
404 ),
405 });
406 return None;
407 };
408 let url: UrlOrFile = uri.into();
409 Some(url)
410 }
411 STeXAnnot::ImportModule { module, .. }
412 | STeXAnnot::UseModule { module, .. } => {
413 let Some(uri) = module
414 .full_path
415 .as_ref()
416 .and_then(|e| lsp::Url::from_file_path(e).ok())
417 else {
418 let _ = client.show_message(lsp::ShowMessageParams {
419 typ: lsp::MessageType::ERROR,
420 message: format!(
421 "Could not find module file {}",
422 &module.uri
423 ),
424 });
425 return None;
426 };
427 let url: UrlOrFile = uri.into();
428 Some(url)
429 }
430 _ => None,
431 })
432 .collect::<Vec<_>>()
433 })
434 .await
435 else {
436 continue;
437 };
438 for url in vec {
439 {
440 let mut dep_lock = deps.lock();
441 if dep_lock.contains(&url) {
442 continue;
443 }
444 dep_lock.push(url.clone());
445 }
446 let state = state.clone();
447 let mut client = client.clone();
448 block(move || {
449 let Some(doc) = state.force_get(&url) else {
450 let _ = client.show_message(lsp::ShowMessageParams {
451 typ: lsp::MessageType::ERROR,
452 message: format!("Could not find document {url}"),
453 });
454 return Ok(());
455 };
456 if let Err(e) = Self::build(&doc, &url, true) {
457 let _ = client.show_message(lsp::ShowMessageParams {
458 typ: lsp::MessageType::ERROR,
459 message: format!("Could not queue document {url}: {e}"),
460 });
461 }
462 Ok(())
463 })
464 .await?
465 }
466 }
467 Ok(())
468 })
469 }
470
471 pub(crate) fn reload(
472 &mut self,
473 _: crate::ReloadParams,
474 ) -> <Self as LanguageServer>::NotifyResult {
475 let state = self.inner.state().clone();
476 let client = self.inner.client().clone();
477 tracing::info!("LSP: reload");
478 state.backend().reset::<TokioEngine>();
479 let _ = tokio::task::spawn_blocking(|| {
480 for e in flams_system::iter::<flams_system::FlamsExtension>() {
481 (e.on_reload)();
482 }
483 });
484 let _ = tokio::task::spawn_blocking(move || {
485 state.load_mathhubs(client.clone());
486 client.update_mathhub();
487 });
488 ControlFlow::Continue(())
489 }
490
491 pub(crate) fn install(
492 &mut self,
493 params: crate::InstallParams,
494 ) -> <Self as LanguageServer>::NotifyResult {
495 let state = self.inner.state().clone();
496 let client = self.inner.client().clone();
497 let mut progress = ProgressCallbackServer::new(client, "Installing".to_string(), None);
498 let _ = tokio::task::spawn(async move {
499 let crate::InstallParams {
500 archives,
501 remote_url,
502 } = params;
503 let mut rescan = false;
504 let archives = {
505 let mut ret = Vec::new();
506 let exis = GlobalBackend.all_archives();
507 for a in archives {
508 if exis.iter().any(|e| *e.id() == a) || ret.contains(&a) {
509 continue;
510 }
511 ret.push(a);
512 }
513 ret
514 };
515 let len = archives.len();
516 for (i, a) in archives.into_iter().enumerate() {
517 let url = format!("{remote_url}/api/backend/download?id={a}");
518 let prefix = format!("{}/{len}: {a}", i + 1);
519 progress.update(prefix.clone(), None);
520 if flams_system::zip::unzip_from_remote(a.clone(), &url, |p| {
521 progress.update(format!("{prefix}: {}", p.display()), None)
522 })
523 .await
524 .is_err()
525 {
526 let _ = progress.client_mut().show_message(lsp::ShowMessageParams {
527 message: format!("Failed to install archive {a}"),
528 typ: lsp::MessageType::ERROR,
529 });
530 } else {
531 rescan = true;
532 }
533 }
534 let client = progress.client();
535 drop(progress);
536 if rescan {
537 state.backend().reset::<TokioEngine>();
538 let _ = tokio::task::spawn_blocking(|| {
539 for e in flams_system::iter::<flams_system::FlamsExtension>() {
540 (e.on_reload)();
541 }
542 });
543 let _ = tokio::task::spawn_blocking(move || {
544 state.load_mathhubs(client.clone());
546 client.update_mathhub();
547 });
548 } else {
549 client.update_mathhub();
550 }
551 });
552 ControlFlow::Continue(())
553 }
554
555 fn update_backend(
556 files: impl Iterator<Item = PathBuf>,
557 ) -> <Self as LanguageServer>::NotifyResult {
558 let backend = GlobalBackend.get();
559 let mut dones = Vec::new();
560 let mut missing = false;
561 for f in files {
562 if backend
563 .archive_of(&f, |a, _| {
564 if !dones.contains(a.id()) {
565 dones.push(a.id().clone());
566 a.update_sources();
567 }
568 })
569 .is_none()
570 {
571 missing = true;
572 }
573 }
574 if missing {
575 backend.load(flams_system::settings::Settings::get().mathhubs());
576 }
577 ControlFlow::Continue(())
578 }
579}
580
581type Res<T> = BoxFuture<'static, Result<T, ResponseError>>;
582
583impl<T: FLAMSLSPServer> LanguageServer for ServerWrapper<T> {
584 type Error = ResponseError;
585 type NotifyResult = ControlFlow<async_lsp::Result<()>>;
586
587 fn initialize(&mut self, params: lsp::InitializeParams) -> Res<lsp::InitializeResult> {
588 tracing::info!("LSP: initialize");
589 self.inner.initialize(
590 params
591 .workspace_folders
592 .unwrap_or_default()
593 .into_iter()
594 .map(|f| (f.name, f.uri)),
595 );
596 Box::pin(std::future::ready({
597 Ok(lsp::InitializeResult {
598 capabilities: super::capabilities::capabilities(),
599 server_info: None,
600 })
601 }))
602 }
603
604 fn shutdown(&mut self, (): ()) -> Res<()> {
605 tracing::info!("LSP: shutdown");
606 Box::pin(std::future::ready(Ok(())))
607 }
608
609 fn initialized(&mut self, _params: lsp::InitializedParams) -> Self::NotifyResult {
613 tracing::info!("LSP: initialized");
614 self.inner.initialized();
615 ControlFlow::Continue(())
618 }
619
620 impl_notification!(!exit = Exit);
621
622 impl_notification!(!did_change_workspace_folders = DidChangeWorkspaceFolders);
624 impl_notification!(!did_change_configuration = DidChangeConfiguration);
625 impl_notification!(!did_change_watched_files = DidChangeWatchedFiles);
626
627 fn did_open(&mut self, params: lsp::DidOpenTextDocumentParams) -> Self::NotifyResult {
630 let document = params.text_document;
631 tracing::trace!(
632 "URI: {}, language: {}, version: {}, text: \n{}",
633 document.uri,
634 document.language_id,
635 document.version,
636 document.text
637 );
638 self.inner
639 .state()
640 .insert(document.uri.into(), document.text);
641 ControlFlow::Continue(())
642 }
643
644 #[allow(clippy::let_underscore_future)]
645 fn did_change(&mut self, params: lsp::DidChangeTextDocumentParams) -> Self::NotifyResult {
647 let document = params.text_document;
648 let uri = document.uri.clone().into();
649 if let Some(d) = self.inner.state().get(&uri) {
650 for change in params.content_changes {
651 tracing::trace!(
652 "URI: {},version: {}, text: \"{}\", range: {:?}",
653 document.uri,
654 document.version,
655 change.text,
656 change.range
657 );
658 d.delta(change.text, change.range);
659 }
660 let mut client = self.inner.client().clone();
661 let _ = tokio::spawn(d.with_annots(self.inner.state().clone(), move |a| {
662 let r = lsp::PublishDiagnosticsParams {
663 uri: document.uri,
664 diagnostics: a.diagnostics.iter().map(to_diagnostic).collect(),
665 version: None,
666 };
667 let _ = client.publish_diagnostics(r);
668 }));
669 } else {
670 tracing::warn!("document not found: {}", document.uri);
671 }
672 ControlFlow::Continue(())
673 }
674
675 #[allow(clippy::let_underscore_future)]
676 fn did_save(&mut self, params: lsp::DidSaveTextDocumentParams) -> Self::NotifyResult {
678 tracing::trace!("did_save: {}", params.text_document.uri);
679 let state = self.inner.state().clone();
680 let client = self.inner.client().clone();
681 let uri = params.text_document.uri.into();
682 let _ = tokio::task::spawn_blocking(move || {
683 state.build_html_and_notify(&uri, client);
684 });
685 ControlFlow::Continue(())
686 }
687
688 impl_notification!(!will_save = WillSaveTextDocument);
689
690 fn did_close(&mut self, params: lsp::DidCloseTextDocumentParams) -> Self::NotifyResult {
692 tracing::trace!("did_close: {}", params.text_document.uri);
693 ControlFlow::Continue(())
694 }
695
696 impl_notification!(work_done_progress_cancel = WorkDoneProgressCancel);
699
700 impl_notification!(!set_trace = SetTrace);
702 impl_notification!(!cancel_request = Cancel);
703 impl_notification!(!progress = Progress);
704
705 fn document_symbol(
711 &mut self,
712 params: lsp::DocumentSymbolParams,
713 ) -> Res<Option<lsp::DocumentSymbolResponse>> {
714 tracing::trace_span!("document_symbol").in_scope(move || {
715 tracing::trace!(
716 "uri: {},work_done_progress_params: {:?}, partial_results: {:?}",
717 params.text_document.uri,
718 params.work_done_progress_params,
719 params.partial_result_params
720 );
721 let p = params
722 .work_done_progress_params
723 .work_done_token
724 .map(|tk| self.get_progress(tk));
725 self.inner
726 .state()
727 .get_symbols(¶ms.text_document.uri.into(), p)
728 .map_or_else(
729 || Box::pin(std::future::ready(Ok(None))) as _,
730 |f| Box::pin(f.map(Result::Ok)) as _,
731 )
732 })
733 }
734
735 fn document_diagnostic(
737 &mut self,
738 params: lsp::DocumentDiagnosticParams,
739 ) -> Res<lsp::DocumentDiagnosticReportResult> {
740 fn default() -> lsp::DocumentDiagnosticReportResult {
741 lsp::DocumentDiagnosticReportResult::Report(lsp::DocumentDiagnosticReport::Full(
742 lsp::RelatedFullDocumentDiagnosticReport::default(),
743 ))
744 }
745 tracing::trace_span!("document_diagnostics").in_scope(move || {
746 tracing::trace!("work_done_progress_params: {:?}, partial_results: {:?}, position: {:?}, context: {:?}",
747 params.work_done_progress_params,
748 params.partial_result_params,
749 params.text_document,
750 params.identifier
751 );
752
753 let p = params.work_done_progress_params.work_done_token.map(
754 |tk| self.get_progress(tk)
755 );
756 self.inner.state().get_diagnostics(¶ms.text_document.uri.into(),p)
757 .map_or_else(|| Box::pin(std::future::ready(Ok(default()))) as _,
758 |f| Box::pin(f.map(Result::Ok)) as _
759 )
760 })
761 }
762
763 fn references(&mut self, params: lsp::ReferenceParams) -> Res<Option<Vec<lsp::Location>>> {
765 tracing::trace_span!("references").in_scope(move || {
766 tracing::trace!("work_done_progress_params: {:?}, partial_results: {:?}, position: {:?}, context: {:?}",
767 params.work_done_progress_params,
768 params.partial_result_params,
769 params.text_document_position,
770 params.context
771 );
772 let p = params.work_done_progress_params.work_done_token.map(
773 |tk| self.get_progress(tk)
774 );
775 self.inner.state().get_references(
776 params.text_document_position.text_document.uri.into(),
777 params.text_document_position.position,p
778 ).map_or_else(|| Box::pin(std::future::ready(Ok(None))) as _,
779 |f| Box::pin(f.map(Result::Ok)) as _
780 )
781 })
782 }
783
784 fn document_link(
786 &mut self,
787 params: lsp::DocumentLinkParams,
788 ) -> Res<Option<Vec<lsp::DocumentLink>>> {
789 tracing::trace_span!("document_link").in_scope(move || {
790 tracing::trace!(
791 "uri: {},work_done_progress_params: {:?}, partial_results: {:?}",
792 params.text_document.uri,
793 params.work_done_progress_params,
794 params.partial_result_params
795 );
796 let p = params
797 .work_done_progress_params
798 .work_done_token
799 .map(|tk| self.get_progress(tk));
800 self.inner
801 .state()
802 .get_links(¶ms.text_document.uri.into(), p)
803 .map_or_else(
804 || Box::pin(std::future::ready(Ok(None))) as _,
805 |f| Box::pin(f.map(Result::Ok)) as _,
806 )
807 })
808 }
809
810 fn hover(&mut self, params: lsp::HoverParams) -> Res<Option<lsp::Hover>> {
812 tracing::trace_span!("hover").in_scope(move || {
813 tracing::trace!(
814 "uri: {},work_done_progress_params: {:?}, position: {:?}",
815 params.text_document_position_params.text_document.uri,
816 params.work_done_progress_params,
817 params.text_document_position_params.position
818 );
819 let p = params
820 .work_done_progress_params
821 .work_done_token
822 .map(|tk| self.get_progress(tk));
823 self.inner
824 .state()
825 .get_hover(
826 ¶ms
827 .text_document_position_params
828 .text_document
829 .uri
830 .into(),
831 params.text_document_position_params.position,
832 p,
833 )
834 .map_or_else(
835 || Box::pin(std::future::ready(Ok(None))) as _,
836 |f| Box::pin(f.map(Result::Ok)) as _,
837 )
838 })
839 }
840
841 fn definition(
843 &mut self,
844 params: lsp::GotoDefinitionParams,
845 ) -> Res<Option<lsp::GotoDefinitionResponse>> {
846 tracing::trace_span!("definition").in_scope(move || {
847 tracing::trace!(
848 "uri: {},work_done_progress_params: {:?}, position: {:?}",
849 params.text_document_position_params.text_document.uri,
850 params.work_done_progress_params,
851 params.text_document_position_params.position
852 );
853 let p = params
854 .work_done_progress_params
855 .work_done_token
856 .map(|tk| self.get_progress(tk));
857 self.inner
858 .state()
859 .get_goto_definition(
860 params
861 .text_document_position_params
862 .text_document
863 .uri
864 .into(),
865 params.text_document_position_params.position,
866 p,
867 )
868 .map_or_else(
869 || Box::pin(std::future::ready(Ok(None))) as _,
870 |f| Box::pin(f.map(Result::Ok)) as _,
871 )
872 })
873 }
874
875 impl_request!(! code_lens = CodeLensRequest => (None));
876
877 impl_request!(! declaration = GotoDefinition => (None));
878
879 impl_request!(! workspace_diagnostic = WorkspaceDiagnosticRequest => (lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {items:Vec::new()})));
880 fn inlay_hint(&mut self, params: lsp::InlayHintParams) -> Res<Option<Vec<lsp::InlayHint>>> {
927 tracing::trace_span!("inlay hint").in_scope(move || {
928 tracing::trace!(
929 "uri: {},work_done_progress_params: {:?}",
930 params.text_document.uri,
931 params.work_done_progress_params,
932 );
933 let p = params
934 .work_done_progress_params
935 .work_done_token
936 .map(|tk| self.get_progress(tk));
937 self.inner
938 .state()
939 .get_inlay_hints(¶ms.text_document.uri.into(), p)
940 .map_or_else(
941 || Box::pin(std::future::ready(Ok(None))) as _,
942 |f| Box::pin(f.map(Result::Ok)) as _,
943 )
944 })
945 }
946 impl_request!(inlay_hint_resolve = InlayHintResolveRequest);
948
949 fn code_action(
951 &mut self,
952 params: lsp::CodeActionParams,
953 ) -> Res<Option<lsp::CodeActionResponse>> {
954 tracing::trace_span!("code_action").in_scope(move || {
955 tracing::trace!(
956 "uri: {},work_done_progress_params: {:?}; range: {:?}; context:{:?}",
957 params.text_document.uri, params.work_done_progress_params,
959 params.range,
960 params.context
961 );
962 let p = params
963 .work_done_progress_params
964 .work_done_token
965 .map(|tk| self.get_progress(tk));
966 self.inner
967 .state()
968 .get_codeaction(
969 params.text_document.uri.into(),
970 params.range,
971 params.context,
972 p,
973 )
974 .map_or_else(
975 || Box::pin(std::future::ready(Ok(None))) as _,
976 |f| Box::pin(f.map(Result::Ok)) as _,
977 )
978 })
979 }
980
981 fn prepare_call_hierarchy(
983 &mut self,
984 params: lsp::CallHierarchyPrepareParams,
985 ) -> Res<Option<Vec<lsp::CallHierarchyItem>>> {
986 tracing::trace_span!("prepare_call_hierarchy").in_scope(move || {
987 tracing::trace!(
988 "uri: {},work_done_progress_params: {:?}; position: {:?}",
989 params.text_document_position_params.text_document.uri,
990 params.work_done_progress_params,
991 params.text_document_position_params.position
992 );
993 let p = params
994 .work_done_progress_params
995 .work_done_token
996 .map(|tk| self.get_progress(tk));
997 self.inner
998 .state()
999 .prepare_module_hierarchy(
1000 params
1001 .text_document_position_params
1002 .text_document
1003 .uri
1004 .into(),
1005 p,
1006 )
1007 .map_or_else(
1008 || Box::pin(std::future::ready(Ok(None))) as _,
1009 |f| Box::pin(f.map(Result::Ok)) as _,
1010 )
1011 })
1012 }
1013
1014 fn incoming_calls(
1017 &mut self,
1018 params: lsp::CallHierarchyIncomingCallsParams,
1019 ) -> Res<Option<Vec<lsp::CallHierarchyIncomingCall>>> {
1020 tracing::trace_span!("incoming_call_hierarchy").in_scope(move || {
1021 tracing::trace!(
1022 "uri: {},work_done_progress_params: {:?};",
1023 params.item.uri,
1024 params.work_done_progress_params,
1025 );
1026 let p = params
1027 .work_done_progress_params
1028 .work_done_token
1029 .map(|tk| self.get_progress(tk));
1030 if let Some(d) = params
1031 .item
1032 .data
1033 .and_then(|d| d.as_str().and_then(|d| d.parse().ok()))
1034 {
1035 self.inner
1036 .state()
1037 .module_hierarchy_imports(params.item.uri, params.item.kind, d, p)
1038 .map_or_else(
1039 || Box::pin(std::future::ready(Ok(None))) as _,
1040 |f| Box::pin(f.map(Result::Ok)) as _,
1041 )
1042 } else {
1043 Box::pin(std::future::ready(Ok(None))) as _
1044 }
1045 })
1046 }
1047 impl_request!(outgoing_calls = CallHierarchyOutgoingCalls);
1048
1049 impl_request!(! document_highlight = DocumentHighlightRequest => (None));
1050 impl_request!(! folding_range = FoldingRangeRequest => (None));
1051
1052 impl_request!(implementation = GotoImplementation);
1053 impl_request!(type_definition = GotoTypeDefinition);
1054 impl_request!(document_color = DocumentColor);
1055 impl_request!(color_presentation = ColorPresentationRequest);
1056 impl_request!(selection_range = SelectionRangeRequest);
1057 impl_request!(moniker = MonikerRequest);
1058 impl_request!(inline_value = InlineValueRequest);
1059 impl_request!(on_type_formatting = OnTypeFormatting);
1060 impl_request!(range_formatting = RangeFormatting);
1061 impl_request!(formatting = Formatting);
1062 impl_request!(prepare_type_hierarchy = TypeHierarchyPrepare);
1063 impl_request!(will_save_wait_until = WillSaveWaitUntil);
1064
1065 impl_request!(!completion = Completion => (None));
1066
1067 impl_request!(signature_help = SignatureHelpRequest);
1068 impl_request!(linked_editing_range = LinkedEditingRange);
1069
1070 fn semantic_tokens_full(
1073 &mut self,
1074 params: lsp::SemanticTokensParams,
1075 ) -> Res<Option<lsp::SemanticTokensResult>> {
1076 tracing::trace_span!("semantic_tokens_full").in_scope(|| {
1077 tracing::trace!(
1078 "work_done_progress_params: {:?}, partial_results: {:?}, uri: {}",
1079 params.work_done_progress_params,
1080 params.partial_result_params,
1081 params.text_document.uri
1082 );
1083 let p = params
1084 .work_done_progress_params
1085 .work_done_token
1086 .map(|tk| self.get_progress(tk));
1087 self.inner
1088 .state()
1089 .get_semantic_tokens(¶ms.text_document.uri.into(), p, None)
1090 .map_or_else(
1091 || Box::pin(std::future::ready(Ok(None))) as _,
1092 |f| Box::pin(f.map(|r| Ok(r.map(lsp::SemanticTokensResult::Tokens)))) as _,
1093 )
1094 })
1095 }
1096
1097 fn semantic_tokens_range(
1099 &mut self,
1100 params: lsp::SemanticTokensRangeParams,
1101 ) -> Res<Option<lsp::SemanticTokensRangeResult>> {
1102 tracing::trace_span!("semantic_tokens_range").in_scope(|| {
1103 tracing::trace!(
1104 "work_done_progress_params: {:?}, partial_results: {:?}, range: {:?}, uri:{}",
1105 params.work_done_progress_params,
1106 params.partial_result_params,
1107 params.range,
1108 params.text_document.uri
1109 );
1110 let p = params
1111 .work_done_progress_params
1112 .work_done_token
1113 .map(|tk| self.get_progress(tk));
1114 self.inner
1115 .state()
1116 .get_semantic_tokens(¶ms.text_document.uri.into(), p, Some(params.range))
1117 .map_or_else(
1118 || Box::pin(std::future::ready(Ok(None))) as _,
1119 |f| Box::pin(f.map(|r| Ok(r.map(lsp::SemanticTokensRangeResult::Tokens)))) as _,
1120 )
1121 })
1122 }
1123
1124 fn semantic_tokens_full_delta(
1126 &mut self,
1127 params: lsp::SemanticTokensDeltaParams,
1128 ) -> Res<Option<lsp::SemanticTokensFullDeltaResult>> {
1129 tracing::info_span!("semantic_tokens_full_delta").in_scope(|| {
1130 tracing::info!("work_done_progress_params: {:?}, partial_results: {:?}, previous_result_id: {:?}, uri:{}",
1131 params.work_done_progress_params,
1132 params.partial_result_params,
1133 params.previous_result_id,
1134 params.text_document.uri
1135 );
1136 Box::pin(std::future::ready(Ok(None)))
1137 })
1138 }
1139
1140 fn did_create_files(&mut self, params: lsp::CreateFilesParams) -> Self::NotifyResult {
1142 let files = params.files.into_iter().map(|f| PathBuf::from(f.uri));
1143 Self::update_backend(files)
1144 }
1145
1146 fn did_rename_files(&mut self, params: lsp::RenameFilesParams) -> Self::NotifyResult {
1148 let files = params
1149 .files
1150 .into_iter()
1151 .flat_map(|f| vec![PathBuf::from(f.old_uri), PathBuf::from(f.new_uri)]);
1152 Self::update_backend(files)
1153 }
1154
1155 fn did_delete_files(&mut self, params: lsp::DeleteFilesParams) -> Self::NotifyResult {
1157 let files = params.files.into_iter().map(|f| PathBuf::from(f.uri));
1158 Self::update_backend(files)
1159 }
1160
1161 impl_request!(prepare_rename = PrepareRenameRequest);
1162 impl_request!(rename = Rename);
1163
1164 impl_request!(will_create_files = WillCreateFiles);
1166 impl_request!(will_rename_files = WillRenameFiles);
1167 impl_request!(will_delete_files = WillDeleteFiles);
1168 impl_request!(symbol = WorkspaceSymbolRequest);
1169 impl_request!(execute_command = ExecuteCommand);
1170
1171 impl_request!(supertypes = TypeHierarchySupertypes);
1173 impl_request!(subtypes = TypeHierarchySubtypes);
1174
1175 impl_request!(completion_item_resolve = ResolveCompletionItem);
1177
1178 impl_request!(code_action_resolve = CodeActionResolveRequest);
1180
1181 impl_request!(workspace_symbol_resolve = WorkspaceSymbolResolve);
1183
1184 impl_request!(code_lens_resolve = CodeLensResolve);
1186
1187 impl_request!(document_link_resolve = DocumentLinkResolve);
1189}
1190
1191fn export_html(uri: &DocumentUri, client: ClientSocket, to: &Path) -> Result<(), String> {
1192 use std::fmt::Write;
1193 let progress =
1194 ProgressCallbackServer::new(client, format!("Exporting {}", uri.document_name()), None);
1195 let mut images = rustc_hash::FxHashSet::default();
1196 let mut all_documents = Vec::new();
1197 let inputs = to.join("aux");
1198 std::fs::create_dir_all(&inputs).map_err(|e| e.to_string())?;
1199 recurse_export(uri, &progress, &inputs, &mut images, &mut all_documents)?;
1200 let htmlstr = backend().get_html_full(uri).map_err(|e| e.to_string())?;
1202 let htmlstr = subst_img(htmlstr, "aux/", &mut images)?.into_string();
1203 let mut replaces = String::new();
1204 for (i, u) in all_documents.into_iter().enumerate() {
1205 if replaces.is_empty() {
1206 replaces.push_str(",\nredirects:[");
1207 } else {
1208 replaces.push(',');
1209 }
1210 let _ = write!(&mut replaces, "[\"{u}\",\"aux/{i}.json\"]");
1211 }
1212 if !replaces.is_empty() {
1213 replaces.push(']');
1214 }
1215 let htmlstr = htmlstr.replace(
1216 "</head>",
1217 &format!(
1218 r#"
1219 <script type="text/javascript" id="ftml">
1220 window.FTML_CONFIG = {{
1221 documentUri:"{uri}",
1222 backendUrl:"https://mathhub.info",
1223 logLevel:"INFO"
1224 {replaces}
1225 }};
1226 </script>
1227 <script src="https://mathhub.info/ftml.js"></script>
1228 </head>"#
1229 ),
1230 );
1231 let index = to.join("index.html");
1232 let mut out = std::fs::File::create_new(index).map_err(|e| e.to_string())?;
1233 out.write_all(htmlstr.as_bytes())
1234 .map_err(|e| e.to_string())?;
1235 out.flush().map_err(|e| e.to_string())?;
1236 Ok(())
1237}
1238
1239fn recurse_export(
1240 uri: &DocumentUri,
1241 progress: &ProgressCallbackServer,
1242 out: &Path,
1243 images: &mut rustc_hash::FxHashSet<Box<str>>,
1244 all_documents: &mut Vec<DocumentUri>,
1245) -> Result<(), String> {
1246 all_documents.push(uri.clone());
1247 let doc = backend().get_document(uri).map_err(|e| e.to_string())?;
1248 for e in doc.dfs() {
1249 if let DocumentElementRef::DocumentReference { target, .. } = e
1250 && !all_documents.contains(target)
1251 {
1252 let idx = all_documents.len();
1253 progress.update(format!("{idx}: {target}"), None);
1254 let path = out.join(format!("{idx}.json"));
1255 do_document(target, &path, images)?;
1256 recurse_export(target, progress, out, images, all_documents)?;
1257 }
1258 }
1259 Ok(())
1260}
1261
1262fn do_document(
1263 uri: &DocumentUri,
1264 path: &Path,
1265 images: &mut rustc_hash::FxHashSet<Box<str>>,
1266) -> Result<(), String> {
1267 let (css, htmlstr) = backend().get_html_body(uri).map_err(|e| e.to_string())?;
1268 let htmlstr = subst_img(htmlstr, "", images)?;
1269 let out = std::fs::File::create_new(path).map_err(|e| e.to_string())?;
1270 serde_json::to_writer(std::io::BufWriter::new(out), &(htmlstr, css)).map_err(|e| e.to_string())
1271}
1272
1273fn subst_img(
1274 htmlstr: Box<str>,
1275 prefix: &str,
1276 images: &mut rustc_hash::FxHashSet<Box<str>>,
1277) -> Result<Box<str>, String> {
1278 let mut i = 0;
1279 let mut htmlstr = htmlstr.into_string();
1280 while let Some(j) = htmlstr[i..].find("data-ftml-src=") {
1282 i = i + j + "data-ftml-src=".len();
1283 let delim: u8 = htmlstr.as_bytes()[i];
1284 i += 1;
1285 let Some(j) = htmlstr[i..].find(delim as char) else {
1286 return Err(format!("Missing delimiter at {}", &htmlstr[i..i + 20]));
1287 };
1288 let img_url = &htmlstr[i..i + j];
1289 htmlstr = format!("{}{}{}", &htmlstr[..i], img_url, &htmlstr[i + j..]);
1291 }
1292 Ok(htmlstr.into_boxed_str())
1293}