1use crate::util::ByteParser;
101use crate::{Command, Control, Query, Sgr};
102use core::iter::successors;
103use std::io::{Error, ErrorKind, Result};
104
105macro_rules! declare_unit_struct {
106 ($name:ident) => {
107 #[doc = concat!("The unit `",stringify!($name),"` command.")]
108 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
109 pub struct $name;
110 };
111}
112
113macro_rules! declare_n_struct {
114 ($name:ident( $( $arg:ident : $typ:ty ),+ $(,)? )) => {
115 #[doc = concat!("The dynamic `",stringify!($name),"(",stringify!($($arg),+),")` command.")]
116 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
117 pub struct $name( $( pub $typ ),+ );
118 };
119 ($name:ident< $( $arg:ident : $typ:ty ),+ >) => {
120 #[doc = concat!("The static `",stringify!($name),"<",stringify!($($arg),+),">` command.")]
121 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
122 pub struct $name< $(const $arg: $typ),+ >;
123 }
124}
125
126macro_rules! implement_command {
127 ($name:ident $(< $( $arg:ident : $typ:ty ),+ >)? : $selfish:ident ; $output:ident $body:block) => {
128 impl $(< $(const $arg: $typ),+ >)? $crate::Command for $name $(< $($arg),+ >)? {}
129
130 impl $(< $(const $arg: $typ),+ >)? ::core::fmt::Display for $name $(< $($arg),+ >)? {
131 #[inline]
132 fn fmt(&$selfish, $output: &mut ::core::fmt::Formatter<'_>) -> core::fmt::Result {
133 $body
134 }
135 }
136 }
137}
138
139macro_rules! define_unit_command {
140 ($name:ident, $ansi:tt) => {
141 declare_unit_struct!($name);
142 implement_command!($name: self; f { f.write_str($ansi) });
143 };
144}
145
146macro_rules! define_cmd_1 {
147 ($name:ident <$arg:ident : $typ:ty>, $dyn_name:ident, $prefix:literal, $suffix:literal) => {
148 declare_n_struct!($name<$arg : $typ>);
149 implement_command!($name<$arg : $typ>: self; f {
150 f.write_str($prefix)?;
151 <_ as ::core::fmt::Display>::fmt(&$arg, f)?;
152 f.write_str($suffix)
153 });
154
155 declare_n_struct!($dyn_name($arg : $typ));
156 implement_command!($dyn_name: self; f {
157 f.write_str($prefix)?;
158 <_ as ::core::fmt::Display>::fmt(&self.0, f)?;
159 f.write_str($suffix)
160 });
161 }
162}
163
164macro_rules! define_cmd_2 {
165 (
166 $name:ident <$arg1:ident : $typ1:ty, $arg2:ident : $typ2:ty>,
167 $dyn_name:ident, $prefix:literal, $suffix:literal
168 ) => {
169 declare_n_struct!($name<$arg1 : $typ1, $arg2 : $typ2>);
170 implement_command!($name<$arg1 : $typ1, $arg2 : $typ2>: self; f {
171 f.write_str($prefix)?;
172 <_ as ::core::fmt::Display>::fmt(&$arg1, f)?;
173 f.write_str(";")?;
174 <_ as ::core::fmt::Display>::fmt(&$arg2, f)?;
175 f.write_str($suffix)
176 });
177
178 declare_n_struct!($dyn_name($arg1 : $typ1, $arg2 : $typ2));
179 implement_command!($dyn_name: self; f {
180 f.write_str($prefix)?;
181 <_ as ::core::fmt::Display>::fmt(&self.0, f)?;
182 f.write_str(";")?;
183 <_ as ::core::fmt::Display>::fmt(&self.1, f)?;
184 f.write_str($suffix)
185 });
186 }
187}
188
189macro_rules! implement_sgr {
190 ($name:ident $(< $( $arg:ident : $typ:ty ),+ >)? : $selfish:ident ; $output:ident $body:block) => {
191 impl $(< $(const $arg: $typ),+ >)? $crate::Command for $name $(< $($arg),+ >)? {}
192
193 impl $(< $(const $arg: $typ),+ >)? $crate::Sgr for $name $(< $($arg),+ >)? {
194 #[inline]
195 fn write_param(&$selfish, $output: &mut ::core::fmt::Formatter<'_>) -> core::fmt::Result {
196 $body
197 }
198 }
199
200 impl $(< $(const $arg: $typ),+ >)? ::core::fmt::Display for $name $(< $($arg),+ >)? {
201 #[inline]
202 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
203 f.write_str("\x1b[")?;
204 self.write_param(f)?;
205 f.write_str("m")
206 }
207 }
208 };
209}
210
211macro_rules! define_unit_sgr {
212 ($name:ident, $ansi:tt) => {
213 declare_unit_struct!($name);
214 implement_sgr!($name: self; f { f.write_str($ansi) });
215 };
216}
217
218macro_rules! define_8bit_color {
219 ($name:ident, $dyn_name:ident, $dark_base:expr, $bright_base:expr, $prefix:literal) => {
220 declare_n_struct!($name<COLOR: u8>);
221 implement_sgr!($name<COLOR: u8>: self; f {
222 match COLOR {
223 0..=7 => <_ as ::core::fmt::Display>::fmt(&($dark_base + COLOR), f),
224 8..=15 => <_ as ::core::fmt::Display>::fmt(&($bright_base + COLOR), f),
225 _ => {
226 f.write_str($prefix)?;
227 <_ as ::core::fmt::Display>::fmt(&COLOR, f)
228 }
229 }
230 });
231
232 declare_n_struct!($dyn_name(COLOR: u8));
233 implement_sgr!($dyn_name: self; f {
234 match self.0 {
235 0..=7 => <_ as ::core::fmt::Display>::fmt(&($dark_base + self.0), f),
236 8..=15 => <_ as ::core::fmt::Display>::fmt(&($bright_base + self.0), f),
237 _ => {
238 f.write_str($prefix)?;
239 <_ as ::core::fmt::Display>::fmt(&self.0, f)
240 }
241 }
242 });
243 }
244}
245
246macro_rules! define_24bit_color {
247 ($name:ident, $dyn_name:ident, $prefix:literal) => {
248 declare_n_struct!($name<R: u8, G: u8, B: u8>);
249 implement_sgr!($name<R: u8, G: u8, B: u8>: self; f {
250 f.write_str($prefix)?;
251 <_ as ::core::fmt::Display>::fmt(&R, f)?;
252 f.write_str(";")?;
253 <_ as ::core::fmt::Display>::fmt(&G, f)?;
254 f.write_str(";")?;
255 <_ as ::core::fmt::Display>::fmt(&B, f)
256 });
257
258 declare_n_struct!($dyn_name(R: u8, G: u8, B: u8));
259 implement_sgr!($dyn_name: self; f {
260 f.write_str($prefix)?;
261 <_ as ::core::fmt::Display>::fmt(&self.0, f)?;
262 f.write_str(";")?;
263 <_ as ::core::fmt::Display>::fmt(&self.1, f)?;
264 f.write_str(";")?;
265 <_ as ::core::fmt::Display>::fmt(&self.2, f)
266 });
267 }
268}
269
270define_unit_command!(RequestTerminalId, "\x1b[>q");
275
276impl Query for RequestTerminalId {
277 type Response = (Option<Vec<u8>>, Option<Vec<u8>>);
278
279 #[inline]
280 fn control(&self) -> Control {
281 Control::DCS
282 }
283
284 fn parse(&self, payload: &[u8]) -> Result<Self::Response> {
285 fn prepare(value: &[u8]) -> Option<Vec<u8>> {
286 if value.is_empty() {
287 None
288 } else {
289 Some(value.to_owned())
290 }
291 }
292
293 let s = payload
294 .strip_prefix(b">|")
295 .and_then(|s| {
296 s.strip_suffix(b"\0x7")
297 .or_else(|| s.strip_suffix(b"\x1b\\"))
298 })
299 .ok_or_else(|| Error::from(ErrorKind::InvalidData))?
300 .trim_ascii();
301
302 if let Some(s) = s.strip_suffix(b")") {
303 let (n, v) = s
304 .iter()
305 .position(|byte| *byte == b'(')
306 .map(|index| s.split_at(index))
307 .ok_or_else(|| Error::from(ErrorKind::InvalidData))?;
308 let n = n.trim_ascii();
309 let v = v[1..].trim_ascii();
310
311 Ok((prepare(n), prepare(v)))
312 } else {
313 Ok((prepare(s), None))
314 }
315 }
316}
317
318define_unit_command!(SaveWindowTitle, "\x1b[22;2t");
321define_unit_command!(RestoreWindowTitle, "\x1b[23;2t");
322
323#[derive(Clone, Debug, PartialEq, Eq)]
327pub struct DynSetWindowTitle(String);
328implement_command!(DynSetWindowTitle: self; f {
329 f.write_str("\x1b]2;")?;
330 f.write_str(self.0.as_str())?;
331 f.write_str("\x1b\\")
332});
333
334define_unit_command!(EnterAlternateScreen, "\x1b[?1049h");
337define_unit_command!(ExitAlternateScreen, "\x1b[?1049l");
338
339define_unit_command!(EnableReverseMode, "\x1b[?5h");
340define_unit_command!(DisableReverseMode, "\x1b[?5l");
341
342define_unit_command!(EraseScreen, "\x1b[2J");
343
344declare_unit_struct!(RequestScreenSize);
345impl Command for RequestScreenSize {}
346
347impl core::fmt::Display for RequestScreenSize {
348 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
349 MoveTo::<{ u16::MAX }, { u16::MAX }>.fmt(f)?;
350 RequestCursorPosition.fmt(f)
351 }
352}
353
354impl Query for RequestScreenSize {
355 type Response = <RequestCursorPosition as Query>::Response;
356
357 #[inline]
358 fn control(&self) -> Control {
359 RequestCursorPosition.control()
360 }
361
362 fn parse(&self, payload: &[u8]) -> Result<Self::Response> {
363 RequestCursorPosition.parse(payload)
364 }
365}
366
367define_cmd_1!(ScrollUp<ROWS: u16>, DynScrollUp, "\x1b[", "S");
370define_cmd_1!(ScrollDown<ROWS: u16>, DynScrollDown, "\x1b[", "S");
371
372define_cmd_2!(SetScrollRegion<TOP: u16, BOTTOM: u16>, DynSetScrollRegion, "\x1b[", "r");
373define_unit_command!(ResetScrollRegion, "\x1b[r");
374define_unit_command!(EnableAutowrap, "\x1b[?7h");
375define_unit_command!(DisableAutowrap, "\x1b[?7l");
376
377#[derive(Clone, Copy, Debug, PartialEq, Eq)]
380pub enum SetCursor {
381 Default = 0,
382 BlinkingBlock = 1,
383 SteadyBlock = 2,
384 BlinkingUnderscore = 3,
385 SteadyUnderscore = 4,
386 BlinkingBar = 5,
387 SteadyBar = 6,
388}
389
390impl Command for SetCursor {}
391
392impl core::fmt::Display for SetCursor {
393 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
394 f.write_str("\x1b[")?;
395 <_ as core::fmt::Display>::fmt(&(*self as u8), f)?;
396 f.write_str(" q")
397 }
398}
399
400define_unit_command!(HideCursor, "\x1b[?25l");
401define_unit_command!(ShowCursor, "\x1b[?25h");
402
403define_cmd_1!(MoveUp<ROWS: u16>, DynMoveUp, "\x1b[", "A");
404define_cmd_1!(MoveDown<ROWS: u16>, DynMoveDown, "\x1b[", "B");
405define_cmd_1!(MoveLeft<COLUMNS: u16>, DynMoveLeft, "\x1b[", "C");
406define_cmd_1!(MoveRight<COLUMNS: u16>, DynMoveRight, "\x1b[", "D");
407
408define_cmd_2!(MoveTo<ROW: u16, COLUMN: u16>, DynMoveTo, "\x1b[", "H");
409
410define_cmd_1!(MoveToColumn<COLUMN: u16>, DynMoveToColumn, "\x1b[", "G");
411define_cmd_1!(MoveToRow<ROW: u16>, DynMoveToRow, "\x1b[", "d");
412
413define_unit_command!(SaveCursorPosition, "\x1b7");
414define_unit_command!(RestoreCursorPosition, "\x1b8");
415
416define_unit_command!(RequestCursorPosition, "\x1b[6n");
417
418impl Query for RequestCursorPosition {
419 type Response = (u16, u16);
421
422 #[inline]
423 fn control(&self) -> Control {
424 Control::CSI
425 }
426
427 fn parse(&self, payload: &[u8]) -> Result<Self::Response> {
428 let s = payload
429 .strip_suffix(b"R")
430 .ok_or_else(|| Error::from(ErrorKind::InvalidData))?;
431
432 let mut index = 0;
433 let mut params = [0_u16; 2];
434 for bytes in s.split(|b| *b == b';' || *b == b':') {
435 if 2 <= index {
436 return Err(ErrorKind::InvalidData.into());
437 }
438 params[index] = ByteParser::Decimal
439 .to_u16(bytes)
440 .ok_or_else(|| Error::from(ErrorKind::InvalidData))?;
441 index += 1;
442 }
443
444 if index < 2 {
445 return Err(ErrorKind::InvalidData.into());
446 }
447
448 Ok(params.into())
449 }
450}
451
452define_unit_command!(EraseLine, "\x1b[2K");
455define_unit_command!(EraseRestOfLine, "\x1b[K");
456
457#[derive(Clone, Copy, Debug, PartialEq, Eq)]
459pub enum BatchMode {
460 NotSupported = 0,
461 Enabled = 1,
462 Disabled = 2,
463 Undefined = 3,
464 PermanentlyDisabled = 4,
465}
466
467define_unit_command!(RequestBatchMode, "\x1b[?2026$p");
468
469impl Query for RequestBatchMode {
470 type Response = BatchMode;
471
472 #[inline]
473 fn control(&self) -> Control {
474 Control::CSI
475 }
476
477 fn parse(&self, payload: &[u8]) -> Result<Self::Response> {
478 let bytes = payload
479 .strip_prefix(b"?2026;")
480 .and_then(|s| s.strip_suffix(b"$y"))
481 .ok_or_else(|| Error::from(ErrorKind::InvalidData))?;
482 let response = ByteParser::Decimal
483 .to_u32(bytes)
484 .ok_or_else(|| Error::from(ErrorKind::InvalidData))?;
485
486 Ok(match response {
487 0 => BatchMode::NotSupported,
488 1 => BatchMode::Enabled,
489 2 => BatchMode::Disabled,
490 4 => BatchMode::PermanentlyDisabled,
491 _ => BatchMode::Undefined,
492 })
493 }
494}
495
496define_unit_command!(BeginBatch, "\x1b[?2026h");
497define_unit_command!(EndBatch, "\x1b[?2026l");
498
499define_unit_command!(BeginPaste, "\x1b[?2004h");
500define_unit_command!(EndPaste, "\x1b[?2004l");
501
502#[derive(Clone, Debug, PartialEq, Eq)]
506pub struct DynLink(Option<String>, String, String);
507
508impl DynLink {
509 pub fn new<H, T>(href: H, text: T) -> Self
511 where
512 H: Into<String>,
513 T: Into<String>,
514 {
515 Self(None, href.into(), text.into())
516 }
517
518 pub fn with_id<I, H, T>(id: Option<I>, href: H, text: T) -> Self
520 where
521 I: Into<String>,
522 H: Into<String>,
523 T: Into<String>,
524 {
525 Self(id.map(core::convert::Into::into), href.into(), text.into())
526 }
527}
528
529implement_command!(DynLink: self; f {
530 if let Some(ref id) = self.0 {
531 f.write_str("\x1b]8;id=")?;
532 f.write_str(id)?;
533 f.write_str(";")?;
534 } else {
535 f.write_str("\x1b]8;;")?;
536 }
537
538 f.write_str(self.1.as_str())?;
539 f.write_str("\x1b\\")?;
540 f.write_str(self.2.as_str())?;
541 f.write_str("\x1b]8;;\x1b\\")
542});
543
544define_unit_command!(ResetStyle, "\x1b[m");
547
548define_unit_sgr!(SetDefaultForeground, "39");
549define_unit_sgr!(SetDefaultBackground, "49");
550define_8bit_color!(
551 SetForeground8,
552 DynSetForeground8,
553 30,
554 (const { 90 - 8 }),
555 "38;5;"
556);
557define_8bit_color!(
558 SetBackground8,
559 DynSetBackground8,
560 40,
561 (const { 100 - 8 }),
562 "48;5;"
563);
564define_24bit_color!(SetForeground24, DynSetForeground24, "38;2;");
565define_24bit_color!(SetBackground24, DynSetBackground24, "48;2;");
566
567#[derive(Clone, Copy, Debug, PartialEq, Eq)]
569#[repr(u8)]
570pub enum Format {
571 Bold = 1,
572 Thin = 2,
573 Regular = 22,
574 Italic = 3,
575 Upright = 23,
576 Underlined = 4,
577 NotUnderlined = 24,
578 Blinking = 5,
579 NotBlinking = 25,
580 Reversed = 7,
581 NotReversed = 27,
582 Hidden = 8,
583 NotHidden = 28,
584 Stricken = 9,
585 NotStricken = 29,
586}
587
588impl Format {
589 #[must_use = "the only reason to invoke method is to access the returned value"]
591 pub fn undo(&self) -> Self {
592 use self::Format::*;
593
594 match *self {
595 Bold | Thin => Regular,
596 Italic => Upright,
597 Underlined => NotUnderlined,
598 Blinking => NotBlinking,
599 Reversed => NotReversed,
600 Hidden => NotHidden,
601 Stricken => NotStricken,
602 _ => *self,
603 }
604 }
605}
606
607impl Sgr for Format {
608 #[inline]
609 fn write_param(&self, f: &mut core::fmt::Formatter<'_>) -> ::core::fmt::Result {
610 <_ as core::fmt::Display>::fmt(&(*self as u8), f)
611 }
612}
613
614impl Command for Format {}
615
616impl core::fmt::Display for Format {
617 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
618 f.write_str("\x1b[")?;
619 self.write_param(f)?;
620 f.write_str("m")
621 }
622}
623
624define_unit_command!(RequestActiveStyle, "\x1bP$qm\x1b\\");
625
626impl Query for RequestActiveStyle {
627 type Response = Vec<u8>;
628
629 #[inline]
630 fn control(&self) -> Control {
631 Control::DCS
632 }
633
634 fn parse(&self, payload: &[u8]) -> Result<Self::Response> {
635 let s = payload
636 .strip_prefix(b"1$r")
637 .and_then(|s| s.strip_suffix(b"m"))
638 .ok_or_else(|| Error::from(ErrorKind::InvalidData))?;
639
640 Ok(s.to_owned())
641 }
642}
643
644#[derive(Clone, Copy, Debug, PartialEq, Eq)]
651#[repr(u8)]
652pub enum RequestColor {
653 Black = 0,
654 Red = 1,
655 Green = 2,
656 Yellow = 3,
657 Blue = 4,
658 Magenta = 5,
659 Cyan = 6,
660 White = 7,
661 BrightBlack = 8,
662 BrightRed = 9,
663 BrightGreen = 10,
664 BrightYellow = 11,
665 BrightBlue = 12,
666 BrightMagenta = 13,
667 BrightCyan = 14,
668 BrightWhite = 15,
669 Foreground = 110,
670 Background = 111,
671 Cursor = 112,
672 Selection = 117,
673}
674
675impl RequestColor {
676 pub const COUNT: usize = 20;
678
679 fn successor(&self) -> Option<Self> {
681 use self::RequestColor::*;
682
683 Some(match *self {
684 Black => Red,
685 Red => Green,
686 Green => Yellow,
687 Yellow => Blue,
688 Blue => Magenta,
689 Magenta => Cyan,
690 Cyan => White,
691 White => BrightBlack,
692 BrightBlack => BrightRed,
693 BrightRed => BrightGreen,
694 BrightGreen => BrightYellow,
695 BrightYellow => BrightBlue,
696 BrightBlue => BrightMagenta,
697 BrightMagenta => BrightCyan,
698 BrightCyan => BrightWhite,
699 BrightWhite => Foreground,
700 Foreground => Background,
701 Background => Cursor,
702 Cursor => Selection,
703 Selection => return None,
704 })
705 }
706
707 pub fn all() -> impl Iterator<Item = Self> {
709 successors(Some(Self::Black), Self::successor)
710 }
711}
712
713impl Command for RequestColor {}
714
715impl core::fmt::Display for RequestColor {
716 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
717 let code = *self as u32;
718 if code < 16 {
719 f.write_str("\x1b]4;")?;
720 <_ as core::fmt::Display>::fmt(&code, f)?;
721 f.write_str(";?\x1b\\")
722 } else {
723 f.write_str("\x1b]")?;
724 <_ as core::fmt::Display>::fmt(&(code - 100), f)?;
725 f.write_str(";?\x1b\\")
726 }
727 }
728}
729
730impl Query for RequestColor {
731 type Response = [(u16, u16); 3];
739
740 #[inline]
741 fn control(&self) -> Control {
742 Control::OSC
743 }
744
745 fn parse(&self, payload: &[u8]) -> Result<Self::Response> {
746 use crate::err::ErrorKind;
747
748 let code = *self as u8;
749 let bytes = if code < 20 {
750 let bytes = payload
751 .strip_prefix(b"4;")
752 .ok_or_else(|| Error::from(ErrorKind::BadSequence))?;
753 if code < 10 {
754 bytes.strip_prefix(&[b'0' + code])
755 } else {
756 bytes.strip_prefix(&[b'1', b'0' + code - 10])
757 }
758 } else {
759 payload.strip_prefix(match *self {
760 Self::Foreground => b"10",
761 Self::Background => b"11",
762 Self::Cursor => b"12",
763 Self::Selection => b"17",
764 _ => panic!("unknown theme color"),
765 })
766 }
767 .and_then(|bytes| bytes.strip_prefix(b";rgb:"))
768 .ok_or_else(|| Error::from(ErrorKind::BadSequence))?;
769
770 fn parse(bytes: Option<&[u8]>) -> core::result::Result<(u16, u16), Error> {
771 let bytes = bytes.ok_or_else(|| Error::from(ErrorKind::TooFewCoordinates))?;
772 if bytes.is_empty() {
773 return Err(ErrorKind::EmptyCoordinate.into());
774 } else if 4 < bytes.len() {
775 return Err(ErrorKind::OversizedCoordinate.into());
776 }
777
778 let n = ByteParser::Hexadecimal
779 .to_u16(bytes)
780 .ok_or_else(|| Error::from(ErrorKind::MalformedCoordinate))?;
781 Ok((n, bytes.len() as u16))
782 }
783
784 let mut iter = bytes.split(|b| *b == b'/');
785 let r = parse(iter.next())?;
786 let g = parse(iter.next())?;
787 let b = parse(iter.next())?;
788 if iter.next().is_some() {
789 return Err(ErrorKind::TooManyCoordinates.into());
790 }
791
792 Ok([r, g, b])
793 }
794}
795
796#[cfg(test)]
799mod test {
800 use super::*;
801 use crate::Control;
802
803 #[test]
804 fn test_size_and_display() {
805 assert_eq!(std::mem::size_of::<BeginBatch>(), 0);
806 assert_eq!(std::mem::size_of::<MoveLeft::<2>>(), 0);
807 assert_eq!(std::mem::size_of::<DynMoveLeft>(), 2);
808 assert_eq!(std::mem::size_of::<MoveTo::<5, 7>>(), 0);
809 assert_eq!(std::mem::size_of::<DynMoveTo>(), 4);
810 assert_eq!(std::mem::size_of::<SetDefaultForeground>(), 0);
811 assert_eq!(std::mem::size_of::<SetForeground8::<0>>(), 0);
812 assert_eq!(std::mem::size_of::<SetForeground8::<15>>(), 0);
813 assert_eq!(std::mem::size_of::<SetForeground8::<88>>(), 0);
814 assert_eq!(std::mem::size_of::<SetBackground8::<7>>(), 0);
815 assert_eq!(std::mem::size_of::<SetBackground8::<9>>(), 0);
816 assert_eq!(std::mem::size_of::<SetBackground8::<226>>(), 0);
817 assert_eq!(std::mem::size_of::<SetForeground24::<255, 103, 227>>(), 0);
818 assert_eq!(std::mem::size_of::<SetBackground24::<134, 36, 161>>(), 0);
819
820 assert_eq!(format!("{}", BeginBatch), "\x1b[?2026h");
821 assert_eq!(format!("{}", MoveLeft::<2>), "\x1b[2C");
822 assert_eq!(format!("{}", DynMoveLeft(2)), "\x1b[2C");
823 assert_eq!(format!("{}", MoveTo::<5, 7>), "\x1b[5;7H");
824 assert_eq!(format!("{}", DynMoveTo(5, 7)), "\x1b[5;7H");
825 assert_eq!(format!("{}", SetDefaultForeground), "\x1b[39m");
826 assert_eq!(format!("{}", SetForeground8::<0>), "\x1b[30m");
827 assert_eq!(format!("{}", SetForeground8::<15>), "\x1b[97m");
828 assert_eq!(format!("{}", SetForeground8::<88>), "\x1b[38;5;88m");
829 assert_eq!(format!("{}", SetBackground8::<7>), "\x1b[47m");
830 assert_eq!(format!("{}", SetBackground8::<9>), "\x1b[101m");
831 assert_eq!(format!("{}", SetBackground8::<226>), "\x1b[48;5;226m");
832 assert_eq!(
833 format!("{}", SetForeground24::<255, 103, 227>),
834 "\x1b[38;2;255;103;227m"
835 );
836 assert_eq!(
837 format!("{}", SetBackground24::<134, 36, 161>),
838 "\x1b[48;2;134;36;161m"
839 );
840 }
841
842 #[test]
843 fn test_parse_terminal_id() -> std::io::Result<()> {
844 assert_eq!(RequestTerminalId.control(), Control::DCS);
845
846 let (term, version) = RequestTerminalId.parse(b">|Terminal\x1b\\")?;
847 assert_eq!(&term.unwrap(), b"Terminal".as_slice());
848 assert!(version.is_none());
849
850 let (term, version) = RequestTerminalId.parse(b">|Terminal (6.65)\x1b\\")?;
851 assert_eq!(&term.unwrap(), b"Terminal".as_slice());
852 assert_eq!(&version.unwrap(), b"6.65".as_slice());
853
854 let (term, version) = RequestTerminalId.parse(b">|Terminal ()\x1b\\")?;
855 assert_eq!(&term.unwrap(), b"Terminal".as_slice());
856 assert_eq!(version, None);
857
858 let (term, version) = RequestTerminalId.parse(b">| ( )\x1b\\")?;
859 assert_eq!(term, None);
860 assert_eq!(version, None);
861
862 let (term, version) = RequestTerminalId.parse(b">|()\x1b\\")?;
863 assert_eq!(term, None);
864 assert_eq!(version, None);
865
866 let (term, version) = RequestTerminalId.parse(b">|\x1b\\")?;
867 assert_eq!(term, None);
868 assert_eq!(version, None);
869 Ok(())
870 }
871
872 #[test]
873 fn test_parse_cursor_position() -> std::io::Result<()> {
874 assert_eq!(RequestCursorPosition.control(), Control::CSI);
875
876 let position = RequestCursorPosition.parse(b"6;65R")?;
877 assert_eq!(position, (6, 65));
878 Ok(())
879 }
880
881 #[test]
882 fn test_parse_theme_color() -> std::io::Result<()> {
883 assert_eq!(RequestColor::Magenta.control(), Control::OSC);
884
885 let color = RequestColor::Background.parse(b"11;rgb:a/b/cdef")?;
886 assert_eq!(color, [(10, 1), (11, 1), (52_719, 4)]);
887 let color = RequestColor::Magenta.parse(b"4;5;rgb:12/345/6789")?;
888 assert_eq!(color, [(18, 2), (837, 3), (26_505, 4)]);
889 let color = RequestColor::BrightMagenta.parse(b"4;13;rgb:ff/00/ff")?;
890 assert_eq!(color, [(255, 2), (0, 2), (255, 2)]);
891 Ok(())
892 }
893}