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)]
68pub 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 {
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
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 return Ok($e);
409 }};
410 }
411
412 let lsp_uri = UrlOrFile::File(p.clone());
413 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 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 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 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}