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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
//! Commands used internally by termit, also available for more precise control

use log::trace;
#[cfg(all(windows, feature = "sys"))]
use windows_sys::Win32::System::Console::{COORD, SMALL_RECT};

use crate::output::ansi::CSI;
use crate::{prelude::Point, sys::Control};
use std::fmt::Debug;
use std::io;

/// Who executes terminal commands?
pub trait TerminalCommander {
    fn send<T: TerminalRequest>(&mut self, request: T) -> io::Result<()>;
}

impl<C: Control, W: io::Write> TerminalCommander for (C, W) {
    fn send<T: TerminalRequest>(&mut self, request: T) -> io::Result<()> {
        trace!("executing terminal request {request:?}");

        let (ref control, ref mut write) = *self;

        let mut ansi_tried = false;

        if control.is_ansi() {
            if request.apply(write)? {
                // happy ansi path
                return Ok(());
            }
            // it is probably not an ansi command
            ansi_tried = true
        }

        let (ref control, ref mut write) = *self;

        // we're going to run ioctl/winapi call which is immediate, flush first
        write.flush()?;

        if request.control(control)? {
            Ok(())
        } else if !ansi_tried && request.apply(write)? {
            Ok(())
        } else {
            Err(io::Error::new(
                io::ErrorKind::Other,
                format!("invalid request {request:?}"),
            ))
        }
    }
}

/// Request is a nicer word for command
pub trait TerminalRequest: Debug {
    /// The command is sent via output byte stream, ANSI TTY way.
    fn apply(&self, _write: impl io::Write) -> io::Result<bool> {
        Ok(false)
    }
    /// The commander will call this method if the control is not explicitly an ANSI terminal.
    /// This will be the case on legacy windows and in the absence of sys.
    /// The command should make use of the control handles to impart the desired action.
    /// If it doesn't have such a solution, it shall return `None` and let the
    /// commander call `apply` instead. We split the methods like this
    /// because control actions are immediate while ANSI sequences are buffered.
    /// We flush the output when control is to be invoked.
    /// TODO: return `Option<closure>` instead to make this more efficient on non-sys.
    fn control(&self, _control: &impl Control) -> io::Result<bool> {
        Ok(false)
    }
}

/// A command that switches the terminal to 'raw' mode,
/// causing it to send unbuffered/unprocessed input among others.
///
/// # Notes
/// * Commands must be sent for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EnterRawMode;
impl TerminalRequest for EnterRawMode {
    fn control(&self, _control: &impl Control) -> io::Result<bool> {
        _control.enable_raw_mode().map(|()| true)
    }
}

/// A command that forces buffered output to flush.
///
/// # Notes
/// * Commands must be sent for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Flush;
impl TerminalRequest for Flush {
    fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
        write.flush().map(|()| true)
    }
}

/// A command that moves the terminal cursor relative to the current position (column, row).
///
/// # Notes
/// * 0,0 is a no-op
/// * Commands must be sent for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MoveCursorBy(pub i16, pub i16);
impl TerminalRequest for MoveCursorBy {
    /// Moves the cursor n (default 1) cells in the given direction. If the cursor is already at the edge of the screen, this has no effect.
    /// CSI n A	CUU	Cursor Up
    /// CSI n B	CUD	Cursor Down
    /// CSI n C	CUF	Cursor Forward
    /// CSI n D	CUB	Cursor Back
    fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
        let Self(x, y) = *self;
        let mut dimension = |x, p, n| {
            if x > 0 {
                write.write_all(format!("{CSI}{x}{p}").as_bytes())
            } else if x < 0 {
                let x = i16::abs(x);
                write.write_all(format!("{CSI}{x}{n}").as_bytes())
            } else {
                Ok(())
            }
        };

        dimension(x, 'C', 'D')?;
        dimension(y, 'B', 'A')?;
        Ok(true)
    }

    fn control(&self, _control: &impl Control) -> io::Result<bool> {
        #[cfg(all(windows, feature = "sys"))]
        return {
            let Self(x, y) = *self;

            let info =
                crate::sys::console::output::get_console_screen_buffer_info(_control.output())?;
            let COORD {
                X: curr_x,
                Y: curr_y,
            } = info.dwCursorPosition;
            let COORD {
                X: width,
                Y: height,
            } = info.dwSize;
            let SMALL_RECT {
                Left: left,
                Top: top,
                Right: right,
                Bottom: bottom,
            } = info.srWindow;

            let x = curr_x
                .saturating_add(x)
                .clamp(left, width.saturating_sub(right).saturating_sub(1))
                as u16;
            let y = curr_y
                .saturating_add(y)
                .clamp(top, height.saturating_sub(bottom).saturating_sub(1))
                as u16;
            crate::sys::console::output::set_console_cursot_position(_control.output(), x, y)?;
            Ok(true)
        };
        #[allow(unreachable_code)]
        Ok(false)
    }
}

