diff options
-rw-r--r-- | .travis.yml | 1 | ||||
-rw-r--r-- | crates/ra_arena/src/lib.rs | 11 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 8 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/project_model.rs | 207 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/project_model/cargo_workspace.rs | 171 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/project_model/sysroot.rs | 132 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/server_world.rs | 75 | ||||
-rw-r--r-- | crates/ra_lsp_server/tests/heavy_tests/main.rs | 34 | ||||
-rw-r--r-- | crates/ra_lsp_server/tests/heavy_tests/support.rs | 13 |
9 files changed, 448 insertions, 204 deletions
diff --git a/.travis.yml b/.travis.yml index 789adcbe3..12e4da4a3 100644 --- a/.travis.yml +++ b/.travis.yml | |||
@@ -9,6 +9,7 @@ build: &rust_build | |||
9 | rust: 1.31.1 | 9 | rust: 1.31.1 |
10 | script: | 10 | script: |
11 | - rustup component add rustfmt | 11 | - rustup component add rustfmt |
12 | - rustup component add rust-src | ||
12 | - cargo gen-tests --verify | 13 | - cargo gen-tests --verify |
13 | - cargo gen-syntax --verify | 14 | - cargo gen-syntax --verify |
14 | - cargo test --no-run # let's measure compile time separately | 15 | - cargo test --no-run # let's measure compile time separately |
diff --git a/crates/ra_arena/src/lib.rs b/crates/ra_arena/src/lib.rs index 43bfa925a..d7d5d5265 100644 --- a/crates/ra_arena/src/lib.rs +++ b/crates/ra_arena/src/lib.rs | |||
@@ -35,12 +35,21 @@ impl fmt::Display for RawId { | |||
35 | } | 35 | } |
36 | } | 36 | } |
37 | 37 | ||
38 | #[derive(Clone, Debug, PartialEq, Eq)] | 38 | #[derive(Clone, PartialEq, Eq)] |
39 | pub struct Arena<ID: ArenaId, T> { | 39 | pub struct Arena<ID: ArenaId, T> { |
40 | data: Vec<T>, | 40 | data: Vec<T>, |
41 | _ty: PhantomData<ID>, | 41 | _ty: PhantomData<ID>, |
42 | } | 42 | } |
43 | 43 | ||
44 | impl<ID: ArenaId, T: fmt::Debug> fmt::Debug for Arena<ID, T> { | ||
45 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||
46 | fmt.debug_struct("Arena") | ||
47 | .field("len", &self.len()) | ||
48 | .field("data", &self.data) | ||
49 | .finish() | ||
50 | } | ||
51 | } | ||
52 | |||
44 | #[macro_export] | 53 | #[macro_export] |
45 | macro_rules! impl_arena_id { | 54 | macro_rules! impl_arena_id { |
46 | ($name:ident) => { | 55 | ($name:ident) => { |
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index 069e7f932..0dda9548a 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs | |||
@@ -347,11 +347,11 @@ pub fn handle_runnables( | |||
347 | .read() | 347 | .read() |
348 | .file2path(ra_vfs::VfsFile(file_id.0.into())); | 348 | .file2path(ra_vfs::VfsFile(file_id.0.into())); |
349 | let res = world.workspaces.iter().find_map(|ws| { | 349 | let res = world.workspaces.iter().find_map(|ws| { |
350 | let tgt = ws.target_by_root(&path)?; | 350 | let tgt = ws.cargo.target_by_root(&path)?; |
351 | let res = CargoTargetSpec { | 351 | let res = CargoTargetSpec { |
352 | package: tgt.package(ws).name(ws).to_string(), | 352 | package: tgt.package(&ws.cargo).name(&ws.cargo).to_string(), |
353 | target: tgt.name(ws).to_string(), | 353 | target: tgt.name(&ws.cargo).to_string(), |
354 | target_kind: tgt.kind(ws), | 354 | target_kind: tgt.kind(&ws.cargo), |
355 | }; | 355 | }; |
356 | Some(res) | 356 | Some(res) |
357 | }); | 357 | }); |
diff --git a/crates/ra_lsp_server/src/project_model.rs b/crates/ra_lsp_server/src/project_model.rs index 9f429c9a1..fd5875a0a 100644 --- a/crates/ra_lsp_server/src/project_model.rs +++ b/crates/ra_lsp_server/src/project_model.rs | |||
@@ -1,163 +1,46 @@ | |||
1 | mod cargo_workspace; | ||
2 | mod sysroot; | ||
3 | |||
1 | use std::path::{Path, PathBuf}; | 4 | use std::path::{Path, PathBuf}; |
2 | 5 | ||
3 | use cargo_metadata::{metadata_run, CargoOpt}; | 6 | use failure::bail; |
4 | use ra_syntax::SmolStr; | ||
5 | use ra_arena::{Arena, RawId, impl_arena_id}; | ||
6 | use rustc_hash::FxHashMap; | ||
7 | use failure::{format_err, bail}; | ||
8 | use thread_worker::{WorkerHandle, Worker}; | 7 | use thread_worker::{WorkerHandle, Worker}; |
9 | 8 | ||
10 | use crate::Result; | 9 | use crate::Result; |
11 | 10 | ||
12 | /// `CargoWorksapce` represents the logical structure of, well, a Cargo | 11 | pub use crate::project_model::{ |
13 | /// workspace. It pretty closely mirrors `cargo metadata` output. | 12 | cargo_workspace::{CargoWorkspace, Package, Target, TargetKind}, |
14 | /// | 13 | sysroot::Sysroot, |
15 | /// Note that internally, rust analyzer uses a differnet structure: | 14 | }; |
16 | /// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates, | ||
17 | /// while this knows about `Pacakges` & `Targets`: purely cargo-related | ||
18 | /// concepts. | ||
19 | #[derive(Debug, Clone)] | ||
20 | pub struct CargoWorkspace { | ||
21 | packages: Arena<Package, PackageData>, | ||
22 | targets: Arena<Target, TargetData>, | ||
23 | } | ||
24 | |||
25 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
26 | pub struct Package(RawId); | ||
27 | impl_arena_id!(Package); | ||
28 | |||
29 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
30 | pub struct Target(RawId); | ||
31 | impl_arena_id!(Target); | ||
32 | |||
33 | #[derive(Debug, Clone)] | ||
34 | struct PackageData { | ||
35 | name: SmolStr, | ||
36 | manifest: PathBuf, | ||
37 | targets: Vec<Target>, | ||
38 | is_member: bool, | ||
39 | dependencies: Vec<PackageDependency>, | ||
40 | } | ||
41 | 15 | ||
42 | #[derive(Debug, Clone)] | 16 | #[derive(Debug, Clone)] |
43 | pub struct PackageDependency { | 17 | pub struct ProjectWorkspace { |
44 | pub pkg: Package, | 18 | pub(crate) cargo: CargoWorkspace, |
45 | pub name: SmolStr, | 19 | pub(crate) sysroot: Sysroot, |
46 | } | 20 | } |
47 | 21 | ||
48 | #[derive(Debug, Clone)] | 22 | impl ProjectWorkspace { |
49 | struct TargetData { | 23 | pub fn discover(path: &Path) -> Result<ProjectWorkspace> { |
50 | pkg: Package, | 24 | let cargo_toml = find_cargo_toml(path)?; |
51 | name: SmolStr, | 25 | let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml)?; |
52 | root: PathBuf, | 26 | let sysroot = Sysroot::discover(&cargo_toml)?; |
53 | kind: TargetKind, | 27 | let res = ProjectWorkspace { cargo, sysroot }; |
54 | } | 28 | Ok(res) |
55 | |||
56 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
57 | pub enum TargetKind { | ||
58 | Bin, | ||
59 | Lib, | ||
60 | Example, | ||
61 | Test, | ||
62 | Bench, | ||
63 | Other, | ||
64 | } | ||
65 | |||
66 | impl Package { | ||
67 | pub fn name(self, ws: &CargoWorkspace) -> &str { | ||
68 | ws.packages[self].name.as_str() | ||
69 | } | ||
70 | pub fn root(self, ws: &CargoWorkspace) -> &Path { | ||
71 | ws.packages[self].manifest.parent().unwrap() | ||
72 | } | ||
73 | pub fn targets<'a>(self, ws: &'a CargoWorkspace) -> impl Iterator<Item = Target> + 'a { | ||
74 | ws.packages[self].targets.iter().cloned() | ||
75 | } | ||
76 | #[allow(unused)] | ||
77 | pub fn is_member(self, ws: &CargoWorkspace) -> bool { | ||
78 | ws.packages[self].is_member | ||
79 | } | ||
80 | pub fn dependencies<'a>( | ||
81 | self, | ||
82 | ws: &'a CargoWorkspace, | ||
83 | ) -> impl Iterator<Item = &'a PackageDependency> + 'a { | ||
84 | ws.packages[self].dependencies.iter() | ||
85 | } | ||
86 | } | ||
87 | |||
88 | impl Target { | ||
89 | pub fn package(self, ws: &CargoWorkspace) -> Package { | ||
90 | ws.targets[self].pkg | ||
91 | } | ||
92 | pub fn name(self, ws: &CargoWorkspace) -> &str { | ||
93 | ws.targets[self].name.as_str() | ||
94 | } | ||
95 | pub fn root(self, ws: &CargoWorkspace) -> &Path { | ||
96 | ws.targets[self].root.as_path() | ||
97 | } | ||
98 | pub fn kind(self, ws: &CargoWorkspace) -> TargetKind { | ||
99 | ws.targets[self].kind | ||
100 | } | 29 | } |
101 | } | 30 | } |
102 | 31 | ||
103 | impl CargoWorkspace { | 32 | pub fn workspace_loader() -> (Worker<PathBuf, Result<ProjectWorkspace>>, WorkerHandle) { |
104 | pub fn from_cargo_metadata(path: &Path) -> Result<CargoWorkspace> { | 33 | thread_worker::spawn::<PathBuf, Result<ProjectWorkspace>, _>( |
105 | let cargo_toml = find_cargo_toml(path)?; | 34 | "workspace loader", |
106 | let meta = metadata_run( | 35 | 1, |
107 | Some(cargo_toml.as_path()), | 36 | |input_receiver, output_sender| { |
108 | true, | 37 | input_receiver |
109 | Some(CargoOpt::AllFeatures), | 38 | .into_iter() |
110 | ) | 39 | .map(|path| ProjectWorkspace::discover(path.as_path())) |
111 | .map_err(|e| format_err!("cargo metadata failed: {}", e))?; | 40 | .try_for_each(|it| output_sender.send(it)) |
112 | let mut pkg_by_id = FxHashMap::default(); | 41 | .unwrap() |
113 | let mut packages = Arena::default(); | 42 | }, |
114 | let mut targets = Arena::default(); | 43 | ) |
115 | |||
116 | let ws_members = &meta.workspace_members; | ||
117 | |||
118 | for meta_pkg in meta.packages { | ||
119 | let is_member = ws_members.contains(&meta_pkg.id); | ||
120 | let pkg = packages.alloc(PackageData { | ||
121 | name: meta_pkg.name.into(), | ||
122 | manifest: meta_pkg.manifest_path.clone(), | ||
123 | targets: Vec::new(), | ||
124 | is_member, | ||
125 | dependencies: Vec::new(), | ||
126 | }); | ||
127 | let pkg_data = &mut packages[pkg]; | ||
128 | pkg_by_id.insert(meta_pkg.id.clone(), pkg); | ||
129 | for meta_tgt in meta_pkg.targets { | ||
130 | let tgt = targets.alloc(TargetData { | ||
131 | pkg, | ||
132 | name: meta_tgt.name.into(), | ||
133 | root: meta_tgt.src_path.clone(), | ||
134 | kind: TargetKind::new(meta_tgt.kind.as_slice()), | ||
135 | }); | ||
136 | pkg_data.targets.push(tgt); | ||
137 | } | ||
138 | } | ||
139 | let resolve = meta.resolve.expect("metadata executed with deps"); | ||
140 | for node in resolve.nodes { | ||
141 | let source = pkg_by_id[&node.id]; | ||
142 | for dep_node in node.deps { | ||
143 | let dep = PackageDependency { | ||
144 | name: dep_node.name.into(), | ||
145 | pkg: pkg_by_id[&dep_node.pkg], | ||
146 | }; | ||
147 | packages[source].dependencies.push(dep); | ||
148 | } | ||
149 | } | ||
150 | |||
151 | Ok(CargoWorkspace { packages, targets }) | ||
152 | } | ||
153 | pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + 'a { | ||
154 | self.packages.iter().map(|(id, _pkg)| id) | ||
155 | } | ||
156 | pub fn target_by_root(&self, root: &Path) -> Option<Target> { | ||
157 | self.packages() | ||
158 | .filter_map(|pkg| pkg.targets(self).find(|it| it.root(self) == root)) | ||
159 | .next() | ||
160 | } | ||
161 | } | 44 | } |
162 | 45 | ||
163 | fn find_cargo_toml(path: &Path) -> Result<PathBuf> { | 46 | fn find_cargo_toml(path: &Path) -> Result<PathBuf> { |
@@ -174,33 +57,3 @@ fn find_cargo_toml(path: &Path) -> Result<PathBuf> { | |||
174 | } | 57 | } |
175 | bail!("can't find Cargo.toml at {}", path.display()) | 58 | bail!("can't find Cargo.toml at {}", path.display()) |
176 | } | 59 | } |
177 | |||
178 | impl TargetKind { | ||
179 | fn new(kinds: &[String]) -> TargetKind { | ||
180 | for kind in kinds { | ||
181 | return match kind.as_str() { | ||
182 | "bin" => TargetKind::Bin, | ||
183 | "test" => TargetKind::Test, | ||
184 | "bench" => TargetKind::Bench, | ||
185 | "example" => TargetKind::Example, | ||
186 | _ if kind.contains("lib") => TargetKind::Lib, | ||
187 | _ => continue, | ||
188 | }; | ||
189 | } | ||
190 | TargetKind::Other | ||
191 | } | ||
192 | } | ||
193 | |||
194 | pub fn workspace_loader() -> (Worker<PathBuf, Result<CargoWorkspace>>, WorkerHandle) { | ||
195 | thread_worker::spawn::<PathBuf, Result<CargoWorkspace>, _>( | ||
196 | "workspace loader", | ||
197 | 1, | ||
198 | |input_receiver, output_sender| { | ||
199 | input_receiver | ||
200 | .into_iter() | ||
201 | .map(|path| CargoWorkspace::from_cargo_metadata(path.as_path())) | ||
202 | .try_for_each(|it| output_sender.send(it)) | ||
203 | .unwrap() | ||
204 | }, | ||
205 | ) | ||
206 | } | ||
diff --git a/crates/ra_lsp_server/src/project_model/cargo_workspace.rs b/crates/ra_lsp_server/src/project_model/cargo_workspace.rs new file mode 100644 index 000000000..8f7518860 --- /dev/null +++ b/crates/ra_lsp_server/src/project_model/cargo_workspace.rs | |||
@@ -0,0 +1,171 @@ | |||
1 | use std::path::{Path, PathBuf}; | ||
2 | |||
3 | use cargo_metadata::{metadata_run, CargoOpt}; | ||
4 | use ra_syntax::SmolStr; | ||
5 | use ra_arena::{Arena, RawId, impl_arena_id}; | ||
6 | use rustc_hash::FxHashMap; | ||
7 | use failure::format_err; | ||
8 | |||
9 | use crate::Result; | ||
10 | |||
11 | /// `CargoWorksapce` represents the logical structure of, well, a Cargo | ||
12 | /// workspace. It pretty closely mirrors `cargo metadata` output. | ||
13 | /// | ||
14 | /// Note that internally, rust analyzer uses a differnet structure: | ||
15 | /// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates, | ||
16 | /// while this knows about `Pacakges` & `Targets`: purely cargo-related | ||
17 | /// concepts. | ||
18 | #[derive(Debug, Clone)] | ||
19 | pub struct CargoWorkspace { | ||
20 | packages: Arena<Package, PackageData>, | ||
21 | targets: Arena<Target, TargetData>, | ||
22 | } | ||
23 | |||
24 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
25 | pub struct Package(RawId); | ||
26 | impl_arena_id!(Package); | ||
27 | |||
28 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
29 | pub struct Target(RawId); | ||
30 | impl_arena_id!(Target); | ||
31 | |||
32 | #[derive(Debug, Clone)] | ||
33 | struct PackageData { | ||
34 | name: SmolStr, | ||
35 | manifest: PathBuf, | ||
36 | targets: Vec<Target>, | ||
37 | is_member: bool, | ||
38 | dependencies: Vec<PackageDependency>, | ||
39 | } | ||
40 | |||
41 | #[derive(Debug, Clone)] | ||
42 | pub struct PackageDependency { | ||
43 | pub pkg: Package, | ||
44 | pub name: SmolStr, | ||
45 | } | ||
46 | |||
47 | #[derive(Debug, Clone)] | ||
48 | struct TargetData { | ||
49 | pkg: Package, | ||
50 | name: SmolStr, | ||
51 | root: PathBuf, | ||
52 | kind: TargetKind, | ||
53 | } | ||
54 | |||
55 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
56 | pub enum TargetKind { | ||
57 | Bin, | ||
58 | Lib, | ||
59 | Example, | ||
60 | Test, | ||
61 | Bench, | ||
62 | Other, | ||
63 | } | ||
64 | |||
65 | impl TargetKind { | ||
66 | fn new(kinds: &[String]) -> TargetKind { | ||
67 | for kind in kinds { | ||
68 | return match kind.as_str() { | ||
69 | "bin" => TargetKind::Bin, | ||
70 | "test" => TargetKind::Test, | ||
71 | "bench" => TargetKind::Bench, | ||
72 | "example" => TargetKind::Example, | ||
73 | _ if kind.contains("lib") => TargetKind::Lib, | ||
74 | _ => continue, | ||
75 | }; | ||
76 | } | ||
77 | TargetKind::Other | ||
78 | } | ||
79 | } | ||
80 | |||
81 | impl Package { | ||
82 | pub fn name(self, ws: &CargoWorkspace) -> &str { | ||
83 | ws.packages[self].name.as_str() | ||
84 | } | ||
85 | pub fn root(self, ws: &CargoWorkspace) -> &Path { | ||
86 | ws.packages[self].manifest.parent().unwrap() | ||
87 | } | ||
88 | pub fn targets<'a>(self, ws: &'a CargoWorkspace) -> impl Iterator<Item = Target> + 'a { | ||
89 | ws.packages[self].targets.iter().cloned() | ||
90 | } | ||
91 | #[allow(unused)] | ||
92 | pub fn is_member(self, ws: &CargoWorkspace) -> bool { | ||
93 | ws.packages[self].is_member | ||
94 | } | ||
95 | pub fn dependencies<'a>( | ||
96 | self, | ||
97 | ws: &'a CargoWorkspace, | ||
98 | ) -> impl Iterator<Item = &'a PackageDependency> + 'a { | ||
99 | ws.packages[self].dependencies.iter() | ||
100 | } | ||
101 | } | ||
102 | |||
103 | impl Target { | ||
104 | pub fn package(self, ws: &CargoWorkspace) -> Package { | ||
105 | ws.targets[self].pkg | ||
106 | } | ||
107 | pub fn name(self, ws: &CargoWorkspace) -> &str { | ||
108 | ws.targets[self].name.as_str() | ||
109 | } | ||
110 | pub fn root(self, ws: &CargoWorkspace) -> &Path { | ||
111 | ws.targets[self].root.as_path() | ||
112 | } | ||
113 | pub fn kind(self, ws: &CargoWorkspace) -> TargetKind { | ||
114 | ws.targets[self].kind | ||
115 | } | ||
116 | } | ||
117 | |||
118 | impl CargoWorkspace { | ||
119 | pub fn from_cargo_metadata(cargo_toml: &Path) -> Result<CargoWorkspace> { | ||
120 | let meta = metadata_run(Some(cargo_toml), true, Some(CargoOpt::AllFeatures)) | ||
121 | .map_err(|e| format_err!("cargo metadata failed: {}", e))?; | ||
122 | let mut pkg_by_id = FxHashMap::default(); | ||
123 | let mut packages = Arena::default(); | ||
124 | let mut targets = Arena::default(); | ||
125 | |||
126 | let ws_members = &meta.workspace_members; | ||
127 | |||
128 | for meta_pkg in meta.packages { | ||
129 | let is_member = ws_members.contains(&meta_pkg.id); | ||
130 | let pkg = packages.alloc(PackageData { | ||
131 | name: meta_pkg.name.into(), | ||
132 | manifest: meta_pkg.manifest_path.clone(), | ||
133 | targets: Vec::new(), | ||
134 | is_member, | ||
135 | dependencies: Vec::new(), | ||
136 | }); | ||
137 | let pkg_data = &mut packages[pkg]; | ||
138 | pkg_by_id.insert(meta_pkg.id.clone(), pkg); | ||
139 | for meta_tgt in meta_pkg.targets { | ||
140 | let tgt = targets.alloc(TargetData { | ||
141 | pkg, | ||
142 | name: meta_tgt.name.into(), | ||
143 | root: meta_tgt.src_path.clone(), | ||
144 | kind: TargetKind::new(meta_tgt.kind.as_slice()), | ||
145 | }); | ||
146 | pkg_data.targets.push(tgt); | ||
147 | } | ||
148 | } | ||
149 | let resolve = meta.resolve.expect("metadata executed with deps"); | ||
150 | for node in resolve.nodes { | ||
151 | let source = pkg_by_id[&node.id]; | ||
152 | for dep_node in node.deps { | ||
153 | let dep = PackageDependency { | ||
154 | name: dep_node.name.into(), | ||
155 | pkg: pkg_by_id[&dep_node.pkg], | ||
156 | }; | ||
157 | packages[source].dependencies.push(dep); | ||
158 | } | ||
159 | } | ||
160 | |||
161 | Ok(CargoWorkspace { packages, targets }) | ||
162 | } | ||
163 | pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + 'a { | ||
164 | self.packages.iter().map(|(id, _pkg)| id) | ||
165 | } | ||
166 | pub fn target_by_root(&self, root: &Path) -> Option<Target> { | ||
167 | self.packages() | ||
168 | .filter_map(|pkg| pkg.targets(self).find(|it| it.root(self) == root)) | ||
169 | .next() | ||
170 | } | ||
171 | } | ||
diff --git a/crates/ra_lsp_server/src/project_model/sysroot.rs b/crates/ra_lsp_server/src/project_model/sysroot.rs new file mode 100644 index 000000000..1dbab57f8 --- /dev/null +++ b/crates/ra_lsp_server/src/project_model/sysroot.rs | |||
@@ -0,0 +1,132 @@ | |||
1 | use std::{ | ||
2 | path::{Path, PathBuf}, | ||
3 | process::Command, | ||
4 | }; | ||
5 | |||
6 | use ra_syntax::SmolStr; | ||
7 | use ra_arena::{Arena, RawId, impl_arena_id}; | ||
8 | |||
9 | use crate::Result; | ||
10 | |||
11 | #[derive(Debug, Clone)] | ||
12 | pub struct Sysroot { | ||
13 | crates: Arena<SysrootCrate, SysrootCrateData>, | ||
14 | } | ||
15 | |||
16 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
17 | pub struct SysrootCrate(RawId); | ||
18 | impl_arena_id!(SysrootCrate); | ||
19 | |||
20 | #[derive(Debug, Clone)] | ||
21 | struct SysrootCrateData { | ||
22 | name: SmolStr, | ||
23 | root: PathBuf, | ||
24 | deps: Vec<SysrootCrate>, | ||
25 | } | ||
26 | |||
27 | impl Sysroot { | ||
28 | pub(crate) fn std(&self) -> Option<SysrootCrate> { | ||
29 | self.by_name("std") | ||
30 | } | ||
31 | |||
32 | pub(crate) fn crates<'a>(&'a self) -> impl Iterator<Item = SysrootCrate> + 'a { | ||
33 | self.crates.iter().map(|(id, _data)| id) | ||
34 | } | ||
35 | |||
36 | pub(super) fn discover(cargo_toml: &Path) -> Result<Sysroot> { | ||
37 | let rustc_output = Command::new("rustc") | ||
38 | .current_dir(cargo_toml.parent().unwrap()) | ||
39 | .args(&["--print", "sysroot"]) | ||
40 | .output()?; | ||
41 | if !rustc_output.status.success() { | ||
42 | failure::bail!("failed to locate sysroot") | ||
43 | } | ||
44 | let stdout = String::from_utf8(rustc_output.stdout)?; | ||
45 | let sysroot_path = Path::new(stdout.trim()); | ||
46 | let src = sysroot_path.join("lib/rustlib/src/rust/src"); | ||
47 | |||
48 | let mut sysroot = Sysroot { | ||
49 | crates: Arena::default(), | ||
50 | }; | ||
51 | for name in SYSROOT_CRATES.trim().lines() { | ||
52 | let root = src.join(format!("lib{}", name)).join("lib.rs"); | ||
53 | if root.exists() { | ||
54 | sysroot.crates.alloc(SysrootCrateData { | ||
55 | name: name.into(), | ||
56 | root, | ||
57 | deps: Vec::new(), | ||
58 | }); | ||
59 | } | ||
60 | } | ||
61 | if let Some(std) = sysroot.std() { | ||
62 | for dep in STD_DEPS.trim().lines() { | ||
63 | if let Some(dep) = sysroot.by_name(dep) { | ||
64 | sysroot.crates[std].deps.push(dep) | ||
65 | } | ||
66 | } | ||
67 | } | ||
68 | Ok(sysroot) | ||
69 | } | ||
70 | |||
71 | fn by_name(&self, name: &str) -> Option<SysrootCrate> { | ||
72 | self.crates | ||
73 | .iter() | ||
74 | .find(|(_id, data)| data.name == name) | ||
75 | .map(|(id, _data)| id) | ||
76 | } | ||
77 | } | ||
78 | |||
79 | impl SysrootCrate { | ||
80 | pub(crate) fn name(self, sysroot: &Sysroot) -> &SmolStr { | ||
81 | &sysroot.crates[self].name | ||
82 | } | ||
83 | pub(crate) fn root(self, sysroot: &Sysroot) -> &Path { | ||
84 | sysroot.crates[self].root.as_path() | ||
85 | } | ||
86 | pub(crate) fn root_dir(self, sysroot: &Sysroot) -> &Path { | ||
87 | self.root(sysroot).parent().unwrap() | ||
88 | } | ||
89 | pub(crate) fn deps<'a>(self, sysroot: &'a Sysroot) -> impl Iterator<Item = SysrootCrate> + 'a { | ||
90 | sysroot.crates[self].deps.iter().map(|&it| it) | ||
91 | } | ||
92 | } | ||
93 | |||
94 | const SYSROOT_CRATES: &str = " | ||
95 | std | ||
96 | core | ||
97 | alloc | ||
98 | collections | ||
99 | libc | ||
100 | panic_unwind | ||
101 | proc_macro | ||
102 | rustc_unicode | ||
103 | std_unicode | ||
104 | test | ||
105 | alloc_jemalloc | ||
106 | alloc_system | ||
107 | compiler_builtins | ||
108 | getopts | ||
109 | panic_unwind | ||
110 | panic_abort | ||
111 | rand | ||
112 | term | ||
113 | unwind | ||
114 | build_helper | ||
115 | rustc_asan | ||
116 | rustc_lsan | ||
117 | rustc_msan | ||
118 | rustc_tsan | ||
119 | syntax"; | ||
120 | |||
121 | const STD_DEPS: &str = " | ||
122 | alloc_jemalloc | ||
123 | alloc_system | ||
124 | panic_abort | ||
125 | rand | ||
126 | compiler_builtins | ||
127 | unwind | ||
128 | rustc_asan | ||
129 | rustc_lsan | ||
130 | rustc_msan | ||
131 | rustc_tsan | ||
132 | build_helper"; | ||
diff --git a/crates/ra_lsp_server/src/server_world.rs b/crates/ra_lsp_server/src/server_world.rs index 76c76766d..4f3c231d3 100644 --- a/crates/ra_lsp_server/src/server_world.rs +++ b/crates/ra_lsp_server/src/server_world.rs | |||
@@ -15,7 +15,7 @@ use parking_lot::RwLock; | |||
15 | use failure::format_err; | 15 | use failure::format_err; |
16 | 16 | ||
17 | use crate::{ | 17 | use crate::{ |
18 | project_model::{CargoWorkspace, TargetKind}, | 18 | project_model::{ProjectWorkspace, TargetKind}, |
19 | Result, | 19 | Result, |
20 | }; | 20 | }; |
21 | 21 | ||
@@ -23,28 +23,33 @@ use crate::{ | |||
23 | pub struct ServerWorldState { | 23 | pub struct ServerWorldState { |
24 | pub roots_to_scan: usize, | 24 | pub roots_to_scan: usize, |
25 | pub root: PathBuf, | 25 | pub root: PathBuf, |
26 | pub workspaces: Arc<Vec<CargoWorkspace>>, | 26 | pub workspaces: Arc<Vec<ProjectWorkspace>>, |
27 | pub analysis_host: AnalysisHost, | 27 | pub analysis_host: AnalysisHost, |
28 | pub vfs: Arc<RwLock<Vfs>>, | 28 | pub vfs: Arc<RwLock<Vfs>>, |
29 | } | 29 | } |
30 | 30 | ||
31 | pub struct ServerWorld { | 31 | pub struct ServerWorld { |
32 | pub workspaces: Arc<Vec<CargoWorkspace>>, | 32 | pub workspaces: Arc<Vec<ProjectWorkspace>>, |
33 | pub analysis: Analysis, | 33 | pub analysis: Analysis, |
34 | pub vfs: Arc<RwLock<Vfs>>, | 34 | pub vfs: Arc<RwLock<Vfs>>, |
35 | } | 35 | } |
36 | 36 | ||
37 | impl ServerWorldState { | 37 | impl ServerWorldState { |
38 | pub fn new(root: PathBuf, workspaces: Vec<CargoWorkspace>) -> ServerWorldState { | 38 | pub fn new(root: PathBuf, workspaces: Vec<ProjectWorkspace>) -> ServerWorldState { |
39 | let mut change = AnalysisChange::new(); | 39 | let mut change = AnalysisChange::new(); |
40 | 40 | ||
41 | let mut roots = Vec::new(); | 41 | let mut roots = Vec::new(); |
42 | roots.push(root.clone()); | 42 | roots.push(root.clone()); |
43 | for ws in workspaces.iter() { | 43 | for ws in workspaces.iter() { |
44 | for pkg in ws.packages() { | 44 | for pkg in ws.cargo.packages() { |
45 | roots.push(pkg.root(&ws).to_path_buf()); | 45 | roots.push(pkg.root(&ws.cargo).to_path_buf()); |
46 | } | ||
47 | for krate in ws.sysroot.crates() { | ||
48 | roots.push(krate.root_dir(&ws.sysroot).to_path_buf()) | ||
46 | } | 49 | } |
47 | } | 50 | } |
51 | roots.sort(); | ||
52 | roots.dedup(); | ||
48 | let roots_to_scan = roots.len(); | 53 | let roots_to_scan = roots.len(); |
49 | let (mut vfs, roots) = Vfs::new(roots); | 54 | let (mut vfs, roots) = Vfs::new(roots); |
50 | for r in roots { | 55 | for r in roots { |
@@ -53,16 +58,43 @@ impl ServerWorldState { | |||
53 | } | 58 | } |
54 | 59 | ||
55 | let mut crate_graph = CrateGraph::default(); | 60 | let mut crate_graph = CrateGraph::default(); |
56 | let mut pkg_to_lib_crate = FxHashMap::default(); | ||
57 | let mut pkg_crates = FxHashMap::default(); | ||
58 | for ws in workspaces.iter() { | 61 | for ws in workspaces.iter() { |
59 | for pkg in ws.packages() { | 62 | // First, load std |
60 | for tgt in pkg.targets(ws) { | 63 | let mut sysroot_crates = FxHashMap::default(); |
61 | let root = tgt.root(ws); | 64 | for krate in ws.sysroot.crates() { |
65 | if let Some(file_id) = vfs.load(krate.root(&ws.sysroot)) { | ||
66 | let file_id = FileId(file_id.0.into()); | ||
67 | sysroot_crates.insert(krate, crate_graph.add_crate_root(file_id)); | ||
68 | } | ||
69 | } | ||
70 | for from in ws.sysroot.crates() { | ||
71 | for to in from.deps(&ws.sysroot) { | ||
72 | let name = to.name(&ws.sysroot); | ||
73 | if let (Some(&from), Some(&to)) = | ||
74 | (sysroot_crates.get(&from), sysroot_crates.get(&to)) | ||
75 | { | ||
76 | crate_graph.add_dep(from, name.clone(), to); | ||
77 | } | ||
78 | } | ||
79 | } | ||
80 | |||
81 | let libstd = ws | ||
82 | .sysroot | ||
83 | .std() | ||
84 | .and_then(|it| sysroot_crates.get(&it).map(|&it| it)); | ||
85 | |||
86 | let mut pkg_to_lib_crate = FxHashMap::default(); | ||
87 | let mut pkg_crates = FxHashMap::default(); | ||
88 | // Next, create crates for each package, target pair | ||
89 | for pkg in ws.cargo.packages() { | ||
90 | let mut lib_tgt = None; | ||
91 | for tgt in pkg.targets(&ws.cargo) { | ||
92 | let root = tgt.root(&ws.cargo); | ||
62 | if let Some(file_id) = vfs.load(root) { | 93 | if let Some(file_id) = vfs.load(root) { |
63 | let file_id = FileId(file_id.0.into()); | 94 | let file_id = FileId(file_id.0.into()); |
64 | let crate_id = crate_graph.add_crate_root(file_id); | 95 | let crate_id = crate_graph.add_crate_root(file_id); |
65 | if tgt.kind(ws) == TargetKind::Lib { | 96 | if tgt.kind(&ws.cargo) == TargetKind::Lib { |
97 | lib_tgt = Some(crate_id); | ||
66 | pkg_to_lib_crate.insert(pkg, crate_id); | 98 | pkg_to_lib_crate.insert(pkg, crate_id); |
67 | } | 99 | } |
68 | pkg_crates | 100 | pkg_crates |
@@ -71,9 +103,24 @@ impl ServerWorldState { | |||
71 | .push(crate_id); | 103 | .push(crate_id); |
72 | } | 104 | } |
73 | } | 105 | } |
106 | |||
107 | // Set deps to the std and to the lib target of the current package | ||
108 | for &from in pkg_crates.get(&pkg).into_iter().flatten() { | ||
109 | if let Some(to) = lib_tgt { | ||
110 | if to != from { | ||
111 | crate_graph.add_dep(from, pkg.name(&ws.cargo).into(), to); | ||
112 | } | ||
113 | } | ||
114 | if let Some(std) = libstd { | ||
115 | crate_graph.add_dep(from, "std".into(), std); | ||
116 | } | ||
117 | } | ||
74 | } | 118 | } |
75 | for pkg in ws.packages() { | 119 | |
76 | for dep in pkg.dependencies(ws) { | 120 | // Now add a dep ednge from all targets of upstream to the lib |
121 | // target of downstream. | ||
122 | for pkg in ws.cargo.packages() { | ||
123 | for dep in pkg.dependencies(&ws.cargo) { | ||
77 | if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { | 124 | if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { |
78 | for &from in pkg_crates.get(&pkg).into_iter().flatten() { | 125 | for &from in pkg_crates.get(&pkg).into_iter().flatten() { |
79 | crate_graph.add_dep(from, dep.name.clone(), to); | 126 | crate_graph.add_dep(from, dep.name.clone(), to); |
diff --git a/crates/ra_lsp_server/tests/heavy_tests/main.rs b/crates/ra_lsp_server/tests/heavy_tests/main.rs index 9eaf46ac8..02d62a259 100644 --- a/crates/ra_lsp_server/tests/heavy_tests/main.rs +++ b/crates/ra_lsp_server/tests/heavy_tests/main.rs | |||
@@ -1,10 +1,15 @@ | |||
1 | mod support; | 1 | mod support; |
2 | 2 | ||
3 | use std::{ | ||
4 | collections::HashMap, | ||
5 | time::Instant, | ||
6 | }; | ||
7 | |||
3 | use languageserver_types::{ | 8 | use languageserver_types::{ |
4 | CodeActionContext, DocumentFormattingParams, FormattingOptions, Position, Range, | 9 | CodeActionContext, DocumentFormattingParams, FormattingOptions, Position, Range, |
5 | }; | 10 | }; |
6 | use ra_lsp_server::req::{ | 11 | use ra_lsp_server::req::{ |
7 | CodeActionParams, CodeActionRequest, Formatting, Runnables, RunnablesParams, | 12 | CodeActionParams, CodeActionRequest, Formatting, Runnables, RunnablesParams, CompletionParams, Completion, |
8 | }; | 13 | }; |
9 | use serde_json::json; | 14 | use serde_json::json; |
10 | 15 | ||
@@ -13,6 +18,32 @@ use crate::support::project; | |||
13 | const LOG: &'static str = ""; | 18 | const LOG: &'static str = ""; |
14 | 19 | ||
15 | #[test] | 20 | #[test] |
21 | fn completes_items_from_standard_library() { | ||
22 | let project_start = Instant::now(); | ||
23 | let server = project( | ||
24 | r#" | ||
25 | //- Cargo.toml | ||
26 | [package] | ||
27 | name = "foo" | ||
28 | version = "0.0.0" | ||
29 | |||
30 | //- src/lib.rs | ||
31 | use std::collections::Spam; | ||
32 | "#, | ||
33 | ); | ||
34 | server.wait_for_feedback("workspace loaded"); | ||
35 | eprintln!("loading took {:?}", project_start.elapsed()); | ||
36 | let completion_start = Instant::now(); | ||
37 | let res = server.send_request::<Completion>(CompletionParams { | ||
38 | text_document: server.doc_id("src/lib.rs"), | ||
39 | context: None, | ||
40 | position: Position::new(0, 23), | ||
41 | }); | ||
42 | assert!(format!("{}", res).contains("HashMap")); | ||
43 | eprintln!("completion took {:?}", completion_start.elapsed()); | ||
44 | } | ||
45 | |||
46 | #[test] | ||
16 | fn test_runnables_no_project() { | 47 | fn test_runnables_no_project() { |
17 | let server = project( | 48 | let server = project( |
18 | r" | 49 | r" |
@@ -122,7 +153,6 @@ fn test_eggs() {} | |||
122 | ); | 153 | ); |
123 | } | 154 | } |
124 | 155 | ||
125 | use std::collections::HashMap; | ||
126 | #[test] | 156 | #[test] |
127 | fn test_format_document() { | 157 | fn test_format_document() { |
128 | let server = project( | 158 | let server = project( |
diff --git a/crates/ra_lsp_server/tests/heavy_tests/support.rs b/crates/ra_lsp_server/tests/heavy_tests/support.rs index 7db168b0f..46107b6b6 100644 --- a/crates/ra_lsp_server/tests/heavy_tests/support.rs +++ b/crates/ra_lsp_server/tests/heavy_tests/support.rs | |||
@@ -93,9 +93,7 @@ impl Server { | |||
93 | R: Request, | 93 | R: Request, |
94 | R::Params: Serialize, | 94 | R::Params: Serialize, |
95 | { | 95 | { |
96 | let id = self.req_id.get(); | 96 | let actual = self.send_request::<R>(params); |
97 | self.req_id.set(id + 1); | ||
98 | let actual = self.send_request::<R>(id, params); | ||
99 | match find_mismatch(&expected_resp, &actual) { | 97 | match find_mismatch(&expected_resp, &actual) { |
100 | Some((expected_part, actual_part)) => panic!( | 98 | Some((expected_part, actual_part)) => panic!( |
101 | "JSON mismatch\nExpected:\n{}\nWas:\n{}\nExpected part:\n{}\nActual part:\n{}\n", | 99 | "JSON mismatch\nExpected:\n{}\nWas:\n{}\nExpected part:\n{}\nActual part:\n{}\n", |
@@ -108,11 +106,14 @@ impl Server { | |||
108 | } | 106 | } |
109 | } | 107 | } |
110 | 108 | ||
111 | fn send_request<R>(&self, id: u64, params: R::Params) -> Value | 109 | pub fn send_request<R>(&self, params: R::Params) -> Value |
112 | where | 110 | where |
113 | R: Request, | 111 | R: Request, |
114 | R::Params: Serialize, | 112 | R::Params: Serialize, |
115 | { | 113 | { |
114 | let id = self.req_id.get(); | ||
115 | self.req_id.set(id + 1); | ||
116 | |||
116 | let r = RawRequest::new::<R>(id, ¶ms); | 117 | let r = RawRequest::new::<R>(id, ¶ms); |
117 | self.send_request_(r) | 118 | self.send_request_(r) |
118 | } | 119 | } |
@@ -178,7 +179,7 @@ impl Server { | |||
178 | 179 | ||
179 | impl Drop for Server { | 180 | impl Drop for Server { |
180 | fn drop(&mut self) { | 181 | fn drop(&mut self) { |
181 | self.send_request::<Shutdown>(666, ()); | 182 | self.send_request::<Shutdown>(()); |
182 | let receiver = self.worker.take().unwrap().shutdown(); | 183 | let receiver = self.worker.take().unwrap().shutdown(); |
183 | while let Some(msg) = recv_timeout(&receiver) { | 184 | while let Some(msg) = recv_timeout(&receiver) { |
184 | drop(msg); | 185 | drop(msg); |
@@ -188,7 +189,7 @@ impl Drop for Server { | |||
188 | } | 189 | } |
189 | 190 | ||
190 | fn recv_timeout(receiver: &Receiver<RawMessage>) -> Option<RawMessage> { | 191 | fn recv_timeout(receiver: &Receiver<RawMessage>) -> Option<RawMessage> { |
191 | let timeout = Duration::from_secs(5); | 192 | let timeout = Duration::from_secs(50); |
192 | select! { | 193 | select! { |
193 | recv(receiver) -> msg => msg.ok(), | 194 | recv(receiver) -> msg => msg.ok(), |
194 | recv(after(timeout)) -> _ => panic!("timed out"), | 195 | recv(after(timeout)) -> _ => panic!("timed out"), |