|
1 | | -use ide::{FileRange, references::References}; |
| 1 | +use ide::{ |
| 2 | + FilePosition, FileRange, SymbolKind, document_symbols::DocumentSymbol, |
| 3 | + navigation_target::NavTarget, references::References, |
| 4 | +}; |
2 | 5 | use itertools::Itertools; |
| 6 | +use utils::text_edit::{TextRange, TextSize}; |
3 | 7 |
|
4 | 8 | use crate::{ |
5 | 9 | global_state::snapshot::GlobalStateSnapshot, |
@@ -33,6 +37,98 @@ pub(crate) fn handle_goto_declaration( |
33 | 37 | Ok(Some(res)) |
34 | 38 | } |
35 | 39 |
|
| 40 | +pub(crate) fn handle_goto_type_definition( |
| 41 | + snap: GlobalStateSnapshot, |
| 42 | + params: lsp_types::request::GotoTypeDefinitionParams, |
| 43 | +) -> anyhow::Result<Option<lsp_types::request::GotoTypeDefinitionResponse>> { |
| 44 | + handle_goto_definition(snap, params) |
| 45 | +} |
| 46 | + |
| 47 | +pub(crate) fn handle_prepare_call_hierarchy( |
| 48 | + snap: GlobalStateSnapshot, |
| 49 | + params: lsp_types::CallHierarchyPrepareParams, |
| 50 | +) -> anyhow::Result<Option<Vec<lsp_types::CallHierarchyItem>>> { |
| 51 | + let position = from_proto::file_position(&snap, params.text_document_position_params)?; |
| 52 | + let Some(nav_info) = snap.analysis.goto_definition(position)? else { |
| 53 | + return Ok(None); |
| 54 | + }; |
| 55 | + |
| 56 | + let items = nav_info |
| 57 | + .info |
| 58 | + .into_iter() |
| 59 | + .filter_map(|nav| call_hierarchy_item_for_nav(&snap, nav).transpose()) |
| 60 | + .collect::<anyhow::Result<Vec<_>>>()? |
| 61 | + .into_iter() |
| 62 | + .unique_by(call_hierarchy_item_key) |
| 63 | + .collect_vec(); |
| 64 | + |
| 65 | + Ok((!items.is_empty()).then_some(items)) |
| 66 | +} |
| 67 | + |
| 68 | +pub(crate) fn handle_call_hierarchy_incoming( |
| 69 | + snap: GlobalStateSnapshot, |
| 70 | + params: lsp_types::CallHierarchyIncomingCallsParams, |
| 71 | +) -> anyhow::Result<Option<Vec<lsp_types::CallHierarchyIncomingCall>>> { |
| 72 | + let target = params.item; |
| 73 | + let target_file_id = from_proto::file_id(&snap, &target.uri)?; |
| 74 | + let target_line_info = snap.line_info(target_file_id)?; |
| 75 | + let target_selection_range = from_proto::text_range(&target_line_info, target.selection_range)?; |
| 76 | + |
| 77 | + let mut groups = Vec::<(lsp_types::CallHierarchyItem, Vec<lsp_types::Range>)>::new(); |
| 78 | + for reference in reference_ranges_for_call_item(&snap, &target)? { |
| 79 | + if reference.file_id == target_file_id && reference.range == target_selection_range { |
| 80 | + continue; |
| 81 | + } |
| 82 | + |
| 83 | + let Some(caller) = enclosing_module_item(&snap, reference)? else { |
| 84 | + continue; |
| 85 | + }; |
| 86 | + let line_info = snap.line_info(reference.file_id)?; |
| 87 | + push_call_range(&mut groups, caller, to_proto::range(&line_info, reference.range)); |
| 88 | + } |
| 89 | + |
| 90 | + let calls = groups |
| 91 | + .into_iter() |
| 92 | + .map(|(from, from_ranges)| lsp_types::CallHierarchyIncomingCall { from, from_ranges }) |
| 93 | + .collect_vec(); |
| 94 | + Ok((!calls.is_empty()).then_some(calls)) |
| 95 | +} |
| 96 | + |
| 97 | +pub(crate) fn handle_call_hierarchy_outgoing( |
| 98 | + snap: GlobalStateSnapshot, |
| 99 | + params: lsp_types::CallHierarchyOutgoingCallsParams, |
| 100 | +) -> anyhow::Result<Option<Vec<lsp_types::CallHierarchyOutgoingCall>>> { |
| 101 | + let caller = params.item; |
| 102 | + let caller_file_id = from_proto::file_id(&snap, &caller.uri)?; |
| 103 | + let caller_line_info = snap.line_info(caller_file_id)?; |
| 104 | + let caller_range = from_proto::text_range(&caller_line_info, caller.range)?; |
| 105 | + |
| 106 | + let mut groups = Vec::<(lsp_types::CallHierarchyItem, Vec<lsp_types::Range>)>::new(); |
| 107 | + for callee in workspace_module_items(&snap)? { |
| 108 | + if same_call_hierarchy_item(&caller, &callee) { |
| 109 | + continue; |
| 110 | + } |
| 111 | + |
| 112 | + for reference in reference_ranges_for_call_item(&snap, &callee)? { |
| 113 | + if reference.file_id == caller_file_id |
| 114 | + && range_contains_range(caller_range, reference.range) |
| 115 | + { |
| 116 | + push_call_range( |
| 117 | + &mut groups, |
| 118 | + callee.clone(), |
| 119 | + to_proto::range(&caller_line_info, reference.range), |
| 120 | + ); |
| 121 | + } |
| 122 | + } |
| 123 | + } |
| 124 | + |
| 125 | + let calls = groups |
| 126 | + .into_iter() |
| 127 | + .map(|(to, from_ranges)| lsp_types::CallHierarchyOutgoingCall { to, from_ranges }) |
| 128 | + .collect_vec(); |
| 129 | + Ok((!calls.is_empty()).then_some(calls)) |
| 130 | +} |
| 131 | + |
36 | 132 | pub(crate) fn handle_document_highlight( |
37 | 133 | snap: GlobalStateSnapshot, |
38 | 134 | params: lsp_types::DocumentHighlightParams, |
@@ -88,6 +184,193 @@ pub(crate) fn handle_references( |
88 | 184 | Ok(Some(locations)) |
89 | 185 | } |
90 | 186 |
|
| 187 | +fn reference_ranges_for_call_item( |
| 188 | + snap: &GlobalStateSnapshot, |
| 189 | + item: &lsp_types::CallHierarchyItem, |
| 190 | +) -> anyhow::Result<Vec<FileRange>> { |
| 191 | + let file_id = from_proto::file_id(snap, &item.uri)?; |
| 192 | + let line_info = snap.line_info(file_id)?; |
| 193 | + let offset = from_proto::offset(&line_info, item.selection_range.start)?; |
| 194 | + let position = FilePosition { file_id, offset }; |
| 195 | + let config = snap.config.references(); |
| 196 | + let Some(references) = snap.analysis.references(position, config)? else { |
| 197 | + return Ok(Vec::new()); |
| 198 | + }; |
| 199 | + |
| 200 | + Ok(references |
| 201 | + .into_iter() |
| 202 | + .flat_map(|References { refs, .. }| { |
| 203 | + refs.into_iter().flat_map(|(file_id, refs)| { |
| 204 | + refs.into_iter().map(move |(range, _)| FileRange { file_id, range }) |
| 205 | + }) |
| 206 | + }) |
| 207 | + .unique() |
| 208 | + .collect_vec()) |
| 209 | +} |
| 210 | + |
| 211 | +fn call_hierarchy_item_for_nav( |
| 212 | + snap: &GlobalStateSnapshot, |
| 213 | + nav: NavTarget, |
| 214 | +) -> anyhow::Result<Option<lsp_types::CallHierarchyItem>> { |
| 215 | + let Some(kind) = nav.kind else { |
| 216 | + return Ok(None); |
| 217 | + }; |
| 218 | + if !is_call_hierarchy_kind(kind) { |
| 219 | + return Ok(None); |
| 220 | + } |
| 221 | + |
| 222 | + let line_info = snap.line_info(nav.file_id)?; |
| 223 | + let uri = to_proto::url(snap, nav.file_id)?; |
| 224 | + let range = to_proto::range(&line_info, nav.full_range); |
| 225 | + let selection_range = to_proto::range(&line_info, nav.focus_or_full_range()); |
| 226 | + let name = nav |
| 227 | + .name |
| 228 | + .map(|name| name.to_string()) |
| 229 | + .unwrap_or_else(|| nav.description.clone().unwrap_or_else(|| "<anonymous>".to_owned())); |
| 230 | + let detail = nav.container_name.map(|name| name.to_string()).or(nav.description); |
| 231 | + |
| 232 | + Ok(Some(lsp_types::CallHierarchyItem { |
| 233 | + name, |
| 234 | + kind: to_proto::symbol_kind(kind), |
| 235 | + tags: None, |
| 236 | + detail, |
| 237 | + uri, |
| 238 | + range, |
| 239 | + selection_range, |
| 240 | + data: None, |
| 241 | + })) |
| 242 | +} |
| 243 | + |
| 244 | +fn workspace_module_items( |
| 245 | + snap: &GlobalStateSnapshot, |
| 246 | +) -> anyhow::Result<Vec<lsp_types::CallHierarchyItem>> { |
| 247 | + let mut file_ids = snap.file_ids(); |
| 248 | + file_ids.sort_unstable_by_key(|file_id| file_id.0); |
| 249 | + file_ids.dedup(); |
| 250 | + |
| 251 | + let mut items = Vec::new(); |
| 252 | + for file_id in file_ids { |
| 253 | + let uri = to_proto::url(snap, file_id)?; |
| 254 | + let line_info = snap.line_info(file_id)?; |
| 255 | + for symbol in snap.analysis.document_symbol(file_id)? { |
| 256 | + collect_module_items(&uri, &line_info, symbol, &mut items); |
| 257 | + } |
| 258 | + } |
| 259 | + |
| 260 | + Ok(items.into_iter().unique_by(call_hierarchy_item_key).collect()) |
| 261 | +} |
| 262 | + |
| 263 | +fn collect_module_items( |
| 264 | + uri: &lsp_types::Url, |
| 265 | + line_info: &utils::lines::LineInfo, |
| 266 | + symbol: DocumentSymbol, |
| 267 | + items: &mut Vec<lsp_types::CallHierarchyItem>, |
| 268 | +) { |
| 269 | + if symbol.kind == SymbolKind::Module { |
| 270 | + items.push(call_hierarchy_item_for_symbol(uri, line_info, &symbol)); |
| 271 | + } |
| 272 | + |
| 273 | + for child in symbol.children { |
| 274 | + collect_module_items(uri, line_info, child, items); |
| 275 | + } |
| 276 | +} |
| 277 | + |
| 278 | +fn enclosing_module_item( |
| 279 | + snap: &GlobalStateSnapshot, |
| 280 | + range: FileRange, |
| 281 | +) -> anyhow::Result<Option<lsp_types::CallHierarchyItem>> { |
| 282 | + let uri = to_proto::url(snap, range.file_id)?; |
| 283 | + let line_info = snap.line_info(range.file_id)?; |
| 284 | + let mut best = None; |
| 285 | + for symbol in snap.analysis.document_symbol(range.file_id)? { |
| 286 | + find_enclosing_module_symbol(symbol, range.range.start(), &mut best); |
| 287 | + } |
| 288 | + |
| 289 | + Ok(best.map(|symbol| call_hierarchy_item_for_symbol(&uri, &line_info, &symbol))) |
| 290 | +} |
| 291 | + |
| 292 | +fn find_enclosing_module_symbol( |
| 293 | + symbol: DocumentSymbol, |
| 294 | + offset: TextSize, |
| 295 | + best: &mut Option<DocumentSymbol>, |
| 296 | +) { |
| 297 | + if !range_contains_offset(symbol.full_range, offset) { |
| 298 | + return; |
| 299 | + } |
| 300 | + |
| 301 | + if symbol.kind == SymbolKind::Module |
| 302 | + && best.as_ref().is_none_or(|current| symbol.full_range.len() < current.full_range.len()) |
| 303 | + { |
| 304 | + *best = Some(symbol.clone()); |
| 305 | + } |
| 306 | + |
| 307 | + for child in symbol.children { |
| 308 | + find_enclosing_module_symbol(child, offset, best); |
| 309 | + } |
| 310 | +} |
| 311 | + |
| 312 | +fn call_hierarchy_item_for_symbol( |
| 313 | + uri: &lsp_types::Url, |
| 314 | + line_info: &utils::lines::LineInfo, |
| 315 | + symbol: &DocumentSymbol, |
| 316 | +) -> lsp_types::CallHierarchyItem { |
| 317 | + lsp_types::CallHierarchyItem { |
| 318 | + name: symbol.name.clone(), |
| 319 | + kind: to_proto::symbol_kind(symbol.kind), |
| 320 | + tags: None, |
| 321 | + detail: symbol.container_name.clone(), |
| 322 | + uri: uri.clone(), |
| 323 | + range: to_proto::range(line_info, symbol.full_range), |
| 324 | + selection_range: to_proto::range(line_info, symbol.focus_range), |
| 325 | + data: None, |
| 326 | + } |
| 327 | +} |
| 328 | + |
| 329 | +fn is_call_hierarchy_kind(kind: SymbolKind) -> bool { |
| 330 | + matches!(kind, SymbolKind::Module) |
| 331 | +} |
| 332 | + |
| 333 | +fn push_call_range( |
| 334 | + groups: &mut Vec<(lsp_types::CallHierarchyItem, Vec<lsp_types::Range>)>, |
| 335 | + item: lsp_types::CallHierarchyItem, |
| 336 | + range: lsp_types::Range, |
| 337 | +) { |
| 338 | + if let Some((_, ranges)) = |
| 339 | + groups.iter_mut().find(|(existing, _)| same_call_hierarchy_item(existing, &item)) |
| 340 | + { |
| 341 | + if !ranges.contains(&range) { |
| 342 | + ranges.push(range); |
| 343 | + } |
| 344 | + return; |
| 345 | + } |
| 346 | + |
| 347 | + groups.push((item, vec![range])); |
| 348 | +} |
| 349 | + |
| 350 | +fn same_call_hierarchy_item( |
| 351 | + lhs: &lsp_types::CallHierarchyItem, |
| 352 | + rhs: &lsp_types::CallHierarchyItem, |
| 353 | +) -> bool { |
| 354 | + lhs.name == rhs.name |
| 355 | + && lhs.uri == rhs.uri |
| 356 | + && lhs.range == rhs.range |
| 357 | + && lhs.selection_range == rhs.selection_range |
| 358 | +} |
| 359 | + |
| 360 | +fn call_hierarchy_item_key( |
| 361 | + item: &lsp_types::CallHierarchyItem, |
| 362 | +) -> (String, lsp_types::Url, lsp_types::Range, lsp_types::Range) { |
| 363 | + (item.name.clone(), item.uri.clone(), item.range, item.selection_range) |
| 364 | +} |
| 365 | + |
| 366 | +fn range_contains_offset(range: TextRange, offset: TextSize) -> bool { |
| 367 | + range.start() <= offset && offset < range.end() |
| 368 | +} |
| 369 | + |
| 370 | +fn range_contains_range(container: TextRange, range: TextRange) -> bool { |
| 371 | + container.start() <= range.start() && range.end() <= container.end() |
| 372 | +} |
| 373 | + |
91 | 374 | pub(crate) fn handle_hover( |
92 | 375 | snap: GlobalStateSnapshot, |
93 | 376 | params: lsp_types::HoverParams, |
|
0 commit comments