on master help sign in
Canonicalize paths before stripping current dir as prefix (#6290)

Co-authored-by: jazzfool <shamoslover69@gmail.com>

commitd04288e0f3d292ce47fc0246bcbdc50a9d57ad5e authorjazzfool <56189047+jazzfool@users.noreply.github.com> dateFri 31 Mar 2023, 03:21 (4 years ago) parent5b3dd6a
6 files changed+37−14
Cargo.lock+1 helix-core/Cargo.toml+1 helix-core/src/path.rs+25−4 helix-term/tests/integration.rs−2 helix-term/tests/test/commands/write.rs+5−5 helix-term/tests/test/splits.rs+5−3
Cargo.lock+1
@@ -1082,6 +1082,7 @@ dependencies = [
10821082 "arc-swap",
10831083 "bitflags 2.0.2",
10841084 "chrono",
1085+ "dunce",
10851086 "encoding_rs",
10861087 "etcetera",
10871088 "hashbrown 0.13.2",
helix-core/Cargo.toml+1
@@ -32,6 +32,7 @@ regex = "1"
3232bitflags = "2.0"
3333ahash = "0.8.3"
3434hashbrown = { version = "0.13.2", features = ["raw"] }
35+dunce = "1.0"
3536
3637log = "0.4"
3738serde = { version = "1.0", features = ["derive"] }
helix-core/src/path.rs+25−4
@@ -40,6 +40,21 @@ pub fn expand_tilde(path: &Path) -> PathBuf {
4040/// needs to improve on.
4141/// Copied from cargo: <https://github.com/rust-lang/cargo/blob/070e459c2d8b79c5b2ac5218064e7603329c92ae/crates/cargo-util/src/paths.rs#L81>
4242pub fn get_normalized_path(path: &Path) -> PathBuf {
43+ // normalization strategy is to canonicalize first ancestor path that exists (i.e., canonicalize as much as possible),
44+ // then run handrolled normalization on the non-existent remainder
45+ let (base, path) = path
46+ .ancestors()
47+ .find_map(|base| {
48+ let canonicalized_base = dunce::canonicalize(base).ok()?;
49+ let remainder = path.strip_prefix(base).ok()?.into();
50+ Some((canonicalized_base, remainder))
51+ })
52+ .unwrap_or_else(|| (PathBuf::new(), PathBuf::from(path)));
53+
54+ if path.as_os_str().is_empty() {
55+ return base;
56+ }
57+
4358 let mut components = path.components().peekable();
4459 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
4560 components.next();
@@ -63,7 +78,7 @@ pub fn get_normalized_path(path: &Path) -> PathBuf {
6378 }
6479 }
6580 }
66− ret
81+ base.join(ret)
6782}
6883
6984/// Returns the canonical, absolute form of a path with all intermediate components normalized.
@@ -82,13 +97,19 @@ pub fn get_canonicalized_path(path: &Path) -> std::io::Result<PathBuf> {
8297}
8398
8499pub fn get_relative_path(path: &Path) -> PathBuf {
100+ let path = PathBuf::from(path);
85101 let path = if path.is_absolute() {
86− let cwdir = std::env::current_dir().expect("couldn't determine current directory");
87− path.strip_prefix(cwdir).unwrap_or(path)
102+ let cwdir = std::env::current_dir()
103+ .map(|path| get_normalized_path(&path))
104+ .expect("couldn't determine current directory");
105+ get_normalized_path(&path)
106+ .strip_prefix(cwdir)
107+ .map(PathBuf::from)
108+ .unwrap_or(path)
88109 } else {
89110 path
90111 };
91− fold_home_dir(path)
112+ fold_home_dir(&path)
92113}
93114
94115/// Returns a truncated filepath where the basepart of the path is reduced to the first
helix-term/tests/integration.rs−2
@@ -2,8 +2,6 @@
22mod test {
33 mod helpers;
44
5− use std::path::PathBuf;
6−
75 use helix_core::{syntax::AutoPairConfig, Selection};
86 use helix_term::config::Config;
97
helix-term/tests/test/commands/write.rs+5−5
@@ -3,7 +3,7 @@ use std::{
33 ops::RangeInclusive,
44};
55
6−use helix_core::diagnostic::Severity;
6+use helix_core::{diagnostic::Severity, path::get_normalized_path};
77use helix_view::doc;
88
99use super::*;
@@ -23,7 +23,7 @@ async fn test_write_quit_fail() -> anyhow::Result<()> {
2323 assert_eq!(1, docs.len());
2424
2525 let doc = docs.pop().unwrap();
26− assert_eq!(Some(file.path()), doc.path().map(PathBuf::as_path));
26+ assert_eq!(Some(&get_normalized_path(file.path())), doc.path());
2727 assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1);
2828 }),
2929 false,
@@ -269,7 +269,7 @@ async fn test_write_scratch_to_new_path() -> anyhow::Result<()> {
269269 assert_eq!(1, docs.len());
270270
271271 let doc = docs.pop().unwrap();
272− assert_eq!(Some(&file.path().to_path_buf()), doc.path());
272+ assert_eq!(Some(&get_normalized_path(file.path())), doc.path());
273273 }),
274274 false,
275275 )
@@ -341,7 +341,7 @@ async fn test_write_new_path() -> anyhow::Result<()> {
341341 Some(&|app| {
342342 let doc = doc!(app.editor);
343343 assert!(!app.editor.is_err());
344− assert_eq!(file1.path(), doc.path().unwrap());
344+ assert_eq!(&get_normalized_path(file1.path()), doc.path().unwrap());
345345 }),
346346 ),
347347 (
@@ -349,7 +349,7 @@ async fn test_write_new_path() -> anyhow::Result<()> {
349349 Some(&|app| {
350350 let doc = doc!(app.editor);
351351 assert!(!app.editor.is_err());
352− assert_eq!(file2.path(), doc.path().unwrap());
352+ assert_eq!(&get_normalized_path(file2.path()), doc.path().unwrap());
353353 assert!(app.editor.document_by_path(file1.path()).is_none());
354354 }),
355355 ),
helix-term/tests/test/splits.rs+5−3
@@ -1,5 +1,7 @@
11use super::*;
22
3+use helix_core::path::get_normalized_path;
4+
35#[tokio::test(flavor = "multi_thread")]
46async fn test_split_write_quit_all() -> anyhow::Result<()> {
57 let mut file1 = tempfile::NamedTempFile::new()?;
@@ -25,21 +27,21 @@ async fn test_split_write_quit_all() -> anyhow::Result<()> {
2527
2628 let doc1 = docs
2729 .iter()
28− .find(|doc| doc.path().unwrap() == file1.path())
30+ .find(|doc| doc.path().unwrap() == &get_normalized_path(file1.path()))
2931 .unwrap();
3032
3133 assert_eq!("hello1", doc1.text().to_string());
3234
3335 let doc2 = docs
3436 .iter()
35− .find(|doc| doc.path().unwrap() == file2.path())
37+ .find(|doc| doc.path().unwrap() == &get_normalized_path(file2.path()))
3638 .unwrap();
3739
3840 assert_eq!("hello2", doc2.text().to_string());
3941
4042 let doc3 = docs
4143 .iter()
42− .find(|doc| doc.path().unwrap() == file3.path())
44+ .find(|doc| doc.path().unwrap() == &get_normalized_path(file3.path()))
4345 .unwrap();
4446
4547 assert_eq!("hello3", doc3.text().to_string());