crossterm/
terminal.rs

1//! # Terminal
2//!
3//! The `terminal` module provides functionality to work with the terminal.
4//!
5//! This documentation does not contain a lot of examples. The reason is that it's fairly
6//! obvious how to use this crate. Although, we do provide
7//! [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) repository
8//! to demonstrate the capabilities.
9//!
10//! Most terminal actions can be performed with commands.
11//! Please have a look at [command documentation](../index.html#command-api) for a more detailed documentation.
12//!
13//! ## Screen Buffer
14//!
15//! A screen buffer is a two-dimensional array of character
16//! and color data which is displayed in a terminal screen.
17//!
18//! The terminal has several of those buffers and is able to switch between them.
19//! The default screen in which you work is called the 'main screen'.
20//! The other screens are called the 'alternative screen'.
21//!
22//! It is important to understand that crossterm does not yet support creating screens,
23//! or switch between more than two buffers, and only offers the ability to change
24//! between the 'alternate' and 'main screen'.
25//!
26//! ### Alternate Screen
27//!
28//! By default, you will be working on the main screen.
29//! There is also another screen called the 'alternative' screen.
30//! This screen is slightly different from the main screen.
31//! For example, it has the exact dimensions of the terminal window,
32//! without any scroll-back area.
33//!
34//! Crossterm offers the possibility to switch to the 'alternative' screen,
35//! make some modifications, and move back to the 'main' screen again.
36//! The main screen will stay intact and will have the original data as we performed all
37//! operations on the alternative screen.
38//!
39//! An good example of this is Vim.
40//! When it is launched from bash, a whole new buffer is used to modify a file.
41//! Then, when the modification is finished, it closes again and continues on the main screen.
42//!
43//! ### Raw Mode
44//!
45//! By default, the terminal functions in a certain way.
46//! For example, it will move the cursor to the beginning of the next line when the input hits the end of a line.
47//! Or that the backspace is interpreted for character removal.
48//!
49//! Sometimes these default modes are irrelevant,
50//! and in this case, we can turn them off.
51//! This is what happens when you enable raw modes.
52//!
53//! Those modes will be set when enabling raw modes:
54//!
55//! - Input will not be forwarded to screen
56//! - Input will not be processed on enter press
57//! - Input will not be line buffered (input sent byte-by-byte to input buffer)
58//! - Special keys like backspace and CTRL+C will not be processed by terminal driver
59//! - New line character will not be processed therefore `println!` can't be used, use `write!` instead
60//!
61//! Raw mode can be enabled/disabled with the [enable_raw_mode](terminal::enable_raw_mode) and [disable_raw_mode](terminal::disable_raw_mode) functions.
62//!
63//! ## Examples
64//!
65//! ```no_run
66//! use std::io::{stdout, Write};
67//! use crossterm::{execute, Result, terminal::{ScrollUp, SetSize, size}};
68//!
69//! fn main() -> Result<()> {
70//!     let (cols, rows) = size()?;
71//!     // Resize terminal and scroll up.
72//!     execute!(
73//!         stdout(),
74//!         SetSize(10, 10),
75//!         ScrollUp(5)
76//!     )?;
77//!
78//!     // Be a good citizen, cleanup
79//!     execute!(stdout(), SetSize(cols, rows))?;
80//!     Ok(())
81//! }
82//! ```
83//!
84//! For manual execution control check out [crossterm::queue](../macro.queue.html).
85
86use std::fmt;
87
88#[cfg(windows)]
89use crossterm_winapi::{ConsoleMode, Handle, ScreenBuffer};
90#[cfg(feature = "serde")]
91use serde::{Deserialize, Serialize};
92#[cfg(windows)]
93use winapi::um::wincon::ENABLE_WRAP_AT_EOL_OUTPUT;
94
95#[doc(no_inline)]
96use crate::Command;
97use crate::{csi, impl_display, Result};
98
99pub(crate) mod sys;
100
101/// Tells whether the raw mode is enabled.
102///
103/// Please have a look at the [raw mode](./index.html#raw-mode) section.
104pub fn is_raw_mode_enabled() -> Result<bool> {
105    #[cfg(unix)]
106    {
107        Ok(sys::is_raw_mode_enabled())
108    }
109
110    #[cfg(windows)]
111    {
112        sys::is_raw_mode_enabled()
113    }
114}
115
116/// Enables raw mode.
117///
118/// Please have a look at the [raw mode](./index.html#raw-mode) section.
119pub fn enable_raw_mode() -> Result<()> {
120    sys::enable_raw_mode()
121}
122
123/// Disables raw mode.
124///
125/// Please have a look at the [raw mode](./index.html#raw-mode) section.
126pub fn disable_raw_mode() -> Result<()> {
127    sys::disable_raw_mode()
128}
129
130/// Returns the terminal size `(columns, rows)`.
131///
132/// The top left cell is represented `(1, 1)`.
133pub fn size() -> Result<(u16, u16)> {
134    sys::size()
135}
136
137/// Disables line wrapping.
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139pub struct DisableLineWrap;
140
141impl Command for DisableLineWrap {
142    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
143        f.write_str(csi!("?7l"))
144    }
145
146    #[cfg(windows)]
147    fn execute_winapi(&self) -> Result<()> {
148        let screen_buffer = ScreenBuffer::current()?;
149        let console_mode = ConsoleMode::from(screen_buffer.handle().clone());
150        let new_mode = console_mode.mode()? & !ENABLE_WRAP_AT_EOL_OUTPUT;
151        console_mode.set_mode(new_mode)?;
152        Ok(())
153    }
154}
155
156/// Enable line wrapping.
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub struct EnableLineWrap;
159
160impl Command for EnableLineWrap {
161    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
162        f.write_str(csi!("?7h"))
163    }
164
165    #[cfg(windows)]
166    fn execute_winapi(&self) -> Result<()> {
167        let screen_buffer = ScreenBuffer::current()?;
168        let console_mode = ConsoleMode::from(screen_buffer.handle().clone());
169        let new_mode = console_mode.mode()? | ENABLE_WRAP_AT_EOL_OUTPUT;
170        console_mode.set_mode(new_mode)?;
171        Ok(())
172    }
173}
174
175/// A command that switches to alternate screen.
176///
177/// # Notes
178///
179/// * Commands must be executed/queued for execution otherwise they do nothing.
180/// * Use [LeaveAlternateScreen](./struct.LeaveAlternateScreen.html) command to leave the entered alternate screen.
181///
182/// # Examples
183///
184/// ```no_run
185/// use std::io::{stdout, Write};
186/// use crossterm::{execute, Result, terminal::{EnterAlternateScreen, LeaveAlternateScreen}};
187///
188/// fn main() -> Result<()> {
189///     execute!(stdout(), EnterAlternateScreen)?;
190///
191///     // Do anything on the alternate screen
192///
193///     execute!(stdout(), LeaveAlternateScreen)
194/// }
195/// ```
196///
197#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198pub struct EnterAlternateScreen;
199
200impl Command for EnterAlternateScreen {
201    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
202        f.write_str(csi!("?1049h"))
203    }
204
205    #[cfg(windows)]
206    fn execute_winapi(&self) -> Result<()> {
207        let alternate_screen = ScreenBuffer::create()?;
208        alternate_screen.show()?;
209        Ok(())
210    }
211}
212
213/// A command that switches back to the main screen.
214///
215/// # Notes
216///
217/// * Commands must be executed/queued for execution otherwise they do nothing.
218/// * Use [EnterAlternateScreen](./struct.EnterAlternateScreen.html) to enter the alternate screen.
219///
220/// # Examples
221///
222/// ```no_run
223/// use std::io::{stdout, Write};
224/// use crossterm::{execute, Result, terminal::{EnterAlternateScreen, LeaveAlternateScreen}};
225///
226/// fn main() -> Result<()> {
227///     execute!(stdout(), EnterAlternateScreen)?;
228///
229///     // Do anything on the alternate screen
230///
231///     execute!(stdout(), LeaveAlternateScreen)
232/// }
233/// ```
234///
235#[derive(Debug, Clone, Copy, PartialEq, Eq)]
236pub struct LeaveAlternateScreen;
237
238impl Command for LeaveAlternateScreen {
239    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
240        f.write_str(csi!("?1049l"))
241    }
242
243    #[cfg(windows)]
244    fn execute_winapi(&self) -> Result<()> {
245        let screen_buffer = ScreenBuffer::from(Handle::current_out_handle()?);
246        screen_buffer.show()?;
247        Ok(())
248    }
249}
250
251/// Different ways to clear the terminal buffer.
252#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
253#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
254pub enum ClearType {
255    /// All cells.
256    All,
257    /// All plus history
258    Purge,
259    /// All cells from the cursor position downwards.
260    FromCursorDown,
261    /// All cells from the cursor position upwards.
262    FromCursorUp,
263    /// All cells at the cursor row.
264    CurrentLine,
265    /// All cells from the cursor position until the new line.
266    UntilNewLine,
267}
268
269/// A command that scrolls the terminal screen a given number of rows up.
270///
271/// # Notes
272///
273/// Commands must be executed/queued for execution otherwise they do nothing.
274#[derive(Debug, Clone, Copy, PartialEq, Eq)]
275pub struct ScrollUp(pub u16);
276
277impl Command for ScrollUp {
278    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
279        if self.0 != 0 {
280            write!(f, csi!("{}S"), self.0)?;
281        }
282        Ok(())
283    }
284
285    #[cfg(windows)]
286    fn execute_winapi(&self) -> Result<()> {
287        sys::scroll_up(self.0)
288    }
289}
290
291/// A command that scrolls the terminal screen a given number of rows down.
292///
293/// # Notes
294///
295/// Commands must be executed/queued for execution otherwise they do nothing.
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
297pub struct ScrollDown(pub u16);
298
299impl Command for ScrollDown {
300    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
301        if self.0 != 0 {
302            write!(f, csi!("{}T"), self.0)?;
303        }
304        Ok(())
305    }
306
307    #[cfg(windows)]
308    fn execute_winapi(&self) -> Result<()> {
309        sys::scroll_down(self.0)
310    }
311}
312
313/// A command that clears the terminal screen buffer.
314///
315/// See the [`ClearType`](enum.ClearType.html) enum.
316///
317/// # Notes
318///
319/// Commands must be executed/queued for execution otherwise they do nothing.
320#[derive(Debug, Clone, Copy, PartialEq, Eq)]
321pub struct Clear(pub ClearType);
322
323impl Command for Clear {
324    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
325        f.write_str(match self.0 {
326            ClearType::All => csi!("2J"),
327            ClearType::Purge => csi!("3J"),
328            ClearType::FromCursorDown => csi!("J"),
329            ClearType::FromCursorUp => csi!("1J"),
330            ClearType::CurrentLine => csi!("2K"),
331            ClearType::UntilNewLine => csi!("K"),
332        })
333    }
334
335    #[cfg(windows)]
336    fn execute_winapi(&self) -> Result<()> {
337        sys::clear(self.0)
338    }
339}
340
341/// A command that sets the terminal buffer size `(columns, rows)`.
342///
343/// # Notes
344///
345/// Commands must be executed/queued for execution otherwise they do nothing.
346#[derive(Debug, Clone, Copy, PartialEq, Eq)]
347pub struct SetSize(pub u16, pub u16);
348
349impl Command for SetSize {
350    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
351        write!(f, csi!("8;{};{}t"), self.1, self.0)
352    }
353
354    #[cfg(windows)]
355    fn execute_winapi(&self) -> Result<()> {
356        sys::set_size(self.0, self.1)
357    }
358}
359
360/// A command that sets the terminal title
361///
362/// # Notes
363///
364/// Commands must be executed/queued for execution otherwise they do nothing.
365#[derive(Debug, Clone, Copy, PartialEq, Eq)]
366pub struct SetTitle<T>(pub T);
367
368impl<T: fmt::Display> Command for SetTitle<T> {
369    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
370        write!(f, "\x1B]0;{}\x07", &self.0)
371    }
372
373    #[cfg(windows)]
374    fn execute_winapi(&self) -> Result<()> {
375        sys::set_window_title(&self.0)
376    }
377}
378
379impl_display!(for ScrollUp);
380impl_display!(for ScrollDown);
381impl_display!(for SetSize);
382impl_display!(for Clear);
383
384#[cfg(test)]
385mod tests {
386    use std::{io::stdout, thread, time};
387
388    use crate::execute;
389
390    use super::*;
391
392    // Test is disabled, because it's failing on Travis CI
393    #[test]
394    #[ignore]
395    fn test_resize_ansi() {
396        let (width, height) = size().unwrap();
397
398        execute!(stdout(), SetSize(35, 35)).unwrap();
399
400        // see issue: https://github.com/eminence/terminal-size/issues/11
401        thread::sleep(time::Duration::from_millis(30));
402
403        assert_eq!((35, 35), size().unwrap());
404
405        // reset to previous size
406        execute!(stdout(), SetSize(width, height)).unwrap();
407
408        // see issue: https://github.com/eminence/terminal-size/issues/11
409        thread::sleep(time::Duration::from_millis(30));
410
411        assert_eq!((width, height), size().unwrap());
412    }
413
414    #[test]
415    fn test_raw_mode() {
416        // check we start from normal mode (may fail on some test harnesses)
417        assert!(!is_raw_mode_enabled().unwrap());
418
419        // enable the raw mode
420        if enable_raw_mode().is_err() {
421            // Enabling raw mode doesn't work on the ci
422            // So we just ignore it
423            return;
424        }
425
426        // check it worked (on unix it doesn't really check the underlying
427        // tty but rather check that the code is consistent)
428        assert!(is_raw_mode_enabled().unwrap());
429
430        // enable it again, this should not change anything
431        enable_raw_mode().unwrap();
432
433        // check we're still in raw mode
434        assert!(is_raw_mode_enabled().unwrap());
435
436        // now let's disable it
437        disable_raw_mode().unwrap();
438
439        // check we're back to normal mode
440        assert!(!is_raw_mode_enabled().unwrap());
441    }
442}