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 87
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//!       * [`BeginPaste`] and [`EndPaste`] to perform
41//!         [bracketed paste](https://cirw.in/blog/bracketed-paste) operations
42//!       * [`DynLink`] to [add
43//!         hyperlinks](https://gist.github.com/christianparpart/180fb3c5f008489c8afcffb3fa46cd8e)
44//!   * Managing modes:
45//!       * [`RequestMode`] and [`DynRequestMode`] to query the status of a mode.
46//!   * Styling content:
47//!       * [`ResetStyle`]
48//!       * [`RequestActiveStyle`]
49//!       * [`SetDefaultForeground`], [`SetForeground8`], [`SetForeground24`],
50//!         [`DynSetForeground8`], and [`DynSetForeground24`]
51//!       * [`SetDefaultBackground`], [`SetBackground8`], [`SetBackground24`],
52//!         [`DynSetBackground8`], and [`DynSetBackground24`]
53//!       * [`Format::Bold`], [`Format::Thin`], and [`Format::Regular`]
54//!       * [`Format::Italic`] and [`Format::Upright`]
55//!       * [`Format::Underlined`] and [`Format::NotUnderlined`]
56//!       * [`Format::Blinking`] and [`Format::NotBlinking`]
57//!       * [`Format::Reversed`] and [`Format::NotReversed`]
58//!       * [`Format::Hidden`] and [`Format::NotHidden`]
59//!       * [`Format::NotStricken`] and [`Format::NotStricken`]
60//!       * [`RequestColor::Black`], [`RequestColor::Red`], and so on for all 16
61//!         ANSI colors, also [`RequestColor::Foreground`],
62//!         [`RequestColor::Background`], [`RequestColor::Cursor`], and
63//!         [`RequestColor::Selection`]
64//!
65//! Most commands are implemented by zero-sized unit structs and enum variants.
66//! Commands that require arguments may come in one or both of two flavors, a
67//! static flavor relying on const generics and a dynamic flavor storing the
68//! arguments. The command name for the latter flavor starts with `Dyn`; it
69//! obviously is *not* zero-sized.
70//!
71//! If a command name starts with `Request`, it also implements the [`Query`]
72//! trait and hence knows how to parse the response's payload. When implementing
73//! your own queries, you may find [`util::ByteParser`](crate::util::ByteParser)
74//! useful.
75//!
76//! You can easily combine several commands into a compound command with the
77//! [`fuse!`](crate::fuse) and [`fuse_sgr!`](crate::fuse_sgr) macros.
78//!
79//!
80//! # Example
81//!
82//! Executing a command is as simple as writing its display:
83//! ```
84//! # use prettytty::{fuse_sgr, Sgr, cmd::{Format, ResetStyle, SetForeground8}};
85//! println!(
86//!     "{}Wow!{}",
87//!     fuse_sgr!(Format::Bold, Format::Underlined, SetForeground8::<124>),
88//!     ResetStyle
89//! );
90//! ```
91//! The invocation of the [`fuse_sgr!`](crate::fuse_sgr) macro in the above
92//! example is not strictly necessary. Separately writing `Format::Bold`,
93//! `Format::Underlined`, and `SetForeground8::<124>` to the console would set
94//! the same style. However, that would also write three distinct ANSI escape
95//! sequences, whereas `fuse_sgr!` returns a value that writes only one ANSI
96//! escape sequence. After receiving the above text, the terminal prints <img
97//! style="display: inline-block; vertical-align: text-top"
98//!      src="https://raw.githubusercontent.com/apparebit/prettypretty/main/docs/figures/wow.png"
99//!      alt="wow!" width="42">. Wow indeed 😜
100
101use 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
271// ====================================== Library ======================================
272
273// -------------------------------- Terminal Management --------------------------------
274
275define_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
319// --------------------------------- Window Management ---------------------------------
320
321define_unit_command!(SaveWindowTitle, "\x1b[22;2t");
322define_unit_command!(RestoreWindowTitle, "\x1b[23;2t");
323
324/// The dynamic `DynSetWindowTitle(String)` command.
325///
326/// This command cannot be copied, only cloned.
327#[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
335// --------------------------------- Screen Management ---------------------------------
336
337define_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
368// ------------------------------------- Scrolling -------------------------------------
369
370define_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// --------------------------------- Cursor Management ---------------------------------
379
380#[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    /// The row and column of the cursor in that order.
421    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
453// -------------------------------- Content Management ---------------------------------
454
455define_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/// The dynamic `DynLink(ID, HREF, TEXT)` command.
465///
466/// This command cannot be copied, only cloned.
467#[derive(Clone, Debug, PartialEq, Eq)]
468pub struct DynLink(Option<String>, String, String);
469
470impl DynLink {
471    /// Create a new hyperlink with the given URL and text.
472    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    /// Create a new hyperlink with the given ID, URL, and text.
481    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// --------------------------------------- Modes ---------------------------------------
507
508/// The current batch processing mode.
509#[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
574// --------------------------------- Style Management ----------------------------------
575
576define_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/// The enumeration of unit `Format` commands.
598#[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    /// Get the format that restores default appearance.
620    #[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/// The enumeration of unit `RequestColor` commands.
675///
676/// The discriminant ranges from 0 to 15 for the 16 ANSI colors. For the default
677/// foreground, default background, cursor, or selection colors, it is 100 plus
678/// the code used in the query. On Windows, this query is only supported by
679/// Terminal 1.22 or later.
680#[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    /// The number of possible color requests.
707    pub const COUNT: usize = 20;
708
709    /// Get the successor.
710    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    /// Get an iterator over all color requests.
738    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    /// An RGB color.
762    ///
763    /// The parsed response comprises one pair per RGB channel, with the first
764    /// number the signal strength and the second number the signal width. The
765    /// signal width is the number of hexadecimal digits, always between 1 and 4
766    /// inclusive, and usually 4. Hence, to normalize (_s_, _w_) to a floating
767    /// point number between 0 and 1, compute _s_/((16^_w_)-1).
768    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// =====================================================================================
827
828#[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}