1use std::path::PathBuf;
7
8use alacritty_terminal::vte::Perform;
9
10#[derive(Default)]
12pub(crate) struct CwdSink {
13 latest: Option<String>,
14}
15
16impl CwdSink {
17 pub(crate) fn take(&mut self) -> Option<String> {
18 self.latest.take()
19 }
20}
21
22impl Perform for CwdSink {
23 fn osc_dispatch(&mut self, params: &[&[u8]], _bell_terminated: bool) {
24 if params.len() >= 2
25 && params[0] == b"7"
26 && let Ok(url) = std::str::from_utf8(params[1])
27 {
28 self.latest = Some(url.to_owned());
29 }
30 }
31}
32
33pub(crate) fn parse_cwd_url(url: &str) -> PathBuf {
36 let Some(stripped) = url.strip_prefix("file://") else {
37 return PathBuf::from(url);
38 };
39 match stripped.split_once('/') {
40 Some((_, path)) => PathBuf::from(format!("/{path}")),
41 None => PathBuf::from(stripped),
42 }
43}
44
45#[cfg(test)]
46mod tests {
47 use alacritty_terminal::vte::Parser as VteParser;
48
49 use super::*;
50
51 fn sniff(chunks: &[&[u8]]) -> Option<String> {
52 let mut parser = VteParser::new();
53 let mut sink = CwdSink::default();
54 for chunk in chunks {
55 parser.advance(&mut sink, chunk);
56 }
57 sink.take()
58 }
59
60 #[test]
61 fn osc7_bel_in_one_chunk() {
62 assert_eq!(
63 sniff(&[b"prefix\x1b]7;file:///home/marc\x07tail"]).as_deref(),
64 Some("file:///home/marc"),
65 );
66 }
67
68 #[test]
69 fn osc7_st_in_one_chunk() {
70 assert_eq!(
71 sniff(&[b"\x1b]7;file:///tmp\x1b\\"]).as_deref(),
72 Some("file:///tmp"),
73 );
74 }
75
76 #[test]
77 fn osc7_split_across_chunks() {
78 assert_eq!(
79 sniff(&[b"\x1b]7;file://", b"/var", b"/log\x07"]).as_deref(),
80 Some("file:///var/log"),
81 );
82 }
83
84 #[test]
85 fn ignores_other_oscs() {
86 assert_eq!(sniff(&[b"\x1b]0;hello\x07"]), None);
87 assert_eq!(sniff(&[b"\x1b]70;nope\x07"]), None);
88 }
89}