Skip to content
This repository has been archived by the owner on Dec 29, 2022. It is now read-only.

Commit

Permalink
Initial CodeLens support
Browse files Browse the repository at this point in the history
The only code lens we provide atm is "run a single test"
  • Loading branch information
matklad committed Jun 26, 2018
1 parent 4668e79 commit 7ef4bf9
Show file tree
Hide file tree
Showing 14 changed files with 271 additions and 10 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ serde_json = "1.0"
serde_derive = "1.0"
url = "1.1.0"
walkdir = "2.1"
regex = "1"
ordslice = "0.3"

[dev-dependencies]
json = "0.11"
Expand Down
1 change: 1 addition & 0 deletions src/actions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub mod requests;
pub mod notifications;
pub mod progress;
pub mod diagnostics;
pub mod run;

/// Persistent context shared across all requests and notifications.
pub enum ActionContext {
Expand Down
32 changes: 32 additions & 0 deletions src/actions/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,38 @@ fn location_from_racer_match(a_match: &racer::Match) -> Option<Location> {
})
}

impl RequestAction for CodeLensRequest {
type Response = Vec<CodeLens>;

fn fallback_response() -> Result<Self::Response, ResponseError> {
Err(ResponseError::Empty)
}

fn handle(
ctx: InitActionContext,
params: Self::Params,
) -> Result<Self::Response, ResponseError> {
let file_path = parse_file_path!(&params.text_document.uri, "code_lens")?;
let mut ret = Vec::new();

for action in collect_run_actions(&ctx, &file_path) {
let command = Command {
title: action.label,
command: "rls.run".to_string(),
arguments: Some(vec![serde_json::to_value(&action.cmd).unwrap()]),
};
let range = ls_util::rls_to_range(action.target_element);
let lens = CodeLens {
range,
command: Some(command),
data: None,
};
ret.push(lens);
}
Ok(ret)
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
92 changes: 92 additions & 0 deletions src/actions/run.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use actions::InitActionContext;
use ordslice::Ext;
use regex::Regex;
use span::{Column, Position, Range, Row, ZeroIndexed};
use vfs::FileContents;

use std::{collections::HashMap, iter, path::Path};

pub fn collect_run_actions(ctx: &InitActionContext, file: &Path) -> Vec<RunAction> {
let text = match ctx.vfs.load_file(file) {
Ok(FileContents::Text(text)) => text,
Ok(FileContents::Binary(_)) => return Vec::new(),
Err(e) => {
error!("failed to collect run actions: {}", e);
return Vec::new();
}
};
if !text.contains("#[test]") {
return Vec::new();
}

lazy_static! {
static ref TEST_FN_RE: Regex = Regex::new(r"#\[test\]\s*\n\s*fn\s*(?P<name>\w+)\(").unwrap();
}

let line_index = LineIndex::new(&text);

let mut ret = Vec::new();
for caps in TEST_FN_RE.captures_iter(&text) {
let group = caps.name("name").unwrap();
let target_element = Range::from_positions(
line_index.offset_to_position(group.start()),
line_index.offset_to_position(group.end()),
);
let test_name = group.as_str();
let run_action = RunAction {
label: "Run test".to_string(),
target_element,
cmd: Cmd {
binary: "cargo".to_string(),
args: vec![
"test".to_string(),
"--".to_string(),
"--nocapture".to_string(),
test_name.to_string(),
],
env: iter::once(("RUST_BACKTRACE".to_string(), "short".to_string())).collect(),
},
};
ret.push(run_action);
}
ret
}

pub struct RunAction {
pub label: String,
pub target_element: Range<ZeroIndexed>,
pub cmd: Cmd,
}

#[derive(Serialize)]
pub struct Cmd {
pub binary: String,
pub args: Vec<String>,
pub env: HashMap<String, String>,
}

pub struct LineIndex {
newlines: Vec<usize>,
}

impl LineIndex {
pub fn new(text: &str) -> LineIndex {
let newlines = text
.bytes()
.enumerate()
.filter(|&(_i, b)| b == b'\n')
.map(|(i, _b)| i + 1);
let newlines = iter::once(0).chain(newlines).collect();
LineIndex { newlines }
}

pub fn offset_to_position(&self, offset: usize) -> Position<ZeroIndexed> {
let line = self.newlines.upper_bound(&offset) - 1;
let line_start_offset = self.newlines[line];
let col = offset - line_start_offset;
Position::new(
Row::new_zero_indexed(line as u32),
Column::new_zero_indexed(col as u32),
)
}
}
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ extern crate serde_json;

extern crate url;
extern crate walkdir;
extern crate regex;
extern crate ordslice;

use std::env;
use std::sync::Arc;
Expand Down
5 changes: 3 additions & 2 deletions src/server/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_millis(3_600_000);

/// Macro enum `DispatchRequest` packing in various similar `Request` types
macro_rules! define_dispatch_request_enum {
($($request_type:ident),*) => {
($($request_type:ident),*$(,)*) => {
#[allow(large_enum_variant)] // seems ok for a short lived macro-enum
pub enum DispatchRequest {
$(
Expand Down Expand Up @@ -102,7 +102,8 @@ define_dispatch_request_enum!(
ResolveCompletion,
Formatting,
RangeFormatting,
ExecuteCommand
ExecuteCommand,
CodeLensRequest,
);

/// Provides ability to dispatch requests to a worker thread that will
Expand Down
9 changes: 6 additions & 3 deletions src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub use ls_types::request::Initialize as InitializeRequest;
pub use ls_types::request::Shutdown as ShutdownRequest;
use ls_types::{
CompletionOptions, ExecuteCommandOptions, InitializeParams, InitializeResult,
ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind,
ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, CodeLensOptions
};
use lsp_data;
use lsp_data::{InitializationOptions, LSPNotification, LSPRequest};
Expand Down Expand Up @@ -279,7 +279,8 @@ impl<O: Output> LsService<O> {
requests::WorkspaceSymbol,
requests::Definition,
requests::References,
requests::Completion;
requests::Completion,
requests::CodeLensRequest;
);
Ok(())
}
Expand Down Expand Up @@ -379,7 +380,9 @@ fn server_caps(ctx: &ActionContext) -> ServerCapabilities {
// info from the client.
document_range_formatting_provider: Some(false),

code_lens_provider: None,
code_lens_provider: Some(CodeLensOptions {
resolve_provider: Some(false),
}),
document_on_type_formatting_provider: None,
signature_help_provider: None,
}
Expand Down
10 changes: 5 additions & 5 deletions src/test/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,11 +219,11 @@ pub fn expect_messages(results: LsResultList, expected: &[&ExpectedMessage]) {

let mut results = results.lock().unwrap();

println!(
"expect_messages:\n results: {:#?},\n expected: {:#?}",
*results,
expected
);
// println!(
// "expect_messages:\n results: {:#?},\n expected: {:#?}",
// *results,
// expected
// );
assert_eq!(results.len(), expected.len());
for (found, expected) in results.iter().zip(expected.iter()) {
let values: serde_json::Value = serde_json::from_str(found).unwrap();
Expand Down
92 changes: 92 additions & 0 deletions src/test/lens.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use std::{
path::Path,
};

use url::Url;
use serde_json;
use ls_types::{
TextDocumentIdentifier, CodeLensParams
};

use ::{
server as ls_server,
actions::requests,
};
use super::{
Environment, expect_messages, request, ExpectedMessage, initialize
};

#[test]
fn test_lens_run() {
let mut env = Environment::new("lens_run");

let source_file_path = Path::new("src").join("main.rs");

let root_path = env.cache.abs_path(Path::new("."));
let url = Url::from_file_path(env.cache.abs_path(&source_file_path))
.expect("couldn't convert file path to URL");
let text_doc = TextDocumentIdentifier::new(url.clone());
let messages = vec![
initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(),
request::<requests::CodeLensRequest>(
100,
CodeLensParams {
text_document: text_doc.clone(),
},
).to_string(),
];

let (mut server, results) = env.mock_server(messages);
// Initialize and build.
assert_eq!(
ls_server::LsService::handle_message(&mut server),
ls_server::ServerStateChange::Continue
);
expect_messages(
results.clone(),
&[
ExpectedMessage::new(Some(0)).expect_contains("rls.deglobImports-"),
ExpectedMessage::new(None).expect_contains("progress"),
ExpectedMessage::new(None).expect_contains("progress"),
ExpectedMessage::new(None).expect_contains("progress"),
ExpectedMessage::new(None).expect_contains("progress"),
ExpectedMessage::new(None).expect_contains("progress"),
],
);

assert_eq!(
ls_server::LsService::handle_message(&mut server),
ls_server::ServerStateChange::Continue
);
wait_for_n_results!(1, results);
let result: serde_json::Value = serde_json::from_str(&results.lock().unwrap().remove(0)).unwrap();
compare_json(
result.get("result").unwrap(),
r#"[{
"command": {
"command": "rls.run",
"title": "Run test",
"arguments": [{
"args": [ "test", "--", "--nocapture", "test_foo" ],
"binary": "cargo",
"env": { "RUST_BACKTRACE": "short" }
}]
},
"range": {
"start": { "character": 3, "line": 14 },
"end": { "character": 11, "line": 14 }
}
}]"#
)
}

fn compare_json(actual: &serde_json::Value, expected: &str) {
let expected: serde_json::Value = serde_json::from_str(expected).unwrap();
if actual != &expected {
panic!(
"JSON differs\nExpected:\n{}\nActual:\n{}\n",
serde_json::to_string_pretty(&expected).unwrap(),
serde_json::to_string_pretty(actual).unwrap(),
);
}
}
1 change: 1 addition & 0 deletions src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ extern crate json;

#[macro_use]
mod harness;
mod lens;

use analysis;
use actions::{requests, notifications};
Expand Down
4 changes: 4 additions & 0 deletions test_data/lens_run/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions test_data/lens_run/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "run"
version = "0.1.0"
authors = ["Nick Cameron <[email protected]>"]

[dependencies]
17 changes: 17 additions & 0 deletions test_data/lens_run/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

pub fn main() {
}

#[test]
fn test_foo() {

}

0 comments on commit 7ef4bf9

Please sign in to comment.