prettytty/
cmd.rs

1//! A library of useful terminal commands.
2//!
3//! This module provides a number of straight-forward struct and enum types that
4//! implement the [`Command`] trait and, where needed, also the [`Sgr`] and
5//! [`Query`] traits. Organized by topic, this library covers the following 86
6//! commands:
7//!
8//!   * Terminal identification:
9//!       * [`RequestTerminalId`]
10//!   * Window title management:
11//!       * [`SaveWindowTitle`] and [`RestoreWindowTitle`]
12//!       * [`DynSetWindowTitle`]
13//!   * Screen management:
14//!       * [`RequestScreenSize`]
15//!       * [`EnterAlternateScreen`] and [`ExitAlternateScreen`]
16//!       * [`EnableReverseMode`] and [`DisableReverseMode`]
17//!       * [`EraseScreen`]
18//!   * Scrolling:
19//!       * [`ScrollUp`], [`ScrollDown`], [`DynScrollUp`], and [`DynScrollDown`]
20//!       * [`SetScrollRegion`] and [`DynSetScrollRegion`]
21//!       * [`ResetScrollRegion`]
22//!       * [`EnableAutowrap`] and [`DisableAutowrap`]
23//!   * Cursor management:
24//!       * [`SetCursor::Default`], [`SetCursor::BlinkingBlock`],
25//!         [`SetCursor::SteadyBlock`], [`SetCursor::BlinkingUnderscore`],
26//!         [`SetCursor::SteadyUnderscore`], [`SetCursor::BlinkingBar`], and
27//!         [`SetCursor::SteadyBar`].
28//!       * [`HideCursor`] and [`ShowCursor`]
29//!       * [`RequestCursorPosition`]
30//!       * Relative [`MoveUp`], [`MoveDown`], [`MoveLeft`], [`MoveRight`],
31//!         [`DynMoveUp`], [`DynMoveDown`], [`DynMoveLeft`], and
32//!         [`DynMoveRight`]
33//!       * Absolute [`MoveToColumn`], [`MoveToRow`], [`MoveTo`],
34//!         [`DynMoveToColumn`], [`DynMoveToRow`], and [`DynMoveTo`]
35//!       * [`SaveCursorPosition`] and [`RestoreCursorPosition`]
36//!   * Managing content:
37//!       * [`EraseLine`] and [`EraseRestOfLine`]
38//!       * [`BeginBatch`] and [`EndBatch`] to [group
39//!         updates](https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036)
40//!       * [`RequestBatchMode`]
41//!       * [`BeginPaste`] and [`EndPaste`] to perform
42//!         [bracketed paste](https://cirw.in/blog/bracketed-paste) operations
43//!       * [`DynLink`] to [add
44//!         hyperlinks](https://gist.github.com/christianparpart/180fb3c5f008489c8afcffb3fa46cd8e)
45//!   * Styling content:
46//!       * [`ResetStyle`]
47//!       * [`RequestActiveStyle`]
48//!       * [`SetDefaultForeground`], [`SetForeground8`], [`SetForeground24`],
49//!         [`DynSetForeground8`], and [`DynSetForeground24`]
50//!       * [`SetDefaultBackground`], [`SetBackground8`], [`SetBackground24`],
51//!         [`DynSetBackground8`], and [`DynSetBackground24`]
52//!       * [`Format::Bold`], [`Format::Thin`], and [`Format::Regular`]
53//!       * [`Format::Italic`] and [`Format::Upright`]
54//!       * [`Format::Underlined`] and [`Format::NotUnderlined`]
55//!       * [`Format::Blinking`] and [`Format::NotBlinking`]
56//!       * [`Format::Reversed`] and [`Format::NotReversed`]
57//!       * [`Format::Hidden`] and [`Format::NotHidden`]
58//!       * [`Format::NotStricken`] and [`Format::NotStricken`]
59//!       * [`RequestColor::Black`], [`RequestColor::Red`], and so on for all 16
60//!         ANSI colors, also [`RequestColor::Foreground`],
61//!         [`RequestColor::Background`], [`RequestColor::Cursor`], and
62//!         [`RequestColor::Selection`]
63//!
64//! Most commands are implemented by zero-sized unit structs and enum variants.
65//! Commands that require arguments may come in one or both of two flavors, a
66//! static flavor relying on const generics and a dynamic flavor storing the
67//! arguments. The command name for the latter flavor starts with `Dyn`; it
68//! obviously is *not* zero-sized.
69//!
70//! If a command name starts with `Request`, it also implements the [`Query`]
71//! trait and hence knows how to parse the response's payload. When implementing
72//! your own queries, you may find [`util::ByteParser`](crate::util::ByteParser)
73//! useful.
74//!
75//! You can easily combine several commands into a compound command with the
76//! [`fuse!`](crate::fuse) and [`fuse_sgr!`](crate::fuse_sgr) macros.
77//!
78//!
79//! # Example
80//!
81//! Executing a command is as simple as writing its display:
82//! ```
83//! # use prettytty::{fuse_sgr, Sgr, cmd::{Format, ResetStyle, SetForeground8}};
84//! println!(
85//!     "{}Wow!{}",
86//!     fuse_sgr!(Format::Bold, Format::Underlined, SetForeground8::<124>),
87//!     ResetStyle
88//! );
89//! ```
90//! The invocation of the [`fuse_sgr!`](crate::fuse_sgr) macro in the above
91//! example is not strictly necessary. Separately writing `Format::Bold`,
92//! `Format::Underlined`, and `SetForeground8::<124>` to the console would set
93//! the same style. However, that would also write three distinct ANSI escape
94//! sequences, whereas `fuse_sgr!` returns a value that writes only one ANSI
95//! escape sequence. After receiving the above text, the terminal prints <img
96//! style="display: inline-block; vertical-align: text-top"
97//!      src="https://raw.githubusercontent.com/apparebit/prettypretty/main/docs/figures/wow.png"
98//!      alt="wow!" width="42">. Wow indeed 😜
99
100use 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
270// ====================================== Library ======================================
271
272// -------------------------------- Terminal Management --------------------------------
273
274define_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
318// --------------------------------- Window Management ---------------------------------
319
320define_unit_command!(SaveWindowTitle, "\x1b[22;2t");
321define_unit_command!(RestoreWindowTitle, "\x1b[23;2t");
322
323/// The dynamic `DynSetWindowTitle(String)` command.
324///
325/// This command cannot be copied, only cloned.
326#[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
334// --------------------------------- Screen Management ---------------------------------
335
336define_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
367// ------------------------------------- Scrolling -------------------------------------
368
369define_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// --------------------------------- Cursor Management ---------------------------------
378
379#[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    /// The row and column of the cursor in that order.
420    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
452// -------------------------------- Content Management ---------------------------------
453
454define_unit_command!(EraseLine, "\x1b[2K");
455define_unit_command!(EraseRestOfLine, "\x1b[K");
456
457/// The current batch processing mode.
458#[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/// The dynamic `DynLink(ID, HREF, TEXT)` command.
503///
504/// This command cannot be copied, only cloned.
505#[derive(Clone, Debug, PartialEq, Eq)]
506pub struct DynLink(Option<String>, String, String);
507
508impl DynLink {
509    /// Create a new hyperlink with the given URL and text.
510    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    /// Create a new hyperlink with the given ID, URL, and text.
519    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
544// --------------------------------- Style Management ----------------------------------
545
546define_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/// The enumeration of unit `Format` commands.
568#[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    /// Get the format that restores default appearance.
590    #[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/// The enumeration of unit `RequestColor` commands.
645///
646/// The discriminant ranges from 0 to 15 for the 16 ANSI colors. For the default
647/// foreground, default background, cursor, or selection colors, it is 100 plus
648/// the code used in the query. On Windows, this query is only supported by
649/// Terminal 1.22 or later.
650#[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    /// The number of possible color requests.
677    pub const COUNT: usize = 20;
678
679    /// Get the successor.
680    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    /// Get an iterator over all color requests.
708    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    /// An RGB color.
732    ///
733    /// The parsed response comprises one pair per RGB channel, with the first
734    /// number the signal strength and the second number the signal width. The
735    /// signal width is the number of hexadecimal digits, always between 1 and 4
736    /// inclusive, and usually 4. Hence, to normalize (_s_, _w_) to a floating
737    /// point number between 0 and 1, compute _s_/((16^_w_)-1).
738    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// =====================================================================================
797
798#[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}