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