tattoy_wezterm_escape_parser/
lib.rs

1// suppress inscrutable useless_attribute clippy that shows up when
2// using derive(FromPrimitive)
3#![allow(clippy::useless_attribute)]
4#![allow(clippy::upper_case_acronyms)]
5#![cfg_attr(not(feature = "std"), no_std)]
6//! This module provides the ability to parse escape sequences and attach
7//! semantic meaning to them.  It can also encode the semantic values as
8//! escape sequences.  It provides encoding and decoding functionality
9//! only; it does not provide terminal emulation facilities itself.
10#[cfg(feature = "tmux_cc")]
11use crate::tmux_cc::Event;
12use core::fmt::{Display, Formatter, Result as FmtResult, Write as FmtWrite};
13use num_derive::*;
14use wezterm_color_types::LinearRgba;
15
16#[cfg_attr(not(feature = "std"), macro_use)]
17extern crate alloc;
18
19mod allocate;
20use allocate::*;
21
22pub mod apc;
23pub mod color;
24pub mod csi;
25pub mod error;
26pub mod esc;
27pub mod hyperlink;
28pub mod osc;
29pub mod parser;
30#[cfg(feature = "tmux_cc")]
31pub mod tmux_cc;
32
33pub use self::apc::KittyImage;
34pub use self::csi::CSI;
35pub use self::error::{Error, Result};
36pub use self::esc::{Esc, EscCode};
37pub use self::osc::OperatingSystemCommand;
38
39use vtparse::CsiParam;
40
41#[derive(Debug, Clone, PartialEq)]
42pub enum Action {
43    /// Send a single printable character to the display
44    Print(char),
45    /// Send a string of printable characters to the display.
46    PrintString(String),
47    /// A C0 or C1 control code
48    Control(ControlCode),
49    /// Device control.  This is uncommon wrt. terminal emulation.
50    DeviceControl(DeviceControlMode),
51    /// A command that typically doesn't change the contents of the
52    /// terminal, but rather influences how it displays or otherwise
53    /// interacts with the rest of the system
54    OperatingSystemCommand(Box<OperatingSystemCommand>),
55    CSI(CSI),
56    Esc(Esc),
57    Sixel(Box<Sixel>),
58    /// A list of termcap, terminfo names for which the application
59    /// wants information
60    XtGetTcap(Vec<String>),
61    KittyImage(Box<KittyImage>),
62}
63
64impl Action {
65    /// Append this `Action` to a `Vec<Action>`.
66    /// If this `Action` is `Print` and the last element is `Print` or
67    /// `PrintString` then the elements are combined into `PrintString`
68    /// to reduce heap utilization.
69    pub fn append_to(self, dest: &mut Vec<Self>) {
70        if let Action::Print(c) = &self {
71            match dest.last_mut() {
72                Some(Action::PrintString(s)) => {
73                    s.push(*c);
74                    return;
75                }
76                Some(Action::Print(prior)) => {
77                    let mut s = prior.to_string();
78                    dest.pop();
79                    s.push(*c);
80                    dest.push(Action::PrintString(s));
81                    return;
82                }
83                _ => {}
84            }
85        }
86        dest.push(self);
87    }
88}
89
90#[cfg(all(test, target_pointer_width = "64"))]
91#[test]
92fn action_size() {
93    assert_eq!(core::mem::size_of::<Action>(), 32);
94    assert_eq!(core::mem::size_of::<DeviceControlMode>(), 16);
95    assert_eq!(core::mem::size_of::<ControlCode>(), 1);
96    assert_eq!(core::mem::size_of::<CSI>(), 32);
97    assert_eq!(core::mem::size_of::<Esc>(), 4);
98}
99
100/// Encode self as an escape sequence.  The escape sequence may potentially
101/// be clear text with no actual escape sequences.
102impl Display for Action {
103    fn fmt(&self, f: &mut Formatter) -> FmtResult {
104        match self {
105            Action::Print(c) => write!(f, "{}", c),
106            Action::PrintString(s) => write!(f, "{}", s),
107            Action::Control(c) => f.write_char(*c as u8 as char),
108            Action::DeviceControl(c) => c.fmt(f),
109            Action::OperatingSystemCommand(osc) => osc.fmt(f),
110            Action::CSI(csi) => csi.fmt(f),
111            Action::Esc(esc) => esc.fmt(f),
112            Action::Sixel(sixel) => sixel.fmt(f),
113            Action::XtGetTcap(names) => {
114                write!(f, "\x1bP+q")?;
115                for (i, name) in names.iter().enumerate() {
116                    if i > 0 {
117                        write!(f, ";")?;
118                    }
119                    for &b in name.as_bytes() {
120                        write!(f, "{:x}", b)?;
121                    }
122                }
123
124                Ok(())
125            }
126            Action::KittyImage(img) => img.fmt(f),
127        }
128    }
129}
130
131/// A fully parsed DCS sequence.
132/// The parser emits these for byte/intermediate sequences that are
133/// known to be relatively short and self contained (eg: DECRQSS)
134/// as opposed to larger ones like Sixel (which is parsed separately),
135/// or long lived terminal modes such as the TMUX CC protocol.
136#[derive(Clone, PartialEq, Eq)]
137pub struct ShortDeviceControl {
138    /// Integer parameter values
139    pub params: Vec<i64>,
140    /// Intermediate bytes to refine the control
141    pub intermediates: Vec<u8>,
142    /// The final byte
143    pub byte: u8,
144    /// The data prior to the string terminator
145    pub data: Vec<u8>,
146}
147
148impl core::fmt::Debug for ShortDeviceControl {
149    fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
150        write!(
151            fmt,
152            "ShortDeviceControl(params: {:?}, intermediates: [",
153            &self.params
154        )?;
155        for b in &self.intermediates {
156            write!(fmt, "{:?} 0x{:x}, ", *b as char, *b)?;
157        }
158        write!(
159            fmt,
160            "], byte: {:?} 0x{:x}, data=[",
161            self.byte as char, self.byte
162        )?;
163
164        for b in &self.data {
165            write!(fmt, "{:?} 0x{:x}, ", *b as char, *b)?;
166        }
167
168        write!(fmt, ")")
169    }
170}
171
172impl Display for ShortDeviceControl {
173    fn fmt(&self, f: &mut Formatter) -> FmtResult {
174        write!(f, "\x1bP")?;
175        for (idx, p) in self.params.iter().enumerate() {
176            if idx > 0 {
177                write!(f, ";")?;
178            }
179            write!(f, "{}", p)?;
180        }
181        for b in &self.intermediates {
182            f.write_char(*b as char)?;
183        }
184        f.write_char(self.byte as char)?;
185        for b in &self.data {
186            f.write_char(*b as char)?;
187        }
188        write!(f, "\x1b\\")
189    }
190}
191
192#[derive(Clone, PartialEq, Eq)]
193pub struct EnterDeviceControlMode {
194    /// The final byte in the DCS mode
195    pub byte: u8,
196    pub params: Vec<i64>,
197    pub intermediates: Vec<u8>,
198    /// if true, more than two intermediates arrived and the
199    /// remaining data was ignored
200    pub ignored_extra_intermediates: bool,
201}
202
203impl core::fmt::Debug for EnterDeviceControlMode {
204    fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
205        write!(
206            fmt,
207            "EnterDeviceControlMode(params: {:?}, intermediates: [",
208            &self.params
209        )?;
210        for b in &self.intermediates {
211            write!(fmt, "{:?} 0x{:x}, ", *b as char, *b)?;
212        }
213        write!(
214            fmt,
215            "], byte: {:?} 0x{:x}, ignored_extra_intermediates={})",
216            self.byte as char, self.byte, self.ignored_extra_intermediates
217        )
218    }
219}
220
221#[derive(Clone, PartialEq, Eq)]
222pub enum DeviceControlMode {
223    /// Identify device control mode from the encoded parameters.
224    /// This mode is activated and must remain active until
225    /// `Exit` is observed.  While the mode is
226    /// active, data is made available to the device mode via
227    /// the `Data` variant.
228    Enter(Box<EnterDeviceControlMode>),
229    /// Exit the current device control mode
230    Exit,
231    /// Data for the device mode to consume
232    Data(u8),
233    /// A self contained (Enter, Data*, Exit) sequence
234    ShortDeviceControl(Box<ShortDeviceControl>),
235    /// Tmux parsed events
236    #[cfg(feature = "tmux_cc")]
237    TmuxEvents(Box<Vec<Event>>),
238}
239
240impl Display for DeviceControlMode {
241    fn fmt(&self, f: &mut Formatter) -> FmtResult {
242        match self {
243            Self::Enter(mode) => {
244                write!(f, "\x1bP")?;
245                for (idx, p) in mode.params.iter().enumerate() {
246                    if idx > 0 {
247                        write!(f, ";")?;
248                    }
249                    write!(f, "{}", p)?;
250                }
251                for b in &mode.intermediates {
252                    f.write_char(*b as char)?;
253                }
254                f.write_char(mode.byte as char)
255            }
256            // We don't need to emit a sequence for the Exit, as we're
257            // followed by eg: StringTerminator
258            Self::Exit => Ok(()),
259            Self::Data(c) => f.write_char(*c as char),
260            Self::ShortDeviceControl(s) => s.fmt(f),
261            #[cfg(feature = "tmux_cc")]
262            Self::TmuxEvents(_) => write!(f, "tmux event"),
263        }
264    }
265}
266
267impl core::fmt::Debug for DeviceControlMode {
268    fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
269        match self {
270            Self::Enter(mode) => write!(fmt, "Enter({:?})", mode),
271            Self::Exit => write!(fmt, "Exit"),
272            Self::Data(b) => write!(fmt, "Data({:?} 0x{:x})", *b as char, *b),
273            Self::ShortDeviceControl(s) => write!(fmt, "ShortDeviceControl({:?})", s),
274            #[cfg(feature = "tmux_cc")]
275            Self::TmuxEvents(_) => write!(fmt, "tmux event"),
276        }
277    }
278}
279
280/// See <https://vt100.net/docs/vt3xx-gp/chapter14.html>
281#[derive(Debug, Clone, PartialEq, Eq)]
282pub struct Sixel {
283    /// Specifies the numerator for the pixel aspect ratio
284    pub pan: i64,
285
286    /// Specifies the denominator for the pixel aspect ratio
287    pub pad: i64,
288
289    /// How wide the image is, in pixels
290    pub pixel_width: Option<u32>,
291
292    /// How tall the image is, in pixels,
293    pub pixel_height: Option<u32>,
294
295    /// When true, pixels with 0 value are left at their
296    /// present color, otherwise, they are set to the background
297    /// color.
298    pub background_is_transparent: bool,
299
300    /// The horizontal spacing between pixels
301    pub horizontal_grid_size: Option<i64>,
302
303    /// The sixel data
304    pub data: Vec<SixelData>,
305}
306
307impl Sixel {
308    /// Returns the width, height of the image
309    pub fn dimensions(&self) -> (u32, u32) {
310        if let (Some(w), Some(h)) = (self.pixel_width, self.pixel_height) {
311            return (w, h);
312        }
313
314        // Compute it by evaluating the sixel data
315        let mut max_x = 0;
316        let mut max_y = 0;
317        let mut x: u32 = 0;
318        let mut rows: u32 = 1;
319
320        for d in &self.data {
321            match d {
322                SixelData::Data(_) => {
323                    max_y = max_y.max(rows * 6);
324                    x = x.saturating_add(1);
325                    max_x = max_x.max(x);
326                }
327                SixelData::Repeat { repeat_count, .. } => {
328                    max_y = max_y.max(rows * 6);
329                    x = x.saturating_add(*repeat_count);
330                    max_x = max_x.max(x);
331                }
332                SixelData::SelectColorMapEntry(_)
333                | SixelData::DefineColorMapRGB { .. }
334                | SixelData::DefineColorMapHSL { .. } => {}
335                SixelData::NewLine => {
336                    max_x = max_x.max(x);
337                    x = 0;
338                    rows = rows.saturating_add(1);
339                }
340                SixelData::CarriageReturn => {
341                    max_x = max_x.max(x);
342                    x = 0;
343                }
344            }
345        }
346
347        (max_x, max_y)
348    }
349}
350
351impl Display for Sixel {
352    fn fmt(&self, f: &mut Formatter) -> FmtResult {
353        if self.pixel_width.is_some() {
354            write!(
355                f,
356                "\x1bP;{}{}q\"{};{};{};{}",
357                if self.background_is_transparent { 1 } else { 0 },
358                match self.horizontal_grid_size {
359                    Some(h) => format!(";{}", h),
360                    None => "".to_string(),
361                },
362                self.pan,
363                self.pad,
364                self.pixel_width.unwrap_or(0),
365                self.pixel_height.unwrap_or(0)
366            )?;
367        } else {
368            write!(
369                f,
370                "\x1bP{};{}{}q",
371                match (self.pan, self.pad) {
372                    (2, 1) => 0,
373                    (5, 1) => 2,
374                    (3, 1) => 3,
375                    (1, 1) => 7,
376                    _ => {
377                        log::error!("bad pad/pan combo: {:?}", self);
378                        return Err(core::fmt::Error);
379                    }
380                },
381                if self.background_is_transparent { 1 } else { 0 },
382                match self.horizontal_grid_size {
383                    Some(h) => format!(";{}", h),
384                    None => "".to_string(),
385                },
386            )?;
387        }
388        for d in &self.data {
389            d.fmt(f)?;
390        }
391        // The sixel data itself doesn't contain the ST
392        // write!(f, "\x1b\\")?;
393        Ok(())
394    }
395}
396
397/// A decoded 6-bit sixel value.
398/// Each sixel represents a six-pixel tall bitmap where
399/// the least significant bit is the topmost bit.
400pub type SixelValue = u8;
401
402#[derive(Debug, Clone, PartialEq, Eq)]
403pub enum SixelData {
404    /// A single sixel value
405    Data(SixelValue),
406
407    /// Run-length encoding; allows repeating a sixel value
408    /// the specified number of times
409    Repeat { repeat_count: u32, data: SixelValue },
410
411    /// Set the specified color map entry to the specified
412    /// linear RGB color value
413    DefineColorMapRGB {
414        color_number: u16,
415        rgb: crate::color::RgbColor,
416    },
417
418    DefineColorMapHSL {
419        color_number: u16,
420        /// 0 to 360 degrees
421        hue_angle: u16,
422        /// 0 to 100
423        lightness: u8,
424        /// 0 to 100
425        saturation: u8,
426    },
427
428    /// Select the numbered color from the color map entry
429    SelectColorMapEntry(u16),
430
431    /// Move the x position to the left page border of the
432    /// current sixel line.
433    CarriageReturn,
434
435    /// Move the x position to the left page border and
436    /// the y position down to the next sixel line.
437    NewLine,
438}
439
440impl Display for SixelData {
441    fn fmt(&self, f: &mut Formatter) -> FmtResult {
442        match self {
443            Self::Data(value) => write!(f, "{}", (value + 0x3f) as char),
444            Self::Repeat { repeat_count, data } => {
445                write!(f, "!{}{}", repeat_count, (data + 0x3f) as char)
446            }
447            Self::DefineColorMapRGB { color_number, rgb } => {
448                let LinearRgba(r, g, b, _) = rgb.to_linear_tuple_rgba();
449                write!(
450                    f,
451                    "#{};2;{};{};{}",
452                    color_number,
453                    (r * 100.) as u8,
454                    (g * 100.) as u8,
455                    (b * 100.0) as u8
456                )
457            }
458            Self::DefineColorMapHSL {
459                color_number,
460                hue_angle,
461                lightness,
462                saturation,
463            } => write!(
464                f,
465                "#{};1;{};{};{}",
466                color_number, hue_angle, lightness, saturation
467            ),
468            Self::SelectColorMapEntry(n) => write!(f, "#{}", n),
469            Self::CarriageReturn => write!(f, "$"),
470            Self::NewLine => write!(f, "-"),
471        }
472    }
473}
474
475/// C0 or C1 control codes
476#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive)]
477#[repr(u8)]
478pub enum ControlCode {
479    Null = 0,
480    StartOfHeading = 1,
481    StartOfText = 2,
482    EndOfText = 3,
483    EndOfTransmission = 4,
484    Enquiry = 5,
485    Acknowledge = 6,
486    Bell = 7,
487    Backspace = 8,
488    HorizontalTab = b'\t',
489    LineFeed = b'\n',
490    VerticalTab = 0xb,
491    FormFeed = 0xc,
492    CarriageReturn = b'\r',
493    ShiftOut = 0xe,
494    ShiftIn = 0xf,
495    DataLinkEscape = 0x10,
496    DeviceControlOne = 0x11,
497    DeviceControlTwo = 0x12,
498    DeviceControlThree = 0x13,
499    DeviceControlFour = 0x14,
500    NegativeAcknowledge = 0x15,
501    SynchronousIdle = 0x16,
502    EndOfTransmissionBlock = 0x17,
503    Cancel = 0x18,
504    EndOfMedium = 0x19,
505    Substitute = 0x1a,
506    Escape = 0x1b,
507    FileSeparator = 0x1c,
508    GroupSeparator = 0x1d,
509    RecordSeparator = 0x1e,
510    UnitSeparator = 0x1f,
511
512    // C1 8-bit values
513    BPH = 0x82,
514    NBH = 0x83,
515    IND = 0x84,
516    NEL = 0x85,
517    SSA = 0x86,
518    ESA = 0x87,
519    HTS = 0x88,
520    HTJ = 0x89,
521    VTS = 0x8a,
522    PLD = 0x8b,
523    PLU = 0x8c,
524    RI = 0x8d,
525    SS2 = 0x8e,
526    SS3 = 0x8f,
527    DCS = 0x90,
528    PU1 = 0x91,
529    PU2 = 0x92,
530    STS = 0x93,
531    CCH = 0x94,
532    MW = 0x95,
533    SPA = 0x96,
534    EPA = 0x97,
535    SOS = 0x98,
536    SCI = 0x9a,
537    CSI = 0x9b,
538    ST = 0x9c,
539    OSC = 0x9d,
540    PM = 0x9e,
541    APC = 0x9f,
542}
543
544/// A helper type to avoid accidentally tripping over problems with
545/// 1-based values in escape sequences.
546#[derive(Debug, Clone, Copy, PartialEq, Eq)]
547pub struct OneBased {
548    value: u32,
549}
550
551impl OneBased {
552    pub fn new(value: u32) -> Self {
553        debug_assert!(
554            value != 0,
555            "programmer error: deliberately assigning zero to a OneBased"
556        );
557        Self { value }
558    }
559
560    pub fn from_zero_based(value: u32) -> Self {
561        Self { value: value + 1 }
562    }
563
564    /// Map a value from an escape sequence parameter.
565    /// 0 is equivalent to 1
566    pub fn from_esc_param(v: &CsiParam) -> core::result::Result<Self, ()> {
567        match v {
568            CsiParam::Integer(v) if *v == 0 => Ok(Self {
569                value: num_traits::one(),
570            }),
571            CsiParam::Integer(v) if *v > 0 && *v <= i64::from(u32::max_value()) => {
572                Ok(Self { value: *v as u32 })
573            }
574            _ => Err(()),
575        }
576    }
577
578    /// Map a value from an escape sequence parameter.
579    /// 0 is equivalent to max_value.
580    pub fn from_esc_param_with_big_default(v: &CsiParam) -> core::result::Result<Self, ()> {
581        match v {
582            CsiParam::Integer(v) if *v == 0 => Ok(Self {
583                value: u32::max_value(),
584            }),
585            CsiParam::Integer(v) if *v > 0 && *v <= i64::from(u32::max_value()) => {
586                Ok(Self { value: *v as u32 })
587            }
588            _ => Err(()),
589        }
590    }
591
592    /// Map a value from an optional escape sequence parameter
593    pub fn from_optional_esc_param(o: Option<&CsiParam>) -> core::result::Result<Self, ()> {
594        Self::from_esc_param(o.unwrap_or(&CsiParam::Integer(1)))
595    }
596
597    /// Return the underlying value as a 0-based value
598    pub fn as_zero_based(self) -> u32 {
599        self.value.saturating_sub(1)
600    }
601
602    pub fn as_one_based(self) -> u32 {
603        self.value
604    }
605}
606
607impl Display for OneBased {
608    fn fmt(&self, f: &mut Formatter) -> FmtResult {
609        self.value.fmt(f)
610    }
611}