1use std::ffi::OsStr;
4use std::fmt::Formatter;
5use std::mem;
6use std::path::Component;
7use std::path::PathBuf;
8use std::path::Prefix;
9use std::str::FromStr;
10use std::sync::Arc;
11use std::sync::OnceLock;
12
13use anyhow::Result;
14use notification::Progress;
15use parking_lot::RwLock;
16use request::WorkDoneProgressCreate;
17use serde::Deserialize;
18use serde::Deserializer;
19use serde_json::to_value;
20use struct_patch::Patch;
21use tower_lsp::Client;
22use tower_lsp::LanguageServer;
23use tower_lsp::LspService;
24use tower_lsp::jsonrpc::Error as RpcError;
25use tower_lsp::jsonrpc::ErrorCode;
26use tower_lsp::jsonrpc::Result as RpcResult;
27use tower_lsp::lsp_types::request::WorkspaceConfiguration;
28use tower_lsp::lsp_types::*;
29use tracing::debug;
30use tracing::error;
31use tracing::info;
32use uuid::Uuid;
33use wdl_analysis::Analyzer;
34use wdl_analysis::Config as AnalysisConfig;
35use wdl_analysis::DiagnosticsConfig;
36use wdl_analysis::FeatureFlags;
37use wdl_analysis::IncrementalChange;
38use wdl_analysis::SourceEdit;
39use wdl_analysis::SourcePosition;
40use wdl_analysis::SourcePositionEncoding;
41use wdl_analysis::Validator;
42use wdl_analysis::handlers::WDL_SEMANTIC_TOKEN_MODIFIERS;
43use wdl_analysis::handlers::WDL_SEMANTIC_TOKEN_TYPES;
44use wdl_analysis::path_to_uri;
45use wdl_lint::Linter;
46use wdl_lint::Rule;
47
48use crate::proto;
49
50fn normalize_uri_path(uri: &mut Url) {
56 if uri.scheme() != "file" {
57 return;
58 }
59
60 if let Ok(path) = uri.to_file_path() {
62 let path = if cfg!(windows) {
64 let mut comps = path.components();
65 match comps.next() {
66 Some(Component::Prefix(prefix)) => match prefix.kind() {
67 Prefix::Disk(d) => {
68 let mut path = PathBuf::new();
69 path.push(format!("{}:", d.to_ascii_uppercase() as char));
70 path.extend(comps);
71 path
72 }
73 Prefix::VerbatimDisk(d) => {
74 let mut path = PathBuf::new();
75 path.push(format!(r"\\?\{}:", d.to_ascii_uppercase() as char));
76 path.extend(comps);
77 path
78 }
79 _ => path,
80 },
81 _ => path,
82 }
83 } else {
84 path
85 };
86
87 if let Ok(u) = Url::from_file_path(path) {
88 *uri = u;
89 }
90 }
91}
92
93#[derive(Clone, Copy, Debug, Default)]
95struct ClientSupport {
96 pub watched_files: bool,
99 pub pull_diagnostics: bool,
102 pub work_done_progress: bool,
105 pub did_change_configuration: bool,
107}
108
109impl ClientSupport {
110 pub fn new(capabilities: &ClientCapabilities) -> Self {
112 Self {
113 watched_files: capabilities
114 .workspace
115 .as_ref()
116 .map(|c| {
117 c.did_change_watched_files
118 .as_ref()
119 .map(|c| c.dynamic_registration == Some(true))
120 .unwrap_or(false)
121 })
122 .unwrap_or(false),
123 pull_diagnostics: capabilities
124 .text_document
125 .as_ref()
126 .map(|c| c.diagnostic.is_some())
127 .unwrap_or(false),
128 work_done_progress: capabilities
129 .window
130 .as_ref()
131 .map(|c| c.work_done_progress == Some(true))
132 .unwrap_or(false),
133 did_change_configuration: capabilities
134 .workspace
135 .as_ref()
136 .map(|c| {
137 c.did_change_configuration
138 .as_ref()
139 .map(|c| c.dynamic_registration == Some(true))
140 .unwrap_or(false)
141 })
142 .unwrap_or(false),
143 }
144 }
145}
146
147#[derive(Debug, Clone, Default)]
149struct ProgressToken(Option<String>);
150
151impl ProgressToken {
152 pub async fn new(client: &Client, client_supported: bool) -> Self {
157 if !client_supported {
158 return Self(None);
159 }
160
161 let token = Uuid::new_v4().to_string();
162 if client
163 .send_request::<WorkDoneProgressCreate>(WorkDoneProgressCreateParams {
164 token: NumberOrString::String(token.clone()),
165 })
166 .await
167 .is_err()
168 {
169 return Self(None);
170 }
171
172 Self(Some(token))
173 }
174
175 pub async fn start(
177 &self,
178 client: &Client,
179 title: impl Into<String>,
180 message: impl Into<String>,
181 ) {
182 if let Some(token) = &self.0 {
183 client
184 .send_notification::<Progress>(ProgressParams {
185 token: NumberOrString::String(token.clone()),
186 value: ProgressParamsValue::WorkDone(WorkDoneProgress::Begin(
187 WorkDoneProgressBegin {
188 title: title.into(),
189 cancellable: None,
190 message: Some(message.into()),
191 percentage: Some(0),
192 },
193 )),
194 })
195 .await;
196 }
197 }
198
199 pub async fn update(&self, client: &Client, message: impl Into<String>, percentage: u32) {
201 if let Some(token) = &self.0 {
202 client
203 .send_notification::<Progress>(ProgressParams {
204 token: NumberOrString::String(token.clone()),
205 value: ProgressParamsValue::WorkDone(WorkDoneProgress::Report(
206 WorkDoneProgressReport {
207 cancellable: None,
208 message: Some(message.into()),
209 percentage: Some(percentage),
210 },
211 )),
212 })
213 .await;
214 }
215 }
216
217 pub async fn complete(self, client: &Client, message: impl Into<String>) {
219 if let Some(token) = self.0 {
220 client
221 .send_notification::<Progress>(ProgressParams {
222 token: NumberOrString::String(token),
223 value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(
224 WorkDoneProgressEnd {
225 message: Some(message.into()),
226 },
227 )),
228 })
229 .await;
230 }
231 }
232}
233
234#[derive(Debug, Clone, Patch)]
237#[patch(attribute(derive(Debug, Default, Deserialize)))]
238#[patch(attribute(serde(rename_all = "camelCase")))]
239#[patch(attribute(allow(missing_docs)))]
240pub struct ServerOptions {
241 #[patch(skip)]
245 pub name: String,
246
247 #[patch(skip)]
251 pub version: String,
252
253 pub log_level: LevelFilter,
255
256 #[patch(nesting)]
258 pub lint: LintOptions,
259
260 pub exceptions: Vec<String>,
262
263 pub ignore_filename: Option<String>,
265
266 #[patch(skip)]
268 pub feature_flags: FeatureFlags,
269
270 #[patch(skip)]
272 pub baseline: Option<wdl_lint::Baseline>,
273}
274
275impl Default for ServerOptions {
276 fn default() -> Self {
277 Self {
278 name: String::from(env!("CARGO_CRATE_NAME")),
279 version: String::from(env!("CARGO_PKG_VERSION")),
280 log_level: LevelFilter(tracing::metadata::LevelFilter::ERROR),
281 lint: Default::default(),
282 exceptions: Vec::new(),
283 ignore_filename: None,
284 feature_flags: Default::default(),
285 baseline: None,
286 }
287 }
288}
289
290#[derive(Debug, Default, Clone, PartialEq, Patch)]
292#[patch(attribute(derive(Debug, Default, Deserialize)))]
293#[patch(attribute(allow(missing_docs)))]
294pub struct LintOptions {
295 pub enabled: bool,
297 #[patch(skip)]
299 pub config: Arc<wdl_lint::Config>,
300}
301
302#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
304#[serde(transparent)]
305pub struct LevelFilter(
306 #[serde(deserialize_with = "deserialize_level_filter")] tracing::metadata::LevelFilter,
307);
308
309impl From<tracing::metadata::LevelFilter> for LevelFilter {
310 fn from(level: tracing::metadata::LevelFilter) -> Self {
311 Self(level)
312 }
313}
314
315fn deserialize_level_filter<'de, D>(
317 deserializer: D,
318) -> Result<tracing::metadata::LevelFilter, D::Error>
319where
320 D: Deserializer<'de>,
321{
322 struct LevelFilterVisitor;
323
324 impl<'de> serde::de::Visitor<'de> for LevelFilterVisitor {
325 type Value = tracing::metadata::LevelFilter;
326
327 fn expecting(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
328 write!(formatter, "a level filter string")
329 }
330
331 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
332 where
333 E: serde::de::Error,
334 {
335 tracing::metadata::LevelFilter::from_str(v).map_err(serde::de::Error::custom)
336 }
337 }
338
339 deserializer.deserialize_str(LevelFilterVisitor)
340}
341
342pub type FilterReloadHandle<S> =
344 tracing_subscriber::reload::Handle<tracing::metadata::LevelFilter, S>;
345
346#[derive(Debug)]
348pub struct Server<S> {
349 client: Client,
351 client_support: OnceLock<ClientSupport>,
353 folders: Arc<RwLock<Vec<WorkspaceFolder>>>,
355 config: Arc<tokio::sync::RwLock<ServerConfig>>,
357 log_handle: Option<FilterReloadHandle<S>>,
359}
360
361#[derive(Debug)]
363struct ServerConfig {
364 options: ServerOptions,
366 analyzer: Analyzer<ProgressToken>,
368}
369
370impl ServerOptions {
371 fn analyzer(&self, client: Client) -> Analyzer<ProgressToken> {
373 let linting_enabled = self.lint.enabled;
374 let exceptions = self.exceptions.clone();
375 let ignore_name = self.ignore_filename.clone();
376 let analyzer_client = client.clone();
377
378 let mut all_rules: Vec<_> = wdl_analysis::ALL_RULE_IDS
379 .iter()
380 .chain(wdl_lint::ALL_RULE_IDS.iter())
381 .map(ToString::to_string)
382 .collect();
383 all_rules.sort_unstable();
384 all_rules.dedup();
385
386 let analyzer_config = AnalysisConfig::default()
389 .with_fallback_version(Some(Default::default()))
390 .with_diagnostics_config(DiagnosticsConfig::new(
391 wdl_analysis::rules()
392 .iter()
393 .filter(|r| !exceptions.contains(&r.id().into())),
394 ))
395 .with_ignore_filename(ignore_name)
396 .with_all_rules(all_rules)
397 .with_feature_flags(self.feature_flags);
398
399 let wdl_lint_config = self.lint.config.clone();
400 Analyzer::<ProgressToken>::new_with_validator(
401 analyzer_config,
402 move |token, kind, current, total| {
403 let client = analyzer_client.clone();
404 async move {
405 let message = format!(
406 "{kind} {current}/{total} file{s}",
407 s = if total > 1 { "s" } else { "" }
408 );
409 let percentage = ((current * 100) as f64 / total as f64) as u32;
410 token.update(&client, message, percentage).await
411 }
412 },
413 move || {
414 let mut validator = Validator::default();
415 if linting_enabled {
416 validator.add_visitor(Linter::new(
417 wdl_lint::rules(&wdl_lint_config)
418 .into_iter()
419 .filter(|r| !exceptions.contains(&r.id().into()))
420 .map(|r| r as Box<dyn Rule>),
421 ));
422 }
423 validator
424 },
425 )
426 }
427}
428
429impl<S: 'static> Server<S> {
430 pub fn new(
434 client: Client,
435 options: ServerOptions,
436 log_handle: Option<FilterReloadHandle<S>>,
437 ) -> Self {
438 let analyzer = options.analyzer(client.clone());
439 Self {
440 client,
441 client_support: Default::default(),
442 folders: Default::default(),
443 config: Arc::new(tokio::sync::RwLock::new(ServerConfig { options, analyzer })),
444 log_handle,
445 }
446 }
447
448 async fn apply_config_patch(&self, patch: ServerOptionsPatch) {
450 let mut config = self.config.write().await;
451 if let Some(log_level) = patch.log_level
452 && let Some(reload_handle) = self.log_handle.as_ref()
453 && let Err(e) = reload_handle.modify(|filter| *filter = log_level.0)
454 {
455 error!("failed to set log level: {e:?}");
456 }
457
458 config.options.apply(patch);
459 config.analyzer = config.options.analyzer(self.client.clone());
460 }
461
462 pub async fn run(
466 options: ServerOptions,
467 log_handle: Option<FilterReloadHandle<S>>,
468 ) -> Result<()> {
469 debug!("running LSP server: {options:#?}");
470
471 let (service, socket) = LspService::new(|client| Self::new(client, options, log_handle));
472
473 let stdin = tokio::io::stdin();
474 let stdout = tokio::io::stdout();
475 tower_lsp::Server::new(stdin, stdout, socket)
476 .serve(service)
477 .await;
478
479 Ok(())
480 }
481
482 async fn info(&self) -> ServerInfo {
484 let config = self.config.read().await;
485
486 ServerInfo {
487 name: config.options.name.clone(),
488 version: Some(config.options.version.clone()),
489 }
490 }
491
492 async fn register_capabilities(&self, client_support: &ClientSupport) {
494 let mut registrations = Vec::new();
495 if client_support.watched_files {
496 registrations.push(Registration {
497 id: Uuid::new_v4().to_string(),
498 method: "workspace/didChangeWatchedFiles".into(),
499 register_options: Some(
500 to_value(DidChangeWatchedFilesRegistrationOptions {
501 watchers: vec![FileSystemWatcher {
502 glob_pattern: GlobPattern::String("**/*".to_string()),
505 kind: None,
506 }],
507 })
508 .expect("should convert to value"),
509 ),
510 });
511 }
512
513 if client_support.did_change_configuration {
514 registrations.push(Registration {
515 id: Uuid::new_v4().to_string(),
516 method: "workspace/didChangeConfiguration".into(),
517 register_options: None,
518 });
519 }
520
521 if registrations.is_empty() {
522 return;
523 }
524
525 self.client
526 .register_capability(registrations)
527 .await
528 .expect("failed to register capabilities with client");
529 }
530}
531
532#[tower_lsp::async_trait]
533impl<S: 'static> LanguageServer for Server<S> {
534 async fn initialize(&self, params: InitializeParams) -> RpcResult<InitializeResult> {
535 debug!("received `initialize` request: {params:#?}");
536
537 if let Some(folders) = params.workspace_folders {
538 let config = self.config.read().await;
539 for mut folder in folders {
540 normalize_uri_path(&mut folder.uri);
541 self.folders.write().push(folder.clone());
542 if let Ok(path) = folder.uri.to_file_path()
543 && let Err(e) = config.analyzer.add_directory(path).await
544 {
545 error!(
546 "failed to add initial workspace directory {uri}: {e}",
547 uri = folder.uri
548 );
549 }
550 }
551 }
552
553 {
554 let client_support = ClientSupport::new(¶ms.capabilities);
555
556 if !client_support.pull_diagnostics {
557 return Err(RpcError {
558 code: ErrorCode::ServerError(0),
559 message: "LSP server currently requires support for pulling diagnostics".into(),
560 data: None,
561 });
562 }
563
564 let _ = self.client_support.set(client_support);
566 }
567
568 Ok(InitializeResult {
569 capabilities: ServerCapabilities {
570 text_document_sync: Some(TextDocumentSyncCapability::Options(
571 TextDocumentSyncOptions {
572 open_close: Some(true),
573 change: Some(TextDocumentSyncKind::INCREMENTAL),
574 ..Default::default()
575 },
576 )),
577 workspace: Some(WorkspaceServerCapabilities {
578 workspace_folders: Some(WorkspaceFoldersServerCapabilities {
579 supported: Some(true),
580 change_notifications: Some(OneOf::Left(true)),
581 }),
582 ..Default::default()
583 }),
584 workspace_symbol_provider: Some(OneOf::Left(true)),
585 diagnostic_provider: Some(DiagnosticServerCapabilities::Options(
586 DiagnosticOptions {
587 inter_file_dependencies: true,
588 workspace_diagnostics: true,
589 ..Default::default()
596 },
597 )),
598 document_symbol_provider: Some(OneOf::Left(true)),
599 document_formatting_provider: Some(OneOf::Left(true)),
600 definition_provider: Some(OneOf::Left(true)),
601 references_provider: Some(OneOf::Left(true)),
602 completion_provider: Some(CompletionOptions {
603 resolve_provider: Some(false),
604 trigger_characters: Some(vec![
605 ".".to_string(),
606 "[".to_string(),
607 "#".to_string(),
608 ]),
609 ..Default::default()
610 }),
611 hover_provider: Some(HoverProviderCapability::Simple(true)),
612 rename_provider: Some(OneOf::Left(true)),
613 semantic_tokens_provider: Some(
614 SemanticTokensServerCapabilities::SemanticTokensOptions(
615 SemanticTokensOptions {
616 work_done_progress_options: Default::default(),
617 legend: SemanticTokensLegend {
618 token_types: WDL_SEMANTIC_TOKEN_TYPES.to_vec(),
619 token_modifiers: WDL_SEMANTIC_TOKEN_MODIFIERS.to_vec(),
620 },
621 range: Some(false),
622 full: Some(SemanticTokensFullOptions::Bool(true)),
623 },
624 ),
625 ),
626 signature_help_provider: Some(SignatureHelpOptions {
627 trigger_characters: Some(vec!["(".to_string(), ",".to_string()]),
628 retrigger_characters: None,
629 work_done_progress_options: WorkDoneProgressOptions {
630 work_done_progress: Some(false),
631 },
632 }),
633 inlay_hint_provider: Some(OneOf::Left(true)),
634 ..Default::default()
635 },
636 server_info: Some(self.info().await),
637 })
638 }
639
640 async fn initialized(&self, _: InitializedParams) {
641 let client_support = self.client_support.get().expect("should exist");
642 self.register_capabilities(client_support).await;
643
644 let info = self.info().await;
645 info!(
646 "{name} (v{version}) server initialized",
647 name = info.name,
648 version = info.version.expect("should exist")
649 );
650 }
651
652 async fn shutdown(&self) -> RpcResult<()> {
653 Ok(())
654 }
655
656 async fn did_open(&self, mut params: DidOpenTextDocumentParams) {
657 normalize_uri_path(&mut params.text_document.uri);
658
659 debug!("received `textDocument/didOpen` request: {params:#?}");
660
661 let config = self.config.read().await;
662 if let Err(e) = config
663 .analyzer
664 .add_document(params.text_document.uri.clone())
665 .await
666 {
667 error!(
668 "failed to add document {uri}: {e}",
669 uri = params.text_document.uri
670 );
671 return;
672 }
673
674 if let Err(e) = config.analyzer.notify_incremental_change(
675 params.text_document.uri,
676 IncrementalChange {
677 version: params.text_document.version,
678 start: Some(params.text_document.text),
679 edits: Vec::new(),
680 },
681 ) {
682 error!("failed to notify incremental change: {e}");
683 }
684 }
685
686 async fn did_change(&self, mut params: DidChangeTextDocumentParams) {
687 let config = self.config.read().await;
688
689 normalize_uri_path(&mut params.text_document.uri);
690
691 debug!("received `textDocument/didChange` request: {params:#?}");
692
693 debug!(
694 "document `{uri}` is now client version {version}",
695 uri = params.text_document.uri,
696 version = params.text_document.version
697 );
698
699 let (start, changes) = match params
701 .content_changes
702 .iter()
703 .rposition(|change| change.range.is_none())
704 {
705 Some(idx) => (
706 Some(mem::take(&mut params.content_changes[idx].text)),
707 &mut params.content_changes[idx + 1..],
708 ),
709 None => (None, &mut params.content_changes[..]),
710 };
711
712 if let Err(e) = config.analyzer.notify_incremental_change(
714 params.text_document.uri,
715 IncrementalChange {
716 version: params.text_document.version,
717 start,
718 edits: changes
719 .iter_mut()
720 .map(|e| {
721 let range = e.range.expect("edit should be after the last full change");
722 SourceEdit::new(
723 SourcePosition::new(range.start.line, range.start.character)
724 ..SourcePosition::new(range.end.line, range.end.character),
725 SourcePositionEncoding::UTF16,
726 mem::take(&mut e.text),
727 )
728 })
729 .collect(),
730 },
731 ) {
732 error!("failed to notify incremental change: {e}");
733 }
734 }
735
736 async fn did_close(&self, mut params: DidCloseTextDocumentParams) {
737 let config = self.config.read().await;
738
739 normalize_uri_path(&mut params.text_document.uri);
740
741 debug!("received `textDocument/didClose` request: {params:#?}");
742 if let Err(e) = config
743 .analyzer
744 .notify_change(params.text_document.uri, true)
745 {
746 error!("failed to notify change: {e}");
747 }
748 }
749
750 async fn diagnostic(
751 &self,
752 mut params: DocumentDiagnosticParams,
753 ) -> RpcResult<DocumentDiagnosticReportResult> {
754 let config = self.config.read().await;
755
756 normalize_uri_path(&mut params.text_document.uri);
757
758 debug!("received `textDocument/diagnostic` request: {params:#?}");
759
760 let results: Vec<wdl_analysis::AnalysisResult> = config
761 .analyzer
762 .analyze_document(ProgressToken::default(), params.text_document.uri.clone())
763 .await
764 .map_err(|e| RpcError {
765 code: ErrorCode::InternalError,
766 message: e.to_string().into(),
767 data: None,
768 })?;
769
770 drop(config);
771 let name = self.info().await.name;
772 let config = self.config.read().await;
773 let mut matcher = config.options.baseline.as_ref().map(|b| b.matcher());
774 proto::document_diagnostic_report(params, results, &name, matcher.as_mut())
775 .ok_or_else(RpcError::request_cancelled)
776 }
777
778 async fn workspace_diagnostic(
779 &self,
780 params: WorkspaceDiagnosticParams,
781 ) -> RpcResult<WorkspaceDiagnosticReportResult> {
782 let config = self.config.read().await;
783
784 debug!("received `workspace/diagnostic` request: {params:#?}");
785
786 let name = self.info().await.name;
787
788 let client_support = self.client_support.get().expect("should exist");
789 let progress = ProgressToken::new(&self.client, client_support.work_done_progress).await;
790 progress
791 .start(&self.client, name.clone(), "analyzing...")
792 .await;
793 let results = config
794 .analyzer
795 .analyze(progress.clone())
796 .await
797 .map_err(|e| RpcError {
798 code: ErrorCode::InternalError,
799 message: e.to_string().into(),
800 data: None,
801 })?;
802 progress.complete(&self.client, "analysis complete").await;
803
804 let mut matcher = config.options.baseline.as_ref().map(|b| b.matcher());
805 Ok(proto::workspace_diagnostic_report(
806 params,
807 results,
808 &name,
809 matcher.as_mut(),
810 ))
811 }
812
813 async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
814 let config = self.config.read().await;
815
816 debug!("received `workspace/didChangeWorkspaceFolders` request: {params:#?}");
817
818 if !params.event.removed.is_empty()
820 && let Err(e) = config
821 .analyzer
822 .remove_documents(
823 params
824 .event
825 .removed
826 .into_iter()
827 .map(|mut f| {
828 normalize_uri_path(&mut f.uri);
829 f.uri
830 })
831 .collect(),
832 )
833 .await
834 {
835 error!("failed to remove documents from analyzer: {e}");
836 }
837
838 if !params.event.added.is_empty() {
840 for folder in ¶ms.event.added {
841 if let Err(e) = config
842 .analyzer
843 .add_directory(folder.uri.to_file_path().expect("should be a file path"))
844 .await
845 {
846 error!("failed to add documents from directory to analyzer: {e}");
847 }
848 }
849 }
850 }
851
852 async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
853 debug!("received `workspace/didChangeConfiguration` notification: {params:#?}");
854
855 let workspace_configs = self
856 .client
857 .send_request::<WorkspaceConfiguration>(ConfigurationParams {
858 items: vec![ConfigurationItem {
859 scope_uri: None,
860 section: Some(String::from("sprocket.server")),
861 }],
862 })
863 .await;
864
865 match workspace_configs {
866 Ok(mut configs) if !configs.is_empty() => {
867 match serde_json::from_value::<ServerOptionsPatch>(configs.remove(0)) {
868 Ok(patch) => self.apply_config_patch(patch).await,
869 Err(e) => error!("failed to deserialize `ServerOptionsPatch`: {e:?}"),
870 }
871 }
872 Ok(_) => error!("client returned no configuration"),
873 Err(e) => error!("failed to fetch workspace configuration: {e}"),
874 }
875 }
876
877 async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
878 let config = self.config.read().await;
879
880 debug!("received `workspace/didChangeWatchedFiles` request: {params:#?}");
881
882 fn to_wdl_file_path(uri: &Url) -> Option<PathBuf> {
884 if let Ok(path) = uri.to_file_path()
885 && path.is_file()
886 && path.extension().and_then(OsStr::to_str) == Some("wdl")
887 {
888 return Some(path);
889 }
890
891 None
892 }
893
894 let mut added = Vec::new();
895 let mut deleted = Vec::new();
896
897 for mut event in params.changes {
898 normalize_uri_path(&mut event.uri);
899
900 match event.typ {
901 FileChangeType::CREATED => {
902 if let Some(path) = to_wdl_file_path(&event.uri) {
903 debug!("document `{uri}` has been created", uri = event.uri);
904 added.push(path);
905 }
906 }
907 FileChangeType::CHANGED => {
908 if to_wdl_file_path(&event.uri).is_some() {
909 debug!("document `{uri}` has been changed", uri = event.uri);
910 if let Err(e) = config.analyzer.notify_change(event.uri, false) {
911 error!("failed to notify change: {e}");
912 }
913 }
914 }
915 FileChangeType::DELETED => {
916 if to_wdl_file_path(&event.uri).is_some() {
917 debug!("document `{uri}` has been deleted", uri = event.uri);
918 deleted.push(event.uri);
919 }
920 }
921 _ => continue,
922 }
923 }
924
925 if !added.is_empty() {
927 for file in added {
928 if let Err(e) = config
929 .analyzer
930 .add_document(path_to_uri(&file).expect("should convert to uri"))
931 .await
932 {
933 error!("failed to add documents to analyzer: {e}");
934 }
935 }
936 }
937
938 if !deleted.is_empty()
940 && let Err(e) = config.analyzer.remove_documents(deleted).await
941 {
942 error!("failed to remove documents from analyzer: {e}");
943 }
944 }
945
946 async fn formatting(
947 &self,
948 mut params: DocumentFormattingParams,
949 ) -> RpcResult<Option<Vec<TextEdit>>> {
950 let config = self.config.read().await;
951
952 normalize_uri_path(&mut params.text_document.uri);
953
954 debug!("received `textDocument/formatting` request: {params:#?}");
955
956 let result = config
957 .analyzer
958 .format_document(params.text_document.uri)
959 .await
960 .map_err(|e| RpcError {
961 code: ErrorCode::InternalError,
962 message: e.to_string().into(),
963 data: None,
964 })?
965 .map(|(end_line, end_col, formatted)| {
966 vec![TextEdit {
967 range: Range {
968 start: Position {
971 line: 0,
972 character: 0,
973 },
974 end: Position {
975 line: end_line,
976 character: end_col,
977 },
978 },
979 new_text: formatted,
980 }]
981 });
982
983 Ok(result)
984 }
985
986 async fn goto_definition(
987 &self,
988 mut params: GotoDefinitionParams,
989 ) -> RpcResult<Option<GotoDefinitionResponse>> {
990 let config = self.config.read().await;
991
992 normalize_uri_path(&mut params.text_document_position_params.text_document.uri);
993
994 debug!("received `textDocument/gotoDefinition` request: {params:#?}");
995
996 let position = SourcePosition::new(
997 params.text_document_position_params.position.line,
998 params.text_document_position_params.position.character,
999 );
1000
1001 let result = config
1002 .analyzer
1003 .goto_definition(
1004 params.text_document_position_params.text_document.uri,
1005 position,
1006 SourcePositionEncoding::UTF16,
1007 )
1008 .await
1009 .map_err(|e| RpcError {
1010 code: ErrorCode::InternalError,
1011 message: e.to_string().into(),
1012 data: None,
1013 })?;
1014
1015 Ok(result)
1016 }
1017
1018 async fn references(&self, mut params: ReferenceParams) -> RpcResult<Option<Vec<Location>>> {
1019 let config = self.config.read().await;
1020
1021 normalize_uri_path(&mut params.text_document_position.text_document.uri);
1022
1023 debug!("received `textDocument/references` request: {params:#?}");
1024
1025 let position = SourcePosition::new(
1026 params.text_document_position.position.line,
1027 params.text_document_position.position.character,
1028 );
1029
1030 let result = config
1031 .analyzer
1032 .find_all_references(
1033 params.text_document_position.text_document.uri,
1034 position,
1035 SourcePositionEncoding::UTF16,
1036 params.context.include_declaration,
1037 )
1038 .await
1039 .map_err(|e| RpcError {
1040 code: ErrorCode::InternalError,
1041 message: e.to_string().into(),
1042 data: None,
1043 })?;
1044
1045 Ok(Some(result))
1046 }
1047
1048 async fn completion(
1049 &self,
1050 mut params: CompletionParams,
1051 ) -> RpcResult<Option<CompletionResponse>> {
1052 let config = self.config.read().await;
1053
1054 normalize_uri_path(&mut params.text_document_position.text_document.uri);
1055
1056 debug!("received `textDocument/completion` request: {params:#?}");
1057
1058 let position = SourcePosition::new(
1059 params.text_document_position.position.line,
1060 params.text_document_position.position.character,
1061 );
1062
1063 let result = config
1064 .analyzer
1065 .completion(
1066 ProgressToken::default(),
1067 params.text_document_position.text_document.uri,
1068 position,
1069 SourcePositionEncoding::UTF16,
1070 )
1071 .await
1072 .map_err(|e| RpcError {
1073 code: ErrorCode::InternalError,
1074 message: e.to_string().into(),
1075 data: None,
1076 })?;
1077
1078 Ok(result)
1079 }
1080
1081 async fn hover(&self, mut params: HoverParams) -> RpcResult<Option<Hover>> {
1082 let config = self.config.read().await;
1083
1084 normalize_uri_path(&mut params.text_document_position_params.text_document.uri);
1085
1086 debug!("received `textDocument/hover` request: {params:#?}");
1087
1088 let position = SourcePosition::new(
1089 params.text_document_position_params.position.line,
1090 params.text_document_position_params.position.character,
1091 );
1092
1093 let result = config
1094 .analyzer
1095 .hover(
1096 params.text_document_position_params.text_document.uri,
1097 position,
1098 SourcePositionEncoding::UTF16,
1099 )
1100 .await
1101 .map_err(|e| RpcError {
1102 code: ErrorCode::InternalError,
1103 message: e.to_string().into(),
1104 data: None,
1105 })?;
1106 Ok(result)
1107 }
1108
1109 async fn rename(&self, mut params: RenameParams) -> RpcResult<Option<WorkspaceEdit>> {
1110 let config = self.config.read().await;
1111
1112 normalize_uri_path(&mut params.text_document_position.text_document.uri);
1113
1114 debug!("received `textDocument/rename` request: {params:#?}");
1115
1116 let position = SourcePosition::new(
1117 params.text_document_position.position.line,
1118 params.text_document_position.position.character,
1119 );
1120
1121 let result = config
1122 .analyzer
1123 .rename(
1124 params.text_document_position.text_document.uri,
1125 position,
1126 SourcePositionEncoding::UTF16,
1127 params.new_name,
1128 )
1129 .await
1130 .map_err(|e| RpcError {
1131 code: ErrorCode::InternalError,
1132 message: e.to_string().into(),
1133 data: None,
1134 })?;
1135
1136 Ok(result)
1137 }
1138
1139 async fn semantic_tokens_full(
1140 &self,
1141 mut params: SemanticTokensParams,
1142 ) -> RpcResult<Option<SemanticTokensResult>> {
1143 let config = self.config.read().await;
1144
1145 normalize_uri_path(&mut params.text_document.uri);
1146
1147 debug!("received `textDocument/semanticTokens/full` request: {params:#?}");
1148
1149 let result = config
1150 .analyzer
1151 .semantic_tokens(params.text_document.uri)
1152 .await
1153 .map_err(|e| RpcError {
1154 code: ErrorCode::InternalError,
1155 message: e.to_string().into(),
1156 data: None,
1157 })?;
1158
1159 Ok(result)
1160 }
1161
1162 async fn document_symbol(
1163 &self,
1164 mut params: DocumentSymbolParams,
1165 ) -> RpcResult<Option<DocumentSymbolResponse>> {
1166 let config = self.config.read().await;
1167
1168 normalize_uri_path(&mut params.text_document.uri);
1169
1170 debug!("received `textDocument/documentSymbol` request: {params:#?}");
1171
1172 let result = config
1173 .analyzer
1174 .document_symbol(params.text_document.uri)
1175 .await
1176 .map_err(|e| RpcError {
1177 code: ErrorCode::InternalError,
1178 message: e.to_string().into(),
1179 data: None,
1180 })?;
1181
1182 Ok(result)
1183 }
1184
1185 async fn symbol(
1186 &self,
1187 params: WorkspaceSymbolParams,
1188 ) -> RpcResult<Option<Vec<SymbolInformation>>> {
1189 let config = self.config.read().await;
1190
1191 debug!("received `workspace/symbol` request: {params:#?}");
1192
1193 let result = config
1194 .analyzer
1195 .workspace_symbol(params.query)
1196 .await
1197 .map_err(|e| RpcError {
1198 code: ErrorCode::InternalError,
1199 message: e.to_string().into(),
1200 data: None,
1201 })?;
1202
1203 Ok(result)
1204 }
1205
1206 async fn signature_help(
1207 &self,
1208 mut params: SignatureHelpParams,
1209 ) -> RpcResult<Option<SignatureHelp>> {
1210 let config = self.config.read().await;
1211
1212 normalize_uri_path(&mut params.text_document_position_params.text_document.uri);
1213
1214 debug!("received `textDocument/signatureHelp` request: {params:#?}");
1215
1216 let position = SourcePosition::new(
1217 params.text_document_position_params.position.line,
1218 params.text_document_position_params.position.character,
1219 );
1220
1221 let result = config
1222 .analyzer
1223 .signature_help(
1224 params.text_document_position_params.text_document.uri,
1225 position,
1226 SourcePositionEncoding::UTF16,
1227 )
1228 .await
1229 .map_err(|e| RpcError {
1230 code: ErrorCode::InternalError,
1231 message: e.to_string().into(),
1232 data: None,
1233 })?;
1234
1235 Ok(result)
1236 }
1237
1238 async fn inlay_hint(&self, mut params: InlayHintParams) -> RpcResult<Option<Vec<InlayHint>>> {
1239 let config = self.config.read().await;
1240
1241 normalize_uri_path(&mut params.text_document.uri);
1242
1243 debug!("received `textDocument/inlayHint` request: {params:#?}");
1244
1245 config
1247 .analyzer
1248 .analyze(ProgressToken(None))
1249 .await
1250 .map_err(|e| RpcError {
1251 code: ErrorCode::InternalError,
1252 message: e.to_string().into(),
1253 data: None,
1254 })?;
1255
1256 let result = config
1257 .analyzer
1258 .inlay_hints(params.text_document.uri, params.range)
1259 .await
1260 .map_err(|e| RpcError {
1261 code: ErrorCode::InternalError,
1262 message: e.to_string().into(),
1263 data: None,
1264 })?;
1265
1266 Ok(result)
1267 }
1268}