Skip to content

Commit

Permalink
feat(lsp): implement textDocument/selectionRange (#9845)
Browse files Browse the repository at this point in the history
Ref: #8643
  • Loading branch information
jeanp413 authored Mar 23, 2021
1 parent b233985 commit 77cfadf
Show file tree
Hide file tree
Showing 7 changed files with 240 additions and 1 deletion.
5 changes: 4 additions & 1 deletion cli/lsp/capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use lspower::lsp::HoverProviderCapability;
use lspower::lsp::ImplementationProviderCapability;
use lspower::lsp::OneOf;
use lspower::lsp::SaveOptions;
use lspower::lsp::SelectionRangeProviderCapability;
use lspower::lsp::ServerCapabilities;
use lspower::lsp::SignatureHelpOptions;
use lspower::lsp::TextDocumentSyncCapability;
Expand Down Expand Up @@ -104,7 +105,9 @@ pub fn server_capabilities(
document_formatting_provider: Some(OneOf::Left(true)),
document_range_formatting_provider: None,
document_on_type_formatting_provider: None,
selection_range_provider: None,
selection_range_provider: Some(SelectionRangeProviderCapability::Simple(
true,
)),
folding_range_provider: None,
rename_provider: Some(OneOf::Left(true)),
document_link_provider: None,
Expand Down
157 changes: 157 additions & 0 deletions cli/lsp/language_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1590,6 +1590,48 @@ impl Inner {
}
}

async fn selection_range(
&self,
params: SelectionRangeParams,
) -> LspResult<Option<Vec<SelectionRange>>> {
if !self.enabled() {
return Ok(None);
}
let mark = self.performance.mark("selection_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 mut selection_ranges = Vec::<SelectionRange>::new();
for position in params.positions {
let req = tsc::RequestMethod::GetSmartSelectionRange((
specifier.clone(),
line_index.offset_tsc(position)?,
));

let selection_range: tsc::SelectionRange = self
.ts_server
.request(self.snapshot(), req)
.await
.map_err(|err| {
error!("Failed to request to tsserver {}", err);
LspError::invalid_request()
})?;

selection_ranges.push(selection_range.to_selection_range(&line_index));
}
self.performance.measure(mark);
Ok(Some(selection_ranges))
}

async fn signature_help(
&self,
params: SignatureHelpParams,
Expand Down Expand Up @@ -1794,6 +1836,13 @@ impl lspower::LanguageServer for LanguageServer {
self.0.lock().await.request_else(method, params).await
}

async fn selection_range(
&self,
params: SelectionRangeParams,
) -> LspResult<Option<Vec<SelectionRange>>> {
self.0.lock().await.selection_range(params).await
}

async fn signature_help(
&self,
params: SignatureHelpParams,
Expand Down Expand Up @@ -2411,6 +2460,114 @@ mod tests {
harness.run().await;
}

#[tokio::test]
async fn test_selection_range() {
let mut harness = LspTestHarness::new(vec![
("initialize_request.json", LspResponse::RequestAny),
("initialized_notification.json", LspResponse::None),
(
"selection_range_did_open_notification.json",
LspResponse::None,
),
(
"selection_range_request.json",
LspResponse::Request(
2,
json!([{
"range": {
"start": {
"line": 2,
"character": 8
},
"end": {
"line": 2,
"character": 9
}
},
"parent": {
"range": {
"start": {
"line": 2,
"character": 8
},
"end": {
"line": 2,
"character": 15
}
},
"parent": {
"range": {
"start": {
"line": 2,
"character": 4
},
"end": {
"line": 4,
"character": 5
}
},
"parent": {
"range": {
"start": {
"line": 1,
"character": 13
},
"end": {
"line": 6,
"character": 2
}
},
"parent": {
"range": {
"start": {
"line": 1,
"character": 2
},
"end": {
"line": 6,
"character": 3
}
},
"parent": {
"range": {
"start": {
"line": 0,
"character": 11
},
"end": {
"line": 7,
"character": 0
}
},
"parent": {
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 7,
"character": 1
}
}
}
}
}
}
}
}
}]),
),
),
(
"shutdown_request.json",
LspResponse::Request(3, json!(null)),
),
("exit_notification.json", LspResponse::None),
]);
harness.run().await;
}

#[tokio::test]
async fn test_code_lens_request() {
let mut harness = LspTestHarness::new(vec![
Expand Down
35 changes: 35 additions & 0 deletions cli/lsp/tsc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1311,6 +1311,31 @@ impl SignatureHelpParameter {
}
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SelectionRange {
text_span: TextSpan,
#[serde(skip_serializing_if = "Option::is_none")]
parent: Option<Box<SelectionRange>>,
}

impl SelectionRange {
pub fn to_selection_range(
&self,
line_index: &LineIndex,
) -> lsp::SelectionRange {
lsp::SelectionRange {
range: self.text_span.to_range(line_index),
parent: match &self.parent {
Some(parent_selection) => {
Some(Box::new(parent_selection.to_selection_range(line_index)))
}
None => None,
},
}
}
}

#[derive(Debug, Clone, Deserialize)]
struct Response {
id: usize,
Expand Down Expand Up @@ -1856,6 +1881,8 @@ pub enum RequestMethod {
GetReferences((ModuleSpecifier, u32)),
/// Get signature help items for a specific position.
GetSignatureHelpItems((ModuleSpecifier, u32, SignatureHelpItemsOptions)),
/// Get a selection range for a specific position.
GetSmartSelectionRange((ModuleSpecifier, u32)),
/// Get the diagnostic codes that support some form of code fix.
GetSupportedCodeFixes,
}
Expand Down Expand Up @@ -1977,6 +2004,14 @@ impl RequestMethod {
"options": options,
})
}
RequestMethod::GetSmartSelectionRange((specifier, position)) => {
json!({
"id": id,
"method": "getSmartSelectionRange",
"specifier": specifier,
"position": position
})
}
RequestMethod::GetSupportedCodeFixes => json!({
"id": id,
"method": "getSupportedCodeFixes",
Expand Down
12 changes: 12 additions & 0 deletions cli/tests/lsp/selection_range_did_open_notification.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"jsonrpc": "2.0",
"method": "textDocument/didOpen",
"params": {
"textDocument": {
"uri": "file:///a/file.ts",
"languageId": "typescript",
"version": 1,
"text": "class Foo {\n bar(a, b) {\n if (a === b) {\n return true;\n }\n return false;\n }\n}"
}
}
}
16 changes: 16 additions & 0 deletions cli/tests/lsp/selection_range_request.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"jsonrpc": "2.0",
"id": 2,
"method": "textDocument/selectionRange",
"params": {
"textDocument": {
"uri": "file:///a/file.ts"
},
"positions": [
{
"line": 2,
"character": 8
}
]
}
}
9 changes: 9 additions & 0 deletions cli/tsc/99_main_compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,15 @@ delete Object.prototype.__proto__;
),
);
}
case "getSmartSelectionRange": {
return respond(
id,
languageService.getSmartSelectionRange(
request.specifier,
request.position,
),
);
}
case "getSupportedCodeFixes": {
return respond(
id,
Expand Down
7 changes: 7 additions & 0 deletions cli/tsc/compiler.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ declare global {
| GetQuickInfoRequest
| GetReferencesRequest
| GetSignatureHelpItemsRequest
| GetSmartSelectionRange
| GetSupportedCodeFixes;

interface BaseLanguageServerRequest {
Expand Down Expand Up @@ -169,6 +170,12 @@ declare global {
options: ts.SignatureHelpItemsOptions;
}

interface GetSmartSelectionRange extends BaseLanguageServerRequest {
method: "getSmartSelectionRange";
specifier: string;
position: number;
}

interface GetSupportedCodeFixes extends BaseLanguageServerRequest {
method: "getSupportedCodeFixes";
}
Expand Down

0 comments on commit 77cfadf

Please sign in to comment.