mcpls_core/bridge/translator/
selection_range.rs1use super::Translator;
4use super::dto::{MAX_SELECTION_CHAIN, Position, SelectionRangesResult};
5use super::navigation::ItemBudget;
6use super::positioned::Positioned;
7use super::routing::{FileLocalCapability, IndexingGate};
8use crate::bridge::ClientPath;
9use crate::error::Result;
10
11fn chain(first: lsp_types::SelectionRange) -> (Vec<lsp_types::Range>, bool) {
17 let mut next = Some(first);
18 let ancestors = std::iter::from_fn(|| {
19 let node = next.take()?;
20 next = node.parent.map(|parent| *parent);
21 Some(node.range)
22 });
23 let mut budget = ItemBudget::with_cap(MAX_SELECTION_CHAIN);
24 let ranges = budget.admit_iter(ancestors);
25 (ranges, budget.truncated())
26}
27
28impl Translator {
29 pub async fn handle_selection_range(
44 &self,
45 file_path: ClientPath,
46 position: Position,
47 ) -> Result<SelectionRangesResult> {
48 let Positioned {
49 result: response,
50 ctx,
51 doc,
52 } = self
53 .position_request::<lsp_types::SelectionRangeRequest>(
54 &file_path,
55 position,
56 IndexingGate::FileLocal(FileLocalCapability::SelectionRange),
57 (),
58 )
59 .await?;
60 let response_uri = doc.uri().clone();
61
62 let (lsp_ranges, truncated) = response
63 .and_then(|chains| chains.into_iter().next())
64 .map_or_default(chain);
65 let mut ranges = Vec::with_capacity(lsp_ranges.len());
66 for range in lsp_ranges {
67 ranges.push(ctx.normalize_range(&response_uri, range).await);
68 }
69
70 Ok(SelectionRangesResult {
71 ranges,
72 truncated,
73 positions_degraded: ctx.positions_degraded(),
74 })
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use std::sync::Arc;
81 use std::time::Duration;
82 use std::{assert_matches, fs};
83
84 use tempfile::TempDir;
85 use tokio::io::BufReader;
86 use tokio::time::timeout;
87
88 use super::*;
89 use crate::bridge::translator::testing::*;
90 use crate::config::ServerId;
91 use crate::error::Error;
92 use crate::test_lsp::client_path;
93
94 fn caps() -> lsp_types::ServerCapabilities {
95 lsp_types::ServerCapabilities {
96 selection_range_provider: Some(lsp_types::SelectionRangeProvider::Bool(true)),
97 ..Default::default()
98 }
99 }
100
101 fn range_json(start: u32, end: u32) -> serde_json::Value {
102 serde_json::json!({
103 "start": {"line": 0, "character": start},
104 "end": {"line": 0, "character": end}
105 })
106 }
107
108 fn nested(widths: &[(u32, u32)]) -> serde_json::Value {
109 widths
110 .iter()
111 .rev()
112 .fold(serde_json::Value::Null, |parent, (s, e)| {
113 let mut node = serde_json::json!({ "range": range_json(*s, *e) });
114 if !parent.is_null() {
115 node["parent"] = parent;
116 }
117 node
118 })
119 }
120
121 async fn selection_with_response(
122 source: &str,
123 response: serde_json::Value,
124 encoding: Option<lsp_types::PositionEncodingKind>,
125 ) -> (serde_json::Value, SelectionRangesResult) {
126 let dir = TempDir::new().unwrap();
127 let server_id = ServerId::from_static("rust");
128 let (translator, mut server) = encoding.map_or_else(
129 || translator_with_capabilities(&dir, &server_id, caps()),
130 |encoding| {
131 translator_with_capabilities_and_encoding(&dir, &server_id, caps(), encoding)
132 },
133 );
134 let path = dir.path().join("a.rs");
135 fs::write(&path, source).unwrap();
136
137 let translator = Arc::new(translator);
138 let handle = {
139 let translator = Arc::clone(&translator);
140 let path = path.to_string_lossy().into_owned();
141 tokio::spawn(async move {
142 translator
143 .handle_selection_range(client_path(path), pos(1, 5))
144 .await
145 })
146 };
147 let mut wire = BufReader::new(&mut server.write_stdout);
148 assert_eq!(
149 read_framed_message(&mut wire).await["method"],
150 "textDocument/didOpen"
151 );
152 let request = read_framed_message(&mut wire).await;
153 assert_eq!(request["method"], "textDocument/selectionRange");
154 write_response(&mut server.read_half_stdin, &request["id"], response).await;
155
156 let result = timeout(Duration::from_secs(30), handle)
157 .await
158 .expect("handler call should not hang")
159 .unwrap()
160 .unwrap();
161 (request, result)
162 }
163
164 #[tokio::test]
165 async fn returns_the_chain_innermost_first_and_sends_one_position() {
166 let (request, result) = selection_with_response(
167 "f(a + b);",
168 serde_json::json!([nested(&[(2, 3), (2, 7), (0, 8), (0, 9)])]),
169 None,
170 )
171 .await;
172
173 assert_eq!(
174 request["params"]["positions"],
175 serde_json::json!([{"line": 0, "character": 4}])
176 );
177 let spans: Vec<_> = result
178 .ranges
179 .iter()
180 .map(|r| (r.start.character, r.end.character))
181 .collect();
182 assert_eq!(spans, [(3, 4), (3, 8), (1, 9), (1, 10)]);
183 assert!(!result.truncated);
184 assert_eq!(result.positions_degraded, None);
185 let wire = serde_json::to_value(&result).unwrap();
186 assert!(wire.get("truncated").is_none());
187 }
188
189 #[tokio::test]
190 async fn keeps_equal_consecutive_ranges_as_reported() {
191 let (_, result) = selection_with_response(
192 "f(a);",
193 serde_json::json!([nested(&[(2, 3), (2, 3), (0, 4)])]),
194 None,
195 )
196 .await;
197 assert_eq!(result.ranges.len(), 3);
198 }
199
200 #[tokio::test]
201 async fn null_and_empty_answers_are_an_empty_chain() {
202 for response in [serde_json::Value::Null, serde_json::json!([])] {
203 let (_, result) = selection_with_response("f(a);", response, None).await;
204 assert!(result.ranges.is_empty() && !result.truncated);
205 }
206 }
207
208 #[tokio::test]
209 async fn a_chain_longer_than_the_cap_keeps_the_innermost_ranges() {
210 let widths: Vec<(u32, u32)> = (0..100).map(|i| (50 - i / 2, 60 + i)).collect();
211 let (_, result) =
212 selection_with_response("x", serde_json::json!([nested(&widths)]), None).await;
213
214 assert_eq!(result.ranges.len(), MAX_SELECTION_CHAIN);
215 assert!(result.truncated);
216 assert_eq!(result.ranges[0].end.character, 61);
217 assert_eq!(
218 result.ranges[MAX_SELECTION_CHAIN - 1].end.character,
219 60 + u32::try_from(MAX_SELECTION_CHAIN).unwrap()
220 );
221 assert_eq!(serde_json::to_value(&result).unwrap()["truncated"], true);
222 }
223
224 #[tokio::test]
225 async fn a_chain_of_exactly_the_cap_is_not_truncated_and_one_more_is() {
226 let widths = |len: u32| -> Vec<(u32, u32)> { (0..len).map(|i| (50 - i, 60 + i)).collect() };
227 let cap = u32::try_from(MAX_SELECTION_CHAIN).unwrap();
228
229 let (_, exact) =
230 selection_with_response("x", serde_json::json!([nested(&widths(cap))]), None).await;
231 assert_eq!(exact.ranges.len(), MAX_SELECTION_CHAIN);
232 assert!(!exact.truncated);
233
234 let (_, over) =
235 selection_with_response("x", serde_json::json!([nested(&widths(cap + 1))]), None).await;
236 assert_eq!(over.ranges.len(), MAX_SELECTION_CHAIN);
237 assert!(over.truncated);
238 }
239
240 #[tokio::test]
241 async fn only_the_first_chain_of_the_response_is_used() {
242 let (_, result) = selection_with_response(
243 "f(a);",
244 serde_json::json!([nested(&[(2, 3)]), nested(&[(0, 4), (0, 5)])]),
245 None,
246 )
247 .await;
248 assert_eq!(result.ranges.len(), 1);
249 }
250
251 #[tokio::test]
252 async fn utf8_server_ranges_and_position_are_converted() {
253 let (request, result) = selection_with_response(
255 "é = a;",
256 serde_json::json!([nested(&[(5, 6), (0, 7)])]),
257 Some(lsp_types::PositionEncodingKind::UTF8),
258 )
259 .await;
260 assert_eq!(request["params"]["positions"][0]["character"], 5);
261 assert_eq!(result.ranges[0].start.character, 5);
262 assert_eq!(result.ranges[0].end.character, 6);
263 assert_eq!(result.positions_degraded, None);
264 }
265
266 #[tokio::test]
267 async fn rejects_missing_capability() {
268 let dir = TempDir::new().unwrap();
269 let (translator, _server) = translator_with_capabilities(
270 &dir,
271 &ServerId::from_static("rust"),
272 lsp_types::ServerCapabilities::default(),
273 );
274 let path = dir.path().join("a.rs");
275 fs::write(&path, "f(a);").unwrap();
276 let result = translator
277 .handle_selection_range(client_path(path.to_string_lossy().into_owned()), pos(1, 1))
278 .await;
279 assert_matches!(result, Err(Error::CapabilityNotSupported { .. }));
280 }
281
282 #[test]
283 fn chain_is_bounded_whatever_the_nesting() {
284 let mut node = lsp_types::SelectionRange {
285 range: lsp_types::Range::default(),
286 parent: None,
287 };
288 for _ in 0..120 {
289 node = lsp_types::SelectionRange {
290 range: lsp_types::Range::default(),
291 parent: Some(Box::new(node)),
292 };
293 }
294 let (ranges, truncated) = chain(node);
295 assert_eq!(ranges.len(), MAX_SELECTION_CHAIN);
296 assert!(truncated);
297 }
298}