diff options
author | Jean Pierre <jeanp413@hotmail.com> | 2021-04-02 01:21:07 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-02 17:21:07 +1100 |
commit | 035f7b0ca037aaff37428d339b5f4abc796320fc (patch) | |
tree | 835ba72b435a64838cbc36e1343904566de67789 /cli/lsp/language_server.rs | |
parent | f50385b2a5c9bed99709522edc9ab759ce5f07ba (diff) |
feat(lsp): implement textDocument/foldingRange (#9900)
Co-authored-by: Kitson Kelly <me@kitsonkelly.com>
Diffstat (limited to 'cli/lsp/language_server.rs')
-rw-r--r-- | cli/lsp/language_server.rs | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index b55c38189..7b738cc00 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -228,6 +228,25 @@ impl Inner { maybe_line_index } + // TODO(@kitsonk) we really should find a better way to just return the + // content as a `&str`, or be able to get the byte at a particular offset + // which is all that this API that is consuming it is trying to do at the + // moment + /// Searches already cached assets and documents and returns its text + /// content. If not found, `None` is returned. + fn get_text_content(&self, specifier: &ModuleSpecifier) -> Option<String> { + if specifier.scheme() == "asset" { + self + .assets + .get(specifier) + .map(|o| o.clone().map(|a| a.text))? + } else if self.documents.contains_key(specifier) { + self.documents.content(specifier).unwrap() + } else { + self.sources.get_source(specifier) + } + } + async fn get_navigation_tree( &mut self, specifier: &ModuleSpecifier, @@ -1515,6 +1534,63 @@ impl Inner { Ok(result) } + async fn folding_range( + &self, + params: FoldingRangeParams, + ) -> LspResult<Option<Vec<FoldingRange>>> { + if !self.enabled() { + return Ok(None); + } + let mark = self.performance.mark("folding_range"); + let specifier = self.url_map.normalize_url(¶ms.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::GetOutliningSpans(specifier.clone()); + let outlining_spans: Vec<tsc::OutliningSpan> = self + .ts_server + .request(self.snapshot(), req) + .await + .map_err(|err| { + error!("Failed to request to tsserver {}", err); + LspError::invalid_request() + })?; + + let response = if !outlining_spans.is_empty() { + let text_content = + self.get_text_content(&specifier).ok_or_else(|| { + LspError::invalid_params(format!( + "An unexpected specifier ({}) was provided.", + specifier + )) + })?; + Some( + outlining_spans + .iter() + .map(|span| { + span.to_folding_range( + &line_index, + text_content.as_str().as_bytes(), + self.config.client_capabilities.line_folding_only, + ) + }) + .collect::<Vec<FoldingRange>>(), + ) + } else { + None + }; + self.performance.measure(mark); + Ok(response) + } + async fn rename( &mut self, params: RenameParams, @@ -1840,6 +1916,13 @@ impl lspower::LanguageServer for LanguageServer { self.0.lock().await.goto_implementation(params).await } + async fn folding_range( + &self, + params: FoldingRangeParams, + ) -> LspResult<Option<Vec<FoldingRange>>> { + self.0.lock().await.folding_range(params).await + } + async fn rename( &self, params: RenameParams, @@ -2426,6 +2509,54 @@ mod tests { } #[tokio::test] + async fn test_folding_range() { + let mut harness = LspTestHarness::new(vec![ + ("initialize_request.json", LspResponse::RequestAny), + ("initialized_notification.json", LspResponse::None), + ( + "folding_range_did_open_notification.json", + LspResponse::None, + ), + ( + "folding_range_request.json", + LspResponse::Request( + 2, + json!([ + { + "startLine": 0, + "endLine": 12, + "kind": "region" + }, + { + "startLine": 1, + "endLine": 3, + "kind": "comment" + }, + { + "startLine": 4, + "endLine": 10 + }, + { + "startLine": 5, + "endLine": 9 + }, + { + "startLine": 6, + "endLine": 7 + } + ]), + ), + ), + ( + "shutdown_request.json", + LspResponse::Request(3, json!(null)), + ), + ("exit_notification.json", LspResponse::None), + ]); + harness.run().await; + } + + #[tokio::test] async fn test_rename() { let mut harness = LspTestHarness::new(vec![ ("initialize_request.json", LspResponse::RequestAny), |