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