diff options
author | Jean Pierre <jeanp413@hotmail.com> | 2021-04-19 00:11:26 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-19 15:11:26 +1000 |
commit | 65a2a04d3bf116c1dee8d2528ec18e71ca4d7bf2 (patch) | |
tree | 6d91d77583d82c0145f8f90ccf4e83bc2ae38228 /cli/lsp/language_server.rs | |
parent | 0552eaf569ef910b0d132b6e60758f17a4519d91 (diff) |
feat(lsp): implement textDocument/prepareCallHierarchy (#10061)
Diffstat (limited to 'cli/lsp/language_server.rs')
-rw-r--r-- | cli/lsp/language_server.rs | 355 |
1 files changed, 355 insertions, 0 deletions
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 70682c41b..d5de93593 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -1643,6 +1643,192 @@ impl Inner { Ok(response) } + async fn incoming_calls( + &mut self, + params: CallHierarchyIncomingCallsParams, + ) -> LspResult<Option<Vec<CallHierarchyIncomingCall>>> { + if !self.enabled() { + return Ok(None); + } + let mark = self.performance.mark("incoming_calls"); + let specifier = self.url_map.normalize_url(¶ms.item.uri); + + let line_index = + if let Some(line_index) = self.get_line_index_sync(&specifier) { + line_index + } else { + return Err(LspError::invalid_params(format!( + "An unexpected specifier ({}) was provided.", + specifier + ))); + }; + + let req = tsc::RequestMethod::ProvideCallHierarchyIncomingCalls(( + specifier.clone(), + line_index.offset_tsc(params.item.selection_range.start)?, + )); + let incoming_calls: Vec<tsc::CallHierarchyIncomingCall> = self + .ts_server + .request(self.snapshot(), req) + .await + .map_err(|err| { + error!("Failed to request to tsserver {}", err); + LspError::invalid_request() + })?; + + let maybe_root_path_owned = self + .config + .root_uri + .as_ref() + .and_then(|uri| uri.to_file_path().ok()); + let mut resolved_items = Vec::<CallHierarchyIncomingCall>::new(); + for item in incoming_calls.iter() { + if let Some(resolved) = item + .try_resolve_call_hierarchy_incoming_call( + self, + maybe_root_path_owned.as_deref(), + ) + .await + { + resolved_items.push(resolved); + } + } + self.performance.measure(mark); + Ok(Some(resolved_items)) + } + + async fn outgoing_calls( + &mut self, + params: CallHierarchyOutgoingCallsParams, + ) -> LspResult<Option<Vec<CallHierarchyOutgoingCall>>> { + if !self.enabled() { + return Ok(None); + } + let mark = self.performance.mark("outgoing_calls"); + let specifier = self.url_map.normalize_url(¶ms.item.uri); + + let line_index = + if let Some(line_index) = self.get_line_index_sync(&specifier) { + line_index + } else { + return Err(LspError::invalid_params(format!( + "An unexpected specifier ({}) was provided.", + specifier + ))); + }; + + let req = tsc::RequestMethod::ProvideCallHierarchyOutgoingCalls(( + specifier.clone(), + line_index.offset_tsc(params.item.selection_range.start)?, + )); + let outgoing_calls: Vec<tsc::CallHierarchyOutgoingCall> = self + .ts_server + .request(self.snapshot(), req) + .await + .map_err(|err| { + error!("Failed to request to tsserver {}", err); + LspError::invalid_request() + })?; + + let maybe_root_path_owned = self + .config + .root_uri + .as_ref() + .and_then(|uri| uri.to_file_path().ok()); + let mut resolved_items = Vec::<CallHierarchyOutgoingCall>::new(); + for item in outgoing_calls.iter() { + if let Some(resolved) = item + .try_resolve_call_hierarchy_outgoing_call( + &line_index, + self, + maybe_root_path_owned.as_deref(), + ) + .await + { + resolved_items.push(resolved); + } + } + self.performance.measure(mark); + Ok(Some(resolved_items)) + } + + async fn prepare_call_hierarchy( + &mut self, + params: CallHierarchyPrepareParams, + ) -> LspResult<Option<Vec<CallHierarchyItem>>> { + if !self.enabled() { + return Ok(None); + } + let mark = self.performance.mark("prepare_call_hierarchy"); + let specifier = self + .url_map + .normalize_url(¶ms.text_document_position_params.text_document.uri); + + let line_index = + if let Some(line_index) = self.get_line_index_sync(&specifier) { + line_index + } else { + return Err(LspError::invalid_params(format!( + "An unexpected specifier ({}) was provided.", + specifier + ))); + }; + + let req = tsc::RequestMethod::PrepareCallHierarchy(( + specifier.clone(), + line_index.offset_tsc(params.text_document_position_params.position)?, + )); + let maybe_one_or_many: Option<tsc::OneOrMany<tsc::CallHierarchyItem>> = + self + .ts_server + .request(self.snapshot(), req) + .await + .map_err(|err| { + error!("Failed to request to tsserver {}", err); + LspError::invalid_request() + })?; + + let response = if let Some(one_or_many) = maybe_one_or_many { + let maybe_root_path_owned = self + .config + .root_uri + .as_ref() + .and_then(|uri| uri.to_file_path().ok()); + let mut resolved_items = Vec::<CallHierarchyItem>::new(); + match one_or_many { + tsc::OneOrMany::One(item) => { + if let Some(resolved) = item + .try_resolve_call_hierarchy_item( + self, + maybe_root_path_owned.as_deref(), + ) + .await + { + resolved_items.push(resolved) + } + } + tsc::OneOrMany::Many(items) => { + for item in items.iter() { + if let Some(resolved) = item + .try_resolve_call_hierarchy_item( + self, + maybe_root_path_owned.as_deref(), + ) + .await + { + resolved_items.push(resolved); + } + } + } + } + Some(resolved_items) + } else { + None + }; + self.performance.measure(mark); + Ok(response) + } + async fn rename( &mut self, params: RenameParams, @@ -1971,6 +2157,27 @@ impl lspower::LanguageServer for LanguageServer { self.0.lock().await.folding_range(params).await } + async fn incoming_calls( + &self, + params: CallHierarchyIncomingCallsParams, + ) -> LspResult<Option<Vec<CallHierarchyIncomingCall>>> { + self.0.lock().await.incoming_calls(params).await + } + + async fn outgoing_calls( + &self, + params: CallHierarchyOutgoingCallsParams, + ) -> LspResult<Option<Vec<CallHierarchyOutgoingCall>>> { + self.0.lock().await.outgoing_calls(params).await + } + + async fn prepare_call_hierarchy( + &self, + params: CallHierarchyPrepareParams, + ) -> LspResult<Option<Vec<CallHierarchyItem>>> { + self.0.lock().await.prepare_call_hierarchy(params).await + } + async fn rename( &self, params: RenameParams, @@ -2472,6 +2679,154 @@ mod tests { } #[tokio::test] + async fn test_call_hierarchy() { + let mut harness = LspTestHarness::new(vec![ + ("initialize_request.json", LspResponse::RequestAny), + ("initialized_notification.json", LspResponse::None), + ( + "prepare_call_hierarchy_did_open_notification.json", + LspResponse::None, + ), + ( + "prepare_call_hierarchy_request.json", + LspResponse::Request( + 2, + json!([ + { + "name": "baz", + "kind": 6, + "detail": "Bar", + "uri": "file:///a/file.ts", + "range": { + "start": { + "line": 5, + "character": 2 + }, + "end": { + "line": 7, + "character": 3 + } + }, + "selectionRange": { + "start": { + "line": 5, + "character": 2 + }, + "end": { + "line": 5, + "character": 5 + } + } + } + ]), + ), + ), + ( + "incoming_calls_request.json", + LspResponse::Request( + 4, + json!([ + { + "from": { + "name": "main", + "kind": 12, + "detail": "", + "uri": "file:///a/file.ts", + "range": { + "start": { + "line": 10, + "character": 0 + }, + "end": { + "line": 13, + "character": 1 + } + }, + "selectionRange": { + "start": { + "line": 10, + "character": 9 + }, + "end": { + "line": 10, + "character": 13 + } + } + }, + "fromRanges": [ + { + "start": { + "line": 12, + "character": 6 + }, + "end": { + "line": 12, + "character": 9 + } + } + ] + } + ]), + ), + ), + ( + "outgoing_calls_request.json", + LspResponse::Request( + 5, + json!([ + { + "to": { + "name": "foo", + "kind": 12, + "detail": "", + "uri": "file:///a/file.ts", + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 2, + "character": 1 + } + }, + "selectionRange": { + "start": { + "line": 0, + "character": 9 + }, + "end": { + "line": 0, + "character": 12 + } + } + }, + "fromRanges": [ + { + "start": { + "line": 6, + "character": 11 + }, + "end": { + "line": 6, + "character": 14 + } + } + ] + } + ]), + ), + ), + ( + "shutdown_request.json", + LspResponse::Request(3, json!(null)), + ), + ("exit_notification.json", LspResponse::None), + ]); + harness.run().await; + } + + #[tokio::test] async fn test_format_mbc() { let mut harness = LspTestHarness::new(vec![ ("initialize_request.json", LspResponse::RequestAny), |