flams_lsp/
lib.rs

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