aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yaml1
-rw-r--r--Cargo.lock65
-rw-r--r--crates/ra_arena/src/map.rs10
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs100
-rw-r--r--crates/ra_flycheck/Cargo.toml (renamed from crates/ra_cargo_watch/Cargo.toml)3
-rw-r--r--crates/ra_flycheck/src/conv.rs (renamed from crates/ra_cargo_watch/src/conv.rs)0
-rw-r--r--crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_clippy_pass_by_ref.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_clippy_pass_by_ref.snap)2
-rw-r--r--crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_handles_macro_location.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_handles_macro_location.snap)2
-rw-r--r--crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_macro_compiler_error.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_macro_compiler_error.snap)2
-rw-r--r--crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_multi_line_fix.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_multi_line_fix.snap)2
-rw-r--r--crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_incompatible_type_for_trait.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_incompatible_type_for_trait.snap)2
-rw-r--r--crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_mismatched_type.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_mismatched_type.snap)2
-rw-r--r--crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_unused_variable.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_unused_variable.snap)2
-rw-r--r--crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_wrong_number_of_parameters.snap (renamed from crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_wrong_number_of_parameters.snap)2
-rw-r--r--crates/ra_flycheck/src/conv/test.rs (renamed from crates/ra_cargo_watch/src/conv/test.rs)0
-rw-r--r--crates/ra_flycheck/src/lib.rs (renamed from crates/ra_cargo_watch/src/lib.rs)248
-rw-r--r--crates/ra_hir_expand/src/proc_macro.rs27
-rw-r--r--crates/ra_hir_ty/src/display.rs44
-rw-r--r--crates/ra_ide/src/completion.rs10
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs2
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs8
-rw-r--r--crates/ra_ide/src/completion/presentation.rs12
-rw-r--r--crates/ra_ide/src/completion/test_utils.rs6
-rw-r--r--crates/ra_ide/src/inlay_hints.rs68
-rw-r--r--crates/ra_ide/src/lib.rs17
-rw-r--r--crates/ra_ide_db/src/lib.rs12
-rw-r--r--crates/ra_proc_macro/Cargo.toml5
-rw-r--r--crates/ra_proc_macro/src/lib.rs85
-rw-r--r--crates/ra_proc_macro/src/msg.rs93
-rw-r--r--crates/ra_proc_macro/src/process.rs196
-rw-r--r--crates/ra_proc_macro/src/rpc.rs266
-rw-r--r--crates/ra_prof/src/lib.rs8
-rw-r--r--crates/ra_project_model/Cargo.toml1
-rw-r--r--crates/ra_project_model/src/cargo_workspace.rs91
-rw-r--r--crates/ra_syntax/src/ast/edit.rs141
-rw-r--r--crates/ra_syntax/src/parsing/text_tree_sink.rs13
-rw-r--r--crates/ra_syntax/test_data/parser/ok/0065_comment_newline.rs3
-rw-r--r--crates/ra_syntax/test_data/parser/ok/0065_comment_newline.txt17
-rw-r--r--crates/ra_tt/src/lib.rs7
-rw-r--r--crates/rust-analyzer/Cargo.toml2
-rw-r--r--crates/rust-analyzer/src/cargo_target_spec.rs2
-rw-r--r--crates/rust-analyzer/src/cli/analysis_bench.rs4
-rw-r--r--crates/rust-analyzer/src/conv.rs2
-rw-r--r--crates/rust-analyzer/src/main_loop.rs247
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs20
-rw-r--r--crates/rust-analyzer/src/req.rs2
-rw-r--r--crates/rust-analyzer/src/semantic_tokens.rs6
-rw-r--r--crates/rust-analyzer/src/world.rs101
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/main.rs46
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/support.rs25
-rw-r--r--crates/stdx/src/lib.rs2
-rw-r--r--docs/user/features.md6
-rw-r--r--editors/code/package-lock.json59
-rw-r--r--editors/code/package.json8
-rw-r--r--editors/code/src/client.ts54
-rw-r--r--editors/code/src/commands/syntax_tree.ts165
-rw-r--r--editors/code/src/config.ts6
-rw-r--r--editors/code/src/ctx.ts12
-rw-r--r--editors/code/src/inlay_hints.ts8
-rw-r--r--editors/code/src/main.ts18
-rw-r--r--editors/code/src/tasks.ts52
-rw-r--r--editors/code/src/util.ts4
62 files changed, 1775 insertions, 651 deletions
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 5050c558c..fb7afe9fd 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -11,6 +11,7 @@ env:
11 CARGO_INCREMENTAL: 0 11 CARGO_INCREMENTAL: 0
12 CARGO_NET_RETRY: 10 12 CARGO_NET_RETRY: 10
13 RUN_SLOW_TESTS: 1 13 RUN_SLOW_TESTS: 1
14 RUST_BACKTRACE: short
14 RUSTFLAGS: -D warnings 15 RUSTFLAGS: -D warnings
15 RUSTUP_MAX_RETRIES: 10 16 RUSTUP_MAX_RETRIES: 10
16 17
diff --git a/Cargo.lock b/Cargo.lock
index 8d81c4839..9a1a05683 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -11,9 +11,9 @@ dependencies = [
11 11
12[[package]] 12[[package]]
13name = "anyhow" 13name = "anyhow"
14version = "1.0.27" 14version = "1.0.28"
15source = "registry+https://github.com/rust-lang/crates.io-index" 15source = "registry+https://github.com/rust-lang/crates.io-index"
16checksum = "013a6e0a2cbe3d20f9c60b65458f7a7f7a5e636c5d0f45a5a6aee5d4b1f01785" 16checksum = "d9a60d744a80c30fcb657dfe2c1b22bcb3e814c1a1e3674f32bf5820b570fbff"
17 17
18[[package]] 18[[package]]
19name = "anymap" 19name = "anymap"
@@ -435,9 +435,9 @@ dependencies = [
435 435
436[[package]] 436[[package]]
437name = "hermit-abi" 437name = "hermit-abi"
438version = "0.1.8" 438version = "0.1.10"
439source = "registry+https://github.com/rust-lang/crates.io-index" 439source = "registry+https://github.com/rust-lang/crates.io-index"
440checksum = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" 440checksum = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e"
441dependencies = [ 441dependencies = [
442 "libc", 442 "libc",
443] 443]
@@ -563,9 +563,9 @@ dependencies = [
563 563
564[[package]] 564[[package]]
565name = "jod-thread" 565name = "jod-thread"
566version = "0.1.0" 566version = "0.1.1"
567source = "registry+https://github.com/rust-lang/crates.io-index" 567source = "registry+https://github.com/rust-lang/crates.io-index"
568checksum = "2f52a11f73b88fab829a0e4d9e13ea5982c7ac457c72eb3541d82a4afdfce4ff" 568checksum = "4022656272c3e564a7cdebcaaba6518d844b0d0c1836597196efb5bfeb98bb49"
569 569
570[[package]] 570[[package]]
571name = "kernel32-sys" 571name = "kernel32-sys"
@@ -792,9 +792,9 @@ dependencies = [
792 792
793[[package]] 793[[package]]
794name = "paste" 794name = "paste"
795version = "0.1.8" 795version = "0.1.9"
796source = "registry+https://github.com/rust-lang/crates.io-index" 796source = "registry+https://github.com/rust-lang/crates.io-index"
797checksum = "8292c1e1e81ddb552c4c90c36af201a0ce7e34995f55f0480f01052f242811c9" 797checksum = "092d791bf7847f70bbd49085489fba25fc2c193571752bff9e36e74e72403932"
798dependencies = [ 798dependencies = [
799 "paste-impl", 799 "paste-impl",
800 "proc-macro-hack", 800 "proc-macro-hack",
@@ -802,9 +802,9 @@ dependencies = [
802 802
803[[package]] 803[[package]]
804name = "paste-impl" 804name = "paste-impl"
805version = "0.1.8" 805version = "0.1.9"
806source = "registry+https://github.com/rust-lang/crates.io-index" 806source = "registry+https://github.com/rust-lang/crates.io-index"
807checksum = "5e9c43f2645f06ee452544ad032886a75f3d1797b9487dcadcae9100ba58a51c" 807checksum = "406c23fb4c45cc6f68a9bbabb8ec7bd6f8cfcbd17e9e8f72c2460282f8325729"
808dependencies = [ 808dependencies = [
809 "proc-macro-hack", 809 "proc-macro-hack",
810 "proc-macro2", 810 "proc-macro2",
@@ -848,9 +848,9 @@ checksum = "fcfdefadc3d57ca21cf17990a28ef4c0f7c61383a28cb7604cf4a18e6ede1420"
848 848
849[[package]] 849[[package]]
850name = "proc-macro2" 850name = "proc-macro2"
851version = "1.0.9" 851version = "1.0.10"
852source = "registry+https://github.com/rust-lang/crates.io-index" 852source = "registry+https://github.com/rust-lang/crates.io-index"
853checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" 853checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3"
854dependencies = [ 854dependencies = [
855 "unicode-xid", 855 "unicode-xid",
856] 856]
@@ -887,18 +887,6 @@ dependencies = [
887] 887]
888 888
889[[package]] 889[[package]]
890name = "ra_cargo_watch"
891version = "0.1.0"
892dependencies = [
893 "cargo_metadata",
894 "crossbeam-channel",
895 "insta",
896 "log",
897 "lsp-types",
898 "serde_json",
899]
900
901[[package]]
902name = "ra_cfg" 890name = "ra_cfg"
903version = "0.1.0" 891version = "0.1.0"
904dependencies = [ 892dependencies = [
@@ -923,6 +911,19 @@ dependencies = [
923] 911]
924 912
925[[package]] 913[[package]]
914name = "ra_flycheck"
915version = "0.1.0"
916dependencies = [
917 "cargo_metadata",
918 "crossbeam-channel",
919 "insta",
920 "jod-thread",
921 "log",
922 "lsp-types",
923 "serde_json",
924]
925
926[[package]]
926name = "ra_fmt" 927name = "ra_fmt"
927version = "0.1.0" 928version = "0.1.0"
928dependencies = [ 929dependencies = [
@@ -1074,7 +1075,12 @@ dependencies = [
1074name = "ra_proc_macro" 1075name = "ra_proc_macro"
1075version = "0.1.0" 1076version = "0.1.0"
1076dependencies = [ 1077dependencies = [
1078 "crossbeam-channel",
1079 "jod-thread",
1080 "log",
1077 "ra_tt", 1081 "ra_tt",
1082 "serde",
1083 "serde_json",
1078] 1084]
1079 1085
1080[[package]] 1086[[package]]
@@ -1095,7 +1101,6 @@ dependencies = [
1095 "cargo_metadata", 1101 "cargo_metadata",
1096 "log", 1102 "log",
1097 "ra_arena", 1103 "ra_arena",
1098 "ra_cargo_watch",
1099 "ra_cfg", 1104 "ra_cfg",
1100 "ra_db", 1105 "ra_db",
1101 "ra_proc_macro", 1106 "ra_proc_macro",
@@ -1294,8 +1299,8 @@ dependencies = [
1294 "lsp-types", 1299 "lsp-types",
1295 "parking_lot", 1300 "parking_lot",
1296 "pico-args", 1301 "pico-args",
1297 "ra_cargo_watch",
1298 "ra_db", 1302 "ra_db",
1303 "ra_flycheck",
1299 "ra_hir", 1304 "ra_hir",
1300 "ra_hir_def", 1305 "ra_hir_def",
1301 "ra_hir_ty", 1306 "ra_hir_ty",
@@ -1425,9 +1430,9 @@ dependencies = [
1425 1430
1426[[package]] 1431[[package]]
1427name = "serde_json" 1432name = "serde_json"
1428version = "1.0.48" 1433version = "1.0.50"
1429source = "registry+https://github.com/rust-lang/crates.io-index" 1434source = "registry+https://github.com/rust-lang/crates.io-index"
1430checksum = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25" 1435checksum = "78a7a12c167809363ec3bd7329fc0a3369056996de43c4b37ef3cd54a6ce4867"
1431dependencies = [ 1436dependencies = [
1432 "itoa", 1437 "itoa",
1433 "ryu", 1438 "ryu",
@@ -1650,9 +1655,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1650 1655
1651[[package]] 1656[[package]]
1652name = "winapi-util" 1657name = "winapi-util"
1653version = "0.1.3" 1658version = "0.1.4"
1654source = "registry+https://github.com/rust-lang/crates.io-index" 1659source = "registry+https://github.com/rust-lang/crates.io-index"
1655checksum = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80" 1660checksum = "fa515c5163a99cc82bab70fd3bfdd36d827be85de63737b40fcef2ce084a436e"
1656dependencies = [ 1661dependencies = [
1657 "winapi 0.3.8", 1662 "winapi 0.3.8",
1658] 1663]
diff --git a/crates/ra_arena/src/map.rs b/crates/ra_arena/src/map.rs
index 5e764113d..0f33907c0 100644
--- a/crates/ra_arena/src/map.rs
+++ b/crates/ra_arena/src/map.rs
@@ -14,14 +14,8 @@ pub struct ArenaMap<ID, V> {
14impl<T, V> ArenaMap<Idx<T>, V> { 14impl<T, V> ArenaMap<Idx<T>, V> {
15 pub fn insert(&mut self, id: Idx<T>, t: V) { 15 pub fn insert(&mut self, id: Idx<T>, t: V) {
16 let idx = Self::to_idx(id); 16 let idx = Self::to_idx(id);
17 if self.v.capacity() <= idx { 17
18 self.v.reserve(idx + 1 - self.v.capacity()); 18 self.v.resize_with((idx + 1).max(self.v.len()), || None);
19 }
20 if self.v.len() <= idx {
21 while self.v.len() <= idx {
22 self.v.push(None);
23 }
24 }
25 self.v[idx] = Some(t); 19 self.v[idx] = Some(t);
26 } 20 }
27 21
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs
index add82e5b1..8d1af9933 100644
--- a/crates/ra_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ra_assists/src/handlers/fill_match_arms.rs
@@ -1,15 +1,11 @@
1//! FIXME: write short doc here
2
3use std::iter; 1use std::iter;
4 2
5use hir::{Adt, HasSource, ModuleDef, Semantics}; 3use hir::{Adt, HasSource, ModuleDef, Semantics};
6use itertools::Itertools; 4use itertools::Itertools;
7use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
8 7
9use crate::{Assist, AssistCtx, AssistId}; 8use crate::{Assist, AssistCtx, AssistId};
10use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner};
11
12use ast::{MatchArm, Pat};
13 9
14// Assist: fill_match_arms 10// Assist: fill_match_arms
15// 11//
@@ -97,10 +93,7 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
97 } 93 }
98 94
99 ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| { 95 ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| {
100 arms.extend(missing_arms); 96 let new_arm_list = match_arm_list.remove_placeholder().append_arms(missing_arms);
101
102 let indent_level = IndentLevel::from_node(match_arm_list.syntax());
103 let new_arm_list = indent_level.increase_indent(make::match_arm_list(arms));
104 97
105 edit.target(match_expr.syntax().text_range()); 98 edit.target(match_expr.syntax().text_range());
106 edit.set_cursor(expr.syntax().text_range().start()); 99 edit.set_cursor(expr.syntax().text_range().start());
@@ -655,4 +648,93 @@ mod tests {
655 "#, 648 "#,
656 ); 649 );
657 } 650 }
651
652 #[test]
653 fn fill_match_arms_preserves_comments() {
654 check_assist(
655 fill_match_arms,
656 r#"
657 enum A {
658 One,
659 Two,
660 }
661 fn foo(a: A) {
662 match a {
663 // foo bar baz<|>
664 A::One => {}
665 // This is where the rest should be
666 }
667 }
668 "#,
669 r#"
670 enum A {
671 One,
672 Two,
673 }
674 fn foo(a: A) {
675 match <|>a {
676 // foo bar baz
677 A::One => {}
678 // This is where the rest should be
679 A::Two => {}
680 }
681 }
682 "#,
683 );
684 }
685
686 #[test]
687 fn fill_match_arms_preserves_comments_empty() {
688 check_assist(
689 fill_match_arms,
690 r#"
691 enum A {
692 One,
693 Two,
694 }
695 fn foo(a: A) {
696 match a {
697 // foo bar baz<|>
698 }
699 }
700 "#,
701 r#"
702 enum A {
703 One,
704 Two,
705 }
706 fn foo(a: A) {
707 match <|>a {
708 // foo bar baz
709 A::One => {}
710 A::Two => {}
711 }
712 }
713 "#,
714 );
715 }
716
717 #[test]
718 fn fill_match_arms_placeholder() {
719 check_assist(
720 fill_match_arms,
721 r#"
722 enum A { One, Two, }
723 fn foo(a: A) {
724 match a<|> {
725 _ => (),
726 }
727 }
728 "#,
729 r#"
730 enum A { One, Two, }
731 fn foo(a: A) {
732 match <|>a {
733 A::One => {}
734 A::Two => {}
735 }
736 }
737 "#,
738 );
739 }
658} 740}
diff --git a/crates/ra_cargo_watch/Cargo.toml b/crates/ra_flycheck/Cargo.toml
index 741345a21..c9a9ddc12 100644
--- a/crates/ra_cargo_watch/Cargo.toml
+++ b/crates/ra_flycheck/Cargo.toml
@@ -1,6 +1,6 @@
1[package] 1[package]
2edition = "2018" 2edition = "2018"
3name = "ra_cargo_watch" 3name = "ra_flycheck"
4version = "0.1.0" 4version = "0.1.0"
5authors = ["rust-analyzer developers"] 5authors = ["rust-analyzer developers"]
6 6
@@ -10,6 +10,7 @@ lsp-types = { version = "0.73.0", features = ["proposed"] }
10log = "0.4.8" 10log = "0.4.8"
11cargo_metadata = "0.9.1" 11cargo_metadata = "0.9.1"
12serde_json = "1.0.48" 12serde_json = "1.0.48"
13jod-thread = "0.1.1"
13 14
14[dev-dependencies] 15[dev-dependencies]
15insta = "0.15.0" 16insta = "0.15.0"
diff --git a/crates/ra_cargo_watch/src/conv.rs b/crates/ra_flycheck/src/conv.rs
index 817543deb..817543deb 100644
--- a/crates/ra_cargo_watch/src/conv.rs
+++ b/crates/ra_flycheck/src/conv.rs
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_clippy_pass_by_ref.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_clippy_pass_by_ref.snap
index a59fa84fa..4c9db0385 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_clippy_pass_by_ref.snap
+++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_clippy_pass_by_ref.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_cargo_watch/src/conv/test.rs 2source: crates/ra_flycheck/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5[ 5[
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_handles_macro_location.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_handles_macro_location.snap
index 61ae0c9ae..7cde4d867 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_handles_macro_location.snap
+++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_handles_macro_location.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_cargo_watch/src/conv/test.rs 2source: crates/ra_flycheck/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5[ 5[
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_macro_compiler_error.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_macro_compiler_error.snap
index 641da1a58..1cc37e087 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_macro_compiler_error.snap
+++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_macro_compiler_error.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_cargo_watch/src/conv/test.rs 2source: crates/ra_flycheck/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5[ 5[
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_multi_line_fix.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_multi_line_fix.snap
index 0557a2e79..615ed8378 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_multi_line_fix.snap
+++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_multi_line_fix.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_cargo_watch/src/conv/test.rs 2source: crates/ra_flycheck/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5[ 5[
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_incompatible_type_for_trait.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_incompatible_type_for_trait.snap
index 754bc33a4..0df0fce18 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_incompatible_type_for_trait.snap
+++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_incompatible_type_for_trait.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_cargo_watch/src/conv/test.rs 2source: crates/ra_flycheck/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5[ 5[
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_mismatched_type.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_mismatched_type.snap
index 78b7f7cc8..28ebcb3b3 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_mismatched_type.snap
+++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_mismatched_type.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_cargo_watch/src/conv/test.rs 2source: crates/ra_flycheck/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5[ 5[
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_unused_variable.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_unused_variable.snap
index 5989ed202..5e0873281 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_unused_variable.snap
+++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_unused_variable.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_cargo_watch/src/conv/test.rs 2source: crates/ra_flycheck/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5[ 5[
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_wrong_number_of_parameters.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_wrong_number_of_parameters.snap
index e34b546dc..e500d3cd6 100644
--- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_wrong_number_of_parameters.snap
+++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_wrong_number_of_parameters.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_cargo_watch/src/conv/test.rs 2source: crates/ra_flycheck/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5[ 5[
diff --git a/crates/ra_cargo_watch/src/conv/test.rs b/crates/ra_flycheck/src/conv/test.rs
index 4e81455ca..4e81455ca 100644
--- a/crates/ra_cargo_watch/src/conv/test.rs
+++ b/crates/ra_flycheck/src/conv/test.rs
diff --git a/crates/ra_cargo_watch/src/lib.rs b/crates/ra_flycheck/src/lib.rs
index 7c525c430..75aece45f 100644
--- a/crates/ra_cargo_watch/src/lib.rs
+++ b/crates/ra_flycheck/src/lib.rs
@@ -1,85 +1,60 @@
1//! cargo_check provides the functionality needed to run `cargo check` or 1//! cargo_check provides the functionality needed to run `cargo check` or
2//! another compatible command (f.x. clippy) in a background thread and provide 2//! another compatible command (f.x. clippy) in a background thread and provide
3//! LSP diagnostics based on the output of the command. 3//! LSP diagnostics based on the output of the command.
4use cargo_metadata::Message; 4mod conv;
5use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; 5
6use lsp_types::{
7 CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin,
8 WorkDoneProgressEnd, WorkDoneProgressReport,
9};
10use std::{ 6use std::{
11 error, fmt, 7 error, fmt,
12 io::{BufRead, BufReader}, 8 io::{BufRead, BufReader},
13 path::{Path, PathBuf}, 9 path::{Path, PathBuf},
14 process::{Command, Stdio}, 10 process::{Command, Stdio},
15 thread::JoinHandle,
16 time::Instant, 11 time::Instant,
17}; 12};
18 13
19mod conv; 14use cargo_metadata::Message;
15use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender};
16use lsp_types::{
17 CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin,
18 WorkDoneProgressEnd, WorkDoneProgressReport,
19};
20 20
21use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic}; 21use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic};
22 22
23pub use crate::conv::url_from_path_with_drive_lowercasing; 23pub use crate::conv::url_from_path_with_drive_lowercasing;
24 24
25#[derive(Clone, Debug)] 25#[derive(Clone, Debug)]
26pub struct CheckOptions { 26pub struct CheckConfig {
27 pub enable: bool,
28 pub args: Vec<String>, 27 pub args: Vec<String>,
29 pub command: String, 28 pub command: String,
30 pub all_targets: bool, 29 pub all_targets: bool,
31} 30}
32 31
33/// CheckWatcher wraps the shared state and communication machinery used for 32/// Flycheck wraps the shared state and communication machinery used for
34/// running `cargo check` (or other compatible command) and providing 33/// running `cargo check` (or other compatible command) and providing
35/// diagnostics based on the output. 34/// diagnostics based on the output.
36/// The spawned thread is shut down when this struct is dropped. 35/// The spawned thread is shut down when this struct is dropped.
37#[derive(Debug)] 36#[derive(Debug)]
38pub struct CheckWatcher { 37pub struct Flycheck {
38 // XXX: drop order is significant
39 cmd_send: Sender<CheckCommand>,
40 handle: jod_thread::JoinHandle<()>,
39 pub task_recv: Receiver<CheckTask>, 41 pub task_recv: Receiver<CheckTask>,
40 cmd_send: Option<Sender<CheckCommand>>,
41 handle: Option<JoinHandle<()>>,
42} 42}
43 43
44impl CheckWatcher { 44impl Flycheck {
45 pub fn new(options: &CheckOptions, workspace_root: PathBuf) -> CheckWatcher { 45 pub fn new(config: CheckConfig, workspace_root: PathBuf) -> Flycheck {
46 let options = options.clone();
47
48 let (task_send, task_recv) = unbounded::<CheckTask>(); 46 let (task_send, task_recv) = unbounded::<CheckTask>();
49 let (cmd_send, cmd_recv) = unbounded::<CheckCommand>(); 47 let (cmd_send, cmd_recv) = unbounded::<CheckCommand>();
50 let handle = std::thread::spawn(move || { 48 let handle = jod_thread::spawn(move || {
51 let mut check = CheckWatcherThread::new(options, workspace_root); 49 let mut check = FlycheckThread::new(config, workspace_root);
52 check.run(&task_send, &cmd_recv); 50 check.run(&task_send, &cmd_recv);
53 }); 51 });
54 CheckWatcher { task_recv, cmd_send: Some(cmd_send), handle: Some(handle) } 52 Flycheck { task_recv, cmd_send, handle }
55 }
56
57 /// Returns a CheckWatcher that doesn't actually do anything
58 pub fn dummy() -> CheckWatcher {
59 CheckWatcher { task_recv: never(), cmd_send: None, handle: None }
60 } 53 }
61 54
62 /// Schedule a re-start of the cargo check worker. 55 /// Schedule a re-start of the cargo check worker.
63 pub fn update(&self) { 56 pub fn update(&self) {
64 if let Some(cmd_send) = &self.cmd_send { 57 self.cmd_send.send(CheckCommand::Update).unwrap();
65 cmd_send.send(CheckCommand::Update).unwrap();
66 }
67 }
68}
69
70impl std::ops::Drop for CheckWatcher {
71 fn drop(&mut self) {
72 if let Some(handle) = self.handle.take() {
73 // Take the sender out of the option
74 let cmd_send = self.cmd_send.take();
75
76 // Dropping the sender finishes the thread loop
77 drop(cmd_send);
78
79 // Join the thread, it should finish shortly. We don't really care
80 // whether it panicked, so it is safe to ignore the result
81 let _ = handle.join();
82 }
83 } 58 }
84} 59}
85 60
@@ -100,24 +75,35 @@ pub enum CheckCommand {
100 Update, 75 Update,
101} 76}
102 77
103struct CheckWatcherThread { 78struct FlycheckThread {
104 options: CheckOptions, 79 options: CheckConfig,
105 workspace_root: PathBuf, 80 workspace_root: PathBuf,
106 watcher: WatchThread,
107 last_update_req: Option<Instant>, 81 last_update_req: Option<Instant>,
82 // XXX: drop order is significant
83 message_recv: Receiver<CheckEvent>,
84 /// WatchThread exists to wrap around the communication needed to be able to
85 /// run `cargo check` without blocking. Currently the Rust standard library
86 /// doesn't provide a way to read sub-process output without blocking, so we
87 /// have to wrap sub-processes output handling in a thread and pass messages
88 /// back over a channel.
89 check_process: Option<jod_thread::JoinHandle<()>>,
108} 90}
109 91
110impl CheckWatcherThread { 92impl FlycheckThread {
111 fn new(options: CheckOptions, workspace_root: PathBuf) -> CheckWatcherThread { 93 fn new(options: CheckConfig, workspace_root: PathBuf) -> FlycheckThread {
112 CheckWatcherThread { 94 FlycheckThread {
113 options, 95 options,
114 workspace_root, 96 workspace_root,
115 watcher: WatchThread::dummy(),
116 last_update_req: None, 97 last_update_req: None,
98 message_recv: never(),
99 check_process: None,
117 } 100 }
118 } 101 }
119 102
120 fn run(&mut self, task_send: &Sender<CheckTask>, cmd_recv: &Receiver<CheckCommand>) { 103 fn run(&mut self, task_send: &Sender<CheckTask>, cmd_recv: &Receiver<CheckCommand>) {
104 // If we rerun the thread, we need to discard the previous check results first
105 self.clean_previous_results(task_send);
106
121 loop { 107 loop {
122 select! { 108 select! {
123 recv(&cmd_recv) -> cmd => match cmd { 109 recv(&cmd_recv) -> cmd => match cmd {
@@ -127,29 +113,32 @@ impl CheckWatcherThread {
127 break; 113 break;
128 }, 114 },
129 }, 115 },
130 recv(self.watcher.message_recv) -> msg => match msg { 116 recv(self.message_recv) -> msg => match msg {
131 Ok(msg) => self.handle_message(msg, task_send), 117 Ok(msg) => self.handle_message(msg, task_send),
132 Err(RecvError) => { 118 Err(RecvError) => {
133 // Watcher finished, replace it with a never channel to 119 // Watcher finished, replace it with a never channel to
134 // avoid busy-waiting. 120 // avoid busy-waiting.
135 std::mem::replace(&mut self.watcher.message_recv, never()); 121 self.message_recv = never();
122 self.check_process = None;
136 }, 123 },
137 } 124 }
138 }; 125 };
139 126
140 if self.should_recheck() { 127 if self.should_recheck() {
141 self.last_update_req.take(); 128 self.last_update_req = None;
142 task_send.send(CheckTask::ClearDiagnostics).unwrap(); 129 task_send.send(CheckTask::ClearDiagnostics).unwrap();
143 130 self.restart_check_process();
144 // Replace with a dummy watcher first so we drop the original and wait for completion
145 std::mem::replace(&mut self.watcher, WatchThread::dummy());
146
147 // Then create the actual new watcher
148 self.watcher = WatchThread::new(&self.options, &self.workspace_root);
149 } 131 }
150 } 132 }
151 } 133 }
152 134
135 fn clean_previous_results(&self, task_send: &Sender<CheckTask>) {
136 task_send.send(CheckTask::ClearDiagnostics).unwrap();
137 task_send
138 .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd { message: None })))
139 .unwrap();
140 }
141
153 fn should_recheck(&mut self) -> bool { 142 fn should_recheck(&mut self) -> bool {
154 if let Some(_last_update_req) = &self.last_update_req { 143 if let Some(_last_update_req) = &self.last_update_req {
155 // We currently only request an update on save, as we need up to 144 // We currently only request an update on save, as we need up to
@@ -221,6 +210,56 @@ impl CheckWatcherThread {
221 CheckEvent::Msg(Message::Unknown) => {} 210 CheckEvent::Msg(Message::Unknown) => {}
222 } 211 }
223 } 212 }
213
214 fn restart_check_process(&mut self) {
215 // First, clear and cancel the old thread
216 self.message_recv = never();
217 self.check_process = None;
218
219 let mut args: Vec<String> = vec![
220 self.options.command.clone(),
221 "--workspace".to_string(),
222 "--message-format=json".to_string(),
223 "--manifest-path".to_string(),
224 format!("{}/Cargo.toml", self.workspace_root.display()),
225 ];
226 if self.options.all_targets {
227 args.push("--all-targets".to_string());
228 }
229 args.extend(self.options.args.iter().cloned());
230
231 let (message_send, message_recv) = unbounded();
232 let workspace_root = self.workspace_root.to_owned();
233 self.message_recv = message_recv;
234 self.check_process = Some(jod_thread::spawn(move || {
235 // If we trigger an error here, we will do so in the loop instead,
236 // which will break out of the loop, and continue the shutdown
237 let _ = message_send.send(CheckEvent::Begin);
238
239 let res = run_cargo(&args, Some(&workspace_root), &mut |message| {
240 // Skip certain kinds of messages to only spend time on what's useful
241 match &message {
242 Message::CompilerArtifact(artifact) if artifact.fresh => return true,
243 Message::BuildScriptExecuted(_) => return true,
244 Message::Unknown => return true,
245 _ => {}
246 }
247
248 // if the send channel was closed, we want to shutdown
249 message_send.send(CheckEvent::Msg(message)).is_ok()
250 });
251
252 if let Err(err) = res {
253 // FIXME: make the `message_send` to be `Sender<Result<CheckEvent, CargoError>>`
254 // to display user-caused misconfiguration errors instead of just logging them here
255 log::error!("Cargo watcher failed {:?}", err);
256 }
257
258 // We can ignore any error here, as we are already in the progress
259 // of shutting down.
260 let _ = message_send.send(CheckEvent::End);
261 }))
262 }
224} 263}
225 264
226#[derive(Debug)] 265#[derive(Debug)]
@@ -229,18 +268,6 @@ pub struct DiagnosticWithFixes {
229 fixes: Vec<CodeAction>, 268 fixes: Vec<CodeAction>,
230} 269}
231 270
232/// WatchThread exists to wrap around the communication needed to be able to
233/// run `cargo check` without blocking. Currently the Rust standard library
234/// doesn't provide a way to read sub-process output without blocking, so we
235/// have to wrap sub-processes output handling in a thread and pass messages
236/// back over a channel.
237/// The correct way to dispose of the thread is to drop it, on which the
238/// sub-process will be killed, and the thread will be joined.
239struct WatchThread {
240 handle: Option<JoinHandle<()>>,
241 message_recv: Receiver<CheckEvent>,
242}
243
244enum CheckEvent { 271enum CheckEvent {
245 Begin, 272 Begin,
246 Msg(cargo_metadata::Message), 273 Msg(cargo_metadata::Message),
@@ -257,7 +284,7 @@ impl fmt::Display for CargoError {
257} 284}
258impl error::Error for CargoError {} 285impl error::Error for CargoError {}
259 286
260pub fn run_cargo( 287fn run_cargo(
261 args: &[String], 288 args: &[String],
262 current_dir: Option<&Path>, 289 current_dir: Option<&Path>,
263 on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool, 290 on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool,
@@ -330,76 +357,3 @@ pub fn run_cargo(
330 357
331 Err(CargoError(err_msg)) 358 Err(CargoError(err_msg))
332} 359}
333
334impl WatchThread {
335 fn dummy() -> WatchThread {
336 WatchThread { handle: None, message_recv: never() }
337 }
338
339 fn new(options: &CheckOptions, workspace_root: &Path) -> WatchThread {
340 let mut args: Vec<String> = vec![
341 options.command.clone(),
342 "--workspace".to_string(),
343 "--message-format=json".to_string(),
344 "--manifest-path".to_string(),
345 format!("{}/Cargo.toml", workspace_root.display()),
346 ];
347 if options.all_targets {
348 args.push("--all-targets".to_string());
349 }
350 args.extend(options.args.iter().cloned());
351
352 let (message_send, message_recv) = unbounded();
353 let workspace_root = workspace_root.to_owned();
354 let handle = if options.enable {
355 Some(std::thread::spawn(move || {
356 // If we trigger an error here, we will do so in the loop instead,
357 // which will break out of the loop, and continue the shutdown
358 let _ = message_send.send(CheckEvent::Begin);
359
360 let res = run_cargo(&args, Some(&workspace_root), &mut |message| {
361 // Skip certain kinds of messages to only spend time on what's useful
362 match &message {
363 Message::CompilerArtifact(artifact) if artifact.fresh => return true,
364 Message::BuildScriptExecuted(_) => return true,
365 Message::Unknown => return true,
366 _ => {}
367 }
368
369 // if the send channel was closed, we want to shutdown
370 message_send.send(CheckEvent::Msg(message)).is_ok()
371 });
372
373 if let Err(err) = res {
374 // FIXME: make the `message_send` to be `Sender<Result<CheckEvent, CargoError>>`
375 // to display user-caused misconfiguration errors instead of just logging them here
376 log::error!("Cargo watcher failed {:?}", err);
377 }
378
379 // We can ignore any error here, as we are already in the progress
380 // of shutting down.
381 let _ = message_send.send(CheckEvent::End);
382 }))
383 } else {
384 None
385 };
386 WatchThread { handle, message_recv }
387 }
388}
389
390impl std::ops::Drop for WatchThread {
391 fn drop(&mut self) {
392 if let Some(handle) = self.handle.take() {
393 // Replace our reciever with dummy one, so we can drop and close the
394 // one actually communicating with the thread
395 let recv = std::mem::replace(&mut self.message_recv, never());
396
397 // Dropping the original reciever initiates thread sub-process shutdown
398 drop(recv);
399
400 // Join the thread, it should finish shortly. We don't really care
401 // whether it panicked, so it is safe to ignore the result
402 let _ = handle.join();
403 }
404 }
405}
diff --git a/crates/ra_hir_expand/src/proc_macro.rs b/crates/ra_hir_expand/src/proc_macro.rs
index 4d270e0de..97d1208ec 100644
--- a/crates/ra_hir_expand/src/proc_macro.rs
+++ b/crates/ra_hir_expand/src/proc_macro.rs
@@ -9,6 +9,15 @@ pub struct ProcMacroExpander {
9 proc_macro_id: ProcMacroId, 9 proc_macro_id: ProcMacroId,
10} 10}
11 11
12macro_rules! err {
13 ($fmt:literal, $($tt:tt),*) => {
14 mbe::ExpandError::ProcMacroError(tt::ExpansionError::Unknown(format!($fmt, $($tt),*)))
15 };
16 ($fmt:literal) => {
17 mbe::ExpandError::ProcMacroError(tt::ExpansionError::Unknown($fmt.to_string()))
18 }
19}
20
12impl ProcMacroExpander { 21impl ProcMacroExpander {
13 pub fn new(krate: CrateId, proc_macro_id: ProcMacroId) -> ProcMacroExpander { 22 pub fn new(krate: CrateId, proc_macro_id: ProcMacroId) -> ProcMacroExpander {
14 ProcMacroExpander { krate, proc_macro_id } 23 ProcMacroExpander { krate, proc_macro_id }
@@ -25,8 +34,24 @@ impl ProcMacroExpander {
25 .proc_macro 34 .proc_macro
26 .get(self.proc_macro_id.0 as usize) 35 .get(self.proc_macro_id.0 as usize)
27 .clone() 36 .clone()
28 .ok_or_else(|| mbe::ExpandError::ConversionError)?; 37 .ok_or_else(|| err!("No derive macro found."))?;
38
39 let tt = remove_derive_atr(tt, &proc_macro.name)
40 .ok_or_else(|| err!("Fail to remove derive for custom derive"))?;
29 41
30 proc_macro.expander.expand(&tt, None).map_err(mbe::ExpandError::from) 42 proc_macro.expander.expand(&tt, None).map_err(mbe::ExpandError::from)
31 } 43 }
32} 44}
45
46fn remove_derive_atr(tt: &tt::Subtree, _name: &str) -> Option<tt::Subtree> {
47 // FIXME: proper handle the remove derive
48 // We assume the first 2 tokens are #[derive(name)]
49 if tt.token_trees.len() > 2 {
50 let mut tt = tt.clone();
51 tt.token_trees.remove(0);
52 tt.token_trees.remove(0);
53 return Some(tt);
54 }
55
56 None
57}
diff --git a/crates/ra_hir_ty/src/display.rs b/crates/ra_hir_ty/src/display.rs
index a6ef44a31..13ecd537a 100644
--- a/crates/ra_hir_ty/src/display.rs
+++ b/crates/ra_hir_ty/src/display.rs
@@ -159,20 +159,13 @@ impl HirDisplay for ApplicationTy {
159 } 159 }
160 TypeCtor::FnDef(def) => { 160 TypeCtor::FnDef(def) => {
161 let sig = f.db.callable_item_signature(def).subst(&self.parameters); 161 let sig = f.db.callable_item_signature(def).subst(&self.parameters);
162 let name = match def { 162 match def {
163 CallableDef::FunctionId(ff) => f.db.function_data(ff).name.clone(), 163 CallableDef::FunctionId(ff) => write!(f, "fn {}", f.db.function_data(ff).name)?,
164 CallableDef::StructId(s) => f.db.struct_data(s).name.clone(), 164 CallableDef::StructId(s) => write!(f, "{}", f.db.struct_data(s).name)?,
165 CallableDef::EnumVariantId(e) => { 165 CallableDef::EnumVariantId(e) => {
166 let enum_data = f.db.enum_data(e.parent); 166 write!(f, "{}", f.db.enum_data(e.parent).variants[e.local_id].name)?
167 enum_data.variants[e.local_id].name.clone()
168 } 167 }
169 }; 168 };
170 match def {
171 CallableDef::FunctionId(_) => write!(f, "fn {}", name)?,
172 CallableDef::StructId(_) | CallableDef::EnumVariantId(_) => {
173 write!(f, "{}", name)?
174 }
175 }
176 if self.parameters.len() > 0 { 169 if self.parameters.len() > 0 {
177 let generics = generics(f.db.upcast(), def.into()); 170 let generics = generics(f.db.upcast(), def.into());
178 let (parent_params, self_param, type_params, _impl_trait_params) = 171 let (parent_params, self_param, type_params, _impl_trait_params) =
@@ -197,8 +190,6 @@ impl HirDisplay for ApplicationTy {
197 }; 190 };
198 write!(f, "{}", name)?; 191 write!(f, "{}", name)?;
199 if self.parameters.len() > 0 { 192 if self.parameters.len() > 0 {
200 write!(f, "<")?;
201
202 let mut non_default_parameters = Vec::with_capacity(self.parameters.len()); 193 let mut non_default_parameters = Vec::with_capacity(self.parameters.len());
203 let parameters_to_write = if f.omit_verbose_types() { 194 let parameters_to_write = if f.omit_verbose_types() {
204 match self 195 match self
@@ -207,8 +198,8 @@ impl HirDisplay for ApplicationTy {
207 .map(|generic_def_id| f.db.generic_defaults(generic_def_id)) 198 .map(|generic_def_id| f.db.generic_defaults(generic_def_id))
208 .filter(|defaults| !defaults.is_empty()) 199 .filter(|defaults| !defaults.is_empty())
209 { 200 {
210 Option::None => self.parameters.0.as_ref(), 201 None => self.parameters.0.as_ref(),
211 Option::Some(default_parameters) => { 202 Some(default_parameters) => {
212 for (i, parameter) in self.parameters.iter().enumerate() { 203 for (i, parameter) in self.parameters.iter().enumerate() {
213 match (parameter, default_parameters.get(i)) { 204 match (parameter, default_parameters.get(i)) {
214 (&Ty::Unknown, _) | (_, None) => { 205 (&Ty::Unknown, _) | (_, None) => {
@@ -228,7 +219,7 @@ impl HirDisplay for ApplicationTy {
228 } else { 219 } else {
229 self.parameters.0.as_ref() 220 self.parameters.0.as_ref()
230 }; 221 };
231 222 write!(f, "<")?;
232 f.write_joined(parameters_to_write, ", ")?; 223 f.write_joined(parameters_to_write, ", ")?;
233 write!(f, ">")?; 224 write!(f, ">")?;
234 } 225 }
@@ -238,9 +229,9 @@ impl HirDisplay for ApplicationTy {
238 AssocContainerId::TraitId(it) => it, 229 AssocContainerId::TraitId(it) => it,
239 _ => panic!("not an associated type"), 230 _ => panic!("not an associated type"),
240 }; 231 };
241 let trait_name = f.db.trait_data(trait_).name.clone(); 232 let trait_ = f.db.trait_data(trait_);
242 let name = f.db.type_alias_data(type_alias).name.clone(); 233 let type_alias = f.db.type_alias_data(type_alias);
243 write!(f, "{}::{}", trait_name, name)?; 234 write!(f, "{}::{}", trait_.name, type_alias.name)?;
244 if self.parameters.len() > 0 { 235 if self.parameters.len() > 0 {
245 write!(f, "<")?; 236 write!(f, "<")?;
246 f.write_joined(&*self.parameters.0, ", ")?; 237 f.write_joined(&*self.parameters.0, ", ")?;
@@ -273,8 +264,8 @@ impl HirDisplay for ProjectionTy {
273 return write!(f, "{}", TYPE_HINT_TRUNCATION); 264 return write!(f, "{}", TYPE_HINT_TRUNCATION);
274 } 265 }
275 266
276 let trait_name = f.db.trait_data(self.trait_(f.db)).name.clone(); 267 let trait_ = f.db.trait_data(self.trait_(f.db));
277 write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_name,)?; 268 write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_.name)?;
278 if self.parameters.len() > 1 { 269 if self.parameters.len() > 1 {
279 write!(f, "<")?; 270 write!(f, "<")?;
280 f.write_joined(&self.parameters[1..], ", ")?; 271 f.write_joined(&self.parameters[1..], ", ")?;
@@ -319,7 +310,7 @@ impl HirDisplay for Ty {
319 Ty::Opaque(_) => write!(f, "impl ")?, 310 Ty::Opaque(_) => write!(f, "impl ")?,
320 _ => unreachable!(), 311 _ => unreachable!(),
321 }; 312 };
322 write_bounds_like_dyn_trait(&predicates, f)?; 313 write_bounds_like_dyn_trait(predicates, f)?;
323 } 314 }
324 Ty::Unknown => write!(f, "{{unknown}}")?, 315 Ty::Unknown => write!(f, "{{unknown}}")?,
325 Ty::Infer(..) => write!(f, "_")?, 316 Ty::Infer(..) => write!(f, "_")?,
@@ -352,7 +343,7 @@ fn write_bounds_like_dyn_trait(
352 // We assume that the self type is $0 (i.e. the 343 // We assume that the self type is $0 (i.e. the
353 // existential) here, which is the only thing that's 344 // existential) here, which is the only thing that's
354 // possible in actual Rust, and hence don't print it 345 // possible in actual Rust, and hence don't print it
355 write!(f, "{}", f.db.trait_data(trait_ref.trait_).name.clone())?; 346 write!(f, "{}", f.db.trait_data(trait_ref.trait_).name)?;
356 if trait_ref.substs.len() > 1 { 347 if trait_ref.substs.len() > 1 {
357 write!(f, "<")?; 348 write!(f, "<")?;
358 f.write_joined(&trait_ref.substs[1..], ", ")?; 349 f.write_joined(&trait_ref.substs[1..], ", ")?;
@@ -369,9 +360,8 @@ fn write_bounds_like_dyn_trait(
369 write!(f, "<")?; 360 write!(f, "<")?;
370 angle_open = true; 361 angle_open = true;
371 } 362 }
372 let name = 363 let type_alias = f.db.type_alias_data(projection_pred.projection_ty.associated_ty);
373 f.db.type_alias_data(projection_pred.projection_ty.associated_ty).name.clone(); 364 write!(f, "{} = ", type_alias.name)?;
374 write!(f, "{} = ", name)?;
375 projection_pred.ty.hir_fmt(f)?; 365 projection_pred.ty.hir_fmt(f)?;
376 } 366 }
377 GenericPredicate::Error => { 367 GenericPredicate::Error => {
@@ -405,7 +395,7 @@ impl TraitRef {
405 } else { 395 } else {
406 write!(f, ": ")?; 396 write!(f, ": ")?;
407 } 397 }
408 write!(f, "{}", f.db.trait_data(self.trait_).name.clone())?; 398 write!(f, "{}", f.db.trait_data(self.trait_).name)?;
409 if self.substs.len() > 1 { 399 if self.substs.len() > 1 {
410 write!(f, "<")?; 400 write!(f, "<")?;
411 f.write_joined(&self.substs[1..], ", ")?; 401 f.write_joined(&self.substs[1..], ", ")?;
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs
index cd0757be5..b683572fb 100644
--- a/crates/ra_ide/src/completion.rs
+++ b/crates/ra_ide/src/completion.rs
@@ -34,15 +34,15 @@ pub use crate::completion::completion_item::{
34}; 34};
35 35
36#[derive(Clone, Debug, PartialEq, Eq)] 36#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct CompletionOptions { 37pub struct CompletionConfig {
38 pub enable_postfix_completions: bool, 38 pub enable_postfix_completions: bool,
39 pub add_call_parenthesis: bool, 39 pub add_call_parenthesis: bool,
40 pub add_call_argument_snippets: bool, 40 pub add_call_argument_snippets: bool,
41} 41}
42 42
43impl Default for CompletionOptions { 43impl Default for CompletionConfig {
44 fn default() -> Self { 44 fn default() -> Self {
45 CompletionOptions { 45 CompletionConfig {
46 enable_postfix_completions: true, 46 enable_postfix_completions: true,
47 add_call_parenthesis: true, 47 add_call_parenthesis: true,
48 add_call_argument_snippets: true, 48 add_call_argument_snippets: true,
@@ -75,9 +75,9 @@ impl Default for CompletionOptions {
75pub(crate) fn completions( 75pub(crate) fn completions(
76 db: &RootDatabase, 76 db: &RootDatabase,
77 position: FilePosition, 77 position: FilePosition,
78 options: &CompletionOptions, 78 config: &CompletionConfig,
79) -> Option<Completions> { 79) -> Option<Completions> {
80 let ctx = CompletionContext::new(db, position, options)?; 80 let ctx = CompletionContext::new(db, position, config)?;
81 81
82 let mut acc = Completions::default(); 82 let mut acc = Completions::default();
83 83
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs
index 0a00054b2..29c2881c6 100644
--- a/crates/ra_ide/src/completion/complete_postfix.rs
+++ b/crates/ra_ide/src/completion/complete_postfix.rs
@@ -15,7 +15,7 @@ use crate::{
15}; 15};
16 16
17pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { 17pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
18 if !ctx.options.enable_postfix_completions { 18 if !ctx.config.enable_postfix_completions {
19 return; 19 return;
20 } 20 }
21 21
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs
index 319e33b61..fdc0da2c5 100644
--- a/crates/ra_ide/src/completion/completion_context.rs
+++ b/crates/ra_ide/src/completion/completion_context.rs
@@ -11,7 +11,7 @@ use ra_syntax::{
11}; 11};
12use ra_text_edit::AtomTextEdit; 12use ra_text_edit::AtomTextEdit;
13 13
14use crate::{completion::CompletionOptions, FilePosition}; 14use crate::{completion::CompletionConfig, FilePosition};
15 15
16/// `CompletionContext` is created early during completion to figure out, where 16/// `CompletionContext` is created early during completion to figure out, where
17/// exactly is the cursor, syntax-wise. 17/// exactly is the cursor, syntax-wise.
@@ -19,7 +19,7 @@ use crate::{completion::CompletionOptions, FilePosition};
19pub(crate) struct CompletionContext<'a> { 19pub(crate) struct CompletionContext<'a> {
20 pub(super) sema: Semantics<'a, RootDatabase>, 20 pub(super) sema: Semantics<'a, RootDatabase>,
21 pub(super) db: &'a RootDatabase, 21 pub(super) db: &'a RootDatabase,
22 pub(super) options: &'a CompletionOptions, 22 pub(super) config: &'a CompletionConfig,
23 pub(super) offset: TextUnit, 23 pub(super) offset: TextUnit,
24 /// The token before the cursor, in the original file. 24 /// The token before the cursor, in the original file.
25 pub(super) original_token: SyntaxToken, 25 pub(super) original_token: SyntaxToken,
@@ -61,7 +61,7 @@ impl<'a> CompletionContext<'a> {
61 pub(super) fn new( 61 pub(super) fn new(
62 db: &'a RootDatabase, 62 db: &'a RootDatabase,
63 position: FilePosition, 63 position: FilePosition,
64 options: &'a CompletionOptions, 64 config: &'a CompletionConfig,
65 ) -> Option<CompletionContext<'a>> { 65 ) -> Option<CompletionContext<'a>> {
66 let sema = Semantics::new(db); 66 let sema = Semantics::new(db);
67 67
@@ -85,7 +85,7 @@ impl<'a> CompletionContext<'a> {
85 let mut ctx = CompletionContext { 85 let mut ctx = CompletionContext {
86 sema, 86 sema,
87 db, 87 db,
88 options, 88 config,
89 original_token, 89 original_token,
90 token, 90 token,
91 offset: position.offset, 91 offset: position.offset,
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs
index 60f1b83f3..1c7c0924d 100644
--- a/crates/ra_ide/src/completion/presentation.rs
+++ b/crates/ra_ide/src/completion/presentation.rs
@@ -106,7 +106,7 @@ impl Completions {
106 }; 106 };
107 107
108 // Add `<>` for generic types 108 // Add `<>` for generic types
109 if ctx.is_path_type && !ctx.has_type_args && ctx.options.add_call_parenthesis { 109 if ctx.is_path_type && !ctx.has_type_args && ctx.config.add_call_parenthesis {
110 let has_non_default_type_params = match resolution { 110 let has_non_default_type_params = match resolution {
111 ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db), 111 ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db),
112 ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db), 112 ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db),
@@ -211,14 +211,14 @@ impl Completions {
211 .detail(function_signature.to_string()); 211 .detail(function_signature.to_string());
212 212
213 // If not an import, add parenthesis automatically. 213 // If not an import, add parenthesis automatically.
214 if ctx.use_item_syntax.is_none() && !ctx.is_call && ctx.options.add_call_parenthesis { 214 if ctx.use_item_syntax.is_none() && !ctx.is_call && ctx.config.add_call_parenthesis {
215 tested_by!(inserts_parens_for_function_calls); 215 tested_by!(inserts_parens_for_function_calls);
216 216
217 let (snippet, label) = if params.is_empty() || has_self_param && params.len() == 1 { 217 let (snippet, label) = if params.is_empty() || has_self_param && params.len() == 1 {
218 (format!("{}()$0", name), format!("{}()", name)) 218 (format!("{}()$0", name), format!("{}()", name))
219 } else { 219 } else {
220 builder = builder.trigger_call_info(); 220 builder = builder.trigger_call_info();
221 let snippet = if ctx.options.add_call_argument_snippets { 221 let snippet = if ctx.config.add_call_argument_snippets {
222 let to_skip = if has_self_param { 1 } else { 0 }; 222 let to_skip = if has_self_param { 1 } else { 0 };
223 let function_params_snippet = function_signature 223 let function_params_snippet = function_signature
224 .parameter_names 224 .parameter_names
@@ -311,7 +311,7 @@ mod tests {
311 311
312 use crate::completion::{ 312 use crate::completion::{
313 test_utils::{do_completion, do_completion_with_options}, 313 test_utils::{do_completion, do_completion_with_options},
314 CompletionItem, CompletionKind, CompletionOptions, 314 CompletionConfig, CompletionItem, CompletionKind,
315 }; 315 };
316 316
317 fn do_reference_completion(ra_fixture: &str) -> Vec<CompletionItem> { 317 fn do_reference_completion(ra_fixture: &str) -> Vec<CompletionItem> {
@@ -320,7 +320,7 @@ mod tests {
320 320
321 fn do_reference_completion_with_options( 321 fn do_reference_completion_with_options(
322 ra_fixture: &str, 322 ra_fixture: &str,
323 options: CompletionOptions, 323 options: CompletionConfig,
324 ) -> Vec<CompletionItem> { 324 ) -> Vec<CompletionItem> {
325 do_completion_with_options(ra_fixture, CompletionKind::Reference, &options) 325 do_completion_with_options(ra_fixture, CompletionKind::Reference, &options)
326 } 326 }
@@ -589,7 +589,7 @@ mod tests {
589 s.f<|> 589 s.f<|>
590 } 590 }
591 ", 591 ",
592 CompletionOptions { 592 CompletionConfig {
593 add_call_argument_snippets: false, 593 add_call_argument_snippets: false,
594 .. Default::default() 594 .. Default::default()
595 } 595 }
diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs
index 136857315..eb90b5279 100644
--- a/crates/ra_ide/src/completion/test_utils.rs
+++ b/crates/ra_ide/src/completion/test_utils.rs
@@ -1,19 +1,19 @@
1//! Runs completion for testing purposes. 1//! Runs completion for testing purposes.
2 2
3use crate::{ 3use crate::{
4 completion::{completion_item::CompletionKind, CompletionOptions}, 4 completion::{completion_item::CompletionKind, CompletionConfig},
5 mock_analysis::{analysis_and_position, single_file_with_position}, 5 mock_analysis::{analysis_and_position, single_file_with_position},
6 CompletionItem, 6 CompletionItem,
7}; 7};
8 8
9pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { 9pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> {
10 do_completion_with_options(code, kind, &CompletionOptions::default()) 10 do_completion_with_options(code, kind, &CompletionConfig::default())
11} 11}
12 12
13pub(crate) fn do_completion_with_options( 13pub(crate) fn do_completion_with_options(
14 code: &str, 14 code: &str,
15 kind: CompletionKind, 15 kind: CompletionKind,
16 options: &CompletionOptions, 16 options: &CompletionConfig,
17) -> Vec<CompletionItem> { 17) -> Vec<CompletionItem> {
18 let (analysis, position) = if code.contains("//-") { 18 let (analysis, position) = if code.contains("//-") {
19 analysis_and_position(code) 19 analysis_and_position(code)
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs
index f4f0751c0..d06fc03d3 100644
--- a/crates/ra_ide/src/inlay_hints.rs
+++ b/crates/ra_ide/src/inlay_hints.rs
@@ -11,14 +11,14 @@ use ra_syntax::{
11use crate::{FileId, FunctionSignature}; 11use crate::{FileId, FunctionSignature};
12 12
13#[derive(Clone, Debug, PartialEq, Eq)] 13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct InlayHintsOptions { 14pub struct InlayHintsConfig {
15 pub type_hints: bool, 15 pub type_hints: bool,
16 pub parameter_hints: bool, 16 pub parameter_hints: bool,
17 pub chaining_hints: bool, 17 pub chaining_hints: bool,
18 pub max_length: Option<usize>, 18 pub max_length: Option<usize>,
19} 19}
20 20
21impl Default for InlayHintsOptions { 21impl Default for InlayHintsConfig {
22 fn default() -> Self { 22 fn default() -> Self {
23 Self { type_hints: true, parameter_hints: true, chaining_hints: true, max_length: None } 23 Self { type_hints: true, parameter_hints: true, chaining_hints: true, max_length: None }
24 } 24 }
@@ -41,7 +41,7 @@ pub struct InlayHint {
41pub(crate) fn inlay_hints( 41pub(crate) fn inlay_hints(
42 db: &RootDatabase, 42 db: &RootDatabase,
43 file_id: FileId, 43 file_id: FileId,
44 options: &InlayHintsOptions, 44 config: &InlayHintsConfig,
45) -> Vec<InlayHint> { 45) -> Vec<InlayHint> {
46 let _p = profile("inlay_hints"); 46 let _p = profile("inlay_hints");
47 let sema = Semantics::new(db); 47 let sema = Semantics::new(db);
@@ -50,14 +50,14 @@ pub(crate) fn inlay_hints(
50 let mut res = Vec::new(); 50 let mut res = Vec::new();
51 for node in file.syntax().descendants() { 51 for node in file.syntax().descendants() {
52 if let Some(expr) = ast::Expr::cast(node.clone()) { 52 if let Some(expr) = ast::Expr::cast(node.clone()) {
53 get_chaining_hints(&mut res, &sema, options, expr); 53 get_chaining_hints(&mut res, &sema, config, expr);
54 } 54 }
55 55
56 match_ast! { 56 match_ast! {
57 match node { 57 match node {
58 ast::CallExpr(it) => { get_param_name_hints(&mut res, &sema, options, ast::Expr::from(it)); }, 58 ast::CallExpr(it) => { get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it)); },
59 ast::MethodCallExpr(it) => { get_param_name_hints(&mut res, &sema, options, ast::Expr::from(it)); }, 59 ast::MethodCallExpr(it) => { get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it)); },
60 ast::BindPat(it) => { get_bind_pat_hints(&mut res, &sema, options, it); }, 60 ast::BindPat(it) => { get_bind_pat_hints(&mut res, &sema, config, it); },
61 _ => (), 61 _ => (),
62 } 62 }
63 } 63 }
@@ -68,10 +68,10 @@ pub(crate) fn inlay_hints(
68fn get_chaining_hints( 68fn get_chaining_hints(
69 acc: &mut Vec<InlayHint>, 69 acc: &mut Vec<InlayHint>,
70 sema: &Semantics<RootDatabase>, 70 sema: &Semantics<RootDatabase>,
71 options: &InlayHintsOptions, 71 config: &InlayHintsConfig,
72 expr: ast::Expr, 72 expr: ast::Expr,
73) -> Option<()> { 73) -> Option<()> {
74 if !options.chaining_hints { 74 if !config.chaining_hints {
75 return None; 75 return None;
76 } 76 }
77 77
@@ -95,7 +95,7 @@ fn get_chaining_hints(
95 let next = tokens.next()?.kind(); 95 let next = tokens.next()?.kind();
96 let next_next = tokens.next()?.kind(); 96 let next_next = tokens.next()?.kind();
97 if next == SyntaxKind::WHITESPACE && next_next == SyntaxKind::DOT { 97 if next == SyntaxKind::WHITESPACE && next_next == SyntaxKind::DOT {
98 let label = ty.display_truncated(sema.db, options.max_length).to_string(); 98 let label = ty.display_truncated(sema.db, config.max_length).to_string();
99 acc.push(InlayHint { 99 acc.push(InlayHint {
100 range: expr.syntax().text_range(), 100 range: expr.syntax().text_range(),
101 kind: InlayKind::ChainingHint, 101 kind: InlayKind::ChainingHint,
@@ -108,10 +108,10 @@ fn get_chaining_hints(
108fn get_param_name_hints( 108fn get_param_name_hints(
109 acc: &mut Vec<InlayHint>, 109 acc: &mut Vec<InlayHint>,
110 sema: &Semantics<RootDatabase>, 110 sema: &Semantics<RootDatabase>,
111 options: &InlayHintsOptions, 111 config: &InlayHintsConfig,
112 expr: ast::Expr, 112 expr: ast::Expr,
113) -> Option<()> { 113) -> Option<()> {
114 if !options.parameter_hints { 114 if !config.parameter_hints {
115 return None; 115 return None;
116 } 116 }
117 117
@@ -148,10 +148,10 @@ fn get_param_name_hints(
148fn get_bind_pat_hints( 148fn get_bind_pat_hints(
149 acc: &mut Vec<InlayHint>, 149 acc: &mut Vec<InlayHint>,
150 sema: &Semantics<RootDatabase>, 150 sema: &Semantics<RootDatabase>,
151 options: &InlayHintsOptions, 151 config: &InlayHintsConfig,
152 pat: ast::BindPat, 152 pat: ast::BindPat,
153) -> Option<()> { 153) -> Option<()> {
154 if !options.type_hints { 154 if !config.type_hints {
155 return None; 155 return None;
156 } 156 }
157 157
@@ -164,7 +164,7 @@ fn get_bind_pat_hints(
164 acc.push(InlayHint { 164 acc.push(InlayHint {
165 range: pat.syntax().text_range(), 165 range: pat.syntax().text_range(),
166 kind: InlayKind::TypeHint, 166 kind: InlayKind::TypeHint,
167 label: ty.display_truncated(sema.db, options.max_length).to_string().into(), 167 label: ty.display_truncated(sema.db, config.max_length).to_string().into(),
168 }); 168 });
169 Some(()) 169 Some(())
170} 170}
@@ -270,7 +270,7 @@ fn get_fn_signature(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<
270 270
271#[cfg(test)] 271#[cfg(test)]
272mod tests { 272mod tests {
273 use crate::inlay_hints::InlayHintsOptions; 273 use crate::inlay_hints::InlayHintsConfig;
274 use insta::assert_debug_snapshot; 274 use insta::assert_debug_snapshot;
275 275
276 use crate::mock_analysis::single_file; 276 use crate::mock_analysis::single_file;
@@ -284,7 +284,7 @@ mod tests {
284 let _x = foo(4, 4); 284 let _x = foo(4, 4);
285 }"#, 285 }"#,
286 ); 286 );
287 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: true, type_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###" 287 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ parameter_hints: true, type_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###"
288 [ 288 [
289 InlayHint { 289 InlayHint {
290 range: [106; 107), 290 range: [106; 107),
@@ -308,7 +308,7 @@ mod tests {
308 let _x = foo(4, 4); 308 let _x = foo(4, 4);
309 }"#, 309 }"#,
310 ); 310 );
311 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: false, parameter_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###"[]"###); 311 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ type_hints: false, parameter_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###"[]"###);
312 } 312 }
313 313
314 #[test] 314 #[test]
@@ -320,7 +320,7 @@ mod tests {
320 let _x = foo(4, 4); 320 let _x = foo(4, 4);
321 }"#, 321 }"#,
322 ); 322 );
323 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: true, parameter_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###" 323 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ type_hints: true, parameter_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###"
324 [ 324 [
325 InlayHint { 325 InlayHint {
326 range: [97; 99), 326 range: [97; 99),
@@ -344,7 +344,7 @@ fn main() {
344}"#, 344}"#,
345 ); 345 );
346 346
347 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" 347 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###"
348 [ 348 [
349 InlayHint { 349 InlayHint {
350 range: [69; 71), 350 range: [69; 71),
@@ -401,7 +401,7 @@ fn main() {
401}"#, 401}"#,
402 ); 402 );
403 403
404 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" 404 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###"
405 [ 405 [
406 InlayHint { 406 InlayHint {
407 range: [193; 197), 407 range: [193; 197),
@@ -481,7 +481,7 @@ fn main() {
481}"#, 481}"#,
482 ); 482 );
483 483
484 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" 484 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###"
485 [ 485 [
486 InlayHint { 486 InlayHint {
487 range: [21; 30), 487 range: [21; 30),
@@ -545,7 +545,7 @@ fn main() {
545}"#, 545}"#,
546 ); 546 );
547 547
548 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" 548 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###"
549 [ 549 [
550 InlayHint { 550 InlayHint {
551 range: [21; 30), 551 range: [21; 30),
@@ -595,7 +595,7 @@ fn main() {
595}"#, 595}"#,
596 ); 596 );
597 597
598 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" 598 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###"
599 [ 599 [
600 InlayHint { 600 InlayHint {
601 range: [188; 192), 601 range: [188; 192),
@@ -690,7 +690,7 @@ fn main() {
690}"#, 690}"#,
691 ); 691 );
692 692
693 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" 693 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###"
694 [ 694 [
695 InlayHint { 695 InlayHint {
696 range: [188; 192), 696 range: [188; 192),
@@ -785,7 +785,7 @@ fn main() {
785}"#, 785}"#,
786 ); 786 );
787 787
788 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" 788 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###"
789 [ 789 [
790 InlayHint { 790 InlayHint {
791 range: [252; 256), 791 range: [252; 256),
@@ -857,7 +857,7 @@ fn main() {
857}"#, 857}"#,
858 ); 858 );
859 859
860 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions { max_length: Some(8), ..Default::default() }).unwrap(), @r###" 860 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig { max_length: Some(8), ..Default::default() }).unwrap(), @r###"
861 [ 861 [
862 InlayHint { 862 InlayHint {
863 range: [74; 75), 863 range: [74; 75),
@@ -945,7 +945,7 @@ fn main() {
945}"#, 945}"#,
946 ); 946 );
947 947
948 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" 948 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###"
949 [ 949 [
950 InlayHint { 950 InlayHint {
951 range: [798; 809), 951 range: [798; 809),
@@ -1067,7 +1067,7 @@ fn main() {
1067}"#, 1067}"#,
1068 ); 1068 );
1069 1069
1070 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions { max_length: Some(8), ..Default::default() }).unwrap(), @r###" 1070 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig { max_length: Some(8), ..Default::default() }).unwrap(), @r###"
1071 [] 1071 []
1072 "### 1072 "###
1073 ); 1073 );
@@ -1093,7 +1093,7 @@ fn main() {
1093}"#, 1093}"#,
1094 ); 1094 );
1095 1095
1096 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions { max_length: Some(8), ..Default::default() }).unwrap(), @r###" 1096 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig { max_length: Some(8), ..Default::default() }).unwrap(), @r###"
1097 [] 1097 []
1098 "### 1098 "###
1099 ); 1099 );
@@ -1115,7 +1115,7 @@ fn main() {
1115 .into_c(); 1115 .into_c();
1116 }"#, 1116 }"#,
1117 ); 1117 );
1118 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###" 1118 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"
1119 [ 1119 [
1120 InlayHint { 1120 InlayHint {
1121 range: [232; 269), 1121 range: [232; 269),
@@ -1144,7 +1144,7 @@ fn main() {
1144 let c = A(B(C)).into_b().into_c(); 1144 let c = A(B(C)).into_b().into_c();
1145 }"#, 1145 }"#,
1146 ); 1146 );
1147 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"[]"###); 1147 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"[]"###);
1148 } 1148 }
1149 1149
1150 #[test] 1150 #[test]
@@ -1162,7 +1162,7 @@ fn main() {
1162 .0; 1162 .0;
1163 }"#, 1163 }"#,
1164 ); 1164 );
1165 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###" 1165 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"
1166 [ 1166 [
1167 InlayHint { 1167 InlayHint {
1168 range: [150; 221), 1168 range: [150; 221),
@@ -1204,7 +1204,7 @@ fn main() {
1204 .into_c(); 1204 .into_c();
1205 }"#, 1205 }"#,
1206 ); 1206 );
1207 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###" 1207 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"
1208 [ 1208 [
1209 InlayHint { 1209 InlayHint {
1210 range: [403; 452), 1210 range: [403; 452),
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index e43414985..285381086 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -62,13 +62,13 @@ use crate::display::ToNav;
62pub use crate::{ 62pub use crate::{
63 assists::{Assist, AssistId}, 63 assists::{Assist, AssistId},
64 call_hierarchy::CallItem, 64 call_hierarchy::CallItem,
65 completion::{CompletionItem, CompletionItemKind, CompletionOptions, InsertTextFormat}, 65 completion::{CompletionConfig, CompletionItem, CompletionItemKind, InsertTextFormat},
66 diagnostics::Severity, 66 diagnostics::Severity,
67 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, 67 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode},
68 expand_macro::ExpandedMacro, 68 expand_macro::ExpandedMacro,
69 folding_ranges::{Fold, FoldKind}, 69 folding_ranges::{Fold, FoldKind},
70 hover::HoverResult, 70 hover::HoverResult,
71 inlay_hints::{InlayHint, InlayHintsOptions, InlayKind}, 71 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
72 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, 72 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult},
73 runnables::{Runnable, RunnableKind, TestId}, 73 runnables::{Runnable, RunnableKind, TestId},
74 source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, 74 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
@@ -138,6 +138,11 @@ impl AnalysisHost {
138 pub fn new(lru_capacity: Option<usize>) -> AnalysisHost { 138 pub fn new(lru_capacity: Option<usize>) -> AnalysisHost {
139 AnalysisHost { db: RootDatabase::new(lru_capacity) } 139 AnalysisHost { db: RootDatabase::new(lru_capacity) }
140 } 140 }
141
142 pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) {
143 self.db.update_lru_capacity(lru_capacity);
144 }
145
141 /// Returns a snapshot of the current state, which you can query for 146 /// Returns a snapshot of the current state, which you can query for
142 /// semantic information. 147 /// semantic information.
143 pub fn analysis(&self) -> Analysis { 148 pub fn analysis(&self) -> Analysis {
@@ -320,9 +325,9 @@ impl Analysis {
320 pub fn inlay_hints( 325 pub fn inlay_hints(
321 &self, 326 &self,
322 file_id: FileId, 327 file_id: FileId,
323 inlay_hint_opts: &InlayHintsOptions, 328 config: &InlayHintsConfig,
324 ) -> Cancelable<Vec<InlayHint>> { 329 ) -> Cancelable<Vec<InlayHint>> {
325 self.with_db(|db| inlay_hints::inlay_hints(db, file_id, inlay_hint_opts)) 330 self.with_db(|db| inlay_hints::inlay_hints(db, file_id, config))
326 } 331 }
327 332
328 /// Returns the set of folding ranges. 333 /// Returns the set of folding ranges.
@@ -445,9 +450,9 @@ impl Analysis {
445 pub fn completions( 450 pub fn completions(
446 &self, 451 &self,
447 position: FilePosition, 452 position: FilePosition,
448 options: &CompletionOptions, 453 config: &CompletionConfig,
449 ) -> Cancelable<Option<Vec<CompletionItem>>> { 454 ) -> Cancelable<Option<Vec<CompletionItem>>> {
450 self.with_db(|db| completion::completions(db, position, options).map(Into::into)) 455 self.with_db(|db| completion::completions(db, position, config).map(Into::into))
451 } 456 }
452 457
453 /// Computes assists (aka code actions aka intentions) for the given 458 /// Computes assists (aka code actions aka intentions) for the given
diff --git a/crates/ra_ide_db/src/lib.rs b/crates/ra_ide_db/src/lib.rs
index 4faeefa8d..e6f2d36e9 100644
--- a/crates/ra_ide_db/src/lib.rs
+++ b/crates/ra_ide_db/src/lib.rs
@@ -115,12 +115,16 @@ impl RootDatabase {
115 db.set_crate_graph_with_durability(Default::default(), Durability::HIGH); 115 db.set_crate_graph_with_durability(Default::default(), Durability::HIGH);
116 db.set_local_roots_with_durability(Default::default(), Durability::HIGH); 116 db.set_local_roots_with_durability(Default::default(), Durability::HIGH);
117 db.set_library_roots_with_durability(Default::default(), Durability::HIGH); 117 db.set_library_roots_with_durability(Default::default(), Durability::HIGH);
118 let lru_capacity = lru_capacity.unwrap_or(ra_db::DEFAULT_LRU_CAP); 118 db.update_lru_capacity(lru_capacity);
119 db.query_mut(ra_db::ParseQuery).set_lru_capacity(lru_capacity);
120 db.query_mut(hir::db::ParseMacroQuery).set_lru_capacity(lru_capacity);
121 db.query_mut(hir::db::MacroExpandQuery).set_lru_capacity(lru_capacity);
122 db 119 db
123 } 120 }
121
122 pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) {
123 let lru_capacity = lru_capacity.unwrap_or(ra_db::DEFAULT_LRU_CAP);
124 self.query_mut(ra_db::ParseQuery).set_lru_capacity(lru_capacity);
125 self.query_mut(hir::db::ParseMacroQuery).set_lru_capacity(lru_capacity);
126 self.query_mut(hir::db::MacroExpandQuery).set_lru_capacity(lru_capacity);
127 }
124} 128}
125 129
126impl salsa::ParallelDatabase for RootDatabase { 130impl salsa::ParallelDatabase for RootDatabase {
diff --git a/crates/ra_proc_macro/Cargo.toml b/crates/ra_proc_macro/Cargo.toml
index bc2c37296..d009ceb82 100644
--- a/crates/ra_proc_macro/Cargo.toml
+++ b/crates/ra_proc_macro/Cargo.toml
@@ -10,3 +10,8 @@ doctest = false
10 10
11[dependencies] 11[dependencies]
12ra_tt = { path = "../ra_tt" } 12ra_tt = { path = "../ra_tt" }
13serde = { version = "1.0", features = ["derive"] }
14serde_json = "1.0"
15log = "0.4.8"
16crossbeam-channel = "0.4.0"
17jod-thread = "0.1.1"
diff --git a/crates/ra_proc_macro/src/lib.rs b/crates/ra_proc_macro/src/lib.rs
index 5e21dd487..51fbb046a 100644
--- a/crates/ra_proc_macro/src/lib.rs
+++ b/crates/ra_proc_macro/src/lib.rs
@@ -5,55 +5,104 @@
5//! is used to provide basic infrastructure for communication between two 5//! is used to provide basic infrastructure for communication between two
6//! processes: Client (RA itself), Server (the external program) 6//! processes: Client (RA itself), Server (the external program)
7 7
8mod rpc;
9mod process;
10pub mod msg;
11
12use process::{ProcMacroProcessSrv, ProcMacroProcessThread};
8use ra_tt::{SmolStr, Subtree}; 13use ra_tt::{SmolStr, Subtree};
14use rpc::ProcMacroKind;
9use std::{ 15use std::{
10 path::{Path, PathBuf}, 16 path::{Path, PathBuf},
11 sync::Arc, 17 sync::Arc,
12}; 18};
13 19
14#[derive(Debug, Clone, PartialEq, Eq)] 20pub use rpc::{ExpansionResult, ExpansionTask};
21
22#[derive(Debug, Clone)]
15pub struct ProcMacroProcessExpander { 23pub struct ProcMacroProcessExpander {
16 process: Arc<ProcMacroProcessSrv>, 24 process: Arc<ProcMacroProcessSrv>,
25 dylib_path: PathBuf,
17 name: SmolStr, 26 name: SmolStr,
18} 27}
19 28
29impl Eq for ProcMacroProcessExpander {}
30impl PartialEq for ProcMacroProcessExpander {
31 fn eq(&self, other: &Self) -> bool {
32 self.name == other.name
33 && self.dylib_path == other.dylib_path
34 && Arc::ptr_eq(&self.process, &other.process)
35 }
36}
37
20impl ra_tt::TokenExpander for ProcMacroProcessExpander { 38impl ra_tt::TokenExpander for ProcMacroProcessExpander {
21 fn expand( 39 fn expand(
22 &self, 40 &self,
23 _subtree: &Subtree, 41 subtree: &Subtree,
24 _attr: Option<&Subtree>, 42 _attr: Option<&Subtree>,
25 ) -> Result<Subtree, ra_tt::ExpansionError> { 43 ) -> Result<Subtree, ra_tt::ExpansionError> {
26 // FIXME: do nothing for now 44 self.process.custom_derive(&self.dylib_path, subtree, &self.name)
27 Ok(Subtree::default())
28 } 45 }
29} 46}
30 47
31#[derive(Debug, Clone, PartialEq, Eq)] 48#[derive(Debug)]
32pub struct ProcMacroProcessSrv { 49enum ProcMacroClientKind {
33 path: PathBuf, 50 Process { process: Arc<ProcMacroProcessSrv>, thread: ProcMacroProcessThread },
51 Dummy,
34} 52}
35 53
36#[derive(Debug, Clone, PartialEq, Eq)] 54#[derive(Debug)]
37pub enum ProcMacroClient { 55pub struct ProcMacroClient {
38 Process { process: Arc<ProcMacroProcessSrv> }, 56 kind: ProcMacroClientKind,
39 Dummy,
40} 57}
41 58
42impl ProcMacroClient { 59impl ProcMacroClient {
43 pub fn extern_process(process_path: &Path) -> ProcMacroClient { 60 pub fn extern_process(process_path: &Path) -> Result<ProcMacroClient, std::io::Error> {
44 let process = ProcMacroProcessSrv { path: process_path.into() }; 61 let (thread, process) = ProcMacroProcessSrv::run(process_path)?;
45 ProcMacroClient::Process { process: Arc::new(process) } 62 Ok(ProcMacroClient {
63 kind: ProcMacroClientKind::Process { process: Arc::new(process), thread },
64 })
46 } 65 }
47 66
48 pub fn dummy() -> ProcMacroClient { 67 pub fn dummy() -> ProcMacroClient {
49 ProcMacroClient::Dummy 68 ProcMacroClient { kind: ProcMacroClientKind::Dummy }
50 } 69 }
51 70
52 pub fn by_dylib_path( 71 pub fn by_dylib_path(
53 &self, 72 &self,
54 _dylib_path: &Path, 73 dylib_path: &Path,
55 ) -> Vec<(SmolStr, Arc<dyn ra_tt::TokenExpander>)> { 74 ) -> Vec<(SmolStr, Arc<dyn ra_tt::TokenExpander>)> {
56 // FIXME: return empty for now 75 match &self.kind {
57 vec![] 76 ProcMacroClientKind::Dummy => vec![],
77 ProcMacroClientKind::Process { process, .. } => {
78 let macros = match process.find_proc_macros(dylib_path) {
79 Err(err) => {
80 eprintln!("Fail to find proc macro. Error: {:#?}", err);
81 return vec![];
82 }
83 Ok(macros) => macros,
84 };
85
86 macros
87 .into_iter()
88 .filter_map(|(name, kind)| {
89 // FIXME: Support custom derive only for now.
90 match kind {
91 ProcMacroKind::CustomDerive => {
92 let name = SmolStr::new(&name);
93 let expander: Arc<dyn ra_tt::TokenExpander> =
94 Arc::new(ProcMacroProcessExpander {
95 process: process.clone(),
96 name: name.clone(),
97 dylib_path: dylib_path.into(),
98 });
99 Some((name, expander))
100 }
101 _ => None,
102 }
103 })
104 .collect()
105 }
106 }
58 } 107 }
59} 108}
diff --git a/crates/ra_proc_macro/src/msg.rs b/crates/ra_proc_macro/src/msg.rs
new file mode 100644
index 000000000..aa95bcc8f
--- /dev/null
+++ b/crates/ra_proc_macro/src/msg.rs
@@ -0,0 +1,93 @@
1//! Defines messages for cross-process message based on `ndjson` wire protocol
2
3use std::{
4 convert::TryFrom,
5 io::{self, BufRead, Write},
6};
7
8use crate::{
9 rpc::{ListMacrosResult, ListMacrosTask},
10 ExpansionResult, ExpansionTask,
11};
12use serde::{de::DeserializeOwned, Deserialize, Serialize};
13
14#[derive(Debug, Serialize, Deserialize, Clone)]
15pub enum Request {
16 ListMacro(ListMacrosTask),
17 ExpansionMacro(ExpansionTask),
18}
19
20#[derive(Debug, Serialize, Deserialize, Clone)]
21pub enum Response {
22 Error(ResponseError),
23 ListMacro(ListMacrosResult),
24 ExpansionMacro(ExpansionResult),
25}
26
27macro_rules! impl_try_from_response {
28 ($ty:ty, $tag:ident) => {
29 impl TryFrom<Response> for $ty {
30 type Error = &'static str;
31 fn try_from(value: Response) -> Result<Self, Self::Error> {
32 match value {
33 Response::$tag(res) => Ok(res),
34 _ => Err("Fail to convert from response"),
35 }
36 }
37 }
38 };
39}
40
41impl_try_from_response!(ListMacrosResult, ListMacro);
42impl_try_from_response!(ExpansionResult, ExpansionMacro);
43
44#[derive(Debug, Serialize, Deserialize, Clone)]
45pub struct ResponseError {
46 pub code: ErrorCode,
47 pub message: String,
48}
49
50#[derive(Debug, Serialize, Deserialize, Clone)]
51pub enum ErrorCode {
52 ServerErrorEnd,
53 ExpansionError,
54}
55
56pub trait Message: Sized + Serialize + DeserializeOwned {
57 fn read(r: &mut impl BufRead) -> io::Result<Option<Self>> {
58 let text = match read_json(r)? {
59 None => return Ok(None),
60 Some(text) => text,
61 };
62 let msg = serde_json::from_str(&text)?;
63 Ok(Some(msg))
64 }
65 fn write(self, w: &mut impl Write) -> io::Result<()> {
66 let text = serde_json::to_string(&self)?;
67 write_json(w, &text)
68 }
69}
70
71impl Message for Request {}
72impl Message for Response {}
73
74fn read_json(inp: &mut impl BufRead) -> io::Result<Option<String>> {
75 let mut buf = String::new();
76 if inp.read_line(&mut buf)? == 0 {
77 return Ok(None);
78 }
79 // Remove ending '\n'
80 let buf = &buf[..buf.len() - 1];
81 if buf.is_empty() {
82 return Ok(None);
83 }
84 Ok(Some(buf.to_string()))
85}
86
87fn write_json(out: &mut impl Write, msg: &str) -> io::Result<()> {
88 log::debug!("> {}", msg);
89 out.write_all(msg.as_bytes())?;
90 out.write_all(b"\n")?;
91 out.flush()?;
92 Ok(())
93}
diff --git a/crates/ra_proc_macro/src/process.rs b/crates/ra_proc_macro/src/process.rs
new file mode 100644
index 000000000..e8c85be38
--- /dev/null
+++ b/crates/ra_proc_macro/src/process.rs
@@ -0,0 +1,196 @@
1//! Handle process life-time and message passing for proc-macro client
2
3use crossbeam_channel::{bounded, Receiver, Sender};
4use ra_tt::Subtree;
5
6use crate::msg::{ErrorCode, Message, Request, Response, ResponseError};
7use crate::rpc::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask, ProcMacroKind};
8
9use io::{BufRead, BufReader};
10use std::{
11 convert::{TryFrom, TryInto},
12 io::{self, Write},
13 path::{Path, PathBuf},
14 process::{Child, Command, Stdio},
15 sync::{Arc, Weak},
16};
17
18#[derive(Debug, Default)]
19pub(crate) struct ProcMacroProcessSrv {
20 inner: Option<Weak<Sender<Task>>>,
21}
22
23#[derive(Debug)]
24pub(crate) struct ProcMacroProcessThread {
25 // XXX: drop order is significant
26 sender: Arc<Sender<Task>>,
27 handle: jod_thread::JoinHandle<()>,
28}
29
30struct Task {
31 req: Request,
32 result_tx: Sender<Option<Response>>,
33}
34
35struct Process {
36 path: PathBuf,
37 child: Child,
38}
39
40impl Drop for Process {
41 fn drop(&mut self) {
42 let _ = self.child.kill();
43 }
44}
45
46impl Process {
47 fn run(process_path: &Path) -> Result<Process, io::Error> {
48 let child = Command::new(process_path.clone())
49 .stdin(Stdio::piped())
50 .stdout(Stdio::piped())
51 .stderr(Stdio::null())
52 .spawn()?;
53
54 Ok(Process { path: process_path.into(), child })
55 }
56
57 fn restart(&mut self) -> Result<(), io::Error> {
58 let _ = self.child.kill();
59 self.child = Command::new(self.path.clone())
60 .stdin(Stdio::piped())
61 .stdout(Stdio::piped())
62 .stderr(Stdio::null())
63 .spawn()?;
64 Ok(())
65 }
66
67 fn stdio(&mut self) -> Option<(impl Write, impl BufRead)> {
68 let stdin = self.child.stdin.take()?;
69 let stdout = self.child.stdout.take()?;
70 let read = BufReader::new(stdout);
71
72 Some((stdin, read))
73 }
74}
75
76impl ProcMacroProcessSrv {
77 pub fn run(
78 process_path: &Path,
79 ) -> Result<(ProcMacroProcessThread, ProcMacroProcessSrv), io::Error> {
80 let process = Process::run(process_path)?;
81
82 let (task_tx, task_rx) = bounded(0);
83 let handle = jod_thread::spawn(move || {
84 client_loop(task_rx, process);
85 });
86
87 let task_tx = Arc::new(task_tx);
88 let srv = ProcMacroProcessSrv { inner: Some(Arc::downgrade(&task_tx)) };
89 let thread = ProcMacroProcessThread { handle, sender: task_tx };
90
91 Ok((thread, srv))
92 }
93
94 pub fn find_proc_macros(
95 &self,
96 dylib_path: &Path,
97 ) -> Result<Vec<(String, ProcMacroKind)>, ra_tt::ExpansionError> {
98 let task = ListMacrosTask { lib: dylib_path.to_path_buf() };
99
100 let result: ListMacrosResult = self.send_task(Request::ListMacro(task))?;
101 Ok(result.macros)
102 }
103
104 pub fn custom_derive(
105 &self,
106 dylib_path: &Path,
107 subtree: &Subtree,
108 derive_name: &str,
109 ) -> Result<Subtree, ra_tt::ExpansionError> {
110 let task = ExpansionTask {
111 macro_body: subtree.clone(),
112 macro_name: derive_name.to_string(),
113 attributes: None,
114 lib: dylib_path.to_path_buf(),
115 };
116
117 let result: ExpansionResult = self.send_task(Request::ExpansionMacro(task))?;
118 Ok(result.expansion)
119 }
120
121 pub fn send_task<R>(&self, req: Request) -> Result<R, ra_tt::ExpansionError>
122 where
123 R: TryFrom<Response, Error = &'static str>,
124 {
125 let sender = match &self.inner {
126 None => return Err(ra_tt::ExpansionError::Unknown("No sender is found.".to_string())),
127 Some(it) => it,
128 };
129
130 let (result_tx, result_rx) = bounded(0);
131 let sender = match sender.upgrade() {
132 None => {
133 return Err(ra_tt::ExpansionError::Unknown("Proc macro process is closed.".into()))
134 }
135 Some(it) => it,
136 };
137 sender.send(Task { req: req.into(), result_tx }).unwrap();
138 let res = result_rx
139 .recv()
140 .map_err(|_| ra_tt::ExpansionError::Unknown("Proc macro thread is closed.".into()))?;
141
142 match res {
143 Some(Response::Error(err)) => {
144 return Err(ra_tt::ExpansionError::ExpansionError(err.message));
145 }
146 Some(res) => Ok(res.try_into().map_err(|err| {
147 ra_tt::ExpansionError::Unknown(format!(
148 "Fail to get response, reason : {:#?} ",
149 err
150 ))
151 })?),
152 None => Err(ra_tt::ExpansionError::Unknown("Empty result".into())),
153 }
154 }
155}
156
157fn client_loop(task_rx: Receiver<Task>, mut process: Process) {
158 let (mut stdin, mut stdout) = match process.stdio() {
159 None => return,
160 Some(it) => it,
161 };
162
163 for task in task_rx {
164 let Task { req, result_tx } = task;
165
166 match send_request(&mut stdin, &mut stdout, req) {
167 Ok(res) => result_tx.send(res).unwrap(),
168 Err(_err) => {
169 let res = Response::Error(ResponseError {
170 code: ErrorCode::ServerErrorEnd,
171 message: "Server closed".into(),
172 });
173 result_tx.send(res.into()).unwrap();
174 // Restart the process
175 if process.restart().is_err() {
176 break;
177 }
178 let stdio = match process.stdio() {
179 None => break,
180 Some(it) => it,
181 };
182 stdin = stdio.0;
183 stdout = stdio.1;
184 }
185 }
186 }
187}
188
189fn send_request(
190 mut writer: &mut impl Write,
191 mut reader: &mut impl BufRead,
192 req: Request,
193) -> Result<Option<Response>, io::Error> {
194 req.write(&mut writer)?;
195 Ok(Response::read(&mut reader)?)
196}
diff --git a/crates/ra_proc_macro/src/rpc.rs b/crates/ra_proc_macro/src/rpc.rs
new file mode 100644
index 000000000..66b3f55db
--- /dev/null
+++ b/crates/ra_proc_macro/src/rpc.rs
@@ -0,0 +1,266 @@
1//! Data struture serialization related stuffs for RPC
2//!
3//! Define all necessary rpc serialization data structure,
4//! which include ra_tt related data and some task messages.
5//! Although adding Serialize and Deserialize trait to ra_tt directly seem to be much easier,
6//! we deliberately duplicate the ra_tt struct with #[serde(with = "XXDef")]
7//! for separation of code responsibility.
8
9use ra_tt::{
10 Delimiter, DelimiterKind, Ident, Leaf, Literal, Punct, SmolStr, Spacing, Subtree, TokenId,
11 TokenTree,
12};
13use serde::{Deserialize, Serialize};
14use std::path::PathBuf;
15
16#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
17pub struct ListMacrosTask {
18 pub lib: PathBuf,
19}
20
21#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
22pub enum ProcMacroKind {
23 CustomDerive,
24 FuncLike,
25 Attr,
26}
27
28#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
29pub struct ListMacrosResult {
30 pub macros: Vec<(String, ProcMacroKind)>,
31}
32
33#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
34pub struct ExpansionTask {
35 /// Argument of macro call.
36 ///
37 /// In custom derive that would be a struct or enum; in attribute-like macro - underlying
38 /// item; in function-like macro - the macro body.
39 #[serde(with = "SubtreeDef")]
40 pub macro_body: Subtree,
41
42 /// Names of macros to expand.
43 ///
44 /// In custom derive those are names of derived traits (`Serialize`, `Getters`, etc.). In
45 /// attribute-like and functiona-like macros - single name of macro itself (`show_streams`).
46 pub macro_name: String,
47
48 /// Possible attributes for the attribute-like macros.
49 #[serde(with = "opt_subtree_def")]
50 pub attributes: Option<Subtree>,
51
52 pub lib: PathBuf,
53}
54
55#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
56pub struct ExpansionResult {
57 #[serde(with = "SubtreeDef")]
58 pub expansion: Subtree,
59}
60
61#[derive(Serialize, Deserialize)]
62#[serde(remote = "DelimiterKind")]
63enum DelimiterKindDef {
64 Parenthesis,
65 Brace,
66 Bracket,
67}
68
69#[derive(Serialize, Deserialize)]
70#[serde(remote = "TokenId")]
71struct TokenIdDef(u32);
72
73#[derive(Serialize, Deserialize)]
74#[serde(remote = "Delimiter")]
75struct DelimiterDef {
76 #[serde(with = "TokenIdDef")]
77 pub id: TokenId,
78 #[serde(with = "DelimiterKindDef")]
79 pub kind: DelimiterKind,
80}
81
82#[derive(Serialize, Deserialize)]
83#[serde(remote = "Subtree")]
84struct SubtreeDef {
85 #[serde(default, with = "opt_delimiter_def")]
86 pub delimiter: Option<Delimiter>,
87 #[serde(with = "vec_token_tree")]
88 pub token_trees: Vec<TokenTree>,
89}
90
91#[derive(Serialize, Deserialize)]
92#[serde(remote = "TokenTree")]
93enum TokenTreeDef {
94 #[serde(with = "LeafDef")]
95 Leaf(Leaf),
96 #[serde(with = "SubtreeDef")]
97 Subtree(Subtree),
98}
99
100#[derive(Serialize, Deserialize)]
101#[serde(remote = "Leaf")]
102enum LeafDef {
103 #[serde(with = "LiteralDef")]
104 Literal(Literal),
105 #[serde(with = "PunctDef")]
106 Punct(Punct),
107 #[serde(with = "IdentDef")]
108 Ident(Ident),
109}
110
111#[derive(Serialize, Deserialize)]
112#[serde(remote = "Literal")]
113struct LiteralDef {
114 pub text: SmolStr,
115 #[serde(with = "TokenIdDef")]
116 pub id: TokenId,
117}
118
119#[derive(Serialize, Deserialize)]
120#[serde(remote = "Punct")]
121struct PunctDef {
122 pub char: char,
123 #[serde(with = "SpacingDef")]
124 pub spacing: Spacing,
125 #[serde(with = "TokenIdDef")]
126 pub id: TokenId,
127}
128
129#[derive(Serialize, Deserialize)]
130#[serde(remote = "Spacing")]
131enum SpacingDef {
132 Alone,
133 Joint,
134}
135
136#[derive(Serialize, Deserialize)]
137#[serde(remote = "Ident")]
138struct IdentDef {
139 pub text: SmolStr,
140 #[serde(with = "TokenIdDef")]
141 pub id: TokenId,
142}
143
144mod opt_delimiter_def {
145 use super::{Delimiter, DelimiterDef};
146 use serde::{Deserialize, Deserializer, Serialize, Serializer};
147
148 pub fn serialize<S>(value: &Option<Delimiter>, serializer: S) -> Result<S::Ok, S::Error>
149 where
150 S: Serializer,
151 {
152 #[derive(Serialize)]
153 struct Helper<'a>(#[serde(with = "DelimiterDef")] &'a Delimiter);
154 value.as_ref().map(Helper).serialize(serializer)
155 }
156
157 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Delimiter>, D::Error>
158 where
159 D: Deserializer<'de>,
160 {
161 #[derive(Deserialize)]
162 struct Helper(#[serde(with = "DelimiterDef")] Delimiter);
163 let helper = Option::deserialize(deserializer)?;
164 Ok(helper.map(|Helper(external)| external))
165 }
166}
167
168mod opt_subtree_def {
169 use super::{Subtree, SubtreeDef};
170 use serde::{Deserialize, Deserializer, Serialize, Serializer};
171
172 pub fn serialize<S>(value: &Option<Subtree>, serializer: S) -> Result<S::Ok, S::Error>
173 where
174 S: Serializer,
175 {
176 #[derive(Serialize)]
177 struct Helper<'a>(#[serde(with = "SubtreeDef")] &'a Subtree);
178 value.as_ref().map(Helper).serialize(serializer)
179 }
180
181 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Subtree>, D::Error>
182 where
183 D: Deserializer<'de>,
184 {
185 #[derive(Deserialize)]
186 struct Helper(#[serde(with = "SubtreeDef")] Subtree);
187 let helper = Option::deserialize(deserializer)?;
188 Ok(helper.map(|Helper(external)| external))
189 }
190}
191
192mod vec_token_tree {
193 use super::{TokenTree, TokenTreeDef};
194 use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer};
195
196 pub fn serialize<S>(value: &Vec<TokenTree>, serializer: S) -> Result<S::Ok, S::Error>
197 where
198 S: Serializer,
199 {
200 #[derive(Serialize)]
201 struct Helper<'a>(#[serde(with = "TokenTreeDef")] &'a TokenTree);
202
203 let items: Vec<_> = value.iter().map(Helper).collect();
204 let mut seq = serializer.serialize_seq(Some(items.len()))?;
205 for element in items {
206 seq.serialize_element(&element)?;
207 }
208 seq.end()
209 }
210
211 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<TokenTree>, D::Error>
212 where
213 D: Deserializer<'de>,
214 {
215 #[derive(Deserialize)]
216 struct Helper(#[serde(with = "TokenTreeDef")] TokenTree);
217
218 let helper = Vec::deserialize(deserializer)?;
219 Ok(helper.into_iter().map(|Helper(external)| external).collect())
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 fn fixture_token_tree() -> Subtree {
228 let mut subtree = Subtree::default();
229 subtree
230 .token_trees
231 .push(TokenTree::Leaf(Ident { text: "struct".into(), id: TokenId(0) }.into()));
232 subtree
233 .token_trees
234 .push(TokenTree::Leaf(Ident { text: "Foo".into(), id: TokenId(1) }.into()));
235 subtree.token_trees.push(TokenTree::Subtree(
236 Subtree {
237 delimiter: Some(Delimiter { id: TokenId(2), kind: DelimiterKind::Brace }),
238 token_trees: vec![],
239 }
240 .into(),
241 ));
242 subtree
243 }
244
245 #[test]
246 fn test_proc_macro_rpc_works() {
247 let tt = fixture_token_tree();
248 let task = ExpansionTask {
249 macro_body: tt.clone(),
250 macro_name: Default::default(),
251 attributes: None,
252 lib: Default::default(),
253 };
254
255 let json = serde_json::to_string(&task).unwrap();
256 let back: ExpansionTask = serde_json::from_str(&json).unwrap();
257
258 assert_eq!(task.macro_body, back.macro_body);
259
260 let result = ExpansionResult { expansion: tt.clone() };
261 let json = serde_json::to_string(&result).unwrap();
262 let back: ExpansionResult = serde_json::from_str(&json).unwrap();
263
264 assert_eq!(result, back);
265 }
266}
diff --git a/crates/ra_prof/src/lib.rs b/crates/ra_prof/src/lib.rs
index 9e167db96..00ea3a9b0 100644
--- a/crates/ra_prof/src/lib.rs
+++ b/crates/ra_prof/src/lib.rs
@@ -339,6 +339,14 @@ pub fn print_backtrace() {
339 let bt = backtrace::Backtrace::new(); 339 let bt = backtrace::Backtrace::new();
340 eprintln!("{:?}", bt); 340 eprintln!("{:?}", bt);
341} 341}
342#[cfg(not(feature = "backtrace"))]
343pub fn print_backtrace() {
344 eprintln!(
345 r#"enable the backtrace feature:
346 ra_prof = {{ path = "../ra_prof", features = [ "backtrace"] }}
347"#
348 );
349}
342 350
343thread_local!(static IN_SCOPE: RefCell<bool> = RefCell::new(false)); 351thread_local!(static IN_SCOPE: RefCell<bool> = RefCell::new(false));
344 352
diff --git a/crates/ra_project_model/Cargo.toml b/crates/ra_project_model/Cargo.toml
index cdcdd63c9..b10644b4b 100644
--- a/crates/ra_project_model/Cargo.toml
+++ b/crates/ra_project_model/Cargo.toml
@@ -16,7 +16,6 @@ cargo_metadata = "0.9.1"
16ra_arena = { path = "../ra_arena" } 16ra_arena = { path = "../ra_arena" }
17ra_db = { path = "../ra_db" } 17ra_db = { path = "../ra_db" }
18ra_cfg = { path = "../ra_cfg" } 18ra_cfg = { path = "../ra_cfg" }
19ra_cargo_watch = { path = "../ra_cargo_watch" }
20ra_proc_macro = { path = "../ra_proc_macro" } 19ra_proc_macro = { path = "../ra_proc_macro" }
21 20
22serde = { version = "1.0.104", features = ["derive"] } 21serde = { version = "1.0.104", features = ["derive"] }
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs
index 291594e2a..f4fd6ad28 100644
--- a/crates/ra_project_model/src/cargo_workspace.rs
+++ b/crates/ra_project_model/src/cargo_workspace.rs
@@ -1,14 +1,16 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use std::{ 3use std::{
4 env,
5 ffi::OsStr,
4 ops, 6 ops,
5 path::{Path, PathBuf}, 7 path::{Path, PathBuf},
8 process::Command,
6}; 9};
7 10
8use anyhow::{Context, Result}; 11use anyhow::{Context, Result};
9use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId}; 12use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId};
10use ra_arena::{Arena, Idx}; 13use ra_arena::{Arena, Idx};
11use ra_cargo_watch::run_cargo;
12use ra_db::Edition; 14use ra_db::Edition;
13use rustc_hash::FxHashMap; 15use rustc_hash::FxHashMap;
14use serde::Deserialize; 16use serde::Deserialize;
@@ -75,6 +77,7 @@ pub type Target = Idx<TargetData>;
75 77
76#[derive(Debug, Clone)] 78#[derive(Debug, Clone)]
77pub struct PackageData { 79pub struct PackageData {
80 pub id: String,
78 pub name: String, 81 pub name: String,
79 pub manifest: PathBuf, 82 pub manifest: PathBuf,
80 pub targets: Vec<Target>, 83 pub targets: Vec<Target>,
@@ -161,7 +164,7 @@ impl CargoWorkspace {
161 let mut out_dir_by_id = FxHashMap::default(); 164 let mut out_dir_by_id = FxHashMap::default();
162 let mut proc_macro_dylib_paths = FxHashMap::default(); 165 let mut proc_macro_dylib_paths = FxHashMap::default();
163 if cargo_features.load_out_dirs_from_check { 166 if cargo_features.load_out_dirs_from_check {
164 let resources = load_extern_resources(cargo_toml, cargo_features); 167 let resources = load_extern_resources(cargo_toml, cargo_features)?;
165 out_dir_by_id = resources.out_dirs; 168 out_dir_by_id = resources.out_dirs;
166 proc_macro_dylib_paths = resources.proc_dylib_paths; 169 proc_macro_dylib_paths = resources.proc_dylib_paths;
167 } 170 }
@@ -180,6 +183,7 @@ impl CargoWorkspace {
180 .with_context(|| format!("Failed to parse edition {}", edition))?; 183 .with_context(|| format!("Failed to parse edition {}", edition))?;
181 let pkg = packages.alloc(PackageData { 184 let pkg = packages.alloc(PackageData {
182 name, 185 name,
186 id: id.to_string(),
183 manifest: manifest_path, 187 manifest: manifest_path,
184 targets: Vec::new(), 188 targets: Vec::new(),
185 is_member, 189 is_member,
@@ -249,6 +253,18 @@ impl CargoWorkspace {
249 pub fn workspace_root(&self) -> &Path { 253 pub fn workspace_root(&self) -> &Path {
250 &self.workspace_root 254 &self.workspace_root
251 } 255 }
256
257 pub fn package_flag(&self, package: &PackageData) -> String {
258 if self.is_unique(&*package.name) {
259 package.name.clone()
260 } else {
261 package.id.clone()
262 }
263 }
264
265 fn is_unique(&self, name: &str) -> bool {
266 self.packages.iter().filter(|(_, v)| v.name == name).count() == 1
267 }
252} 268}
253 269
254#[derive(Debug, Clone, Default)] 270#[derive(Debug, Clone, Default)]
@@ -257,48 +273,61 @@ pub struct ExternResources {
257 proc_dylib_paths: FxHashMap<PackageId, PathBuf>, 273 proc_dylib_paths: FxHashMap<PackageId, PathBuf>,
258} 274}
259 275
260pub fn load_extern_resources(cargo_toml: &Path, cargo_features: &CargoFeatures) -> ExternResources { 276pub fn load_extern_resources(
261 let mut args: Vec<String> = vec![ 277 cargo_toml: &Path,
262 "check".to_string(), 278 cargo_features: &CargoFeatures,
263 "--message-format=json".to_string(), 279) -> Result<ExternResources> {
264 "--manifest-path".to_string(), 280 let mut cmd = Command::new(cargo_binary());
265 cargo_toml.display().to_string(), 281 cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml);
266 ];
267
268 if cargo_features.all_features { 282 if cargo_features.all_features {
269 args.push("--all-features".to_string()); 283 cmd.arg("--all-features");
270 } else if cargo_features.no_default_features { 284 } else if cargo_features.no_default_features {
271 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` 285 // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
272 // https://github.com/oli-obk/cargo_metadata/issues/79 286 // https://github.com/oli-obk/cargo_metadata/issues/79
273 args.push("--no-default-features".to_string()); 287 cmd.arg("--no-default-features");
274 } else { 288 } else {
275 args.extend(cargo_features.features.iter().cloned()); 289 cmd.args(&cargo_features.features);
276 } 290 }
277 291
278 let mut acc = ExternResources::default(); 292 let output = cmd.output()?;
279 let res = run_cargo(&args, cargo_toml.parent(), &mut |message| {
280 match message {
281 Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => {
282 acc.out_dirs.insert(package_id, out_dir);
283 }
284 293
285 Message::CompilerArtifact(message) => { 294 let mut res = ExternResources::default();
286 if message.target.kind.contains(&"proc-macro".to_string()) { 295
287 let package_id = message.package_id; 296 let stdout = String::from_utf8(output.stdout)?;
288 if let Some(filename) = message.filenames.get(0) { 297 for line in stdout.lines() {
289 acc.proc_dylib_paths.insert(package_id, filename.clone()); 298 if let Ok(message) = serde_json::from_str::<cargo_metadata::Message>(&line) {
299 match message {
300 Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => {
301 res.out_dirs.insert(package_id, out_dir);
302 }
303
304 Message::CompilerArtifact(message) => {
305 if message.target.kind.contains(&"proc-macro".to_string()) {
306 let package_id = message.package_id;
307 // Skip rmeta file
308 if let Some(filename) =
309 message.filenames.iter().filter(|name| is_dylib(name)).next()
310 {
311 res.proc_dylib_paths.insert(package_id, filename.clone());
312 }
290 } 313 }
291 } 314 }
315 Message::CompilerMessage(_) => (),
316 Message::Unknown => (),
292 } 317 }
293 Message::CompilerMessage(_) => (),
294 Message::Unknown => (),
295 } 318 }
296 true 319 }
297 }); 320 Ok(res)
321}
298 322
299 if let Err(err) = res { 323// FIXME: File a better way to know if it is a dylib
300 log::error!("Failed to load outdirs: {:?}", err); 324fn is_dylib(path: &Path) -> bool {
325 match path.extension().and_then(OsStr::to_str).map(|it| it.to_string().to_lowercase()) {
326 None => false,
327 Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
301 } 328 }
329}
302 330
303 acc 331fn cargo_binary() -> String {
332 env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())
304} 333}
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs
index 2304e00cf..b69cae234 100644
--- a/crates/ra_syntax/src/ast/edit.rs
+++ b/crates/ra_syntax/src/ast/edit.rs
@@ -46,14 +46,46 @@ impl ast::FnDef {
46 } 46 }
47} 47}
48 48
49fn make_multiline<N>(node: N) -> N
50where
51 N: AstNode + Clone,
52{
53 let l_curly = match node.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) {
54 Some(it) => it,
55 None => return node,
56 };
57 let sibling = match l_curly.next_sibling_or_token() {
58 Some(it) => it,
59 None => return node,
60 };
61 let existing_ws = match sibling.as_token() {
62 None => None,
63 Some(tok) if tok.kind() != WHITESPACE => None,
64 Some(ws) => {
65 if ws.text().contains('\n') {
66 return node;
67 }
68 Some(ws.clone())
69 }
70 };
71
72 let indent = leading_indent(node.syntax()).unwrap_or_default();
73 let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
74 let to_insert = iter::once(ws.ws().into());
75 match existing_ws {
76 None => node.insert_children(InsertPosition::After(l_curly), to_insert),
77 Some(ws) => node.replace_children(single_node(ws), to_insert),
78 }
79}
80
49impl ast::ItemList { 81impl ast::ItemList {
50 #[must_use] 82 #[must_use]
51 pub fn append_items(&self, items: impl Iterator<Item = ast::ImplItem>) -> ast::ItemList { 83 pub fn append_items(&self, items: impl IntoIterator<Item = ast::ImplItem>) -> ast::ItemList {
52 let mut res = self.clone(); 84 let mut res = self.clone();
53 if !self.syntax().text().contains_char('\n') { 85 if !self.syntax().text().contains_char('\n') {
54 res = res.make_multiline(); 86 res = make_multiline(res);
55 } 87 }
56 items.for_each(|it| res = res.append_item(it)); 88 items.into_iter().for_each(|it| res = res.append_item(it));
57 res 89 res
58 } 90 }
59 91
@@ -81,35 +113,6 @@ impl ast::ItemList {
81 fn l_curly(&self) -> Option<SyntaxElement> { 113 fn l_curly(&self) -> Option<SyntaxElement> {
82 self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) 114 self.syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
83 } 115 }
84
85 fn make_multiline(&self) -> ast::ItemList {
86 let l_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) {
87 Some(it) => it,
88 None => return self.clone(),
89 };
90 let sibling = match l_curly.next_sibling_or_token() {
91 Some(it) => it,
92 None => return self.clone(),
93 };
94 let existing_ws = match sibling.as_token() {
95 None => None,
96 Some(tok) if tok.kind() != WHITESPACE => None,
97 Some(ws) => {
98 if ws.text().contains('\n') {
99 return self.clone();
100 }
101 Some(ws.clone())
102 }
103 };
104
105 let indent = leading_indent(self.syntax()).unwrap_or_default();
106 let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
107 let to_insert = iter::once(ws.ws().into());
108 match existing_ws {
109 None => self.insert_children(InsertPosition::After(l_curly), to_insert),
110 Some(ws) => self.replace_children(single_node(ws), to_insert),
111 }
112 }
113} 116}
114 117
115impl ast::RecordFieldList { 118impl ast::RecordFieldList {
@@ -334,6 +337,80 @@ impl ast::UseTree {
334 } 337 }
335} 338}
336 339
340impl ast::MatchArmList {
341 #[must_use]
342 pub fn append_arms(&self, items: impl IntoIterator<Item = ast::MatchArm>) -> ast::MatchArmList {
343 let mut res = self.clone();
344 res = res.strip_if_only_whitespace();
345 if !res.syntax().text().contains_char('\n') {
346 res = make_multiline(res);
347 }
348 items.into_iter().for_each(|it| res = res.append_arm(it));
349 res
350 }
351
352 fn strip_if_only_whitespace(&self) -> ast::MatchArmList {
353 let mut iter = self.syntax().children_with_tokens().skip_while(|it| it.kind() != T!['{']);
354 iter.next(); // Eat the curly
355 let mut inner = iter.take_while(|it| it.kind() != T!['}']);
356 if !inner.clone().all(|it| it.kind() == WHITESPACE) {
357 return self.clone();
358 }
359 let start = match inner.next() {
360 Some(s) => s,
361 None => return self.clone(),
362 };
363 let end = match inner.last() {
364 Some(s) => s,
365 None => start.clone(),
366 };
367 self.replace_children(start..=end, &mut iter::empty())
368 }
369
370 #[must_use]
371 pub fn remove_placeholder(&self) -> ast::MatchArmList {
372 let placeholder =
373 self.arms().find(|arm| matches!(arm.pat(), Some(ast::Pat::PlaceholderPat(_))));
374 if let Some(placeholder) = placeholder {
375 self.remove_arm(&placeholder)
376 } else {
377 self.clone()
378 }
379 }
380
381 #[must_use]
382 fn remove_arm(&self, arm: &ast::MatchArm) -> ast::MatchArmList {
383 let start = arm.syntax().clone();
384 let end = if let Some(comma) = start
385 .siblings_with_tokens(Direction::Next)
386 .skip(1)
387 .skip_while(|it| it.kind().is_trivia())
388 .next()
389 .filter(|it| it.kind() == T![,])
390 {
391 comma
392 } else {
393 start.clone().into()
394 };
395 self.replace_children(start.into()..=end, None)
396 }
397
398 #[must_use]
399 pub fn append_arm(&self, item: ast::MatchArm) -> ast::MatchArmList {
400 let r_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['}']) {
401 Some(t) => t,
402 None => return self.clone(),
403 };
404 let position = InsertPosition::Before(r_curly.into());
405 let arm_ws = tokens::WsBuilder::new(" ");
406 let match_indent = &leading_indent(self.syntax()).unwrap_or_default();
407 let match_ws = tokens::WsBuilder::new(&format!("\n{}", match_indent));
408 let to_insert: ArrayVec<[SyntaxElement; 3]> =
409 [arm_ws.ws().into(), item.syntax().clone().into(), match_ws.ws().into()].into();
410 self.insert_children(position, to_insert)
411 }
412}
413
337#[must_use] 414#[must_use]
338pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { 415pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
339 N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() 416 N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap()
diff --git a/crates/ra_syntax/src/parsing/text_tree_sink.rs b/crates/ra_syntax/src/parsing/text_tree_sink.rs
index dd202601d..87bb21cd9 100644
--- a/crates/ra_syntax/src/parsing/text_tree_sink.rs
+++ b/crates/ra_syntax/src/parsing/text_tree_sink.rs
@@ -149,10 +149,21 @@ fn n_attached_trivias<'a>(
149 MACRO_CALL | CONST_DEF | TYPE_ALIAS_DEF | STRUCT_DEF | ENUM_DEF | ENUM_VARIANT | FN_DEF 149 MACRO_CALL | CONST_DEF | TYPE_ALIAS_DEF | STRUCT_DEF | ENUM_DEF | ENUM_VARIANT | FN_DEF
150 | TRAIT_DEF | MODULE | RECORD_FIELD_DEF | STATIC_DEF => { 150 | TRAIT_DEF | MODULE | RECORD_FIELD_DEF | STATIC_DEF => {
151 let mut res = 0; 151 let mut res = 0;
152 for (i, (kind, text)) in trivias.enumerate() { 152 let mut trivias = trivias.enumerate().peekable();
153
154 while let Some((i, (kind, text))) = trivias.next() {
153 match kind { 155 match kind {
154 WHITESPACE => { 156 WHITESPACE => {
155 if text.contains("\n\n") { 157 if text.contains("\n\n") {
158 // we check whether the next token is a doc-comment
159 // and skip the whitespace in this case
160 if let Some((peek_kind, peek_text)) =
161 trivias.peek().map(|(_, pair)| pair)
162 {
163 if *peek_kind == COMMENT && peek_text.starts_with("///") {
164 continue;
165 }
166 }
156 break; 167 break;
157 } 168 }
158 } 169 }
diff --git a/crates/ra_syntax/test_data/parser/ok/0065_comment_newline.rs b/crates/ra_syntax/test_data/parser/ok/0065_comment_newline.rs
new file mode 100644
index 000000000..1fafe216b
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/ok/0065_comment_newline.rs
@@ -0,0 +1,3 @@
1/// Example
2
3fn test() {}
diff --git a/crates/ra_syntax/test_data/parser/ok/0065_comment_newline.txt b/crates/ra_syntax/test_data/parser/ok/0065_comment_newline.txt
new file mode 100644
index 000000000..91d0c3736
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/ok/0065_comment_newline.txt
@@ -0,0 +1,17 @@
1SOURCE_FILE@[0; 26)
2 FN_DEF@[0; 25)
3 COMMENT@[0; 11) "/// Example"
4 WHITESPACE@[11; 13) "\n\n"
5 FN_KW@[13; 15) "fn"
6 WHITESPACE@[15; 16) " "
7 NAME@[16; 20)
8 IDENT@[16; 20) "test"
9 PARAM_LIST@[20; 22)
10 L_PAREN@[20; 21) "("
11 R_PAREN@[21; 22) ")"
12 WHITESPACE@[22; 23) " "
13 BLOCK_EXPR@[23; 25)
14 BLOCK@[23; 25)
15 L_CURLY@[23; 24) "{"
16 R_CURLY@[24; 25) "}"
17 WHITESPACE@[25; 26) "\n"
diff --git a/crates/ra_tt/src/lib.rs b/crates/ra_tt/src/lib.rs
index 1015ce0a6..bd484aa30 100644
--- a/crates/ra_tt/src/lib.rs
+++ b/crates/ra_tt/src/lib.rs
@@ -189,7 +189,12 @@ impl Subtree {
189pub mod buffer; 189pub mod buffer;
190 190
191#[derive(Debug, PartialEq, Eq)] 191#[derive(Debug, PartialEq, Eq)]
192pub enum ExpansionError {} 192pub enum ExpansionError {
193 IOError(String),
194 JsonError(String),
195 Unknown(String),
196 ExpansionError(String),
197}
193 198
194pub trait TokenExpander: Debug + Send + Sync + RefUnwindSafe { 199pub trait TokenExpander: Debug + Send + Sync + RefUnwindSafe {
195 fn expand(&self, subtree: &Subtree, attrs: Option<&Subtree>) 200 fn expand(&self, subtree: &Subtree, attrs: Option<&Subtree>)
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 8fe6799d2..f5f773432 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -33,7 +33,7 @@ threadpool = "1.7.1"
33stdx = { path = "../stdx" } 33stdx = { path = "../stdx" }
34 34
35lsp-server = "0.3.1" 35lsp-server = "0.3.1"
36ra_cargo_watch = { path = "../ra_cargo_watch" } 36ra_flycheck = { path = "../ra_flycheck" }
37ra_ide = { path = "../ra_ide" } 37ra_ide = { path = "../ra_ide" }
38ra_prof = { path = "../ra_prof" } 38ra_prof = { path = "../ra_prof" }
39ra_project_model = { path = "../ra_project_model" } 39ra_project_model = { path = "../ra_project_model" }
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs
index f87bdcec5..942c30328 100644
--- a/crates/rust-analyzer/src/cargo_target_spec.rs
+++ b/crates/rust-analyzer/src/cargo_target_spec.rs
@@ -77,7 +77,7 @@ impl CargoTargetSpec {
77 ProjectWorkspace::Cargo { cargo, .. } => { 77 ProjectWorkspace::Cargo { cargo, .. } => {
78 let tgt = cargo.target_by_root(&path)?; 78 let tgt = cargo.target_by_root(&path)?;
79 Some(CargoTargetSpec { 79 Some(CargoTargetSpec {
80 package: cargo[cargo[tgt].package].name.clone(), 80 package: cargo.package_flag(&cargo[cargo[tgt].package]),
81 target: cargo[tgt].name.clone(), 81 target: cargo[tgt].name.clone(),
82 target_kind: cargo[tgt].kind, 82 target_kind: cargo[tgt].kind,
83 }) 83 })
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs
index 7164b0ade..7667873d5 100644
--- a/crates/rust-analyzer/src/cli/analysis_bench.rs
+++ b/crates/rust-analyzer/src/cli/analysis_bench.rs
@@ -12,7 +12,7 @@ use ra_db::{
12 salsa::{Database, Durability}, 12 salsa::{Database, Durability},
13 FileId, SourceDatabaseExt, 13 FileId, SourceDatabaseExt,
14}; 14};
15use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CompletionOptions, FilePosition, LineCol}; 15use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CompletionConfig, FilePosition, LineCol};
16 16
17use crate::cli::{load_cargo::load_cargo, Verbosity}; 17use crate::cli::{load_cargo::load_cargo, Verbosity};
18 18
@@ -102,7 +102,7 @@ pub fn analysis_bench(
102 let file_position = FilePosition { file_id, offset }; 102 let file_position = FilePosition { file_id, offset };
103 103
104 if is_completion { 104 if is_completion {
105 let options = CompletionOptions::default(); 105 let options = CompletionConfig::default();
106 let res = do_work(&mut host, file_id, |analysis| { 106 let res = do_work(&mut host, file_id, |analysis| {
107 analysis.completions(file_position, &options) 107 analysis.completions(file_position, &options)
108 }); 108 });
diff --git a/crates/rust-analyzer/src/conv.rs b/crates/rust-analyzer/src/conv.rs
index 6edc03fe0..e8dc953c3 100644
--- a/crates/rust-analyzer/src/conv.rs
+++ b/crates/rust-analyzer/src/conv.rs
@@ -579,7 +579,7 @@ impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<Vec<NavigationTarget>>)
579 .into_iter() 579 .into_iter()
580 .map(|nav| (file_id, RangeInfo::new(range, nav))) 580 .map(|nav| (file_id, RangeInfo::new(range, nav)))
581 .try_conv_with_to_vec(world)?; 581 .try_conv_with_to_vec(world)?;
582 if world.options.supports_location_link { 582 if world.config.supports_location_link {
583 Ok(links.into()) 583 Ok(links.into())
584 } else { 584 } else {
585 let locations: Vec<Location> = links 585 let locations: Vec<Location> = links
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 7825b0077..79dc03de4 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -14,14 +14,15 @@ use std::{
14 time::{Duration, Instant}, 14 time::{Duration, Instant},
15}; 15};
16 16
17use crossbeam_channel::{select, unbounded, RecvError, Sender}; 17use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
18use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; 18use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
19use lsp_types::{ 19use lsp_types::{
20 ClientCapabilities, NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, 20 ClientCapabilities, NumberOrString, TextDocumentClientCapabilities, WorkDoneProgress,
21 WorkDoneProgressCreateParams, WorkDoneProgressEnd, WorkDoneProgressReport, 21 WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd,
22 WorkDoneProgressReport,
22}; 23};
23use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckTask}; 24use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckConfig, CheckTask};
24use ra_ide::{Canceled, FileId, InlayHintsOptions, LibraryData, SourceRootId}; 25use ra_ide::{Canceled, FileId, InlayHintsConfig, LibraryData, SourceRootId};
25use ra_prof::profile; 26use ra_prof::profile;
26use ra_vfs::{VfsFile, VfsTask, Watch}; 27use ra_vfs::{VfsFile, VfsTask, Watch};
27use relative_path::RelativePathBuf; 28use relative_path::RelativePathBuf;
@@ -37,9 +38,10 @@ use crate::{
37 subscriptions::Subscriptions, 38 subscriptions::Subscriptions,
38 }, 39 },
39 req, 40 req,
40 world::{Options, WorldSnapshot, WorldState}, 41 world::{Config, WorldSnapshot, WorldState},
41 Result, ServerConfig, 42 Result, ServerConfig,
42}; 43};
44use req::ConfigurationParams;
43 45
44#[derive(Debug)] 46#[derive(Debug)]
45pub struct LspError { 47pub struct LspError {
@@ -63,6 +65,57 @@ impl fmt::Display for LspError {
63 65
64impl Error for LspError {} 66impl Error for LspError {}
65 67
68fn get_feature_flags(config: &ServerConfig, connection: &Connection) -> FeatureFlags {
69 let mut ff = FeatureFlags::default();
70 for (flag, &value) in &config.feature_flags {
71 if ff.set(flag.as_str(), value).is_err() {
72 log::error!("unknown feature flag: {:?}", flag);
73 show_message(
74 req::MessageType::Error,
75 format!("unknown feature flag: {:?}", flag),
76 &connection.sender,
77 );
78 }
79 }
80 log::info!("feature_flags: {:#?}", ff);
81 ff
82}
83
84fn get_config(
85 config: &ServerConfig,
86 text_document_caps: Option<&TextDocumentClientCapabilities>,
87) -> Config {
88 Config {
89 publish_decorations: config.publish_decorations,
90 supports_location_link: text_document_caps
91 .and_then(|it| it.definition)
92 .and_then(|it| it.link_support)
93 .unwrap_or(false),
94 line_folding_only: text_document_caps
95 .and_then(|it| it.folding_range.as_ref())
96 .and_then(|it| it.line_folding_only)
97 .unwrap_or(false),
98 inlay_hints: InlayHintsConfig {
99 type_hints: config.inlay_hints_type,
100 parameter_hints: config.inlay_hints_parameter,
101 chaining_hints: config.inlay_hints_chaining,
102 max_length: config.inlay_hints_max_length,
103 },
104 check: if config.cargo_watch_enable {
105 Some(CheckConfig {
106 args: config.cargo_watch_args.clone(),
107 command: config.cargo_watch_command.clone(),
108 all_targets: config.cargo_watch_all_targets,
109 })
110 } else {
111 None
112 },
113 rustfmt_args: config.rustfmt_args.clone(),
114 vscode_lldb: config.vscode_lldb,
115 proc_macro_srv: None, // FIXME: get this from config
116 }
117}
118
66pub fn main_loop( 119pub fn main_loop(
67 ws_roots: Vec<PathBuf>, 120 ws_roots: Vec<PathBuf>,
68 client_caps: ClientCapabilities, 121 client_caps: ClientCapabilities,
@@ -90,23 +143,10 @@ pub fn main_loop(
90 SetThreadPriority(thread, thread_priority_above_normal); 143 SetThreadPriority(thread, thread_priority_above_normal);
91 } 144 }
92 145
146 let text_document_caps = client_caps.text_document.as_ref();
93 let mut loop_state = LoopState::default(); 147 let mut loop_state = LoopState::default();
94 let mut world_state = { 148 let mut world_state = {
95 let feature_flags = { 149 let feature_flags = get_feature_flags(&config, &connection);
96 let mut ff = FeatureFlags::default();
97 for (flag, value) in config.feature_flags {
98 if ff.set(flag.as_str(), value).is_err() {
99 log::error!("unknown feature flag: {:?}", flag);
100 show_message(
101 req::MessageType::Error,
102 format!("unknown feature flag: {:?}", flag),
103 &connection.sender,
104 );
105 }
106 }
107 ff
108 };
109 log::info!("feature_flags: {:#?}", feature_flags);
110 150
111 // FIXME: support dynamic workspace loading. 151 // FIXME: support dynamic workspace loading.
112 let workspaces = { 152 let workspaces = {
@@ -168,46 +208,20 @@ pub fn main_loop(
168 connection.sender.send(request.into()).unwrap(); 208 connection.sender.send(request.into()).unwrap();
169 } 209 }
170 210
171 let options = {
172 let text_document_caps = client_caps.text_document.as_ref();
173 Options {
174 publish_decorations: config.publish_decorations,
175 supports_location_link: text_document_caps
176 .and_then(|it| it.definition)
177 .and_then(|it| it.link_support)
178 .unwrap_or(false),
179 line_folding_only: text_document_caps
180 .and_then(|it| it.folding_range.as_ref())
181 .and_then(|it| it.line_folding_only)
182 .unwrap_or(false),
183 inlay_hints: InlayHintsOptions {
184 type_hints: config.inlay_hints_type,
185 parameter_hints: config.inlay_hints_parameter,
186 chaining_hints: config.inlay_hints_chaining,
187 max_length: config.inlay_hints_max_length,
188 },
189 cargo_watch: CheckOptions {
190 enable: config.cargo_watch_enable,
191 args: config.cargo_watch_args,
192 command: config.cargo_watch_command,
193 all_targets: config.cargo_watch_all_targets,
194 },
195 rustfmt_args: config.rustfmt_args,
196 vscode_lldb: config.vscode_lldb,
197 }
198 };
199
200 WorldState::new( 211 WorldState::new(
201 ws_roots, 212 ws_roots,
202 workspaces, 213 workspaces,
203 config.lru_capacity, 214 config.lru_capacity,
204 &globs, 215 &globs,
205 Watch(!config.use_client_watching), 216 Watch(!config.use_client_watching),
206 options, 217 get_config(&config, text_document_caps),
207 feature_flags, 218 feature_flags,
208 ) 219 )
209 }; 220 };
210 221
222 loop_state.roots_total = world_state.vfs.read().n_roots();
223 loop_state.roots_scanned = 0;
224
211 let pool = ThreadPool::default(); 225 let pool = ThreadPool::default();
212 let (task_sender, task_receiver) = unbounded::<Task>(); 226 let (task_sender, task_receiver) = unbounded::<Task>();
213 let (libdata_sender, libdata_receiver) = unbounded::<LibraryData>(); 227 let (libdata_sender, libdata_receiver) = unbounded::<LibraryData>();
@@ -229,7 +243,7 @@ pub fn main_loop(
229 Err(RecvError) => return Err("vfs died".into()), 243 Err(RecvError) => return Err("vfs died".into()),
230 }, 244 },
231 recv(libdata_receiver) -> data => Event::Lib(data.unwrap()), 245 recv(libdata_receiver) -> data => Event::Lib(data.unwrap()),
232 recv(world_state.check_watcher.task_recv) -> task => match task { 246 recv(world_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task {
233 Ok(task) => Event::CheckWatcher(task), 247 Ok(task) => Event::CheckWatcher(task),
234 Err(RecvError) => return Err("check watcher died".into()), 248 Err(RecvError) => return Err("check watcher died".into()),
235 } 249 }
@@ -244,6 +258,7 @@ pub fn main_loop(
244 &task_sender, 258 &task_sender,
245 &libdata_sender, 259 &libdata_sender,
246 &connection, 260 &connection,
261 text_document_caps,
247 &mut world_state, 262 &mut world_state,
248 &mut loop_state, 263 &mut loop_state,
249 event, 264 event,
@@ -333,7 +348,10 @@ struct LoopState {
333 in_flight_libraries: usize, 348 in_flight_libraries: usize,
334 pending_libraries: Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>, 349 pending_libraries: Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>,
335 workspace_loaded: bool, 350 workspace_loaded: bool,
336 roots_scanned_progress: Option<usize>, 351 roots_progress_reported: Option<usize>,
352 roots_scanned: usize,
353 roots_total: usize,
354 configuration_request_id: Option<RequestId>,
337} 355}
338 356
339impl LoopState { 357impl LoopState {
@@ -351,6 +369,7 @@ fn loop_turn(
351 task_sender: &Sender<Task>, 369 task_sender: &Sender<Task>,
352 libdata_sender: &Sender<LibraryData>, 370 libdata_sender: &Sender<LibraryData>,
353 connection: &Connection, 371 connection: &Connection,
372 text_document_caps: Option<&TextDocumentClientCapabilities>,
354 world_state: &mut WorldState, 373 world_state: &mut WorldState,
355 loop_state: &mut LoopState, 374 loop_state: &mut LoopState,
356 event: Event, 375 event: Event,
@@ -377,6 +396,7 @@ fn loop_turn(
377 world_state.add_lib(lib); 396 world_state.add_lib(lib);
378 world_state.maybe_collect_garbage(); 397 world_state.maybe_collect_garbage();
379 loop_state.in_flight_libraries -= 1; 398 loop_state.in_flight_libraries -= 1;
399 loop_state.roots_scanned += 1;
380 } 400 }
381 Event::CheckWatcher(task) => on_check_task(task, world_state, task_sender)?, 401 Event::CheckWatcher(task) => on_check_task(task, world_state, task_sender)?,
382 Event::Msg(msg) => match msg { 402 Event::Msg(msg) => match msg {
@@ -390,25 +410,53 @@ fn loop_turn(
390 req, 410 req,
391 )?, 411 )?,
392 Message::Notification(not) => { 412 Message::Notification(not) => {
393 on_notification( 413 on_notification(&connection.sender, world_state, loop_state, not)?;
394 &connection.sender,
395 world_state,
396 &mut loop_state.pending_requests,
397 &mut loop_state.subscriptions,
398 not,
399 )?;
400 } 414 }
401 Message::Response(resp) => { 415 Message::Response(resp) => {
402 let removed = loop_state.pending_responses.remove(&resp.id); 416 let removed = loop_state.pending_responses.remove(&resp.id);
403 if !removed { 417 if !removed {
404 log::error!("unexpected response: {:?}", resp) 418 log::error!("unexpected response: {:?}", resp)
405 } 419 }
420
421 if Some(&resp.id) == loop_state.configuration_request_id.as_ref() {
422 loop_state.configuration_request_id = None;
423 log::debug!("config update response: '{:?}", resp);
424 let Response { error, result, .. } = resp;
425
426 match (
427 error,
428 result.map(|result| serde_json::from_value::<Vec<ServerConfig>>(result)),
429 ) {
430 (Some(err), _) => {
431 log::error!("failed to fetch the server settings: {:?}", err)
432 }
433 (None, Some(Ok(new_config))) => {
434 let new_config = new_config
435 .first()
436 .expect(
437 "the client is expected to always send a non-empty config data",
438 )
439 .to_owned();
440 world_state.update_configuration(
441 new_config.lru_capacity,
442 get_config(&new_config, text_document_caps),
443 get_feature_flags(&new_config, connection),
444 );
445 }
446 (None, Some(Err(e))) => {
447 log::error!("failed to parse client config response: {}", e)
448 }
449 (None, None) => {
450 log::error!("received empty server settings response from the client")
451 }
452 }
453 }
406 } 454 }
407 }, 455 },
408 }; 456 };
409 457
410 let mut state_changed = false; 458 let mut state_changed = false;
411 if let Some(changes) = world_state.process_changes() { 459 if let Some(changes) = world_state.process_changes(&mut loop_state.roots_scanned) {
412 state_changed = true; 460 state_changed = true;
413 loop_state.pending_libraries.extend(changes); 461 loop_state.pending_libraries.extend(changes);
414 } 462 }
@@ -427,28 +475,34 @@ fn loop_turn(
427 }); 475 });
428 } 476 }
429 477
478 let show_progress = !loop_state.workspace_loaded
479 && world_state.feature_flags.get("notifications.workspace-loaded");
480
430 if !loop_state.workspace_loaded 481 if !loop_state.workspace_loaded
431 && world_state.roots_to_scan == 0 482 && loop_state.roots_scanned == loop_state.roots_total
432 && loop_state.pending_libraries.is_empty() 483 && loop_state.pending_libraries.is_empty()
433 && loop_state.in_flight_libraries == 0 484 && loop_state.in_flight_libraries == 0
434 { 485 {
435 loop_state.workspace_loaded = true; 486 loop_state.workspace_loaded = true;
436 world_state.check_watcher.update(); 487 if let Some(flycheck) = &world_state.flycheck {
488 flycheck.update();
489 }
437 pool.execute({ 490 pool.execute({
438 let subs = loop_state.subscriptions.subscriptions(); 491 let subs = loop_state.subscriptions.subscriptions();
439 let snap = world_state.snapshot(); 492 let snap = world_state.snapshot();
440 move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ()) 493 move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ())
441 }); 494 });
442 send_startup_progress(&connection.sender, loop_state, world_state); 495 }
443 } else if !loop_state.workspace_loaded { 496
444 send_startup_progress(&connection.sender, loop_state, world_state); 497 if show_progress {
498 send_startup_progress(&connection.sender, loop_state);
445 } 499 }
446 500
447 if state_changed { 501 if state_changed {
448 update_file_notifications_on_threadpool( 502 update_file_notifications_on_threadpool(
449 pool, 503 pool,
450 world_state.snapshot(), 504 world_state.snapshot(),
451 world_state.options.publish_decorations, 505 world_state.config.publish_decorations,
452 task_sender.clone(), 506 task_sender.clone(),
453 loop_state.subscriptions.subscriptions(), 507 loop_state.subscriptions.subscriptions(),
454 ) 508 )
@@ -556,8 +610,7 @@ fn on_request(
556fn on_notification( 610fn on_notification(
557 msg_sender: &Sender<Message>, 611 msg_sender: &Sender<Message>,
558 state: &mut WorldState, 612 state: &mut WorldState,
559 pending_requests: &mut PendingRequests, 613 loop_state: &mut LoopState,
560 subs: &mut Subscriptions,
561 not: Notification, 614 not: Notification,
562) -> Result<()> { 615) -> Result<()> {
563 let not = match notification_cast::<req::Cancel>(not) { 616 let not = match notification_cast::<req::Cancel>(not) {
@@ -566,7 +619,7 @@ fn on_notification(
566 NumberOrString::Number(id) => id.into(), 619 NumberOrString::Number(id) => id.into(),
567 NumberOrString::String(id) => id.into(), 620 NumberOrString::String(id) => id.into(),
568 }; 621 };
569 if pending_requests.cancel(&id) { 622 if loop_state.pending_requests.cancel(&id) {
570 let response = Response::new_err( 623 let response = Response::new_err(
571 id, 624 id,
572 ErrorCode::RequestCanceled as i32, 625 ErrorCode::RequestCanceled as i32,
@@ -585,7 +638,7 @@ fn on_notification(
585 if let Some(file_id) = 638 if let Some(file_id) =
586 state.vfs.write().add_file_overlay(&path, params.text_document.text) 639 state.vfs.write().add_file_overlay(&path, params.text_document.text)
587 { 640 {
588 subs.add_sub(FileId(file_id.0)); 641 loop_state.subscriptions.add_sub(FileId(file_id.0));
589 } 642 }
590 return Ok(()); 643 return Ok(());
591 } 644 }
@@ -604,7 +657,9 @@ fn on_notification(
604 }; 657 };
605 let not = match notification_cast::<req::DidSaveTextDocument>(not) { 658 let not = match notification_cast::<req::DidSaveTextDocument>(not) {
606 Ok(_params) => { 659 Ok(_params) => {
607 state.check_watcher.update(); 660 if let Some(flycheck) = &state.flycheck {
661 flycheck.update();
662 }
608 return Ok(()); 663 return Ok(());
609 } 664 }
610 Err(not) => not, 665 Err(not) => not,
@@ -614,7 +669,7 @@ fn on_notification(
614 let uri = params.text_document.uri; 669 let uri = params.text_document.uri;
615 let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; 670 let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
616 if let Some(file_id) = state.vfs.write().remove_file_overlay(path.as_path()) { 671 if let Some(file_id) = state.vfs.write().remove_file_overlay(path.as_path()) {
617 subs.remove_sub(FileId(file_id.0)); 672 loop_state.subscriptions.remove_sub(FileId(file_id.0));
618 } 673 }
619 let params = 674 let params =
620 req::PublishDiagnosticsParams { uri, diagnostics: Vec::new(), version: None }; 675 req::PublishDiagnosticsParams { uri, diagnostics: Vec::new(), version: None };
@@ -625,7 +680,17 @@ fn on_notification(
625 Err(not) => not, 680 Err(not) => not,
626 }; 681 };
627 let not = match notification_cast::<req::DidChangeConfiguration>(not) { 682 let not = match notification_cast::<req::DidChangeConfiguration>(not) {
628 Ok(_params) => { 683 Ok(_) => {
684 // As stated in https://github.com/microsoft/language-server-protocol/issues/676,
685 // this notification's parameters should be ignored and the actual config queried separately.
686 let request_id = loop_state.next_request_id();
687 let request = request_new::<req::WorkspaceConfiguration>(
688 request_id.clone(),
689 ConfigurationParams::default(),
690 );
691 msg_sender.send(request.into())?;
692 loop_state.configuration_request_id = Some(request_id);
693
629 return Ok(()); 694 return Ok(());
630 } 695 }
631 Err(not) => not, 696 Err(not) => not,
@@ -706,21 +771,13 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state:
706 } 771 }
707} 772}
708 773
709fn send_startup_progress( 774fn send_startup_progress(sender: &Sender<Message>, loop_state: &mut LoopState) {
710 sender: &Sender<Message>, 775 let total: usize = loop_state.roots_total;
711 loop_state: &mut LoopState, 776 let prev = loop_state.roots_progress_reported;
712 world_state: &WorldState, 777 let progress = loop_state.roots_scanned;
713) { 778 loop_state.roots_progress_reported = Some(progress);
714 if !world_state.feature_flags.get("notifications.workspace-loaded") {
715 return;
716 }
717 779
718 let total: usize = world_state.workspaces.iter().map(|it| it.n_packages()).sum(); 780 match (prev, loop_state.workspace_loaded) {
719 let prev_progress = loop_state.roots_scanned_progress;
720 let progress = total - world_state.roots_to_scan;
721 loop_state.roots_scanned_progress = Some(progress);
722
723 match (prev_progress, loop_state.workspace_loaded) {
724 (None, false) => { 781 (None, false) => {
725 let work_done_progress_create = request_new::<req::WorkDoneProgressCreate>( 782 let work_done_progress_create = request_new::<req::WorkDoneProgressCreate>(
726 loop_state.next_request_id(), 783 loop_state.next_request_id(),
@@ -755,14 +812,14 @@ fn send_startup_progress(
755 ), 812 ),
756 _ => {} 813 _ => {}
757 } 814 }
758}
759 815
760fn send_startup_progress_notif(sender: &Sender<Message>, work_done_progress: WorkDoneProgress) { 816 fn send_startup_progress_notif(sender: &Sender<Message>, work_done_progress: WorkDoneProgress) {
761 let notif = notification_new::<req::Progress>(req::ProgressParams { 817 let notif = notification_new::<req::Progress>(req::ProgressParams {
762 token: req::ProgressToken::String("rustAnalyzer/startup".into()), 818 token: req::ProgressToken::String("rustAnalyzer/startup".into()),
763 value: req::ProgressParamsValue::WorkDone(work_done_progress), 819 value: req::ProgressParamsValue::WorkDone(work_done_progress),
764 }); 820 });
765 sender.send(notif.into()).unwrap(); 821 sender.send(notif.into()).unwrap();
822 }
766} 823}
767 824
768struct PoolDispatcher<'a> { 825struct PoolDispatcher<'a> {
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index 12f8ca297..d5cb5d137 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -14,12 +14,12 @@ use lsp_types::{
14 CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic, 14 CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic,
15 DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams, 15 DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams,
16 Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse, 16 Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse,
17 Range, RenameParams, SemanticTokens, SemanticTokensParams, SemanticTokensRangeParams, 17 Range, RenameParams, SemanticTokensParams, SemanticTokensRangeParams,
18 SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, 18 SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, TextDocumentIdentifier,
19 TextEdit, WorkspaceEdit, 19 TextEdit, WorkspaceEdit,
20}; 20};
21use ra_ide::{ 21use ra_ide::{
22 Assist, AssistId, CompletionOptions, FileId, FilePosition, FileRange, Query, RangeInfo, 22 Assist, AssistId, CompletionConfig, FileId, FilePosition, FileRange, Query, RangeInfo,
23 Runnable, RunnableKind, SearchScope, 23 Runnable, RunnableKind, SearchScope,
24}; 24};
25use ra_prof::profile; 25use ra_prof::profile;
@@ -425,7 +425,7 @@ pub fn handle_completion(
425 return Ok(None); 425 return Ok(None);
426 } 426 }
427 427
428 let options = CompletionOptions { 428 let config = CompletionConfig {
429 enable_postfix_completions: world.feature_flags.get("completion.enable-postfix"), 429 enable_postfix_completions: world.feature_flags.get("completion.enable-postfix"),
430 add_call_parenthesis: world.feature_flags.get("completion.insertion.add-call-parenthesis"), 430 add_call_parenthesis: world.feature_flags.get("completion.insertion.add-call-parenthesis"),
431 add_call_argument_snippets: world 431 add_call_argument_snippets: world
@@ -433,7 +433,7 @@ pub fn handle_completion(
433 .get("completion.insertion.add-argument-snippets"), 433 .get("completion.insertion.add-argument-snippets"),
434 }; 434 };
435 435
436 let items = match world.analysis().completions(position, &options)? { 436 let items = match world.analysis().completions(position, &config)? {
437 None => return Ok(None), 437 None => return Ok(None),
438 Some(items) => items, 438 Some(items) => items,
439 }; 439 };
@@ -457,7 +457,7 @@ pub fn handle_folding_range(
457 let ctx = FoldConvCtx { 457 let ctx = FoldConvCtx {
458 text: &text, 458 text: &text,
459 line_index: &line_index, 459 line_index: &line_index,
460 line_folding_only: world.options.line_folding_only, 460 line_folding_only: world.config.line_folding_only,
461 }; 461 };
462 let res = Some(folds.into_iter().map_conv_with(&ctx).collect()); 462 let res = Some(folds.into_iter().map_conv_with(&ctx).collect());
463 Ok(res) 463 Ok(res)
@@ -611,7 +611,7 @@ pub fn handle_formatting(
611 let end_position = TextUnit::of_str(&file).conv_with(&file_line_index); 611 let end_position = TextUnit::of_str(&file).conv_with(&file_line_index);
612 612
613 let mut rustfmt = process::Command::new("rustfmt"); 613 let mut rustfmt = process::Command::new("rustfmt");
614 rustfmt.args(&world.options.rustfmt_args); 614 rustfmt.args(&world.config.rustfmt_args);
615 if let Some(&crate_id) = crate_ids.first() { 615 if let Some(&crate_id) = crate_ids.first() {
616 // Assume all crates are in the same edition 616 // Assume all crates are in the same edition
617 let edition = world.analysis().crate_edition(crate_id)?; 617 let edition = world.analysis().crate_edition(crate_id)?;
@@ -815,7 +815,7 @@ pub fn handle_code_lens(
815 }; 815 };
816 lenses.push(lens); 816 lenses.push(lens);
817 817
818 if world.options.vscode_lldb { 818 if world.config.vscode_lldb {
819 if r.args[0] == "run" { 819 if r.args[0] == "run" {
820 r.args[0] = "build".into(); 820 r.args[0] = "build".into();
821 } else { 821 } else {
@@ -1028,7 +1028,7 @@ pub fn handle_inlay_hints(
1028 let analysis = world.analysis(); 1028 let analysis = world.analysis();
1029 let line_index = analysis.file_line_index(file_id)?; 1029 let line_index = analysis.file_line_index(file_id)?;
1030 Ok(analysis 1030 Ok(analysis
1031 .inlay_hints(file_id, &world.options.inlay_hints)? 1031 .inlay_hints(file_id, &world.config.inlay_hints)?
1032 .into_iter() 1032 .into_iter()
1033 .map_conv_with(&line_index) 1033 .map_conv_with(&line_index)
1034 .collect()) 1034 .collect())
@@ -1145,7 +1145,7 @@ pub fn handle_semantic_tokens(
1145 } 1145 }
1146 } 1146 }
1147 1147
1148 let tokens = SemanticTokens { data: builder.build(), ..Default::default() }; 1148 let tokens = builder.build();
1149 1149
1150 Ok(Some(tokens.into())) 1150 Ok(Some(tokens.into()))
1151} 1151}
@@ -1166,7 +1166,7 @@ pub fn handle_semantic_tokens_range(
1166 builder.push(highlight_range.range.conv_with(&line_index), token_type, token_modifiers); 1166 builder.push(highlight_range.range.conv_with(&line_index), token_type, token_modifiers);
1167 } 1167 }
1168 1168
1169 let tokens = SemanticTokens { data: builder.build(), ..Default::default() }; 1169 let tokens = builder.build();
1170 1170
1171 Ok(Some(tokens.into())) 1171 Ok(Some(tokens.into()))
1172} 1172}
diff --git a/crates/rust-analyzer/src/req.rs b/crates/rust-analyzer/src/req.rs
index 8557294f6..994f0ed61 100644
--- a/crates/rust-analyzer/src/req.rs
+++ b/crates/rust-analyzer/src/req.rs
@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
6 6
7pub use lsp_types::{ 7pub use lsp_types::{
8 notification::*, request::*, ApplyWorkspaceEditParams, CodeActionParams, CodeLens, 8 notification::*, request::*, ApplyWorkspaceEditParams, CodeActionParams, CodeLens,
9 CodeLensParams, CompletionParams, CompletionResponse, DiagnosticTag, 9 CodeLensParams, CompletionParams, CompletionResponse, ConfigurationParams, DiagnosticTag,
10 DidChangeConfigurationParams, DidChangeWatchedFilesParams, 10 DidChangeConfigurationParams, DidChangeWatchedFilesParams,
11 DidChangeWatchedFilesRegistrationOptions, DocumentOnTypeFormattingParams, DocumentSymbolParams, 11 DidChangeWatchedFilesRegistrationOptions, DocumentOnTypeFormattingParams, DocumentSymbolParams,
12 DocumentSymbolResponse, FileSystemWatcher, Hover, InitializeResult, MessageType, 12 DocumentSymbolResponse, FileSystemWatcher, Hover, InitializeResult, MessageType,
diff --git a/crates/rust-analyzer/src/semantic_tokens.rs b/crates/rust-analyzer/src/semantic_tokens.rs
index d9ba77050..2a66bbfd8 100644
--- a/crates/rust-analyzer/src/semantic_tokens.rs
+++ b/crates/rust-analyzer/src/semantic_tokens.rs
@@ -2,7 +2,7 @@
2 2
3use std::ops; 3use std::ops;
4 4
5use lsp_types::{Range, SemanticToken, SemanticTokenModifier, SemanticTokenType}; 5use lsp_types::{Range, SemanticToken, SemanticTokenModifier, SemanticTokenType, SemanticTokens};
6 6
7pub(crate) const ATTRIBUTE: SemanticTokenType = SemanticTokenType::new("attribute"); 7pub(crate) const ATTRIBUTE: SemanticTokenType = SemanticTokenType::new("attribute");
8pub(crate) const BUILTIN_TYPE: SemanticTokenType = SemanticTokenType::new("builtinType"); 8pub(crate) const BUILTIN_TYPE: SemanticTokenType = SemanticTokenType::new("builtinType");
@@ -109,8 +109,8 @@ impl SemanticTokensBuilder {
109 self.prev_char = range.start.character as u32; 109 self.prev_char = range.start.character as u32;
110 } 110 }
111 111
112 pub fn build(self) -> Vec<SemanticToken> { 112 pub fn build(self) -> SemanticTokens {
113 self.data 113 SemanticTokens { result_id: None, data: self.data }
114 } 114 }
115} 115}
116 116
diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/world.rs
index 64a7b907e..7814a682e 100644
--- a/crates/rust-analyzer/src/world.rs
+++ b/crates/rust-analyzer/src/world.rs
@@ -11,9 +11,9 @@ use std::{
11use crossbeam_channel::{unbounded, Receiver}; 11use crossbeam_channel::{unbounded, Receiver};
12use lsp_types::Url; 12use lsp_types::Url;
13use parking_lot::RwLock; 13use parking_lot::RwLock;
14use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckWatcher}; 14use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckConfig, Flycheck};
15use ra_ide::{ 15use ra_ide::{
16 Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, InlayHintsOptions, LibraryData, 16 Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, InlayHintsConfig, LibraryData,
17 SourceRootId, 17 SourceRootId,
18}; 18};
19use ra_project_model::{get_rustc_cfg_options, ProcMacroClient, ProjectWorkspace}; 19use ra_project_model::{get_rustc_cfg_options, ProcMacroClient, ProjectWorkspace};
@@ -31,15 +31,36 @@ use crate::{
31use ra_db::ExternSourceId; 31use ra_db::ExternSourceId;
32use rustc_hash::{FxHashMap, FxHashSet}; 32use rustc_hash::{FxHashMap, FxHashSet};
33 33
34fn create_flycheck(workspaces: &[ProjectWorkspace], config: &Config) -> Option<Flycheck> {
35 let check_config = config.check.as_ref()?;
36
37 // FIXME: Figure out the multi-workspace situation
38 workspaces
39 .iter()
40 .find_map(|w| match w {
41 ProjectWorkspace::Cargo { cargo, .. } => Some(cargo),
42 ProjectWorkspace::Json { .. } => None,
43 })
44 .map(|cargo| {
45 let cargo_project_root = cargo.workspace_root().to_path_buf();
46 Some(Flycheck::new(check_config.clone(), cargo_project_root))
47 })
48 .unwrap_or_else(|| {
49 log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
50 None
51 })
52}
53
34#[derive(Debug, Clone)] 54#[derive(Debug, Clone)]
35pub struct Options { 55pub struct Config {
36 pub publish_decorations: bool, 56 pub publish_decorations: bool,
37 pub supports_location_link: bool, 57 pub supports_location_link: bool,
38 pub line_folding_only: bool, 58 pub line_folding_only: bool,
39 pub inlay_hints: InlayHintsOptions, 59 pub inlay_hints: InlayHintsConfig,
40 pub rustfmt_args: Vec<String>, 60 pub rustfmt_args: Vec<String>,
41 pub cargo_watch: CheckOptions, 61 pub check: Option<CheckConfig>,
42 pub vscode_lldb: bool, 62 pub vscode_lldb: bool,
63 pub proc_macro_srv: Option<String>,
43} 64}
44 65
45/// `WorldState` is the primary mutable state of the language server 66/// `WorldState` is the primary mutable state of the language server
@@ -49,23 +70,21 @@ pub struct Options {
49/// incremental salsa database. 70/// incremental salsa database.
50#[derive(Debug)] 71#[derive(Debug)]
51pub struct WorldState { 72pub struct WorldState {
52 pub options: Options, 73 pub config: Config,
53 pub feature_flags: Arc<FeatureFlags>, 74 pub feature_flags: Arc<FeatureFlags>,
54 //FIXME: this belongs to `LoopState` rather than to `WorldState`
55 pub roots_to_scan: usize,
56 pub roots: Vec<PathBuf>, 75 pub roots: Vec<PathBuf>,
57 pub workspaces: Arc<Vec<ProjectWorkspace>>, 76 pub workspaces: Arc<Vec<ProjectWorkspace>>,
58 pub analysis_host: AnalysisHost, 77 pub analysis_host: AnalysisHost,
59 pub vfs: Arc<RwLock<Vfs>>, 78 pub vfs: Arc<RwLock<Vfs>>,
60 pub task_receiver: Receiver<VfsTask>, 79 pub task_receiver: Receiver<VfsTask>,
61 pub latest_requests: Arc<RwLock<LatestRequests>>, 80 pub latest_requests: Arc<RwLock<LatestRequests>>,
62 pub check_watcher: CheckWatcher, 81 pub flycheck: Option<Flycheck>,
63 pub diagnostics: DiagnosticCollection, 82 pub diagnostics: DiagnosticCollection,
64} 83}
65 84
66/// An immutable snapshot of the world's state at a point in time. 85/// An immutable snapshot of the world's state at a point in time.
67pub struct WorldSnapshot { 86pub struct WorldSnapshot {
68 pub options: Options, 87 pub config: Config,
69 pub feature_flags: Arc<FeatureFlags>, 88 pub feature_flags: Arc<FeatureFlags>,
70 pub workspaces: Arc<Vec<ProjectWorkspace>>, 89 pub workspaces: Arc<Vec<ProjectWorkspace>>,
71 pub analysis: Analysis, 90 pub analysis: Analysis,
@@ -81,7 +100,7 @@ impl WorldState {
81 lru_capacity: Option<usize>, 100 lru_capacity: Option<usize>,
82 exclude_globs: &[Glob], 101 exclude_globs: &[Glob],
83 watch: Watch, 102 watch: Watch,
84 options: Options, 103 config: Config,
85 feature_flags: FeatureFlags, 104 feature_flags: FeatureFlags,
86 ) -> WorldState { 105 ) -> WorldState {
87 let mut change = AnalysisChange::new(); 106 let mut change = AnalysisChange::new();
@@ -123,7 +142,7 @@ impl WorldState {
123 let (task_sender, task_receiver) = unbounded(); 142 let (task_sender, task_receiver) = unbounded();
124 let task_sender = Box::new(move |t| task_sender.send(t).unwrap()); 143 let task_sender = Box::new(move |t| task_sender.send(t).unwrap());
125 let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch); 144 let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch);
126 let roots_to_scan = vfs_roots.len(); 145
127 for r in vfs_roots { 146 for r in vfs_roots {
128 let vfs_root_path = vfs.root2path(r); 147 let vfs_root_path = vfs.root2path(r);
129 let is_local = folder_roots.iter().any(|it| vfs_root_path.starts_with(it)); 148 let is_local = folder_roots.iter().any(|it| vfs_root_path.starts_with(it));
@@ -151,8 +170,23 @@ impl WorldState {
151 vfs_file.map(|f| FileId(f.0)) 170 vfs_file.map(|f| FileId(f.0))
152 }; 171 };
153 172
154 let proc_macro_client = 173 let proc_macro_client = match &config.proc_macro_srv {
155 ProcMacroClient::extern_process(std::path::Path::new("ra_proc_macro_srv")); 174 None => ProcMacroClient::dummy(),
175 Some(srv) => {
176 let path = Path::new(&srv);
177 match ProcMacroClient::extern_process(path) {
178 Ok(it) => it,
179 Err(err) => {
180 log::error!(
181 "Fail to run ra_proc_macro_srv from path {}, error : {}",
182 path.to_string_lossy(),
183 err
184 );
185 ProcMacroClient::dummy()
186 }
187 }
188 }
189 };
156 190
157 workspaces 191 workspaces
158 .iter() 192 .iter()
@@ -169,43 +203,41 @@ impl WorldState {
169 }); 203 });
170 change.set_crate_graph(crate_graph); 204 change.set_crate_graph(crate_graph);
171 205
172 // FIXME: Figure out the multi-workspace situation 206 let flycheck = create_flycheck(&workspaces, &config);
173 let check_watcher = workspaces
174 .iter()
175 .find_map(|w| match w {
176 ProjectWorkspace::Cargo { cargo, .. } => Some(cargo),
177 ProjectWorkspace::Json { .. } => None,
178 })
179 .map(|cargo| {
180 let cargo_project_root = cargo.workspace_root().to_path_buf();
181 CheckWatcher::new(&options.cargo_watch, cargo_project_root)
182 })
183 .unwrap_or_else(|| {
184 log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
185 CheckWatcher::dummy()
186 });
187 207
188 let mut analysis_host = AnalysisHost::new(lru_capacity); 208 let mut analysis_host = AnalysisHost::new(lru_capacity);
189 analysis_host.apply_change(change); 209 analysis_host.apply_change(change);
190 WorldState { 210 WorldState {
191 options, 211 config: config,
192 feature_flags: Arc::new(feature_flags), 212 feature_flags: Arc::new(feature_flags),
193 roots_to_scan,
194 roots: folder_roots, 213 roots: folder_roots,
195 workspaces: Arc::new(workspaces), 214 workspaces: Arc::new(workspaces),
196 analysis_host, 215 analysis_host,
197 vfs: Arc::new(RwLock::new(vfs)), 216 vfs: Arc::new(RwLock::new(vfs)),
198 task_receiver, 217 task_receiver,
199 latest_requests: Default::default(), 218 latest_requests: Default::default(),
200 check_watcher, 219 flycheck,
201 diagnostics: Default::default(), 220 diagnostics: Default::default(),
202 } 221 }
203 } 222 }
204 223
224 pub fn update_configuration(
225 &mut self,
226 lru_capacity: Option<usize>,
227 config: Config,
228 feature_flags: FeatureFlags,
229 ) {
230 self.feature_flags = Arc::new(feature_flags);
231 self.analysis_host.update_lru_capacity(lru_capacity);
232 self.flycheck = create_flycheck(&self.workspaces, &config);
233 self.config = config;
234 }
235
205 /// Returns a vec of libraries 236 /// Returns a vec of libraries
206 /// FIXME: better API here 237 /// FIXME: better API here
207 pub fn process_changes( 238 pub fn process_changes(
208 &mut self, 239 &mut self,
240 roots_scanned: &mut usize,
209 ) -> Option<Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>> { 241 ) -> Option<Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>> {
210 let changes = self.vfs.write().commit_changes(); 242 let changes = self.vfs.write().commit_changes();
211 if changes.is_empty() { 243 if changes.is_empty() {
@@ -219,7 +251,7 @@ impl WorldState {
219 let root_path = self.vfs.read().root2path(root); 251 let root_path = self.vfs.read().root2path(root);
220 let is_local = self.roots.iter().any(|r| root_path.starts_with(r)); 252 let is_local = self.roots.iter().any(|r| root_path.starts_with(r));
221 if is_local { 253 if is_local {
222 self.roots_to_scan -= 1; 254 *roots_scanned += 1;
223 for (file, path, text) in files { 255 for (file, path, text) in files {
224 change.add_file(SourceRootId(root.0), FileId(file.0), path, text); 256 change.add_file(SourceRootId(root.0), FileId(file.0), path, text);
225 } 257 }
@@ -247,7 +279,6 @@ impl WorldState {
247 } 279 }
248 280
249 pub fn add_lib(&mut self, data: LibraryData) { 281 pub fn add_lib(&mut self, data: LibraryData) {
250 self.roots_to_scan -= 1;
251 let mut change = AnalysisChange::new(); 282 let mut change = AnalysisChange::new();
252 change.add_library(data); 283 change.add_library(data);
253 self.analysis_host.apply_change(change); 284 self.analysis_host.apply_change(change);
@@ -255,7 +286,7 @@ impl WorldState {
255 286
256 pub fn snapshot(&self) -> WorldSnapshot { 287 pub fn snapshot(&self) -> WorldSnapshot {
257 WorldSnapshot { 288 WorldSnapshot {
258 options: self.options.clone(), 289 config: self.config.clone(),
259 feature_flags: Arc::clone(&self.feature_flags), 290 feature_flags: Arc::clone(&self.feature_flags),
260 workspaces: Arc::clone(&self.workspaces), 291 workspaces: Arc::clone(&self.workspaces),
261 analysis: self.analysis_host.analysis(), 292 analysis: self.analysis_host.analysis(),
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs
index 145429571..5af5eaad2 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/main.rs
@@ -9,7 +9,7 @@ use lsp_types::{
9}; 9};
10use rust_analyzer::req::{ 10use rust_analyzer::req::{
11 CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument, 11 CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument,
12 Formatting, OnEnter, Runnables, RunnablesParams, 12 Formatting, GotoDefinition, OnEnter, Runnables, RunnablesParams,
13}; 13};
14use serde_json::json; 14use serde_json::json;
15use tempfile::TempDir; 15use tempfile::TempDir;
@@ -581,3 +581,47 @@ version = \"0.0.0\"
581 }), 581 }),
582 ); 582 );
583} 583}
584
585#[test]
586fn resolve_include_concat_env() {
587 if skip_slow_tests() {
588 return;
589 }
590
591 let server = Project::with_fixture(
592 r###"
593//- Cargo.toml
594[package]
595name = "foo"
596version = "0.0.0"
597
598//- build.rs
599use std::{env, fs, path::Path};
600
601fn main() {
602 let out_dir = env::var_os("OUT_DIR").unwrap();
603 let dest_path = Path::new(&out_dir).join("hello.rs");
604 fs::write(
605 &dest_path,
606 r#"pub fn message() -> &'static str { "Hello, World!" }"#,
607 )
608 .unwrap();
609 println!("cargo:rerun-if-changed=build.rs");
610}
611//- src/main.rs
612include!(concat!(env!("OUT_DIR"), "/hello.rs"));
613
614fn main() { message(); }
615"###,
616 )
617 .with_config(|config| {
618 config.cargo_features.load_out_dirs_from_check = true;
619 })
620 .server();
621 server.wait_until_workspace_is_loaded();
622 let res = server.send_request::<GotoDefinition>(TextDocumentPositionParams::new(
623 server.doc_id("src/main.rs"),
624 Position::new(2, 15),
625 ));
626 assert!(format!("{}", res).contains("hello.rs"));
627}
diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs
index 1d7062bdf..d8bed6d7f 100644
--- a/crates/rust-analyzer/tests/heavy_tests/support.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/support.rs
@@ -27,11 +27,12 @@ pub struct Project<'a> {
27 with_sysroot: bool, 27 with_sysroot: bool,
28 tmp_dir: Option<TempDir>, 28 tmp_dir: Option<TempDir>,
29 roots: Vec<PathBuf>, 29 roots: Vec<PathBuf>,
30 config: Option<Box<dyn Fn(&mut ServerConfig)>>,
30} 31}
31 32
32impl<'a> Project<'a> { 33impl<'a> Project<'a> {
33 pub fn with_fixture(fixture: &str) -> Project { 34 pub fn with_fixture(fixture: &str) -> Project {
34 Project { fixture, tmp_dir: None, roots: vec![], with_sysroot: false } 35 Project { fixture, tmp_dir: None, roots: vec![], with_sysroot: false, config: None }
35 } 36 }
36 37
37 pub fn tmp_dir(mut self, tmp_dir: TempDir) -> Project<'a> { 38 pub fn tmp_dir(mut self, tmp_dir: TempDir) -> Project<'a> {
@@ -49,6 +50,11 @@ impl<'a> Project<'a> {
49 self 50 self
50 } 51 }
51 52
53 pub fn with_config(mut self, config: impl Fn(&mut ServerConfig) + 'static) -> Project<'a> {
54 self.config = Some(Box::new(config));
55 self
56 }
57
52 pub fn server(self) -> Server { 58 pub fn server(self) -> Server {
53 let tmp_dir = self.tmp_dir.unwrap_or_else(|| TempDir::new().unwrap()); 59 let tmp_dir = self.tmp_dir.unwrap_or_else(|| TempDir::new().unwrap());
54 static INIT: Once = Once::new(); 60 static INIT: Once = Once::new();
@@ -72,7 +78,14 @@ impl<'a> Project<'a> {
72 78
73 let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect(); 79 let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect();
74 80
75 Server::new(tmp_dir, self.with_sysroot, roots, paths) 81 let mut config =
82 ServerConfig { with_sysroot: self.with_sysroot, ..ServerConfig::default() };
83
84 if let Some(f) = &self.config {
85 f(&mut config)
86 }
87
88 Server::new(tmp_dir, config, roots, paths)
76 } 89 }
77} 90}
78 91
@@ -83,15 +96,16 @@ pub fn project(fixture: &str) -> Server {
83pub struct Server { 96pub struct Server {
84 req_id: Cell<u64>, 97 req_id: Cell<u64>,
85 messages: RefCell<Vec<Message>>, 98 messages: RefCell<Vec<Message>>,
86 dir: TempDir,
87 _thread: jod_thread::JoinHandle<()>, 99 _thread: jod_thread::JoinHandle<()>,
88 client: Connection, 100 client: Connection,
101 /// XXX: remove the tempdir last
102 dir: TempDir,
89} 103}
90 104
91impl Server { 105impl Server {
92 fn new( 106 fn new(
93 dir: TempDir, 107 dir: TempDir,
94 with_sysroot: bool, 108 config: ServerConfig,
95 roots: Vec<PathBuf>, 109 roots: Vec<PathBuf>,
96 files: Vec<(PathBuf, String)>, 110 files: Vec<(PathBuf, String)>,
97 ) -> Server { 111 ) -> Server {
@@ -117,7 +131,7 @@ impl Server {
117 window: None, 131 window: None,
118 experimental: None, 132 experimental: None,
119 }, 133 },
120 ServerConfig { with_sysroot, ..ServerConfig::default() }, 134 config,
121 connection, 135 connection,
122 ) 136 )
123 .unwrap() 137 .unwrap()
@@ -187,6 +201,7 @@ impl Server {
187 self.client.sender.send(r.into()).unwrap(); 201 self.client.sender.send(r.into()).unwrap();
188 while let Some(msg) = self.recv() { 202 while let Some(msg) = self.recv() {
189 match msg { 203 match msg {
204 Message::Request(req) if req.method == "window/workDoneProgress/create" => (),
190 Message::Request(req) => panic!("unexpected request: {:?}", req), 205 Message::Request(req) => panic!("unexpected request: {:?}", req),
191 Message::Notification(_) => (), 206 Message::Notification(_) => (),
192 Message::Response(res) => { 207 Message::Response(res) => {
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index 8492c17af..d2efa2236 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -5,7 +5,7 @@ use std::{cell::Cell, fmt};
5/// Appends formatted string to a `String`. 5/// Appends formatted string to a `String`.
6#[macro_export] 6#[macro_export]
7macro_rules! format_to { 7macro_rules! format_to {
8 (&buf:expr) => (); 8 ($buf:expr) => ();
9 ($buf:expr, $lit:literal $($arg:tt)*) => { 9 ($buf:expr, $lit:literal $($arg:tt)*) => {
10 { use ::std::fmt::Write as _; let _ = ::std::write!($buf, $lit $($arg)*); } 10 { use ::std::fmt::Write as _; let _ = ::std::write!($buf, $lit $($arg)*); }
11 }; 11 };
diff --git a/docs/user/features.md b/docs/user/features.md
index 56d2969fd..8aeec2e81 100644
--- a/docs/user/features.md
+++ b/docs/user/features.md
@@ -81,6 +81,12 @@ Join selected lines into one, smartly fixing up whitespace and trailing commas.
81Shows the parse tree of the current file. It exists mostly for debugging 81Shows the parse tree of the current file. It exists mostly for debugging
82rust-analyzer itself. 82rust-analyzer itself.
83 83
84You can hover over syntax nodes in the opened text file to see the appropriate
85rust code that it refers to and the rust editor will also highlight the proper
86text range.
87
88<img src="https://user-images.githubusercontent.com/36276403/78043783-7425e180-737c-11ea-8653-b02b773c5aa1.png" alt="demo" height="200px" >
89
84#### Expand Macro Recursively 90#### Expand Macro Recursively
85 91
86Shows the full macro expansion of the macro at current cursor. 92Shows the full macro expansion of the macro at current cursor.
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json
index b7220d8e2..955a05066 100644
--- a/editors/code/package-lock.json
+++ b/editors/code/package-lock.json
@@ -84,9 +84,9 @@
84 "dev": true 84 "dev": true
85 }, 85 },
86 "@types/node": { 86 "@types/node": {
87 "version": "12.12.30", 87 "version": "12.12.34",
88 "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.30.tgz", 88 "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.34.tgz",
89 "integrity": "sha512-sz9MF/zk6qVr3pAnM0BSQvYIBK44tS75QC5N+VbWSE4DjCV/pJ+UzCW/F+vVnl7TkOPcuwQureKNtSSwjBTaMg==", 89 "integrity": "sha512-BneGN0J9ke24lBRn44hVHNeDlrXRYF+VRp0HbSUNnEZahXGAysHZIqnf/hER6aabdBgzM4YOV4jrR8gj4Zfi0g==",
90 "dev": true 90 "dev": true
91 }, 91 },
92 "@types/node-fetch": { 92 "@types/node-fetch": {
@@ -115,45 +115,56 @@
115 "dev": true 115 "dev": true
116 }, 116 },
117 "@typescript-eslint/eslint-plugin": { 117 "@typescript-eslint/eslint-plugin": {
118 "version": "2.24.0", 118 "version": "2.26.0",
119 "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.24.0.tgz", 119 "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.26.0.tgz",
120 "integrity": "sha512-wJRBeaMeT7RLQ27UQkDFOu25MqFOBus8PtOa9KaT5ZuxC1kAsd7JEHqWt4YXuY9eancX0GK9C68i5OROnlIzBA==", 120 "integrity": "sha512-4yUnLv40bzfzsXcTAtZyTjbiGUXMrcIJcIMioI22tSOyAxpdXiZ4r7YQUU8Jj6XXrLz9d5aMHPQf5JFR7h27Nw==",
121 "dev": true, 121 "dev": true,
122 "requires": { 122 "requires": {
123 "@typescript-eslint/experimental-utils": "2.24.0", 123 "@typescript-eslint/experimental-utils": "2.26.0",
124 "eslint-utils": "^1.4.3",
125 "functional-red-black-tree": "^1.0.1", 124 "functional-red-black-tree": "^1.0.1",
126 "regexpp": "^3.0.0", 125 "regexpp": "^3.0.0",
127 "tsutils": "^3.17.1" 126 "tsutils": "^3.17.1"
128 } 127 }
129 }, 128 },
130 "@typescript-eslint/experimental-utils": { 129 "@typescript-eslint/experimental-utils": {
131 "version": "2.24.0", 130 "version": "2.26.0",
132 "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.24.0.tgz", 131 "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.26.0.tgz",
133 "integrity": "sha512-DXrwuXTdVh3ycNCMYmWhUzn/gfqu9N0VzNnahjiDJvcyhfBy4gb59ncVZVxdp5XzBC77dCncu0daQgOkbvPwBw==", 132 "integrity": "sha512-RELVoH5EYd+JlGprEyojUv9HeKcZqF7nZUGSblyAw1FwOGNnmQIU8kxJ69fttQvEwCsX5D6ECJT8GTozxrDKVQ==",
134 "dev": true, 133 "dev": true,
135 "requires": { 134 "requires": {
136 "@types/json-schema": "^7.0.3", 135 "@types/json-schema": "^7.0.3",
137 "@typescript-eslint/typescript-estree": "2.24.0", 136 "@typescript-eslint/typescript-estree": "2.26.0",
138 "eslint-scope": "^5.0.0" 137 "eslint-scope": "^5.0.0",
138 "eslint-utils": "^2.0.0"
139 },
140 "dependencies": {
141 "eslint-utils": {
142 "version": "2.0.0",
143 "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz",
144 "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==",
145 "dev": true,
146 "requires": {
147 "eslint-visitor-keys": "^1.1.0"
148 }
149 }
139 } 150 }
140 }, 151 },
141 "@typescript-eslint/parser": { 152 "@typescript-eslint/parser": {
142 "version": "2.24.0", 153 "version": "2.26.0",
143 "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.24.0.tgz", 154 "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.26.0.tgz",
144 "integrity": "sha512-H2Y7uacwSSg8IbVxdYExSI3T7uM1DzmOn2COGtCahCC3g8YtM1xYAPi2MAHyfPs61VKxP/J/UiSctcRgw4G8aw==", 155 "integrity": "sha512-+Xj5fucDtdKEVGSh9353wcnseMRkPpEAOY96EEenN7kJVrLqy/EVwtIh3mxcUz8lsFXW1mT5nN5vvEam/a5HiQ==",
145 "dev": true, 156 "dev": true,
146 "requires": { 157 "requires": {
147 "@types/eslint-visitor-keys": "^1.0.0", 158 "@types/eslint-visitor-keys": "^1.0.0",
148 "@typescript-eslint/experimental-utils": "2.24.0", 159 "@typescript-eslint/experimental-utils": "2.26.0",
149 "@typescript-eslint/typescript-estree": "2.24.0", 160 "@typescript-eslint/typescript-estree": "2.26.0",
150 "eslint-visitor-keys": "^1.1.0" 161 "eslint-visitor-keys": "^1.1.0"
151 } 162 }
152 }, 163 },
153 "@typescript-eslint/typescript-estree": { 164 "@typescript-eslint/typescript-estree": {
154 "version": "2.24.0", 165 "version": "2.26.0",
155 "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.24.0.tgz", 166 "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.26.0.tgz",
156 "integrity": "sha512-RJ0yMe5owMSix55qX7Mi9V6z2FDuuDpN6eR5fzRJrp+8in9UF41IGNQHbg5aMK4/PjVaEQksLvz0IA8n+Mr/FA==", 167 "integrity": "sha512-3x4SyZCLB4zsKsjuhxDLeVJN6W29VwBnYpCsZ7vIdPel9ZqLfIZJgJXO47MNUkurGpQuIBALdPQKtsSnWpE1Yg==",
157 "dev": true, 168 "dev": true,
158 "requires": { 169 "requires": {
159 "debug": "^4.1.1", 170 "debug": "^4.1.1",
@@ -1388,9 +1399,9 @@
1388 } 1399 }
1389 }, 1400 },
1390 "rollup": { 1401 "rollup": {
1391 "version": "2.1.0", 1402 "version": "2.3.1",
1392 "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.1.0.tgz", 1403 "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.3.1.tgz",
1393 "integrity": "sha512-gfE1455AEazVVTJoeQtcOq/U6GSxwoj4XPSWVsuWmgIxj7sBQNLDOSA82PbdMe+cP8ql8fR1jogPFe8Wg8g4SQ==", 1404 "integrity": "sha512-BRjzOauORe+R0U0I6SkMTSG22nYmtztR/TaBl0SvbXgc3VAxBDrZoB6HROiK0S5px1pUBnLnjBkbzmVuwC9Q1Q==",
1394 "dev": true, 1405 "dev": true,
1395 "requires": { 1406 "requires": {
1396 "fsevents": "~2.1.2" 1407 "fsevents": "~2.1.2"
diff --git a/editors/code/package.json b/editors/code/package.json
index 37e083220..ba31c4e63 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -39,13 +39,13 @@
39 "devDependencies": { 39 "devDependencies": {
40 "@rollup/plugin-commonjs": "^11.0.2", 40 "@rollup/plugin-commonjs": "^11.0.2",
41 "@rollup/plugin-node-resolve": "^7.1.1", 41 "@rollup/plugin-node-resolve": "^7.1.1",
42 "@types/node": "^12.12.30", 42 "@types/node": "^12.12.34",
43 "@types/node-fetch": "^2.5.5", 43 "@types/node-fetch": "^2.5.5",
44 "@types/vscode": "^1.43.0", 44 "@types/vscode": "^1.43.0",
45 "@typescript-eslint/eslint-plugin": "^2.24.0", 45 "@typescript-eslint/eslint-plugin": "^2.26.0",
46 "@typescript-eslint/parser": "^2.24.0", 46 "@typescript-eslint/parser": "^2.26.0",
47 "eslint": "^6.8.0", 47 "eslint": "^6.8.0",
48 "rollup": "^2.1.0", 48 "rollup": "^2.3.1",
49 "tslib": "^1.11.1", 49 "tslib": "^1.11.1",
50 "typescript": "^3.8.3", 50 "typescript": "^3.8.3",
51 "typescript-formatter": "^7.2.2", 51 "typescript-formatter": "^7.2.2",
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 98f2f232f..f909f8db2 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -5,15 +5,39 @@ import { Config } from './config';
5import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed'; 5import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed';
6import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed'; 6import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed';
7 7
8export async function createClient(config: Config, serverPath: string): Promise<lc.LanguageClient> { 8export function configToServerOptions(config: Config) {
9 return {
10 publishDecorations: !config.highlightingSemanticTokens,
11 lruCapacity: config.lruCapacity,
12
13 inlayHintsType: config.inlayHints.typeHints,
14 inlayHintsParameter: config.inlayHints.parameterHints,
15 inlayHintsChaining: config.inlayHints.chainingHints,
16 inlayHintsMaxLength: config.inlayHints.maxLength,
17
18 cargoWatchEnable: config.cargoWatchOptions.enable,
19 cargoWatchArgs: config.cargoWatchOptions.arguments,
20 cargoWatchCommand: config.cargoWatchOptions.command,
21 cargoWatchAllTargets: config.cargoWatchOptions.allTargets,
22
23 excludeGlobs: config.excludeGlobs,
24 useClientWatching: config.useClientWatching,
25 featureFlags: config.featureFlags,
26 withSysroot: config.withSysroot,
27 cargoFeatures: config.cargoFeatures,
28 rustfmtArgs: config.rustfmtArgs,
29 vscodeLldb: vscode.extensions.getExtension("vadimcn.vscode-lldb") != null,
30 };
31}
32
33export async function createClient(config: Config, serverPath: string, cwd: string): Promise<lc.LanguageClient> {
9 // '.' Is the fallback if no folder is open 34 // '.' Is the fallback if no folder is open
10 // TODO?: Workspace folders support Uri's (eg: file://test.txt). 35 // TODO?: Workspace folders support Uri's (eg: file://test.txt).
11 // It might be a good idea to test if the uri points to a file. 36 // It might be a good idea to test if the uri points to a file.
12 const workspaceFolderPath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? '.';
13 37
14 const run: lc.Executable = { 38 const run: lc.Executable = {
15 command: serverPath, 39 command: serverPath,
16 options: { cwd: workspaceFolderPath }, 40 options: { cwd },
17 }; 41 };
18 const serverOptions: lc.ServerOptions = { 42 const serverOptions: lc.ServerOptions = {
19 run, 43 run,
@@ -22,32 +46,10 @@ export async function createClient(config: Config, serverPath: string): Promise<
22 const traceOutputChannel = vscode.window.createOutputChannel( 46 const traceOutputChannel = vscode.window.createOutputChannel(
23 'Rust Analyzer Language Server Trace', 47 'Rust Analyzer Language Server Trace',
24 ); 48 );
25 const cargoWatchOpts = config.cargoWatchOptions;
26 49
27 const clientOptions: lc.LanguageClientOptions = { 50 const clientOptions: lc.LanguageClientOptions = {
28 documentSelector: [{ scheme: 'file', language: 'rust' }], 51 documentSelector: [{ scheme: 'file', language: 'rust' }],
29 initializationOptions: { 52 initializationOptions: configToServerOptions(config),
30 publishDecorations: !config.highlightingSemanticTokens,
31 lruCapacity: config.lruCapacity,
32
33 inlayHintsType: config.inlayHints.typeHints,
34 inlayHintsParameter: config.inlayHints.parameterHints,
35 inlayHintsChaining: config.inlayHints.chainingHints,
36 inlayHintsMaxLength: config.inlayHints.maxLength,
37
38 cargoWatchEnable: cargoWatchOpts.enable,
39 cargoWatchArgs: cargoWatchOpts.arguments,
40 cargoWatchCommand: cargoWatchOpts.command,
41 cargoWatchAllTargets: cargoWatchOpts.allTargets,
42
43 excludeGlobs: config.excludeGlobs,
44 useClientWatching: config.useClientWatching,
45 featureFlags: config.featureFlags,
46 withSysroot: config.withSysroot,
47 cargoFeatures: config.cargoFeatures,
48 rustfmtArgs: config.rustfmtArgs,
49 vscodeLldb: vscode.extensions.getExtension("vadimcn.vscode-lldb") != null,
50 },
51 traceOutputChannel, 53 traceOutputChannel,
52 middleware: { 54 middleware: {
53 // Workaround for https://github.com/microsoft/vscode-languageserver-node/issues/576 55 // Workaround for https://github.com/microsoft/vscode-languageserver-node/issues/576
diff --git a/editors/code/src/commands/syntax_tree.ts b/editors/code/src/commands/syntax_tree.ts
index 2e08e8f11..996c7a716 100644
--- a/editors/code/src/commands/syntax_tree.ts
+++ b/editors/code/src/commands/syntax_tree.ts
@@ -1,8 +1,10 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as ra from '../rust-analyzer-api'; 2import * as ra from '../rust-analyzer-api';
3 3
4import { Ctx, Cmd } from '../ctx'; 4import { Ctx, Cmd, Disposable } from '../ctx';
5import { isRustDocument } from '../util'; 5import { isRustDocument, RustEditor, isRustEditor, sleep } from '../util';
6
7const AST_FILE_SCHEME = "rust-analyzer";
6 8
7// Opens the virtual file that will show the syntax tree 9// Opens the virtual file that will show the syntax tree
8// 10//
@@ -10,35 +12,13 @@ import { isRustDocument } from '../util';
10export function syntaxTree(ctx: Ctx): Cmd { 12export function syntaxTree(ctx: Ctx): Cmd {
11 const tdcp = new TextDocumentContentProvider(ctx); 13 const tdcp = new TextDocumentContentProvider(ctx);
12 14
13 ctx.pushCleanup( 15 void new AstInspector(ctx);
14 vscode.workspace.registerTextDocumentContentProvider( 16
15 'rust-analyzer', 17 ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider(AST_FILE_SCHEME, tdcp));
16 tdcp,
17 ),
18 );
19
20 vscode.workspace.onDidChangeTextDocument(
21 (event: vscode.TextDocumentChangeEvent) => {
22 const doc = event.document;
23 if (!isRustDocument(doc)) return;
24 afterLs(() => tdcp.eventEmitter.fire(tdcp.uri));
25 },
26 null,
27 ctx.subscriptions,
28 );
29
30 vscode.window.onDidChangeActiveTextEditor(
31 (editor: vscode.TextEditor | undefined) => {
32 if (!editor || !isRustDocument(editor.document)) return;
33 tdcp.eventEmitter.fire(tdcp.uri);
34 },
35 null,
36 ctx.subscriptions,
37 );
38 18
39 return async () => { 19 return async () => {
40 const editor = vscode.window.activeTextEditor; 20 const editor = vscode.window.activeTextEditor;
41 const rangeEnabled = !!(editor && !editor.selection.isEmpty); 21 const rangeEnabled = !!editor && !editor.selection.isEmpty;
42 22
43 const uri = rangeEnabled 23 const uri = rangeEnabled
44 ? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`) 24 ? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`)
@@ -48,45 +28,126 @@ export function syntaxTree(ctx: Ctx): Cmd {
48 28
49 tdcp.eventEmitter.fire(uri); 29 tdcp.eventEmitter.fire(uri);
50 30
51 return vscode.window.showTextDocument( 31 void await vscode.window.showTextDocument(document, {
52 document, 32 viewColumn: vscode.ViewColumn.Two,
53 vscode.ViewColumn.Two, 33 preserveFocus: true
54 true, 34 });
55 );
56 }; 35 };
57} 36}
58 37
59// We need to order this after LS updates, but there's no API for that.
60// Hence, good old setTimeout.
61function afterLs(f: () => void) {
62 setTimeout(f, 10);
63}
64
65
66class TextDocumentContentProvider implements vscode.TextDocumentContentProvider { 38class TextDocumentContentProvider implements vscode.TextDocumentContentProvider {
67 uri = vscode.Uri.parse('rust-analyzer://syntaxtree'); 39 readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree');
68 eventEmitter = new vscode.EventEmitter<vscode.Uri>(); 40 readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
41
69 42
70 constructor(private readonly ctx: Ctx) { 43 constructor(private readonly ctx: Ctx) {
44 vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
45 vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions);
71 } 46 }
72 47
73 provideTextDocumentContent(uri: vscode.Uri): vscode.ProviderResult<string> { 48 private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
74 const editor = vscode.window.activeTextEditor; 49 if (isRustDocument(event.document)) {
75 const client = this.ctx.client; 50 // We need to order this after language server updates, but there's no API for that.
76 if (!editor || !client) return ''; 51 // Hence, good old sleep().
52 void sleep(10).then(() => this.eventEmitter.fire(this.uri));
53 }
54 }
55 private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) {
56 if (editor && isRustEditor(editor)) {
57 this.eventEmitter.fire(this.uri);
58 }
59 }
60
61 provideTextDocumentContent(uri: vscode.Uri, ct: vscode.CancellationToken): vscode.ProviderResult<string> {
62 const rustEditor = this.ctx.activeRustEditor;
63 if (!rustEditor) return '';
77 64
78 // When the range based query is enabled we take the range of the selection 65 // When the range based query is enabled we take the range of the selection
79 const range = uri.query === 'range=true' && !editor.selection.isEmpty 66 const range = uri.query === 'range=true' && !rustEditor.selection.isEmpty
80 ? client.code2ProtocolConverter.asRange(editor.selection) 67 ? this.ctx.client.code2ProtocolConverter.asRange(rustEditor.selection)
81 : null; 68 : null;
82 69
83 return client.sendRequest(ra.syntaxTree, { 70 const params = { textDocument: { uri: rustEditor.document.uri.toString() }, range, };
84 textDocument: { uri: editor.document.uri.toString() }, 71 return this.ctx.client.sendRequest(ra.syntaxTree, params, ct);
85 range,
86 });
87 } 72 }
88 73
89 get onDidChange(): vscode.Event<vscode.Uri> { 74 get onDidChange(): vscode.Event<vscode.Uri> {
90 return this.eventEmitter.event; 75 return this.eventEmitter.event;
91 } 76 }
92} 77}
78
79
80// FIXME: consider implementing this via the Tree View API?
81// https://code.visualstudio.com/api/extension-guides/tree-view
82class AstInspector implements vscode.HoverProvider, Disposable {
83 private static readonly astDecorationType = vscode.window.createTextEditorDecorationType({
84 fontStyle: "normal",
85 border: "#ffffff 1px solid",
86 });
87 private rustEditor: undefined | RustEditor;
88
89 constructor(ctx: Ctx) {
90 ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: AST_FILE_SCHEME }, this));
91 vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, ctx.subscriptions);
92 vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions);
93
94 ctx.pushCleanup(this);
95 }
96 dispose() {
97 this.setRustEditor(undefined);
98 }
99
100 private onDidCloseTextDocument(doc: vscode.TextDocument) {
101 if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) {
102 this.setRustEditor(undefined);
103 }
104 }
105
106 private onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]) {
107 if (editors.every(suspect => suspect.document.uri.scheme !== AST_FILE_SCHEME)) {
108 this.setRustEditor(undefined);
109 return;
110 }
111 this.setRustEditor(editors.find(isRustEditor));
112 }
113
114 private setRustEditor(newRustEditor: undefined | RustEditor) {
115 if (newRustEditor !== this.rustEditor) {
116 this.rustEditor?.setDecorations(AstInspector.astDecorationType, []);
117 }
118 this.rustEditor = newRustEditor;
119 }
120
121 provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult<vscode.Hover> {
122 if (!this.rustEditor) return;
123
124 const astTextLine = doc.lineAt(hoverPosition.line);
125
126 const rustTextRange = this.parseRustTextRange(this.rustEditor.document, astTextLine.text);
127 if (!rustTextRange) return;
128
129 this.rustEditor.setDecorations(AstInspector.astDecorationType, [rustTextRange]);
130 this.rustEditor.revealRange(rustTextRange);
131
132 const rustSourceCode = this.rustEditor.document.getText(rustTextRange);
133 const astTextRange = this.findAstRange(astTextLine);
134
135 return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astTextRange);
136 }
137
138 private findAstRange(astLine: vscode.TextLine) {
139 const lineOffset = astLine.range.start;
140 const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex);
141 const end = lineOffset.translate(undefined, astLine.text.trimEnd().length);
142 return new vscode.Range(begin, end);
143 }
144
145 private parseRustTextRange(doc: vscode.TextDocument, astLine: string): undefined | vscode.Range {
146 const parsedRange = /\[(\d+); (\d+)\)/.exec(astLine);
147 if (!parsedRange) return;
148
149 const [begin, end] = parsedRange.slice(1).map(off => doc.positionAt(+off));
150
151 return new vscode.Range(begin, end);
152 }
153}
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index e77462c1b..501997fef 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -12,9 +12,9 @@ export class Config {
12 private readonly requiresReloadOpts = [ 12 private readonly requiresReloadOpts = [
13 "serverPath", 13 "serverPath",
14 "cargoFeatures", 14 "cargoFeatures",
15 "cargo-watch", 15 "excludeGlobs",
16 "highlighting.semanticTokens", 16 "useClientWatching",
17 "inlayHints", 17 "highlighting",
18 "updates.channel", 18 "updates.channel",
19 ] 19 ]
20 .map(opt => `${this.rootSection}.${opt}`); 20 .map(opt => `${this.rootSection}.${opt}`);
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index 84c170ea8..86b5f3629 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -2,7 +2,7 @@ import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3 3
4import { Config } from './config'; 4import { Config } from './config';
5import { createClient } from './client'; 5import { createClient, configToServerOptions } from './client';
6import { isRustEditor, RustEditor } from './util'; 6import { isRustEditor, RustEditor } from './util';
7 7
8export class Ctx { 8export class Ctx {
@@ -15,11 +15,17 @@ export class Ctx {
15 15
16 } 16 }
17 17
18 static async create(config: Config, extCtx: vscode.ExtensionContext, serverPath: string): Promise<Ctx> { 18 static async create(
19 const client = await createClient(config, serverPath); 19 config: Config,
20 extCtx: vscode.ExtensionContext,
21 serverPath: string,
22 cwd: string,
23 ): Promise<Ctx> {
24 const client = await createClient(config, serverPath, cwd);
20 const res = new Ctx(config, extCtx, client, serverPath); 25 const res = new Ctx(config, extCtx, client, serverPath);
21 res.pushCleanup(client.start()); 26 res.pushCleanup(client.start());
22 await client.onReady(); 27 await client.onReady();
28 client.onRequest('workspace/configuration', _ => [configToServerOptions(config)]);
23 return res; 29 return res;
24 } 30 }
25 31
diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts
index 542d1f367..6a8bd942e 100644
--- a/editors/code/src/inlay_hints.ts
+++ b/editors/code/src/inlay_hints.ts
@@ -17,7 +17,11 @@ export function activateInlayHints(ctx: Ctx) {
17 ) { 17 ) {
18 return this.dispose(); 18 return this.dispose();
19 } 19 }
20 if (!this.updater) this.updater = new HintsUpdater(ctx); 20 if (this.updater) {
21 this.updater.syncCacheAndRenderHints();
22 } else {
23 this.updater = new HintsUpdater(ctx);
24 }
21 }, 25 },
22 dispose() { 26 dispose() {
23 this.updater?.dispose(); 27 this.updater?.dispose();
@@ -124,7 +128,7 @@ class HintsUpdater implements Disposable {
124 this.syncCacheAndRenderHints(); 128 this.syncCacheAndRenderHints();
125 } 129 }
126 130
127 private syncCacheAndRenderHints() { 131 syncCacheAndRenderHints() {
128 // FIXME: make inlayHints request pass an array of files? 132 // FIXME: make inlayHints request pass an array of files?
129 this.sourceFiles.forEach((file, uri) => this.fetchHints(file).then(hints => { 133 this.sourceFiles.forEach((file, uri) => this.fetchHints(file).then(hints => {
130 if (!hints) return; 134 if (!hints) return;
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 980ed925b..7ba16120c 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -13,6 +13,7 @@ import { log, assert } from './util';
13import { PersistentState } from './persistent_state'; 13import { PersistentState } from './persistent_state';
14import { fetchRelease, download } from './net'; 14import { fetchRelease, download } from './net';
15import { spawnSync } from 'child_process'; 15import { spawnSync } from 'child_process';
16import { activateTaskProvider } from './tasks';
16 17
17let ctx: Ctx | undefined; 18let ctx: Ctx | undefined;
18 19
@@ -41,11 +42,18 @@ export async function activate(context: vscode.ExtensionContext) {
41 const state = new PersistentState(context.globalState); 42 const state = new PersistentState(context.globalState);
42 const serverPath = await bootstrap(config, state); 43 const serverPath = await bootstrap(config, state);
43 44
45 const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
46 if (workspaceFolder === undefined) {
47 const err = "Cannot activate rust-analyzer when no folder is opened";
48 void vscode.window.showErrorMessage(err);
49 throw new Error(err);
50 }
51
44 // Note: we try to start the server before we activate type hints so that it 52 // Note: we try to start the server before we activate type hints so that it
45 // registers its `onDidChangeDocument` handler before us. 53 // registers its `onDidChangeDocument` handler before us.
46 // 54 //
47 // This a horribly, horribly wrong way to deal with this problem. 55 // This a horribly, horribly wrong way to deal with this problem.
48 ctx = await Ctx.create(config, context, serverPath); 56 ctx = await Ctx.create(config, context, serverPath, workspaceFolder.uri.fsPath);
49 57
50 // Commands which invokes manually via command palette, shortcut, etc. 58 // Commands which invokes manually via command palette, shortcut, etc.
51 59
@@ -85,12 +93,20 @@ export async function activate(context: vscode.ExtensionContext) {
85 ctx.registerCommand('applySourceChange', commands.applySourceChange); 93 ctx.registerCommand('applySourceChange', commands.applySourceChange);
86 ctx.registerCommand('selectAndApplySourceChange', commands.selectAndApplySourceChange); 94 ctx.registerCommand('selectAndApplySourceChange', commands.selectAndApplySourceChange);
87 95
96 ctx.pushCleanup(activateTaskProvider(workspaceFolder));
97
88 activateStatusDisplay(ctx); 98 activateStatusDisplay(ctx);
89 99
90 if (!ctx.config.highlightingSemanticTokens) { 100 if (!ctx.config.highlightingSemanticTokens) {
91 activateHighlighting(ctx); 101 activateHighlighting(ctx);
92 } 102 }
93 activateInlayHints(ctx); 103 activateInlayHints(ctx);
104
105 vscode.workspace.onDidChangeConfiguration(
106 _ => ctx?.client?.sendNotification('workspace/didChangeConfiguration', { settings: "" }),
107 null,
108 ctx.subscriptions,
109 );
94} 110}
95 111
96export async function deactivate() { 112export async function deactivate() {
diff --git a/editors/code/src/tasks.ts b/editors/code/src/tasks.ts
new file mode 100644
index 000000000..fa1c4a951
--- /dev/null
+++ b/editors/code/src/tasks.ts
@@ -0,0 +1,52 @@
1import * as vscode from 'vscode';
2
3// This ends up as the `type` key in tasks.json. RLS also uses `cargo` and
4// our configuration should be compatible with it so use the same key.
5const TASK_TYPE = 'cargo';
6
7export function activateTaskProvider(target: vscode.WorkspaceFolder): vscode.Disposable {
8 const provider: vscode.TaskProvider = {
9 // Detect Rust tasks. Currently we do not do any actual detection
10 // of tasks (e.g. aliases in .cargo/config) and just return a fixed
11 // set of tasks that always exist. These tasks cannot be removed in
12 // tasks.json - only tweaked.
13 provideTasks: () => getStandardCargoTasks(target),
14
15 // We don't need to implement this.
16 resolveTask: () => undefined,
17 };
18
19 return vscode.tasks.registerTaskProvider(TASK_TYPE, provider);
20}
21
22function getStandardCargoTasks(target: vscode.WorkspaceFolder): vscode.Task[] {
23 return [
24 { command: 'build', group: vscode.TaskGroup.Build },
25 { command: 'check', group: vscode.TaskGroup.Build },
26 { command: 'test', group: vscode.TaskGroup.Test },
27 { command: 'clean', group: vscode.TaskGroup.Clean },
28 { command: 'run', group: undefined },
29 ]
30 .map(({ command, group }) => {
31 const vscodeTask = new vscode.Task(
32 // The contents of this object end up in the tasks.json entries.
33 {
34 type: TASK_TYPE,
35 command,
36 },
37 // The scope of the task - workspace or specific folder (global
38 // is not supported).
39 target,
40 // The task name, and task source. These are shown in the UI as
41 // `${source}: ${name}`, e.g. `rust: cargo build`.
42 `cargo ${command}`,
43 'rust',
44 // What to do when this command is executed.
45 new vscode.ShellExecution('cargo', [command]),
46 // Problem matchers.
47 ['$rustc'],
48 );
49 vscodeTask.group = group;
50 return vscodeTask;
51 });
52}
diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts
index 978a31751..6f91f81d6 100644
--- a/editors/code/src/util.ts
+++ b/editors/code/src/util.ts
@@ -65,12 +65,12 @@ export async function sendRequestWithRetry<TParam, TRet>(
65 throw 'unreachable'; 65 throw 'unreachable';
66} 66}
67 67
68function sleep(ms: number) { 68export function sleep(ms: number) {
69 return new Promise(resolve => setTimeout(resolve, ms)); 69 return new Promise(resolve => setTimeout(resolve, ms));
70} 70}
71 71
72export type RustDocument = vscode.TextDocument & { languageId: "rust" }; 72export type RustDocument = vscode.TextDocument & { languageId: "rust" };
73export type RustEditor = vscode.TextEditor & { document: RustDocument; id: string }; 73export type RustEditor = vscode.TextEditor & { document: RustDocument };
74 74
75export function isRustDocument(document: vscode.TextDocument): document is RustDocument { 75export function isRustDocument(document: vscode.TextDocument): document is RustDocument {
76 return document.languageId === 'rust' 76 return document.languageId === 'rust'