aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-06-13 10:00:06 +0100
committerAleksey Kladov <[email protected]>2020-06-13 13:15:38 +0100
commit50bbf7233dcda8a27255f123622bf57651c9f51c (patch)
tree183d011ed6a2f5fc5917d7bcd2763e7896a47a4f
parentb56ad148db0c69eb279c225f45d324b4e80e7367 (diff)
Cleanup URL handling
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs73
-rw-r--r--crates/rust-analyzer/src/from_proto.rs2
-rw-r--r--crates/rust-analyzer/src/global_state.rs27
-rw-r--r--crates/rust-analyzer/src/main_loop.rs18
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs4
-rw-r--r--crates/rust-analyzer/src/to_proto.rs170
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.
3use std::{ 3use std::{collections::HashMap, path::Path};
4 collections::HashMap,
5 path::{Component, Path, Prefix},
6 str::FromStr,
7};
8 4
9use lsp_types::{ 5use lsp_types::{
10 Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location, 6 Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location,
@@ -13,7 +9,7 @@ use lsp_types::{
13use ra_flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion}; 9use ra_flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion};
14use stdx::format_to; 10use stdx::format_to;
15 11
16use crate::{lsp_ext, Result}; 12use 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
19fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> { 15fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> {
@@ -65,7 +61,7 @@ fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &Path) -> Locatio
65fn map_span_to_location_naive(span: &DiagnosticSpan, workspace_root: &Path) -> Location { 61fn 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`.
282pub 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))]
315mod tests { 276mod 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
19pub(crate) fn file_id(world: &GlobalStateSnapshot, url: &lsp_types::Url) -> Result<FileId> { 19pub(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
23pub(crate) fn file_position( 23pub(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
23use crate::{ 23use 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
333pub(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};
27use ra_ide::{Canceled, FileId, LibraryData, LineIndex, SourceRootId}; 27use ra_ide::{Canceled, FileId, LibraryData, LineIndex, SourceRootId};
28use ra_prof::profile; 28use ra_prof::profile;
29use ra_project_model::{PackageRoot, ProjectWorkspace}; 29use ra_project_model::{PackageRoot, ProjectWorkspace};
30use ra_vfs::{VfsFile, VfsTask, Watch}; 30use ra_vfs::{VfsTask, Watch};
31use relative_path::RelativePathBuf; 31use relative_path::RelativePathBuf;
32use rustc_hash::FxHashSet; 32use rustc_hash::FxHashSet;
33use serde::{de::DeserializeOwned, Serialize}; 33use serde::{de::DeserializeOwned, Serialize};
@@ -35,9 +35,9 @@ use threadpool::ThreadPool;
35 35
36use crate::{ 36use 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.
2use std::path::{self, Path};
3
4use itertools::Itertools;
2use ra_db::{FileId, FileRange}; 5use ra_db::{FileId, FileRange};
3use ra_ide::{ 6use 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
388pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> Result<lsp_types::Url> { 391pub(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`.
399pub(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
392pub(crate) fn versioned_text_document_identifier( 427pub(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
401pub(crate) fn location( 435pub(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(
492pub(crate) fn resource_op( 526pub(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
510pub(crate) fn snippet_workspace_edit( 543pub(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
571pub fn call_hierarchy_item( 604pub(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)]
583mod 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;
591mod b;
592mod c;</fold>
593
594fn 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
626pub(crate) fn unresolved_code_action( 615pub(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)]
670mod 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;
678mod b;
679mod c;</fold>
680
681fn 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}