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(); 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(); 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 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 ChangeTitleTabColor = "6",
466 SetCurrentWorkingDirectory = "7",
467 SetHyperlink = "8",
469 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 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 write!(f, "{}{}", code, $s)?;
552 }
553 _ => {
554 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 write!(f, "\x1b\\")?;
616 Ok(())
617 }
618}
619
620#[derive(Debug, Clone, PartialEq, Eq)]
622pub enum FinalTermClick {
623 Line,
625 MultipleLine,
627 ConservativeVertical,
629 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#[derive(Debug, Clone, PartialEq, Eq)]
661pub enum FinalTermPromptKind {
662 Initial,
664 RightSide,
666 Continuation,
668 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#[derive(Debug, Clone, PartialEq, Eq)]
704pub enum FinalTermSemanticPrompt {
705 FreshLine,
708
709 FreshLineAndStartPrompt {
713 aid: Option<String>,
714 cl: Option<FinalTermClick>,
715 },
716
717 MarkEndOfCommandWithFreshLine {
719 aid: Option<String>,
720 cl: Option<FinalTermClick>,
721 },
722
723 StartPrompt(FinalTermPromptKind),
725
726 MarkEndOfPromptAndStartOfInputUntilNextMarker,
730
731 MarkEndOfPromptAndStartOfInputUntilEndOfLine,
735
736 MarkEndOfInputAndStartOfOutput {
737 aid: Option<String>,
738 },
739
740 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 SetMark,
894 StealFocus,
896 ClearScrollback,
898 CurrentDir(String),
900 SetProfile(String),
902 CopyToClipboard(String),
905 EndCopy,
907 HighlightCursorLine(bool),
909 RequestCellSize,
911 ReportCellSize {
917 height_pixels: NotNan<f32>,
918 width_pixels: NotNan<f32>,
919 scale: Option<NotNan<f32>>,
920 },
921 Copy(String),
923 ReportVariable(String),
927 SetUserVar {
929 name: String,
930 value: String,
931 },
932 SetBadgeFormat(String),
933 File(Box<ITermFileData>),
935
936 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 pub name: Option<String>,
951 pub size: Option<usize>,
954 pub width: ITermDimension,
956 pub height: ITermDimension,
958 pub preserve_aspect_ratio: bool,
960 pub inline: bool,
963 pub do_not_move_cursor: bool,
965 pub data: Vec<u8>,
967}
968
969impl ITermFileData {
970 fn parse(osc: &[&[u8]]) -> Result<Self> {
971 let mut params = HashMap::new();
972
973 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 &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 if let Some(colon) = param.iter().position(|c| *c == b':') {
998 data = Some(base64_decode(¶m[colon + 1..])?);
999 ¶m[..colon]
1000 } else {
1001 bail!("failed to parse file data; no colon found");
1004 }
1005 } else {
1006 param
1007 };
1008
1009 if param.is_empty() {
1011 continue;
1012 }
1013
1014 if let Some(equal) = param.iter().position(|c| *c == b'=') {
1016 let key = ¶m[..equal];
1017 let value = ¶m[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 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 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 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
1315pub(crate) fn base64_encode<T: AsRef<[u8]>>(s: T) -> String {
1317 base64::engine::general_purpose::STANDARD.encode(s)
1318}
1319
1320pub(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 assert_eq!(
1447 parse(&["0"], "\x1b]0\x1b\\"),
1448 OperatingSystemCommand::Unspecified(vec![b"0".to_vec()])
1449 );
1450
1451 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 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}