crossterm/
macros.rs

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