flams_lsp/
implementation.rs

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(&params.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        //tracing::info!("Queueing [{id}]{{{rel_path}}}");
319        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                    // should be unreachable ?
370                    continue;
371                };
372                //let deps = deps.clone();
373                //let state = state.clone();
374                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                    // <- necessary, but I don't quite understand why
536                    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    // Notifications -------------------------------------------
576
577    //impl_notification!(! initialized = Initialized);
578    fn initialized(&mut self, _params: lsp::InitializedParams) -> Self::NotifyResult {
579        tracing::info!("LSP: initialized");
580        self.inner.initialized();
581        /*
582         */
583        ControlFlow::Continue(())
584    }
585
586    impl_notification!(!exit = Exit);
587
588    // workspace/
589    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    // textDocument/
597    //impl_notification!(! did_open = DidOpenTextDocument);
598    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    //impl_notification!(! did_change = DidChangeTextDocument);
615    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    //impl_notification!(! did_save = DidSaveTextDocument);
646    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    //impl_notification!(! did_close = DidCloseTextDocument);
660    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    // window/
666    // workDoneProgress/
667    impl_notification!(work_done_progress_cancel = WorkDoneProgressCancel);
668
669    // $/
670    impl_notification!(!set_trace = SetTrace);
671    impl_notification!(!cancel_request = Cancel);
672    impl_notification!(!progress = Progress);
673
674    // Requests -----------------------------------------------
675
676    // textDocument/
677
678    // impl_request!(document_symbol = DocumentSymbolRequest);
679    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(&params.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    // impl_request!(! document_diagnostic = DocumentDiagnosticRequest => (lsp::DocumentDiagnosticReportResult::Report(lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport::default()))));
705    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(&params.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    //impl_request!(? references = References => (None));
733    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    //impl_request!(! document_link = DocumentLinkRequest => (None));
754    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(&params.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    // impl_request!(! hover = HoverRequest => (None));
780    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                    &params
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    // impl_request!(! definition = GotoDefinition => (None));
811    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    /*
850        #[must_use]
851        fn workspace_diagnostic(&mut self, params: lsp::WorkspaceDiagnosticParams) -> Res<lsp::WorkspaceDiagnosticReportResult> {
852            tracing::info_span!("workspace_diagnostics").in_scope(move || {
853                tracing::info!("work_done_progress_params: {:?}, partial_results: {:?}, identifier: {:?}, previous_results_id: {:?}",
854                    params.work_done_progress_params,
855                    params.partial_result_params,
856                    params.identifier,
857                    params.previous_result_ids
858                );
859                if let Some(_token) = params.partial_result_params.partial_result_token {
860                    if self.ws_diagnostics.load(Ordering::Relaxed) {
861                        self.ws_diagnostics.store(false, Ordering::Relaxed);
862                        return Box::pin(std::future::ready(Ok(
863                            lsp::WorkspaceDiagnosticReportResult::Partial(lsp::WorkspaceDiagnosticReportPartialResult {
864                                items:Vec::new()
865                            })
866                        )))
867                    }
868
869                    self.ws_diagnostics.store(true, Ordering::Relaxed);
870                    return Box::pin(std::future::ready(Ok(
871                        lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
872                            items:Vec::new()
873                        })
874                    )))
875                }
876
877                /*
878                if let Some(p) = params.work_done_progress_params.work_done_token {
879                    self.get_progress(p).finish_delay();
880                }
881                if let Some(p) = params.partial_result_params.partial_result_token {
882                    self.get_progress(p).finish_delay();
883                }
884                */
885                Box::pin(std::future::ready(Ok(
886                    lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
887                        items:Vec::new()
888                    })
889                )))
890            })
891        }
892    */
893
894    //impl_request!(! inlay_hint = InlayHintRequest => (None));
895    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(&params.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    // inlayHint/
916    impl_request!(inlay_hint_resolve = InlayHintResolveRequest);
917
918    //impl_request!(! code_action = CodeActionRequest => (None));
919    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, //.text_document_position_params.text_document.uri,
927                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    //impl_request!(prepare_call_hierarchy = CallHierarchyPrepare);
951    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    // callHierarchy/
984    //impl_request!(incoming_calls = CallHierarchyIncomingCalls);
985    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    // semanticTokens/
1042    // impl_request!(semantic_tokens_full = SemanticTokensFullRequest);
1043    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(&params.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    // impl_request!(semantic_tokens_range = SemanticTokensRangeRequest);
1069    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(&params.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    // impl_request!(semantic_tokens_full_delta = SemanticTokensFullDeltaRequest);
1096    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    // workspace/
1112    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    // typeHierarchy/
1119    impl_request!(supertypes = TypeHierarchySupertypes);
1120    impl_request!(subtypes = TypeHierarchySubtypes);
1121
1122    // completionItem/
1123    impl_request!(completion_item_resolve = ResolveCompletionItem);
1124
1125    // codeAction/
1126    impl_request!(code_action_resolve = CodeActionResolveRequest);
1127
1128    // workspaceSymbol/
1129    impl_request!(workspace_symbol_resolve = WorkspaceSymbolResolve);
1130
1131    // codeLens/
1132    impl_request!(code_lens_resolve = CodeLensResolve);
1133
1134    // documentLink/
1135    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    //
1148    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    // images:
1228    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        // TODO: find image, add to list
1237        htmlstr = format!("{}{}{}", &htmlstr[..i], img_url, &htmlstr[i + j..]);
1238    }
1239    Ok(htmlstr.into_boxed_str())
1240}