1use std::path::{Path, PathBuf};
2
3#[cfg(windows)]
4pub fn canonicalize(path: impl AsRef<Path>) -> std::io::Result<PathBuf> {
5 dunce::canonicalize(path)
6}
7
8#[cfg(not(windows))]
9pub fn canonicalize(path: impl AsRef<Path>) -> std::io::Result<PathBuf> {
10 std::fs::canonicalize(path)
11}
12
13#[cfg(windows)]
14pub fn expand_tilde(path: &str) -> PathBuf {
15 return PathBuf::from(path);
16}
17
18#[cfg(not(windows))]
19pub fn expand_tilde(path: &str) -> PathBuf {
20 if let Some(stripped) = path.strip_prefix("~/")
21 && let Some(home_dir) = dirs::home_dir()
22 {
23 return home_dir.join(stripped);
24 }
25
26 PathBuf::from(path)
27}
28
29pub fn calculate_distance_penalty(current_file: Option<&str>, candidate_dir: &str) -> i32 {
37 let Some(current_path) = current_file else {
38 return 0;
39 };
40
41 let current_dir = Path::new(current_path).parent().unwrap_or(Path::new(""));
42 let candidate = Path::new(candidate_dir);
43
44 if current_dir == candidate {
45 return 0;
46 }
47
48 let mut current_parts = current_dir.components();
49 let mut candidate_parts = candidate.components();
50
51 let mut common_len = 0usize;
52 let mut current_total = 0usize;
53
54 loop {
55 match (current_parts.next(), candidate_parts.next()) {
56 (Some(a), Some(b)) => {
57 current_total += 1;
58 if a == b {
59 common_len += 1;
60 } else {
61 current_total += current_parts.count();
62 break;
63 }
64 }
65 (Some(_), None) => {
66 current_total += 1 + current_parts.count();
67 break;
68 }
69 (None, _) => {
70 break;
71 }
72 }
73 }
74
75 let depth_from_common = current_total - common_len;
76 if depth_from_common == 0 {
77 return 0;
78 }
79
80 (-(depth_from_common as i32)).max(-20)
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 #[cfg(not(target_family = "windows"))]
89 fn test_calculate_distance_penalty() {
90 assert_eq!(calculate_distance_penalty(None, "examples/user/test/"), 0);
92 assert_eq!(
94 calculate_distance_penalty(Some("examples/user/test/main.rs"), "examples/user/test/"),
95 0
96 );
97 assert_eq!(
100 calculate_distance_penalty(
101 Some("examples/user/test/subdir/file.rs"),
102 "examples/user/test/"
103 ),
104 -1
105 );
106 assert_eq!(
109 calculate_distance_penalty(
110 Some("examples/user/test/dir1/file.rs"),
111 "examples/user/test/dir2/"
112 ),
113 -1
114 );
115
116 assert_eq!(
117 calculate_distance_penalty(
118 Some("examples/audio-announce/src/lib/audio-announce.rs"),
119 "examples/audio-announce/src/"
120 ),
121 -1
122 );
123
124 assert_eq!(
125 calculate_distance_penalty(
126 Some("examples/audio-announce/src/audio-announce.rs"),
127 "examples/pixel/src/"
128 ),
129 -2
130 );
131
132 assert_eq!(calculate_distance_penalty(Some("main.rs"), ""), 0);
134 }
135
136 #[test]
137 #[cfg(target_family = "windows")]
138 fn distance_penalty_works_on_windows() {
139 assert_eq!(
140 calculate_distance_penalty(None, "examples\\user\\test\\"),
141 0
142 );
143 assert_eq!(
145 calculate_distance_penalty(
146 Some("examples\\user\\test\\main.rs"),
147 "examples\\user\\test\\"
148 ),
149 0
150 );
151 assert_eq!(
154 calculate_distance_penalty(
155 Some("examples\\user\\test\\subdir\\file.rs"),
156 "examples\\user\\test\\"
157 ),
158 -1
159 );
160 }
161}