diff --git a/Cargo.lock b/Cargo.lock index add6bd254e0..5871f595e06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -794,6 +794,11 @@ dependencies = [ "vcpkg 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ordslice" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "owning_ref" version = "0.3.3" @@ -1020,8 +1025,10 @@ dependencies = [ "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ordslice 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "racer 2.0.14 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "rls-analysis 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "rls-blacklist 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "rls-data 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1740,6 +1747,7 @@ dependencies = [ "checksum openssl 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)" = "736898acffb0e00a14d86c5b836aee2ca1c502efcf1c1b0d17a936dfc49ec47f" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" "checksum openssl-sys 0.9.31 (registry+https://github.com/rust-lang/crates.io-index)" = "a4d6a27d108b29befe1822d40e2e22f85518dac59acbf7f30fdc532f48fd0a77" +"checksum ordslice 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd20eec3dbe4376829cb7d80ae6ac45e0a766831dca50202ff2d40db46a8a024" "checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" "checksum parking_lot 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4d05f1349491390b1730afba60bb20d55761bef489a954546b58b4b34e1e2ac" "checksum parking_lot_core 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "4db1a8ccf734a7bce794cc19b3df06ed87ab2f3907036b693c68f56b4d4537fa" diff --git a/Cargo.toml b/Cargo.toml index d59dc3ee763..e34403b23c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/actions/mod.rs b/src/actions/mod.rs index fb5200691d9..45a746d53d9 100644 --- a/src/actions/mod.rs +++ b/src/actions/mod.rs @@ -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 { diff --git a/src/actions/requests.rs b/src/actions/requests.rs index 9a601e9ccf2..caaf6285dfc 100644 --- a/src/actions/requests.rs +++ b/src/actions/requests.rs @@ -858,6 +858,38 @@ fn location_from_racer_match(a_match: &racer::Match) -> Option { }) } +impl RequestAction for CodeLensRequest { + type Response = Vec; + + fn fallback_response() -> Result { + Err(ResponseError::Empty) + } + + fn handle( + ctx: InitActionContext, + params: Self::Params, + ) -> Result { + let file_path = parse_file_path!(¶ms.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::*; diff --git a/src/actions/run.rs b/src/actions/run.rs new file mode 100644 index 00000000000..c90a7c3ed6e --- /dev/null +++ b/src/actions/run.rs @@ -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 { + 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\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, + pub cmd: Cmd, +} + +#[derive(Serialize)] +pub struct Cmd { + pub binary: String, + pub args: Vec, + pub env: HashMap, +} + +pub struct LineIndex { + newlines: Vec, +} + +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 { + 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), + ) + } +} diff --git a/src/main.rs b/src/main.rs index e6b68a2f57a..9ad70ed041d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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; diff --git a/src/server/dispatch.rs b/src/server/dispatch.rs index 8c297175134..5f80c57c8cf 100644 --- a/src/server/dispatch.rs +++ b/src/server/dispatch.rs @@ -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 { $( @@ -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 diff --git a/src/server/mod.rs b/src/server/mod.rs index 2b8244183fb..87fc83a5b25 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -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}; @@ -279,7 +279,8 @@ impl LsService { requests::WorkspaceSymbol, requests::Definition, requests::References, - requests::Completion; + requests::Completion, + requests::CodeLensRequest; ); Ok(()) } @@ -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, } diff --git a/src/test/harness.rs b/src/test/harness.rs index 83772349a78..90450069cf3 100644 --- a/src/test/harness.rs +++ b/src/test/harness.rs @@ -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(); diff --git a/src/test/lens.rs b/src/test/lens.rs new file mode 100644 index 00000000000..793c5cdff4c --- /dev/null +++ b/src/test/lens.rs @@ -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::( + 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(), + ); + } +} \ No newline at end of file diff --git a/src/test/mod.rs b/src/test/mod.rs index a47ca5af753..245dc65b746 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -15,6 +15,7 @@ extern crate json; #[macro_use] mod harness; +mod lens; use analysis; use actions::{requests, notifications}; diff --git a/test_data/lens_run/Cargo.lock b/test_data/lens_run/Cargo.lock new file mode 100644 index 00000000000..79c5fbccd1a --- /dev/null +++ b/test_data/lens_run/Cargo.lock @@ -0,0 +1,4 @@ +[[package]] +name = "run" +version = "0.1.0" + diff --git a/test_data/lens_run/Cargo.toml b/test_data/lens_run/Cargo.toml new file mode 100644 index 00000000000..4a8b2d2f885 --- /dev/null +++ b/test_data/lens_run/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "run" +version = "0.1.0" +authors = ["Nick Cameron "] + +[dependencies] diff --git a/test_data/lens_run/src/main.rs b/test_data/lens_run/src/main.rs new file mode 100644 index 00000000000..ae6f2cfb85e --- /dev/null +++ b/test_data/lens_run/src/main.rs @@ -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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +pub fn main() { +} + +#[test] +fn test_foo() { + +}