1use std::path::Path;
4
5pub fn hyperlink_name(path: &Path, text: &str, enabled: bool) -> String {
7 if !enabled {
8 return text.to_string();
9 }
10 let uri = file_uri(path);
11 format!("\x1b]8;;{uri}\x1b\\{text}\x1b]8;;\x1b\\")
13}
14
15fn file_uri(path: &Path) -> String {
16 let abs = if path.is_absolute() {
17 path.to_path_buf()
18 } else {
19 std::env::current_dir()
20 .map(|cwd| cwd.join(path))
21 .unwrap_or_else(|_| path.to_path_buf())
22 };
23 let host = hostname();
24 let mut uri = String::from("file://");
25 if !host.is_empty() {
26 uri.push_str(&host);
27 }
28 for b in abs.to_string_lossy().bytes() {
30 match b {
31 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'/' | b'.' | b'-' | b'_' | b'~' => {
32 uri.push(b as char);
33 }
34 _ => uri.push_str(&format!("%{b:02X}")),
35 }
36 }
37 uri
38}
39
40fn hostname() -> String {
41 std::env::var("HOSTNAME")
42 .or_else(|_| std::env::var("COMPUTERNAME"))
43 .unwrap_or_default()
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49 use std::path::PathBuf;
50
51 #[test]
52 fn disabled_is_passthrough() {
53 assert_eq!(hyperlink_name(Path::new("/tmp/x"), "x", false), "x");
54 }
55
56 #[test]
57 fn enabled_wraps_osc8() {
58 let s = hyperlink_name(&PathBuf::from("/tmp/x"), "x", true);
59 assert!(s.contains("\x1b]8;;file://"), "{s:?}");
60 assert!(s.contains("/tmp/x") || s.contains("%"), "{s:?}");
61 assert!(s.ends_with("\x1b]8;;\x1b\\"), "{s:?}");
62 }
63}