mod support; use std::{collections::HashMap, time::Instant}; use lsp_types::{ CodeActionContext, DidOpenTextDocumentParams, DocumentFormattingParams, FormattingOptions, Position, Range, TextDocumentItem, TextDocumentPositionParams, }; use ra_lsp_server::req::{ CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument, Formatting, OnEnter, Runnables, RunnablesParams, }; use serde_json::json; use tempfile::TempDir; use test_utils::skip_slow_tests; use crate::support::{project, Project}; const PROFILE: &'static str = ""; // const PROFILE: &'static str = "*@3>100"; #[test] fn completes_items_from_standard_library() { if skip_slow_tests() { return; } let project_start = Instant::now(); let server = Project::with_fixture( r#" //- Cargo.toml [package] name = "foo" version = "0.0.0" //- src/lib.rs use std::collections::Spam; "#, ) .with_sysroot(true) .server(); server.wait_until_workspace_is_loaded(); eprintln!("loading took {:?}", project_start.elapsed()); let completion_start = Instant::now(); let res = server.send_request::(CompletionParams { text_document_position: TextDocumentPositionParams::new( server.doc_id("src/lib.rs"), Position::new(0, 23), ), context: None, }); assert!(format!("{}", res).contains("HashMap")); eprintln!("completion took {:?}", completion_start.elapsed()); } #[test] fn test_runnables_no_project() { if skip_slow_tests() { return; } let server = project( r" //- lib.rs #[test] fn foo() { } ", ); server.wait_until_workspace_is_loaded(); server.request::( RunnablesParams { text_document: server.doc_id("lib.rs"), position: None }, json!([ { "args": [ "test", "--", "foo", "--nocapture" ], "bin": "cargo", "env": { "RUST_BACKTRACE": "short" }, "cwd": null, "label": "test foo", "range": { "end": { "character": 1, "line": 2 }, "start": { "character": 0, "line": 0 } } }, { "args": [ "check", "--all" ], "bin": "cargo", "env": {}, "cwd": null, "label": "cargo check --all", "range": { "end": { "character": 0, "line": 0 }, "start": { "character": 0, "line": 0 } } } ]), ); } #[test] fn test_runnables_project() { if skip_slow_tests() { return; } let code = r#" //- foo/Cargo.toml [package] name = "foo" version = "0.0.0" //- foo/src/lib.rs pub fn foo() {} //- foo/tests/spam.rs #[test] fn test_eggs() {} //- bar/Cargo.toml [package] name = "bar" version = "0.0.0" //- bar/src/main.rs fn main() {} "#; let server = Project::with_fixture(code).root("foo").root("bar").server(); server.wait_until_workspace_is_loaded(); server.request::( RunnablesParams { text_document: server.doc_id("foo/tests/spam.rs"), position: None, }, json!([ { "args": [ "test", "--package", "foo", "--test", "spam", "--", "test_eggs", "--nocapture" ], "bin": "cargo", "env": { "RUST_BACKTRACE": "short" }, "label": "test test_eggs", "range": { "end": { "character": 17, "line": 1 }, "start": { "character": 0, "line": 0 } }, "cwd": server.path().join("foo") }, { "args": [ "check", "--package", "foo", "--test", "spam" ], "bin": "cargo", "env": {}, "cwd": server.path().join("foo"), "label": "cargo check -p foo", "range": { "end": { "character": 0, "line": 0 }, "start": { "character": 0, "line": 0 } } } ]) ); } #[test] fn test_format_document() { if skip_slow_tests() { return; } let server = project( r#" //- Cargo.toml [package] name = "foo" version = "0.0.0" //- src/lib.rs mod bar; fn main() { } pub use std::collections::HashMap; "#, ); server.wait_until_workspace_is_loaded(); server.request::( DocumentFormattingParams { text_document: server.doc_id("src/lib.rs"), options: FormattingOptions { tab_size: 4, insert_spaces: false, properties: HashMap::new(), }, }, json!([ { "newText": r#"mod bar; fn main() {} pub use std::collections::HashMap; "#, "range": { "end": { "character": 0, "line": 7 }, "start": { "character": 0, "line": 0 } } } ]), ); } #[test] fn test_format_document_2018() { if skip_slow_tests() { return; } let server = project( r#" //- Cargo.toml [package] name = "foo" version = "0.0.0" edition = "2018" //- src/lib.rs mod bar; async fn test() { } fn main() { } pub use std::collections::HashMap; "#, ); server.wait_until_workspace_is_loaded(); server.request::( DocumentFormattingParams { text_document: server.doc_id("src/lib.rs"), options: FormattingOptions { tab_size: 4, insert_spaces: false, properties: HashMap::new(), }, }, json!([ { "newText": r#"mod bar; async fn test() {} fn main() {} pub use std::collections::HashMap; "#, "range": { "end": { "character": 0, "line": 10 }, "start": { "character": 0, "line": 0 } } } ]), ); } #[test] fn test_missing_module_code_action() { if skip_slow_tests() { return; } let server = project( r#" //- Cargo.toml [package] name = "foo" version = "0.0.0" //- src/lib.rs mod bar; fn main() {} "#, ); server.wait_until_workspace_is_loaded(); let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None }; server.request::( CodeActionParams { text_document: server.doc_id("src/lib.rs"), range: Range::new(Position::new(0, 4), Position::new(0, 7)), context: empty_context(), }, json!([ { "command": { "arguments": [ { "cursorPosition": null, "label": "create module", "workspaceEdit": { "documentChanges": [ { "kind": "create", "uri": "file:///[..]/src/bar.rs" } ] } } ], "command": "rust-analyzer.applySourceChange", "title": "create module" }, "title": "create module" } ]), ); server.request::( CodeActionParams { text_document: server.doc_id("src/lib.rs"), range: Range::new(Position::new(2, 4), Position::new(2, 7)), context: empty_context(), }, json!([]), ); } #[test] fn test_missing_module_code_action_in_json_project() { if skip_slow_tests() { return; } let tmp_dir = TempDir::new().unwrap(); let path = tmp_dir.path(); let project = json!({ "roots": [path], "crates": [ { "root_module": path.join("src/lib.rs"), "deps": [], "edition": "2015", "atom_cfgs": [], "key_value_cfgs": {} } ] }); let code = format!( r#" //- rust-project.json {PROJECT} //- src/lib.rs mod bar; fn main() {{}} "#, PROJECT = project.to_string(), ); let server = Project::with_fixture(&code).tmp_dir(tmp_dir).server(); server.wait_until_workspace_is_loaded(); let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None }; server.request::( CodeActionParams { text_document: server.doc_id("src/lib.rs"), range: Range::new(Position::new(0, 4), Position::new(0, 7)), context: empty_context(), }, json!([ { "command": { "arguments": [ { "cursorPosition": null, "label": "create module", "workspaceEdit": { "documentChanges": [ { "kind": "create", "uri": "file:///[..]/src/bar.rs" } ] } } ], "command": "rust-analyzer.applySourceChange", "title": "create module" }, "title": "create module" } ]), ); server.request::( CodeActionParams { text_document: server.doc_id("src/lib.rs"), range: Range::new(Position::new(2, 4), Position::new(2, 7)), context: empty_context(), }, json!([]), ); } #[test] fn diagnostics_dont_block_typing() { if skip_slow_tests() { return; } let librs: String = (0..10).map(|i| format!("mod m{};", i)).collect(); let libs: String = (0..10).map(|i| format!("//- src/m{}.rs\nfn foo() {{}}\n\n", i)).collect(); let server = Project::with_fixture(&format!( r#" //- Cargo.toml [package] name = "foo" version = "0.0.0" //- src/lib.rs {} {} fn main() {{}} "#, librs, libs )) .with_sysroot(true) .server(); server.wait_until_workspace_is_loaded(); for i in 0..10 { server.notification::(DidOpenTextDocumentParams { text_document: TextDocumentItem { uri: server.doc_id(&format!("src/m{}.rs", i)).uri, language_id: "rust".to_string(), version: 0, text: "/// Docs\nfn foo() {}".to_string(), }, }); } let start = std::time::Instant::now(); server.request::( TextDocumentPositionParams { text_document: server.doc_id("src/m0.rs"), position: Position { line: 0, character: 5 }, }, json!({ "cursorPosition": { "position": { "character": 4, "line": 1 }, "textDocument": { "uri": "file:///[..]src/m0.rs" } }, "label": "on enter", "workspaceEdit": { "documentChanges": [ { "edits": [ { "newText": "\n/// ", "range": { "end": { "character": 5, "line": 0 }, "start": { "character": 5, "line": 0 } } } ], "textDocument": { "uri": "file:///[..]src/m0.rs", "version": null } } ] } }), ); let elapsed = start.elapsed(); assert!(elapsed.as_millis() < 2000, "typing enter took {:?}", elapsed); } #[test] fn preserves_dos_line_endings() { if skip_slow_tests() { return; } let server = Project::with_fixture( &" //- Cargo.toml [package] name = \"foo\" version = \"0.0.0\" //- src/main.rs /// Some Docs\r\nfn main() {} ", ) .server(); server.request::( TextDocumentPositionParams { text_document: server.doc_id("src/main.rs"), position: Position { line: 0, character: 8 }, }, json!({ "cursorPosition": { "position": { "line": 1, "character": 4 }, "textDocument": { "uri": "file:///[..]src/main.rs" } }, "label": "on enter", "workspaceEdit": { "documentChanges": [ { "edits": [ { "newText": "\r\n/// ", "range": { "end": { "line": 0, "character": 8 }, "start": { "line": 0, "character": 8 } } } ], "textDocument": { "uri": "file:///[..]src/main.rs", "version": null } } ] } }), ); }