Skip to main content

freya_terminal/
osc7.rs

1//! OSC 7 (current working directory) extraction.
2//!
3//! alacritty's vte dispatcher silently drops OSC 7, so we run a second
4//! `vte::Parser` whose `Perform` impl intercepts only that sequence.
5
6use std::path::PathBuf;
7
8use alacritty_terminal::vte::Perform;
9
10/// Captures the payload of OSC 7 sequences seen on the byte stream.
11#[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
33/// Parse the URL payload of an OSC 7 sequence into a filesystem path.
34/// Accepts `file:///path`, `file://host/path`, and bare paths.
35pub(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}