3 use std::{collections::HashMap, path::PathBuf, time::Instant};
6 notification::DidOpenTextDocument,
8 CodeActionRequest, Completion, Formatting, GotoDefinition, GotoTypeDefinition, HoverRequest,
10 CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams,
11 DocumentFormattingParams, FormattingOptions, GotoDefinitionParams, HoverParams,
12 PartialResultParams, Position, Range, TextDocumentItem, TextDocumentPositionParams,
13 WorkDoneProgressParams,
15 use rust_analyzer::lsp_ext::{OnEnter, Runnables, RunnablesParams};
17 use tempfile::TempDir;
18 use test_utils::skip_slow_tests;
20 use crate::support::{project, Project};
22 const PROFILE: &str = "";
23 // const PROFILE: &'static str = "*@3>100";
26 fn completes_items_from_standard_library() {
27 if skip_slow_tests() {
31 let project_start = Instant::now();
32 let server = Project::with_fixture(
40 use std::collections::Spam;
45 server.wait_until_workspace_is_loaded();
46 eprintln!("loading took {:?}", project_start.elapsed());
47 let completion_start = Instant::now();
48 let res = server.send_request::<Completion>(CompletionParams {
49 text_document_position: TextDocumentPositionParams::new(
50 server.doc_id("src/lib.rs"),
54 partial_result_params: PartialResultParams::default(),
55 work_done_progress_params: WorkDoneProgressParams::default(),
57 assert!(format!("{}", res).contains("HashMap"));
58 eprintln!("completion took {:?}", completion_start.elapsed());
62 fn test_runnables_no_project() {
63 if skip_slow_tests() {
75 server.wait_until_workspace_is_loaded();
76 server.request::<Runnables>(
77 RunnablesParams { text_document: server.doc_id("lib.rs"), position: None },
81 "cargoArgs": ["test"],
82 "executableArgs": ["foo", "--nocapture"],
88 "end": { "character": 1, "line": 2 },
89 "start": { "character": 0, "line": 0 }
91 "targetSelectionRange": {
92 "end": { "character": 6, "line": 1 },
93 "start": { "character": 3, "line": 1 }
95 "targetUri": "file:///[..]/lib.rs"
100 "cargoArgs": ["check", "--workspace"],
101 "executableArgs": [],
104 "label": "cargo check --workspace"
111 fn test_runnables_project() {
112 if skip_slow_tests() {
125 //- foo/tests/spam.rs
138 let server = Project::with_fixture(code).root("foo").root("bar").server();
140 server.wait_until_workspace_is_loaded();
141 server.request::<Runnables>(
142 RunnablesParams { text_document: server.doc_id("foo/tests/spam.rs"), position: None },
146 "cargoArgs": ["test", "--package", "foo", "--test", "spam"],
147 "executableArgs": ["test_eggs", "--exact", "--nocapture"],
148 "workspaceRoot": server.path().join("foo")
151 "label": "test test_eggs",
154 "end": { "character": 17, "line": 1 },
155 "start": { "character": 0, "line": 0 }
157 "targetSelectionRange": {
158 "end": { "character": 12, "line": 1 },
159 "start": { "character": 3, "line": 1 }
161 "targetUri": "file:///[..]/tests/spam.rs"
166 "cargoArgs": ["check", "--package", "foo"],
167 "executableArgs": [],
168 "workspaceRoot": server.path().join("foo")
171 "label": "cargo check -p foo"
175 "cargoArgs": ["test", "--package", "foo"],
176 "executableArgs": [],
177 "workspaceRoot": server.path().join("foo")
180 "label": "cargo test -p foo"
187 fn test_format_document() {
188 if skip_slow_tests() {
192 let server = project(
205 pub use std::collections::HashMap;
208 server.wait_until_workspace_is_loaded();
210 server.request::<Formatting>(
211 DocumentFormattingParams {
212 text_document: server.doc_id("src/lib.rs"),
213 options: FormattingOptions {
215 insert_spaces: false,
216 insert_final_newline: None,
217 trim_final_newlines: None,
218 trim_trailing_whitespace: None,
219 properties: HashMap::new(),
221 work_done_progress_params: WorkDoneProgressParams::default(),
225 "newText": r#"mod bar;
229 pub use std::collections::HashMap;
247 fn test_format_document_2018() {
248 if skip_slow_tests() {
252 let server = project(
269 pub use std::collections::HashMap;
272 server.wait_until_workspace_is_loaded();
274 server.request::<Formatting>(
275 DocumentFormattingParams {
276 text_document: server.doc_id("src/lib.rs"),
277 options: FormattingOptions {
279 insert_spaces: false,
280 properties: HashMap::new(),
281 insert_final_newline: None,
282 trim_final_newlines: None,
283 trim_trailing_whitespace: None,
285 work_done_progress_params: WorkDoneProgressParams::default(),
289 "newText": r#"mod bar;
295 pub use std::collections::HashMap;
313 fn test_missing_module_code_action() {
314 if skip_slow_tests() {
318 let server = project(
331 server.wait_until_workspace_is_loaded();
332 let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None };
333 server.request::<CodeActionRequest>(
335 text_document: server.doc_id("src/lib.rs"),
336 range: Range::new(Position::new(0, 4), Position::new(0, 7)),
337 context: empty_context(),
338 partial_result_params: PartialResultParams::default(),
339 work_done_progress_params: WorkDoneProgressParams::default(),
346 "uri": "file:///[..]/src/bar.rs"
350 "title": "Create module"
354 server.request::<CodeActionRequest>(
356 text_document: server.doc_id("src/lib.rs"),
357 range: Range::new(Position::new(2, 4), Position::new(2, 7)),
358 context: empty_context(),
359 partial_result_params: PartialResultParams::default(),
360 work_done_progress_params: WorkDoneProgressParams::default(),
367 fn test_missing_module_code_action_in_json_project() {
368 if skip_slow_tests() {
372 let tmp_dir = TempDir::new().unwrap();
374 let path = tmp_dir.path();
376 let project = json!({
379 "root_module": path.join("src/lib.rs"),
382 "cfg": [ "cfg_atom_1", "feature=cfg_1"],
383 "atom_cfgs": ["atom_2"],
384 "key_value_cfgs": { "feature": "key_value_feature", "other": "value"}
390 //- rust-project.json
398 PROJECT = project.to_string(),
401 let server = Project::with_fixture(&code).tmp_dir(tmp_dir).server();
403 server.wait_until_workspace_is_loaded();
404 let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None };
405 server.request::<CodeActionRequest>(
407 text_document: server.doc_id("src/lib.rs"),
408 range: Range::new(Position::new(0, 4), Position::new(0, 7)),
409 context: empty_context(),
410 partial_result_params: PartialResultParams::default(),
411 work_done_progress_params: WorkDoneProgressParams::default(),
418 "uri": "file://[..]/src/bar.rs"
422 "title": "Create module"
426 server.request::<CodeActionRequest>(
428 text_document: server.doc_id("src/lib.rs"),
429 range: Range::new(Position::new(2, 4), Position::new(2, 7)),
430 context: empty_context(),
431 partial_result_params: PartialResultParams::default(),
432 work_done_progress_params: WorkDoneProgressParams::default(),
439 fn diagnostics_dont_block_typing() {
440 if skip_slow_tests() {
444 let librs: String = (0..10).map(|i| format!("mod m{};", i)).collect();
445 let libs: String = (0..10).map(|i| format!("//- src/m{}.rs\nfn foo() {{}}\n\n", i)).collect();
446 let server = Project::with_fixture(&format!(
465 server.wait_until_workspace_is_loaded();
467 server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
468 text_document: TextDocumentItem {
469 uri: server.doc_id(&format!("src/m{}.rs", i)).uri,
470 language_id: "rust".to_string(),
472 text: "/// Docs\nfn foo() {}".to_string(),
476 let start = std::time::Instant::now();
477 server.request::<OnEnter>(
478 TextDocumentPositionParams {
479 text_document: server.doc_id("src/m0.rs"),
480 position: Position { line: 0, character: 5 },
483 "insertTextFormat": 2,
484 "newText": "\n/// $0",
486 "end": { "character": 5, "line": 0 },
487 "start": { "character": 5, "line": 0 }
491 let elapsed = start.elapsed();
492 assert!(elapsed.as_millis() < 2000, "typing enter took {:?}", elapsed);
496 fn preserves_dos_line_endings() {
497 if skip_slow_tests() {
501 let server = Project::with_fixture(
509 /// Some Docs\r\nfn main() {}
514 server.request::<OnEnter>(
515 TextDocumentPositionParams {
516 text_document: server.doc_id("src/main.rs"),
517 position: Position { line: 0, character: 8 },
520 "insertTextFormat": 2,
521 "newText": "\r\n/// $0",
523 "end": { "line": 0, "character": 8 },
524 "start": { "line": 0, "character": 8 }
531 fn out_dirs_check() {
532 if skip_slow_tests() {
536 let server = Project::with_fixture(
544 use std::{env, fs, path::Path};
547 let out_dir = env::var_os("OUT_DIR").unwrap();
548 let dest_path = Path::new(&out_dir).join("hello.rs");
551 r#"pub fn message() -> &'static str { "Hello, World!" }"#,
554 println!("cargo:rustc-cfg=atom_cfg");
555 println!("cargo:rustc-cfg=featlike=\"set\"");
556 println!("cargo:rerun-if-changed=build.rs");
559 include!(concat!(env!("OUT_DIR"), "/hello.rs"));
565 #[cfg(featlike = "set")]
567 #[cfg(featlike = "not_set")]
576 fn main() { message(); }
579 .with_config(|config| {
580 config.cargo.load_out_dirs_from_check = true;
583 server.wait_until_workspace_is_loaded();
584 let res = server.send_request::<GotoDefinition>(GotoDefinitionParams {
585 text_document_position_params: TextDocumentPositionParams::new(
586 server.doc_id("src/main.rs"),
587 Position::new(14, 8),
589 work_done_progress_params: Default::default(),
590 partial_result_params: Default::default(),
592 assert!(format!("{}", res).contains("hello.rs"));
593 server.request::<GotoTypeDefinition>(
594 GotoDefinitionParams {
595 text_document_position_params: TextDocumentPositionParams::new(
596 server.doc_id("src/main.rs"),
597 Position::new(12, 9),
599 work_done_progress_params: Default::default(),
600 partial_result_params: Default::default(),
603 "originSelectionRange": {
623 "targetSelectionRange": {
633 "targetUri": "file:///[..]src/main.rs"
636 server.request::<GotoTypeDefinition>(
637 GotoDefinitionParams {
638 text_document_position_params: TextDocumentPositionParams::new(
639 server.doc_id("src/main.rs"),
640 Position::new(13, 9),
642 work_done_progress_params: Default::default(),
643 partial_result_params: Default::default(),
646 "originSelectionRange": {
666 "targetSelectionRange": {
676 "targetUri": "file:///[..]src/main.rs"
682 fn resolve_proc_macro() {
683 if skip_slow_tests() {
686 let server = Project::with_fixture(
694 bar = {path = "../bar"}
717 extern crate proc_macro;
718 use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
721 TokenTree::from(Ident::new($n, Span::call_site()))
724 TokenTree::from(Group::new(Delimiter::Brace, TokenStream::new()))
727 TokenTree::from(Group::new(Delimiter::Parenthesis, TokenStream::new()))
730 #[proc_macro_derive(Bar)]
731 pub fn foo(_input: TokenStream) -> TokenStream {
732 // We hard code the output here for preventing to use any deps
733 let mut res = TokenStream::new();
735 // impl Bar for Foo { fn bar() {} }
736 let mut tokens = vec![t!("impl"), t!("Bar"), t!("for"), t!("Foo")];
737 let mut fn_stream = TokenStream::new();
738 fn_stream.extend(vec![t!("fn"), t!("bar"), t!(()), t!({})]);
739 tokens.push(Group::new(Delimiter::Brace, fn_stream).into());
746 .with_config(|config| {
747 let macro_srv_path = PathBuf::from(env!("CARGO_BIN_EXE_rust-analyzer"));
749 config.cargo.load_out_dirs_from_check = true;
750 config.proc_macro_srv = Some((macro_srv_path, vec!["proc-macro".into()]));
755 server.wait_until_workspace_is_loaded();
756 let res = server.send_request::<HoverRequest>(HoverParams {
757 text_document_position_params: TextDocumentPositionParams::new(
758 server.doc_id("foo/src/main.rs"),
761 work_done_progress_params: Default::default(),
764 let value = res.get("contents").unwrap().get("value").unwrap().to_string();
765 assert_eq!(value, r#""```rust\nfoo::Bar\n```\n\n```rust\nfn bar()\n```""#)