1 | /// Append a the first few characters of an ANSI escape code to the given string.
|
2 | #[macro_export ]
|
3 | #[doc (hidden)]
|
4 | macro_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 ]
|
58 | macro_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 ]
|
109 | macro_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 ]
|
123 | macro_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 ]
|
135 | macro_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)]
|
146 | mod tests {
|
147 | use std::io;
|
148 | use std::str;
|
149 |
|
150 | // Helper for execute tests to confirm flush
|
151 | #[derive (Default, Debug, Clone)]
|
152 | pub(self) 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 | }
|
371 | |