flams_lsp/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3pub mod annotations;
4pub mod capabilities;
5pub mod documents;
6mod implementation;
7pub mod state;
8#[cfg(feature = "ws")]
9pub mod ws;
10
11use std::{collections::hash_map::Entry, path::Path};
12
13pub use async_lsp;
14use async_lsp::{lsp_types as lsp, ClientSocket, LanguageClient};
15use flams_math_archives::backend::AnyBackend;
16use flams_stex::quickparse::stex::{
17    structs::{GetModuleError, ModuleReference, STeXModuleStore},
18    STeXParseData,
19};
20use flams_system::settings::Settings;
21use flams_utils::{
22    background,
23    prelude::HMap,
24    sourcerefs::{LSPLineCol, SourceRange},
25    unwrap,
26};
27use ftml_uris::{ArchiveId, BaseUri, DocumentUri};
28use state::{DocData, LSPState, UrlOrFile};
29
30static GLOBAL_STATE: std::sync::OnceLock<LSPState> = std::sync::OnceLock::new();
31pub struct STDIOLSPServer {
32    client: ClientSocket,
33    on_port: tokio::sync::watch::Receiver<Option<u16>>,
34    workspaces: Vec<(String, lsp::Url)>,
35}
36
37impl STDIOLSPServer {
38    #[inline]
39    pub fn global_state() -> Option<&'static LSPState> {
40        GLOBAL_STATE.get()
41    }
42    fn load_all(&self) {
43        let client = self.client.clone();
44        let state = unwrap!(Self::global_state().clone());
45        for (name, uri) in &self.workspaces {
46            tracing::info!("workspace: {name}@{uri}");
47        }
48        background(move || state.load_mathhubs(client));
49    }
50
51    fn new_router(
52        client: ClientSocket,
53        on_port: tokio::sync::watch::Receiver<Option<u16>>,
54    ) -> async_lsp::router::Router<ServerWrapper<Self>> {
55        let _ = GLOBAL_STATE.set(LSPState::default());
56        let server = ServerWrapper::new(Self {
57            client,
58            on_port,
59            workspaces: Vec::new(),
60        });
61        server.router()
62    }
63}
64
65#[allow(clippy::future_not_send)]
66/// #### Panics
67pub async fn start_lsp(on_port: tokio::sync::watch::Receiver<Option<u16>>) {
68    let (server, _client) = async_lsp::MainLoop::new_server(|client| {
69        tower::ServiceBuilder::new()
70            .layer(async_lsp::tracing::TracingLayer::default())
71            .layer(async_lsp::server::LifecycleLayer::default())
72            .layer(async_lsp::panic::CatchUnwindLayer::default())
73            .layer(async_lsp::concurrency::ConcurrencyLayer::default())
74            .layer(async_lsp::client_monitor::ClientProcessMonitorLayer::new(
75                client.clone(),
76            ))
77            .service(STDIOLSPServer::new_router(client, on_port))
78    });
79
80    #[cfg(unix)]
81    let (stdin, stdout) = (
82        async_lsp::stdio::PipeStdin::lock_tokio().expect("Failed to lock stdin"),
83        async_lsp::stdio::PipeStdout::lock_tokio().expect("Failed to lock stdout"),
84    );
85    #[cfg(not(unix))]
86    let (stdin, stdout) = (
87        tokio_util::compat::TokioAsyncReadCompatExt::compat(tokio::io::stdin()),
88        tokio_util::compat::TokioAsyncWriteCompatExt::compat_write(tokio::io::stdout()),
89    );
90
91    server
92        .run_buffered(stdin, stdout)
93        .await
94        .expect("Failed to run server");
95}
96
97impl FLAMSLSPServer for STDIOLSPServer {
98    #[inline]
99    fn client_mut(&mut self) -> &mut ClientSocket {
100        &mut self.client
101    }
102    #[inline]
103    fn client(&self) -> &ClientSocket {
104        &self.client
105    }
106    #[inline]
107    fn state(&self) -> &LSPState {
108        Self::global_state().unwrap_or_else(|| unreachable!())
109    }
110    fn initialized(&mut self) {
111        let v = *self.on_port.borrow();
112        if v.is_some() {
113            if let Err(r) = self.client.notify::<ServerURL>(ServerURL::get()) {
114                tracing::error!("failed to send notification: {}", r);
115            }
116        } else {
117            let mut port = self.on_port.clone();
118            let client = self.client.clone();
119            tokio::spawn(async move {
120                let _ = port
121                    .wait_for(|e| {
122                        e.map_or(false, |_| {
123                            if let Err(r) = client.notify::<ServerURL>(ServerURL::get()) {
124                                tracing::error!("failed to send notification: {}", r);
125                            };
126                            true
127                        })
128                    })
129                    .await;
130            });
131        }
132        tracing::info!(
133            "Using {} threads",
134            tokio::runtime::Handle::current().metrics().num_workers()
135        );
136        //#[cfg(not(debug_assertions))]
137        {
138            self.load_all();
139        }
140    }
141
142    fn initialize<I: Iterator<Item = (String, lsp::Url)> + Send + 'static>(
143        &mut self,
144        workspaces: I,
145    ) {
146        self.workspaces = workspaces.collect();
147    }
148}
149
150#[derive(serde::Serialize, serde::Deserialize)]
151struct ReloadParams {}
152struct Reload;
153impl lsp::notification::Notification for Reload {
154    type Params = ReloadParams;
155    const METHOD: &str = "flams/reload";
156}
157
158#[derive(serde::Serialize, serde::Deserialize)]
159struct InstallParams {
160    pub archives: Vec<ArchiveId>,
161    pub remote_url: String,
162}
163struct InstallArchives;
164impl lsp::notification::Notification for InstallArchives {
165    type Params = InstallParams;
166    const METHOD: &str = "flams/install";
167}
168
169#[derive(serde::Serialize, serde::Deserialize)]
170struct NewArchiveParams {
171    pub archive: ArchiveId,
172    pub urlbase: BaseUri,
173}
174struct NewArchive;
175impl lsp::notification::Notification for NewArchive {
176    type Params = NewArchiveParams;
177    const METHOD: &str = "flams/newArchive";
178}
179
180pub(crate) struct StandaloneExport;
181#[derive(serde::Serialize, serde::Deserialize)]
182pub struct StandaloneExportParams {
183    pub uri: lsp::Url,
184    pub target: std::path::PathBuf,
185}
186impl lsp::notification::Notification for StandaloneExport {
187    type Params = StandaloneExportParams;
188    const METHOD: &str = "flams/standaloneExport";
189}
190
191pub(crate) struct HtmlExport;
192impl lsp::notification::Notification for HtmlExport {
193    type Params = StandaloneExportParams;
194    const METHOD: &str = "flams/htmlExport";
195}
196
197struct UpdateMathHub;
198impl lsp::notification::Notification for UpdateMathHub {
199    type Params = ();
200    const METHOD: &str = "flams/updateMathHub";
201}
202
203struct OpenFile;
204impl lsp::notification::Notification for OpenFile {
205    type Params = lsp::Url;
206    const METHOD: &str = "flams/openFile";
207}
208
209struct HTMLResult;
210impl lsp::notification::Notification for HTMLResult {
211    type Params = String;
212    const METHOD: &str = "flams/htmlResult";
213}
214
215#[derive(serde::Serialize, serde::Deserialize)]
216pub struct HtmlRequestParams {
217    pub uri: lsp::Url,
218}
219pub(crate) struct HTMLRequest;
220impl lsp::request::Request for HTMLRequest {
221    type Params = HtmlRequestParams;
222    type Result = Option<String>;
223    const METHOD: &'static str = "flams/htmlRequest";
224}
225
226#[derive(serde::Serialize, serde::Deserialize)]
227struct BuildParams {
228    pub uri: lsp::Url,
229}
230struct BuildOne;
231impl lsp::request::Request for BuildOne {
232    type Params = BuildParams;
233    type Result = ();
234    const METHOD: &str = "flams/buildOne";
235}
236struct BuildAll;
237impl lsp::request::Request for BuildAll {
238    type Params = BuildParams;
239    type Result = ();
240    const METHOD: &str = "flams/buildAll";
241}
242
243pub(crate) struct QuizRequest;
244#[derive(serde::Serialize, serde::Deserialize)]
245pub struct QuizRequestParams {
246    pub uri: lsp::Url,
247}
248impl lsp::request::Request for QuizRequest {
249    type Params = QuizRequestParams;
250    type Result = String;
251    const METHOD: &'static str = "flams/quizRequest";
252}
253
254pub struct ServerURL;
255impl ServerURL {
256    fn get() -> String {
257        let settings = Settings::get();
258        format!("http://{}:{}", settings.ip, settings.port())
259    }
260}
261impl lsp::notification::Notification for ServerURL {
262    type Params = String;
263    const METHOD: &str = "flams/serverURL";
264}
265
266pub trait ClientExt {
267    fn html_result(&self, uri: &DocumentUri);
268    fn update_mathhub(&self);
269    fn open_file(&self, path: &Path);
270}
271impl ClientExt for ClientSocket {
272    #[inline]
273    fn html_result(&self, uri: &DocumentUri) {
274        let _ = self.notify::<HTMLResult>(uri.to_string());
275    }
276    #[inline]
277    fn update_mathhub(&self) {
278        if let Err(e) = self.notify::<UpdateMathHub>(()) {
279            tracing::error!("failed to send notification: {}", e);
280            return;
281        }
282    }
283
284    fn open_file(&self, path: &Path) {
285        let Ok(url) = lsp::Url::from_file_path(path) else {
286            return;
287        };
288        if let Err(e) = self.notify::<OpenFile>(url) {
289            tracing::error!("failed to send notification: {}", e);
290            return;
291        }
292    }
293}
294
295pub trait FLAMSLSPServer: 'static {
296    fn client_mut(&mut self) -> &mut ClientSocket;
297    fn client(&self) -> &ClientSocket;
298    fn state(&self) -> &LSPState;
299    #[inline]
300    fn initialized(&mut self) {}
301    #[inline]
302    fn initialize<I: Iterator<Item = (String, lsp::Url)> + Send + 'static>(
303        &mut self,
304        _workspaces: I,
305    ) {
306    }
307}
308
309pub struct ServerWrapper<T: FLAMSLSPServer> {
310    pub inner: T,
311}
312impl<T: FLAMSLSPServer> ServerWrapper<T> {
313    #[inline]
314    pub const fn new(inner: T) -> Self {
315        Self { inner }
316    }
317
318    pub fn router(self) -> async_lsp::router::Router<Self> {
319        let mut r = async_lsp::router::Router::from_language_server(self);
320        r.request::<HTMLRequest, _>(Self::html_request);
321        r.request::<QuizRequest, _>(Self::quiz_request);
322        r.request::<BuildOne, _>(Self::build_one);
323        r.request::<BuildAll, _>(Self::build_all);
324
325        r.notification::<Reload>(Self::reload);
326        r.notification::<StandaloneExport>(Self::export_standalone);
327        r.notification::<HtmlExport>(Self::export_html);
328        r.notification::<InstallArchives>(Self::install);
329        r.notification::<NewArchive>(Self::new_archive);
330        //r.request(handler)
331        r
332    }
333
334    pub fn get_progress(&self, tk: lsp::ProgressToken) -> ProgressCallbackClient {
335        match &tk {
336            lsp::ProgressToken::Number(n) if *n > 0 => {
337                TOKEN.fetch_max(*n as u32 + 1, std::sync::atomic::Ordering::Relaxed);
338            }
339            _ => (),
340        }
341        ProgressCallbackClient {
342            client: self.inner.client().clone(),
343            token: tk,
344        }
345    }
346}
347
348pub struct LSPStore<'a, const FULL: bool> {
349    pub(crate) map: &'a mut HMap<UrlOrFile, DocData>,
350    cycles: Vec<DocumentUri>,
351}
352impl<'a, const FULL: bool> LSPStore<'a, FULL> {
353    #[inline]
354    pub fn new(map: &'a mut HMap<UrlOrFile, DocData>) -> Self {
355        Self {
356            map,
357            cycles: Vec::new(),
358        }
359    }
360
361    pub fn load(&mut self, p: &Path, uri: &DocumentUri) -> Option<STeXParseData> {
362        let text = std::fs::read_to_string(p).ok()?;
363        let r = flams_stex::quickparse::stex::quickparse(uri, &text, p, &AnyBackend::Global, self)
364            .lock();
365        Some(r)
366    }
367
368    fn load_as_false(&mut self, p: &Path, uri: &DocumentUri) -> Option<STeXParseData> {
369        if !FULL {
370            self.load(p, uri)
371        } else {
372            let mut nstore = LSPStore::<'_, false>::new(self.map);
373            nstore.cycles = std::mem::take(&mut self.cycles);
374            let r = nstore.load(p, uri);
375            self.cycles = nstore.cycles;
376            r
377        }
378    }
379}
380
381impl<const FULL: bool> STeXModuleStore for &mut LSPStore<'_, FULL> {
382    const FULL: bool = FULL;
383    fn get_module(
384        &mut self,
385        module: &ModuleReference,
386        _in_path: Option<&std::sync::Arc<Path>>,
387    ) -> Result<STeXParseData, GetModuleError> {
388        let Some(p) = module.full_path.as_ref() else {
389            return Err(GetModuleError::NotFound(module.uri.clone()));
390        };
391        let uri = &module.in_doc;
392        if let Some(i) = self.cycles.iter().position(|u| u == uri) {
393            let mut ret = self.cycles[i..].to_vec();
394            ret.push(uri.clone());
395            return Err(GetModuleError::Cycle(ret));
396        }
397
398        macro_rules! do_return {
399            ($e:expr) => {{
400                /*if TRACK_DEPS {
401                  if let Some(in_path) = in_path {
402                    if module.uri != *flams_ontology::metatheory::URI {
403                      $e.lock().dependents.insert_clone(in_path);
404                    }
405                  }
406                }*/
407                return Ok($e);
408            }};
409        }
410
411        let lsp_uri = UrlOrFile::File(p.clone());
412        //let lsp_uri = lsp::Url::from_file_path(p).map_err(|_| GetModuleError::NotFound(module.uri.clone()))?;
413        match self.map.get(&lsp_uri) {
414            Some(DocData::Data(d, _)) => do_return!(d.clone()),
415            Some(DocData::Doc(d)) if d.is_up_to_date() => do_return!(d.annotations.clone()),
416            _ => (),
417        }
418
419        self.cycles.push(uri.clone());
420        let r = self
421            .load_as_false(p, uri)
422            .inspect(|ret| match self.map.entry(lsp_uri) {
423                Entry::Vacant(e) => {
424                    e.insert(DocData::Data(ret.clone(), FULL));
425                }
426                Entry::Occupied(mut e) => {
427                    e.get_mut().merge(DocData::Data(ret.clone(), FULL));
428                }
429            });
430        /*if TRACK_DEPS {
431          if let Some(r) = &r {
432            if let Some(in_path) = in_path {
433              if module.uri != *flams_ontology::metatheory::URI {
434                r.lock().dependencies.insert_clone(in_path);
435              }
436            }
437          }
438        }*/
439        self.cycles.pop();
440        r.ok_or_else(|| GetModuleError::NotFound(module.uri.clone()))
441    }
442}
443
444pub trait IsLSPRange {
445    fn into_range(self) -> lsp::Range;
446    fn from_range(range: lsp::Range) -> Self;
447}
448
449impl IsLSPRange for SourceRange<LSPLineCol> {
450    fn into_range(self) -> lsp::Range {
451        lsp::Range {
452            start: lsp::Position {
453                line: self.start.line,
454                character: self.start.col,
455            },
456            end: lsp::Position {
457                line: self.end.line,
458                character: self.end.col,
459            },
460        }
461    }
462    fn from_range(range: lsp::Range) -> Self {
463        Self {
464            start: LSPLineCol {
465                line: range.start.line,
466                col: range.start.character,
467            },
468            end: LSPLineCol {
469                line: range.end.line,
470                col: range.end.character,
471            },
472        }
473    }
474}
475
476pub struct ProgressCallbackServer {
477    client: ClientSocket,
478    token: u32,
479    handle: tokio::task::JoinHandle<()>,
480    progress: Option<parking_lot::Mutex<(u32, u32)>>,
481}
482
483lazy_static::lazy_static! {
484  static ref TOKEN:triomphe::Arc<std::sync::atomic::AtomicU32> = triomphe::Arc::new(std::sync::atomic::AtomicU32::new(42));
485}
486
487impl ProgressCallbackServer {
488    #[inline]
489    pub fn client_mut(&mut self) -> &mut ClientSocket {
490        &mut self.client
491    }
492
493    #[inline]
494    pub fn client(&self) -> ClientSocket {
495        self.client.clone()
496    }
497
498    pub fn with<R>(
499        client: ClientSocket,
500        title: String,
501        total: Option<u32>,
502        f: impl FnOnce(Self) -> R,
503    ) -> R {
504        let slf = Self::new(client, title, total);
505        f(slf)
506    }
507
508    #[must_use]
509    #[allow(clippy::let_underscore_future)]
510    pub fn new(mut client: ClientSocket, title: String, total: Option<u32>) -> Self {
511        let token = TOKEN.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
512        let f = client.work_done_progress_create(lsp::WorkDoneProgressCreateParams {
513            token: lsp::NumberOrString::Number(token as _),
514        });
515        let mut c = client.clone();
516        let handle = tokio::spawn(async move {
517            if let Err(e) = f.await {
518                tracing::error!("Error: {}", e);
519            } else {
520                let _ = c.progress(async_lsp::lsp_types::ProgressParams {
521                    token: async_lsp::lsp_types::ProgressToken::Number(token as _),
522                    value: async_lsp::lsp_types::ProgressParamsValue::WorkDone(
523                        async_lsp::lsp_types::WorkDoneProgress::Begin(
524                            async_lsp::lsp_types::WorkDoneProgressBegin {
525                                message: None,
526                                title,
527                                percentage: total.map(|_| 0),
528                                cancellable: None,
529                            },
530                        ),
531                    ),
532                });
533            }
534        });
535        //tracing::info!("New progress with id {token}");
536        Self {
537            client,
538            token,
539            handle,
540            progress: total.map(|i| parking_lot::Mutex::new((0, i))),
541        }
542    }
543
544    pub fn update(&self, message: String, add_step: Option<u32>) {
545        let (message, percentage) = if let Some(i) = add_step {
546            if let Some(lock) = self.progress.as_ref() {
547                let mut lock = lock.lock();
548                lock.0 += i;
549                (
550                    format!("{}/{}:{message}", lock.0, lock.1),
551                    Some(100 * lock.0 / lock.1),
552                )
553            } else {
554                (message, None)
555            }
556        } else if let Some(lock) = self.progress.as_ref() {
557            let lock = lock.lock();
558            (
559                format!("{}/{}:{message}", lock.0, lock.1),
560                Some(100 * lock.0 / lock.1),
561            )
562        } else {
563            (message, None)
564        };
565        let token = async_lsp::lsp_types::ProgressToken::Number(self.token as _);
566        //tracing::info!("updating progress {}",self.token);
567        while !self.handle.is_finished() {
568            std::thread::sleep(std::time::Duration::from_millis(10));
569        }
570        let _ = self
571            .client
572            .clone()
573            .progress(async_lsp::lsp_types::ProgressParams {
574                token,
575                value: async_lsp::lsp_types::ProgressParamsValue::WorkDone(
576                    async_lsp::lsp_types::WorkDoneProgress::Report(
577                        async_lsp::lsp_types::WorkDoneProgressReport {
578                            message: Some(message),
579                            percentage,
580                            cancellable: None,
581                        },
582                    ),
583                ),
584            });
585    }
586}
587
588impl Drop for ProgressCallbackServer {
589    fn drop(&mut self) {
590        let token = async_lsp::lsp_types::ProgressToken::Number(self.token as _);
591        let _ = self.client.progress(async_lsp::lsp_types::ProgressParams {
592            token,
593            value: async_lsp::lsp_types::ProgressParamsValue::WorkDone(
594                async_lsp::lsp_types::WorkDoneProgress::End(
595                    async_lsp::lsp_types::WorkDoneProgressEnd {
596                        message: Some("done".to_string()),
597                    },
598                ),
599            ),
600        });
601    }
602}
603
604pub struct ProgressCallbackClient {
605    client: ClientSocket,
606    token: async_lsp::lsp_types::ProgressToken,
607}
608
609impl ProgressCallbackClient {
610    pub fn finish(mut self) {
611        let _ = self.client.progress(async_lsp::lsp_types::ProgressParams {
612            token: self.token,
613            value: async_lsp::lsp_types::ProgressParamsValue::WorkDone(
614                async_lsp::lsp_types::WorkDoneProgress::End(
615                    async_lsp::lsp_types::WorkDoneProgressEnd {
616                        message: Some("done".to_string()),
617                    },
618                ),
619            ),
620        });
621    }
622
623    #[allow(clippy::let_underscore_future)]
624    pub fn finish_delay(self) {
625        let _ = tokio::spawn(async move {
626            tokio::time::sleep(tokio::time::Duration::from_secs_f32(0.5)).await;
627            self.finish();
628        });
629    }
630}