aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-04-14 18:26:07 +0100
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-04-14 18:26:07 +0100
commit88be6f32172813f53dae60d73c9f5deb0c3fb29f (patch)
tree0b5f8f793929f651dfe332e0f5545b938ff5189f /crates
parent5d35f284f5ac70cde5d758e7c63a38eae0fb0b55 (diff)
parentc2dfc8a229c0a18dff08d5ce7e6836c91648eee5 (diff)
Merge #1137
1137: Adds support for multiple editor workspaces on initialization r=matklad a=jrvidal OK, so this "simple hack" turned out to be way more contrived than I expected :joy: ### What works This patch only handles multi-folder editor workspaces _on initialization_. * I've found that modifying the layout of a workspace in VSCode just reloads the extension, so this hack should be enough for now. * Not sure about how emacs-lsp behaves, but we fallback gracefully to the mono-folder workspace, so it should be fine. ### What doesn't work * [x] `cargo watch` can only watch a single root folder with a `Cargo.toml`. I've left this part untouched but we could either warn that it's not supported or launch _multiple_ `cargo-watch` processes. * [x] The `rust-analyzer/runnables` command is not functional, since we don't send the correct `cwd`. * [x] Should we add some happy path test to `heavy_tests`? * [ ] Going from a single `root` to multiple `roots` leaves us with a couple of `n * m` loops that smell a bit. The number of folders in the editor workspace is probably low though. Co-authored-by: Roberto Vidal <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_lsp_server/src/main.rs13
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs31
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs5
-rw-r--r--crates/ra_lsp_server/src/req.rs1
-rw-r--r--crates/ra_lsp_server/src/server_world.rs27
-rw-r--r--crates/ra_lsp_server/tests/heavy_tests/main.rs37
-rw-r--r--crates/ra_lsp_server/tests/heavy_tests/support.rs68
-rw-r--r--crates/ra_project_model/src/cargo_workspace.rs3
-rw-r--r--crates/ra_project_model/src/lib.rs12
9 files changed, 142 insertions, 55 deletions
diff --git a/crates/ra_lsp_server/src/main.rs b/crates/ra_lsp_server/src/main.rs
index eb4091a3d..82f52a6e8 100644
--- a/crates/ra_lsp_server/src/main.rs
+++ b/crates/ra_lsp_server/src/main.rs
@@ -40,12 +40,23 @@ fn main_inner() -> Result<()> {
40 run_server(ra_lsp_server::server_capabilities(), receiver, sender, |params, r, s| { 40 run_server(ra_lsp_server::server_capabilities(), receiver, sender, |params, r, s| {
41 let root = params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd); 41 let root = params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd);
42 42
43 let workspace_roots = params
44 .workspace_folders
45 .map(|workspaces| {
46 workspaces
47 .into_iter()
48 .filter_map(|it| it.uri.to_file_path().ok())
49 .collect::<Vec<_>>()
50 })
51 .filter(|workspaces| !workspaces.is_empty())
52 .unwrap_or_else(|| vec![root]);
53
43 let opts = params 54 let opts = params
44 .initialization_options 55 .initialization_options
45 .and_then(|v| InitializationOptions::deserialize(v).ok()) 56 .and_then(|v| InitializationOptions::deserialize(v).ok())
46 .unwrap_or(InitializationOptions::default()); 57 .unwrap_or(InitializationOptions::default());
47 58
48 ra_lsp_server::main_loop(root, opts, r, s) 59 ra_lsp_server::main_loop(workspace_roots, opts, r, s)
49 })?; 60 })?;
50 log::info!("shutting down IO..."); 61 log::info!("shutting down IO...");
51 threads.join()?; 62 threads.join()?;
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs
index 82410bee3..07ac4917a 100644
--- a/crates/ra_lsp_server/src/main_loop.rs
+++ b/crates/ra_lsp_server/src/main_loop.rs
@@ -48,7 +48,7 @@ enum Task {
48const THREADPOOL_SIZE: usize = 8; 48const THREADPOOL_SIZE: usize = 8;
49 49
50pub fn main_loop( 50pub fn main_loop(
51 ws_root: PathBuf, 51 ws_roots: Vec<PathBuf>,
52 options: InitializationOptions, 52 options: InitializationOptions,
53 msg_receiver: &Receiver<RawMessage>, 53 msg_receiver: &Receiver<RawMessage>,
54 msg_sender: &Sender<RawMessage>, 54 msg_sender: &Sender<RawMessage>,
@@ -59,23 +59,26 @@ pub fn main_loop(
59 // FIXME: support dynamic workspace loading. 59 // FIXME: support dynamic workspace loading.
60 let workspaces = { 60 let workspaces = {
61 let ws_worker = workspace_loader(); 61 let ws_worker = workspace_loader();
62 ws_worker.sender().send(ws_root.clone()).unwrap(); 62 let mut loaded_workspaces = Vec::new();
63 match ws_worker.receiver().recv().unwrap() { 63 for ws_root in &ws_roots {
64 Ok(ws) => vec![ws], 64 ws_worker.sender().send(ws_root.clone()).unwrap();
65 Err(e) => { 65 match ws_worker.receiver().recv().unwrap() {
66 log::error!("loading workspace failed: {}", e); 66 Ok(ws) => loaded_workspaces.push(ws),
67 67 Err(e) => {
68 show_message( 68 log::error!("loading workspace failed: {}", e);
69 req::MessageType::Error, 69
70 format!("rust-analyzer failed to load workspace: {}", e), 70 show_message(
71 msg_sender, 71 req::MessageType::Error,
72 ); 72 format!("rust-analyzer failed to load workspace: {}", e),
73 Vec::new() 73 msg_sender,
74 );
75 }
74 } 76 }
75 } 77 }
78 loaded_workspaces
76 }; 79 };
77 80
78 let mut state = ServerWorldState::new(ws_root.clone(), workspaces); 81 let mut state = ServerWorldState::new(ws_roots, workspaces);
79 82
80 log::info!("server initialized, serving requests"); 83 log::info!("server initialized, serving requests");
81 84
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index b96deb061..41d1f759f 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -263,6 +263,7 @@ pub fn handle_runnables(
263 let line_index = world.analysis().file_line_index(file_id); 263 let line_index = world.analysis().file_line_index(file_id);
264 let offset = params.position.map(|it| it.conv_with(&line_index)); 264 let offset = params.position.map(|it| it.conv_with(&line_index));
265 let mut res = Vec::new(); 265 let mut res = Vec::new();
266 let workspace_root = world.workspace_root_for(file_id);
266 for runnable in world.analysis().runnables(file_id)? { 267 for runnable in world.analysis().runnables(file_id)? {
267 if let Some(offset) = offset { 268 if let Some(offset) = offset {
268 if !runnable.range.contains_inclusive(offset) { 269 if !runnable.range.contains_inclusive(offset) {
@@ -287,6 +288,7 @@ pub fn handle_runnables(
287 m.insert("RUST_BACKTRACE".to_string(), "short".to_string()); 288 m.insert("RUST_BACKTRACE".to_string(), "short".to_string());
288 m 289 m
289 }, 290 },
291 cwd: workspace_root.map(|root| root.to_string_lossy().to_string()),
290 }; 292 };
291 res.push(r); 293 res.push(r);
292 } 294 }
@@ -309,6 +311,7 @@ pub fn handle_runnables(
309 bin: "cargo".to_string(), 311 bin: "cargo".to_string(),
310 args: check_args, 312 args: check_args,
311 env: FxHashMap::default(), 313 env: FxHashMap::default(),
314 cwd: workspace_root.map(|root| root.to_string_lossy().to_string()),
312 }); 315 });
313 Ok(res) 316 Ok(res)
314} 317}
@@ -627,6 +630,7 @@ pub fn handle_code_lens(
627 let line_index = world.analysis().file_line_index(file_id); 630 let line_index = world.analysis().file_line_index(file_id);
628 631
629 let mut lenses: Vec<CodeLens> = Default::default(); 632 let mut lenses: Vec<CodeLens> = Default::default();
633 let workspace_root = world.workspace_root_for(file_id);
630 634
631 // Gather runnables 635 // Gather runnables
632 for runnable in world.analysis().runnables(file_id)? { 636 for runnable in world.analysis().runnables(file_id)? {
@@ -647,6 +651,7 @@ pub fn handle_code_lens(
647 bin: "cargo".into(), 651 bin: "cargo".into(),
648 args, 652 args,
649 env: Default::default(), 653 env: Default::default(),
654 cwd: workspace_root.map(|root| root.to_string_lossy().to_string()),
650 }; 655 };
651 656
652 let lens = CodeLens { 657 let lens = CodeLens {
diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs
index e0571fd78..4f35ab9b5 100644
--- a/crates/ra_lsp_server/src/req.rs
+++ b/crates/ra_lsp_server/src/req.rs
@@ -163,6 +163,7 @@ pub struct Runnable {
163 pub bin: String, 163 pub bin: String,
164 pub args: Vec<String>, 164 pub args: Vec<String>,
165 pub env: FxHashMap<String, String>, 165 pub env: FxHashMap<String, String>,
166 pub cwd: Option<String>,
166} 167}
167 168
168#[derive(Serialize, Debug)] 169#[derive(Serialize, Debug)]
diff --git a/crates/ra_lsp_server/src/server_world.rs b/crates/ra_lsp_server/src/server_world.rs
index 4b1d592bb..b2808b817 100644
--- a/crates/ra_lsp_server/src/server_world.rs
+++ b/crates/ra_lsp_server/src/server_world.rs
@@ -1,5 +1,5 @@
1use std::{ 1use std::{
2 path::PathBuf, 2 path::{Path, PathBuf},
3 sync::Arc, 3 sync::Arc,
4}; 4};
5 5
@@ -24,7 +24,7 @@ use crate::{
24#[derive(Debug)] 24#[derive(Debug)]
25pub struct ServerWorldState { 25pub struct ServerWorldState {
26 pub roots_to_scan: usize, 26 pub roots_to_scan: usize,
27 pub root: PathBuf, 27 pub roots: Vec<PathBuf>,
28 pub workspaces: Arc<Vec<ProjectWorkspace>>, 28 pub workspaces: Arc<Vec<ProjectWorkspace>>,
29 pub analysis_host: AnalysisHost, 29 pub analysis_host: AnalysisHost,
30 pub vfs: Arc<RwLock<Vfs>>, 30 pub vfs: Arc<RwLock<Vfs>>,
@@ -37,19 +37,20 @@ pub struct ServerWorld {
37} 37}
38 38
39impl ServerWorldState { 39impl ServerWorldState {
40 pub fn new(root: PathBuf, workspaces: Vec<ProjectWorkspace>) -> ServerWorldState { 40 pub fn new(folder_roots: Vec<PathBuf>, workspaces: Vec<ProjectWorkspace>) -> ServerWorldState {
41 let mut change = AnalysisChange::new(); 41 let mut change = AnalysisChange::new();
42 42
43 let mut roots = Vec::new(); 43 let mut roots = Vec::new();
44 roots.push(IncludeRustFiles::member(root.clone())); 44 roots.extend(folder_roots.iter().cloned().map(IncludeRustFiles::member));
45 for ws in workspaces.iter() { 45 for ws in workspaces.iter() {
46 roots.extend(IncludeRustFiles::from_roots(ws.to_roots())); 46 roots.extend(IncludeRustFiles::from_roots(ws.to_roots()));
47 } 47 }
48 48
49 let (mut vfs, roots) = Vfs::new(roots); 49 let (mut vfs, vfs_roots) = Vfs::new(roots);
50 let roots_to_scan = roots.len(); 50 let roots_to_scan = vfs_roots.len();
51 for r in roots { 51 for r in vfs_roots {
52 let is_local = vfs.root2path(r).starts_with(&root); 52 let vfs_root_path = vfs.root2path(r);
53 let is_local = folder_roots.iter().any(|it| vfs_root_path.starts_with(it));
53 change.add_root(SourceRootId(r.0.into()), is_local); 54 change.add_root(SourceRootId(r.0.into()), is_local);
54 } 55 }
55 56
@@ -68,7 +69,7 @@ impl ServerWorldState {
68 analysis_host.apply_change(change); 69 analysis_host.apply_change(change);
69 ServerWorldState { 70 ServerWorldState {
70 roots_to_scan, 71 roots_to_scan,
71 root, 72 roots: folder_roots,
72 workspaces: Arc::new(workspaces), 73 workspaces: Arc::new(workspaces),
73 analysis_host, 74 analysis_host,
74 vfs: Arc::new(RwLock::new(vfs)), 75 vfs: Arc::new(RwLock::new(vfs)),
@@ -90,7 +91,8 @@ impl ServerWorldState {
90 match c { 91 match c {
91 VfsChange::AddRoot { root, files } => { 92 VfsChange::AddRoot { root, files } => {
92 let root_path = self.vfs.read().root2path(root); 93 let root_path = self.vfs.read().root2path(root);
93 if root_path.starts_with(&self.root) { 94 let is_local = self.roots.iter().any(|r| root_path.starts_with(r));
95 if is_local {
94 self.roots_to_scan -= 1; 96 self.roots_to_scan -= 1;
95 for (file, path, text) in files { 97 for (file, path, text) in files {
96 change.add_file( 98 change.add_file(
@@ -193,4 +195,9 @@ impl ServerWorld {
193 res.push_str(&self.analysis.status()); 195 res.push_str(&self.analysis.status());
194 res 196 res
195 } 197 }
198
199 pub fn workspace_root_for(&self, file_id: FileId) -> Option<&Path> {
200 let path = self.vfs.read().file2path(VfsFile(file_id.0.into()));
201 self.workspaces.iter().find_map(|ws| ws.workspace_root_for(&path))
202 }
196} 203}
diff --git a/crates/ra_lsp_server/tests/heavy_tests/main.rs b/crates/ra_lsp_server/tests/heavy_tests/main.rs
index 407719080..6f37a980d 100644
--- a/crates/ra_lsp_server/tests/heavy_tests/main.rs
+++ b/crates/ra_lsp_server/tests/heavy_tests/main.rs
@@ -14,7 +14,7 @@ use ra_lsp_server::req::{
14use serde_json::json; 14use serde_json::json;
15use tempfile::TempDir; 15use tempfile::TempDir;
16 16
17use crate::support::{project, project_with_tmpdir}; 17use crate::support::{project, Project};
18 18
19const LOG: &'static str = ""; 19const LOG: &'static str = "";
20 20
@@ -62,6 +62,7 @@ fn foo() {
62 "args": [ "test", "--", "foo", "--nocapture" ], 62 "args": [ "test", "--", "foo", "--nocapture" ],
63 "bin": "cargo", 63 "bin": "cargo",
64 "env": { "RUST_BACKTRACE": "short" }, 64 "env": { "RUST_BACKTRACE": "short" },
65 "cwd": null,
65 "label": "test foo", 66 "label": "test foo",
66 "range": { 67 "range": {
67 "end": { "character": 1, "line": 2 }, 68 "end": { "character": 1, "line": 2 },
@@ -75,6 +76,7 @@ fn foo() {
75 ], 76 ],
76 "bin": "cargo", 77 "bin": "cargo",
77 "env": {}, 78 "env": {},
79 "cwd": null,
78 "label": "cargo check --all", 80 "label": "cargo check --all",
79 "range": { 81 "range": {
80 "end": { 82 "end": {
@@ -93,25 +95,34 @@ fn foo() {
93 95
94#[test] 96#[test]
95fn test_runnables_project() { 97fn test_runnables_project() {
96 let server = project( 98 let code = r#"
97 r#" 99//- foo/Cargo.toml
98//- Cargo.toml
99[package] 100[package]
100name = "foo" 101name = "foo"
101version = "0.0.0" 102version = "0.0.0"
102 103
103//- src/lib.rs 104//- foo/src/lib.rs
104pub fn foo() {} 105pub fn foo() {}
105 106
106//- tests/spam.rs 107//- foo/tests/spam.rs
107#[test] 108#[test]
108fn test_eggs() {} 109fn test_eggs() {}
109"#, 110
110 ); 111//- bar/Cargo.toml
112[package]
113name = "bar"
114version = "0.0.0"
115
116//- bar/src/main.rs
117fn main() {}
118"#;
119
120 let server = Project::with_fixture(code).root("foo").root("bar").server();
121
111 server.wait_until_workspace_is_loaded(); 122 server.wait_until_workspace_is_loaded();
112 server.request::<Runnables>( 123 server.request::<Runnables>(
113 RunnablesParams { 124 RunnablesParams {
114 text_document: server.doc_id("tests/spam.rs"), 125 text_document: server.doc_id("foo/tests/spam.rs"),
115 position: None, 126 position: None,
116 }, 127 },
117 json!([ 128 json!([
@@ -123,7 +134,8 @@ fn test_eggs() {}
123 "range": { 134 "range": {
124 "end": { "character": 17, "line": 1 }, 135 "end": { "character": 17, "line": 1 },
125 "start": { "character": 0, "line": 0 } 136 "start": { "character": 0, "line": 0 }
126 } 137 },
138 "cwd": server.path().join("foo")
127 }, 139 },
128 { 140 {
129 "args": [ 141 "args": [
@@ -135,6 +147,7 @@ fn test_eggs() {}
135 ], 147 ],
136 "bin": "cargo", 148 "bin": "cargo",
137 "env": {}, 149 "env": {},
150 "cwd": server.path().join("foo"),
138 "label": "cargo check -p foo", 151 "label": "cargo check -p foo",
139 "range": { 152 "range": {
140 "end": { 153 "end": {
@@ -283,7 +296,9 @@ fn main() {{}}
283"#, 296"#,
284 PROJECT = project.to_string(), 297 PROJECT = project.to_string(),
285 ); 298 );
286 let server = project_with_tmpdir(tmp_dir, &code); 299
300 let server = Project::with_fixture(&code).tmp_dir(tmp_dir).server();
301
287 server.wait_until_workspace_is_loaded(); 302 server.wait_until_workspace_is_loaded();
288 let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None }; 303 let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None };
289 server.request::<CodeActionRequest>( 304 server.request::<CodeActionRequest>(
diff --git a/crates/ra_lsp_server/tests/heavy_tests/support.rs b/crates/ra_lsp_server/tests/heavy_tests/support.rs
index ab9db3dd4..b07882700 100644
--- a/crates/ra_lsp_server/tests/heavy_tests/support.rs
+++ b/crates/ra_lsp_server/tests/heavy_tests/support.rs
@@ -1,7 +1,7 @@
1use std::{ 1use std::{
2 cell::{Cell, RefCell}, 2 cell::{Cell, RefCell},
3 fs, 3 fs,
4 path::PathBuf, 4 path::{Path, PathBuf},
5 sync::Once, 5 sync::Once,
6 time::Duration, 6 time::Duration,
7}; 7};
@@ -26,26 +26,51 @@ use ra_lsp_server::{
26 InitializationOptions, 26 InitializationOptions,
27}; 27};
28 28
29pub fn project(fixture: &str) -> Server { 29pub struct Project<'a> {
30 let tmp_dir = TempDir::new().unwrap(); 30 fixture: &'a str,
31 project_with_tmpdir(tmp_dir, fixture) 31 tmp_dir: Option<TempDir>,
32 roots: Vec<PathBuf>,
32} 33}
33 34
34pub fn project_with_tmpdir(tmp_dir: TempDir, fixture: &str) -> Server { 35impl<'a> Project<'a> {
35 static INIT: Once = Once::new(); 36 pub fn with_fixture(fixture: &str) -> Project {
36 INIT.call_once(|| { 37 Project { fixture, tmp_dir: None, roots: vec![] }
37 let _ = Logger::with_env_or_str(crate::LOG).start().unwrap(); 38 }
38 });
39 39
40 let mut paths = vec![]; 40 pub fn tmp_dir(mut self, tmp_dir: TempDir) -> Project<'a> {
41 self.tmp_dir = Some(tmp_dir);
42 self
43 }
41 44
42 for entry in parse_fixture(fixture) { 45 pub fn root(mut self, path: &str) -> Project<'a> {
43 let path = tmp_dir.path().join(entry.meta); 46 self.roots.push(path.into());
44 fs::create_dir_all(path.parent().unwrap()).unwrap(); 47 self
45 fs::write(path.as_path(), entry.text.as_bytes()).unwrap();
46 paths.push((path, entry.text));
47 } 48 }
48 Server::new(tmp_dir, paths) 49
50 pub fn server(self) -> Server {
51 let tmp_dir = self.tmp_dir.unwrap_or_else(|| TempDir::new().unwrap());
52 static INIT: Once = Once::new();
53 INIT.call_once(|| {
54 let _ = Logger::with_env_or_str(crate::LOG).start().unwrap();
55 });
56
57 let mut paths = vec![];
58
59 for entry in parse_fixture(self.fixture) {
60 let path = tmp_dir.path().join(entry.meta);
61 fs::create_dir_all(path.parent().unwrap()).unwrap();
62 fs::write(path.as_path(), entry.text.as_bytes()).unwrap();
63 paths.push((path, entry.text));
64 }
65
66 let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect();
67
68 Server::new(tmp_dir, roots, paths)
69 }
70}
71
72pub fn project(fixture: &str) -> Server {
73 Project::with_fixture(fixture).server()
49} 74}
50 75
51pub struct Server { 76pub struct Server {
@@ -56,14 +81,17 @@ pub struct Server {
56} 81}
57 82
58impl Server { 83impl Server {
59 fn new(dir: TempDir, files: Vec<(PathBuf, String)>) -> Server { 84 fn new(dir: TempDir, roots: Vec<PathBuf>, files: Vec<(PathBuf, String)>) -> Server {
60 let path = dir.path().to_path_buf(); 85 let path = dir.path().to_path_buf();
86
87 let roots = if roots.is_empty() { vec![path] } else { roots };
88
61 let worker = Worker::<RawMessage, RawMessage>::spawn( 89 let worker = Worker::<RawMessage, RawMessage>::spawn(
62 "test server", 90 "test server",
63 128, 91 128,
64 move |mut msg_receiver, mut msg_sender| { 92 move |mut msg_receiver, mut msg_sender| {
65 main_loop( 93 main_loop(
66 path, 94 roots,
67 InitializationOptions::default(), 95 InitializationOptions::default(),
68 &mut msg_receiver, 96 &mut msg_receiver,
69 &mut msg_sender, 97 &mut msg_sender,
@@ -177,6 +205,10 @@ impl Server {
177 fn send_notification(&self, not: RawNotification) { 205 fn send_notification(&self, not: RawNotification) {
178 self.worker.as_ref().unwrap().sender().send(RawMessage::Notification(not)).unwrap(); 206 self.worker.as_ref().unwrap().sender().send(RawMessage::Notification(not)).unwrap();
179 } 207 }
208
209 pub fn path(&self) -> &Path {
210 self.dir.path()
211 }
180} 212}
181 213
182impl Drop for Server { 214impl Drop for Server {
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs
index 81cb506b7..71976071f 100644
--- a/crates/ra_project_model/src/cargo_workspace.rs
+++ b/crates/ra_project_model/src/cargo_workspace.rs
@@ -19,6 +19,7 @@ use crate::Result;
19pub struct CargoWorkspace { 19pub struct CargoWorkspace {
20 packages: Arena<Package, PackageData>, 20 packages: Arena<Package, PackageData>,
21 targets: Arena<Target, TargetData>, 21 targets: Arena<Target, TargetData>,
22 pub(crate) workspace_root: PathBuf,
22} 23}
23 24
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
@@ -165,7 +166,7 @@ impl CargoWorkspace {
165 } 166 }
166 } 167 }
167 168
168 Ok(CargoWorkspace { packages, targets }) 169 Ok(CargoWorkspace { packages, targets, workspace_root: meta.workspace_root })
169 } 170 }
170 171
171 pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + 'a { 172 pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + 'a {
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index 3bad4f8d3..63eb7041e 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -255,6 +255,18 @@ impl ProjectWorkspace {
255 } 255 }
256 crate_graph 256 crate_graph
257 } 257 }
258
259 pub fn workspace_root_for(&self, path: &Path) -> Option<&Path> {
260 match self {
261 ProjectWorkspace::Cargo { cargo, .. } => {
262 Some(cargo.workspace_root.as_ref()).filter(|root| path.starts_with(root))
263 }
264 ProjectWorkspace::Json { project: JsonProject { roots, .. } } => roots
265 .iter()
266 .find(|root| path.starts_with(&root.path))
267 .map(|root| root.path.as_ref()),
268 }
269 }
258} 270}
259 271
260fn find_rust_project_json(path: &Path) -> Option<PathBuf> { 272fn find_rust_project_json(path: &Path) -> Option<PathBuf> {