tattoy_wezterm_escape_parser/
osc.rs

1use crate::color::SrgbaTuple;
2pub use crate::hyperlink::Hyperlink;
3use crate::{Result, bail, ensure, format_err};
4use base64::Engine;
5use bitflags::bitflags;
6use core::fmt::{Display, Error as FmtError, Formatter, Result as FmtResult};
7use core::str;
8use core::str::FromStr;
9use num_derive::*;
10use num_traits::FromPrimitive;
11use ordered_float::NotNan;
12#[cfg(feature = "std")]
13use std::sync::LazyLock;
14
15use crate::allocate::*;
16
17#[derive(Debug, Clone, PartialEq)]
18pub enum ColorOrQuery {
19    Color(SrgbaTuple),
20    Query,
21}
22
23impl Display for ColorOrQuery {
24    fn fmt(&self, f: &mut Formatter) -> FmtResult {
25        match self {
26            ColorOrQuery::Query => write!(f, "?"),
27            ColorOrQuery::Color(c) => write!(f, "{}", c.to_x11_16bit_rgb_string()),
28        }
29    }
30}
31
32#[derive(Debug, Clone, PartialEq)]
33pub enum OperatingSystemCommand {
34    SetIconNameAndWindowTitle(String),
35    SetWindowTitle(String),
36    SetWindowTitleSun(String),
37    SetIconName(String),
38    SetIconNameSun(String),
39    SetHyperlink(Option<Hyperlink>),
40    ClearSelection(Selection),
41    QuerySelection(Selection),
42    SetSelection(Selection, String),
43    SystemNotification(String),
44    ITermProprietary(ITermProprietary),
45    FinalTermSemanticPrompt(FinalTermSemanticPrompt),
46    ChangeColorNumber(Vec<ChangeColorPair>),
47    ChangeDynamicColors(DynamicColorNumber, Vec<ColorOrQuery>),
48    ResetDynamicColor(DynamicColorNumber),
49    CurrentWorkingDirectory(String),
50    ResetColors(Vec<u8>),
51    RxvtExtension(Vec<String>),
52    ConEmuProgress(Progress),
53
54    Unspecified(Vec<Vec<u8>>),
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive)]
58#[repr(u8)]
59pub enum DynamicColorNumber {
60    TextForegroundColor = 10,
61    TextBackgroundColor = 11,
62    TextCursorColor = 12,
63    MouseForegroundColor = 13,
64    MouseBackgroundColor = 14,
65    TektronixForegroundColor = 15,
66    TektronixBackgroundColor = 16,
67    HighlightBackgroundColor = 17,
68    TektronixCursorColor = 18,
69    HighlightForegroundColor = 19,
70}
71
72#[derive(Debug, Clone, PartialEq)]
73pub struct ChangeColorPair {
74    pub palette_index: u8,
75    pub color: ColorOrQuery,
76}
77
78bitflags! {
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub struct Selection :u16{
81    const NONE = 0;
82    const CLIPBOARD = 1<<1;
83    const PRIMARY=1<<2;
84    const SELECT=1<<3;
85    const CUT0=1<<4;
86    const CUT1=1<<5;
87    const CUT2=1<<6;
88    const CUT3=1<<7;
89    const CUT4=1<<8;
90    const CUT5=1<<9;
91    const CUT6=1<<10;
92    const CUT7=1<<11;
93    const CUT8=1<<12;
94    const CUT9=1<<13;
95}
96}
97
98impl Selection {
99    fn try_parse(buf: &[u8]) -> Result<Selection> {
100        if buf == b"" {
101            Ok(Selection::SELECT | Selection::CUT0)
102        } else {
103            let mut s = Selection::NONE;
104            for c in buf {
105                s |= match c {
106                    b'c' => Selection::CLIPBOARD,
107                    b'p' => Selection::PRIMARY,
108                    b's' => Selection::SELECT,
109                    b'0' => Selection::CUT0,
110                    b'1' => Selection::CUT1,
111                    b'2' => Selection::CUT2,
112                    b'3' => Selection::CUT3,
113                    b'4' => Selection::CUT4,
114                    b'5' => Selection::CUT5,
115                    b'6' => Selection::CUT6,
116                    b'7' => Selection::CUT7,
117                    b'8' => Selection::CUT8,
118                    b'9' => Selection::CUT9,
119                    _ => bail!("invalid selection {:?}", buf),
120                }
121            }
122            Ok(s)
123        }
124    }
125}
126
127impl Display for Selection {
128    fn fmt(&self, f: &mut Formatter) -> FmtResult {
129        macro_rules! item {
130            ($variant:ident, $s:expr) => {
131                if (*self & Selection::$variant) != Selection::NONE {
132                    write!(f, $s)?;
133                }
134            };
135        }
136
137        item!(CLIPBOARD, "c");
138        item!(PRIMARY, "p");
139        item!(SELECT, "s");
140        item!(CUT0, "0");
141        item!(CUT1, "1");
142        item!(CUT2, "2");
143        item!(CUT3, "3");
144        item!(CUT4, "4");
145        item!(CUT5, "5");
146        item!(CUT6, "6");
147        item!(CUT7, "7");
148        item!(CUT8, "8");
149        item!(CUT9, "9");
150        Ok(())
151    }
152}
153
154impl OperatingSystemCommand {
155    pub fn parse(osc: &[&[u8]]) -> Self {
156        Self::internal_parse(osc).unwrap_or_else(|err| {
157            let mut vec = Vec::new();
158            for slice in osc {
159                vec.push(slice.to_vec());
160            }
161            log::trace!(
162                "OSC internal parse err: {}, track as Unspecified {:?}",
163                err,
164                vec
165            );
166            OperatingSystemCommand::Unspecified(vec)
167        })
168    }
169
170    fn parse_selection(osc: &[&[u8]]) -> Result<Self> {
171        if osc.len() == 2 {
172            Selection::try_parse(osc[1]).map(OperatingSystemCommand::ClearSelection)
173        } else if osc.len() == 3 && osc[2] == b"?" {
174            Selection::try_parse(osc[1]).map(OperatingSystemCommand::QuerySelection)
175        } else if osc.len() == 3 {
176            let sel = Selection::try_parse(osc[1])?;
177            let bytes = base64_decode(osc[2])?;
178            let s = String::from_utf8(bytes)?;
179            Ok(OperatingSystemCommand::SetSelection(sel, s))
180        } else {
181            bail!("unhandled OSC 52: {:?}", osc);
182        }
183    }
184
185    fn parse_reset_colors(osc: &[&[u8]]) -> Result<Self> {
186        let mut colors = vec![];
187        let mut iter = osc.iter();
188        iter.next(); // skip the command word that we already know is present
189
190        while let Some(index) = iter.next() {
191            if index.is_empty() {
192                continue;
193            }
194            let index: u8 = str::from_utf8(index)?.parse()?;
195            colors.push(index);
196        }
197
198        Ok(OperatingSystemCommand::ResetColors(colors))
199    }
200
201    fn parse_change_color_number(osc: &[&[u8]]) -> Result<Self> {
202        let mut pairs = vec![];
203        let mut iter = osc.iter();
204        iter.next(); // skip the command word that we already know is present
205
206        while let (Some(index), Some(spec)) = (iter.next(), iter.next()) {
207            let index: u8 = str::from_utf8(index)?.parse()?;
208            let spec = str::from_utf8(spec)?;
209            let spec = if spec == "?" {
210                ColorOrQuery::Query
211            } else {
212                ColorOrQuery::Color(
213                    SrgbaTuple::from_str(spec)
214                        .map_err(|()| format!("invalid color spec {:?}", spec))?,
215                )
216            };
217
218            pairs.push(ChangeColorPair {
219                palette_index: index,
220                color: spec,
221            });
222        }
223
224        Ok(OperatingSystemCommand::ChangeColorNumber(pairs))
225    }
226
227    fn parse_reset_dynamic_color_number(idx: u8) -> Result<Self> {
228        let which_color: DynamicColorNumber = FromPrimitive::from_u8(idx)
229            .ok_or_else(|| format!("osc code is not a valid DynamicColorNumber!?"))?;
230
231        Ok(OperatingSystemCommand::ResetDynamicColor(which_color))
232    }
233
234    fn parse_change_dynamic_color_number(idx: u8, osc: &[&[u8]]) -> Result<Self> {
235        let which_color: DynamicColorNumber = FromPrimitive::from_u8(idx)
236            .ok_or_else(|| format!("osc code is not a valid DynamicColorNumber!?"))?;
237        let mut colors = vec![];
238        for spec in osc.iter().skip(1) {
239            if spec == b"?" {
240                colors.push(ColorOrQuery::Query);
241            } else {
242                let spec = str::from_utf8(spec)?;
243                colors.push(ColorOrQuery::Color(
244                    SrgbaTuple::from_str(spec)
245                        .map_err(|()| format!("invalid color spec {:?}", spec))?,
246                ));
247            }
248        }
249
250        Ok(OperatingSystemCommand::ChangeDynamicColors(
251            which_color,
252            colors,
253        ))
254    }
255
256    fn internal_parse(osc: &[&[u8]]) -> Result<Self> {
257        ensure!(!osc.is_empty(), "no params");
258        let p1str = String::from_utf8_lossy(osc[0]);
259
260        if p1str.is_empty() {
261            bail!("zero length osc");
262        }
263
264        // Ugh, this is to handle "OSC ltitle" which is a legacyish
265        // OSC for encoding a window title change request.  These days
266        // OSC 2 is preferred for this purpose, but we need to support
267        // generating and parsing the legacy form because it is the
268        // response for the CSI ReportWindowTitle.
269        // So, for non-numeric OSCs, we look up the prefix and use that.
270        // This only works if the non-numeric OSC code has length == 1.
271        let osc_code = if !p1str.chars().nth(0).unwrap().is_ascii_digit() && osc.len() == 1 {
272            let mut p1 = String::new();
273            p1.push(p1str.chars().nth(0).unwrap());
274            OperatingSystemCommandCode::from_code(&p1)
275        } else {
276            OperatingSystemCommandCode::from_code(&p1str)
277        }
278        .ok_or_else(|| format!("unknown code"))?;
279
280        macro_rules! single_string {
281            ($variant:ident) => {{
282                if osc.len() != 2 {
283                    bail!("wrong param count");
284                }
285                let s = String::from_utf8(osc[1].to_vec())?;
286                Ok(OperatingSystemCommand::$variant(s))
287            }};
288        }
289
290        macro_rules! single_title_string {
291            ($variant:ident) => {{
292                if osc.len() < 2 {
293                    bail!("wrong param count");
294                }
295                let mut s = String::from_utf8(osc[1].to_vec())?;
296                for i in 2..osc.len() {
297                    s = [s, String::from_utf8(osc[i].to_vec())?].join(";");
298                }
299
300                Ok(OperatingSystemCommand::$variant(s))
301            }};
302        }
303
304        use self::OperatingSystemCommandCode::*;
305        match osc_code {
306            SetIconNameAndWindowTitle => single_title_string!(SetIconNameAndWindowTitle),
307            SetWindowTitle => single_title_string!(SetWindowTitle),
308            SetWindowTitleSun => Ok(OperatingSystemCommand::SetWindowTitleSun(
309                p1str[1..].to_owned(),
310            )),
311
312            SetIconName => single_title_string!(SetIconName),
313            SetIconNameSun => Ok(OperatingSystemCommand::SetIconNameSun(
314                p1str[1..].to_owned(),
315            )),
316            SetHyperlink => Ok(OperatingSystemCommand::SetHyperlink(Hyperlink::parse(osc)?)),
317            ManipulateSelectionData => Self::parse_selection(osc),
318            SystemNotification => {
319                if osc.len() >= 3 && osc[1] == b"4" {
320                    fn get_pct(v: &&[u8]) -> u8 {
321                        let number = str::from_utf8(v).unwrap_or("0");
322                        number.parse::<u8>().unwrap_or(0).max(0).min(100)
323                    }
324                    match osc[2] {
325                        b"0" => return Ok(OperatingSystemCommand::ConEmuProgress(Progress::None)),
326                        b"1" => {
327                            let pct = osc.get(3).map(get_pct).unwrap_or(0);
328                            return Ok(OperatingSystemCommand::ConEmuProgress(
329                                Progress::SetPercentage(pct),
330                            ));
331                        }
332                        b"2" => {
333                            let pct = osc.get(3).map(get_pct).unwrap_or(0);
334                            return Ok(OperatingSystemCommand::ConEmuProgress(Progress::SetError(
335                                pct,
336                            )));
337                        }
338                        b"3" => {
339                            return Ok(OperatingSystemCommand::ConEmuProgress(
340                                Progress::SetIndeterminate,
341                            ));
342                        }
343                        b"4" => {
344                            return Ok(OperatingSystemCommand::ConEmuProgress(Progress::Paused));
345                        }
346                        _ => {}
347                    }
348                }
349                single_string!(SystemNotification)
350            }
351            SetCurrentWorkingDirectory => single_string!(CurrentWorkingDirectory),
352            ITermProprietary => {
353                self::ITermProprietary::parse(osc).map(OperatingSystemCommand::ITermProprietary)
354            }
355            RxvtProprietary => {
356                let mut vec = vec![];
357                for slice in osc.iter().skip(1) {
358                    vec.push(String::from_utf8_lossy(slice).to_string());
359                }
360                Ok(OperatingSystemCommand::RxvtExtension(vec))
361            }
362            FinalTermSemanticPrompt => self::FinalTermSemanticPrompt::parse(osc)
363                .map(OperatingSystemCommand::FinalTermSemanticPrompt),
364            ChangeColorNumber => Self::parse_change_color_number(osc),
365            ResetColors => Self::parse_reset_colors(osc),
366
367            ResetSpecialColor
368            | ResetTextForegroundColor
369            | ResetTextBackgroundColor
370            | ResetTextCursorColor
371            | ResetMouseForegroundColor
372            | ResetMouseBackgroundColor
373            | ResetTektronixForegroundColor
374            | ResetTektronixBackgroundColor
375            | ResetHighlightColor
376            | ResetTektronixCursorColor
377            | ResetHighlightForegroundColor => Self::parse_reset_dynamic_color_number(
378                p1str.parse::<u8>().unwrap().saturating_sub(100),
379            ),
380
381            SetTextForegroundColor
382            | SetTextBackgroundColor
383            | SetTextCursorColor
384            | SetMouseForegroundColor
385            | SetMouseBackgroundColor
386            | SetTektronixForegroundColor
387            | SetTektronixBackgroundColor
388            | SetHighlightBackgroundColor
389            | SetTektronixCursorColor
390            | SetHighlightForegroundColor => {
391                Self::parse_change_dynamic_color_number(p1str.parse::<u8>().unwrap(), osc)
392            }
393
394            osc_code => bail!("{:?} not impl", osc_code),
395        }
396    }
397}
398
399macro_rules! osc_entries {
400($(
401    $( #[doc=$doc:expr] )*
402    $label:ident = $value:expr
403),* $(,)?) => {
404
405#[derive(Debug, Clone, PartialEq, Eq, FromPrimitive, Hash, Copy)]
406pub enum OperatingSystemCommandCode {
407    $(
408        $( #[doc=$doc] )*
409        $label,
410    )*
411}
412
413impl OscMap {
414#[cfg(feature = "std")]
415    fn new() -> Self {
416        let mut code_to_variant = HashMap::new();
417        let mut variant_to_code = HashMap::new();
418
419        use OperatingSystemCommandCode::*;
420
421        $(
422            code_to_variant.insert($value, $label);
423            variant_to_code.insert($label, $value);
424        )*
425
426        Self {
427            code_to_variant,
428            variant_to_code,
429        }
430    }
431
432#[cfg(not(feature = "std"))]
433    fn linear_search_code(code: &str) -> Option<OperatingSystemCommandCode> {
434        use OperatingSystemCommandCode::*;
435        match code {
436        $(
437            $value => Some($label),
438        )*
439            _ => None,
440        }
441    }
442
443#[cfg(not(feature = "std"))]
444    fn linear_search_variant(v: &OperatingSystemCommandCode) -> &'static str {
445        use OperatingSystemCommandCode::*;
446        match *v {
447        $(
448            $label => $value,
449        )*
450        }
451    }
452
453}
454    };
455}
456
457osc_entries!(
458    SetIconNameAndWindowTitle = "0",
459    SetIconName = "1",
460    SetWindowTitle = "2",
461    SetXWindowProperty = "3",
462    ChangeColorNumber = "4",
463    ChangeSpecialColorNumber = "5",
464    /// iTerm2
465    ChangeTitleTabColor = "6",
466    SetCurrentWorkingDirectory = "7",
467    /// See https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
468    SetHyperlink = "8",
469    /// iTerm2
470    SystemNotification = "9",
471    SetTextForegroundColor = "10",
472    SetTextBackgroundColor = "11",
473    SetTextCursorColor = "12",
474    SetMouseForegroundColor = "13",
475    SetMouseBackgroundColor = "14",
476    SetTektronixForegroundColor = "15",
477    SetTektronixBackgroundColor = "16",
478    SetHighlightBackgroundColor = "17",
479    SetTektronixCursorColor = "18",
480    SetHighlightForegroundColor = "19",
481    SetLogFileName = "46",
482    SetFont = "50",
483    EmacsShell = "51",
484    ManipulateSelectionData = "52",
485    ResetColors = "104",
486    ResetSpecialColor = "105",
487    ResetTextForegroundColor = "110",
488    ResetTextBackgroundColor = "111",
489    ResetTextCursorColor = "112",
490    ResetMouseForegroundColor = "113",
491    ResetMouseBackgroundColor = "114",
492    ResetTektronixForegroundColor = "115",
493    ResetTektronixBackgroundColor = "116",
494    ResetHighlightColor = "117",
495    ResetTektronixCursorColor = "118",
496    ResetHighlightForegroundColor = "119",
497    RxvtProprietary = "777",
498    FinalTermSemanticPrompt = "133",
499    ITermProprietary = "1337",
500    /// Here the "Sun" suffix comes from the table in
501    /// <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Miscellaneous>
502    /// that lays out various window related escape sequences.
503    SetWindowTitleSun = "l",
504    SetIconNameSun = "L",
505);
506
507struct OscMap {
508    #[cfg(feature = "std")]
509    code_to_variant: HashMap<&'static str, OperatingSystemCommandCode>,
510    #[cfg(feature = "std")]
511    variant_to_code: HashMap<OperatingSystemCommandCode, &'static str>,
512}
513
514#[cfg(feature = "std")]
515static OSC_MAP: LazyLock<OscMap> = LazyLock::new(OscMap::new);
516
517#[cfg(feature = "std")]
518impl OperatingSystemCommandCode {
519    fn from_code(code: &str) -> Option<Self> {
520        OSC_MAP.code_to_variant.get(code).copied()
521    }
522
523    fn as_code(self) -> &'static str {
524        OSC_MAP.variant_to_code.get(&self).unwrap()
525    }
526}
527
528#[cfg(not(feature = "std"))]
529impl OperatingSystemCommandCode {
530    fn from_code(code: &str) -> Option<Self> {
531        OscMap::linear_search_code(code)
532    }
533
534    fn as_code(self) -> &'static str {
535        OscMap::linear_search_variant(&self)
536    }
537}
538
539impl Display for OperatingSystemCommand {
540    fn fmt(&self, f: &mut Formatter) -> FmtResult {
541        write!(f, "\x1b]")?;
542
543        macro_rules! single_string {
544            ($variant:ident, $s:expr) => {{
545                let code = OperatingSystemCommandCode::$variant.as_code();
546                match OperatingSystemCommandCode::$variant {
547                    OperatingSystemCommandCode::SetWindowTitleSun
548                    | OperatingSystemCommandCode::SetIconNameSun => {
549                        // For the legacy sun terminals, the `l` and `L` OSCs are
550                        // not separated by `;`.
551                        write!(f, "{}{}", code, $s)?;
552                    }
553                    _ => {
554                        // In the common case, the OSC is numeric and is separated
555                        // from the rest of the string
556                        write!(f, "{};{}", code, $s)?;
557                    }
558                }
559            }};
560        }
561
562        use self::OperatingSystemCommand::*;
563        match self {
564            SetIconNameAndWindowTitle(title) => single_string!(SetIconNameAndWindowTitle, title),
565            SetWindowTitle(title) => single_string!(SetWindowTitle, title),
566            SetWindowTitleSun(title) => single_string!(SetWindowTitleSun, title),
567            SetIconName(title) => single_string!(SetIconName, title),
568            SetIconNameSun(title) => single_string!(SetIconNameSun, title),
569            SetHyperlink(Some(link)) => link.fmt(f)?,
570            SetHyperlink(None) => write!(f, "8;;")?,
571            RxvtExtension(params) => write!(f, "777;{}", params.join(";"))?,
572            Unspecified(v) => {
573                for (idx, item) in v.iter().enumerate() {
574                    if idx > 0 {
575                        write!(f, ";")?;
576                    }
577                    f.write_str(&String::from_utf8_lossy(item))?;
578                }
579            }
580            ClearSelection(s) => write!(f, "52;{}", s)?,
581            QuerySelection(s) => write!(f, "52;{};?", s)?,
582            SetSelection(s, val) => write!(f, "52;{};{}", s, base64_encode(val))?,
583            SystemNotification(s) => write!(f, "9;{}", s)?,
584            ITermProprietary(i) => i.fmt(f)?,
585            FinalTermSemanticPrompt(i) => i.fmt(f)?,
586            ResetColors(colors) => {
587                write!(f, "104")?;
588                for c in colors {
589                    write!(f, ";{}", c)?;
590                }
591            }
592            ChangeColorNumber(specs) => {
593                write!(f, "4;")?;
594                for pair in specs {
595                    write!(f, "{};{}", pair.palette_index, pair.color)?
596                }
597            }
598            ChangeDynamicColors(first_color, colors) => {
599                write!(f, "{}", *first_color as u8)?;
600                for color in colors {
601                    write!(f, ";{}", color)?
602                }
603            }
604            ResetDynamicColor(color) => {
605                write!(f, "{}", 100 + *color as u8)?;
606            }
607            CurrentWorkingDirectory(s) => write!(f, "7;{}", s)?,
608            ConEmuProgress(Progress::None) => write!(f, "9;4;0")?,
609            ConEmuProgress(Progress::SetPercentage(pct)) => write!(f, "9;4;1;{pct}")?,
610            ConEmuProgress(Progress::SetError(pct)) => write!(f, "9;4;2;{pct}")?,
611            ConEmuProgress(Progress::SetIndeterminate) => write!(f, "9;4;3")?,
612            ConEmuProgress(Progress::Paused) => write!(f, "9;4;4")?,
613        };
614        // Use the longer form ST as neovim doesn't like the BEL version
615        write!(f, "\x1b\\")?;
616        Ok(())
617    }
618}
619
620/// https://gitlab.freedesktop.org/Per_Bothner/specifications/blob/master/proposals/semantic-prompts.md
621#[derive(Debug, Clone, PartialEq, Eq)]
622pub enum FinalTermClick {
623    /// Allow motion only within the single input line using left/right arrow keys
624    Line,
625    /// Allow moving between multiple lines of input using left/right arrow keys
626    MultipleLine,
627    /// Allow left/right and conservative up/down arrow motion
628    ConservativeVertical,
629    /// Allow left/right and up/down motion, and the line editor ensures that
630    /// there are no spurious trailing spaces at ends of lines and that vertical
631    /// motion across shorter lines causes some horizontal cursor motion.
632    SmartVertical,
633}
634
635impl core::convert::TryFrom<&str> for FinalTermClick {
636    type Error = crate::Error;
637    fn try_from(s: &str) -> Result<Self> {
638        match s {
639            "line" => Ok(Self::Line),
640            "m" => Ok(Self::MultipleLine),
641            "v" => Ok(Self::ConservativeVertical),
642            "w" => Ok(Self::SmartVertical),
643            _ => bail!("invalid FinalTermClick {}", s),
644        }
645    }
646}
647
648impl Display for FinalTermClick {
649    fn fmt(&self, f: &mut Formatter) -> FmtResult {
650        match self {
651            Self::Line => write!(f, "line"),
652            Self::MultipleLine => write!(f, "m"),
653            Self::ConservativeVertical => write!(f, "v"),
654            Self::SmartVertical => write!(f, "w"),
655        }
656    }
657}
658
659/// https://gitlab.freedesktop.org/Per_Bothner/specifications/blob/master/proposals/semantic-prompts.md
660#[derive(Debug, Clone, PartialEq, Eq)]
661pub enum FinalTermPromptKind {
662    /// A normal left side primary prompt
663    Initial,
664    /// A right-aligned prompt
665    RightSide,
666    /// A continuation prompt for an input that can be edited
667    Continuation,
668    /// A continuation prompt where the input cannot be edited
669    Secondary,
670}
671
672impl Default for FinalTermPromptKind {
673    fn default() -> Self {
674        Self::Initial
675    }
676}
677
678impl core::convert::TryFrom<&str> for FinalTermPromptKind {
679    type Error = crate::Error;
680    fn try_from(s: &str) -> Result<Self> {
681        match s {
682            "i" => Ok(Self::Initial),
683            "r" => Ok(Self::RightSide),
684            "c" => Ok(Self::Continuation),
685            "s" => Ok(Self::Secondary),
686            _ => bail!("invalid FinalTermPromptKind {}", s),
687        }
688    }
689}
690
691impl Display for FinalTermPromptKind {
692    fn fmt(&self, f: &mut Formatter) -> FmtResult {
693        match self {
694            Self::Initial => write!(f, "i"),
695            Self::RightSide => write!(f, "r"),
696            Self::Continuation => write!(f, "c"),
697            Self::Secondary => write!(f, "s"),
698        }
699    }
700}
701
702/// https://gitlab.freedesktop.org/Per_Bothner/specifications/blob/master/proposals/semantic-prompts.md
703#[derive(Debug, Clone, PartialEq, Eq)]
704pub enum FinalTermSemanticPrompt {
705    /// Do a "fresh line"; if the cursor is at the left margin then
706    /// do nothing, otherwise perform the equivalent of "\r\n"
707    FreshLine,
708
709    /// Do a "fresh line" as above and then place the terminal into
710    /// prompt mode; the output between now and the next marker is
711    /// considered part of the prompt.
712    FreshLineAndStartPrompt {
713        aid: Option<String>,
714        cl: Option<FinalTermClick>,
715    },
716
717    /// Denote the end of a command output and then perform FreshLine
718    MarkEndOfCommandWithFreshLine {
719        aid: Option<String>,
720        cl: Option<FinalTermClick>,
721    },
722
723    /// Start a prompt
724    StartPrompt(FinalTermPromptKind),
725
726    /// Mark the end of a prompt and the start of the user input.
727    /// The terminal considers all subsequent output to be "user input"
728    /// until the next semantic marker.
729    MarkEndOfPromptAndStartOfInputUntilNextMarker,
730
731    /// Mark the end of a prompt and the start of the user input.
732    /// The terminal considers all subsequent output to be "user input"
733    /// until the end of the line.
734    MarkEndOfPromptAndStartOfInputUntilEndOfLine,
735
736    MarkEndOfInputAndStartOfOutput {
737        aid: Option<String>,
738    },
739
740    /// Indicates the result of the command
741    CommandStatus {
742        status: i32,
743        aid: Option<String>,
744    },
745}
746
747impl FinalTermSemanticPrompt {
748    fn parse(osc: &[&[u8]]) -> Result<Self> {
749        ensure!(osc.len() > 1, "not enough args");
750        let param = String::from_utf8_lossy(osc[1]);
751
752        macro_rules! single {
753            ($variant:ident, $text:expr) => {
754                if osc.len() == 2 && param == $text {
755                    return Ok(FinalTermSemanticPrompt::$variant);
756                }
757            };
758        }
759
760        single!(FreshLine, "L");
761        single!(MarkEndOfPromptAndStartOfInputUntilNextMarker, "B");
762        single!(MarkEndOfPromptAndStartOfInputUntilEndOfLine, "I");
763
764        let mut params = HashMap::new();
765        use core::convert::TryInto;
766
767        for s in osc.iter().skip(if param == "D" { 3 } else { 2 }) {
768            if let Some(equal) = s.iter().position(|c| *c == b'=') {
769                let key = &s[..equal];
770                let value = &s[equal + 1..];
771                params.insert(str::from_utf8(key)?, str::from_utf8(value)?);
772            } else if !s.is_empty() {
773                bail!("malformed FinalTermSemanticPrompt");
774            }
775        }
776
777        if param == "A" {
778            return Ok(Self::FreshLineAndStartPrompt {
779                aid: params.get("aid").map(|&s| s.to_owned()),
780                cl: match params.get("cl") {
781                    Some(&cl) => Some(cl.try_into()?),
782                    None => None,
783                },
784            });
785        }
786
787        if param == "C" {
788            return Ok(Self::MarkEndOfInputAndStartOfOutput {
789                aid: params.get("aid").map(|&s| s.to_owned()),
790            });
791        }
792
793        if param == "D" {
794            let status = match osc.get(2).map(|&p| p) {
795                Some(s) => match str::from_utf8(s) {
796                    Ok(s) => s.parse().unwrap_or(0),
797                    _ => 0,
798                },
799                _ => 0,
800            };
801
802            return Ok(Self::CommandStatus {
803                status,
804                aid: params.get("aid").map(|&s| s.to_owned()),
805            });
806        }
807
808        if param == "N" {
809            return Ok(Self::MarkEndOfCommandWithFreshLine {
810                aid: params.get("aid").map(|&s| s.to_owned()),
811                cl: match params.get("cl") {
812                    Some(&cl) => Some(cl.try_into()?),
813                    None => None,
814                },
815            });
816        }
817
818        if param == "P" {
819            return Ok(Self::StartPrompt(match params.get("k") {
820                Some(&cl) => cl.try_into()?,
821                None => FinalTermPromptKind::default(),
822            }));
823        }
824
825        bail!(
826            "invalid FinalTermSemanticPrompt p1:{:?}, params:{:?}",
827            param,
828            params
829        );
830    }
831}
832
833impl Display for FinalTermSemanticPrompt {
834    fn fmt(&self, f: &mut Formatter) -> FmtResult {
835        write!(f, "133;")?;
836        match self {
837            Self::FreshLine => write!(f, "L")?,
838            Self::FreshLineAndStartPrompt { aid, cl } => {
839                write!(f, "A")?;
840                if let Some(aid) = aid {
841                    write!(f, ";aid={}", aid)?;
842                }
843                if let Some(cl) = cl {
844                    write!(f, ";cl={}", cl)?;
845                }
846            }
847            Self::MarkEndOfCommandWithFreshLine { aid, cl } => {
848                write!(f, "N")?;
849                if let Some(aid) = aid {
850                    write!(f, ";aid={}", aid)?;
851                }
852                if let Some(cl) = cl {
853                    write!(f, ";cl={}", cl)?;
854                }
855            }
856            Self::StartPrompt(kind) => {
857                write!(f, "P;k={}", kind)?;
858            }
859            Self::MarkEndOfPromptAndStartOfInputUntilNextMarker => write!(f, "B")?,
860            Self::MarkEndOfPromptAndStartOfInputUntilEndOfLine => write!(f, "I")?,
861            Self::MarkEndOfInputAndStartOfOutput { aid } => {
862                write!(f, "C")?;
863                if let Some(aid) = aid {
864                    write!(f, ";aid={}", aid)?;
865                }
866            }
867            Self::CommandStatus {
868                status,
869                aid: Some(aid),
870            } => {
871                write!(f, "D;{};err={};aid={}", status, status, aid)?;
872            }
873            Self::CommandStatus { status, aid: None } => {
874                write!(f, "D;{}", status)?;
875            }
876        }
877        Ok(())
878    }
879}
880
881#[derive(Debug, Clone, PartialEq, Eq)]
882pub enum Progress {
883    None,
884    SetPercentage(u8),
885    SetError(u8),
886    SetIndeterminate,
887    Paused,
888}
889
890#[derive(Debug, Clone, PartialEq, Eq)]
891pub enum ITermProprietary {
892    /// The "Set Mark" command allows you to record a location and then jump back to it later
893    SetMark,
894    /// To bring iTerm2 to the foreground
895    StealFocus,
896    /// To erase the scrollback history
897    ClearScrollback,
898    /// To inform iTerm2 of the current directory to help semantic history
899    CurrentDir(String),
900    /// To change the session's profile on the fly
901    SetProfile(String),
902    /// Currently defined values for the string parameter are "rule", "find", "font"
903    /// or an empty string.  iTerm2 will go into paste mode until EndCopy is received.
904    CopyToClipboard(String),
905    /// Ends CopyToClipboard mode in iTerm2.
906    EndCopy,
907    /// The boolean should be yes or no. This shows or hides the cursor guide
908    HighlightCursorLine(bool),
909    /// Request that the terminal send a ReportCellSize response
910    RequestCellSize,
911    /// The response to RequestCellSize.  The height and width are the dimensions
912    /// of a cell measured in points according to the docs, but in practice, they
913    /// are actually pixels.
914    /// If scale is_some(), the width and height will be multiplied by scale to
915    /// get the true device dimensions
916    ReportCellSize {
917        height_pixels: NotNan<f32>,
918        width_pixels: NotNan<f32>,
919        scale: Option<NotNan<f32>>,
920    },
921    /// Place a string in the systems pasteboard
922    Copy(String),
923    /// Each iTerm2 session has internal variables (as described in
924    /// <https://www.iterm2.com/documentation-badges.html>). This escape sequence reports
925    /// a variable's value.  The response is another ReportVariable.
926    ReportVariable(String),
927    /// User-defined variables may be set with the following escape sequence
928    SetUserVar {
929        name: String,
930        value: String,
931    },
932    SetBadgeFormat(String),
933    /// Download file data from the application.
934    File(Box<ITermFileData>),
935
936    /// Configure unicode version
937    UnicodeVersion(ITermUnicodeVersionOp),
938}
939
940#[derive(Debug, Clone, PartialEq, Eq)]
941pub enum ITermUnicodeVersionOp {
942    Set(u8),
943    Push(Option<String>),
944    Pop(Option<String>),
945}
946
947#[derive(Debug, Clone, PartialEq, Eq)]
948pub struct ITermFileData {
949    /// file name
950    pub name: Option<String>,
951    /// size of the data in bytes; this is used by iterm to show progress
952    /// while waiting for the rest of the payload
953    pub size: Option<usize>,
954    /// width to render
955    pub width: ITermDimension,
956    /// height to render
957    pub height: ITermDimension,
958    /// if true, preserve aspect ratio when fitting to width/height
959    pub preserve_aspect_ratio: bool,
960    /// if true, attempt to display in the terminal rather than downloading to
961    /// the users download directory
962    pub inline: bool,
963    /// if true, do not move the cursor
964    pub do_not_move_cursor: bool,
965    /// The data to transfer
966    pub data: Vec<u8>,
967}
968
969impl ITermFileData {
970    fn parse(osc: &[&[u8]]) -> Result<Self> {
971        let mut params = HashMap::new();
972
973        // Unfortunately, the encoding for the file download data is
974        // awkward to fit in the conventional OSC data that our parser
975        // expects at a higher level.
976        // We have a mix of '=', ';' and ':' separated keys and values,
977        // and a number of them are optional.
978        // ESC ] 1337 ; File = [optional arguments] : base-64 encoded file contents ^G
979
980        let mut data = None;
981
982        let last = osc.len() - 1;
983        for (idx, s) in osc.iter().enumerate().skip(1) {
984            let param = if idx == 1 {
985                if s.len() >= 5 {
986                    // skip over File=
987                    &s[5..]
988                } else {
989                    bail!("failed to parse file data; File= not found");
990                }
991            } else {
992                s
993            };
994
995            let param = if idx == last {
996                // The final argument contains `:base64`, so look for that
997                if let Some(colon) = param.iter().position(|c| *c == b':') {
998                    data = Some(base64_decode(&param[colon + 1..])?);
999                    &param[..colon]
1000                } else {
1001                    // If we don't find the colon in the last piece, we've
1002                    // got nothing useful
1003                    bail!("failed to parse file data; no colon found");
1004                }
1005            } else {
1006                param
1007            };
1008
1009            // eg: `File=;size=1234` case. <https://github.com/wezterm/wezterm/issues/1291>
1010            if param.is_empty() {
1011                continue;
1012            }
1013
1014            // look for k=v in param
1015            if let Some(equal) = param.iter().position(|c| *c == b'=') {
1016                let key = &param[..equal];
1017                let value = &param[equal + 1..];
1018                params.insert(str::from_utf8(key)?, str::from_utf8(value)?);
1019            } else if idx != last {
1020                bail!("failed to parse file data; no equals found");
1021            }
1022        }
1023
1024        let name = params
1025            .get("name")
1026            .and_then(|s| base64_decode(s).ok())
1027            .and_then(|b| String::from_utf8(b).ok());
1028        let size = params.get("size").and_then(|s| s.parse().ok());
1029        let width = params
1030            .get("width")
1031            .and_then(|s| ITermDimension::parse(s).ok())
1032            .unwrap_or(ITermDimension::Automatic);
1033        let height = params
1034            .get("height")
1035            .and_then(|s| ITermDimension::parse(s).ok())
1036            .unwrap_or(ITermDimension::Automatic);
1037        let preserve_aspect_ratio = params
1038            .get("preserveAspectRatio")
1039            .map(|s| *s != "0")
1040            .unwrap_or(true);
1041        let inline = params.get("inline").map(|s| *s != "0").unwrap_or(false);
1042        let do_not_move_cursor = params
1043            .get("doNotMoveCursor")
1044            .map(|s| *s != "0")
1045            .unwrap_or(false);
1046        let data = data.ok_or_else(|| format!("didn't set data"))?;
1047        Ok(Self {
1048            name,
1049            size,
1050            width,
1051            height,
1052            preserve_aspect_ratio,
1053            inline,
1054            do_not_move_cursor,
1055            data,
1056        })
1057    }
1058}
1059
1060impl Display for ITermFileData {
1061    fn fmt(&self, f: &mut Formatter) -> FmtResult {
1062        write!(f, "File")?;
1063        let mut sep = "=";
1064        let emit_sep = |sep, f: &mut Formatter| -> core::result::Result<&str, FmtError> {
1065            write!(f, "{}", sep)?;
1066            Ok(";")
1067        };
1068        if let Some(size) = self.size {
1069            sep = emit_sep(sep, f)?;
1070            write!(f, "size={}", size)?;
1071        }
1072        if let Some(ref name) = self.name {
1073            sep = emit_sep(sep, f)?;
1074            write!(f, "name={}", base64_encode(name))?;
1075        }
1076        if self.width != ITermDimension::Automatic {
1077            sep = emit_sep(sep, f)?;
1078            write!(f, "width={}", self.width)?;
1079        }
1080        if self.height != ITermDimension::Automatic {
1081            sep = emit_sep(sep, f)?;
1082            write!(f, "height={}", self.height)?;
1083        }
1084        if !self.preserve_aspect_ratio {
1085            sep = emit_sep(sep, f)?;
1086            write!(f, "preserveAspectRatio=0")?;
1087        }
1088        if self.inline {
1089            sep = emit_sep(sep, f)?;
1090            write!(f, "inline=1")?;
1091        }
1092        if self.do_not_move_cursor {
1093            sep = emit_sep(sep, f)?;
1094            write!(f, "doNotMoveCursor=1")?;
1095        }
1096        // Ensure that we emit a sep if we didn't already.
1097        // It will still be set to '=' in that case.
1098        if sep == "=" {
1099            write!(f, "=")?;
1100        }
1101        write!(f, ":{}", base64_encode(&self.data))?;
1102        Ok(())
1103    }
1104}
1105
1106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1107pub enum ITermDimension {
1108    Automatic,
1109    Cells(i64),
1110    Pixels(i64),
1111    Percent(i64),
1112}
1113
1114impl Default for ITermDimension {
1115    fn default() -> Self {
1116        Self::Automatic
1117    }
1118}
1119
1120impl Display for ITermDimension {
1121    fn fmt(&self, f: &mut Formatter) -> FmtResult {
1122        use self::ITermDimension::*;
1123        match self {
1124            Automatic => write!(f, "auto"),
1125            Cells(n) => write!(f, "{}", n),
1126            Pixels(n) => write!(f, "{}px", n),
1127            Percent(n) => write!(f, "{}%", n),
1128        }
1129    }
1130}
1131
1132impl core::str::FromStr for ITermDimension {
1133    type Err = crate::Error;
1134    fn from_str(s: &str) -> Result<Self> {
1135        ITermDimension::parse(s)
1136    }
1137}
1138
1139impl ITermDimension {
1140    fn parse(s: &str) -> Result<Self> {
1141        if s == "auto" {
1142            Ok(ITermDimension::Automatic)
1143        } else if s.ends_with("px") {
1144            let s = &s[..s.len() - 2];
1145            let num = s.parse()?;
1146            Ok(ITermDimension::Pixels(num))
1147        } else if s.ends_with('%') {
1148            let s = &s[..s.len() - 1];
1149            let num = s.parse()?;
1150            Ok(ITermDimension::Percent(num))
1151        } else {
1152            let num = s.parse()?;
1153            Ok(ITermDimension::Cells(num))
1154        }
1155    }
1156
1157    /// Convert the dimension into a number of pixels based on the provided
1158    /// size of a cell and number of cells in that dimension.
1159    /// Returns None for the Automatic variant.
1160    pub fn to_pixels(&self, cell_size: usize, num_cells: usize) -> Option<usize> {
1161        match self {
1162            ITermDimension::Automatic => None,
1163            ITermDimension::Cells(n) => Some((*n).max(0) as usize * cell_size),
1164            ITermDimension::Pixels(n) => Some((*n).max(0) as usize),
1165            ITermDimension::Percent(n) => Some(
1166                (((*n).max(0).min(100) as f32 / 100.0) * num_cells as f32 * cell_size as f32)
1167                    as usize,
1168            ),
1169        }
1170    }
1171}
1172
1173impl ITermProprietary {
1174    #[allow(clippy::cyclomatic_complexity, clippy::cognitive_complexity)]
1175    fn parse(osc: &[&[u8]]) -> Result<Self> {
1176        // iTerm has a number of different styles of OSC parameter
1177        // encodings, which makes this section of code a bit gnarly.
1178        ensure!(osc.len() > 1, "not enough args");
1179
1180        let param = String::from_utf8_lossy(osc[1]);
1181
1182        let mut iter = param.splitn(2, '=');
1183        let keyword = iter.next().ok_or_else(|| format!("bad params"))?;
1184        let p1 = iter.next();
1185
1186        macro_rules! single {
1187            ($variant:ident, $text:expr) => {
1188                if osc.len() == 2 && keyword == $text && p1.is_none() {
1189                    return Ok(ITermProprietary::$variant);
1190                }
1191            };
1192        }
1193
1194        macro_rules! one_str {
1195            ($variant:ident, $text:expr) => {
1196                if osc.len() == 2 && keyword == $text {
1197                    if let Some(p1) = p1 {
1198                        return Ok(ITermProprietary::$variant(p1.into()));
1199                    }
1200                }
1201            };
1202        }
1203        macro_rules! const_arg {
1204            ($variant:ident, $text:expr, $value:expr, $res:expr) => {
1205                if osc.len() == 2 && keyword == $text {
1206                    if let Some(p1) = p1 {
1207                        if p1 == $value {
1208                            return Ok(ITermProprietary::$variant($res));
1209                        }
1210                    }
1211                }
1212            };
1213        }
1214
1215        single!(SetMark, "SetMark");
1216        single!(StealFocus, "StealFocus");
1217        single!(ClearScrollback, "ClearScrollback");
1218        single!(EndCopy, "EndCopy");
1219        single!(RequestCellSize, "ReportCellSize");
1220        const_arg!(HighlightCursorLine, "HighlightCursorLine", "yes", true);
1221        const_arg!(HighlightCursorLine, "HighlightCursorLine", "no", false);
1222        one_str!(CurrentDir, "CurrentDir");
1223        one_str!(SetProfile, "SetProfile");
1224        one_str!(CopyToClipboard, "CopyToClipboard");
1225
1226        let p1_empty = match p1 {
1227            Some(p1) if p1 == "" => true,
1228            None => true,
1229            _ => false,
1230        };
1231
1232        if osc.len() == 3 && keyword == "Copy" && p1_empty {
1233            return Ok(ITermProprietary::Copy(String::from_utf8(base64_decode(
1234                osc[2],
1235            )?)?));
1236        }
1237        if osc.len() == 3 && keyword == "SetBadgeFormat" && p1_empty {
1238            return Ok(ITermProprietary::SetBadgeFormat(String::from_utf8(
1239                base64_decode(osc[2])?,
1240            )?));
1241        }
1242
1243        if osc.len() == 3 && keyword == "ReportCellSize" && p1.is_some() {
1244            if let Some(p1) = p1 {
1245                return Ok(ITermProprietary::ReportCellSize {
1246                    height_pixels: NotNan::new(p1.parse()?).map_err(not_nan_err)?,
1247                    width_pixels: NotNan::new(String::from_utf8_lossy(osc[2]).parse()?)
1248                        .map_err(not_nan_err)?,
1249                    scale: None,
1250                });
1251            }
1252        }
1253        if osc.len() == 4 && keyword == "ReportCellSize" && p1.is_some() {
1254            if let Some(p1) = p1 {
1255                return Ok(ITermProprietary::ReportCellSize {
1256                    height_pixels: NotNan::new(p1.parse()?).map_err(not_nan_err)?,
1257                    width_pixels: NotNan::new(String::from_utf8_lossy(osc[2]).parse()?)
1258                        .map_err(not_nan_err)?,
1259                    scale: Some(
1260                        NotNan::new(String::from_utf8_lossy(osc[3]).parse()?)
1261                            .map_err(not_nan_err)?,
1262                    ),
1263                });
1264            }
1265        }
1266
1267        if osc.len() == 2 && keyword == "SetUserVar" {
1268            if let Some(p1) = p1 {
1269                let mut iter = p1.splitn(2, '=');
1270                let p1 = iter.next();
1271                let p2 = iter.next();
1272
1273                if let (Some(k), Some(v)) = (p1, p2) {
1274                    return Ok(ITermProprietary::SetUserVar {
1275                        name: k.to_string(),
1276                        value: String::from_utf8(base64_decode(v)?)?,
1277                    });
1278                }
1279            }
1280        }
1281
1282        if osc.len() == 2 && keyword == "UnicodeVersion" {
1283            if let Some(p1) = p1 {
1284                let mut iter = p1.splitn(2, ' ');
1285                let keyword = iter.next();
1286                let label = iter.next();
1287
1288                if let Some("push") = keyword {
1289                    return Ok(ITermProprietary::UnicodeVersion(
1290                        ITermUnicodeVersionOp::Push(label.map(|s| s.to_string())),
1291                    ));
1292                }
1293                if let Some("pop") = keyword {
1294                    return Ok(ITermProprietary::UnicodeVersion(
1295                        ITermUnicodeVersionOp::Pop(label.map(|s| s.to_string())),
1296                    ));
1297                }
1298
1299                if let Ok(n) = p1.parse::<u8>() {
1300                    return Ok(ITermProprietary::UnicodeVersion(
1301                        ITermUnicodeVersionOp::Set(n),
1302                    ));
1303                }
1304            }
1305        }
1306
1307        if keyword == "File" {
1308            return Ok(ITermProprietary::File(Box::new(ITermFileData::parse(osc)?)));
1309        }
1310
1311        bail!("ITermProprietary {:?}", osc);
1312    }
1313}
1314
1315/// base64::encode is deprecated, so make a less frustrating helper
1316pub(crate) fn base64_encode<T: AsRef<[u8]>>(s: T) -> String {
1317    base64::engine::general_purpose::STANDARD.encode(s)
1318}
1319
1320/// base64::decode is deprecated, so make a less frustrating helper
1321pub(crate) fn base64_decode<T: AsRef<[u8]>>(s: T) -> Result<Vec<u8>> {
1322    use base64::engine::{GeneralPurpose, GeneralPurposeConfig};
1323    GeneralPurpose::new(
1324        &base64::alphabet::STANDARD,
1325        GeneralPurposeConfig::new().with_decode_allow_trailing_bits(true),
1326    )
1327    .decode(s)
1328    .map_err(|err| crate::format_err!("base64_decode: {:#}", err))
1329}
1330
1331impl Display for ITermProprietary {
1332    fn fmt(&self, f: &mut Formatter) -> FmtResult {
1333        write!(f, "1337;")?;
1334        use self::ITermProprietary::*;
1335        match self {
1336            SetMark => write!(f, "SetMark")?,
1337            StealFocus => write!(f, "StealFocus")?,
1338            ClearScrollback => write!(f, "ClearScrollback")?,
1339            CurrentDir(s) => write!(f, "CurrentDir={}", s)?,
1340            SetProfile(s) => write!(f, "SetProfile={}", s)?,
1341            CopyToClipboard(s) => write!(f, "CopyToClipboard={}", s)?,
1342            EndCopy => write!(f, "EndCopy")?,
1343            HighlightCursorLine(yes) => {
1344                write!(f, "HighlightCursorLine={}", if *yes { "yes" } else { "no" })?
1345            }
1346            RequestCellSize => write!(f, "ReportCellSize")?,
1347            ReportCellSize {
1348                height_pixels,
1349                width_pixels,
1350                scale: None,
1351            } => write!(f, "ReportCellSize={height_pixels:.1};{width_pixels:.1}")?,
1352            ReportCellSize {
1353                height_pixels,
1354                width_pixels,
1355                scale: Some(scale),
1356            } => write!(
1357                f,
1358                "ReportCellSize={height_pixels:.1};{width_pixels:.1};{scale:.1}",
1359            )?,
1360            Copy(s) => write!(f, "Copy=;{}", base64_encode(s))?,
1361            ReportVariable(s) => write!(f, "ReportVariable={}", base64_encode(s))?,
1362            SetUserVar { name, value } => {
1363                write!(f, "SetUserVar={}={}", name, base64_encode(value))?
1364            }
1365            SetBadgeFormat(s) => write!(f, "SetBadgeFormat={}", base64_encode(s))?,
1366            File(file) => file.fmt(f)?,
1367            UnicodeVersion(ITermUnicodeVersionOp::Set(n)) => write!(f, "UnicodeVersion={}", n)?,
1368            UnicodeVersion(ITermUnicodeVersionOp::Push(Some(label))) => {
1369                write!(f, "UnicodeVersion=push {}", label)?
1370            }
1371            UnicodeVersion(ITermUnicodeVersionOp::Push(None)) => write!(f, "UnicodeVersion=push")?,
1372            UnicodeVersion(ITermUnicodeVersionOp::Pop(Some(label))) => {
1373                write!(f, "UnicodeVersion=pop {}", label)?
1374            }
1375            UnicodeVersion(ITermUnicodeVersionOp::Pop(None)) => write!(f, "UnicodeVersion=pop")?,
1376        }
1377        Ok(())
1378    }
1379}
1380
1381fn not_nan_err(err: ordered_float::FloatIsNan) -> crate::Error {
1382    format_err!("{:#}", err)
1383}
1384
1385#[cfg(test)]
1386mod test {
1387    use super::*;
1388
1389    fn encode(osc: &OperatingSystemCommand) -> String {
1390        format!("{}", osc)
1391    }
1392
1393    fn parse(osc: &[&str], expected: &str) -> OperatingSystemCommand {
1394        let mut v = Vec::new();
1395        for s in osc {
1396            v.push(s.as_bytes());
1397        }
1398        let result = OperatingSystemCommand::parse(&v);
1399
1400        assert_eq!(encode(&result), expected);
1401
1402        result
1403    }
1404
1405    #[test]
1406    fn reset_colors() {
1407        assert_eq!(
1408            parse(&["104"], "\x1b]104\x1b\\"),
1409            OperatingSystemCommand::ResetColors(vec![])
1410        );
1411        assert_eq!(
1412            parse(&["104", ""], "\x1b]104\x1b\\"),
1413            OperatingSystemCommand::ResetColors(vec![])
1414        );
1415        assert_eq!(
1416            parse(&["104", "1"], "\x1b]104;1\x1b\\"),
1417            OperatingSystemCommand::ResetColors(vec![1])
1418        );
1419        assert_eq!(
1420            parse(&["112"], "\x1b]112\x1b\\"),
1421            OperatingSystemCommand::ResetDynamicColor(DynamicColorNumber::TextCursorColor)
1422        );
1423    }
1424
1425    #[test]
1426    fn title() {
1427        assert_eq!(
1428            parse(&["0", "hello"], "\x1b]0;hello\x1b\\"),
1429            OperatingSystemCommand::SetIconNameAndWindowTitle("hello".into())
1430        );
1431
1432        assert_eq!(
1433            parse(&["0", "hello \u{1f915}"], "\x1b]0;hello \u{1f915}\x1b\\"),
1434            OperatingSystemCommand::SetIconNameAndWindowTitle("hello \u{1f915}".into())
1435        );
1436
1437        assert_eq!(
1438            parse(
1439                &["0", "hello \u{1f915}", " world"],
1440                "\x1b]0;hello \u{1f915}; world\x1b\\"
1441            ),
1442            OperatingSystemCommand::SetIconNameAndWindowTitle("hello \u{1f915}; world".into())
1443        );
1444
1445        // Missing title parameter
1446        assert_eq!(
1447            parse(&["0"], "\x1b]0\x1b\\"),
1448            OperatingSystemCommand::Unspecified(vec![b"0".to_vec()])
1449        );
1450
1451        // parsing legacy sun OSC; why bother? This format is used in response
1452        // to the CSI ReportWindowTitle sequence
1453        assert_eq!(
1454            parse(&["lhello"], "\x1b]lhello\x1b\\"),
1455            OperatingSystemCommand::SetWindowTitleSun("hello".into())
1456        );
1457    }
1458
1459    #[test]
1460    fn hyperlink() {
1461        assert_eq!(
1462            parse(
1463                &["8", "id=foo", "http://example.com"],
1464                "\x1b]8;id=foo;http://example.com\x1b\\"
1465            ),
1466            OperatingSystemCommand::SetHyperlink(Some(Hyperlink::new_with_id(
1467                "http://example.com",
1468                "foo"
1469            )))
1470        );
1471
1472        assert_eq!(
1473            parse(&["8", "", ""], "\x1b]8;;\x1b\\"),
1474            OperatingSystemCommand::SetHyperlink(None)
1475        );
1476
1477        // too many params
1478        assert_eq!(
1479            parse(&["8", "1", "2"], "\x1b]8;1;2\x1b\\"),
1480            OperatingSystemCommand::Unspecified(vec![b"8".to_vec(), b"1".to_vec(), b"2".to_vec()])
1481        );
1482
1483        assert_eq!(
1484            Hyperlink::parse(&[b"8", b"", b"x"]).unwrap(),
1485            Some(Hyperlink::new("x"))
1486        );
1487    }
1488
1489    #[test]
1490    fn finalterm() {
1491        assert_eq!(
1492            parse(&["133", "L"], "\x1b]133;L\x1b\\"),
1493            OperatingSystemCommand::FinalTermSemanticPrompt(FinalTermSemanticPrompt::FreshLine)
1494        );
1495        assert_eq!(
1496            parse(&["133", "C"], "\x1b]133;C\x1b\\"),
1497            OperatingSystemCommand::FinalTermSemanticPrompt(
1498                FinalTermSemanticPrompt::MarkEndOfInputAndStartOfOutput { aid: None }
1499            )
1500        );
1501
1502        assert_eq!(
1503            parse(&["133", "C", "aid=123"], "\x1b]133;C;aid=123\x1b\\"),
1504            OperatingSystemCommand::FinalTermSemanticPrompt(
1505                FinalTermSemanticPrompt::MarkEndOfInputAndStartOfOutput {
1506                    aid: Some("123".to_string())
1507                }
1508            )
1509        );
1510
1511        assert_eq!(
1512            parse(&["133", "D", "1"], "\x1b]133;D;1\x1b\\"),
1513            OperatingSystemCommand::FinalTermSemanticPrompt(
1514                FinalTermSemanticPrompt::CommandStatus {
1515                    status: 1,
1516                    aid: None
1517                }
1518            )
1519        );
1520
1521        assert_eq!(
1522            parse(&["133", "D", "0"], "\x1b]133;D;0\x1b\\"),
1523            OperatingSystemCommand::FinalTermSemanticPrompt(
1524                FinalTermSemanticPrompt::CommandStatus {
1525                    status: 0,
1526                    aid: None
1527                }
1528            )
1529        );
1530
1531        assert_eq!(
1532            parse(
1533                &["133", "D", "0", "aid=23"],
1534                "\x1b]133;D;0;err=0;aid=23\x1b\\"
1535            ),
1536            OperatingSystemCommand::FinalTermSemanticPrompt(
1537                FinalTermSemanticPrompt::CommandStatus {
1538                    status: 0,
1539                    aid: Some("23".to_owned())
1540                }
1541            )
1542        );
1543
1544        assert_eq!(
1545            parse(
1546                &["133", "D", "1", "aid=23"],
1547                "\x1b]133;D;1;err=1;aid=23\x1b\\"
1548            ),
1549            OperatingSystemCommand::FinalTermSemanticPrompt(
1550                FinalTermSemanticPrompt::CommandStatus {
1551                    status: 1,
1552                    aid: Some("23".to_owned())
1553                }
1554            )
1555        );
1556
1557        assert_eq!(
1558            parse(&["133", "P"], "\x1b]133;P;k=i\x1b\\"),
1559            OperatingSystemCommand::FinalTermSemanticPrompt(FinalTermSemanticPrompt::StartPrompt(
1560                FinalTermPromptKind::Initial
1561            ))
1562        );
1563
1564        assert_eq!(
1565            parse(&["133", "P", "k=i"], "\x1b]133;P;k=i\x1b\\"),
1566            OperatingSystemCommand::FinalTermSemanticPrompt(FinalTermSemanticPrompt::StartPrompt(
1567                FinalTermPromptKind::Initial
1568            ))
1569        );
1570
1571        assert_eq!(
1572            parse(&["133", "P", "k=r"], "\x1b]133;P;k=r\x1b\\"),
1573            OperatingSystemCommand::FinalTermSemanticPrompt(FinalTermSemanticPrompt::StartPrompt(
1574                FinalTermPromptKind::RightSide
1575            ))
1576        );
1577
1578        assert_eq!(
1579            parse(&["133", "P", "k=c"], "\x1b]133;P;k=c\x1b\\"),
1580            OperatingSystemCommand::FinalTermSemanticPrompt(FinalTermSemanticPrompt::StartPrompt(
1581                FinalTermPromptKind::Continuation
1582            ))
1583        );
1584        assert_eq!(
1585            parse(&["133", "P", "k=s"], "\x1b]133;P;k=s\x1b\\"),
1586            OperatingSystemCommand::FinalTermSemanticPrompt(FinalTermSemanticPrompt::StartPrompt(
1587                FinalTermPromptKind::Secondary
1588            ))
1589        );
1590
1591        assert_eq!(
1592            parse(&["133", "B"], "\x1b]133;B\x1b\\"),
1593            OperatingSystemCommand::FinalTermSemanticPrompt(
1594                FinalTermSemanticPrompt::MarkEndOfPromptAndStartOfInputUntilNextMarker
1595            ),
1596        );
1597
1598        assert_eq!(
1599            parse(&["133", "I"], "\x1b]133;I\x1b\\"),
1600            OperatingSystemCommand::FinalTermSemanticPrompt(
1601                FinalTermSemanticPrompt::MarkEndOfPromptAndStartOfInputUntilEndOfLine
1602            ),
1603        );
1604
1605        assert_eq!(
1606            parse(&["133", "N"], "\x1b]133;N\x1b\\"),
1607            OperatingSystemCommand::FinalTermSemanticPrompt(
1608                FinalTermSemanticPrompt::MarkEndOfCommandWithFreshLine {
1609                    aid: None,
1610                    cl: None,
1611                }
1612            ),
1613        );
1614
1615        assert_eq!(
1616            parse(&["133", "N", "aid=12"], "\x1b]133;N;aid=12\x1b\\"),
1617            OperatingSystemCommand::FinalTermSemanticPrompt(
1618                FinalTermSemanticPrompt::MarkEndOfCommandWithFreshLine {
1619                    aid: Some("12".to_owned()),
1620                    cl: None,
1621                }
1622            ),
1623        );
1624
1625        assert_eq!(
1626            parse(
1627                &["133", "N", "aid=12", "cl=line"],
1628                "\x1b]133;N;aid=12;cl=line\x1b\\"
1629            ),
1630            OperatingSystemCommand::FinalTermSemanticPrompt(
1631                FinalTermSemanticPrompt::MarkEndOfCommandWithFreshLine {
1632                    aid: Some("12".to_owned()),
1633                    cl: Some(FinalTermClick::Line),
1634                }
1635            ),
1636        );
1637
1638        assert_eq!(
1639            parse(
1640                &["133", "N", "aid=12", "cl=m"],
1641                "\x1b]133;N;aid=12;cl=m\x1b\\"
1642            ),
1643            OperatingSystemCommand::FinalTermSemanticPrompt(
1644                FinalTermSemanticPrompt::MarkEndOfCommandWithFreshLine {
1645                    aid: Some("12".to_owned()),
1646                    cl: Some(FinalTermClick::MultipleLine),
1647                }
1648            ),
1649        );
1650
1651        assert_eq!(
1652            parse(
1653                &["133", "N", "aid=12", "cl=v"],
1654                "\x1b]133;N;aid=12;cl=v\x1b\\"
1655            ),
1656            OperatingSystemCommand::FinalTermSemanticPrompt(
1657                FinalTermSemanticPrompt::MarkEndOfCommandWithFreshLine {
1658                    aid: Some("12".to_owned()),
1659                    cl: Some(FinalTermClick::ConservativeVertical),
1660                }
1661            ),
1662        );
1663        assert_eq!(
1664            parse(
1665                &["133", "N", "aid=12", "cl=w"],
1666                "\x1b]133;N;aid=12;cl=w\x1b\\"
1667            ),
1668            OperatingSystemCommand::FinalTermSemanticPrompt(
1669                FinalTermSemanticPrompt::MarkEndOfCommandWithFreshLine {
1670                    aid: Some("12".to_owned()),
1671                    cl: Some(FinalTermClick::SmartVertical),
1672                }
1673            ),
1674        );
1675
1676        assert_eq!(
1677            parse(
1678                &["133", "A", "aid=12", "cl=w"],
1679                "\x1b]133;A;aid=12;cl=w\x1b\\"
1680            ),
1681            OperatingSystemCommand::FinalTermSemanticPrompt(
1682                FinalTermSemanticPrompt::FreshLineAndStartPrompt {
1683                    aid: Some("12".to_owned()),
1684                    cl: Some(FinalTermClick::SmartVertical),
1685                }
1686            ),
1687        );
1688    }
1689
1690    #[test]
1691    fn rxvt() {
1692        assert_eq!(
1693            parse(
1694                &["777", "notify", "alert user", "the tea is ready"],
1695                "\x1b]777;notify;alert user;the tea is ready\x1b\\"
1696            ),
1697            OperatingSystemCommand::RxvtExtension(vec![
1698                "notify".into(),
1699                "alert user".into(),
1700                "the tea is ready".into()
1701            ]),
1702        )
1703    }
1704
1705    #[test]
1706    fn conemu() {
1707        assert_eq!(
1708            parse(&["9", "4", "1", "42"], "\x1b]9;4;1;42\x1b\\"),
1709            OperatingSystemCommand::ConEmuProgress(Progress::SetPercentage(42))
1710        );
1711        assert_eq!(
1712            parse(&["9", "4", "2", "64"], "\x1b]9;4;2;64\x1b\\"),
1713            OperatingSystemCommand::ConEmuProgress(Progress::SetError(64))
1714        );
1715        assert_eq!(
1716            parse(&["9", "4", "3"], "\x1b]9;4;3\x1b\\"),
1717            OperatingSystemCommand::ConEmuProgress(Progress::SetIndeterminate)
1718        );
1719        assert_eq!(
1720            parse(&["9", "4", "4"], "\x1b]9;4;4\x1b\\"),
1721            OperatingSystemCommand::ConEmuProgress(Progress::Paused)
1722        );
1723    }
1724
1725    #[test]
1726    fn iterm() {
1727        assert_eq!(
1728            parse(&["1337", "SetMark"], "\x1b]1337;SetMark\x1b\\"),
1729            OperatingSystemCommand::ITermProprietary(ITermProprietary::SetMark)
1730        );
1731
1732        assert_eq!(
1733            parse(
1734                &["1337", "CurrentDir=woot"],
1735                "\x1b]1337;CurrentDir=woot\x1b\\"
1736            ),
1737            OperatingSystemCommand::ITermProprietary(ITermProprietary::CurrentDir("woot".into()))
1738        );
1739
1740        assert_eq!(
1741            parse(
1742                &["1337", "HighlightCursorLine=yes"],
1743                "\x1b]1337;HighlightCursorLine=yes\x1b\\"
1744            ),
1745            OperatingSystemCommand::ITermProprietary(ITermProprietary::HighlightCursorLine(true))
1746        );
1747
1748        assert_eq!(
1749            parse(
1750                &["1337", "Copy=", "aGVsbG8="],
1751                "\x1b]1337;Copy=;aGVsbG8=\x1b\\"
1752            ),
1753            OperatingSystemCommand::ITermProprietary(ITermProprietary::Copy("hello".into()))
1754        );
1755
1756        assert_eq!(
1757            parse(
1758                &["1337", "SetUserVar=foo=aGVsbG8="],
1759                "\x1b]1337;SetUserVar=foo=aGVsbG8=\x1b\\"
1760            ),
1761            OperatingSystemCommand::ITermProprietary(ITermProprietary::SetUserVar {
1762                name: "foo".into(),
1763                value: "hello".into()
1764            })
1765        );
1766
1767        assert_eq!(
1768            parse(
1769                &["1337", "SetBadgeFormat=", "aGVsbG8="],
1770                "\x1b]1337;SetBadgeFormat=aGVsbG8=\x1b\\"
1771            ),
1772            OperatingSystemCommand::ITermProprietary(ITermProprietary::SetBadgeFormat(
1773                "hello".into()
1774            ))
1775        );
1776
1777        assert_eq!(
1778            parse(
1779                &["1337", "ReportCellSize=12.0", "15.5"],
1780                "\x1b]1337;ReportCellSize=12.0;15.5\x1b\\"
1781            ),
1782            OperatingSystemCommand::ITermProprietary(ITermProprietary::ReportCellSize {
1783                height_pixels: NotNan::new(12.0).unwrap(),
1784                width_pixels: NotNan::new(15.5).unwrap(),
1785                scale: None,
1786            })
1787        );
1788
1789        assert_eq!(
1790            parse(
1791                &["1337", "ReportCellSize=12.0", "15.5", "2.0"],
1792                "\x1b]1337;ReportCellSize=12.0;15.5;2.0\x1b\\"
1793            ),
1794            OperatingSystemCommand::ITermProprietary(ITermProprietary::ReportCellSize {
1795                height_pixels: NotNan::new(12.0).unwrap(),
1796                width_pixels: NotNan::new(15.5).unwrap(),
1797                scale: Some(NotNan::new(2.0).unwrap()),
1798            })
1799        );
1800
1801        assert_eq!(
1802            parse(
1803                &["1337", "File=:aGVsbG8="],
1804                "\x1b]1337;File=:aGVsbG8=\x1b\\"
1805            ),
1806            OperatingSystemCommand::ITermProprietary(ITermProprietary::File(Box::new(
1807                ITermFileData {
1808                    name: None,
1809                    size: None,
1810                    width: ITermDimension::Automatic,
1811                    height: ITermDimension::Automatic,
1812                    preserve_aspect_ratio: true,
1813                    inline: false,
1814                    do_not_move_cursor: false,
1815                    data: b"hello".to_vec(),
1816                }
1817            )))
1818        );
1819
1820        assert_eq!(
1821            parse(
1822                &["1337", "File=name=bXluYW1l:aGVsbG8="],
1823                "\x1b]1337;File=name=bXluYW1l:aGVsbG8=\x1b\\"
1824            ),
1825            OperatingSystemCommand::ITermProprietary(ITermProprietary::File(Box::new(
1826                ITermFileData {
1827                    name: Some("myname".into()),
1828                    size: None,
1829                    width: ITermDimension::Automatic,
1830                    height: ITermDimension::Automatic,
1831                    preserve_aspect_ratio: true,
1832                    inline: false,
1833                    do_not_move_cursor: false,
1834                    data: b"hello".to_vec(),
1835                }
1836            )))
1837        );
1838
1839        assert_eq!(
1840            parse(
1841                &["1337", "File=size=123", "name=bXluYW1l:aGVsbG8="],
1842                "\x1b]1337;File=size=123;name=bXluYW1l:aGVsbG8=\x1b\\"
1843            ),
1844            OperatingSystemCommand::ITermProprietary(ITermProprietary::File(Box::new(
1845                ITermFileData {
1846                    name: Some("myname".into()),
1847                    size: Some(123),
1848                    width: ITermDimension::Automatic,
1849                    height: ITermDimension::Automatic,
1850                    preserve_aspect_ratio: true,
1851                    inline: false,
1852                    do_not_move_cursor: false,
1853                    data: b"hello".to_vec(),
1854                }
1855            )))
1856        );
1857
1858        assert_eq!(
1859            parse(
1860                &["1337", "File=name=bXluYW1l", "size=234:aGVsbG8="],
1861                "\x1b]1337;File=size=234;name=bXluYW1l:aGVsbG8=\x1b\\"
1862            ),
1863            OperatingSystemCommand::ITermProprietary(ITermProprietary::File(Box::new(
1864                ITermFileData {
1865                    name: Some("myname".into()),
1866                    size: Some(234),
1867                    width: ITermDimension::Automatic,
1868                    height: ITermDimension::Automatic,
1869                    preserve_aspect_ratio: true,
1870                    inline: false,
1871                    do_not_move_cursor: false,
1872                    data: b"hello".to_vec(),
1873                }
1874            )))
1875        );
1876
1877        assert_eq!(
1878            parse(
1879                &[
1880                    "1337",
1881                    "File=name=bXluYW1l",
1882                    "width=auto",
1883                    "size=234:aGVsbG8="
1884                ],
1885                "\x1b]1337;File=size=234;name=bXluYW1l:aGVsbG8=\x1b\\"
1886            ),
1887            OperatingSystemCommand::ITermProprietary(ITermProprietary::File(Box::new(
1888                ITermFileData {
1889                    name: Some("myname".into()),
1890                    size: Some(234),
1891                    width: ITermDimension::Automatic,
1892                    height: ITermDimension::Automatic,
1893                    preserve_aspect_ratio: true,
1894                    inline: false,
1895                    do_not_move_cursor: false,
1896                    data: b"hello".to_vec(),
1897                }
1898            )))
1899        );
1900
1901        assert_eq!(
1902            parse(
1903                &["1337", "File=name=bXluYW1l", "width=5", "size=234:aGVsbG8="],
1904                "\x1b]1337;File=size=234;name=bXluYW1l;width=5:aGVsbG8=\x1b\\"
1905            ),
1906            OperatingSystemCommand::ITermProprietary(ITermProprietary::File(Box::new(
1907                ITermFileData {
1908                    name: Some("myname".into()),
1909                    size: Some(234),
1910                    width: ITermDimension::Cells(5),
1911                    height: ITermDimension::Automatic,
1912                    preserve_aspect_ratio: true,
1913                    inline: false,
1914                    do_not_move_cursor: false,
1915                    data: b"hello".to_vec(),
1916                }
1917            )))
1918        );
1919
1920        assert_eq!(
1921            parse(
1922                &[
1923                    "1337",
1924                    "File=name=bXluYW1l",
1925                    "width=5",
1926                    "height=10%",
1927                    "size=234:aGVsbG8="
1928                ],
1929                "\x1b]1337;File=size=234;name=bXluYW1l;width=5;height=10%:aGVsbG8=\x1b\\"
1930            ),
1931            OperatingSystemCommand::ITermProprietary(ITermProprietary::File(Box::new(
1932                ITermFileData {
1933                    name: Some("myname".into()),
1934                    size: Some(234),
1935                    width: ITermDimension::Cells(5),
1936                    height: ITermDimension::Percent(10),
1937                    preserve_aspect_ratio: true,
1938                    inline: false,
1939                    do_not_move_cursor: false,
1940                    data: b"hello".to_vec(),
1941                }
1942            )))
1943        );
1944
1945        assert_eq!(
1946            parse(
1947                &[
1948                    "1337",
1949                    "File=name=bXluYW1l",
1950                    "preserveAspectRatio=0",
1951                    "width=5",
1952                    "inline=1",
1953                    "height=10px",
1954                    "size=234:aGVsbG8="
1955                ],
1956                "\x1b]1337;File=size=234;name=bXluYW1l;width=5;height=10px;preserveAspectRatio=0;inline=1:aGVsbG8=\x1b\\"
1957            ),
1958            OperatingSystemCommand::ITermProprietary(ITermProprietary::File(Box::new(
1959                ITermFileData {
1960                    name: Some("myname".into()),
1961                    size: Some(234),
1962                    width: ITermDimension::Cells(5),
1963                    height: ITermDimension::Pixels(10),
1964                    preserve_aspect_ratio: false,
1965                    inline: true,
1966                    do_not_move_cursor: false,
1967                    data: b"hello".to_vec(),
1968                }
1969            )))
1970        );
1971
1972        assert_eq!(
1973            parse(
1974                &[
1975                    "1337",
1976                    "File=name=bXluYW1l",
1977                    "preserveAspectRatio=0",
1978                    "width=5",
1979                    "inline=1",
1980                    "doNotMoveCursor=1",
1981                    "height=10px",
1982                    "size=234:aGVsbG8="
1983                ],
1984                "\x1b]1337;File=size=234;name=bXluYW1l;width=5;height=10px;preserveAspectRatio=0;inline=1;doNotMoveCursor=1:aGVsbG8=\x1b\\"
1985            ),
1986            OperatingSystemCommand::ITermProprietary(ITermProprietary::File(Box::new(
1987                ITermFileData {
1988                    name: Some("myname".into()),
1989                    size: Some(234),
1990                    width: ITermDimension::Cells(5),
1991                    height: ITermDimension::Pixels(10),
1992                    preserve_aspect_ratio: false,
1993                    inline: true,
1994                    do_not_move_cursor: true,
1995                    data: b"hello".to_vec(),
1996                }
1997            )))
1998        );
1999    }
2000}