aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_lsp_server/src/world.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_lsp_server/src/world.rs')
-rw-r--r--crates/ra_lsp_server/src/world.rs75
1 files changed, 69 insertions, 6 deletions
diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs
index 927449b45..79431e7e6 100644
--- a/crates/ra_lsp_server/src/world.rs
+++ b/crates/ra_lsp_server/src/world.rs
@@ -1,4 +1,7 @@
1//! FIXME: write short doc here 1//! The context or environment in which the language server functions.
2//! In our server implementation this is know as the `WorldState`.
3//!
4//! Each tick provides an immutable snapshot of the state as `WorldSnapshot`.
2 5
3use std::{ 6use std::{
4 path::{Path, PathBuf}, 7 path::{Path, PathBuf},
@@ -17,11 +20,13 @@ use ra_project_model::{get_rustc_cfg_options, ProjectWorkspace};
17use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch}; 20use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch};
18use ra_vfs_glob::{Glob, RustPackageFilterBuilder}; 21use ra_vfs_glob::{Glob, RustPackageFilterBuilder};
19use relative_path::RelativePathBuf; 22use relative_path::RelativePathBuf;
23use std::path::{Component, Prefix};
20 24
21use crate::{ 25use crate::{
22 main_loop::pending_requests::{CompletedRequest, LatestRequests}, 26 main_loop::pending_requests::{CompletedRequest, LatestRequests},
23 LspError, Result, 27 LspError, Result,
24}; 28};
29use std::str::FromStr;
25 30
26#[derive(Debug, Clone)] 31#[derive(Debug, Clone)]
27pub struct Options { 32pub struct Options {
@@ -140,10 +145,10 @@ impl WorldState {
140 /// FIXME: better API here 145 /// FIXME: better API here
141 pub fn process_changes( 146 pub fn process_changes(
142 &mut self, 147 &mut self,
143 ) -> Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)> { 148 ) -> Option<Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>> {
144 let changes = self.vfs.write().commit_changes(); 149 let changes = self.vfs.write().commit_changes();
145 if changes.is_empty() { 150 if changes.is_empty() {
146 return Vec::new(); 151 return None;
147 } 152 }
148 let mut libs = Vec::new(); 153 let mut libs = Vec::new();
149 let mut change = AnalysisChange::new(); 154 let mut change = AnalysisChange::new();
@@ -177,7 +182,7 @@ impl WorldState {
177 } 182 }
178 } 183 }
179 self.analysis_host.apply_change(change); 184 self.analysis_host.apply_change(change);
180 libs 185 Some(libs)
181 } 186 }
182 187
183 pub fn add_lib(&mut self, data: LibraryData) { 188 pub fn add_lib(&mut self, data: LibraryData) {
@@ -233,8 +238,8 @@ impl WorldSnapshot {
233 238
234 pub fn file_id_to_uri(&self, id: FileId) -> Result<Url> { 239 pub fn file_id_to_uri(&self, id: FileId) -> Result<Url> {
235 let path = self.vfs.read().file2path(VfsFile(id.0)); 240 let path = self.vfs.read().file2path(VfsFile(id.0));
236 let url = Url::from_file_path(&path) 241 let url = url_from_path_with_drive_lowercasing(path)?;
237 .map_err(|_| format!("can't convert path to url: {}", path.display()))?; 242
238 Ok(url) 243 Ok(url)
239 } 244 }
240 245
@@ -279,3 +284,61 @@ impl WorldSnapshot {
279 self.analysis.feature_flags() 284 self.analysis.feature_flags()
280 } 285 }
281} 286}
287
288/// Returns a `Url` object from a given path, will lowercase drive letters if present.
289/// This will only happen when processing windows paths.
290///
291/// When processing non-windows path, this is essentially the same as `Url::from_file_path`.
292fn url_from_path_with_drive_lowercasing(path: impl AsRef<Path>) -> Result<Url> {
293 let component_has_windows_drive = path.as_ref().components().any(|comp| {
294 if let Component::Prefix(c) = comp {
295 match c.kind() {
296 Prefix::Disk(_) | Prefix::VerbatimDisk(_) => return true,
297 _ => return false,
298 }
299 }
300 false
301 });
302
303 // VSCode expects drive letters to be lowercased, where rust will uppercase the drive letters.
304 if component_has_windows_drive {
305 let url_original = Url::from_file_path(&path)
306 .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?;
307
308 let drive_partition: Vec<&str> = url_original.as_str().rsplitn(2, ':').collect();
309
310 // There is a drive partition, but we never found a colon.
311 // This should not happen, but in this case we just pass it through.
312 if drive_partition.len() == 1 {
313 return Ok(url_original);
314 }
315
316 let joined = drive_partition[1].to_ascii_lowercase() + ":" + drive_partition[0];
317 let url = Url::from_str(&joined).expect("This came from a valid `Url`");
318
319 Ok(url)
320 } else {
321 Ok(Url::from_file_path(&path)
322 .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?)
323 }
324}
325
326// `Url` is not able to parse windows paths on unix machines.
327#[cfg(target_os = "windows")]
328#[cfg(test)]
329mod path_conversion_windows_tests {
330 use super::url_from_path_with_drive_lowercasing;
331 #[test]
332 fn test_lowercase_drive_letter_with_drive() {
333 let url = url_from_path_with_drive_lowercasing("C:\\Test").unwrap();
334
335 assert_eq!(url.to_string(), "file:///c:/Test");
336 }
337
338 #[test]
339 fn test_drive_without_colon_passthrough() {
340 let url = url_from_path_with_drive_lowercasing(r#"\\localhost\C$\my_dir"#).unwrap();
341
342 assert_eq!(url.to_string(), "file://localhost/C$/my_dir");
343 }
344}