feat(lsp): implement textDocument/foldingRange (#9900)

Co-authored-by: Kitson Kelly <me@kitsonkelly.com>
This commit is contained in:
Jean Pierre 2021-04-02 01:21:07 -05:00 committed by GitHub
parent f50385b2a5
commit 035f7b0ca0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 274 additions and 2 deletions

View file

@ -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(&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::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,
@ -2425,6 +2508,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![