diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_proc_macro_srv/src/dylib.rs | 72 | ||||
-rw-r--r-- | crates/ra_project_model/src/lib.rs | 231 | ||||
-rw-r--r-- | crates/rust-analyzer/src/cli/load_cargo.rs | 8 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 60 |
4 files changed, 185 insertions, 186 deletions
diff --git a/crates/ra_proc_macro_srv/src/dylib.rs b/crates/ra_proc_macro_srv/src/dylib.rs index ec63d587b..7d6e5d323 100644 --- a/crates/ra_proc_macro_srv/src/dylib.rs +++ b/crates/ra_proc_macro_srv/src/dylib.rs | |||
@@ -16,55 +16,53 @@ fn invalid_data_err(e: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> I | |||
16 | IoError::new(IoErrorKind::InvalidData, e) | 16 | IoError::new(IoErrorKind::InvalidData, e) |
17 | } | 17 | } |
18 | 18 | ||
19 | fn get_symbols_from_lib(file: &Path) -> Result<Vec<String>, IoError> { | 19 | fn is_derive_registrar_symbol(symbol: &str) -> bool { |
20 | symbol.contains(NEW_REGISTRAR_SYMBOL) | ||
21 | } | ||
22 | |||
23 | fn find_registrar_symbol(file: &Path) -> Result<Option<String>, IoError> { | ||
20 | let buffer = std::fs::read(file)?; | 24 | let buffer = std::fs::read(file)?; |
21 | let object = Object::parse(&buffer).map_err(invalid_data_err)?; | 25 | let object = Object::parse(&buffer).map_err(invalid_data_err)?; |
22 | 26 | ||
23 | match object { | 27 | match object { |
24 | Object::Elf(elf) => { | 28 | Object::Elf(elf) => { |
25 | let symbols = elf.dynstrtab.to_vec().map_err(invalid_data_err)?; | 29 | let symbols = elf.dynstrtab.to_vec().map_err(invalid_data_err)?; |
26 | let names = symbols.iter().map(|s| s.to_string()).collect(); | 30 | let name = |
27 | Ok(names) | 31 | symbols.iter().find(|s| is_derive_registrar_symbol(s)).map(|s| s.to_string()); |
32 | Ok(name) | ||
28 | } | 33 | } |
29 | Object::PE(pe) => { | 34 | Object::PE(pe) => { |
30 | let symbol_names = | 35 | let name = pe |
31 | pe.exports.iter().flat_map(|s| s.name).map(|n| n.to_string()).collect(); | 36 | .exports |
32 | Ok(symbol_names) | 37 | .iter() |
38 | .flat_map(|s| s.name) | ||
39 | .find(|s| is_derive_registrar_symbol(s)) | ||
40 | .map(|s| s.to_string()); | ||
41 | Ok(name) | ||
33 | } | 42 | } |
34 | Object::Mach(mach) => match mach { | 43 | Object::Mach(Mach::Binary(binary)) => { |
35 | Mach::Binary(binary) => { | 44 | let exports = binary.exports().map_err(invalid_data_err)?; |
36 | let exports = binary.exports().map_err(invalid_data_err)?; | 45 | let name = exports |
37 | let names = exports | 46 | .iter() |
38 | .into_iter() | 47 | .map(|s| { |
39 | .map(|s| { | 48 | // In macos doc: |
40 | // In macos doc: | 49 | // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dlsym.3.html |
41 | // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dlsym.3.html | 50 | // Unlike other dyld API's, the symbol name passed to dlsym() must NOT be |
42 | // Unlike other dyld API's, the symbol name passed to dlsym() must NOT be | 51 | // prepended with an underscore. |
43 | // prepended with an underscore. | 52 | if s.name.starts_with("_") { |
44 | if s.name.starts_with("_") { | 53 | &s.name[1..] |
45 | s.name[1..].to_string() | 54 | } else { |
46 | } else { | 55 | &s.name |
47 | s.name | 56 | } |
48 | } | 57 | }) |
49 | }) | 58 | .find(|s| is_derive_registrar_symbol(&s)) |
50 | .collect(); | 59 | .map(|s| s.to_string()); |
51 | Ok(names) | 60 | Ok(name) |
52 | } | 61 | } |
53 | Mach::Fat(_) => Ok(vec![]), | 62 | _ => Ok(None), |
54 | }, | ||
55 | Object::Archive(_) | Object::Unknown(_) => Ok(vec![]), | ||
56 | } | 63 | } |
57 | } | 64 | } |
58 | 65 | ||
59 | fn is_derive_registrar_symbol(symbol: &str) -> bool { | ||
60 | symbol.contains(NEW_REGISTRAR_SYMBOL) | ||
61 | } | ||
62 | |||
63 | fn find_registrar_symbol(file: &Path) -> Result<Option<String>, IoError> { | ||
64 | let symbols = get_symbols_from_lib(file)?; | ||
65 | Ok(symbols.into_iter().find(|s| is_derive_registrar_symbol(s))) | ||
66 | } | ||
67 | |||
68 | /// Loads dynamic library in platform dependent manner. | 66 | /// Loads dynamic library in platform dependent manner. |
69 | /// | 67 | /// |
70 | /// For unix, you have to use RTLD_DEEPBIND flag to escape problems described | 68 | /// For unix, you have to use RTLD_DEEPBIND flag to escape problems described |
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index 0ab64a1e0..03f2629da 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs | |||
@@ -5,9 +5,8 @@ mod json_project; | |||
5 | mod sysroot; | 5 | mod sysroot; |
6 | 6 | ||
7 | use std::{ | 7 | use std::{ |
8 | error::Error, | ||
9 | fs::{read_dir, File, ReadDir}, | 8 | fs::{read_dir, File, ReadDir}, |
10 | io::BufReader, | 9 | io::{self, BufReader}, |
11 | path::{Path, PathBuf}, | 10 | path::{Path, PathBuf}, |
12 | process::Command, | 11 | process::Command, |
13 | }; | 12 | }; |
@@ -25,25 +24,6 @@ pub use crate::{ | |||
25 | }; | 24 | }; |
26 | pub use ra_proc_macro::ProcMacroClient; | 25 | pub use ra_proc_macro::ProcMacroClient; |
27 | 26 | ||
28 | #[derive(Clone, PartialEq, Eq, Hash, Debug)] | ||
29 | pub struct CargoTomlNotFoundError { | ||
30 | pub searched_at: PathBuf, | ||
31 | pub reason: String, | ||
32 | } | ||
33 | |||
34 | impl std::fmt::Display for CargoTomlNotFoundError { | ||
35 | fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
36 | write!( | ||
37 | fmt, | ||
38 | "can't find Cargo.toml at {}, due to {}", | ||
39 | self.searched_at.display(), | ||
40 | self.reason | ||
41 | ) | ||
42 | } | ||
43 | } | ||
44 | |||
45 | impl Error for CargoTomlNotFoundError {} | ||
46 | |||
47 | #[derive(Debug, Clone)] | 27 | #[derive(Debug, Clone)] |
48 | pub enum ProjectWorkspace { | 28 | pub enum ProjectWorkspace { |
49 | /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. | 29 | /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. |
@@ -77,31 +57,119 @@ impl PackageRoot { | |||
77 | } | 57 | } |
78 | } | 58 | } |
79 | 59 | ||
80 | impl ProjectWorkspace { | 60 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
81 | pub fn discover(path: &Path, cargo_features: &CargoConfig) -> Result<ProjectWorkspace> { | 61 | pub enum ProjectRoot { |
82 | ProjectWorkspace::discover_with_sysroot(path, true, cargo_features) | 62 | ProjectJson(PathBuf), |
63 | CargoToml(PathBuf), | ||
64 | } | ||
65 | |||
66 | impl ProjectRoot { | ||
67 | pub fn from_manifest_file(path: PathBuf) -> Result<ProjectRoot> { | ||
68 | if path.ends_with("rust-project.json") { | ||
69 | return Ok(ProjectRoot::ProjectJson(path)); | ||
70 | } | ||
71 | if path.ends_with("Cargo.toml") { | ||
72 | return Ok(ProjectRoot::CargoToml(path)); | ||
73 | } | ||
74 | bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display()) | ||
83 | } | 75 | } |
84 | 76 | ||
85 | pub fn discover_with_sysroot( | 77 | pub fn discover_single(path: &Path) -> Result<ProjectRoot> { |
86 | path: &Path, | 78 | let mut candidates = ProjectRoot::discover(path)?; |
87 | with_sysroot: bool, | 79 | let res = match candidates.pop() { |
80 | None => bail!("no projects"), | ||
81 | Some(it) => it, | ||
82 | }; | ||
83 | |||
84 | if !candidates.is_empty() { | ||
85 | bail!("more than one project") | ||
86 | } | ||
87 | Ok(res) | ||
88 | } | ||
89 | |||
90 | pub fn discover(path: &Path) -> io::Result<Vec<ProjectRoot>> { | ||
91 | if let Some(project_json) = find_rust_project_json(path) { | ||
92 | return Ok(vec![ProjectRoot::ProjectJson(project_json)]); | ||
93 | } | ||
94 | return find_cargo_toml(path) | ||
95 | .map(|paths| paths.into_iter().map(ProjectRoot::CargoToml).collect()); | ||
96 | |||
97 | fn find_rust_project_json(path: &Path) -> Option<PathBuf> { | ||
98 | if path.ends_with("rust-project.json") { | ||
99 | return Some(path.to_path_buf()); | ||
100 | } | ||
101 | |||
102 | let mut curr = Some(path); | ||
103 | while let Some(path) = curr { | ||
104 | let candidate = path.join("rust-project.json"); | ||
105 | if candidate.exists() { | ||
106 | return Some(candidate); | ||
107 | } | ||
108 | curr = path.parent(); | ||
109 | } | ||
110 | |||
111 | None | ||
112 | } | ||
113 | |||
114 | fn find_cargo_toml(path: &Path) -> io::Result<Vec<PathBuf>> { | ||
115 | if path.ends_with("Cargo.toml") { | ||
116 | return Ok(vec![path.to_path_buf()]); | ||
117 | } | ||
118 | |||
119 | if let Some(p) = find_cargo_toml_in_parent_dir(path) { | ||
120 | return Ok(vec![p]); | ||
121 | } | ||
122 | |||
123 | let entities = read_dir(path)?; | ||
124 | Ok(find_cargo_toml_in_child_dir(entities)) | ||
125 | } | ||
126 | |||
127 | fn find_cargo_toml_in_parent_dir(path: &Path) -> Option<PathBuf> { | ||
128 | let mut curr = Some(path); | ||
129 | while let Some(path) = curr { | ||
130 | let candidate = path.join("Cargo.toml"); | ||
131 | if candidate.exists() { | ||
132 | return Some(candidate); | ||
133 | } | ||
134 | curr = path.parent(); | ||
135 | } | ||
136 | |||
137 | None | ||
138 | } | ||
139 | |||
140 | fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<PathBuf> { | ||
141 | // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects | ||
142 | let mut valid_canditates = vec![]; | ||
143 | for entity in entities.filter_map(Result::ok) { | ||
144 | let candidate = entity.path().join("Cargo.toml"); | ||
145 | if candidate.exists() { | ||
146 | valid_canditates.push(candidate) | ||
147 | } | ||
148 | } | ||
149 | valid_canditates | ||
150 | } | ||
151 | } | ||
152 | } | ||
153 | |||
154 | impl ProjectWorkspace { | ||
155 | pub fn load( | ||
156 | root: ProjectRoot, | ||
88 | cargo_features: &CargoConfig, | 157 | cargo_features: &CargoConfig, |
158 | with_sysroot: bool, | ||
89 | ) -> Result<ProjectWorkspace> { | 159 | ) -> Result<ProjectWorkspace> { |
90 | match find_rust_project_json(path) { | 160 | let res = match root { |
91 | Some(json_path) => { | 161 | ProjectRoot::ProjectJson(project_json) => { |
92 | let file = File::open(&json_path) | 162 | let file = File::open(&project_json).with_context(|| { |
93 | .with_context(|| format!("Failed to open json file {}", json_path.display()))?; | 163 | format!("Failed to open json file {}", project_json.display()) |
164 | })?; | ||
94 | let reader = BufReader::new(file); | 165 | let reader = BufReader::new(file); |
95 | Ok(ProjectWorkspace::Json { | 166 | ProjectWorkspace::Json { |
96 | project: from_reader(reader).with_context(|| { | 167 | project: from_reader(reader).with_context(|| { |
97 | format!("Failed to deserialize json file {}", json_path.display()) | 168 | format!("Failed to deserialize json file {}", project_json.display()) |
98 | })?, | 169 | })?, |
99 | }) | 170 | } |
100 | } | 171 | } |
101 | None => { | 172 | ProjectRoot::CargoToml(cargo_toml) => { |
102 | let cargo_toml = find_cargo_toml(path).with_context(|| { | ||
103 | format!("Failed to find Cargo.toml for path {}", path.display()) | ||
104 | })?; | ||
105 | let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features) | 173 | let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features) |
106 | .with_context(|| { | 174 | .with_context(|| { |
107 | format!( | 175 | format!( |
@@ -119,9 +187,11 @@ impl ProjectWorkspace { | |||
119 | } else { | 187 | } else { |
120 | Sysroot::default() | 188 | Sysroot::default() |
121 | }; | 189 | }; |
122 | Ok(ProjectWorkspace::Cargo { cargo, sysroot }) | 190 | ProjectWorkspace::Cargo { cargo, sysroot } |
123 | } | 191 | } |
124 | } | 192 | }; |
193 | |||
194 | Ok(res) | ||
125 | } | 195 | } |
126 | 196 | ||
127 | /// Returns the roots for the current `ProjectWorkspace` | 197 | /// Returns the roots for the current `ProjectWorkspace` |
@@ -469,87 +539,6 @@ impl ProjectWorkspace { | |||
469 | } | 539 | } |
470 | } | 540 | } |
471 | 541 | ||
472 | fn find_rust_project_json(path: &Path) -> Option<PathBuf> { | ||
473 | if path.ends_with("rust-project.json") { | ||
474 | return Some(path.to_path_buf()); | ||
475 | } | ||
476 | |||
477 | let mut curr = Some(path); | ||
478 | while let Some(path) = curr { | ||
479 | let candidate = path.join("rust-project.json"); | ||
480 | if candidate.exists() { | ||
481 | return Some(candidate); | ||
482 | } | ||
483 | curr = path.parent(); | ||
484 | } | ||
485 | |||
486 | None | ||
487 | } | ||
488 | |||
489 | fn find_cargo_toml_in_parent_dir(path: &Path) -> Option<PathBuf> { | ||
490 | let mut curr = Some(path); | ||
491 | while let Some(path) = curr { | ||
492 | let candidate = path.join("Cargo.toml"); | ||
493 | if candidate.exists() { | ||
494 | return Some(candidate); | ||
495 | } | ||
496 | curr = path.parent(); | ||
497 | } | ||
498 | |||
499 | None | ||
500 | } | ||
501 | |||
502 | fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<PathBuf> { | ||
503 | // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects | ||
504 | let mut valid_canditates = vec![]; | ||
505 | for entity in entities.filter_map(Result::ok) { | ||
506 | let candidate = entity.path().join("Cargo.toml"); | ||
507 | if candidate.exists() { | ||
508 | valid_canditates.push(candidate) | ||
509 | } | ||
510 | } | ||
511 | valid_canditates | ||
512 | } | ||
513 | |||
514 | fn find_cargo_toml(path: &Path) -> Result<PathBuf> { | ||
515 | if path.ends_with("Cargo.toml") { | ||
516 | return Ok(path.to_path_buf()); | ||
517 | } | ||
518 | |||
519 | if let Some(p) = find_cargo_toml_in_parent_dir(path) { | ||
520 | return Ok(p); | ||
521 | } | ||
522 | |||
523 | let entities = match read_dir(path) { | ||
524 | Ok(entities) => entities, | ||
525 | Err(e) => { | ||
526 | return Err(CargoTomlNotFoundError { | ||
527 | searched_at: path.to_path_buf(), | ||
528 | reason: format!("file system error: {}", e), | ||
529 | } | ||
530 | .into()); | ||
531 | } | ||
532 | }; | ||
533 | |||
534 | let mut valid_canditates = find_cargo_toml_in_child_dir(entities); | ||
535 | match valid_canditates.len() { | ||
536 | 1 => Ok(valid_canditates.remove(0)), | ||
537 | 0 => Err(CargoTomlNotFoundError { | ||
538 | searched_at: path.to_path_buf(), | ||
539 | reason: "no Cargo.toml file found".to_string(), | ||
540 | } | ||
541 | .into()), | ||
542 | _ => Err(CargoTomlNotFoundError { | ||
543 | searched_at: path.to_path_buf(), | ||
544 | reason: format!( | ||
545 | "multiple equally valid Cargo.toml files found: {:?}", | ||
546 | valid_canditates | ||
547 | ), | ||
548 | } | ||
549 | .into()), | ||
550 | } | ||
551 | } | ||
552 | |||
553 | pub fn get_rustc_cfg_options() -> CfgOptions { | 542 | pub fn get_rustc_cfg_options() -> CfgOptions { |
554 | let mut cfg_options = CfgOptions::default(); | 543 | let mut cfg_options = CfgOptions::default(); |
555 | 544 | ||
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs index 018be70ee..762f776fe 100644 --- a/crates/rust-analyzer/src/cli/load_cargo.rs +++ b/crates/rust-analyzer/src/cli/load_cargo.rs | |||
@@ -8,7 +8,7 @@ use crossbeam_channel::{unbounded, Receiver}; | |||
8 | use ra_db::{ExternSourceId, FileId, SourceRootId}; | 8 | use ra_db::{ExternSourceId, FileId, SourceRootId}; |
9 | use ra_ide::{AnalysisChange, AnalysisHost}; | 9 | use ra_ide::{AnalysisChange, AnalysisHost}; |
10 | use ra_project_model::{ | 10 | use ra_project_model::{ |
11 | get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectWorkspace, | 11 | get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectRoot, ProjectWorkspace, |
12 | }; | 12 | }; |
13 | use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; | 13 | use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; |
14 | use rustc_hash::{FxHashMap, FxHashSet}; | 14 | use rustc_hash::{FxHashMap, FxHashSet}; |
@@ -28,9 +28,11 @@ pub(crate) fn load_cargo( | |||
28 | with_proc_macro: bool, | 28 | with_proc_macro: bool, |
29 | ) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> { | 29 | ) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> { |
30 | let root = std::env::current_dir()?.join(root); | 30 | let root = std::env::current_dir()?.join(root); |
31 | let ws = ProjectWorkspace::discover( | 31 | let root = ProjectRoot::discover_single(&root)?; |
32 | root.as_ref(), | 32 | let ws = ProjectWorkspace::load( |
33 | root, | ||
33 | &CargoConfig { load_out_dirs_from_check, ..Default::default() }, | 34 | &CargoConfig { load_out_dirs_from_check, ..Default::default() }, |
35 | true, | ||
34 | )?; | 36 | )?; |
35 | 37 | ||
36 | let mut extern_dirs = FxHashSet::default(); | 38 | let mut extern_dirs = FxHashSet::default(); |
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 8d1429196..fc4c77f8a 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -15,6 +15,7 @@ use std::{ | |||
15 | }; | 15 | }; |
16 | 16 | ||
17 | use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; | 17 | use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; |
18 | use itertools::Itertools; | ||
18 | use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; | 19 | use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; |
19 | use lsp_types::{ | 20 | use lsp_types::{ |
20 | NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams, | 21 | NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams, |
@@ -88,37 +89,46 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection) | |||
88 | 89 | ||
89 | let mut loop_state = LoopState::default(); | 90 | let mut loop_state = LoopState::default(); |
90 | let mut world_state = { | 91 | let mut world_state = { |
91 | // FIXME: support dynamic workspace loading. | ||
92 | let workspaces = { | 92 | let workspaces = { |
93 | let mut loaded_workspaces = Vec::new(); | 93 | // FIXME: support dynamic workspace loading. |
94 | for ws_root in &ws_roots { | 94 | let mut visited = FxHashSet::default(); |
95 | let workspace = ra_project_model::ProjectWorkspace::discover_with_sysroot( | 95 | let project_roots = ws_roots |
96 | ws_root.as_path(), | 96 | .iter() |
97 | config.with_sysroot, | 97 | .filter_map(|it| ra_project_model::ProjectRoot::discover(it).ok()) |
98 | &config.cargo, | 98 | .flatten() |
99 | ); | 99 | .filter(|it| visited.insert(it.clone())) |
100 | match workspace { | 100 | .collect::<Vec<_>>(); |
101 | Ok(workspace) => loaded_workspaces.push(workspace), | 101 | |
102 | Err(e) => { | 102 | if project_roots.is_empty() && config.notifications.cargo_toml_not_found { |
103 | log::error!("loading workspace failed: {:?}", e); | 103 | show_message( |
104 | 104 | req::MessageType::Error, | |
105 | if let Some(ra_project_model::CargoTomlNotFoundError { .. }) = | 105 | format!( |
106 | e.downcast_ref() | 106 | "rust-analyzer failed to discover workspace, no Cargo.toml found, dirs searched: {}", |
107 | { | 107 | ws_roots.iter().format_with(", ", |it, f| f(&it.display())) |
108 | if !config.notifications.cargo_toml_not_found { | 108 | ), |
109 | continue; | 109 | &connection.sender, |
110 | } | 110 | ); |
111 | } | 111 | }; |
112 | 112 | ||
113 | project_roots | ||
114 | .into_iter() | ||
115 | .filter_map(|root| { | ||
116 | ra_project_model::ProjectWorkspace::load( | ||
117 | root, | ||
118 | &config.cargo, | ||
119 | config.with_sysroot, | ||
120 | ) | ||
121 | .map_err(|err| { | ||
122 | log::error!("failed to load workspace: {:#}", err); | ||
113 | show_message( | 123 | show_message( |
114 | req::MessageType::Error, | 124 | req::MessageType::Error, |
115 | format!("rust-analyzer failed to load workspace: {:?}", e), | 125 | format!("rust-analyzer failed to load workspace: {:#}", err), |
116 | &connection.sender, | 126 | &connection.sender, |
117 | ); | 127 | ); |
118 | } | 128 | }) |
119 | } | 129 | .ok() |
120 | } | 130 | }) |
121 | loaded_workspaces | 131 | .collect::<Vec<_>>() |
122 | }; | 132 | }; |
123 | 133 | ||
124 | let globs = config | 134 | let globs = config |