| 1 | /// Concatenate string literals while prepending a ANSI control sequence introducer (`"\x1b["`)
|
| 2 | #[macro_export ]
|
| 3 | #[doc (hidden)]
|
| 4 | macro_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)]
|
| 12 | macro_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 ]
|
| 66 | macro_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 ]
|
| 117 | macro_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 ]
|
| 131 | macro_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 ]
|
| 143 | macro_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)]
|
| 154 | mod 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 | }
|
| 379 | |