prettytty/
api.rs

1use std::io::Result;
2
3use crate::util::ByteFormat;
4use crate::{Input, Output};
5
6/// A command for the terminal.
7///
8/// Commands provide instructions to the terminal and are communicated in-band
9/// by writing ANSI escape codes. Doing so is the responsibility of the
10/// [`core::fmt::Display`] implementation, whereas the [`core::fmt::Debug`]
11/// implementation should simply identify the command.
12///
13/// This trait is object-safe.
14pub trait Command: core::fmt::Debug + core::fmt::Display {}
15
16/// A borrowed command is a command.
17impl<C: Command + ?Sized> Command for &C {}
18
19/// A boxed command is a command.
20impl<C: Command + ?Sized> Command for Box<C> {}
21
22/// Combine several commands into a single new command.
23///
24/// The new command preserves the order of its component commands. Upon display,
25/// it emits as many ANSI escape sequence as it has component commands. Upon
26/// debug, it reveals the macro's source arguments.
27///
28/// Since commands in the [`cmd`](crate::cmd) module generally implement
29/// [`Clone`], [`Copy`], [`Debug`](core::fmt::Debug), [`PartialEq`], and [`Eq`],
30/// fused commands do so, too. However, since [`DynLink`](crate::cmd::DynLink)
31/// and [`DynSetWindowTitle`](crate::cmd::DynSetWindowTitle) have string-valued
32/// fields and hence cannot implement [`Copy`], these two commands *cannot* be
33/// fused.
34///
35/// When fusing only SGR commands, prefer [`fuse_sgr!`](crate::fuse_sgr), which
36/// generates commands that emit a single ANSI escape sequence only.
37///
38/// # Example
39///
40/// ```
41/// # use prettytty::{cmd::{MoveDown, MoveRight}, fuse};
42/// let move_down_right_twice = fuse!(MoveDown::<2>, MoveRight::<2>);
43/// assert_eq!(format!("{}", move_down_right_twice), "\x1b[2B\x1b[2D");
44/// ```
45#[macro_export]
46macro_rules! fuse {
47    ($($command:expr),+ $(,)?) => {{
48        /// One or more combined commands.
49        #[derive(Copy, Clone, PartialEq, Eq)]
50        struct Fused;
51
52        impl $crate::Command for Fused {}
53
54        impl ::core::fmt::Debug for Fused {
55            fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
56                f.write_str(concat!(stringify!(fuse!), "(", stringify!($($command),+), ")"))
57            }
58        }
59
60        impl ::core::fmt::Display for Fused {
61            fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
62                $($command.fmt(f)?;)*
63                Ok(())
64            }
65        }
66
67        Fused
68    }}
69}
70
71// ------------------------------------------------------------------------------------------------
72
73/// A command using select-graphic-rendition ANSI escape sequences.
74///
75/// To facilitate composition, SGR commands implement [`Sgr::write_param`],
76/// which writes the parameter(s) with the leading `CSI` and the trailing `m`.
77///
78/// Technically, an `impl &mut core::fmt::Write` would suffice for `out`, but
79/// that would make the method generic and hence also prevent the trait from
80/// being object-safe. Declaring `out` to be a formatter instead doesn't
81/// restrict the trait by much, since `write_param()` is most likely invoked
82/// inside an implementation of `Display::fmt` anyways, while also ensuring that
83/// the trait is object-safe.
84pub trait Sgr: Command {
85    /// Write the parameter(s) for this SGR command.
86    fn write_param(&self, out: &mut core::fmt::Formatter<'_>) -> ::core::fmt::Result;
87}
88
89/// A borrowed SGR is an SGR.
90impl<S: Sgr + ?Sized> Sgr for &S {
91    fn write_param(&self, out: &mut core::fmt::Formatter<'_>) -> ::core::fmt::Result {
92        (**self).write_param(out)
93    }
94}
95
96/// A boxed SGR is an SGR.
97impl<S: Sgr + ?Sized> Sgr for Box<S> {
98    fn write_param(&self, out: &mut core::fmt::Formatter<'_>) -> ::core::fmt::Result {
99        (**self).write_param(out)
100    }
101}
102
103/// Combine several SGR commands into a single new SGR command.
104///
105/// The new SGR command preserves the order of its component commands. Upon
106/// display, it emits only one ANSI escape sequence. Upon debug, it reveals the
107/// macro's source arguments.
108///
109/// Since commands in the [`cmd`](crate::cmd) module generally implement
110/// [`Clone`], [`Copy`], [`Debug`](core::fmt::Debug), [`PartialEq`], and [`Eq`],
111/// fused SGR commands do so, too.
112///
113/// To fuse commands other than SGR commands, use [`fuse!`].
114#[macro_export]
115macro_rules! fuse_sgr {
116    ( $sgr:expr, $( $sgr2:expr ),* $(,)? ) => {{
117        /// One or more SGR commands fused into one.
118        #[derive(Copy, Clone, PartialEq, Eq)]
119        struct FusedSgr;
120
121        impl ::core::fmt::Debug for FusedSgr {
122            fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
123                f.write_str(concat!(stringify!(fuse_sgr!), "(", stringify!($sgr, $($sgr2),*), ")"))
124            }
125        }
126
127        impl ::core::fmt::Display for FusedSgr {
128            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
129                f.write_str("\x1b[")?;
130                self.write_param(f)?;
131                f.write_str("m")
132            }
133        }
134
135        impl $crate::Command for FusedSgr {}
136        impl $crate::Sgr for FusedSgr {
137            fn write_param(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
138                $sgr.write_param(f)?;
139                $(
140                    f.write_str(";")?;
141                    $sgr2.write_param(f)?;
142                )*
143                Ok(())
144            }
145        }
146
147        FusedSgr
148    }};
149}
150
151// ------------------------------------------------------------------------------------------------
152
153/// Control codes that start or end ANSI escape sequences.
154#[derive(Clone, Copy, Debug, PartialEq, Eq)]
155pub enum Control {
156    /// Bell (C0)
157    BEL = 0x07,
158    /// Escape (C0)
159    ESC = 0x1b,
160    /// Device control string: `ESC P` (C0) or 0x90 (C1)
161    DCS = 0x90,
162    /// Start of String: `ESC X` (C0) or 0x98 (C1)
163    SOS = 0x98,
164    /// Single Shift 2: `ESC N` (C0) or 0x8e (C1)
165    SS2 = 0x8e,
166    /// Single Shift 3: `ESC O` (C0) or 0x8f (C1)
167    SS3 = 0x8f,
168    /// Control Sequence Introducer: `ESC [` (C0) or 0x9b (C1)
169    CSI = 0x9b,
170    /// String Terminator: `ESC \\` (C0) or 0x9c (C1)
171    ST = 0x9c,
172    /// Operating System Command: `ESC ]` (C0) or 0x9d (C1)
173    OSC = 0x9d,
174    /// Privacy Message: `ESC ^` (C0) or 0x9e (C1)
175    PM = 0x9e,
176    /// Application Program Command: `ESC _` (C0) or 0x9f (C1)
177    APC = 0x9f,
178}
179
180impl core::fmt::Display for Control {
181    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
182        use self::Control::*;
183
184        f.write_str(match *self {
185            BEL => "\x07",
186            ESC => "\x1b",
187            DCS => "\x1bP",
188            SOS => "\x1bX",
189            SS2 => "\x1bN",
190            SS3 => "\x1bO",
191            CSI => "\x1b[",
192            ST => "\x1b\\",
193            OSC => "\x1b]",
194            PM => "\x1b^",
195            APC => "\x1b_",
196        })
197    }
198}
199
200/// A command that receives a response.
201///
202/// Queries are request/response interactions with the terminal. For purposes of
203/// this trait, the response consists of a control followed by the payload
204/// optionally followed by another control (usually `BEL` or `ST`). The trait
205/// uses a method, and not a constant, for the control, so as to remain
206/// object-safe.
207///
208///
209/// # Example
210///
211/// ## The Elaborate Version
212///
213/// To process a query's response, [`Scan::read_token()`] and ensure that the
214/// [`Token`] is a sequence with a [`Query::control()`]. Then [`Query::parse`]
215/// the payload.
216///
217/// ```
218/// # use prettytty::{Connection, Query, Scan, Token};
219/// # use prettytty::cmd::{MoveToColumn, RequestCursorPosition};
220/// # use prettytty::err::ErrorKind;
221/// # use prettytty::opt::Options;
222/// # let options = Options::default();
223/// # let tty = match Connection::with_options(options) {
224/// #     Ok(tty) => tty,
225/// #     Err(err) if err.kind() == std::io::ErrorKind::ConnectionRefused => return Ok(()),
226/// #     Err(err) => return Err(err),
227/// # };
228/// # let (mut input, mut output) = tty.io();
229/// # output.exec(MoveToColumn::<17>)?;
230/// // Write the command
231/// output.exec(RequestCursorPosition)?;
232///
233/// // Read the token
234/// let token = input.read_token()?;
235///
236/// // Extract and parse payload
237/// let pos;
238/// if let Token::Sequence(control, payload) = token {
239///     if control == RequestCursorPosition.control() {
240///         pos = RequestCursorPosition.parse(payload)?
241///     } else {
242///         return Err(ErrorKind::BadControl.into());
243///     }
244/// } else {
245///     return Err(ErrorKind::NotASequence.into());
246/// }
247/// # assert_eq!(pos.1, 17);
248/// # Ok::<(), std::io::Error>(())
249/// ```
250///
251/// ## Using `Scan::read_sequence`
252///
253/// In the above example, generating precise errors requires about as much code
254/// as extracting and parsing the payload. The [`Scan::read_sequence`] method
255/// abstracts over this boilerplate. It certainly helps:
256///
257/// ```
258/// # use prettytty::{Connection, Query, Scan, Token};
259/// # use prettytty::cmd::{MoveToColumn, RequestCursorPosition};
260/// # use prettytty::err::ErrorKind;
261/// # use prettytty::opt::Options;
262/// # let options = Options::default();
263/// # let tty = match Connection::with_options(options) {
264/// #     Ok(tty) => tty,
265/// #     Err(err) if err.kind() == std::io::ErrorKind::ConnectionRefused => return Ok(()),
266/// #     Err(err) => return Err(err),
267/// # };
268/// # let (mut input, mut output) = tty.io();
269/// # output.exec(MoveToColumn::<17>)?;
270/// // Write the command
271/// output.exec(RequestCursorPosition)?;
272///
273/// // Read the ANSI escape sequence and extract the payload
274/// let payload = input.read_sequence(RequestCursorPosition.control())?;
275///
276/// // Parse the payload
277/// let pos = RequestCursorPosition.parse(payload)?;
278/// # assert_eq!(pos.1, 17);
279/// # Ok::<(), std::io::Error>(())
280/// ```
281///
282/// ## Using `Query::run`
283///
284/// While much cleaner, the previous example still is boilerplate. After all,
285/// every query needs to write the request, scan the response for the payload,
286/// and parse the payload. [`Query::run`] abstracts over that:
287///
288/// ```
289/// # use prettytty::{Connection, Query, Scan, Token};
290/// # use prettytty::cmd::{MoveToColumn, RequestCursorPosition};
291/// # use prettytty::err::ErrorKind;
292/// # use prettytty::opt::Options;
293/// # let options = Options::default();
294/// # let tty = match Connection::with_options(options) {
295/// #     Ok(tty) => tty,
296/// #     Err(err) if err.kind() == std::io::ErrorKind::ConnectionRefused => return Ok(()),
297/// #     Err(err) => return Err(err),
298/// # };
299/// # let (mut input, mut output) = tty.io();
300/// # output.exec(MoveToColumn::<17>)?;
301/// let pos = RequestCursorPosition.run(&mut input, &mut output)?;
302/// # assert_eq!(pos.1, 17);
303/// # Ok::<(), std::io::Error>(())
304/// ```
305///
306/// Nice, right? Alas, [`Query::run`] may be slower than needs be when
307/// processing a batch of queries. The method's documentation addresses this and
308/// other performance considerations.
309pub trait Query: Command {
310    /// The type of the response data.
311    type Response;
312
313    /// Get the response's control.
314    fn control(&self) -> Control;
315
316    /// Parse the payload into a response object.
317    fn parse(&self, payload: &[u8]) -> Result<Self::Response>;
318
319    /// Run this query.
320    ///
321    /// This method writes the request to the given output, reads the response
322    /// from the given input, parses the response payload, returning the result.
323    ///
324    ///
325    /// # Performance Considerations
326    ///
327    /// Since accessing a connection's input and output entails acquiring a
328    /// mutex each, this method takes the input and output objects as arguments.
329    /// That way, the caller controls when to acquire the two objects and incur
330    /// the corresponding overhead. As a result, the caller also incurs the
331    /// notational overhead of passing two arguments prefixed with `&mut`
332    /// instead of passing one argument prefixed with `&` (as the connection
333    /// object uses interior mutability). While not ideal, favoring flexibility
334    /// and performance over concision seems the right trade-off.
335    ///
336    /// This method is well-suited to running the occasional query. However,
337    /// when executing several queries in a row, e.g., when querying a terminal
338    /// for its color theme, this method may not be performant, especially when
339    /// running in a remote shell. Instead, an application should write all
340    /// requests to output before flushing (once) and then process all
341    /// responses. Prettypretty's
342    /// [`Theme::query`](https://apparebit.github.io/prettypretty/prettypretty/theme/struct.Theme.html#method.query)
343    /// does just that. If you [check the
344    /// source](https://github.com/apparebit/prettypretty/blob/f25d2215d0747ca86ac8bcb5a48426dd7a496eb4/crates/prettypretty/src/theme.rs#L95),
345    /// it actually implements versions with one, two, and three processing
346    /// loops; the [query
347    /// benchmark](https://github.com/apparebit/prettypretty/blob/main/crates/prettypretty/benches/query.rs)
348    /// compares their performance.
349    fn run(&self, input: &mut Input, output: &mut Output) -> Result<Self::Response> {
350        output.exec(self)?;
351        let payload = input.read_sequence(self.control())?;
352        self.parse(payload)
353    }
354}
355
356/// A borrowed query is a query.
357impl<Q: Query + ?Sized> Query for &Q {
358    type Response = Q::Response;
359
360    fn control(&self) -> Control {
361        (**self).control()
362    }
363
364    fn parse(&self, payload: &[u8]) -> Result<Self::Response> {
365        (**self).parse(payload)
366    }
367}
368
369/// A boxed query is a query.
370impl<Q: Query + ?Sized> Query for Box<Q> {
371    type Response = Q::Response;
372
373    fn control(&self) -> Control {
374        (**self).control()
375    }
376
377    fn parse(&self, payload: &[u8]) -> Result<Self::Response> {
378        (**self).parse(payload)
379    }
380}
381
382// ------------------------------------------------------------------------------------------------
383
384/// A text or control sequence token.
385#[derive(Clone, PartialEq, Eq)]
386pub enum Token<'t> {
387    /// One or more UTF-8 characters excluding C0 and C1 controls.
388    Text(&'t [u8]),
389    /// A C0 or C1 control that doesn't start or end a sequence. This token
390    /// always has one byte of character data.
391    Control(&'t [u8]),
392    /// A control sequence with its initial control and subsequent payload.
393    Sequence(Control, &'t [u8]),
394}
395
396impl Token<'_> {
397    /// Get this token's control.
398    pub fn control(&self) -> Option<Control> {
399        match *self {
400            Token::Sequence(control, _) => Some(control),
401            _ => None,
402        }
403    }
404
405    /// Get this token's character data.
406    ///
407    /// The length of the returned byte slice varies for text and sequence
408    /// tokens. It always is 1, however, for the control token.
409    pub fn data(&self) -> &[u8] {
410        use self::Token::*;
411
412        match *self {
413            Text(data) => data,
414            Control(data) => data,
415            Sequence(_, data) => data,
416        }
417    }
418}
419
420impl core::fmt::Debug for Token<'_> {
421    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
422        let name = match *self {
423            Self::Text(_) => "Text",
424            Self::Control(_) => "Control",
425            Self::Sequence(_, _) => "Sequence",
426        };
427
428        let mut debug = f.debug_tuple(name);
429        if let Some(control) = self.control() {
430            debug.field(&control);
431        }
432        debug
433            .field(&format!("{}", ByteFormat::Nicely(self.data())))
434            .finish()
435    }
436}
437
438/// A scanner for UTF-8 characters and control sequences.
439///
440/// An implementation of this trait provides the state machine necessary for
441/// scanning UTF-8 characters and control sequences. Since scanning control
442/// sequences requires one byte lookahead, an implementation also buffers the
443/// data it reads from the terminal.
444///
445/// Some terminal emulators may require more than one read from terminal input
446/// to consume a complete ANSI escape sequence serving as query response. Hence
447/// [`Scan::read_token`] may perform an arbitrary number of reads from the
448/// underlying input, including none, to recognize a complete control sequence.
449/// However, an implementation must not issue reads after it has started
450/// recognizing a text token. In other words, when reading a text token, the end
451/// of buffered data also is the end of the text token.
452///
453/// This trait is object-safe.
454pub trait Scan: std::io::BufRead {
455    /// Determine if the state machine currently is in-flight.
456    ///
457    /// Using a scanner as a reader is only safe if this method returns `false`.
458    fn in_flight(&self) -> bool;
459
460    /// Read the next token.
461    ///
462    /// If the internal buffer has been exhausted, this method may read from the
463    /// connection upon invocation. For text tokens, it performs no further
464    /// reads. That is, a text token always ends with the currently buffered
465    /// data.
466    fn read_token(&mut self) -> Result<Token>;
467
468    /// Read the next token as a control sequence.
469    ///
470    /// This method reads the next token and, after making sure it is a control
471    /// sequence starting with the given control, returns the payload.
472    fn read_sequence(&mut self, control: Control) -> Result<&[u8]> {
473        match self.read_token()? {
474            Token::Sequence(actual, payload) => {
475                if actual == control {
476                    Ok(payload)
477                } else {
478                    Err(crate::err::ErrorKind::BadControl.into())
479                }
480            }
481            _ => Err(crate::err::ErrorKind::NotASequence.into()),
482        }
483    }
484}
485
486/// A mutably borrowed scanner is a scanner.
487impl<S: Scan + ?Sized> Scan for &mut S {
488    #[inline]
489    fn in_flight(&self) -> bool {
490        (**self).in_flight()
491    }
492
493    #[inline]
494    fn read_token(&mut self) -> Result<Token> {
495        (**self).read_token()
496    }
497}
498
499/// A boxed scanner is a scanner.
500impl<S: Scan + ?Sized> Scan for Box<S> {
501    #[inline]
502    fn in_flight(&self) -> bool {
503        (**self).in_flight()
504    }
505
506    #[inline]
507    fn read_token(&mut self) -> Result<Token> {
508        (**self).read_token()
509    }
510}
511
512fn _assert_traits_are_object_safe<T>() {
513    fn is_object_safe<T: ?Sized>() {}
514
515    is_object_safe::<dyn Command>();
516    is_object_safe::<dyn Sgr>();
517    is_object_safe::<dyn Query<Response = T>>();
518    is_object_safe::<dyn Scan>();
519}
520
521#[cfg(test)]
522mod test {
523    use super::*;
524    use crate::cmd::{Format, SetBackground8, SetForeground8};
525
526    #[test]
527    fn test_fuse() {
528        let s = format!(
529            "{}",
530            fuse!(Format::Bold, SetForeground8::<0>, SetBackground8::<15>)
531        );
532        assert_eq!(s, "\x1b[1m\x1b[30m\x1b[107m");
533
534        let cmd = fuse!(Format::Blinking, SetBackground8::<219>);
535        assert_eq!(format!("{}", cmd), "\x1b[5m\x1b[48;5;219m");
536        assert_eq!(
537            format!("{:?}", cmd),
538            "fuse!(Format::Blinking, SetBackground8::<219>)"
539        );
540
541        let double = format!("{}{}", cmd, cmd);
542
543        let copy = cmd;
544        assert_eq!(format!("{}{}", cmd, copy), double);
545        assert_eq!(
546            format!("{:?}", cmd),
547            "fuse!(Format::Blinking, SetBackground8::<219>)"
548        );
549
550        let clone = cmd.clone();
551        assert_eq!(format!("{}{}", cmd, clone), double);
552        assert_eq!(
553            format!("{:?}", cmd),
554            "fuse!(Format::Blinking, SetBackground8::<219>)"
555        );
556
557        assert_eq!(cmd, copy);
558        assert_eq!(cmd, clone);
559    }
560
561    #[test]
562    fn test_fuse_sgr() {
563        let s = format!(
564            "{}",
565            fuse_sgr!(Format::Bold, SetForeground8::<0>, SetBackground8::<15>)
566        );
567        assert_eq!(s, "\x1b[1;30;107m");
568
569        let cmd = fuse_sgr!(Format::Bold, SetForeground8::<0>, SetBackground8::<15>);
570        assert_eq!(format!("{}", cmd), "\x1b[1;30;107m");
571        assert_eq!(
572            format!("{:?}", cmd),
573            "fuse_sgr!(Format::Bold, SetForeground8::<0>, SetBackground8::<15>)"
574        );
575
576        let double = format!("{}{}", cmd, cmd);
577
578        let copy = cmd;
579        assert_eq!(format!("{}{}", cmd, copy), double);
580        assert_eq!(
581            format!("{:?}", cmd),
582            "fuse_sgr!(Format::Bold, SetForeground8::<0>, SetBackground8::<15>)"
583        );
584
585        let clone = cmd.clone();
586        assert_eq!(format!("{}{}", cmd, clone), double);
587        assert_eq!(
588            format!("{:?}", cmd),
589            "fuse_sgr!(Format::Bold, SetForeground8::<0>, SetBackground8::<15>)"
590        );
591
592        assert_eq!(cmd, copy);
593        assert_eq!(cmd, clone);
594    }
595}