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