1use crate::config::{FstarConfig, LspSettings};
4use crate::document::{DocumentState, FragmentStatus, StatusUpdate};
5use crate::error::Result;
6use dashmap::DashMap;
7use std::sync::Arc;
8use tokio::sync::{mpsc, RwLock};
9use tower_lsp::jsonrpc::Result as RpcResult;
10use tower_lsp::lsp_types::{
11 CompletionOptions, CompletionParams, CompletionResponse, DidChangeConfigurationParams,
12 DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
13 DidSaveTextDocumentParams, DocumentFormattingParams, DocumentRangeFormattingParams,
14 GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams,
15 HoverProviderCapability, InitializeParams, InitializeResult, InitializedParams, MarkupContent,
16 MarkupKind, MessageType, OneOf, Position, Range, SaveOptions, ServerCapabilities, ServerInfo,
17 TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
18 TextDocumentSyncSaveOptions, TextEdit, Url, WorkspaceFolder,
19};
20use tower_lsp::{Client, LanguageServer};
21use tracing::{debug, error, info};
22
23pub mod requests {
29 use serde::{Deserialize, Serialize};
30 use tower_lsp::lsp_types::{Range, TextDocumentIdentifier};
31
32 pub type GetDiagnosticsParams = TextDocumentIdentifier;
34
35 pub type GetFragmentsParams = TextDocumentIdentifier;
37
38 #[derive(Debug, Clone, Serialize, Deserialize)]
44 pub struct FragmentInfo {
45 pub range: Range,
47 pub status: String,
49 #[serde(skip_serializing_if = "std::ops::Not::not")]
51 pub stale: bool,
52 }
53}
54
55pub mod notifications {
57 use serde::{Deserialize, Serialize};
58 use tower_lsp::lsp_types::notification::Notification;
59
60 #[derive(Debug)]
62 pub enum StatusNotification {}
63
64 impl Notification for StatusNotification {
65 type Params = StatusParams;
66 const METHOD: &'static str = "$/fstar/status";
67 }
68
69 #[derive(Debug, Clone, Serialize, Deserialize)]
70 pub struct StatusParams {
71 pub uri: String,
72 pub fragments: Vec<FragmentStatus>,
73 }
74
75 #[derive(Debug, Clone, Serialize, Deserialize)]
76 pub struct FragmentStatus {
77 pub kind: String,
78 pub range: tower_lsp::lsp_types::Range,
79 #[serde(skip_serializing_if = "std::ops::Not::not")]
80 pub stale: bool,
81 }
82
83 #[allow(dead_code)]
90 #[derive(Debug)]
91 pub enum VerifyToPosition {}
92
93 impl Notification for VerifyToPosition {
94 type Params = VerifyToPositionParams;
95 const METHOD: &'static str = "$/fstar/verifyToPosition";
96 }
97
98 #[derive(Debug, Clone, Serialize, Deserialize)]
99 pub struct VerifyToPositionParams {
100 pub uri: String,
101 pub position: tower_lsp::lsp_types::Position,
102 pub lax: bool,
103 }
104
105 #[allow(dead_code)]
109 #[derive(Debug)]
110 pub enum Restart {}
111
112 impl Notification for Restart {
113 type Params = RestartParams;
114 const METHOD: &'static str = "$/fstar/restart";
115 }
116
117 #[derive(Debug, Clone, Serialize, Deserialize)]
118 pub struct RestartParams {
119 pub uri: String,
120 }
121
122 #[allow(dead_code)]
126 #[derive(Debug)]
127 pub enum KillAndRestartSolver {}
128
129 impl Notification for KillAndRestartSolver {
130 type Params = KillAndRestartSolverParams;
131 const METHOD: &'static str = "$/fstar/killAndRestartSolver";
132 }
133
134 #[derive(Debug, Clone, Serialize, Deserialize)]
135 pub struct KillAndRestartSolverParams {
136 pub uri: String,
137 }
138
139 #[allow(dead_code)]
143 #[derive(Debug)]
144 pub enum KillAll {}
145
146 impl Notification for KillAll {
147 type Params = KillAllParams;
148 const METHOD: &'static str = "$/fstar/killAll";
149 }
150
151 #[derive(Debug, Clone, Serialize, Deserialize)]
153 pub struct KillAllParams {}
154
155 #[derive(Debug, Clone, Serialize, Deserialize)]
157 pub struct GetTranslatedFstParams {
158 pub uri: String,
159 pub position: tower_lsp::lsp_types::Position,
160 }
161
162 #[derive(Debug, Clone, Serialize, Deserialize)]
163 pub struct GetTranslatedFstResponse {
164 pub uri: String,
165 pub position: tower_lsp::lsp_types::Position,
166 }
167}
168
169pub struct FstarServer {
171 client: Client,
173
174 documents: DashMap<String, Arc<DocumentState>>,
176
177 settings: RwLock<LspSettings>,
179
180 status_tx: mpsc::Sender<StatusUpdate>,
182
183 workspace_folders: RwLock<Vec<WorkspaceFolder>>,
185
186 flycheck_handles: DashMap<String, tokio::task::AbortHandle>,
189}
190
191impl FstarServer {
192 pub fn new(client: Client, cli_settings: LspSettings) -> Self {
194 let (status_tx, status_rx) = mpsc::channel(100);
195
196 let server = Self {
197 client: client.clone(),
198 documents: DashMap::new(),
199 settings: RwLock::new(cli_settings),
200 status_tx,
201 workspace_folders: RwLock::new(Vec::new()),
202 flycheck_handles: DashMap::new(),
203 };
204
205 tokio::spawn(Self::status_update_handler(client, status_rx));
207
208 server
209 }
210
211 async fn status_update_handler(client: Client, mut rx: mpsc::Receiver<StatusUpdate>) {
213 while let Some(update) = rx.recv().await {
214 let params = notifications::StatusParams {
215 uri: update.uri.to_string(),
216 fragments: update
217 .fragments
218 .iter()
219 .map(|f| notifications::FragmentStatus {
220 kind: match f.status {
221 FragmentStatus::Ok => "ok",
222 FragmentStatus::LaxOk => "lax-ok",
223 FragmentStatus::Started => "started",
224 FragmentStatus::Failed => "failed",
225 }
226 .to_string(),
227 range: f.range,
228 stale: f.stale,
229 })
230 .collect(),
231 };
232
233 client
234 .send_notification::<notifications::StatusNotification>(params)
235 .await;
236
237 client
239 .publish_diagnostics(update.uri, update.diagnostics, None)
240 .await;
241 }
242 }
243
244 async fn get_or_create_document(
246 &self,
247 uri: &Url,
248 text: Option<String>,
249 version: i32,
250 ) -> Result<Arc<DocumentState>> {
251 let key = uri.to_string();
252
253 if let Some(doc) = self.documents.get(&key) {
254 return Ok(Arc::clone(&doc));
255 }
256
257 let file_path = uri
259 .to_file_path()
260 .map_err(|_| crate::error::FstarError::Config("Invalid URI".to_string()))?;
261
262 let ws_folders: Vec<std::path::PathBuf> = self
264 .workspace_folders
265 .read()
266 .await
267 .iter()
268 .filter_map(|f| Url::parse(&f.uri.to_string()).ok()?.to_file_path().ok())
269 .collect();
270
271 let mut config = match FstarConfig::find_and_load(&file_path, &ws_folders).await? {
272 Some((config, _)) => config,
273 None => FstarConfig::default(),
274 };
275
276 let settings = self.settings.read().await;
278 if config.fstar_exe.is_none() {
279 if let Some(ref exe) = settings.fstar_exe {
280 config.fstar_exe = Some(exe.clone());
281 }
282 }
283 drop(settings);
284
285 let settings = self.settings.read().await.clone();
286 let doc = Arc::new(
287 DocumentState::new(
288 uri.clone(),
289 text.unwrap_or_default(),
290 version,
291 config,
292 settings,
293 self.status_tx.clone(),
294 )
295 .await?,
296 );
297
298 if let Err(e) = doc.initialize().await {
300 error!("Failed to initialize F* for {}: {}", uri, e);
301 self.client
302 .show_message(MessageType::ERROR, format!("Failed to start F*: {}", e))
303 .await;
304 return Err(e);
305 }
306
307 self.documents.insert(key, Arc::clone(&doc));
308 Ok(doc)
309 }
310
311 fn get_document(&self, uri: &Url) -> Option<Arc<DocumentState>> {
313 self.documents.get(&uri.to_string()).map(|r| Arc::clone(&r))
314 }
315
316 async fn remove_document(&self, uri: &Url) {
318 if let Some((_, doc)) = self.documents.remove(&uri.to_string()) {
319 doc.dispose().await;
320 }
321 }
322}
323
324#[tower_lsp::async_trait]
325impl LanguageServer for FstarServer {
326 async fn initialize(&self, params: InitializeParams) -> RpcResult<InitializeResult> {
327 info!("F* LSP server initializing");
328
329 if let Some(folders) = params.workspace_folders {
331 *self.workspace_folders.write().await = folders;
332 }
333
334 if let Some(options) = params.initialization_options {
337 if let Ok(client_settings) = serde_json::from_value::<LspSettings>(options) {
338 let mut settings = self.settings.write().await;
339
340 let cli_fstar_exe = settings.fstar_exe.clone();
342
343 settings.verify_on_open = client_settings.verify_on_open;
345 settings.verify_on_save = client_settings.verify_on_save;
346 settings.fly_check = client_settings.fly_check;
347 settings.timeout_ms = client_settings.timeout_ms;
348 settings.max_processes = client_settings.max_processes;
349
350 settings.debug = settings.debug || client_settings.debug;
352
353 if cli_fstar_exe.is_some() {
355 settings.fstar_exe = cli_fstar_exe;
356 } else {
357 settings.fstar_exe = client_settings.fstar_exe;
358 }
359 }
360 }
361
362 Ok(InitializeResult {
363 capabilities: ServerCapabilities {
364 text_document_sync: Some(TextDocumentSyncCapability::Options(
365 TextDocumentSyncOptions {
366 open_close: Some(true),
367 change: Some(TextDocumentSyncKind::INCREMENTAL),
368 save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions {
369 include_text: Some(false),
370 })),
371 ..Default::default()
372 },
373 )),
374 hover_provider: Some(HoverProviderCapability::Simple(true)),
375 completion_provider: Some(CompletionOptions {
376 trigger_characters: Some(vec![".".to_string()]),
377 ..Default::default()
378 }),
379 definition_provider: Some(OneOf::Left(true)),
380 document_formatting_provider: Some(OneOf::Left(true)),
381 document_range_formatting_provider: Some(OneOf::Left(true)),
382 ..Default::default()
383 },
384 server_info: Some(ServerInfo {
385 name: "fstar-lsp".to_string(),
386 version: Some(env!("CARGO_PKG_VERSION").to_string()),
387 }),
388 })
389 }
390
391 async fn initialized(&self, _: InitializedParams) {
392 info!("F* LSP server initialized");
393 }
394
395 async fn shutdown(&self) -> RpcResult<()> {
396 info!("F* LSP server shutting down");
397
398 let docs: Vec<Arc<DocumentState>> = self
402 .documents
403 .iter()
404 .map(|entry| Arc::clone(entry.value()))
405 .collect();
406
407 for doc in docs {
409 doc.dispose().await;
410 }
411 self.documents.clear();
412
413 Ok(())
414 }
415
416 async fn did_open(&self, params: DidOpenTextDocumentParams) {
417 let uri = params.text_document.uri;
418 let text = params.text_document.text;
419 let version = params.text_document.version;
420
421 debug!("Document opened: {}", uri);
422
423 match self.get_or_create_document(&uri, Some(text), version).await {
424 Ok(doc) => {
425 let settings = self.settings.read().await;
426 if settings.verify_on_open {
427 drop(settings);
428 if let Err(e) = doc.verify_full().await {
429 error!("Verification failed: {}", e);
430 }
431 } else if settings.fly_check {
432 drop(settings);
433 if let Err(e) = doc.verify_lax().await {
434 error!("Lax check failed: {}", e);
435 }
436 }
437 }
438 Err(e) => {
439 error!("Failed to open document: {}", e);
440 }
441 }
442 }
443
444 async fn did_change(&self, params: DidChangeTextDocumentParams) {
445 let uri = params.text_document.uri;
446 let version = params.text_document.version;
447 let key = uri.to_string();
448
449 if let Some(doc) = self.get_document(&uri) {
450 doc.update(params.content_changes, version).await;
451
452 let settings = self.settings.read().await;
454 if settings.fly_check {
455 drop(settings);
456
457 if let Some((_, prev)) = self.flycheck_handles.remove(&key) {
459 prev.abort();
460 }
461
462 let doc = Arc::clone(&doc);
463 let handle_key = key.clone();
464 let handles = self.flycheck_handles.clone();
465 let task = tokio::spawn(async move {
466 tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
467 let _ = doc.verify_lax().await;
469 let _ = doc.verify_cache().await;
473 handles.remove(&handle_key);
474 });
475
476 self.flycheck_handles.insert(key, task.abort_handle());
477 }
478 }
479 }
480
481 async fn did_save(&self, params: DidSaveTextDocumentParams) {
482 let uri = params.text_document.uri;
483
484 if let Some(doc) = self.get_document(&uri) {
485 let settings = self.settings.read().await;
486 if settings.verify_on_save {
487 drop(settings);
488 if let Err(e) = doc.verify_full().await {
489 error!("Verification on save failed: {}", e);
490 }
491 }
492 }
493 }
494
495 async fn did_close(&self, params: DidCloseTextDocumentParams) {
496 let uri = params.text_document.uri;
497 let key = uri.to_string();
498 debug!("Document closed: {}", uri);
499
500 if let Some((_, handle)) = self.flycheck_handles.remove(&key) {
505 handle.abort();
506 }
507
508 self.remove_document(&uri).await;
509 }
510
511 async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
512 if let Ok(client_settings) = serde_json::from_value::<LspSettings>(params.settings) {
513 info!("Configuration updated");
514 let mut settings = self.settings.write().await;
515
516 let cli_fstar_exe = settings.fstar_exe.clone();
518
519 settings.verify_on_open = client_settings.verify_on_open;
521 settings.verify_on_save = client_settings.verify_on_save;
522 settings.fly_check = client_settings.fly_check;
523 settings.timeout_ms = client_settings.timeout_ms;
524 settings.max_processes = client_settings.max_processes;
525
526 settings.debug = settings.debug || client_settings.debug;
528
529 if cli_fstar_exe.is_some() {
531 settings.fstar_exe = cli_fstar_exe;
532 } else {
533 settings.fstar_exe = client_settings.fstar_exe;
534 }
535 }
536 }
537
538 async fn hover(&self, params: HoverParams) -> RpcResult<Option<Hover>> {
539 let uri = params.text_document_position_params.text_document.uri;
540 let position = params.text_document_position_params.position;
541
542 if let Some(doc) = self.get_document(&uri) {
543 if let Some((contents, range)) = doc.hover(position).await {
544 return Ok(Some(Hover {
545 contents: HoverContents::Markup(MarkupContent {
546 kind: MarkupKind::Markdown,
547 value: contents,
548 }),
549 range,
550 }));
551 }
552 }
553
554 Ok(None)
555 }
556
557 async fn goto_definition(
558 &self,
559 params: GotoDefinitionParams,
560 ) -> RpcResult<Option<GotoDefinitionResponse>> {
561 let uri = params.text_document_position_params.text_document.uri;
562 let position = params.text_document_position_params.position;
563
564 if let Some(doc) = self.get_document(&uri) {
565 if let Some(location) = doc.definition(position).await {
566 return Ok(Some(GotoDefinitionResponse::Scalar(location)));
567 }
568 }
569
570 Ok(None)
571 }
572
573 async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> {
574 let uri = params.text_document_position.text_document.uri;
575 let position = params.text_document_position.position;
576
577 if let Some(doc) = self.get_document(&uri) {
578 let items = doc.completions(position).await;
579 if !items.is_empty() {
580 return Ok(Some(CompletionResponse::Array(items)));
581 }
582 }
583
584 Ok(None)
585 }
586
587 async fn formatting(
588 &self,
589 params: DocumentFormattingParams,
590 ) -> RpcResult<Option<Vec<TextEdit>>> {
591 let uri = params.text_document.uri;
592
593 if let Some(doc) = self.get_document(&uri) {
594 if let Some(formatted) = doc.format().await {
595 let end_line = doc.line_count().await;
596
597 return Ok(Some(vec![TextEdit {
598 range: Range {
599 start: Position {
600 line: 0,
601 character: 0,
602 },
603 end: Position {
604 line: end_line,
605 character: 0,
606 },
607 },
608 new_text: formatted,
609 }]));
610 }
611 }
612
613 Ok(None)
614 }
615
616 async fn range_formatting(
617 &self,
618 params: DocumentRangeFormattingParams,
619 ) -> RpcResult<Option<Vec<TextEdit>>> {
620 let uri = params.text_document.uri;
621 let range = params.range;
622
623 if let Some(doc) = self.get_document(&uri) {
630 if let Some(selected_text) = doc.get_text_in_range(range).await {
632 if let Some(formatted) = doc.format_text(&selected_text).await {
634 return Ok(Some(vec![TextEdit {
635 range,
636 new_text: formatted,
637 }]));
638 }
639 }
640 }
641
642 Ok(None)
643 }
644}
645
646impl FstarServer {
648 pub async fn handle_verify_to_position(&self, params: notifications::VerifyToPositionParams) {
650 if let Ok(uri) = Url::parse(¶ms.uri) {
651 if let Some(doc) = self.get_document(&uri) {
652 if let Err(e) = doc.verify_to_position(params.position, params.lax).await {
653 error!("Verify to position failed: {}", e);
654 }
655 }
656 }
657 }
658
659 pub async fn handle_restart(&self, params: notifications::RestartParams) {
661 if let Ok(uri) = Url::parse(¶ms.uri) {
662 if let Some(doc) = self.get_document(&uri) {
663 if let Err(e) = doc.restart().await {
664 error!("Restart failed: {}", e);
665 self.client
666 .show_message(MessageType::ERROR, format!("Failed to restart F*: {}", e))
667 .await;
668 }
669 }
670 }
671 }
672
673 pub async fn handle_kill_and_restart_solver(
675 &self,
676 params: notifications::KillAndRestartSolverParams,
677 ) {
678 if let Ok(uri) = Url::parse(¶ms.uri) {
679 if let Some(doc) = self.get_document(&uri) {
680 if let Err(e) = doc.restart_solver().await {
681 error!("Restart solver failed: {}", e);
682 }
683 }
684 }
685 }
686
687 pub async fn handle_kill_all(&self, _params: notifications::KillAllParams) {
689 let docs: Vec<Arc<DocumentState>> = self
693 .documents
694 .iter()
695 .map(|entry| Arc::clone(entry.value()))
696 .collect();
697
698 for doc in docs {
700 doc.dispose().await;
701 }
702 self.documents.clear();
703 }
704
705 pub async fn handle_get_translated_fst(
708 &self,
709 _params: notifications::GetTranslatedFstParams,
710 ) -> tower_lsp::jsonrpc::Result<Option<notifications::GetTranslatedFstResponse>> {
711 Ok(None)
712 }
713
714 pub async fn handle_get_diagnostics(
721 &self,
722 params: requests::GetDiagnosticsParams,
723 ) -> RpcResult<Vec<tower_lsp::lsp_types::Diagnostic>> {
724 if let Some(doc) = self.get_document(¶ms.uri) {
725 Ok(doc.get_diagnostics().await)
726 } else {
727 Ok(vec![])
728 }
729 }
730
731 pub async fn handle_get_fragments(
737 &self,
738 params: requests::GetFragmentsParams,
739 ) -> RpcResult<Vec<requests::FragmentInfo>> {
740 if let Some(doc) = self.get_document(¶ms.uri) {
741 let fragments = doc.get_fragments().await;
742 Ok(fragments
743 .into_iter()
744 .map(|f| requests::FragmentInfo {
745 range: f.range,
746 status: fragment_status_to_string(f.status),
747 stale: f.stale,
748 })
749 .collect())
750 } else {
751 Ok(vec![])
752 }
753 }
754}
755
756fn fragment_status_to_string(status: FragmentStatus) -> String {
758 match status {
759 FragmentStatus::Ok => "ok",
760 FragmentStatus::LaxOk => "lax-ok",
761 FragmentStatus::Started => "started",
762 FragmentStatus::Failed => "failed",
763 }
764 .to_string()
765}