deps_lsp/handlers/
hover.rs1use crate::config::DepsConfig;
4use crate::document::{ServerState, ensure_document_loaded};
5use deps_core::VersionData;
6use std::sync::Arc;
7use tokio::sync::RwLock;
8use tower_lsp_server::Client;
9use tower_lsp_server::ls_types::{Hover, HoverParams};
10
11pub async fn handle_hover(
13 state: Arc<ServerState>,
14 params: HoverParams,
15 client: Client,
16 config: Arc<RwLock<DepsConfig>>,
17) -> Option<Hover> {
18 let uri = ¶ms.text_document_position_params.text_document.uri;
19 let position = params.text_document_position_params.position;
20
21 if !ensure_document_loaded(uri, Arc::clone(&state), client, Arc::clone(&config)).await {
23 tracing::warn!("Could not load document for hover: {:?}", uri);
24 return None;
25 }
26
27 let (freshness, offline, supply_chain_enabled) = {
30 let config = config.read().await;
31 (
32 config.freshness.to_settings(),
33 config.network.offline,
34 config.supply_chain.enabled,
35 )
36 };
37
38 let (
44 ecosystem,
45 ecosystem_id,
46 parse_result,
47 cached_versions,
48 resolved_versions,
49 vulnerabilities,
50 outcomes,
51 ) = state
52 .with_document(uri, |doc| {
53 let ecosystem = state.ecosystem_registry.get(doc.ecosystem_id())?;
54 let parse_result = doc.parse_result_arc()?;
55 Some((
56 ecosystem,
57 doc.ecosystem,
58 parse_result,
59 doc.cached_versions.clone(),
60 doc.resolved_versions.clone(),
61 doc.vulnerabilities.clone(),
62 doc.outcomes.clone(),
63 ))
64 })
65 .flatten()?;
66
67 let mut versions = VersionData::new(&cached_versions, &resolved_versions)
68 .with_vulnerabilities(&vulnerabilities)
69 .with_outcomes(&outcomes)
70 .with_ecosystem(ecosystem_id)
71 .with_offline(offline);
72 if supply_chain_enabled {
77 versions = versions.with_trust(&state.deps_dev);
78 }
79
80 ecosystem
81 .generate_hover(parse_result.as_ref(), position, versions, freshness)
82 .await
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use crate::document::ServerState;
89 use crate::test_utils::test_helpers::create_test_client_and_config;
90 use deps_core::EcosystemId;
91 use tower_lsp_server::ls_types::{
92 Position, TextDocumentIdentifier, TextDocumentPositionParams,
93 };
94
95 #[tokio::test]
98 async fn test_handle_hover_missing_document() {
99 let state = Arc::new(ServerState::new());
100 let uri = deps_core::test_util::test_uri("/test/Cargo.toml");
101 let (client, config) = create_test_client_and_config();
102
103 let params = HoverParams {
104 text_document_position_params: TextDocumentPositionParams {
105 text_document: TextDocumentIdentifier { uri },
106 position: Position::new(0, 0),
107 },
108 work_done_progress_params: Default::default(),
109 };
110
111 let result = handle_hover(state, params, client, config).await;
112 assert!(result.is_none());
113 }
114
115 #[cfg(feature = "cargo")]
117 mod cargo_tests {
118 use super::*;
119 use crate::document::DocumentState;
120
121 #[tokio::test]
122 async fn test_handle_hover() {
123 let state = Arc::new(ServerState::new());
124 let uri = deps_core::test_util::test_uri("/test/Cargo.toml");
125
126 let ecosystem = state.ecosystem_registry.get("cargo").unwrap();
127 let content = r#"[dependencies]
128serde = "1.0.0"
129"#
130 .to_string();
131
132 let parse_result = ecosystem
133 .parse_manifest(&content, &uri)
134 .await
135 .expect("Failed to parse manifest");
136
137 let doc_state =
138 DocumentState::new_from_parse_result(EcosystemId::Cargo, content, parse_result);
139 state.update_document(uri.clone(), doc_state);
140
141 let params = HoverParams {
142 text_document_position_params: TextDocumentPositionParams {
143 text_document: TextDocumentIdentifier { uri },
144 position: Position::new(1, 0),
145 },
146 work_done_progress_params: Default::default(),
147 };
148
149 let (client, config) = create_test_client_and_config();
150 let _result = handle_hover(state, params, client, config).await;
151 }
153
154 #[tokio::test]
155 async fn test_handle_hover_no_parse_result() {
156 let state = Arc::new(ServerState::new());
157 let uri = deps_core::test_util::test_uri("/test/Cargo.toml");
158
159 let doc_state =
160 DocumentState::new_without_parse_result(EcosystemId::Cargo, String::new());
161 state.update_document(uri.clone(), doc_state);
162
163 let params = HoverParams {
164 text_document_position_params: TextDocumentPositionParams {
165 text_document: TextDocumentIdentifier { uri },
166 position: Position::new(0, 0),
167 },
168 work_done_progress_params: Default::default(),
169 };
170
171 let (client, config) = create_test_client_and_config();
172 let result = handle_hover(state, params, client, config).await;
173 assert!(result.is_none());
174 }
175 }
176
177 #[cfg(feature = "npm")]
179 mod npm_tests {
180 use super::*;
181 use crate::document::DocumentState;
182
183 #[tokio::test]
184 async fn test_handle_hover() {
185 let state = Arc::new(ServerState::new());
186 let uri = deps_core::test_util::test_uri("/test/package.json");
187
188 let ecosystem = state.ecosystem_registry.get("npm").unwrap();
189 let content = r#"{"dependencies": {"express": "4.0.0"}}"#.to_string();
190
191 let parse_result = ecosystem
192 .parse_manifest(&content, &uri)
193 .await
194 .expect("Failed to parse manifest");
195
196 let doc_state =
197 DocumentState::new_from_parse_result(EcosystemId::Npm, content, parse_result);
198 state.update_document(uri.clone(), doc_state);
199
200 let params = HoverParams {
201 text_document_position_params: TextDocumentPositionParams {
202 text_document: TextDocumentIdentifier { uri },
203 position: Position::new(0, 20),
204 },
205 work_done_progress_params: Default::default(),
206 };
207
208 let (client, config) = create_test_client_and_config();
209 let _result = handle_hover(state, params, client, config).await;
210 }
212 }
213}