diff options
author | Aleksey Kladov <[email protected]> | 2020-06-13 10:00:06 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2020-06-13 13:15:38 +0100 |
commit | 50bbf7233dcda8a27255f123622bf57651c9f51c (patch) | |
tree | 183d011ed6a2f5fc5917d7bcd2763e7896a47a4f /crates | |
parent | b56ad148db0c69eb279c225f45d324b4e80e7367 (diff) |
Cleanup URL handling
Diffstat (limited to 'crates')
-rw-r--r-- | crates/rust-analyzer/src/diagnostics/to_proto.rs | 73 | ||||
-rw-r--r-- | crates/rust-analyzer/src/from_proto.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/global_state.rs | 27 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 18 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop/handlers.rs | 4 | ||||
-rw-r--r-- | crates/rust-analyzer/src/to_proto.rs | 170 |
6 files changed, 134 insertions, 160 deletions
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs index 04e286780..789c86a35 100644 --- a/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs | |||
@@ -1,10 +1,6 @@ | |||
1 | //! This module provides the functionality needed to convert diagnostics from | 1 | //! This module provides the functionality needed to convert diagnostics from |
2 | //! `cargo check` json format to the LSP diagnostic format. | 2 | //! `cargo check` json format to the LSP diagnostic format. |
3 | use std::{ | 3 | use std::{collections::HashMap, path::Path}; |
4 | collections::HashMap, | ||
5 | path::{Component, Path, Prefix}, | ||
6 | str::FromStr, | ||
7 | }; | ||
8 | 4 | ||
9 | use lsp_types::{ | 5 | use lsp_types::{ |
10 | Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location, | 6 | Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location, |
@@ -13,7 +9,7 @@ use lsp_types::{ | |||
13 | use ra_flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion}; | 9 | use ra_flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion}; |
14 | use stdx::format_to; | 10 | use stdx::format_to; |
15 | 11 | ||
16 | use crate::{lsp_ext, Result}; | 12 | use crate::{lsp_ext, to_proto::url_from_abs_path}; |
17 | 13 | ||
18 | /// Converts a Rust level string to a LSP severity | 14 | /// Converts a Rust level string to a LSP severity |
19 | fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> { | 15 | fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> { |
@@ -65,7 +61,7 @@ fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &Path) -> Locatio | |||
65 | fn map_span_to_location_naive(span: &DiagnosticSpan, workspace_root: &Path) -> Location { | 61 | fn map_span_to_location_naive(span: &DiagnosticSpan, workspace_root: &Path) -> Location { |
66 | let mut file_name = workspace_root.to_path_buf(); | 62 | let mut file_name = workspace_root.to_path_buf(); |
67 | file_name.push(&span.file_name); | 63 | file_name.push(&span.file_name); |
68 | let uri = url_from_path_with_drive_lowercasing(file_name).unwrap(); | 64 | let uri = url_from_abs_path(&file_name); |
69 | 65 | ||
70 | // FIXME: this doesn't handle UTF16 offsets correctly | 66 | // FIXME: this doesn't handle UTF16 offsets correctly |
71 | let range = Range::new( | 67 | let range = Range::new( |
@@ -275,70 +271,16 @@ pub(crate) fn map_rust_diagnostic_to_lsp( | |||
275 | .collect() | 271 | .collect() |
276 | } | 272 | } |
277 | 273 | ||
278 | /// Returns a `Url` object from a given path, will lowercase drive letters if present. | ||
279 | /// This will only happen when processing windows paths. | ||
280 | /// | ||
281 | /// When processing non-windows path, this is essentially the same as `Url::from_file_path`. | ||
282 | pub fn url_from_path_with_drive_lowercasing(path: impl AsRef<Path>) -> Result<Url> { | ||
283 | let component_has_windows_drive = path.as_ref().components().any(|comp| { | ||
284 | if let Component::Prefix(c) = comp { | ||
285 | return matches!(c.kind(), Prefix::Disk(_) | Prefix::VerbatimDisk(_)); | ||
286 | } | ||
287 | false | ||
288 | }); | ||
289 | |||
290 | // VSCode expects drive letters to be lowercased, where rust will uppercase the drive letters. | ||
291 | let res = if component_has_windows_drive { | ||
292 | let url_original = Url::from_file_path(&path) | ||
293 | .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?; | ||
294 | |||
295 | let drive_partition: Vec<&str> = url_original.as_str().rsplitn(2, ':').collect(); | ||
296 | |||
297 | // There is a drive partition, but we never found a colon. | ||
298 | // This should not happen, but in this case we just pass it through. | ||
299 | if drive_partition.len() == 1 { | ||
300 | return Ok(url_original); | ||
301 | } | ||
302 | |||
303 | let joined = drive_partition[1].to_ascii_lowercase() + ":" + drive_partition[0]; | ||
304 | let url = Url::from_str(&joined).expect("This came from a valid `Url`"); | ||
305 | |||
306 | url | ||
307 | } else { | ||
308 | Url::from_file_path(&path) | ||
309 | .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))? | ||
310 | }; | ||
311 | Ok(res) | ||
312 | } | ||
313 | |||
314 | #[cfg(test)] | 274 | #[cfg(test)] |
275 | #[cfg(not(windows))] | ||
315 | mod tests { | 276 | mod tests { |
316 | use super::*; | 277 | use super::*; |
317 | 278 | ||
318 | // `Url` is not able to parse windows paths on unix machines. | ||
319 | #[test] | ||
320 | #[cfg(target_os = "windows")] | ||
321 | fn test_lowercase_drive_letter_with_drive() { | ||
322 | let url = url_from_path_with_drive_lowercasing("C:\\Test").unwrap(); | ||
323 | |||
324 | assert_eq!(url.to_string(), "file:///c:/Test"); | ||
325 | } | ||
326 | |||
327 | #[test] | ||
328 | #[cfg(target_os = "windows")] | ||
329 | fn test_drive_without_colon_passthrough() { | ||
330 | let url = url_from_path_with_drive_lowercasing(r#"\\localhost\C$\my_dir"#).unwrap(); | ||
331 | |||
332 | assert_eq!(url.to_string(), "file://localhost/C$/my_dir"); | ||
333 | } | ||
334 | |||
335 | #[cfg(not(windows))] | ||
336 | fn parse_diagnostic(val: &str) -> ra_flycheck::Diagnostic { | 279 | fn parse_diagnostic(val: &str) -> ra_flycheck::Diagnostic { |
337 | serde_json::from_str::<ra_flycheck::Diagnostic>(val).unwrap() | 280 | serde_json::from_str::<ra_flycheck::Diagnostic>(val).unwrap() |
338 | } | 281 | } |
339 | 282 | ||
340 | #[test] | 283 | #[test] |
341 | #[cfg(not(windows))] | ||
342 | fn snap_rustc_incompatible_type_for_trait() { | 284 | fn snap_rustc_incompatible_type_for_trait() { |
343 | let diag = parse_diagnostic( | 285 | let diag = parse_diagnostic( |
344 | r##"{ | 286 | r##"{ |
@@ -392,7 +334,6 @@ mod tests { | |||
392 | } | 334 | } |
393 | 335 | ||
394 | #[test] | 336 | #[test] |
395 | #[cfg(not(windows))] | ||
396 | fn snap_rustc_unused_variable() { | 337 | fn snap_rustc_unused_variable() { |
397 | let diag = parse_diagnostic( | 338 | let diag = parse_diagnostic( |
398 | r##"{ | 339 | r##"{ |
@@ -475,7 +416,6 @@ mod tests { | |||
475 | } | 416 | } |
476 | 417 | ||
477 | #[test] | 418 | #[test] |
478 | #[cfg(not(windows))] | ||
479 | fn snap_rustc_wrong_number_of_parameters() { | 419 | fn snap_rustc_wrong_number_of_parameters() { |
480 | let diag = parse_diagnostic( | 420 | let diag = parse_diagnostic( |
481 | r##"{ | 421 | r##"{ |
@@ -600,7 +540,6 @@ mod tests { | |||
600 | } | 540 | } |
601 | 541 | ||
602 | #[test] | 542 | #[test] |
603 | #[cfg(not(windows))] | ||
604 | fn snap_clippy_pass_by_ref() { | 543 | fn snap_clippy_pass_by_ref() { |
605 | let diag = parse_diagnostic( | 544 | let diag = parse_diagnostic( |
606 | r##"{ | 545 | r##"{ |
@@ -721,7 +660,6 @@ mod tests { | |||
721 | } | 660 | } |
722 | 661 | ||
723 | #[test] | 662 | #[test] |
724 | #[cfg(not(windows))] | ||
725 | fn snap_rustc_mismatched_type() { | 663 | fn snap_rustc_mismatched_type() { |
726 | let diag = parse_diagnostic( | 664 | let diag = parse_diagnostic( |
727 | r##"{ | 665 | r##"{ |
@@ -765,7 +703,6 @@ mod tests { | |||
765 | } | 703 | } |
766 | 704 | ||
767 | #[test] | 705 | #[test] |
768 | #[cfg(not(windows))] | ||
769 | fn snap_handles_macro_location() { | 706 | fn snap_handles_macro_location() { |
770 | let diag = parse_diagnostic( | 707 | let diag = parse_diagnostic( |
771 | r##"{ | 708 | r##"{ |
@@ -1037,7 +974,6 @@ mod tests { | |||
1037 | } | 974 | } |
1038 | 975 | ||
1039 | #[test] | 976 | #[test] |
1040 | #[cfg(not(windows))] | ||
1041 | fn snap_macro_compiler_error() { | 977 | fn snap_macro_compiler_error() { |
1042 | let diag = parse_diagnostic( | 978 | let diag = parse_diagnostic( |
1043 | r##"{ | 979 | r##"{ |
@@ -1267,7 +1203,6 @@ mod tests { | |||
1267 | } | 1203 | } |
1268 | 1204 | ||
1269 | #[test] | 1205 | #[test] |
1270 | #[cfg(not(windows))] | ||
1271 | fn snap_multi_line_fix() { | 1206 | fn snap_multi_line_fix() { |
1272 | let diag = parse_diagnostic( | 1207 | let diag = parse_diagnostic( |
1273 | r##"{ | 1208 | r##"{ |
diff --git a/crates/rust-analyzer/src/from_proto.rs b/crates/rust-analyzer/src/from_proto.rs index 206673829..40f856e6e 100644 --- a/crates/rust-analyzer/src/from_proto.rs +++ b/crates/rust-analyzer/src/from_proto.rs | |||
@@ -17,7 +17,7 @@ pub(crate) fn text_range(line_index: &LineIndex, range: lsp_types::Range) -> Tex | |||
17 | } | 17 | } |
18 | 18 | ||
19 | pub(crate) fn file_id(world: &GlobalStateSnapshot, url: &lsp_types::Url) -> Result<FileId> { | 19 | pub(crate) fn file_id(world: &GlobalStateSnapshot, url: &lsp_types::Url) -> Result<FileId> { |
20 | world.uri_to_file_id(url) | 20 | world.url_to_file_id(url) |
21 | } | 21 | } |
22 | 22 | ||
23 | pub(crate) fn file_position( | 23 | pub(crate) fn file_position( |
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index 21116e165..9d5685d88 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs | |||
@@ -22,10 +22,9 @@ use stdx::format_to; | |||
22 | 22 | ||
23 | use crate::{ | 23 | use crate::{ |
24 | config::Config, | 24 | config::Config, |
25 | diagnostics::{ | 25 | diagnostics::{CheckFixes, DiagnosticCollection}, |
26 | to_proto::url_from_path_with_drive_lowercasing, CheckFixes, DiagnosticCollection, | ||
27 | }, | ||
28 | main_loop::pending_requests::{CompletedRequest, LatestRequests}, | 26 | main_loop::pending_requests::{CompletedRequest, LatestRequests}, |
27 | to_proto::url_from_abs_path, | ||
29 | vfs_glob::{Glob, RustPackageFilterBuilder}, | 28 | vfs_glob::{Glob, RustPackageFilterBuilder}, |
30 | LspError, Result, | 29 | LspError, Result, |
31 | }; | 30 | }; |
@@ -274,8 +273,8 @@ impl GlobalStateSnapshot { | |||
274 | &self.analysis | 273 | &self.analysis |
275 | } | 274 | } |
276 | 275 | ||
277 | pub fn uri_to_file_id(&self, uri: &Url) -> Result<FileId> { | 276 | pub fn url_to_file_id(&self, url: &Url) -> Result<FileId> { |
278 | let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; | 277 | let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?; |
279 | let file = self.vfs.read().path2file(&path).ok_or_else(|| { | 278 | let file = self.vfs.read().path2file(&path).ok_or_else(|| { |
280 | // Show warning as this file is outside current workspace | 279 | // Show warning as this file is outside current workspace |
281 | // FIXME: just handle such files, and remove `LspError::UNKNOWN_FILE`. | 280 | // FIXME: just handle such files, and remove `LspError::UNKNOWN_FILE`. |
@@ -287,11 +286,8 @@ impl GlobalStateSnapshot { | |||
287 | Ok(FileId(file.0)) | 286 | Ok(FileId(file.0)) |
288 | } | 287 | } |
289 | 288 | ||
290 | pub fn file_id_to_uri(&self, id: FileId) -> Result<Url> { | 289 | pub fn file_id_to_url(&self, id: FileId) -> Url { |
291 | let path = self.vfs.read().file2path(VfsFile(id.0)); | 290 | file_id_to_url(&self.vfs.read(), id) |
292 | let url = url_from_path_with_drive_lowercasing(path)?; | ||
293 | |||
294 | Ok(url) | ||
295 | } | 291 | } |
296 | 292 | ||
297 | pub fn file_id_to_path(&self, id: FileId) -> PathBuf { | 293 | pub fn file_id_to_path(&self, id: FileId) -> PathBuf { |
@@ -302,12 +298,10 @@ impl GlobalStateSnapshot { | |||
302 | self.vfs.read().file_line_endings(VfsFile(id.0)) | 298 | self.vfs.read().file_line_endings(VfsFile(id.0)) |
303 | } | 299 | } |
304 | 300 | ||
305 | pub fn path_to_uri(&self, root: SourceRootId, path: &RelativePathBuf) -> Result<Url> { | 301 | pub fn path_to_url(&self, root: SourceRootId, path: &RelativePathBuf) -> Url { |
306 | let base = self.vfs.read().root2path(VfsRoot(root.0)); | 302 | let base = self.vfs.read().root2path(VfsRoot(root.0)); |
307 | let path = path.to_path(base); | 303 | let path = path.to_path(base); |
308 | let url = Url::from_file_path(&path) | 304 | url_from_abs_path(&path) |
309 | .map_err(|_| format!("can't convert path to url: {}", path.display()))?; | ||
310 | Ok(url) | ||
311 | } | 305 | } |
312 | 306 | ||
313 | pub fn status(&self) -> String { | 307 | pub fn status(&self) -> String { |
@@ -335,3 +329,8 @@ impl GlobalStateSnapshot { | |||
335 | self.workspaces.iter().find_map(|ws| ws.workspace_root_for(&path)) | 329 | self.workspaces.iter().find_map(|ws| ws.workspace_root_for(&path)) |
336 | } | 330 | } |
337 | } | 331 | } |
332 | |||
333 | pub(crate) fn file_id_to_url(vfs: &Vfs, id: FileId) -> Url { | ||
334 | let path = vfs.file2path(VfsFile(id.0)); | ||
335 | url_from_abs_path(&path) | ||
336 | } | ||
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 752dbf145..8ec571b70 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -27,7 +27,7 @@ use ra_flycheck::{CheckTask, Status}; | |||
27 | use ra_ide::{Canceled, FileId, LibraryData, LineIndex, SourceRootId}; | 27 | use ra_ide::{Canceled, FileId, LibraryData, LineIndex, SourceRootId}; |
28 | use ra_prof::profile; | 28 | use ra_prof::profile; |
29 | use ra_project_model::{PackageRoot, ProjectWorkspace}; | 29 | use ra_project_model::{PackageRoot, ProjectWorkspace}; |
30 | use ra_vfs::{VfsFile, VfsTask, Watch}; | 30 | use ra_vfs::{VfsTask, Watch}; |
31 | use relative_path::RelativePathBuf; | 31 | use relative_path::RelativePathBuf; |
32 | use rustc_hash::FxHashSet; | 32 | use rustc_hash::FxHashSet; |
33 | use serde::{de::DeserializeOwned, Serialize}; | 33 | use serde::{de::DeserializeOwned, Serialize}; |
@@ -35,9 +35,9 @@ use threadpool::ThreadPool; | |||
35 | 35 | ||
36 | use crate::{ | 36 | use crate::{ |
37 | config::{Config, FilesWatcher, LinkedProject}, | 37 | config::{Config, FilesWatcher, LinkedProject}, |
38 | diagnostics::{to_proto::url_from_path_with_drive_lowercasing, DiagnosticTask}, | 38 | diagnostics::DiagnosticTask, |
39 | from_proto, | 39 | from_proto, |
40 | global_state::{GlobalState, GlobalStateSnapshot}, | 40 | global_state::{file_id_to_url, GlobalState, GlobalStateSnapshot}, |
41 | lsp_ext, | 41 | lsp_ext, |
42 | main_loop::{ | 42 | main_loop::{ |
43 | pending_requests::{PendingRequest, PendingRequests}, | 43 | pending_requests::{PendingRequest, PendingRequests}, |
@@ -801,17 +801,9 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state: | |||
801 | let subscriptions = state.diagnostics.handle_task(task); | 801 | let subscriptions = state.diagnostics.handle_task(task); |
802 | 802 | ||
803 | for file_id in subscriptions { | 803 | for file_id in subscriptions { |
804 | let path = state.vfs.read().file2path(VfsFile(file_id.0)); | 804 | let url = file_id_to_url(&state.vfs.read(), file_id); |
805 | let uri = match url_from_path_with_drive_lowercasing(&path) { | ||
806 | Ok(uri) => uri, | ||
807 | Err(err) => { | ||
808 | log::error!("Couldn't convert path to url ({}): {}", err, path.display()); | ||
809 | continue; | ||
810 | } | ||
811 | }; | ||
812 | |||
813 | let diagnostics = state.diagnostics.diagnostics_for(file_id).cloned().collect(); | 805 | let diagnostics = state.diagnostics.diagnostics_for(file_id).cloned().collect(); |
814 | let params = lsp_types::PublishDiagnosticsParams { uri, diagnostics, version: None }; | 806 | let params = lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version: None }; |
815 | let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params); | 807 | let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params); |
816 | msg_sender.send(not.into()).unwrap(); | 808 | msg_sender.send(not.into()).unwrap(); |
817 | } | 809 | } |
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index a41adf8b0..b34b529b5 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs | |||
@@ -258,7 +258,7 @@ pub fn handle_document_symbol( | |||
258 | let res = if snap.config.client_caps.hierarchical_symbols { | 258 | let res = if snap.config.client_caps.hierarchical_symbols { |
259 | document_symbols.into() | 259 | document_symbols.into() |
260 | } else { | 260 | } else { |
261 | let url = to_proto::url(&snap, file_id)?; | 261 | let url = to_proto::url(&snap, file_id); |
262 | let mut symbol_information = Vec::<SymbolInformation>::new(); | 262 | let mut symbol_information = Vec::<SymbolInformation>::new(); |
263 | for symbol in document_symbols { | 263 | for symbol in document_symbols { |
264 | flatten_document_symbol(&symbol, None, &url, &mut symbol_information); | 264 | flatten_document_symbol(&symbol, None, &url, &mut symbol_information); |
@@ -1160,7 +1160,7 @@ fn show_impl_command_link( | |||
1160 | ) -> Option<lsp_ext::CommandLinkGroup> { | 1160 | ) -> Option<lsp_ext::CommandLinkGroup> { |
1161 | if snap.config.hover.implementations { | 1161 | if snap.config.hover.implementations { |
1162 | if let Some(nav_data) = snap.analysis().goto_implementation(*position).unwrap_or(None) { | 1162 | if let Some(nav_data) = snap.analysis().goto_implementation(*position).unwrap_or(None) { |
1163 | let uri = to_proto::url(snap, position.file_id).ok()?; | 1163 | let uri = to_proto::url(snap, position.file_id); |
1164 | let line_index = snap.analysis().file_line_index(position.file_id).ok()?; | 1164 | let line_index = snap.analysis().file_line_index(position.file_id).ok()?; |
1165 | let position = to_proto::position(&line_index, position.offset); | 1165 | let position = to_proto::position(&line_index, position.offset); |
1166 | let locations: Vec<_> = nav_data | 1166 | let locations: Vec<_> = nav_data |
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 710df1fbd..881aa1c55 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -1,4 +1,7 @@ | |||
1 | //! Conversion of rust-analyzer specific types to lsp_types equivalents. | 1 | //! Conversion of rust-analyzer specific types to lsp_types equivalents. |
2 | use std::path::{self, Path}; | ||
3 | |||
4 | use itertools::Itertools; | ||
2 | use ra_db::{FileId, FileRange}; | 5 | use ra_db::{FileId, FileRange}; |
3 | use ra_ide::{ | 6 | use ra_ide::{ |
4 | Assist, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold, FoldKind, | 7 | Assist, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold, FoldKind, |
@@ -385,24 +388,55 @@ pub(crate) fn folding_range( | |||
385 | } | 388 | } |
386 | } | 389 | } |
387 | 390 | ||
388 | pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> Result<lsp_types::Url> { | 391 | pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> lsp_types::Url { |
389 | snap.file_id_to_uri(file_id) | 392 | snap.file_id_to_url(file_id) |
393 | } | ||
394 | |||
395 | /// Returns a `Url` object from a given path, will lowercase drive letters if present. | ||
396 | /// This will only happen when processing windows paths. | ||
397 | /// | ||
398 | /// When processing non-windows path, this is essentially the same as `Url::from_file_path`. | ||
399 | pub(crate) fn url_from_abs_path(path: &Path) -> lsp_types::Url { | ||
400 | assert!(path.is_absolute()); | ||
401 | let url = lsp_types::Url::from_file_path(path).unwrap(); | ||
402 | match path.components().next() { | ||
403 | Some(path::Component::Prefix(prefix)) if matches!(prefix.kind(), path::Prefix::Disk(_) | path::Prefix::VerbatimDisk(_)) => | ||
404 | { | ||
405 | // Need to lowercase driver letter | ||
406 | } | ||
407 | _ => return url, | ||
408 | } | ||
409 | |||
410 | let driver_letter_range = { | ||
411 | let (scheme, drive_letter, _rest) = match url.as_str().splitn(3, ':').collect_tuple() { | ||
412 | Some(it) => it, | ||
413 | None => return url, | ||
414 | }; | ||
415 | let start = scheme.len() + ':'.len_utf8(); | ||
416 | start..(start + drive_letter.len()) | ||
417 | }; | ||
418 | |||
419 | // Note: lowercasing the `path` itself doesn't help, the `Url::parse` | ||
420 | // machinery *also* canonicalizes the drive letter. So, just massage the | ||
421 | // string in place. | ||
422 | let mut url = url.into_string(); | ||
423 | url[driver_letter_range].make_ascii_lowercase(); | ||
424 | lsp_types::Url::parse(&url).unwrap() | ||
390 | } | 425 | } |
391 | 426 | ||
392 | pub(crate) fn versioned_text_document_identifier( | 427 | pub(crate) fn versioned_text_document_identifier( |
393 | snap: &GlobalStateSnapshot, | 428 | snap: &GlobalStateSnapshot, |
394 | file_id: FileId, | 429 | file_id: FileId, |
395 | version: Option<i64>, | 430 | version: Option<i64>, |
396 | ) -> Result<lsp_types::VersionedTextDocumentIdentifier> { | 431 | ) -> lsp_types::VersionedTextDocumentIdentifier { |
397 | let res = lsp_types::VersionedTextDocumentIdentifier { uri: url(snap, file_id)?, version }; | 432 | lsp_types::VersionedTextDocumentIdentifier { uri: url(snap, file_id), version } |
398 | Ok(res) | ||
399 | } | 433 | } |
400 | 434 | ||
401 | pub(crate) fn location( | 435 | pub(crate) fn location( |
402 | snap: &GlobalStateSnapshot, | 436 | snap: &GlobalStateSnapshot, |
403 | frange: FileRange, | 437 | frange: FileRange, |
404 | ) -> Result<lsp_types::Location> { | 438 | ) -> Result<lsp_types::Location> { |
405 | let url = url(snap, frange.file_id)?; | 439 | let url = url(snap, frange.file_id); |
406 | let line_index = snap.analysis().file_line_index(frange.file_id)?; | 440 | let line_index = snap.analysis().file_line_index(frange.file_id)?; |
407 | let range = range(&line_index, frange.range); | 441 | let range = range(&line_index, frange.range); |
408 | let loc = lsp_types::Location::new(url, range); | 442 | let loc = lsp_types::Location::new(url, range); |
@@ -438,7 +472,7 @@ fn location_info( | |||
438 | ) -> Result<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> { | 472 | ) -> Result<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> { |
439 | let line_index = snap.analysis().file_line_index(target.file_id())?; | 473 | let line_index = snap.analysis().file_line_index(target.file_id())?; |
440 | 474 | ||
441 | let target_uri = url(snap, target.file_id())?; | 475 | let target_uri = url(snap, target.file_id()); |
442 | let target_range = range(&line_index, target.full_range()); | 476 | let target_range = range(&line_index, target.full_range()); |
443 | let target_selection_range = | 477 | let target_selection_range = |
444 | target.focus_range().map(|it| range(&line_index, it)).unwrap_or(target_range); | 478 | target.focus_range().map(|it| range(&line_index, it)).unwrap_or(target_range); |
@@ -478,7 +512,7 @@ pub(crate) fn snippet_text_document_edit( | |||
478 | is_snippet: bool, | 512 | is_snippet: bool, |
479 | source_file_edit: SourceFileEdit, | 513 | source_file_edit: SourceFileEdit, |
480 | ) -> Result<lsp_ext::SnippetTextDocumentEdit> { | 514 | ) -> Result<lsp_ext::SnippetTextDocumentEdit> { |
481 | let text_document = versioned_text_document_identifier(snap, source_file_edit.file_id, None)?; | 515 | let text_document = versioned_text_document_identifier(snap, source_file_edit.file_id, None); |
482 | let line_index = snap.analysis().file_line_index(source_file_edit.file_id)?; | 516 | let line_index = snap.analysis().file_line_index(source_file_edit.file_id)?; |
483 | let line_endings = snap.file_line_endings(source_file_edit.file_id); | 517 | let line_endings = snap.file_line_endings(source_file_edit.file_id); |
484 | let edits = source_file_edit | 518 | let edits = source_file_edit |
@@ -492,19 +526,18 @@ pub(crate) fn snippet_text_document_edit( | |||
492 | pub(crate) fn resource_op( | 526 | pub(crate) fn resource_op( |
493 | snap: &GlobalStateSnapshot, | 527 | snap: &GlobalStateSnapshot, |
494 | file_system_edit: FileSystemEdit, | 528 | file_system_edit: FileSystemEdit, |
495 | ) -> Result<lsp_types::ResourceOp> { | 529 | ) -> lsp_types::ResourceOp { |
496 | let res = match file_system_edit { | 530 | match file_system_edit { |
497 | FileSystemEdit::CreateFile { source_root, path } => { | 531 | FileSystemEdit::CreateFile { source_root, path } => { |
498 | let uri = snap.path_to_uri(source_root, &path)?; | 532 | let uri = snap.path_to_url(source_root, &path); |
499 | lsp_types::ResourceOp::Create(lsp_types::CreateFile { uri, options: None }) | 533 | lsp_types::ResourceOp::Create(lsp_types::CreateFile { uri, options: None }) |
500 | } | 534 | } |
501 | FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => { | 535 | FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => { |
502 | let old_uri = snap.file_id_to_uri(src)?; | 536 | let old_uri = snap.file_id_to_url(src); |
503 | let new_uri = snap.path_to_uri(dst_source_root, &dst_path)?; | 537 | let new_uri = snap.path_to_url(dst_source_root, &dst_path); |
504 | lsp_types::ResourceOp::Rename(lsp_types::RenameFile { old_uri, new_uri, options: None }) | 538 | lsp_types::ResourceOp::Rename(lsp_types::RenameFile { old_uri, new_uri, options: None }) |
505 | } | 539 | } |
506 | }; | 540 | } |
507 | Ok(res) | ||
508 | } | 541 | } |
509 | 542 | ||
510 | pub(crate) fn snippet_workspace_edit( | 543 | pub(crate) fn snippet_workspace_edit( |
@@ -513,7 +546,7 @@ pub(crate) fn snippet_workspace_edit( | |||
513 | ) -> Result<lsp_ext::SnippetWorkspaceEdit> { | 546 | ) -> Result<lsp_ext::SnippetWorkspaceEdit> { |
514 | let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new(); | 547 | let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new(); |
515 | for op in source_change.file_system_edits { | 548 | for op in source_change.file_system_edits { |
516 | let op = resource_op(&snap, op)?; | 549 | let op = resource_op(&snap, op); |
517 | document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Op(op)); | 550 | document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Op(op)); |
518 | } | 551 | } |
519 | for edit in source_change.source_file_edits { | 552 | for edit in source_change.source_file_edits { |
@@ -568,7 +601,7 @@ impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit { | |||
568 | } | 601 | } |
569 | } | 602 | } |
570 | 603 | ||
571 | pub fn call_hierarchy_item( | 604 | pub(crate) fn call_hierarchy_item( |
572 | snap: &GlobalStateSnapshot, | 605 | snap: &GlobalStateSnapshot, |
573 | target: NavigationTarget, | 606 | target: NavigationTarget, |
574 | ) -> Result<lsp_types::CallHierarchyItem> { | 607 | ) -> Result<lsp_types::CallHierarchyItem> { |
@@ -579,50 +612,6 @@ pub fn call_hierarchy_item( | |||
579 | Ok(lsp_types::CallHierarchyItem { name, kind, tags: None, detail, uri, range, selection_range }) | 612 | Ok(lsp_types::CallHierarchyItem { name, kind, tags: None, detail, uri, range, selection_range }) |
580 | } | 613 | } |
581 | 614 | ||
582 | #[cfg(test)] | ||
583 | mod tests { | ||
584 | use test_utils::extract_ranges; | ||
585 | |||
586 | use super::*; | ||
587 | |||
588 | #[test] | ||
589 | fn conv_fold_line_folding_only_fixup() { | ||
590 | let text = r#"<fold>mod a; | ||
591 | mod b; | ||
592 | mod c;</fold> | ||
593 | |||
594 | fn main() <fold>{ | ||
595 | if cond <fold>{ | ||
596 | a::do_a(); | ||
597 | }</fold> else <fold>{ | ||
598 | b::do_b(); | ||
599 | }</fold> | ||
600 | }</fold>"#; | ||
601 | |||
602 | let (ranges, text) = extract_ranges(text, "fold"); | ||
603 | assert_eq!(ranges.len(), 4); | ||
604 | let folds = vec![ | ||
605 | Fold { range: ranges[0], kind: FoldKind::Mods }, | ||
606 | Fold { range: ranges[1], kind: FoldKind::Block }, | ||
607 | Fold { range: ranges[2], kind: FoldKind::Block }, | ||
608 | Fold { range: ranges[3], kind: FoldKind::Block }, | ||
609 | ]; | ||
610 | |||
611 | let line_index = LineIndex::new(&text); | ||
612 | let converted: Vec<lsp_types::FoldingRange> = | ||
613 | folds.into_iter().map(|it| folding_range(&text, &line_index, true, it)).collect(); | ||
614 | |||
615 | let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)]; | ||
616 | assert_eq!(converted.len(), expected_lines.len()); | ||
617 | for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) { | ||
618 | assert_eq!(folding_range.start_line, *start_line); | ||
619 | assert_eq!(folding_range.start_character, None); | ||
620 | assert_eq!(folding_range.end_line, *end_line); | ||
621 | assert_eq!(folding_range.end_character, None); | ||
622 | } | ||
623 | } | ||
624 | } | ||
625 | |||
626 | pub(crate) fn unresolved_code_action( | 615 | pub(crate) fn unresolved_code_action( |
627 | snap: &GlobalStateSnapshot, | 616 | snap: &GlobalStateSnapshot, |
628 | assist: Assist, | 617 | assist: Assist, |
@@ -676,3 +665,62 @@ pub(crate) fn runnable( | |||
676 | }, | 665 | }, |
677 | }) | 666 | }) |
678 | } | 667 | } |
668 | |||
669 | #[cfg(test)] | ||
670 | mod tests { | ||
671 | use test_utils::extract_ranges; | ||
672 | |||
673 | use super::*; | ||
674 | |||
675 | #[test] | ||
676 | fn conv_fold_line_folding_only_fixup() { | ||
677 | let text = r#"<fold>mod a; | ||
678 | mod b; | ||
679 | mod c;</fold> | ||
680 | |||
681 | fn main() <fold>{ | ||
682 | if cond <fold>{ | ||
683 | a::do_a(); | ||
684 | }</fold> else <fold>{ | ||
685 | b::do_b(); | ||
686 | }</fold> | ||
687 | }</fold>"#; | ||
688 | |||
689 | let (ranges, text) = extract_ranges(text, "fold"); | ||
690 | assert_eq!(ranges.len(), 4); | ||
691 | let folds = vec![ | ||
692 | Fold { range: ranges[0], kind: FoldKind::Mods }, | ||
693 | Fold { range: ranges[1], kind: FoldKind::Block }, | ||
694 | Fold { range: ranges[2], kind: FoldKind::Block }, | ||
695 | Fold { range: ranges[3], kind: FoldKind::Block }, | ||
696 | ]; | ||
697 | |||
698 | let line_index = LineIndex::new(&text); | ||
699 | let converted: Vec<lsp_types::FoldingRange> = | ||
700 | folds.into_iter().map(|it| folding_range(&text, &line_index, true, it)).collect(); | ||
701 | |||
702 | let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)]; | ||
703 | assert_eq!(converted.len(), expected_lines.len()); | ||
704 | for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) { | ||
705 | assert_eq!(folding_range.start_line, *start_line); | ||
706 | assert_eq!(folding_range.start_character, None); | ||
707 | assert_eq!(folding_range.end_line, *end_line); | ||
708 | assert_eq!(folding_range.end_character, None); | ||
709 | } | ||
710 | } | ||
711 | |||
712 | // `Url` is not able to parse windows paths on unix machines. | ||
713 | #[test] | ||
714 | #[cfg(target_os = "windows")] | ||
715 | fn test_lowercase_drive_letter_with_drive() { | ||
716 | let url = url_from_abs_path(Path::new("C:\\Test")); | ||
717 | assert_eq!(url.to_string(), "file:///c:/Test"); | ||
718 | } | ||
719 | |||
720 | #[test] | ||
721 | #[cfg(target_os = "windows")] | ||
722 | fn test_drive_without_colon_passthrough() { | ||
723 | let url = url_from_abs_path(Path::new(r#"\\localhost\C$\my_dir"#)); | ||
724 | assert_eq!(url.to_string(), "file://localhost/C$/my_dir"); | ||
725 | } | ||
726 | } | ||