1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
use core::sync::atomic::{AtomicBool, Ordering};

use crate::Channel;
use alloc::vec::Vec;
use sync_channel::{new_channel, Receiver, Sender};
use core2::io::Result;
use sync_block::Mutex;

// FIXME: Ctrl+C, Ctrl+Z, etc.

/// A TTY line discipline.
///
/// The line discipline can be configured based on what application is using the
/// slave end. Most applications should use the [`sane`](Self::sane) setting,
/// which handles line editing and echoing to the terminal. Applications that
/// require more control over the display should use the [`raw`](Self::raw)
/// setting.
///
/// The line discipline's behaviour is documented in terms of Linux `termios`
/// flags. For more information, visit the [`cfmakeraw`
/// documentation][cfmakeraw].
///
/// The line discipline prepends a carriage return to all line feeds on output.
/// This behaviour is equivalent to `ONLCR` on Linux.
///
/// [cfmakeraw]: https://linux.die.net/man/3/cfmakeraw
pub struct LineDiscipline {
    echo: AtomicBool,
    /// The input buffer for canonical mode.
    ///
    /// If `None`, canonical mode is disabled
    canonical: Mutex<Option<Vec<u8>>>,
    manager: Sender<Event>,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Event {
    CtrlC,
    CtrlD,
    CtrlZ,
}

impl Default for LineDiscipline {
    /// Equivalent to [`Self::new`].
    fn default() -> Self {
        Self::new()
    }
}

impl LineDiscipline {
    /// Creates a new line discipline with sane defaults.
    pub fn new() -> Self {
        let (sender, _) = new_channel(16);
        Self {
            echo: AtomicBool::new(true),
            canonical: Mutex::new(Some(Vec::new())),
            manager: sender,
        }
    }

    /// Resets the line discipline to sane defaults.
    ///
    /// This is equivalent to:
    /// ```rust
    /// # let discipline = tty::LineDiscipline::default();
    /// discipline.set_echo(true);
    /// discipline.set_canonical(true);
    /// ```
    pub fn set_sane(&self) {
        self.set_echo(true);
        self.set_canonical(true);
    }

    /// Sets the line discipline to raw mode.
    ///
    /// This is equivalent to:
    /// ```rust
    /// # let discipline = tty::LineDiscipline::default();
    /// discipline.set_echo(false);
    /// discipline.set_canonical(false);
    /// ```
    pub fn set_raw(&self) {
        self.set_echo(false);
        self.set_canonical(false);
    }

    pub fn echo(&self) -> bool {
        self.echo.load(Ordering::SeqCst)
    }

    /// Sets the echo flag.
    ///
    /// This is equivalent to `ECHO | ECHOE | ECHOCTL` on Linux.
    pub fn set_echo(&self, echo: bool) {
        self.echo.store(echo, Ordering::SeqCst);
    }

    /// Sets the line discipline to canonical mode.
    ///
    /// This is equivalent to `ICANON | ICRNL` on Linux.
    pub fn canonical(&self) -> bool {
        self.canonical.lock().is_some()
    }

    pub fn event_receiver(&self) -> Receiver<Event> {
        self.manager.receiver()
    }

    pub fn clear_events(&self) {
        let receiver = self.manager.receiver();
        while receiver.try_receive().is_ok() {}
    }

    /// Sets the canonical flag.
    ///
    /// This is equivalent to `ICANON` on Linux.
    pub fn set_canonical(&self, canonical: bool) {
        if canonical {
            *self.canonical.lock() = Some(Vec::new());
        } else {
            // TODO: Flush buffer?
            *self.canonical.lock() = None;
        }
    }

    fn clear_input_buf(&self, canonical: Option<&mut Vec<u8>>) {
        if let Some(input_buf) = canonical {
            input_buf.clear();
        }
    }

    pub(crate) fn process_input_byte(
        &self,
        byte: u8,
        master: &Channel,
        slave: &Channel,
    ) -> Result<()> {
        let mut canonical = self.canonical.lock();
        self.process_input_byte_internal(byte, master, slave, (*canonical).as_mut())
    }

    fn process_input_byte_internal(
        &self,
        byte: u8,
        master: &Channel,
        slave: &Channel,
        canonical: Option<&mut Vec<u8>>,
    ) -> Result<()> {
        const ERASE: u8 = 0x7f; // DEL (backspace key)
        const WERASE: u8 = 0x17; // ^W

        const INTERRUPT: u8 = 0x3;
        const SUSPEND: u8 = 0x1a;

        match byte {
            INTERRUPT => {
                let _ = self.manager.send(Event::CtrlC);
                self.clear_input_buf(canonical);
                return Ok(());
            }
            SUSPEND => {
                let _ = self.manager.send(Event::CtrlZ);
                self.clear_input_buf(canonical);
                return Ok(());
            }
            _ => {}
        }

        // TODO: EOF and EOL
        // TODO: UTF-8?
        if self.echo.load(Ordering::SeqCst) {
            match (byte, &canonical) {
                // TODO: Also pass-through START and STOP characters
                (b'\t' | b'\n', _) => {
                    master.send(byte)?;
                }
                (ERASE, Some(input_buf)) => {
                    if !input_buf.is_empty() {
                        master.send_all([0x8, b' ', 0x8])?
                    }
                }
                (WERASE, Some(input_buf)) => {
                    if !input_buf.is_empty() {
                        // TODO: Cache offset. Currently we're calculating it twice: once here,
                        // and once if canonical mode is enabled.
                        let offset = werase(input_buf);
                        for _ in 0..offset {
                            master.send_all([0x8, b' ', 0x8])?;
                        }
                    }
                }
                (0..=0x1f, _) => {
                    master.send_all([b'^', byte + 0x40])?;
                }
                _ => {
                    master.send(byte)?;
                }
            }
        }

        if let Some(input_buf) = canonical {
            match byte {
                b'\r' | b'\n' => {
                    slave.send_all(core::mem::take(input_buf))?;
                    slave.send(b'\n')?;
                }
                ERASE => {
                    input_buf.pop();
                }
                WERASE => {
                    for _ in 0..werase(input_buf) {
                        input_buf.pop();
                    }
                }
                _ => input_buf.push(byte),
            }
        } else {
            slave.send(byte)?;
        }
        Ok(())
    }

    pub(crate) fn process_input_buf(
        &self,
        buf: &[u8],
        master: &Channel,
        slave: &Channel,
    ) -> Result<()> {
        let mut canonical = self.canonical.lock();
        for byte in buf {
            self.process_input_byte_internal(*byte, master, slave, (*canonical).as_mut())?;
        }
        Ok(())
    }

    pub(crate) fn process_output_byte(&self, byte: u8, master: &Channel) -> Result<()> {
        if byte == b'\n' {
            master.send_all([b'\r', b'\n'])
        } else {
            master.send(byte)
        }
    }

    pub(crate) fn process_output_buf(&self, buf: &[u8], master: &Channel) -> Result<()> {
        for byte in buf {
            self.process_output_byte(*byte, master)?;
        }
        Ok(())
    }
}

/// Returns how many characters need to be removed to erase a word.
const fn werase(buf: &[u8]) -> usize {
    let len = buf.len();
    let mut offset = 0;

    let mut initial_whitespace = true;

    // TODO: Tabs?

    loop {
        if offset == len {
            return offset;
        }

        offset += 1;

        if initial_whitespace {
            if buf[len - offset] != b' ' {
                initial_whitespace = false;
            }
        } else if buf[len - offset] == b' ' {
            return offset - 1;
        }
    }
}