1use once_cell::sync::Lazy;
2use regex::Regex;
3use std::borrow::Cow;
4use url::Url;
5
6use super::path as path_utils;
7
8const FILE_PROTOCOL: &str = "file://";
9static RE_URI_PROTOCOL: Lazy<Regex> = Lazy::new(|| Regex::new(r"^([A-Za-z_-]+://)").unwrap());
10
11pub fn is_url(path: &str) -> bool {
13 RE_URI_PROTOCOL
14 .captures(path)
15 .and_then(|v| v.get(0))
16 .is_some()
17}
18
19pub fn to_url(path: &str, base: &Option<Url>) -> Option<Url> {
21 if is_url(path) {
22 return path.parse().ok();
23 }
24 let url = if path_utils::is_window(path) {
25 format!("{}/{}", FILE_PROTOCOL, path_utils::encode_url(path))
26 } else if path_utils::is_absolute(path) {
27 if let Some(base) = base.as_ref() {
28 let mut base: Url = base.clone();
29 base.set_path("");
30 format!("{}{}", base, path_utils::to_unix(path))
31 } else {
32 format!("{}{}", FILE_PROTOCOL, path_utils::encode_url(path))
33 }
34 } else {
35 let base = base.as_ref()?.as_str();
36 let slash = if base.ends_with('/') { "" } else { "/" };
37 format!("{}{}{}", base, slash, path_utils::to_unix(path))
38 };
39 url.parse().ok()
40}
41
42pub fn to_file_path(url: &Url) -> Option<String> {
44 let path = url.path();
45 let mut chars = path.chars();
46 let mut driver = None;
47 let check_driver = |v: char, driver: &mut Option<char>| {
48 if v.is_ascii_alphabetic() {
49 *driver = Some(v);
50 true
51 } else {
52 false
53 }
54 };
55 if matches!(
56 (
57 chars.next(),
58 chars.next().map(|v| check_driver(v, &mut driver)),
59 chars.next(),
60 chars.next(),
61 chars.next()
62 ),
63 (Some('/'), Some(true), Some('%'), Some('3'), Some('A'))
64 ) {
65 let (_, path) = path.split_at(5);
67 let path = path
68 .split('/')
69 .map(|v| urlencoding::decode(v).ok())
70 .collect::<Option<Vec<Cow<str>>>>()?
71 .join("\\");
72 Some(format!("{}:{}", driver.unwrap().to_ascii_uppercase(), path))
73 } else {
74 Some(path.to_string())
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 macro_rules! assert_to_url {
82 ($p:expr, $o:expr) => {
83 assert_eq!(to_url($p, &None).unwrap().to_string(), $o);
84 };
85 ($p:expr, $b:expr, $o:expr) => {
86 let b = $b.parse().ok();
87 assert_eq!(to_url($p, &b).unwrap().to_string(), $o);
88 };
89 }
90 macro_rules! assert_to_file_path {
91 ($i:expr, $o:expr) => {
92 assert_eq!(to_file_path(&$i.parse().unwrap()).unwrap(), $o.to_string());
93 };
94 }
95
96 #[test]
97 fn test_to_url() {
98 assert_to_url!("a\\b", "file:///c%3A/dir1", "file:///c%3A/dir1/a/b");
99 assert_to_url!("D:\\c", "file:///c%3A/dir1", "file:///d%3A/c");
100 assert_to_url!("a/b", "file:///dir1", "file:///dir1/a/b");
101 assert_to_url!("/a/b", "file:///dir1", "file:///a/b");
102 assert_to_url!("/a/b", "file:///dir1/", "file:///a/b");
103 assert_to_url!(
104 "/a/b",
105 "vscode-test-web://mount",
106 "vscode-test-web://mount/a/b"
107 );
108 assert_to_url!(
109 "/a/b",
110 "vscode-test-web://mount/",
111 "vscode-test-web://mount/a/b"
112 );
113 assert_to_url!(
114 "a/b",
115 "vscode-test-web://mount",
116 "vscode-test-web://mount/a/b"
117 );
118 assert_to_url!(
119 "a/b",
120 "vscode-test-web://mount/",
121 "vscode-test-web://mount/a/b"
122 );
123 assert_to_url!("a/b", "vscode-vfs:///dir1", "vscode-vfs:///dir1/a/b");
124 assert_to_url!("http://example.com/a/b", "http://example.com/a/b");
125 }
126
127 #[test]
128 fn test_to_file_path() {
129 assert_to_file_path!("file:///c%3A/a/b", "C:\\a\\b");
130 assert_to_file_path!("file:///C%3A/a/b", "C:\\a\\b");
131 assert_to_file_path!("file:///dir1/a/b", "/dir1/a/b");
132 assert_to_file_path!(
133 "vscode-vfs://github/jsona/schemastore",
134 "/jsona/schemastore"
135 );
136 }
137
138 #[test]
139 fn test_is_url() {
140 assert!(is_url("file:///c%3A/a/b"));
141 assert!(is_url("vscode-test-web://mount"));
142 assert!(is_url("vscode-vfs://github/jsona/jsona"));
143 assert!(!is_url("/a/b"));
144 assert!(!is_url("a/b"));
145 assert!(!is_url("C:\\a\\b"));
146 }
147}