Skip to content

Commit

Permalink
Replace runnables custom fn with LSP codeLens (FuelLabs#3819)
Browse files Browse the repository at this point in the history
Related FuelLabs#3693

This PR moves the logic from the custom `runnables` method into the LSP
`codeLens` method. From here, we'll add other types of runnables, e.g.
sway tests.

Added a test for `codeLens` as well as a missing test for `codeActions`.

Unfortunately there is currently no support for tooltips on
server-supplied code lenses
(microsoft/language-server-protocol#1644)
because they aren't part of the LSP spec. So, the tooltip will no longer
show up for the time being.

The upside is that the run button could be implemented for other IDEs
with less effort (not entirely free, still need to define the run
command in the client-side plugin code).

There will be two follow-up PRs to this:
- Removing the runnables code in the vscode client & updating the run
command
- Removing the `runnables` custom method in the server.
  • Loading branch information
sdankel authored Jan 20, 2023
1 parent 89c45ee commit d73a033
Showing 1 changed file with 143 additions and 38 deletions.
181 changes: 143 additions & 38 deletions sway-lsp/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ fn capabilities() -> ServerCapabilities {
definition_provider: Some(OneOf::Left(true)),
inlay_hint_provider: Some(OneOf::Left(true)),
code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
code_lens_provider: Some(CodeLensOptions {
resolve_provider: Some(false),
}),
hover_provider: Some(HoverProviderCapability::Simple(true)),
..ServerCapabilities::default()
}
Expand Down Expand Up @@ -326,6 +329,36 @@ impl LanguageServer for Backend {
}
}

async fn code_lens(&self, params: CodeLensParams) -> jsonrpc::Result<Option<Vec<CodeLens>>> {
let mut result = vec![];
match self.get_uri_and_session(&params.text_document.uri) {
Ok((_, session)) => {
// Construct code lenses for runnable functions
let _ = session
.runnables
.try_get(&capabilities::runnable::RunnableType::MainFn)
.try_unwrap()
.map(|item| {
let runnable = item.value();
result.push(CodeLens {
range: runnable.range,
command: Some(Command {
command: "sway.runScript".to_string(),
arguments: None,
title: "▶\u{fe0e} Run".to_string(),
}),
data: None,
});
});
Ok(Some(result))
}
Err(err) => {
tracing::error!("{}", err.to_string());
Ok(None)
}
}
}

async fn completion(
&self,
params: CompletionParams,
Expand Down Expand Up @@ -488,28 +521,12 @@ impl Backend {
}
}

// TODO: Delete this method once the client code calling it has been removed.
pub async fn runnables(
&self,
params: RunnableParams,
_params: RunnableParams,
) -> jsonrpc::Result<Option<Vec<(Range, String)>>> {
match self.get_uri_and_session(&params.text_document.uri) {
Ok((_, session)) => {
let ranges = session
.runnables
.try_get(&capabilities::runnable::RunnableType::MainFn)
.try_unwrap()
.map(|item| {
let runnable = item.value();
vec![(runnable.range, format!("{}", runnable.tree_type))]
});

Ok(ranges)
}
Err(err) => {
tracing::error!("{}", err.to_string());
Ok(None)
}
}
Ok(None)
}

/// This method is triggered by a command palette request in VScode
Expand Down Expand Up @@ -621,6 +638,7 @@ mod tests {
use crate::utils::test::{
assert_server_requests, doc_comments_dir, e2e_test_dir, get_fixture, test_fixtures_dir,
};
use assert_json_diff::assert_json_eq;
use serde_json::json;
use std::{borrow::Cow, fs, io::Read, path::PathBuf};
use tower::{Service, ServiceExt};
Expand Down Expand Up @@ -659,8 +677,8 @@ mod tests {
let params = json!({ "capabilities": capabilities() });
let initialize = build_request_with_id("initialize", params, 1);
let response = call_request(service, initialize.clone()).await;
let ok = Response::from_ok(1.into(), json!({ "capabilities": capabilities() }));
assert_eq!(response, Ok(Some(ok)));
let expected = Response::from_ok(1.into(), json!({ "capabilities": capabilities() }));
assert_json_eq!(expected, response.ok().unwrap());
initialize
}

Expand All @@ -673,8 +691,8 @@ mod tests {
async fn shutdown_request(service: &mut LspService<Backend>) -> Request {
let shutdown = Request::build("shutdown").id(1).finish();
let response = call_request(service, shutdown.clone()).await;
let ok = Response::from_ok(1.into(), json!(null));
assert_eq!(response, Ok(Some(ok)));
let expected = Response::from_ok(1.into(), json!(null));
assert_json_eq!(expected, response.ok().unwrap());
shutdown
}

Expand Down Expand Up @@ -747,8 +765,8 @@ mod tests {
});
let show_ast = build_request_with_id("sway/show_ast", params, 1);
let response = call_request(service, show_ast.clone()).await;
let ok = Response::from_ok(1.into(), json!({"uri": "file:///tmp/typed_ast.rs"}));
assert_eq!(response, Ok(Some(ok)));
let expected = Response::from_ok(1.into(), json!({"uri": "file:///tmp/typed_ast.rs"}));
assert_json_eq!(expected, response.ok().unwrap());
show_ast
}

Expand Down Expand Up @@ -792,7 +810,7 @@ mod tests {
});
let definition = build_request_with_id("textDocument/definition", params, id);
let response = call_request(service, definition.clone()).await;
let ok = Response::from_ok(
let expected = Response::from_ok(
id.into(),
json!({
"range": {
Expand All @@ -808,7 +826,7 @@ mod tests {
"uri": uri,
}),
);
assert_eq!(response, Ok(Some(ok)));
assert_json_eq!(expected, response.ok().unwrap());
definition
}

Expand All @@ -824,7 +842,7 @@ mod tests {
});
let hover = build_request_with_id("textDocument/hover", params, 1);
let response = call_request(service, hover.clone()).await;
let ok = Response::from_ok(
let expected = Response::from_ok(
1.into(),
json!({
"contents": {
Expand All @@ -843,7 +861,7 @@ mod tests {
}
}),
);
assert_eq!(response, Ok(Some(ok)));
assert_json_eq!(expected, response.ok().unwrap());
hover
}

Expand Down Expand Up @@ -874,7 +892,7 @@ mod tests {
});
let highlight = build_request_with_id("textDocument/documentHighlight", params, 1);
let response = call_request(service, highlight.clone()).await;
let ok = Response::from_ok(
let expected = Response::from_ok(
1.into(),
json!([{
"range": {
Expand All @@ -890,10 +908,95 @@ mod tests {
}
]),
);
assert_eq!(response, Ok(Some(ok)));
assert_json_eq!(expected, response.ok().unwrap());
highlight
}

async fn code_action_request(service: &mut LspService<Backend>, uri: &Url) -> Request {
let params = json!({
"textDocument": {
"uri": uri,
},
"range" : {
"start":{
"line": 27,
"character": 4
},
"end":{
"line": 27,
"character": 9
},
},
"context": {
"diagnostics": [],
"triggerKind": 2
}
});
let code_action = build_request_with_id("textDocument/codeAction", params, 1);
let response = call_request(service, code_action.clone()).await;
let uri_string = uri.to_string();
let expected = Response::from_ok(
1.into(),
json!([{
"data": uri,
"edit": {
"changes": {
uri_string: [
{
"newText": "\nimpl FooABI for Contract {\n /// This is the `main` method on the `FooABI` abi\n fn main() -> u64 {}\n}\n",
"range": {
"end": {
"character": 0,
"line": 31
},
"start": {
"character": 0,
"line": 31
}
}
}
]
}
},
"kind": "refactor",
"title": "Generate impl for contract"
}]),
);
assert_json_eq!(expected, response.ok().unwrap());
code_action
}

async fn code_lens_request(service: &mut LspService<Backend>, uri: &Url) -> Request {
let params = json!({
"textDocument": {
"uri": uri,
},
});
let code_lens = build_request_with_id("textDocument/codeLens", params, 1);
let response = call_request(service, code_lens.clone()).await;
let expected = Response::from_ok(
1.into(),
json!([{
"command": {
"command": "sway.runScript",
"title":"▶︎ Run"
},
"range": {
"end": {
"character": 7,
"line": 4
},
"start": {
"character": 3,
"line":4
}
}
}]),
);
assert_json_eq!(expected, response.ok().unwrap());
code_lens
}

async fn init_and_open(service: &mut LspService<Backend>, manifest_dir: PathBuf) -> Url {
let _ = initialize_request(service).await;
initialized_notification(service).await;
Expand Down Expand Up @@ -1037,17 +1140,19 @@ mod tests {
}

macro_rules! lsp_capability_test {
($test:ident, $capability:expr) => {
($test:ident, $capability:expr, $dir:expr) => {
#[tokio::test]
async fn $test() {
test_lsp_capability!(doc_comments_dir(), $capability);
test_lsp_capability!($dir(), $capability);
}
};
}

lsp_capability_test!(semantic_tokens, semantic_tokens_request);
lsp_capability_test!(document_symbol, document_symbol_request);
lsp_capability_test!(format, format_request);
lsp_capability_test!(hover, hover_request);
lsp_capability_test!(highlight, highlight_request);
lsp_capability_test!(semantic_tokens, semantic_tokens_request, doc_comments_dir);
lsp_capability_test!(document_symbol, document_symbol_request, doc_comments_dir);
lsp_capability_test!(format, format_request, doc_comments_dir);
lsp_capability_test!(hover, hover_request, doc_comments_dir);
lsp_capability_test!(highlight, highlight_request, doc_comments_dir);
lsp_capability_test!(code_action, code_action_request, doc_comments_dir);
lsp_capability_test!(code_lens, code_lens_request, e2e_test_dir);
}

0 comments on commit d73a033

Please sign in to comment.