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