/// A command that moves the terminal cursor to the given position (column, row).
///
/// # Notes
/// * Top left cell is represented as `0,0`.
/// * Commands must be sent for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MoveCursorTo(pub Point);
impl TerminalRequest for MoveCursorTo {
    fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
        let Self(Point { mut col, mut row }) = self;
        col += 1;
        row += 1;
        write
            .write_all(format!("{CSI}{row};{col}H").as_bytes())
            .map(|()| true)
    }

    fn control(&self, _control: &impl Control) -> io::Result<bool> {
        #[cfg(all(windows, feature = "sys"))]
        return crate::sys::console::output::set_console_cursot_position(
            _control.output(),
            self.0.col,
            self.0.row,
        )
        .map(|()| true);
        #[allow(unreachable_code)]
        Ok(false)
    }
}
/// Reset terminal style to the initial
///
/// # Notes
/// * Commands must be sent for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResetStyles;
impl TerminalRequest for ResetStyles {
    fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
        write
            .write_all(format!("{CSI}0m").as_bytes())
            .map(|()| true)
    }

    fn control(&self, _control: &impl Control) -> io::Result<bool> {
        #[cfg(all(windows, feature = "sys"))]
        return crate::sys::console::output::set_console_text_attribute(
            _control.output(),
            _control.get_initial_style(),
        )
        .map(|()| true);
        #[allow(unreachable_code)]
        Ok(false)
    }
}
/// Clear the screen currently in view
///
/// # Notes
/// * Commands must be sent for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClearScreen;
impl TerminalRequest for ClearScreen {
    fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
        write
            .write_all(format!("{CSI}2J").as_bytes())
            .map(|()| true)
    }

    fn control(&self, _control: &impl Control) -> io::Result<bool> {
        #[cfg(all(windows, feature = "sys"))]
        return {
            let info =
                crate::sys::console::output::get_console_screen_buffer_info(_control.output())?;
            //let pos = info.dwCursorPosition;
            let size = info.dwSize;
            let style = info.wAttributes;
            let start_location = COORD { X: 0, Y: 0 };
            let cells_to_write = size.X as u32 * size.Y as u32;
            crate::sys::console::output::fill_with_character(
                _control.output(),
                start_location,
                cells_to_write,
                ' ',
            )?;
            crate::sys::console::output::fill_with_attribute(
                _control.output(),
                start_location,
                cells_to_write,
                style,
            )?;
            Ok(true)
        };
        #[allow(unreachable_code)]
        Ok(false)
    }
}

/// Switch to an alternate screen or back.
///
/// # Notes
/// * Commands must be sent for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UseAlternateScreen(pub bool);

impl TerminalRequest for UseAlternateScreen {
    fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
        let act = if self.0 { 'h' } else { 'l' };
        write
            .write_all(format!("{CSI}?1049{act}").as_bytes())
            .map(|()| true)
    }
    fn control(&self, _control: &impl Control) -> io::Result<bool> {
        #[cfg(all(windows, feature = "sys"))]
        return _control.use_alternate_screen(self.0).map(|()| true);
        #[allow(unreachable_code)]
        Ok(false)
    }
}
/// Show or hide the cursor pointer/block/underline
///
/// # Notes
/// * Commands must be sent for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ShowCursor(pub bool);

impl TerminalRequest for ShowCursor {
    fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
        let act = if self.0 { 'h' } else { 'l' };
        write
            .write_all(format!("{CSI}?25{act}").as_bytes())
            .map(|()| true)
    }
    fn control(&self, _control: &impl Control) -> io::Result<bool> {
        #[cfg(all(windows, feature = "sys"))]
        return crate::sys::console::output::show_cursor(_control.output(), self.0).map(|()| true);
        #[allow(unreachable_code)]
        Ok(false)
    }
}
/// Enable or disable line wrap at the end of the visible line
///
/// # Notes
/// * Commands must be sent for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WrapLines(pub bool);

impl TerminalRequest for WrapLines {
    fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
        let act = if self.0 { 'h' } else { 'l' };
        write
            .write_all(format!("{CSI}?7{act}").as_bytes())
            .map(|()| true)
    }
    fn control(&self, _control: &impl Control) -> io::Result<bool> {
        #[cfg(all(windows, feature = "sys"))]
        return crate::sys::console::output::wrap_lines(_control.output(), self.0).map(|()| true);
        #[allow(unreachable_code)]
        Ok(false)
    }
}

/// Make the terminal sen (or not) the mouse events
///
/// # Notes
/// * Commands must be sent for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CaptureMouse(pub bool);

impl TerminalRequest for CaptureMouse {
    fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
        let act = if self.0 { 'h' } else { 'l' };
        let mut modes = [
            1000, // Normal tracking: Send mouse X & Y on button press and release
            1002, // Button-event tracking: Report button motion events (dragging)
            1003, // Any-event tracking: Report all motion events
            1015, // RXVT mouse mode: Allows mouse coordinates of >223
            1006, // SGR mouse mode: Allows mouse coordinates of >223, preferred over RXVT mode
        ];

        if !self.0 {
            modes.reverse();
        }

        for mode in modes {
            write.write_all(format!("{CSI}?{mode}{act}").as_bytes())?
        }
        Ok(true)
    }
    fn control(&self, _control: &impl Control) -> io::Result<bool> {
        #[cfg(all(windows, feature = "sys"))]
        return crate::sys::console::input::enable_mouse(_control.output(), self.0).map(|()| true);
        #[allow(unreachable_code)]
        Ok(false)
    }
}

/// Make the terminal send (or not) the window focus change events
///
/// # Notes
/// * Commands must be sent for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CaptureFocus(pub bool);

impl TerminalRequest for CaptureFocus {
    fn apply(&self, mut write: impl io::Write) -> io::Result<bool> {
        let act = if self.0 { 'h' } else { 'l' };
        write.write_all(format!("{CSI}?1004{act}").as_bytes())?;
        Ok(true)
    }
    fn control(&self, _control: &impl Control) -> io::Result<bool> {
        #[cfg(all(windows, feature = "sys"))]
        return Ok(true); // always on on wincon
        #[allow(unreachable_code)]
        Ok(false)
    }
}