1use std::time::Duration;
5
6use lsp_types::ReferenceContext;
7use tokio::time::Instant;
8
9use super::Translator;
10use super::dto::{
11 HoverResult, IndexCoverage, Location, LocationsResult, Position, PositionDegradation,
12 ReferencesResult,
13};
14use super::enclosing::{Contextualized, ResultContext};
15use super::encoding_ctx::EncodingCtx;
16use super::positioned::{FromPosition, GotoRequest, Positioned};
17use super::routing::{Capability, IndexingGate};
18use crate::bridge::indexing::{
19 DEFAULT_INDEXING_READY_TIMEOUT_SECS, INDEXING_STALENESS_BOUND, PROGRESS_LATCH_IDLE,
20 PROGRESS_SETTLE,
21};
22use crate::bridge::{ClientPath, IndexingState};
23use crate::config::ServerId;
24use crate::error::{Error, Result};
25
26pub(super) const INDEXING_READY_TIMEOUT: Duration =
40 Duration::from_secs(DEFAULT_INDEXING_READY_TIMEOUT_SECS);
41
42const INDEXING_POLL_INTERVAL: Duration = Duration::from_millis(100);
47
48const _: () = assert!(
53 INDEXING_STALENESS_BOUND.as_nanos() > INDEXING_READY_TIMEOUT.as_nanos(),
54 "INDEXING_STALENESS_BOUND must be greater than INDEXING_READY_TIMEOUT"
55);
56
57const _: () = assert!(
62 PROGRESS_SETTLE.as_nanos() < PROGRESS_LATCH_IDLE.as_nanos(),
63 "PROGRESS_SETTLE must be less than PROGRESS_LATCH_IDLE"
64);
65
66const _: () = assert!(
70 PROGRESS_SETTLE.as_nanos() < INDEXING_READY_TIMEOUT.as_nanos(),
71 "PROGRESS_SETTLE must be less than INDEXING_READY_TIMEOUT"
72);
73
74fn definition_to_locations(definition: lsp_types::Definition) -> Vec<lsp_types::Location> {
76 match definition {
77 lsp_types::Definition::Location(loc) => vec![loc],
78 lsp_types::Definition::LocationList(locs) => locs,
79 }
80}
81
82fn definition_link_to_location(link: lsp_types::DefinitionLink) -> lsp_types::Location {
84 lsp_types::Location {
85 uri: link.target_uri,
86 range: link.target_selection_range,
87 }
88}
89
90pub(super) const MAX_NORMALIZED_LOCATIONS: usize = 10_000;
101
102#[derive(Debug)]
110pub(super) struct ItemBudget {
111 remaining: usize,
112 cap: usize,
113 truncated: bool,
114}
115
116impl ItemBudget {
117 pub(super) const fn new() -> Self {
119 Self::with_cap(MAX_NORMALIZED_LOCATIONS)
120 }
121
122 pub(super) const fn with_cap(cap: usize) -> Self {
124 Self {
125 remaining: cap,
126 cap,
127 truncated: false,
128 }
129 }
130
131 pub(super) fn admit<T>(&mut self, mut items: Vec<T>) -> Vec<T> {
135 if items.len() > self.remaining {
136 self.record_drop(items.len());
137 items.truncate(self.remaining);
138 }
139 self.remaining = self.remaining.saturating_sub(items.len());
140 items
141 }
142
143 pub(super) fn admit_iter<T>(&mut self, items: impl IntoIterator<Item = T>) -> Vec<T> {
147 let mut items = items.into_iter();
148 let kept: Vec<T> = items.by_ref().take(self.remaining).collect();
149 if items.next().is_some() {
150 self.record_drop(kept.len().saturating_add(1));
151 }
152 self.remaining = self.remaining.saturating_sub(kept.len());
153 kept
154 }
155
156 pub(super) fn admit_whole<T>(&mut self, items: Vec<T>) -> Option<Vec<T>> {
160 self.spend_whole(items.len()).then_some(items)
161 }
162
163 pub(super) fn spend_whole(&mut self, count: usize) -> bool {
167 if count > self.remaining {
168 self.record_drop(count);
169 return false;
170 }
171 self.remaining = self.remaining.saturating_sub(count);
172 true
173 }
174
175 fn record_drop(&mut self, reported: usize) {
176 if !self.truncated {
177 tracing::warn!(
178 reported,
179 cap = self.cap,
180 "LSP response item count exceeds the item cap; truncating"
181 );
182 }
183 self.truncated = true;
184 }
185
186 pub(super) const fn truncated(&self) -> bool {
189 self.truncated
190 }
191}
192
193async fn lsp_locations_to_mcp(
210 locs: Vec<lsp_types::Location>,
211 ctx: &EncodingCtx,
212) -> NormalizedLocations {
213 let mut budget = ItemBudget::new();
214 let locs = budget.admit(locs);
215 let truncated = budget.truncated();
216 let mut locations = Vec::with_capacity(locs.len());
217 for loc in locs {
218 locations.push(Location {
219 uri: loc.uri.to_string(),
220 range: ctx.normalize_range(&loc.uri, loc.range).await,
221 out_of_workspace: ctx.is_out_of_workspace(&loc.uri),
222 });
223 }
224 NormalizedLocations {
225 locations,
226 truncated,
227 positions_degraded: ctx.positions_degraded(),
228 }
229}
230
231struct NormalizedLocations {
241 locations: Vec<Location>,
242 truncated: bool,
243 positions_degraded: Option<PositionDegradation>,
244}
245
246enum GotoKind {
251 Locations(Vec<lsp_types::Location>),
253 Links(Vec<lsp_types::LocationLink>),
255}
256
257trait GotoResponse {
261 fn into_kind(self) -> GotoKind;
264}
265
266impl GotoResponse for lsp_types::DefinitionResponse {
267 fn into_kind(self) -> GotoKind {
268 match self {
269 Self::Definition(def) => GotoKind::Locations(definition_to_locations(def)),
270 Self::DefinitionLinkList(links) => GotoKind::Links(links),
271 }
272 }
273}
274
275impl GotoResponse for lsp_types::ImplementationResponse {
276 fn into_kind(self) -> GotoKind {
277 match self {
278 Self::Definition(def) => GotoKind::Locations(definition_to_locations(def)),
279 Self::DefinitionLinkList(links) => GotoKind::Links(links),
280 }
281 }
282}
283
284impl GotoResponse for lsp_types::TypeDefinitionResponse {
285 fn into_kind(self) -> GotoKind {
286 match self {
287 Self::Definition(def) => GotoKind::Locations(definition_to_locations(def)),
288 Self::DefinitionLinkList(links) => GotoKind::Links(links),
289 }
290 }
291}
292
293impl GotoResponse for lsp_types::DeclarationResponse {
294 fn into_kind(self) -> GotoKind {
295 match self {
296 Self::Declaration(lsp_types::Declaration::Location(loc)) => {
297 GotoKind::Locations(vec![loc])
298 }
299 Self::Declaration(lsp_types::Declaration::LocationList(locs)) => {
300 GotoKind::Locations(locs)
301 }
302 Self::DeclarationLinkList(links) => GotoKind::Links(links),
303 }
304 }
305}
306
307async fn goto_response_to_locations<R: GotoResponse>(
312 response: Option<R>,
313 ctx: &EncodingCtx,
314) -> NormalizedLocations {
315 let lsp_locs = match response.map(GotoResponse::into_kind) {
316 Some(GotoKind::Locations(locs)) => locs,
317 Some(GotoKind::Links(links)) => {
318 links.into_iter().map(definition_link_to_location).collect()
319 }
320 None => vec![],
321 };
322 lsp_locations_to_mcp(lsp_locs, ctx).await
323}
324
325#[allow(deprecated, reason = "LSP servers still send this deprecated field")]
333fn extract_hover_contents(contents: lsp_types::Contents) -> String {
334 match contents {
335 lsp_types::Contents::MarkedString(marked_string) => marked_string_to_string(marked_string),
336 lsp_types::Contents::MarkedStringList(marked_strings) => marked_strings
337 .into_iter()
338 .map(marked_string_to_string)
339 .collect::<Vec<_>>()
340 .join("\n\n"),
341 lsp_types::Contents::MarkupContent(markup) => markup.value,
342 }
343}
344
345#[allow(deprecated, reason = "LSP servers still send this deprecated field")]
347fn marked_string_to_string(marked: lsp_types::MarkedString) -> String {
348 match marked {
349 lsp_types::MarkedString::String(s) => s,
350 lsp_types::MarkedString::MarkedStringWithLanguage(ls) => {
351 format!("```{}\n{}\n```", ls.language, ls.value)
352 }
353 }
354}
355
356impl Translator {
357 pub(super) async fn wait_for_indexing_ready(&self, server_id: &ServerId) -> Result<()> {
381 self.wait_for_indexing_ready_with(
382 server_id,
383 self.indexing_ready_timeout,
384 INDEXING_POLL_INTERVAL,
385 )
386 .await
387 }
388
389 async fn wait_for_indexing_ready_with(
404 &self,
405 server_id: &ServerId,
406 timeout: Duration,
407 poll_interval: Duration,
408 ) -> Result<()> {
409 let Some(cache) = self.notification_cache.as_ref() else {
410 return Ok(());
411 };
412
413 let start = Instant::now();
414 #[allow(
415 clippy::arithmetic_side_effects,
416 reason = "production passes the fixed INDEXING_READY_TIMEOUT"
417 )]
418 let deadline = start + timeout;
419 loop {
420 let state = cache.lock().await.indexing_state(server_id);
421 if state != IndexingState::Loading {
422 return Ok(());
423 }
424 let remaining = deadline.saturating_duration_since(Instant::now());
425 if remaining.is_zero() {
426 return Err(Error::WorkspaceIndexing {
427 server_id: server_id.clone(),
428 elapsed_secs: start.elapsed().as_secs(),
429 });
430 }
431 tokio::time::sleep(poll_interval.min(remaining)).await;
432 }
433 }
434
435 pub async fn handle_hover(
444 &self,
445 file_path: ClientPath,
446 position: Position,
447 ) -> Result<HoverResult> {
448 let Positioned {
449 result: response,
450 ctx,
451 doc,
452 } = self
453 .position_request::<lsp_types::HoverRequest>(
454 &file_path,
455 position,
456 IndexingGate::Required(Capability::Hover),
457 (),
458 )
459 .await?;
460 let response_uri = doc.uri();
461
462 let result = match response {
463 Some(hover) => {
464 let contents = extract_hover_contents(hover.contents);
465 let range = match hover.range {
466 Some(r) => Some(ctx.normalize_range(response_uri, r).await),
467 None => None,
468 };
469 HoverResult {
470 contents,
471 range,
472 positions_degraded: ctx.positions_degraded(),
473 }
474 }
475 None => HoverResult {
476 contents: "No hover information available".to_string(),
477 range: None,
478 positions_degraded: ctx.positions_degraded(),
479 },
480 };
481
482 Ok(result)
483 }
484
485 async fn contextualize_into(
486 &self,
487 normalized: NormalizedLocations,
488 context: ResultContext,
489 ) -> LocationsResult {
490 let truncated = normalized.truncated;
491 let Contextualized {
492 items: locations,
493 enrichment,
494 positions_degraded,
495 } = self
496 .contextualize_locations(normalized.locations, context, normalized.positions_degraded)
497 .await;
498 LocationsResult {
499 locations,
500 truncated,
501 positions_degraded,
502 enrichment,
503 }
504 }
505
506 async fn handle_goto<R, T>(
520 &self,
521 file_path: &ClientPath,
522 position: Position,
523 ) -> Result<NormalizedLocations>
524 where
525 R: GotoRequest + lsp_types::Request<Result = Option<T>>,
526 R::Params: FromPosition<Extra = ()>,
527 T: GotoResponse,
528 {
529 let Positioned {
530 result: response,
531 ctx,
532 doc: _doc,
533 } = self
534 .position_request::<R>(
535 file_path,
536 position,
537 IndexingGate::Required(R::CAPABILITY),
538 (),
539 )
540 .await?;
541
542 Ok(goto_response_to_locations(response, &ctx).await)
543 }
544
545 pub async fn handle_definition(
554 &self,
555 file_path: ClientPath,
556 position: Position,
557 context: ResultContext,
558 ) -> Result<LocationsResult> {
559 let normalized = self
560 .handle_goto::<lsp_types::DefinitionRequest, _>(&file_path, position)
561 .await?;
562 Ok(self.contextualize_into(normalized, context).await)
563 }
564
565 pub async fn handle_references(
574 &self,
575 file_path: ClientPath,
576 position: Position,
577 include_declaration: bool,
578 context: ResultContext,
579 ) -> Result<ReferencesResult> {
580 let Positioned {
581 result: response,
582 ctx,
583 doc,
584 } = self
585 .position_request::<lsp_types::ReferencesRequest>(
586 &file_path,
587 position,
588 IndexingGate::Required(Capability::References),
589 ReferenceContext {
590 include_declaration,
591 },
592 )
593 .await?;
594
595 let normalized = lsp_locations_to_mcp(response.unwrap_or_default(), &ctx).await;
596 let result = self.contextualize_into(normalized, context).await;
597 let index_coverage = result
598 .locations
599 .is_empty()
600 .then(|| self.index_coverage(doc.server_id()));
601 Ok(ReferencesResult {
602 result,
603 index_coverage,
604 })
605 }
606
607 pub(super) fn index_coverage(&self, server: &ServerId) -> IndexCoverage {
609 let tracker = &self.document_tracker;
610 IndexCoverage {
611 synced_documents: u32::try_from(tracker.synced_document_count(server))
612 .unwrap_or(u32::MAX),
613 server_restarted: tracker.sync_history_forgotten(server),
614 }
615 }
616
617 pub async fn handle_implementation(
628 &self,
629 file_path: ClientPath,
630 position: Position,
631 context: ResultContext,
632 ) -> Result<LocationsResult> {
633 let normalized = self
634 .handle_goto::<lsp_types::ImplementationRequest, _>(&file_path, position)
635 .await?;
636 Ok(self.contextualize_into(normalized, context).await)
637 }
638
639 pub async fn handle_type_definition(
651 &self,
652 file_path: ClientPath,
653 position: Position,
654 context: ResultContext,
655 ) -> Result<LocationsResult> {
656 let normalized = self
657 .handle_goto::<lsp_types::TypeDefinitionRequest, _>(&file_path, position)
658 .await?;
659 Ok(self.contextualize_into(normalized, context).await)
660 }
661
662 pub async fn handle_declaration(
675 &self,
676 file_path: ClientPath,
677 position: Position,
678 context: ResultContext,
679 ) -> Result<LocationsResult> {
680 let normalized = self
681 .handle_goto::<lsp_types::DeclarationRequest, _>(&file_path, position)
682 .await?;
683 Ok(self.contextualize_into(normalized, context).await)
684 }
685}
686
687#[cfg(test)]
688#[allow(deprecated, reason = "LSP servers still send this deprecated field")]
689mod tests {
690 use std::sync::Arc;
691 use std::time::Duration;
692 use std::{assert_matches, fs};
693
694 use tempfile::TempDir;
695 use tokio::io::BufReader;
696 use tokio::sync::Mutex;
697 use tokio::time::timeout;
698 use url::Url;
699
700 use super::*;
701 use crate::bridge::encoding::PositionEncoding;
702 use crate::bridge::translator::testing::*;
703 use crate::bridge::{NotificationCache, path_to_uri};
704 use crate::config::{IndexingReadyTimeoutSecs, ServerId};
705 use crate::test_lsp::client_path;
706 use crate::util::lock_std;
707
708 #[tokio::test]
713 async fn test_wait_for_indexing_ready_without_cache_is_noop() {
714 let translator = Translator::new();
716 let server_id = ServerId::from_static("rust");
717
718 translator
719 .wait_for_indexing_ready(&server_id)
720 .await
721 .unwrap();
722 }
723
724 #[tokio::test]
725 async fn test_wait_for_indexing_ready_unknown_state_is_noop() {
726 let translator = Translator::new()
727 .with_notification_cache(Arc::new(Mutex::new(NotificationCache::new())));
728 let server_id = ServerId::from_static("rust");
729
730 translator
731 .wait_for_indexing_ready(&server_id)
732 .await
733 .unwrap();
734 }
735
736 #[tokio::test]
737 async fn test_wait_for_indexing_ready_ready_state_is_noop() {
738 let cache = Arc::new(Mutex::new(NotificationCache::new()));
739 let server_id = ServerId::from_static("rust");
740 cache.lock().await.observe_indexing_signal(
741 &server_id,
742 "experimental/serverStatus",
743 Some(&serde_json::json!({"quiescent": true})),
744 );
745 let translator = Translator::new().with_notification_cache(cache);
746
747 translator
748 .wait_for_indexing_ready(&server_id)
749 .await
750 .unwrap();
751 }
752
753 #[tokio::test(start_paused = true)]
759 async fn test_wait_for_indexing_ready_uses_configured_timeout_override() {
760 let cache = Arc::new(Mutex::new(NotificationCache::new()));
761 let server_id = ServerId::from_static("rust");
762 cache.lock().await.observe_indexing_signal(
763 &server_id,
764 "experimental/serverStatus",
765 Some(&serde_json::json!({"quiescent": false})),
766 );
767 let translator = Translator::new()
768 .with_notification_cache(cache)
769 .with_indexing_ready_timeout(IndexingReadyTimeoutSecs::new(5).unwrap());
770
771 let start = Instant::now();
772 let err = translator
773 .wait_for_indexing_ready(&server_id)
774 .await
775 .unwrap_err();
776
777 assert_matches!(err, Error::WorkspaceIndexing { elapsed_secs, .. } if elapsed_secs == 5);
778 assert_eq!(start.elapsed(), Duration::from_secs(5));
779 }
780
781 #[tokio::test]
782 async fn test_wait_for_indexing_ready_loading_times_out() {
783 let cache = Arc::new(Mutex::new(NotificationCache::new()));
784 let server_id = ServerId::from_static("rust");
785 cache.lock().await.observe_indexing_signal(
786 &server_id,
787 "experimental/serverStatus",
788 Some(&serde_json::json!({"quiescent": false})),
789 );
790 let translator = Translator::new().with_notification_cache(cache);
791
792 let err = translator
793 .wait_for_indexing_ready_with(
794 &server_id,
795 Duration::from_millis(50),
796 Duration::from_millis(10),
797 )
798 .await
799 .unwrap_err();
800
801 assert_matches!(
802 err,
803 Error::WorkspaceIndexing { server_id: id, .. } if id == ServerId::from_static("rust")
804 );
805 }
806
807 #[tokio::test]
808 async fn test_wait_for_indexing_ready_returns_ok_once_signaled_ready() {
809 let cache = Arc::new(Mutex::new(NotificationCache::new()));
810 let server_id = ServerId::from_static("rust");
811 cache.lock().await.observe_indexing_signal(
812 &server_id,
813 "experimental/serverStatus",
814 Some(&serde_json::json!({"quiescent": false})),
815 );
816 let translator = Translator::new().with_notification_cache(Arc::clone(&cache));
817
818 let waiter = {
819 let server_id = server_id.clone();
820 tokio::spawn(async move {
821 translator
822 .wait_for_indexing_ready_with(
823 &server_id,
824 Duration::from_secs(5),
825 Duration::from_millis(10),
826 )
827 .await
828 })
829 };
830
831 tokio::time::sleep(Duration::from_millis(30)).await;
832 cache.lock().await.observe_indexing_signal(
833 &server_id,
834 "experimental/serverStatus",
835 Some(&serde_json::json!({"quiescent": true})),
836 );
837
838 timeout(Duration::from_secs(1), waiter)
839 .await
840 .expect("waiter task timed out")
841 .expect("waiter task panicked")
842 .expect("expected Ok once quiescent");
843 }
844
845 #[tokio::test(start_paused = true)]
851 async fn test_handle_hover_returns_workspace_indexing_error_when_loading() {
852 let dir = TempDir::new().unwrap();
853 let server_id = ServerId::from_static("rust");
854 let caps = lsp_types::ServerCapabilities {
855 hover_provider: Some(lsp_types::HoverProvider::Bool(true)),
856 ..Default::default()
857 };
858 let (translator, _server) = translator_with_capabilities(&dir, &server_id, caps);
859
860 let cache = Arc::new(Mutex::new(NotificationCache::new()));
861 cache.lock().await.observe_indexing_signal(
862 &server_id,
863 "experimental/serverStatus",
864 Some(&serde_json::json!({"quiescent": false})),
865 );
866 let translator = translator.with_notification_cache(cache);
867
868 let path = dir.path().join("main.rs");
869 fs::write(&path, "fn main() {}").unwrap();
870
871 let err = translator
872 .handle_hover(client_path(path.to_string_lossy().into_owned()), pos(1, 1))
873 .await
874 .unwrap_err();
875
876 assert_matches!(
877 err,
878 Error::WorkspaceIndexing { server_id: id, elapsed_secs: 30 } if id == server_id
879 );
880 }
881
882 #[tokio::test]
885 async fn test_handle_hover_dispatches_when_indexing_ready() {
886 let dir = TempDir::new().unwrap();
887 let server_id = ServerId::from_static("rust");
888 let caps = lsp_types::ServerCapabilities {
889 hover_provider: Some(lsp_types::HoverProvider::Bool(true)),
890 ..Default::default()
891 };
892 let (translator, mut server) = translator_with_capabilities(&dir, &server_id, caps);
893
894 let cache = Arc::new(Mutex::new(NotificationCache::new()));
895 cache.lock().await.observe_indexing_signal(
896 &server_id,
897 "experimental/serverStatus",
898 Some(&serde_json::json!({"quiescent": true})),
899 );
900 let translator = Arc::new(translator.with_notification_cache(cache));
901
902 let path = dir.path().join("main.rs");
903 fs::write(&path, "fn main() {}").unwrap();
904
905 let handle = {
906 let translator = Arc::clone(&translator);
907 let path = path.to_string_lossy().to_string();
908 tokio::spawn(async move { translator.handle_hover(client_path(path), pos(1, 1)).await })
909 };
910
911 let mut wire = BufReader::new(&mut server.write_stdout);
912 let opened = read_framed_message(&mut wire).await;
913 assert_eq!(opened["method"], "textDocument/didOpen");
914 let request = read_framed_message(&mut wire).await;
915 assert_eq!(request["method"], "textDocument/hover");
916
917 write_response(
918 &mut server.read_half_stdin,
919 &request["id"],
920 serde_json::json!({
921 "contents": {"kind": "markdown", "value": "hover text"}
922 }),
923 )
924 .await;
925
926 let result = handle.await.unwrap().unwrap();
927 assert_eq!(result.contents, "hover text");
928 }
929
930 #[tokio::test(start_paused = true)]
934 async fn test_handle_hover_timeout_keeps_document_open_for_next_request() {
935 let dir = TempDir::new().unwrap();
936 let server_id = ServerId::from_static("rust");
937 let caps = lsp_types::ServerCapabilities {
938 hover_provider: Some(lsp_types::HoverProvider::Bool(true)),
939 ..Default::default()
940 };
941 let (translator, mut server) = translator_with_capabilities(&dir, &server_id, caps);
942
943 let cache = Arc::new(Mutex::new(NotificationCache::new()));
944 cache.lock().await.observe_indexing_signal(
945 &server_id,
946 "experimental/serverStatus",
947 Some(&serde_json::json!({"quiescent": true})),
948 );
949 let translator = Arc::new(translator.with_notification_cache(cache));
950
951 let path = dir.path().join("main.rs");
952 fs::write(&path, "fn main() {}").unwrap();
953 let path = path.to_string_lossy().to_string();
954
955 let spawn_hover = || {
956 let translator = Arc::clone(&translator);
957 let path = path.clone();
958 tokio::spawn(async move { translator.handle_hover(client_path(path), pos(1, 1)).await })
959 };
960
961 let mut wire = BufReader::new(&mut server.write_stdout);
962
963 let first = spawn_hover();
964 let opened = read_framed_message(&mut wire).await;
965 assert_eq!(opened["method"], "textDocument/didOpen");
966 let first_request = read_framed_message(&mut wire).await;
967 assert_eq!(first_request["method"], "textDocument/hover");
968
969 let timeout_secs = crate::config::LspServerConfig::rust_analyzer().request_timeout_seconds;
970 tokio::time::advance(Duration::from_secs(timeout_secs.get() + 1)).await;
971 assert_matches!(first.await.unwrap().unwrap_err(), Error::Timeout(_));
972 let cancel = read_framed_message(&mut wire).await;
973 assert_eq!(cancel["method"], "$/cancelRequest");
974 assert_eq!(cancel["params"]["id"], first_request["id"]);
975
976 let second = spawn_hover();
977 let second_request = read_framed_message(&mut wire).await;
978 assert_eq!(
979 second_request["method"], "textDocument/hover",
980 "a timed-out hover must not cause the document to be re-opened"
981 );
982 write_response(
983 &mut server.read_half_stdin,
984 &second_request["id"],
985 serde_json::json!({"contents": {"kind": "markdown", "value": "after timeout"}}),
986 )
987 .await;
988
989 assert_eq!(second.await.unwrap().unwrap().contents, "after timeout");
990 }
991
992 #[tokio::test(start_paused = true)]
996 async fn test_handle_definition_returns_workspace_indexing_error_when_loading() {
997 let dir = TempDir::new().unwrap();
998 let server_id = ServerId::from_static("rust");
999 let caps = lsp_types::ServerCapabilities {
1000 definition_provider: Some(lsp_types::DefinitionProvider::Bool(true)),
1001 ..Default::default()
1002 };
1003 let (translator, _server) = translator_with_capabilities(&dir, &server_id, caps);
1004
1005 let cache = Arc::new(Mutex::new(NotificationCache::new()));
1006 cache.lock().await.observe_indexing_signal(
1007 &server_id,
1008 "experimental/serverStatus",
1009 Some(&serde_json::json!({"quiescent": false})),
1010 );
1011 let translator = translator.with_notification_cache(cache);
1012
1013 let path = dir.path().join("main.rs");
1014 fs::write(&path, "fn main() {}").unwrap();
1015
1016 let err = translator
1017 .handle_definition(
1018 client_path(path.to_string_lossy().into_owned()),
1019 pos(1, 1),
1020 ResultContext::None,
1021 )
1022 .await
1023 .unwrap_err();
1024
1025 assert_matches!(
1026 err,
1027 Error::WorkspaceIndexing { server_id: id, .. } if id == server_id
1028 );
1029 }
1030
1031 #[tokio::test]
1034 async fn test_handle_definition_dispatches_when_indexing_ready() {
1035 let dir = TempDir::new().unwrap();
1036 let server_id = ServerId::from_static("rust");
1037 let caps = lsp_types::ServerCapabilities {
1038 definition_provider: Some(lsp_types::DefinitionProvider::Bool(true)),
1039 ..Default::default()
1040 };
1041 let (translator, mut server) = translator_with_capabilities(&dir, &server_id, caps);
1042
1043 let cache = Arc::new(Mutex::new(NotificationCache::new()));
1044 cache.lock().await.observe_indexing_signal(
1045 &server_id,
1046 "experimental/serverStatus",
1047 Some(&serde_json::json!({"quiescent": true})),
1048 );
1049 let translator = Arc::new(translator.with_notification_cache(cache));
1050
1051 let path = dir.path().join("main.rs");
1052 fs::write(&path, "fn main() {}").unwrap();
1053
1054 let handle = {
1055 let translator = Arc::clone(&translator);
1056 let path = path.to_string_lossy().to_string();
1057 tokio::spawn(async move {
1058 translator
1059 .handle_definition(client_path(path), pos(1, 1), ResultContext::None)
1060 .await
1061 })
1062 };
1063
1064 let mut wire = BufReader::new(&mut server.write_stdout);
1065 let opened = read_framed_message(&mut wire).await;
1066 assert_eq!(opened["method"], "textDocument/didOpen");
1067 let request = read_framed_message(&mut wire).await;
1068 assert_eq!(request["method"], "textDocument/definition");
1069
1070 write_response(
1071 &mut server.read_half_stdin,
1072 &request["id"],
1073 serde_json::Value::Null,
1074 )
1075 .await;
1076
1077 let result = handle.await.unwrap().unwrap();
1078 assert!(result.locations.is_empty());
1079 }
1080
1081 #[tokio::test(start_paused = true)]
1085 async fn test_handle_references_returns_workspace_indexing_error_when_loading() {
1086 let dir = TempDir::new().unwrap();
1087 let server_id = ServerId::from_static("rust");
1088 let caps = lsp_types::ServerCapabilities {
1089 references_provider: Some(lsp_types::ReferencesProvider::Bool(true)),
1090 ..Default::default()
1091 };
1092 let (translator, _server) = translator_with_capabilities(&dir, &server_id, caps);
1093
1094 let cache = Arc::new(Mutex::new(NotificationCache::new()));
1095 cache.lock().await.observe_indexing_signal(
1096 &server_id,
1097 "experimental/serverStatus",
1098 Some(&serde_json::json!({"quiescent": false})),
1099 );
1100 let translator = translator.with_notification_cache(cache);
1101
1102 let path = dir.path().join("main.rs");
1103 fs::write(&path, "fn main() {}").unwrap();
1104
1105 let err = translator
1106 .handle_references(
1107 client_path(path.to_string_lossy().into_owned()),
1108 pos(1, 1),
1109 true,
1110 ResultContext::None,
1111 )
1112 .await
1113 .unwrap_err();
1114
1115 assert_matches!(
1116 err,
1117 Error::WorkspaceIndexing { server_id: id, .. } if id == server_id
1118 );
1119 }
1120
1121 #[tokio::test]
1124 async fn test_handle_references_dispatches_when_indexing_ready() {
1125 let dir = TempDir::new().unwrap();
1126 let server_id = ServerId::from_static("rust");
1127 let caps = lsp_types::ServerCapabilities {
1128 references_provider: Some(lsp_types::ReferencesProvider::Bool(true)),
1129 ..Default::default()
1130 };
1131 let (translator, mut server) = translator_with_capabilities(&dir, &server_id, caps);
1132
1133 let cache = Arc::new(Mutex::new(NotificationCache::new()));
1134 cache.lock().await.observe_indexing_signal(
1135 &server_id,
1136 "experimental/serverStatus",
1137 Some(&serde_json::json!({"quiescent": true})),
1138 );
1139 let translator = Arc::new(translator.with_notification_cache(cache));
1140
1141 let path = dir.path().join("main.rs");
1142 fs::write(&path, "fn main() {}").unwrap();
1143
1144 let handle = {
1145 let translator = Arc::clone(&translator);
1146 let path = path.to_string_lossy().to_string();
1147 tokio::spawn(async move {
1148 translator
1149 .handle_references(client_path(path), pos(1, 1), true, ResultContext::None)
1150 .await
1151 })
1152 };
1153
1154 let mut wire = BufReader::new(&mut server.write_stdout);
1155 let opened = read_framed_message(&mut wire).await;
1156 assert_eq!(opened["method"], "textDocument/didOpen");
1157 let request = read_framed_message(&mut wire).await;
1158 assert_eq!(request["method"], "textDocument/references");
1159
1160 write_response(
1161 &mut server.read_half_stdin,
1162 &request["id"],
1163 serde_json::Value::Null,
1164 )
1165 .await;
1166
1167 let result = handle.await.unwrap().unwrap();
1168 assert!(result.result.locations.is_empty());
1169 assert_eq!(
1170 result.index_coverage,
1171 Some(IndexCoverage {
1172 synced_documents: 1,
1173 server_restarted: false,
1174 })
1175 );
1176 let json = serde_json::to_value(&result).unwrap();
1177 assert_eq!(
1178 json["index_coverage"],
1179 serde_json::json!({"synced_documents": 1})
1180 );
1181
1182 translator.document_tracker.forget_server(&server_id);
1183 assert_eq!(
1184 translator.index_coverage(&server_id),
1185 IndexCoverage {
1186 synced_documents: 0,
1187 server_restarted: true,
1188 }
1189 );
1190 }
1191
1192 #[tokio::test(start_paused = true)]
1197 async fn test_handle_implementation_returns_workspace_indexing_error_when_loading() {
1198 let dir = TempDir::new().unwrap();
1199 let server_id = ServerId::from_static("rust");
1200 let caps = lsp_types::ServerCapabilities {
1201 implementation_provider: Some(lsp_types::ImplementationProvider::Bool(true)),
1202 ..Default::default()
1203 };
1204 let (translator, _server) = translator_with_capabilities(&dir, &server_id, caps);
1205
1206 let cache = Arc::new(Mutex::new(NotificationCache::new()));
1207 cache.lock().await.observe_indexing_signal(
1208 &server_id,
1209 "experimental/serverStatus",
1210 Some(&serde_json::json!({"quiescent": false})),
1211 );
1212 let translator = translator.with_notification_cache(cache);
1213
1214 let path = dir.path().join("main.rs");
1215 fs::write(&path, "fn main() {}").unwrap();
1216
1217 let err = translator
1218 .handle_implementation(
1219 client_path(path.to_string_lossy().into_owned()),
1220 pos(1, 1),
1221 ResultContext::None,
1222 )
1223 .await
1224 .unwrap_err();
1225
1226 assert_matches!(
1227 err,
1228 Error::WorkspaceIndexing { server_id: id, .. } if id == server_id
1229 );
1230 }
1231
1232 #[tokio::test(start_paused = true)]
1234 async fn test_handle_type_definition_returns_workspace_indexing_error_when_loading() {
1235 let dir = TempDir::new().unwrap();
1236 let server_id = ServerId::from_static("rust");
1237 let caps = lsp_types::ServerCapabilities {
1238 type_definition_provider: Some(lsp_types::TypeDefinitionProvider::Bool(true)),
1239 ..Default::default()
1240 };
1241 let (translator, _server) = translator_with_capabilities(&dir, &server_id, caps);
1242
1243 let cache = Arc::new(Mutex::new(NotificationCache::new()));
1244 cache.lock().await.observe_indexing_signal(
1245 &server_id,
1246 "experimental/serverStatus",
1247 Some(&serde_json::json!({"quiescent": false})),
1248 );
1249 let translator = translator.with_notification_cache(cache);
1250
1251 let path = dir.path().join("main.rs");
1252 fs::write(&path, "fn main() {}").unwrap();
1253
1254 let err = translator
1255 .handle_type_definition(
1256 client_path(path.to_string_lossy().into_owned()),
1257 pos(1, 1),
1258 ResultContext::None,
1259 )
1260 .await
1261 .unwrap_err();
1262
1263 assert_matches!(
1264 err,
1265 Error::WorkspaceIndexing { server_id: id, .. } if id == server_id
1266 );
1267 }
1268
1269 #[tokio::test]
1278 async fn test_wait_for_indexing_ready_timeout_does_not_mutate_shared_state() {
1279 let cache = Arc::new(Mutex::new(NotificationCache::new()));
1280 let server_id = ServerId::from_static("rust");
1281 cache.lock().await.observe_indexing_signal(
1282 &server_id,
1283 "experimental/serverStatus",
1284 Some(&serde_json::json!({"quiescent": false})),
1285 );
1286 let translator = Translator::new().with_notification_cache(Arc::clone(&cache));
1287
1288 translator
1289 .wait_for_indexing_ready_with(
1290 &server_id,
1291 Duration::from_millis(30),
1292 Duration::from_millis(10),
1293 )
1294 .await
1295 .unwrap_err();
1296
1297 assert_eq!(
1298 cache.lock().await.indexing_state(&server_id),
1299 IndexingState::Loading,
1300 "a timed-out wait must not touch the shared entry -- it is still fresh, so it must \
1301 still read as Loading for any other caller"
1302 );
1303 }
1304
1305 #[tokio::test]
1312 async fn test_wait_for_indexing_ready_one_callers_timeout_does_not_release_another() {
1313 let cache = Arc::new(Mutex::new(NotificationCache::new()));
1314 let server_id = ServerId::from_static("rust");
1315 cache.lock().await.observe_indexing_signal(
1316 &server_id,
1317 "experimental/serverStatus",
1318 Some(&serde_json::json!({"quiescent": false})),
1319 );
1320 let translator = Arc::new(Translator::new().with_notification_cache(Arc::clone(&cache)));
1321
1322 let short = {
1323 let translator = Arc::clone(&translator);
1324 let server_id = server_id.clone();
1325 tokio::spawn(async move {
1326 translator
1327 .wait_for_indexing_ready_with(
1328 &server_id,
1329 Duration::from_millis(80),
1330 Duration::from_millis(10),
1331 )
1332 .await
1333 })
1334 };
1335 let long = {
1336 let translator = Arc::clone(&translator);
1337 let server_id = server_id.clone();
1338 tokio::spawn(async move {
1339 translator
1340 .wait_for_indexing_ready_with(
1341 &server_id,
1342 Duration::from_secs(30),
1343 Duration::from_millis(10),
1344 )
1345 .await
1346 })
1347 };
1348
1349 let short_result = short.await.unwrap();
1350 assert_matches!(
1351 short_result,
1352 Err(Error::WorkspaceIndexing { .. }),
1353 "the short-timeout waiter must time out on its own schedule, got {short_result:?}"
1354 );
1355
1356 tokio::time::sleep(Duration::from_millis(150)).await;
1359 assert!(
1360 !long.is_finished(),
1361 "a concurrent caller's short timeout must never resolve another caller's \
1362 independent wait early"
1363 );
1364 long.abort();
1365 }
1366
1367 #[test]
1368 fn test_extract_hover_contents_string() {
1369 let marked_string = lsp_types::MarkedString::String("Test hover".to_string());
1370 let contents = lsp_types::Contents::MarkedString(marked_string);
1371 let result = extract_hover_contents(contents);
1372 assert_eq!(result, "Test hover");
1373 }
1374
1375 #[test]
1376 fn test_extract_hover_contents_language_string() {
1377 let marked_string = lsp_types::MarkedString::MarkedStringWithLanguage(
1378 lsp_types::MarkedStringWithLanguage {
1379 language: "rust".to_string(),
1380 value: "fn main() {}".to_string(),
1381 },
1382 );
1383 let contents = lsp_types::Contents::MarkedString(marked_string);
1384 let result = extract_hover_contents(contents);
1385 assert_eq!(result, "```rust\nfn main() {}\n```");
1386 }
1387
1388 #[test]
1389 fn test_extract_hover_contents_markup() {
1390 let markup = lsp_types::MarkupContent {
1391 kind: lsp_types::MarkupKind::Markdown,
1392 value: "# Documentation".to_string(),
1393 };
1394 let contents = lsp_types::Contents::MarkupContent(markup);
1395 let result = extract_hover_contents(contents);
1396 assert_eq!(result, "# Documentation");
1397 }
1398
1399 #[tokio::test]
1403 async fn test_handle_definition_flattens_single_location() {
1404 let dir = TempDir::new().unwrap();
1405 let server_id = ServerId::from_static("rust");
1406 let caps = lsp_types::ServerCapabilities {
1407 definition_provider: Some(lsp_types::DefinitionProvider::Bool(true)),
1408 ..Default::default()
1409 };
1410 let (translator, mut server) = translator_with_capabilities(&dir, &server_id, caps);
1411
1412 let path = canonical_dir(&dir).join("main.rs");
1413 fs::write(&path, "fn main() {}").unwrap();
1414 let target_path = canonical_dir(&dir).join("target.rs");
1415 fs::write(&target_path, "fn target() {}").unwrap();
1416 let target_uri = Url::from_file_path(&target_path).unwrap().to_string();
1417
1418 let translator = Arc::new(translator);
1419 let handle = {
1420 let translator = Arc::clone(&translator);
1421 let path = path.to_string_lossy().to_string();
1422 tokio::spawn(async move {
1423 translator
1424 .handle_definition(client_path(path), Position::at(1, 1), ResultContext::None)
1425 .await
1426 })
1427 };
1428
1429 let mut wire = BufReader::new(&mut server.write_stdout);
1430 let opened = read_framed_message(&mut wire).await;
1431 assert_eq!(opened["method"], "textDocument/didOpen");
1432 let request = read_framed_message(&mut wire).await;
1433 assert_eq!(request["method"], "textDocument/definition");
1434
1435 write_response(
1436 &mut server.read_half_stdin,
1437 &request["id"],
1438 serde_json::json!({
1439 "uri": target_uri,
1440 "range": {
1441 "start": {"line": 0, "character": 0},
1442 "end": {"line": 0, "character": 6}
1443 }
1444 }),
1445 )
1446 .await;
1447
1448 let result = timeout(Duration::from_secs(2), handle)
1449 .await
1450 .expect("handle_definition should not hang")
1451 .unwrap()
1452 .unwrap();
1453
1454 assert_eq!(result.locations.len(), 1);
1455 assert_eq!(result.locations[0].uri, target_uri);
1456 assert!(
1457 !result.locations[0].out_of_workspace,
1458 "a definition location inside the workspace root must not be marked out_of_workspace"
1459 );
1460 }
1461
1462 #[tokio::test]
1470 async fn test_handle_definition_does_not_filter_out_of_workspace_location() {
1471 let dir = TempDir::new().unwrap();
1472 let server_id = ServerId::from_static("rust");
1473 let caps = lsp_types::ServerCapabilities {
1474 definition_provider: Some(lsp_types::DefinitionProvider::Bool(true)),
1475 ..Default::default()
1476 };
1477 let (translator, mut server) = translator_with_capabilities(&dir, &server_id, caps);
1478
1479 let path = dir.path().join("main.rs");
1480 fs::write(&path, "fn main() {}").unwrap();
1481 let outside_uri = "file:///outside/workspace/stdlib.rs";
1482
1483 let translator = Arc::new(translator);
1484 let handle = {
1485 let translator = Arc::clone(&translator);
1486 let path = path.to_string_lossy().to_string();
1487 tokio::spawn(async move {
1488 translator
1489 .handle_definition(client_path(path), Position::at(1, 1), ResultContext::None)
1490 .await
1491 })
1492 };
1493
1494 let mut wire = BufReader::new(&mut server.write_stdout);
1495 let opened = read_framed_message(&mut wire).await;
1496 assert_eq!(opened["method"], "textDocument/didOpen");
1497 let request = read_framed_message(&mut wire).await;
1498 assert_eq!(request["method"], "textDocument/definition");
1499
1500 write_response(
1501 &mut server.read_half_stdin,
1502 &request["id"],
1503 serde_json::json!({
1504 "uri": outside_uri,
1505 "range": {
1506 "start": {"line": 0, "character": 0},
1507 "end": {"line": 0, "character": 6}
1508 }
1509 }),
1510 )
1511 .await;
1512
1513 let result = timeout(Duration::from_secs(2), handle)
1514 .await
1515 .expect("handle_definition should not hang")
1516 .unwrap()
1517 .unwrap();
1518
1519 assert_eq!(
1520 result.locations.len(),
1521 1,
1522 "an out-of-workspace definition location (e.g. stdlib/a dependency) must be \
1523 returned, not dropped"
1524 );
1525 assert_eq!(result.locations[0].uri, outside_uri);
1526 assert!(
1527 result.locations[0].out_of_workspace,
1528 "a definition location outside every workspace root must be marked out_of_workspace"
1529 );
1530 }
1531
1532 #[tokio::test]
1536 async fn test_handle_references_does_not_filter_out_of_workspace_location() {
1537 let dir = TempDir::new().unwrap();
1538 let server_id = ServerId::from_static("rust");
1539 let caps = lsp_types::ServerCapabilities {
1540 references_provider: Some(lsp_types::ReferencesProvider::Bool(true)),
1541 ..Default::default()
1542 };
1543 let (translator, mut server) = translator_with_capabilities(&dir, &server_id, caps);
1544
1545 let path = canonical_dir(&dir).join("main.rs");
1546 fs::write(&path, "fn main() {}").unwrap();
1547 let inside_path = canonical_dir(&dir).join("inside.rs");
1548 fs::write(&inside_path, "fn used() {}").unwrap();
1549 let inside_uri = Url::from_file_path(&inside_path).unwrap().to_string();
1550 let outside_uri = "file:///outside/workspace/stdlib.rs";
1551
1552 let translator = Arc::new(translator);
1553 let handle = {
1554 let translator = Arc::clone(&translator);
1555 let path = path.to_string_lossy().to_string();
1556 tokio::spawn(async move {
1557 translator
1558 .handle_references(client_path(path), pos(1, 1), true, ResultContext::None)
1559 .await
1560 })
1561 };
1562
1563 let mut wire = BufReader::new(&mut server.write_stdout);
1564 let opened = read_framed_message(&mut wire).await;
1565 assert_eq!(opened["method"], "textDocument/didOpen");
1566 let request = read_framed_message(&mut wire).await;
1567 assert_eq!(request["method"], "textDocument/references");
1568
1569 write_response(
1570 &mut server.read_half_stdin,
1571 &request["id"],
1572 serde_json::json!([
1573 {
1574 "uri": inside_uri,
1575 "range": {
1576 "start": {"line": 0, "character": 0},
1577 "end": {"line": 0, "character": 4}
1578 }
1579 },
1580 {
1581 "uri": outside_uri,
1582 "range": {
1583 "start": {"line": 0, "character": 0},
1584 "end": {"line": 0, "character": 4}
1585 }
1586 }
1587 ]),
1588 )
1589 .await;
1590
1591 let result = timeout(Duration::from_secs(2), handle)
1592 .await
1593 .expect("handle_references should not hang")
1594 .unwrap()
1595 .unwrap();
1596 assert!(result.index_coverage.is_none());
1597 assert!(
1598 serde_json::to_value(&result)
1599 .unwrap()
1600 .get("index_coverage")
1601 .is_none()
1602 );
1603 let result = result.result;
1604
1605 assert_eq!(
1606 result.locations.len(),
1607 2,
1608 "both the in-workspace and out-of-workspace locations must survive"
1609 );
1610 assert!(result.locations.iter().any(|l| l.uri == inside_uri));
1611 assert!(result.locations.iter().any(|l| l.uri == outside_uri));
1612 assert!(
1613 !result
1614 .locations
1615 .iter()
1616 .find(|l| l.uri == inside_uri)
1617 .unwrap()
1618 .out_of_workspace,
1619 "an in-workspace reference location must not be marked out_of_workspace"
1620 );
1621 assert!(
1622 result
1623 .locations
1624 .iter()
1625 .find(|l| l.uri == outside_uri)
1626 .unwrap()
1627 .out_of_workspace,
1628 "an out-of-workspace reference location must be marked out_of_workspace"
1629 );
1630 }
1631
1632 #[tokio::test]
1638 async fn test_handle_references_sets_truncated_flag_past_cap() {
1639 let dir = TempDir::new().unwrap();
1640 let server_id = ServerId::from_static("rust");
1641 let caps = lsp_types::ServerCapabilities {
1642 references_provider: Some(lsp_types::ReferencesProvider::Bool(true)),
1643 ..Default::default()
1644 };
1645 let (translator, mut server) = translator_with_capabilities(&dir, &server_id, caps);
1646
1647 let path = dir.path().join("main.rs");
1648 fs::write(&path, "fn main() {}").unwrap();
1649 let uri = Url::from_file_path(&path).unwrap().to_string();
1650
1651 let translator = Arc::new(translator);
1652 let handle = {
1653 let translator = Arc::clone(&translator);
1654 let path = path.to_string_lossy().to_string();
1655 tokio::spawn(async move {
1656 translator
1657 .handle_references(client_path(path), pos(1, 1), true, ResultContext::None)
1658 .await
1659 })
1660 };
1661
1662 let mut wire = BufReader::new(&mut server.write_stdout);
1663 let opened = read_framed_message(&mut wire).await;
1664 assert_eq!(opened["method"], "textDocument/didOpen");
1665 let request = read_framed_message(&mut wire).await;
1666 assert_eq!(request["method"], "textDocument/references");
1667
1668 let locations: Vec<serde_json::Value> = (0..MAX_NORMALIZED_LOCATIONS + 500)
1669 .map(|_| {
1670 serde_json::json!({
1671 "uri": uri,
1672 "range": {
1673 "start": {"line": 0, "character": 0},
1674 "end": {"line": 0, "character": 4}
1675 }
1676 })
1677 })
1678 .collect();
1679 write_response(
1680 &mut server.read_half_stdin,
1681 &request["id"],
1682 serde_json::json!(locations),
1683 )
1684 .await;
1685
1686 let result = timeout(Duration::from_secs(5), handle)
1687 .await
1688 .expect("handle_references should not hang")
1689 .unwrap()
1690 .unwrap()
1691 .result;
1692
1693 assert_eq!(result.locations.len(), MAX_NORMALIZED_LOCATIONS);
1694 assert!(
1695 result.truncated,
1696 "a references response past MAX_NORMALIZED_LOCATIONS must set truncated: true"
1697 );
1698 }
1699
1700 #[tokio::test]
1704 async fn test_handle_implementation_flattens_location_list() {
1705 let dir = TempDir::new().unwrap();
1706 let server_id = ServerId::from_static("rust");
1707 let caps = lsp_types::ServerCapabilities {
1708 implementation_provider: Some(lsp_types::ImplementationProvider::Bool(true)),
1709 ..Default::default()
1710 };
1711 let (translator, mut server) = translator_with_capabilities(&dir, &server_id, caps);
1712
1713 let path = dir.path().join("main.rs");
1714 fs::write(&path, "fn main() {}").unwrap();
1715 let first_impl_path = dir.path().join("impl_a.rs");
1716 fs::write(&first_impl_path, "struct A;").unwrap();
1717 let first_impl_uri = Url::from_file_path(&first_impl_path).unwrap().to_string();
1718 let second_impl_path = dir.path().join("impl_b.rs");
1719 fs::write(&second_impl_path, "struct B;").unwrap();
1720 let second_impl_uri = Url::from_file_path(&second_impl_path).unwrap().to_string();
1721
1722 let translator = Arc::new(translator);
1723 let handle = {
1724 let translator = Arc::clone(&translator);
1725 let path = path.to_string_lossy().to_string();
1726 tokio::spawn(async move {
1727 translator
1728 .handle_implementation(
1729 client_path(path),
1730 Position::at(1, 1),
1731 ResultContext::None,
1732 )
1733 .await
1734 })
1735 };
1736
1737 let mut wire = BufReader::new(&mut server.write_stdout);
1738 let opened = read_framed_message(&mut wire).await;
1739 assert_eq!(opened["method"], "textDocument/didOpen");
1740 let request = read_framed_message(&mut wire).await;
1741 assert_eq!(request["method"], "textDocument/implementation");
1742
1743 write_response(
1744 &mut server.read_half_stdin,
1745 &request["id"],
1746 serde_json::json!([
1747 {
1748 "uri": first_impl_uri,
1749 "range": {
1750 "start": {"line": 0, "character": 0},
1751 "end": {"line": 0, "character": 9}
1752 }
1753 },
1754 {
1755 "uri": second_impl_uri,
1756 "range": {
1757 "start": {"line": 0, "character": 0},
1758 "end": {"line": 0, "character": 9}
1759 }
1760 }
1761 ]),
1762 )
1763 .await;
1764
1765 let result = timeout(Duration::from_secs(2), handle)
1766 .await
1767 .expect("handle_implementation should not hang")
1768 .unwrap()
1769 .unwrap();
1770
1771 assert_eq!(result.locations.len(), 2);
1772 assert_eq!(result.locations[0].uri, first_impl_uri);
1773 assert_eq!(result.locations[1].uri, second_impl_uri);
1774 }
1775
1776 #[tokio::test]
1779 async fn test_handle_declaration_flattens_location_list() {
1780 let dir = TempDir::new().unwrap();
1781 let server_id = ServerId::from_static("rust");
1782 let caps = lsp_types::ServerCapabilities {
1783 declaration_provider: Some(lsp_types::DeclarationProvider::Bool(true)),
1784 ..Default::default()
1785 };
1786 let (translator, mut server) = translator_with_capabilities(&dir, &server_id, caps);
1787
1788 let path = dir.path().join("main.rs");
1789 fs::write(&path, "fn main() {}").unwrap();
1790 let decl_path = dir.path().join("decl.rs");
1791 fs::write(&decl_path, "fn decl();").unwrap();
1792 let decl_uri = Url::from_file_path(&decl_path).unwrap().to_string();
1793
1794 let translator = Arc::new(translator);
1795 let handle = {
1796 let translator = Arc::clone(&translator);
1797 let path = client_path(&path);
1798 tokio::spawn(async move {
1799 translator
1800 .handle_declaration(path, pos(1, 1), ResultContext::None)
1801 .await
1802 })
1803 };
1804
1805 let mut wire = BufReader::new(&mut server.write_stdout);
1806 let opened = read_framed_message(&mut wire).await;
1807 assert_eq!(opened["method"], "textDocument/didOpen");
1808 let request = read_framed_message(&mut wire).await;
1809 assert_eq!(request["method"], "textDocument/declaration");
1810
1811 write_response(
1812 &mut server.read_half_stdin,
1813 &request["id"],
1814 serde_json::json!([{
1815 "uri": decl_uri,
1816 "range": {
1817 "start": {"line": 0, "character": 3},
1818 "end": {"line": 0, "character": 7}
1819 }
1820 }]),
1821 )
1822 .await;
1823
1824 let result = timeout(Duration::from_secs(2), handle)
1825 .await
1826 .expect("handle_declaration should not hang")
1827 .unwrap()
1828 .unwrap();
1829
1830 assert_eq!(result.locations.len(), 1);
1831 assert_eq!(result.locations[0].uri, decl_uri);
1832 }
1833
1834 #[tokio::test]
1836 async fn test_handle_declaration_flattens_link_list() {
1837 let dir = TempDir::new().unwrap();
1838 let server_id = ServerId::from_static("rust");
1839 let caps = lsp_types::ServerCapabilities {
1840 declaration_provider: Some(lsp_types::DeclarationProvider::Bool(true)),
1841 ..Default::default()
1842 };
1843 let (translator, mut server) = translator_with_capabilities(&dir, &server_id, caps);
1844
1845 let path = dir.path().join("main.rs");
1846 fs::write(&path, "fn main() {}").unwrap();
1847 let target_path = dir.path().join("target.h");
1848 fs::write(&target_path, "struct TargetType;").unwrap();
1849 let target_uri = Url::from_file_path(&target_path).unwrap().to_string();
1850
1851 let translator = Arc::new(translator);
1852 let handle = {
1853 let translator = Arc::clone(&translator);
1854 let path = client_path(&path);
1855 tokio::spawn(async move {
1856 translator
1857 .handle_declaration(path, pos(1, 1), ResultContext::None)
1858 .await
1859 })
1860 };
1861
1862 let mut wire = BufReader::new(&mut server.write_stdout);
1863 let _opened = read_framed_message(&mut wire).await;
1864 let request = read_framed_message(&mut wire).await;
1865 assert_eq!(request["method"], "textDocument/declaration");
1866
1867 write_response(
1868 &mut server.read_half_stdin,
1869 &request["id"],
1870 serde_json::json!([{
1871 "targetUri": target_uri,
1872 "targetRange": {
1873 "start": {"line": 0, "character": 0},
1874 "end": {"line": 0, "character": 18}
1875 },
1876 "targetSelectionRange": {
1877 "start": {"line": 0, "character": 7},
1878 "end": {"line": 0, "character": 17}
1879 }
1880 }]),
1881 )
1882 .await;
1883
1884 let result = timeout(Duration::from_secs(2), handle)
1885 .await
1886 .expect("handle_declaration should not hang")
1887 .unwrap()
1888 .unwrap();
1889
1890 assert_eq!(result.locations.len(), 1);
1891 assert_eq!(result.locations[0].range.start.character, 8);
1892 }
1893
1894 #[tokio::test]
1896 async fn test_handle_declaration_null_response_is_empty() {
1897 let dir = TempDir::new().unwrap();
1898 let server_id = ServerId::from_static("rust");
1899 let caps = lsp_types::ServerCapabilities {
1900 declaration_provider: Some(lsp_types::DeclarationProvider::Bool(true)),
1901 ..Default::default()
1902 };
1903 let (translator, mut server) = translator_with_capabilities(&dir, &server_id, caps);
1904
1905 let path = dir.path().join("main.rs");
1906 fs::write(&path, "fn main() {}").unwrap();
1907
1908 let translator = Arc::new(translator);
1909 let handle = {
1910 let translator = Arc::clone(&translator);
1911 let path = client_path(&path);
1912 tokio::spawn(async move {
1913 translator
1914 .handle_declaration(path, pos(1, 1), ResultContext::None)
1915 .await
1916 })
1917 };
1918
1919 let mut wire = BufReader::new(&mut server.write_stdout);
1920 let _opened = read_framed_message(&mut wire).await;
1921 let request = read_framed_message(&mut wire).await;
1922 write_response(
1923 &mut server.read_half_stdin,
1924 &request["id"],
1925 serde_json::Value::Null,
1926 )
1927 .await;
1928
1929 let result = timeout(Duration::from_secs(2), handle)
1930 .await
1931 .expect("handle_declaration should not hang")
1932 .unwrap()
1933 .unwrap();
1934 assert!(result.locations.is_empty());
1935 }
1936
1937 #[tokio::test(start_paused = true)]
1939 async fn test_handle_declaration_returns_workspace_indexing_error_when_loading() {
1940 let dir = TempDir::new().unwrap();
1941 let server_id = ServerId::from_static("rust");
1942 let caps = lsp_types::ServerCapabilities {
1943 declaration_provider: Some(lsp_types::DeclarationProvider::Bool(true)),
1944 ..Default::default()
1945 };
1946 let (translator, _server) = translator_with_capabilities(&dir, &server_id, caps);
1947
1948 let cache = Arc::new(Mutex::new(NotificationCache::new()));
1949 cache.lock().await.observe_indexing_signal(
1950 &server_id,
1951 "experimental/serverStatus",
1952 Some(&serde_json::json!({"quiescent": false})),
1953 );
1954 let translator = translator.with_notification_cache(cache);
1955
1956 let path = dir.path().join("main.rs");
1957 fs::write(&path, "fn main() {}").unwrap();
1958
1959 let err = translator
1960 .handle_declaration(client_path(&path), pos(1, 1), ResultContext::None)
1961 .await
1962 .unwrap_err();
1963
1964 assert_matches!(
1965 err,
1966 Error::WorkspaceIndexing { server_id: id, .. } if id == server_id
1967 );
1968 }
1969
1970 #[tokio::test]
1976 async fn test_handle_type_definition_flattens_definition_link_list() {
1977 let dir = TempDir::new().unwrap();
1978 let server_id = ServerId::from_static("rust");
1979 let caps = lsp_types::ServerCapabilities {
1980 type_definition_provider: Some(lsp_types::TypeDefinitionProvider::Bool(true)),
1981 ..Default::default()
1982 };
1983 let (translator, mut server) = translator_with_capabilities(&dir, &server_id, caps);
1984
1985 let path = dir.path().join("main.rs");
1986 fs::write(&path, "fn main() {}").unwrap();
1987 let target_path = dir.path().join("target_type.rs");
1988 fs::write(&target_path, "struct TargetType;").unwrap();
1989 let target_uri = Url::from_file_path(&target_path).unwrap().to_string();
1990
1991 let translator = Arc::new(translator);
1992 let handle = {
1993 let translator = Arc::clone(&translator);
1994 let path = path.to_string_lossy().to_string();
1995 tokio::spawn(async move {
1996 translator
1997 .handle_type_definition(
1998 client_path(path),
1999 Position::at(1, 1),
2000 ResultContext::None,
2001 )
2002 .await
2003 })
2004 };
2005
2006 let mut wire = BufReader::new(&mut server.write_stdout);
2007 let opened = read_framed_message(&mut wire).await;
2008 assert_eq!(opened["method"], "textDocument/didOpen");
2009 let request = read_framed_message(&mut wire).await;
2010 assert_eq!(request["method"], "textDocument/typeDefinition");
2011
2012 write_response(
2013 &mut server.read_half_stdin,
2014 &request["id"],
2015 serde_json::json!([{
2016 "targetUri": target_uri,
2017 "targetRange": {
2018 "start": {"line": 0, "character": 0},
2019 "end": {"line": 0, "character": 18}
2020 },
2021 "targetSelectionRange": {
2022 "start": {"line": 0, "character": 7},
2023 "end": {"line": 0, "character": 17}
2024 }
2025 }]),
2026 )
2027 .await;
2028
2029 let result = timeout(Duration::from_secs(2), handle)
2030 .await
2031 .expect("handle_type_definition should not hang")
2032 .unwrap()
2033 .unwrap();
2034
2035 assert_eq!(result.locations.len(), 1);
2036 assert_eq!(result.locations[0].uri, target_uri);
2037 assert_eq!(result.locations[0].range.start.character, 8);
2038 }
2039
2040 #[tokio::test]
2052 async fn test_lsp_locations_to_mcp_reads_disk_once_per_distinct_file_line() {
2053 let dir = TempDir::new().unwrap();
2054 let mut uris = Vec::new();
2055 for i in 0..3 {
2056 let path = dir.path().join(format!("file{i}.rs"));
2057 fs::write(&path, "hello").unwrap();
2058 uris.push(path_to_uri(&path).unwrap());
2059 }
2060
2061 let ctx = test_ctx_with(PositionEncoding::Utf8);
2062 let locs: Vec<lsp_types::Location> = (0..300)
2064 .map(|i| lsp_types::Location {
2065 uri: uris[i % 3].clone(),
2066 range: lsp_types::Range {
2067 start: lsp_types::Position {
2068 line: 0,
2069 character: 0,
2070 },
2071 end: lsp_types::Position {
2072 line: 0,
2073 character: 3,
2074 },
2075 },
2076 })
2077 .collect();
2078
2079 let result = lsp_locations_to_mcp(locs, &ctx).await;
2080
2081 assert_eq!(result.locations.len(), 300);
2082 assert!(!result.truncated);
2083 assert!(
2084 result.locations.iter().all(|l| l.range.end.character == 4),
2085 "MCP columns are 1-based, so LSP byte offset 3 in all-ASCII \"hello\" must convert \
2086 to 4"
2087 );
2088 assert_eq!(
2089 lock_std(&ctx.line_cache).entries.len(),
2090 3,
2091 "300 locations across 3 distinct files must populate the cache with exactly 3 \
2092 entries (one disk read per distinct file/line), not one per location"
2093 );
2094 }
2095
2096 #[tokio::test]
2103 async fn test_lsp_locations_to_mcp_truncates_to_max_normalized_locations() {
2104 let ctx = test_ctx();
2105 let uri = test_uri();
2106 let locs: Vec<lsp_types::Location> = (0..MAX_NORMALIZED_LOCATIONS + 500)
2107 .map(|_| lsp_types::Location {
2108 uri: uri.clone(),
2109 range: lsp_types::Range {
2110 start: lsp_types::Position {
2111 line: 0,
2112 character: 0,
2113 },
2114 end: lsp_types::Position {
2115 line: 0,
2116 character: 1,
2117 },
2118 },
2119 })
2120 .collect();
2121
2122 let result = lsp_locations_to_mcp(locs, &ctx).await;
2123
2124 assert_eq!(result.locations.len(), MAX_NORMALIZED_LOCATIONS);
2125 assert!(
2126 result.truncated,
2127 "a response naming more than MAX_NORMALIZED_LOCATIONS must report truncated: true"
2128 );
2129 }
2130
2131 #[test]
2132 fn test_item_budget_admit_spends_shared_allowance() {
2133 let mut budget = ItemBudget::new();
2134 let first = budget.admit(vec![0u8; MAX_NORMALIZED_LOCATIONS - 1]);
2135 assert_eq!(first.len(), MAX_NORMALIZED_LOCATIONS - 1);
2136 assert!(!budget.truncated());
2137
2138 let second = budget.admit(vec![0u8; 5]);
2139 assert_eq!(second.len(), 1);
2140 assert!(budget.truncated());
2141
2142 assert_eq!(budget.admit(vec![0u8; 3]).len(), 0);
2143 }
2144
2145 #[test]
2146 fn test_item_budget_admit_whole_never_splits_a_list() {
2147 let mut budget = ItemBudget::new();
2148 assert!(
2149 budget
2150 .admit_whole(vec![0u8; MAX_NORMALIZED_LOCATIONS - 2])
2151 .is_some()
2152 );
2153 assert!(budget.admit_whole(vec![0u8; 3]).is_none());
2154 assert!(budget.truncated());
2155 assert!(budget.admit_whole(vec![0u8; 2]).is_some());
2156 }
2157
2158 #[test]
2159 fn test_item_budget_with_cap_admits_a_lazy_sequence_up_to_the_cap() {
2160 let mut budget = ItemBudget::with_cap(3);
2161 assert_eq!(budget.admit_iter(0..3), [0, 1, 2]);
2162 assert!(!budget.truncated());
2163
2164 let mut budget = ItemBudget::with_cap(3);
2165 assert_eq!(budget.admit_iter(0..), [0, 1, 2]);
2166 assert!(budget.truncated());
2167 assert_eq!(budget.admit_iter(0..2).len(), 0);
2168 }
2169
2170 #[test]
2171 fn test_item_budget_exactly_at_cap_is_not_truncated() {
2172 let mut budget = ItemBudget::new();
2173 let items = budget.admit(vec![0u8; MAX_NORMALIZED_LOCATIONS]);
2174 assert_eq!(items.len(), MAX_NORMALIZED_LOCATIONS);
2175 assert!(!budget.truncated());
2176 }
2177}