Browse Source

terminal: fix Synchronized Output response parsed as input

Since 2af5c8b6fd225b0f58a4f9cf7876a6302d13c618 ("terminal: add
QuerySynchronizedOutputSupport WIP") we send a synchronised output
support query [1] to the terminal but we don't parse the response. If the
terminal replies, it gets parsed as user input. And if the user happens
to press a navigation key such as scroll_down which supports a number
modifier, the `2026` part of the mode response which was parsed as user
input makes meli scroll down 2026 lines.

Fixes: #502 ("Initial navigation state in mailbox index view is wrong")

[1]:
<https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036>

Resolves: <https://git.meli-email.org/meli/meli/issues/502>
Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
Manos Pitsidianakis 8 months ago
parent
commit
c375b48ebf
1 changed files with 110 additions and 0 deletions
  1. 110 0
      meli/src/terminal/keys.rs

+ 110 - 0
meli/src/terminal/keys.rs

@@ -209,11 +209,55 @@ impl PartialEq<Key> for &Key {
     }
 }
 
+/// Setting mode value in ANSI or DEC report sequences.
+///
+/// See <https://vt100.net/docs/vt510-rm/DECRPM.html>.
+#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
+#[repr(u8)]
+enum ANSIDECModeSetting {
+    #[default]
+    ModeNotRecognized = 0,
+    Set = 1,
+    Reset = 2,
+    PermanentlySet = 3,
+    PermanentlyReset = 4,
+}
+
+/// Report Mode, Terminal to Host.
+///
+/// See <https://vt100.net/docs/vt510-rm/DECRPM.html>.
+///
+/// Format is:
+///
+/// ```text
+/// CSI ? Pd ; Ps $ y
+/// ```
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+enum DECRPMReport {
+    WaitingForSemicolon {
+        mode: u16,
+    },
+    Semicolon {
+        mode: u16,
+    },
+    WaitingForDollar {
+        mode: u16,
+        setting: ANSIDECModeSetting,
+    },
+    WaitingForEnd {
+        mode: u16,
+        setting: ANSIDECModeSetting,
+    },
+}
+
 #[derive(Debug, Eq, PartialEq)]
 /// Keep track of whether we're accepting normal user input or a pasted string.
 enum InputMode {
     Normal,
     EscapeSequence(Vec<u8>),
+    #[allow(clippy::upper_case_acronyms)]
+    /// Report Mode, Terminal to Host.
+    DECRPM(DECRPMReport),
     Paste(Vec<u8>),
 }
 
@@ -319,6 +363,72 @@ pub fn get_events(
                                 closure((Key::Mouse(mev.into()), bytes));
                                 continue 'poll_while;
                                 }
+                            (Ok((TEvent::Unsupported(ref k,), _)), InputMode::Normal) if k.as_slice() == [27, 91, 63] => {
+                                // DECRPM - Report Mode - Terminal To Host
+                                esc_seq_buf.clear();
+                                input_mode = InputMode::DECRPM(DECRPMReport::WaitingForSemicolon { mode: 0});
+                            }
+                            (Ok((TEvent::Key(TKey::Char(k)), _)), InputMode::DECRPM(ref report_state)) => {
+                                // CSI ? Pd ; Ps $ y
+                                match (k, report_state) {
+                                    (d, DECRPMReport::WaitingForSemicolon { mode }) if d.is_ascii_digit() => {
+                                        let mut mode = *mode;
+                                        mode *= 10;
+                                        // SAFETY: we performed an char::is_ascii_digit() check in
+                                        // the guard above.
+                                        mode += (d as u8 - b'0') as u16;
+                                        input_mode = InputMode::DECRPM(DECRPMReport::WaitingForSemicolon { mode });
+                                    },
+                                    (';', DECRPMReport::WaitingForSemicolon { mode }) => {
+                                        input_mode = InputMode::DECRPM(DECRPMReport::Semicolon { mode: *mode });
+                                    },
+                                    (other, DECRPMReport::WaitingForSemicolon { mode }) => {
+                                        log::trace!("Received invalid DECRPM response: Was waiting for an ASCII digit or `;` after `Pd` argument (mode, whose value was currently {mode:?} but instead got character {other:?}");
+                                        // Revert to normal input mode, to prevent locking
+                                        // up the user's terminal input
+                                        input_mode = InputMode::Normal;
+                                    }
+                                    (d, DECRPMReport::Semicolon { mode }) if d.is_ascii_digit() => {
+                                        let setting = match d {
+                                            '0' => ANSIDECModeSetting::ModeNotRecognized,
+                                            '1' => ANSIDECModeSetting::Set,
+                                            '2' => ANSIDECModeSetting::Reset,
+                                            '3' => ANSIDECModeSetting::PermanentlySet,
+                                            '4' => ANSIDECModeSetting::PermanentlyReset,
+                                            other => {
+                                                log::trace!("Received invalid DECRPM setting value: {:?}: expected one of {{0, 1, 2, 3, 4}}", other);
+                                                ANSIDECModeSetting::default()
+                                            }
+                                        };
+                                        input_mode = InputMode::DECRPM(DECRPMReport::WaitingForDollar { mode: *mode, setting });
+                                    },
+                                    (other, DECRPMReport::Semicolon { ref mode }) => {
+                                        log::trace!("Received invalid DECRPM response: Was waiting for an ASCII digit reporting setting value (`Ps` argument), for mode {mode:?} but instead got character {other:?}");
+                                        // Revert to normal input mode, to prevent locking
+                                        // up the user's terminal input
+                                        input_mode = InputMode::Normal;
+                                    }
+                                    ('$', DECRPMReport::WaitingForDollar { mode, setting }) => {
+                                        input_mode = InputMode::DECRPM(DECRPMReport::WaitingForEnd { mode: *mode, setting: *setting });
+                                    },
+                                    (other, DECRPMReport::WaitingForDollar { mode, setting }) => {
+                                        log::trace!("Received invalid DECRPM response: Was waiting for an ASCII `$` character (`Pm` argument was {mode:?} and `Ps` argument was {setting:?}) but instead got character {other:?}");
+                                        // Revert to normal input mode, to prevent locking
+                                        // up the user's terminal input
+                                        input_mode = InputMode::Normal;
+                                    }
+                                    (c, DECRPMReport::WaitingForEnd { mode, setting }) => {
+                                        if c != 'y' {
+                                            log::trace!("Received invalid DECRPM response: Was waiting for an ASCII `y` character (`Pm` argument was {mode:?} and `Ps` argument was {setting:?}) but instead got character {c:?}");
+                                        } else {
+                                            log::trace!("Got an DECRPM Terminal mode report: Mode {mode:?} is set to {setting:?}");
+                                        }
+                                        // end of report sequence.
+                                        input_mode = InputMode::Normal;
+                                    },
+
+                                }
+                            }
                             other => {
                                 log::trace!("get_events other = {:?}", other);
                                 continue 'poll_while;