crossterm/
macros.rs

1/// Append a the first few characters of an ANSI escape code to the given string.
2#[macro_export]
3#[doc(hidden)]
4macro_rules! csi {
5    ($( $l:expr ),*) => { concat!("\x1B[", $( $l ),*) };
6}
7
8/// Queues one or more command(s) for further execution.
9///
10/// Queued commands must be flushed to the underlying device to be executed.
11/// This generally happens in the following cases:
12///
13/// * When `flush` is called manually on the given type implementing `io::Write`.
14/// * The terminal will `flush` automatically if the buffer is full.
15/// * Each line is flushed in case of `stdout`, because it is line buffered.
16///
17/// # Arguments
18///
19/// - [std::io::Writer](std::io::Write)
20///
21///     ANSI escape codes are written on the given 'writer', after which they are flushed.
22///
23/// - [Command](./trait.Command.html)
24///
25///     One or more commands
26///
27/// # Examples
28///
29/// ```rust
30/// use std::io::{Write, stdout};
31/// use crossterm::{queue, style::Print};
32///
33/// let mut stdout = stdout();
34///
35/// // `Print` will executed executed when `flush` is called.
36/// queue!(stdout, Print("foo".to_string()));
37///
38/// // some other code (no execution happening here) ...
39///
40/// // when calling `flush` on `stdout`, all commands will be written to the stdout and therefore executed.
41/// stdout.flush();
42///
43/// // ==== Output ====
44/// // foo
45/// ```
46///
47/// Have a look over at the [Command API](./index.html#command-api) for more details.
48///
49/// # Notes
50///
51/// In case of Windows versions lower than 10, a direct WinAPI call will be made.
52/// The reason for this is that Windows versions lower than 10 do not support ANSI codes,
53/// and can therefore not be written to the given `writer`.
54/// Therefore, there is no difference between [execute](macro.execute.html)
55/// and [queue](macro.queue.html) for those old Windows versions.
56///
57#[macro_export]
58macro_rules! queue {
59    ($writer:expr $(, $command:expr)* $(,)?) => {{
60        use ::std::io::Write;
61
62        // This allows the macro to take both mut impl Write and &mut impl Write.
63        Ok($writer.by_ref())
64            $(.and_then(|writer| $crate::QueueableCommand::queue(writer, $command)))*
65            .map(|_| ())
66    }}
67}
68
69/// Executes one or more command(s).
70///
71/// # Arguments
72///
73/// - [std::io::Writer](std::io::Write)
74///
75///     ANSI escape codes are written on the given 'writer', after which they are flushed.
76///
77/// - [Command](./trait.Command.html)
78///
79///     One or more commands
80///
81/// # Examples
82///
83/// ```rust
84/// use std::io::{Write, stdout};
85/// use crossterm::{execute, style::Print};
86///
87/// // will be executed directly
88/// execute!(stdout(), Print("sum:\n".to_string()));
89///
90/// // will be executed directly
91/// execute!(stdout(), Print("1 + 1 = ".to_string()), Print((1+1).to_string()));
92///
93/// // ==== Output ====
94/// // sum:
95/// // 1 + 1 = 2
96/// ```
97///
98/// Have a look over at the [Command API](./index.html#command-api) for more details.
99///
100/// # Notes
101///
102/// * In the case of UNIX and Windows 10, ANSI codes are written to the given 'writer'.
103/// * In case of Windows versions lower than 10, a direct WinAPI call will be made.
104///     The reason for this is that Windows versions lower than 10 do not support ANSI codes,
105///     and can therefore not be written to the given `writer`.
106///     Therefore, there is no difference between [execute](macro.execute.html)
107///     and [queue](macro.queue.html) for those old Windows versions.
108#[macro_export]
109macro_rules! execute {
110    ($writer:expr $(, $command:expr)* $(,)? ) => {{
111        use ::std::io::Write;
112
113        // Queue each command, then flush
114        $crate::queue!($writer $(, $command)*)
115            .and_then(|()| {
116                ::std::io::Write::flush($writer.by_ref())
117            })
118    }}
119}
120
121#[doc(hidden)]
122#[macro_export]
123macro_rules! impl_display {
124    (for $($t:ty),+) => {
125        $(impl ::std::fmt::Display for $t {
126            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
127                $crate::command::execute_fmt(f, self)
128            }
129        })*
130    }
131}
132
133#[doc(hidden)]
134#[macro_export]
135macro_rules! impl_from {
136    ($from:path, $to:expr) => {
137        impl From<$from> for ErrorKind {
138            fn from(e: $from) -> Self {
139                $to(e)
140            }
141        }
142    };
143}
144
145#[cfg(test)]
146mod tests {
147    use std::io;
148    use std::str;
149
150    // Helper for execute tests to confirm flush
151    #[derive(Default, Debug, Clone)]
152    struct FakeWrite {
153        buffer: String,
154        flushed: bool,
155    }
156
157    impl io::Write for FakeWrite {
158        fn write(&mut self, content: &[u8]) -> io::Result<usize> {
159            let content = str::from_utf8(content)
160                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
161            self.buffer.push_str(content);
162            self.flushed = false;
163            Ok(content.len())
164        }
165
166        fn flush(&mut self) -> io::Result<()> {
167            self.flushed = true;
168            Ok(())
169        }
170    }
171
172    #[cfg(not(windows))]
173    mod unix {
174        use std::fmt;
175
176        use super::FakeWrite;
177        use crate::command::Command;
178
179        pub struct FakeCommand;
180
181        impl Command for FakeCommand {
182            fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
183                f.write_str("cmd")
184            }
185        }
186
187        #[test]
188        fn test_queue_one() {
189            let mut result = FakeWrite::default();
190            queue!(&mut result, FakeCommand).unwrap();
191            assert_eq!(&result.buffer, "cmd");
192            assert!(!result.flushed);
193        }
194
195        #[test]
196        fn test_queue_many() {
197            let mut result = FakeWrite::default();
198            queue!(&mut result, FakeCommand, FakeCommand).unwrap();
199            assert_eq!(&result.buffer, "cmdcmd");
200            assert!(!result.flushed);
201        }
202
203        #[test]
204        fn test_queue_trailing_comma() {
205            let mut result = FakeWrite::default();
206            queue!(&mut result, FakeCommand, FakeCommand,).unwrap();
207            assert_eq!(&result.buffer, "cmdcmd");
208            assert!(!result.flushed);
209        }
210
211        #[test]
212        fn test_execute_one() {
213            let mut result = FakeWrite::default();
214            execute!(&mut result, FakeCommand).unwrap();
215            assert_eq!(&result.buffer, "cmd");
216            assert!(result.flushed);
217        }
218
219        #[test]
220        fn test_execute_many() {
221            let mut result = FakeWrite::default();
222            execute!(&mut result, FakeCommand, FakeCommand).unwrap();
223            assert_eq!(&result.buffer, "cmdcmd");
224            assert!(result.flushed);
225        }
226
227        #[test]
228        fn test_execute_trailing_comma() {
229            let mut result = FakeWrite::default();
230            execute!(&mut result, FakeCommand, FakeCommand,).unwrap();
231            assert_eq!(&result.buffer, "cmdcmd");
232            assert!(result.flushed);
233        }
234    }
235
236    #[cfg(windows)]
237    mod windows {
238        use std::fmt;
239
240        use std::cell::RefCell;
241
242        use super::FakeWrite;
243        use crate::command::Command;
244
245        // We need to test two different APIs: WinAPI and the write api. We
246        // don't know until runtime which we're supporting (via
247        // Command::is_ansi_code_supported), so we have to test them both. The
248        // CI environment hopefully includes both versions of windows.
249
250        // WindowsEventStream is a place for execute_winapi to push strings,
251        // when called.
252        type WindowsEventStream = Vec<&'static str>;
253
254        struct FakeCommand<'a> {
255            // Need to use a refcell because we want execute_winapi to be able
256            // push to the vector, but execute_winapi take &self.
257            stream: RefCell<&'a mut WindowsEventStream>,
258            value: &'static str,
259        }
260
261        impl<'a> FakeCommand<'a> {
262            fn new(stream: &'a mut WindowsEventStream, value: &'static str) -> Self {
263                Self {
264                    value,
265                    stream: RefCell::new(stream),
266                }
267            }
268        }
269
270        impl<'a> Command for FakeCommand<'a> {
271            fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
272                f.write_str(self.value)
273            }
274
275            fn execute_winapi(&self) -> std::io::Result<()> {
276                self.stream.borrow_mut().push(self.value);
277                Ok(())
278            }
279        }
280
281        // Helper function for running tests against either WinAPI or an
282        // io::Write.
283        //
284        // This function will execute the `test` function, which should
285        // queue some commands against the given FakeWrite and
286        // WindowsEventStream. It will then test that the correct data sink
287        // was populated. It does not currently check is_ansi_code_supported;
288        // for now it simply checks that one of the two streams was correctly
289        // populated.
290        //
291        // If the stream was populated, it tests that the two arrays are equal.
292        // If the writer was populated, it tests that the contents of the
293        // write buffer are equal to the concatenation of `stream_result`.
294        fn test_harness(
295            stream_result: &[&'static str],
296            test: impl FnOnce(&mut FakeWrite, &mut WindowsEventStream) -> std::io::Result<()>,
297        ) {
298            let mut stream = WindowsEventStream::default();
299            let mut writer = FakeWrite::default();
300
301            if let Err(err) = test(&mut writer, &mut stream) {
302                panic!("Error returned from test function: {:?}", err);
303            }
304
305            // We need this for type inference, for whatever reason.
306            const EMPTY_RESULT: [&str; 0] = [];
307
308            // TODO: confirm that the correct sink was used, based on
309            // is_ansi_code_supported
310            match (writer.buffer.is_empty(), stream.is_empty()) {
311                (true, true) if stream_result == EMPTY_RESULT => {}
312                (true, true) => panic!(
313                    "Neither the event stream nor the writer were populated. Expected {:?}",
314                    stream_result
315                ),
316
317                // writer is populated
318                (false, true) => {
319                    // Concat the stream result to find the string result
320                    let result: String = stream_result.iter().copied().collect();
321                    assert_eq!(result, writer.buffer);
322                    assert_eq!(&stream, &EMPTY_RESULT);
323                }
324
325                // stream is populated
326                (true, false) => {
327                    assert_eq!(stream, stream_result);
328                    assert_eq!(writer.buffer, "");
329                }
330
331                // Both are populated
332                (false, false) => panic!(
333                    "Both the writer and the event stream were written to.\n\
334                     Only one should be used, based on is_ansi_code_supported.\n\
335                     stream: {stream:?}\n\
336                     writer: {writer:?}",
337                    stream = stream,
338                    writer = writer,
339                ),
340            }
341        }
342
343        #[test]
344        fn test_queue_one() {
345            test_harness(&["cmd1"], |writer, stream| {
346                queue!(writer, FakeCommand::new(stream, "cmd1"))
347            })
348        }
349
350        #[test]
351        fn test_queue_some() {
352            test_harness(&["cmd1", "cmd2"], |writer, stream| {
353                queue!(
354                    writer,
355                    FakeCommand::new(stream, "cmd1"),
356                    FakeCommand::new(stream, "cmd2"),
357                )
358            })
359        }
360
361        #[test]
362        fn test_many_queues() {
363            test_harness(&["cmd1", "cmd2", "cmd3"], |writer, stream| {
364                queue!(writer, FakeCommand::new(stream, "cmd1"))?;
365                queue!(writer, FakeCommand::new(stream, "cmd2"))?;
366                queue!(writer, FakeCommand::new(stream, "cmd3"))
367            })
368        }
369    }
370}