Skip to main content

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    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(&params.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        //tracing::info!("Queueing [{id}]{{{rel_path}}}");
318        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                    // should be unreachable ?
369                    continue;
370                };
371                //let deps = deps.clone();
372                //let state = state.clone();
373                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                    // <- necessary, but I don't quite understand why
545                    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    // Notifications -------------------------------------------
610
611    //impl_notification!(! initialized = Initialized);
612    fn initialized(&mut self, _params: lsp::InitializedParams) -> Self::NotifyResult {
613        tracing::info!("LSP: initialized");
614        self.inner.initialized();
615        /*
616         */
617        ControlFlow::Continue(())
618    }
619
620    impl_notification!(!exit = Exit);
621
622    // workspace/
623    impl_notification!(!did_change_workspace_folders = DidChangeWorkspaceFolders);
624    impl_notification!(!did_change_configuration = DidChangeConfiguration);
625    impl_notification!(!did_change_watched_files = DidChangeWatchedFiles);
626
627    // textDocument/
628    //impl_notification!(! did_open = DidOpenTextDocument);
629    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    //impl_notification!(! did_change = DidChangeTextDocument);
646    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    //impl_notification!(! did_save = DidSaveTextDocument);
677    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    //impl_notification!(! did_close = DidCloseTextDocument);
691    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    // window/
697    // workDoneProgress/
698    impl_notification!(work_done_progress_cancel = WorkDoneProgressCancel);
699
700    // $/
701    impl_notification!(!set_trace = SetTrace);
702    impl_notification!(!cancel_request = Cancel);
703    impl_notification!(!progress = Progress);
704
705    // Requests -----------------------------------------------
706
707    // textDocument/
708
709    // impl_request!(document_symbol = DocumentSymbolRequest);
710    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(&params.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    // impl_request!(! document_diagnostic = DocumentDiagnosticRequest => (lsp::DocumentDiagnosticReportResult::Report(lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport::default()))));
736    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(&params.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    //impl_request!(? references = References => (None));
764    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    //impl_request!(! document_link = DocumentLinkRequest => (None));
785    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(&params.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    // impl_request!(! hover = HoverRequest => (None));
811    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                    &params
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    // impl_request!(! definition = GotoDefinition => (None));
842    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    /*
881        #[must_use]
882        fn workspace_diagnostic(&mut self, params: lsp::WorkspaceDiagnosticParams) -> Res<lsp::WorkspaceDiagnosticReportResult> {
883            tracing::info_span!("workspace_diagnostics").in_scope(move || {
884                tracing::info!("work_done_progress_params: {:?}, partial_results: {:?}, identifier: {:?}, previous_results_id: {:?}",
885                    params.work_done_progress_params,
886                    params.partial_result_params,
887                    params.identifier,
888                    params.previous_result_ids
889                );
890                if let Some(_token) = params.partial_result_params.partial_result_token {
891                    if self.ws_diagnostics.load(Ordering::Relaxed) {
892                        self.ws_diagnostics.store(false, Ordering::Relaxed);
893                        return Box::pin(std::future::ready(Ok(
894                            lsp::WorkspaceDiagnosticReportResult::Partial(lsp::WorkspaceDiagnosticReportPartialResult {
895                                items:Vec::new()
896                            })
897                        )))
898                    }
899
900                    self.ws_diagnostics.store(true, Ordering::Relaxed);
901                    return Box::pin(std::future::ready(Ok(
902                        lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
903                            items:Vec::new()
904                        })
905                    )))
906                }
907
908                /*
909                if let Some(p) = params.work_done_progress_params.work_done_token {
910                    self.get_progress(p).finish_delay();
911                }
912                if let Some(p) = params.partial_result_params.partial_result_token {
913                    self.get_progress(p).finish_delay();
914                }
915                */
916                Box::pin(std::future::ready(Ok(
917                    lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
918                        items:Vec::new()
919                    })
920                )))
921            })
922        }
923    */
924
925    //impl_request!(! inlay_hint = InlayHintRequest => (None));
926    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(&params.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    // inlayHint/
947    impl_request!(inlay_hint_resolve = InlayHintResolveRequest);
948
949    //impl_request!(! code_action = CodeActionRequest => (None));
950    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, //.text_document_position_params.text_document.uri,
958                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    //impl_request!(prepare_call_hierarchy = CallHierarchyPrepare);
982    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    // callHierarchy/
1015    //impl_request!(incoming_calls = CallHierarchyIncomingCalls);
1016    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    // semanticTokens/
1071    // impl_request!(semantic_tokens_full = SemanticTokensFullRequest);
1072    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(&params.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    // impl_request!(semantic_tokens_range = SemanticTokensRangeRequest);
1098    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(&params.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    // impl_request!(semantic_tokens_full_delta = SemanticTokensFullDeltaRequest);
1125    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    //impl_notification!(!did_create_files = DidCreateFiles);
1141    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    //impl_notification!(!did_rename_files = DidRenameFiles);
1147    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    //impl_notification!(!did_delete_files = DidDeleteFiles);
1156    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    // workspace/
1165    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    // typeHierarchy/
1172    impl_request!(supertypes = TypeHierarchySupertypes);
1173    impl_request!(subtypes = TypeHierarchySubtypes);
1174
1175    // completionItem/
1176    impl_request!(completion_item_resolve = ResolveCompletionItem);
1177
1178    // codeAction/
1179    impl_request!(code_action_resolve = CodeActionResolveRequest);
1180
1181    // workspaceSymbol/
1182    impl_request!(workspace_symbol_resolve = WorkspaceSymbolResolve);
1183
1184    // codeLens/
1185    impl_request!(code_lens_resolve = CodeLensResolve);
1186
1187    // documentLink/
1188    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    //
1201    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    // images:
1281    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        // TODO: find image, add to list
1290        htmlstr = format!("{}{}{}", &htmlstr[..i], img_url, &htmlstr[i + j..]);
1291    }
1292    Ok(htmlstr.into_boxed_str())
1293}