Refactored Screen Module (#336)

This commit is contained in:
Timon 2019-12-04 17:40:11 +01:00 committed by GitHub
parent dec0d74b32
commit b4241e0107
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 342 additions and 657 deletions

View File

@ -8,6 +8,11 @@
- Replace `docs/UPGRADE.md` with the [Upgrade Paths](https://github.com/crossterm-rs/crossterm/wiki#upgrade-paths)
documentation
- Add `MoveToColumn`, `MoveToPreviousLine`, `MoveToNextLine` commands
- Merge `screen` module into `terminal`
- Remove `screen::AlternateScreen`
- Remove `screen::Rawscreen`
* Move and rename `Rawscreen::into_raw_mode` and `Rawscreen::disable_raw_mode` to `terminal::enable_raw_mode` and `terminal::disable_raw_mode`
- Move `screen::EnterAlternateScreen` and `screen::LeaveAlternateScreen` to `terminal::EnterAlternateScreen` and `terminal::LeaveAlternateScreen`
- Replace `utils::Output` command with `style::Print` command
- Fix enable/disable mouse capture commands on Windows.

View File

@ -1,14 +1,16 @@
//
// cargo run --example event-poll-read
//
use std::io::{stdout, Write};
use std::time::Duration;
use std::{
io::{stdout, Write},
time::Duration,
};
use crossterm::{
cursor::position,
event::{poll, read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
execute,
screen::RawScreen,
terminal::{disable_raw_mode, enable_raw_mode},
Result,
};
@ -47,7 +49,7 @@ fn print_events() -> Result<()> {
fn main() -> Result<()> {
println!("{}", HELP);
let _r = RawScreen::into_raw_mode()?;
enable_raw_mode();
let mut stdout = stdout();
execute!(stdout, EnableMouseCapture)?;
@ -57,5 +59,6 @@ fn main() -> Result<()> {
}
execute!(stdout, DisableMouseCapture)?;
Ok(())
disable_raw_mode()
}

View File

@ -7,7 +7,7 @@ use crossterm::{
cursor::position,
event::{read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
execute,
screen::RawScreen,
terminal::{disable_raw_mode, enable_raw_mode},
Result,
};
@ -39,7 +39,7 @@ fn print_events() -> Result<()> {
fn main() -> Result<()> {
println!("{}", HELP);
let _r = RawScreen::into_raw_mode()?;
enable_raw_mode()?;
let mut stdout = stdout();
execute!(stdout, EnableMouseCapture)?;
@ -49,5 +49,6 @@ fn main() -> Result<()> {
}
execute!(stdout, DisableMouseCapture)?;
Ok(())
disable_raw_mode()
}

View File

@ -1,8 +1,10 @@
//
// cargo run --features event-stream --example event-stream-async-std
//
use std::io::{stdout, Write};
use std::time::Duration;
use std::{
io::{stdout, Write},
time::Duration,
};
use futures::{future::FutureExt, select, StreamExt};
use futures_timer::Delay;
@ -11,7 +13,7 @@ use crossterm::{
cursor::position,
event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream, KeyCode},
execute,
screen::RawScreen,
terminal::{disable_raw_mode, enable_raw_mode},
Result,
};
@ -55,7 +57,7 @@ async fn print_events() {
fn main() -> Result<()> {
println!("{}", HELP);
let _r = RawScreen::into_raw_mode()?;
enable_raw_mode()?;
let mut stdout = stdout();
execute!(stdout, EnableMouseCapture)?;
@ -63,5 +65,6 @@ fn main() -> Result<()> {
async_std::task::block_on(print_events());
execute!(stdout, DisableMouseCapture)?;
Ok(())
disable_raw_mode()
}

View File

@ -1,8 +1,10 @@
//
// cargo run --features event-stream --example event-stream-tokio
//
use std::io::{stdout, Write};
use std::time::Duration;
use std::{
io::{stdout, Write},
time::Duration,
};
use futures::{future::FutureExt, select, StreamExt};
use futures_timer::Delay;
@ -11,7 +13,7 @@ use crossterm::{
cursor::position,
event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream, KeyCode},
execute,
screen::RawScreen,
terminal::{disable_raw_mode, enable_raw_mode},
Result,
};
@ -56,7 +58,7 @@ async fn print_events() {
async fn main() -> Result<()> {
println!("{}", HELP);
let _r = RawScreen::into_raw_mode()?;
enable_raw_mode()?;
let mut stdout = stdout();
execute!(stdout, EnableMouseCapture)?;
@ -64,5 +66,6 @@ async fn main() -> Result<()> {
print_events().await;
execute!(stdout, DisableMouseCapture)?;
Ok(())
disable_raw_mode()
}

View File

@ -5,10 +5,8 @@ use std::{
use crate::{
event::{filter::CursorPositionFilter, poll_internal, read_internal, InternalEvent},
utils::{
sys::unix::{disable_raw_mode, enable_raw_mode, is_raw_mode_enabled},
Result,
},
terminal::{disable_raw_mode, enable_raw_mode, sys::is_raw_mode_enabled},
utils::Result,
};
/// Returns the cursor position (column, row).

View File

@ -1,11 +1,10 @@
use std::{collections::vec_deque::VecDeque, time::Duration};
use super::filter::Filter;
#[cfg(unix)]
use super::source::unix::UnixInternalEventSource;
#[cfg(windows)]
use super::source::windows::WindowsEventSource;
use super::{source::EventSource, timeout::PollTimeout, InternalEvent, Result};
use super::{filter::Filter, source::EventSource, timeout::PollTimeout, InternalEvent, Result};
/// Can be used to read `InternalEvent`s.
pub(crate) struct InternalEventReader {
@ -121,8 +120,7 @@ impl InternalEventReader {
#[cfg(test)]
mod tests {
use std::collections::VecDeque;
use std::time::Duration;
use std::{collections::VecDeque, time::Duration};
use crate::ErrorKind;
@ -130,7 +128,7 @@ mod tests {
use super::super::filter::CursorPositionFilter;
use super::{
super::{filter::InternalEventFilter, Event},
{EventSource, InternalEvent, InternalEventReader},
EventSource, InternalEvent, InternalEventReader,
};
#[test]

View File

@ -1,6 +1,5 @@
use std::collections::VecDeque;
use std::io;
use std::time::Duration;
use std::{io, time::Duration};
use mio::{unix::EventedFd, Events, Poll, PollOpt, Ready, Token};
use signal_hook::iterator::Signals;

View File

@ -2,8 +2,7 @@ use std::time::Duration;
use crossterm_winapi::{Console, Handle, InputEventType, KeyEventRecord, MouseEvent};
use crate::event::sys::windows::WinApiPoll;
use crate::event::Event;
use crate::event::{sys::windows::WinApiPoll, Event};
use super::super::{
source::EventSource,

View File

@ -1,27 +1,25 @@
//! This is a WINDOWS specific implementation for input related action.
use std::io;
use std::io::ErrorKind;
use std::sync::Mutex;
use std::time::Duration;
use std::{io, io::ErrorKind, sync::Mutex, time::Duration};
use crossterm_winapi::{
ConsoleMode, ControlKeyState, EventFlags, Handle, KeyEventRecord, MouseEvent, ScreenBuffer,
Semaphore,
};
use winapi::shared::winerror::WAIT_TIMEOUT;
use winapi::um::{
use winapi::{
shared::winerror::WAIT_TIMEOUT,
um::{
synchapi::WaitForMultipleObjects,
winbase::{INFINITE, WAIT_ABANDONED_0, WAIT_FAILED, WAIT_OBJECT_0},
};
use winapi::um::{
wincon::{
LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED, SHIFT_PRESSED,
LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED,
SHIFT_PRESSED,
},
winuser::{
VK_BACK, VK_CONTROL, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, VK_F24, VK_HOME,
VK_INSERT, VK_LEFT, VK_MENU, VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SHIFT, VK_UP,
},
},
};
use lazy_static::lazy_static;

View File

@ -51,9 +51,6 @@
//! - Module `event`
//! - Mouse events - [`EnableMouseCapture`](event/struct.EnableMouseCapture.html),
//! [`DisableMouseCapture`](event/struct.DisableMouseCapture.html)
//! - Module `screen`
//! - Alternate screen - [`EnterAlternateScreen`](screen/struct.EnterAlternateScreen.html),
//! [`LeaveAlternateScreen`](screen/struct.LeaveAlternateScreen.html)
//! - Module `style`
//! - Colors - [`SetForegroundColor`](style/struct.SetForegroundColor.html),
//! [`SetBackgroundColor`](style/struct.SetBackgroundColor.html),
@ -65,6 +62,8 @@
//! [`ScrollDown`](terminal/struct.ScrollDown.html)
//! - Miscellaneous - [`Clear`](terminal/struct.Clear.html),
//! [`SetSize`](terminal/struct.SetSize.html)
//! - Alternate screen - [`EnterAlternateScreen`](screen/struct.EnterAlternateScreen.html),
//! [`LeaveAlternateScreen`](screen/struct.LeaveAlternateScreen.html)
//!
//! ### Command Execution
//!
@ -234,8 +233,6 @@ pub use utils::{Command, ErrorKind, ExecutableCommand, QueueableCommand, Result}
pub mod cursor;
/// A module to read events.
pub mod event;
/// A module to work with the terminal screen and modes.
pub mod screen;
/// A module to apply attributes and colors on your text.
pub mod style;
/// A module to work with the terminal.

View File

@ -1,209 +0,0 @@
//! # Screen
//!
//! The `screen` module provides the functionality to work with the terminal screen.
//!
//! This documentation does not contain a lot of examples. The reason is that it's fairly
//! obvious how to use this crate. Although, we do provide
//! [examples](https://github.com/crossterm-rs/examples) repository
//! to demonstrate the capabilities.
//!
//! ## Screen Buffer
//!
//! A screen buffer is a two-dimensional array of characters and color data to be output in a console window.
//! A terminal can have multiple of those screen buffers, and the active screen buffer is the one that is
//! displayed on the screen.
//!
//! Crossterm allows you to switch between those buffers; the screen you are working in is called the
//! 'main screen'. We call the other screen the 'alternate screen'. One note to take is that crossterm
//! does not support the creation and switching between several buffers.
//!
//! ### Alternate Screen
//!
//! Normally you are working on the main screen but an alternate screen is somewhat different from a
//! normal screen. For example, it has the exact dimensions of the terminal window, without any
//! scroll back region. An example of this is vim when it is launched from bash.
//!
//! Vim uses the entirety of the screen to edit the file, then exits to bash leaving the original buffer unchanged.
//!
//! Crossterm provides the ability to switch to the alternate screen, make some changes, and then go back
//! to the main screen. The main screen will still have its original data since we made all the edits on
//! the alternate screen.
//!
//! ### Raw Mode
//!
//! By default, the terminal behaves in a certain way.
//! You can think of going to a new line if the input is at the end of the current line, or interpreting backspace
//! to remove letters. Sometimes it can be useful to disable these modes because this is undesirable.
//! This may be undesirable if your application wants to read the input without it being shown on the screen.
//! Raw modes are the modes to create this possibility.
//!
//! Those modes will be set when enabling raw modes:
//!
//! - Input will not be forwarded to screen
//! - Input will not be processed on enter press
//! - Input will not be line buffered (input sent byte-by-byte to input buffer)
//! - Special keys like backspace and CTL+C will not be processed by terminal driver
//! - New line character will not be processed therefore `println!` can't be used, use `write!` instead
// This brings the trait into scope, so we're able to call enter()/leave(),
// but it it's false positive for unused_imports check
#[allow(unused_imports)]
use alternate::AlternateScreen as _;
use crate::utils::{Command, Result};
pub use self::raw::{IntoRawMode, RawScreen};
mod alternate;
mod raw;
mod sys;
/// An alternate screen.
///
/// With this type, you will be able to switch to the alternate screen and then back to
/// the main screen.
///
/// Be aware that you'll be switched back to the main screen when you drop the
/// `AlternateScreen` value.
///
/// It's recommended to use the command API. See the
/// [`EnterAlternateScreen`](struct.EnterAlternateScreen.html)
/// and [`LeaveAlternateScreen`](struct.LeaveAlternateScreen.html)
/// commands documentation for more info.
///
/// # Examples
///
/// Alternate screen with raw mode enabled:
///
/// ```no_run
/// use crossterm::{screen::{AlternateScreen}, Result};
///
/// fn main() -> Result<()> {
/// let _alternate = AlternateScreen::to_alternate(true)?;
///
/// // Do something on the alternate screen in the raw mode
///
/// Ok(())
/// } // `_alternate` dropped here <- raw mode disabled & back to main screen
/// ```
pub struct AlternateScreen {
#[cfg(windows)]
alternate: Box<(dyn alternate::AlternateScreen + Sync + Send)>,
#[cfg(unix)]
alternate: alternate::AnsiAlternateScreen,
raw_screen: Option<RawScreen>,
}
impl AlternateScreen {
/// Switches to the alternate screen.
///
/// # Arguments
///
/// * `raw_mode` - `true` enables the raw mode as well
///
/// # Notes
///
/// You'll be automatically switched to the main screen if this function
/// fails.
#[allow(clippy::wrong_self_convention)]
pub fn to_alternate(raw_mode: bool) -> Result<AlternateScreen> {
let alternate = alternate::alternate_screen();
alternate.enter()?;
let mut alternate = AlternateScreen {
alternate,
raw_screen: None,
};
if raw_mode {
// If into_raw_mode fails, `alternate` will be dropped and
// we'll switch back to the main screen.
alternate.raw_screen = Some(RawScreen::into_raw_mode()?);
}
Ok(alternate)
}
/// Switches to the main screen.
pub fn to_main(&self) -> Result<()> {
self.alternate.leave()
}
}
impl Drop for AlternateScreen {
fn drop(&mut self) {
let _ = self.to_main();
}
}
/// A command to switch to the alternate screen.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
///
/// # Examples
///
/// ```no_run
/// use std::io::{stdout, Write};
/// use crossterm::{execute, Result,screen::{EnterAlternateScreen, LeaveAlternateScreen}};
///
/// fn main() -> Result<()> {
/// execute!(stdout(), EnterAlternateScreen)?;
///
/// // Do anything on the alternate screen
///
/// execute!(stdout(), LeaveAlternateScreen)
/// }
/// ```
pub struct EnterAlternateScreen;
impl Command for EnterAlternateScreen {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
alternate::ansi::ENTER_ALTERNATE_SCREEN_CSI_SEQUENCE
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
let alternate = alternate::alternate_screen();
alternate.enter()
}
}
/// A command to switch back to the main screen.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
///
/// # Examples
///
/// ```no_run
/// use std::io::{stdout, Write};
/// use crossterm::{execute, Result, screen::{EnterAlternateScreen, LeaveAlternateScreen}};
///
/// fn main() -> Result<()> {
/// execute!(stdout(), EnterAlternateScreen)?;
///
/// // Do anything on the alternate screen
///
/// execute!(stdout(), LeaveAlternateScreen)
/// }
/// ```
pub struct LeaveAlternateScreen;
impl Command for LeaveAlternateScreen {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
alternate::ansi::LEAVE_ALTERNATE_SCREEN_CSI_SEQUENCE
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
let alternate = alternate::alternate_screen();
alternate.leave()
}
}

View File

@ -1,30 +0,0 @@
pub(crate) use ansi::AnsiAlternateScreen;
#[cfg(windows)]
pub(crate) use windows::WinApiAlternateScreen;
#[cfg(windows)]
use crate::utils::functions::supports_ansi;
use crate::utils::Result;
pub(crate) mod ansi;
#[cfg(windows)]
pub(crate) mod windows;
pub(crate) trait AlternateScreen: Sync + Send {
fn enter(&self) -> Result<()>;
fn leave(&self) -> Result<()>;
}
#[cfg(windows)]
pub(crate) fn alternate_screen() -> Box<dyn AlternateScreen + Send + Sync> {
if supports_ansi() {
Box::new(AnsiAlternateScreen)
} else {
Box::new(WinApiAlternateScreen)
}
}
#[cfg(unix)]
pub(crate) fn alternate_screen() -> AnsiAlternateScreen {
AnsiAlternateScreen
}

View File

@ -1,26 +0,0 @@
use std::io::{stdout, Write};
use crate::{csi, utils::Result};
use super::AlternateScreen;
pub(crate) const ENTER_ALTERNATE_SCREEN_CSI_SEQUENCE: &str = csi!("?1049h");
pub(crate) const LEAVE_ALTERNATE_SCREEN_CSI_SEQUENCE: &str = csi!("?1049l");
pub(crate) struct AnsiAlternateScreen;
impl AlternateScreen for AnsiAlternateScreen {
fn enter(&self) -> Result<()> {
let mut stdout = stdout();
write!(stdout, "{}", ENTER_ALTERNATE_SCREEN_CSI_SEQUENCE)?;
stdout.flush()?;
Ok(())
}
fn leave(&self) -> Result<()> {
let mut stdout = stdout();
write!(stdout, "{}", LEAVE_ALTERNATE_SCREEN_CSI_SEQUENCE)?;
stdout.flush()?;
Ok(())
}
}

View File

@ -1,21 +0,0 @@
use crossterm_winapi::{Handle, ScreenBuffer};
use crate::utils::Result;
use super::AlternateScreen;
pub(crate) struct WinApiAlternateScreen;
impl AlternateScreen for WinApiAlternateScreen {
fn enter(&self) -> Result<()> {
let alternate_screen = ScreenBuffer::create();
alternate_screen.show()?;
Ok(())
}
fn leave(&self) -> Result<()> {
let screen_buffer = ScreenBuffer::from(Handle::output_handle()?);
screen_buffer.show()?;
Ok(())
}
}

View File

@ -1,128 +0,0 @@
use std::io::{Stdout, Write};
use crate::utils::Result;
use super::sys;
/// A raw screen.
///
/// Be aware that the raw mode is disabled when you drop the `RawScreen` value.
/// Call the [`keep_raw_mode_on_drop`](struct.RawScreen.html#method.keep_raw_mode_on_drop)
/// method to disable this behavior (keep the raw mode enabled).
///
/// # Examples
///
/// Basic usage:
///
/// ```no_run
/// use crossterm::{screen::RawScreen, Result};
///
/// fn main() -> Result<()> {
/// let _raw = RawScreen::into_raw_mode()?;
/// // Do something in the raw mode
/// Ok(())
/// } // `_raw` is dropped here <- raw mode is disabled
/// ```
///
/// Do not disable the raw mode implicitly:
///
/// ```no_run
/// use crossterm::{screen::RawScreen, Result};
///
/// fn main() -> Result<()> {
/// let mut raw = RawScreen::into_raw_mode()?;
/// raw.keep_raw_mode_on_drop();
/// // Feel free to leave `raw` on it's own/drop it, the raw
/// // mode won't be disabled
///
/// // Do something in the raw mode
///
/// // Disable raw mode explicitly
/// RawScreen::disable_raw_mode()
/// }
/// ```
pub struct RawScreen {
disable_raw_mode_on_drop: bool,
}
impl RawScreen {
// TODO enable_raw_mode() to keep it synced with enable/disable?
/// Enables raw mode.
pub fn into_raw_mode() -> Result<RawScreen> {
#[cfg(unix)]
let mut command = sys::unix::RawModeCommand::new();
#[cfg(windows)]
let mut command = sys::winapi::RawModeCommand::new();
command.enable()?;
Ok(RawScreen {
disable_raw_mode_on_drop: true,
})
}
/// Disables raw mode.
pub fn disable_raw_mode() -> Result<()> {
#[cfg(unix)]
let mut command = sys::unix::RawModeCommand::new();
#[cfg(windows)]
let command = sys::winapi::RawModeCommand::new();
command.disable()?;
Ok(())
}
/// Keeps the raw mode enabled when `self` is dropped.
///
/// See the [`RawScreen`](struct.RawScreen.html) documentation for more
/// information.
pub fn keep_raw_mode_on_drop(&mut self) {
self.disable_raw_mode_on_drop = false;
}
}
/// Allows to enable raw mode.
///
/// Why this type must be implemented on writers?
///
/// TTYs has their state controlled by the writer, not the reader. You use the writer to
/// clear the screen, move the cursor and so on, so naturally you use the writer to change
/// the mode as well.
///
/// # Examples
///
/// ```no_run
/// use std::io::stdout;
/// use crossterm::{screen::IntoRawMode, Result};
///
/// fn main() -> Result<()> {
/// let stdout = stdout();
/// let _raw = stdout.into_raw_mode()?;
///
/// // Do something in the raw mode
///
/// Ok(())
/// } // `_raw` dropped here <- raw mode disabled
/// ```
pub trait IntoRawMode: Write + Sized {
/// Enables raw mode.
fn into_raw_mode(self) -> Result<RawScreen>;
}
impl IntoRawMode for Stdout {
fn into_raw_mode(self) -> Result<RawScreen> {
RawScreen::into_raw_mode()?;
// this make's sure that raw screen will be disabled when it goes out of scope.
Ok(RawScreen {
disable_raw_mode_on_drop: true,
})
}
}
impl Drop for RawScreen {
fn drop(&mut self) {
if self.disable_raw_mode_on_drop {
let _ = RawScreen::disable_raw_mode();
}
}
}

View File

@ -1,5 +0,0 @@
#[cfg(unix)]
pub(crate) mod unix;
#[cfg(windows)]
pub(crate) mod winapi;

View File

@ -1,22 +0,0 @@
use crate::utils::Result;
/// This command is used for enabling and disabling raw mode for the terminal.
pub struct RawModeCommand;
impl RawModeCommand {
pub fn new() -> Self {
RawModeCommand
}
/// Enables raw mode.
pub fn enable(&mut self) -> Result<()> {
crate::utils::sys::unix::enable_raw_mode()?;
Ok(())
}
/// Disables raw mode.
pub fn disable(&mut self) -> Result<()> {
crate::utils::sys::unix::disable_raw_mode()?;
Ok(())
}
}

View File

@ -1,50 +0,0 @@
use crossterm_winapi::{ConsoleMode, Handle};
use winapi::{shared::minwindef::DWORD, um::wincon};
use crate::utils::Result;
use self::wincon::{ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT};
/// This command is used for enabling and disabling raw mode for Windows systems.
/// For more info check: https://docs.microsoft.com/en-us/windows/console/high-level-console-modes.
#[derive(Clone, Copy)]
pub struct RawModeCommand {
mask: DWORD,
}
impl RawModeCommand {
pub fn new() -> Self {
RawModeCommand {
mask: ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT,
}
}
}
impl RawModeCommand {
/// Enables raw mode.
pub fn enable(&mut self) -> Result<()> {
let console_mode = ConsoleMode::from(Handle::input_handle()?);
let dw_mode = console_mode.mode()?;
let new_mode = dw_mode & !self.mask;
console_mode.set_mode(new_mode)?;
Ok(())
}
/// Disables raw mode.
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn disable(&self) -> Result<()> {
let console_mode = ConsoleMode::from(Handle::input_handle()?);
let dw_mode = console_mode.mode()?;
let new_mode = dw_mode | self.mask;
console_mode.set_mode(new_mode)?;
Ok(())
}
}

View File

@ -7,9 +7,57 @@
//! [examples](https://github.com/crossterm-rs/examples) repository
//! to demonstrate the capabilities.
//!
//! Terminal actions can be performed with commands.
//! Most terminal actions can be performed with commands.
//! Please have a look at [command documention](../index.html#command-api) for a more detailed documentation.
//!
//! ## Screen Buffer
//!
//! A screen buffer is a two-dimensional array of character
//! and color data which is displayed in a terminal screen.
//!
//! The terminal has several of those buffers and is able to switch between them.
//! The default screen in which you work is called the 'main screen'.
//! The other screens are called the 'alternative screen'.
//!
//! It is important to understand that crossterm does not yet support creating screens,
//! or switch between more than two buffers, and only offers the ability to change
//! between the 'alternate' and 'main screen'.
//!
//! ### Alternate Screen
//!
//! By default, you will be working on the main screen.
//! There is also another screen called the 'alternative' screen.
//! This screen is slightly different from the main screen.
//! For example, it has the exact dimensions of the terminal window,
//! without any scroll-back area.
//!
//! Crossterm offers the possibility to switch to the 'alternative' screen,
//! make some modifications, and move back to the 'main' screen again.
//! The main screen will stay intact and will have the original data as we performed all
//! operations on the alternative screen.
//!
//! An good example of this is Vim.
//! When it is launched from bash, a whole new buffer is used to modify a file.
//! Then, when the modification is finished, it closes again and continues on the main screen.
//!
//! ### Raw Mode
//!
//! By default, the terminal functions in a certain way.
//! For example, it will move the cursor to the beginning of the next line when the input hits the end of a line.
//! Or that the backspace is interpreted for character removal.
//!
//! Sometimes these default modes are irrelevant,
//! and in this case, we can turn them off.
//! This is what happens when you enable raw modes.
//!
//! Those modes will be set when enabling raw modes:
//!
//! - Input will not be forwarded to screen
//! - Input will not be processed on enter press
//! - Input will not be line buffered (input sent byte-by-byte to input buffer)
//! - Special keys like backspace and CTL+C will not be processed by terminal driver
//! - New line character will not be processed therefore `println!` can't be used, use `write!` instead
//!
//! ## Examples
//!
//! ```no_run
@ -18,7 +66,7 @@
//!
//! fn main() -> Result<()> {
//! let (cols, rows) = size()?;
//! // Do something with the terminal
//! // Resize terminal and scroll up.
//! execute!(
//! stdout(),
//! SetSize(10, 10),
@ -33,21 +81,123 @@
//!
//! For manual execution control check out [crossterm::queue](../macro.queue.html).
#[cfg(windows)]
use crossterm_winapi::{Handle, ScreenBuffer};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub use sys::{exit, size};
use crate::impl_display;
#[doc(no_inline)]
use crate::utils::Command;
#[cfg(windows)]
use crate::utils::Result;
use crate::{impl_display, Result};
mod ansi;
mod sys;
pub(crate) mod sys;
/// Represents different options how to clear the terminal.
/// Enables raw mode.
///
/// Please have a look at the [raw mode](./#raw-mode) section.
pub fn enable_raw_mode() -> Result<()> {
sys::enable_raw_mode()
}
/// Disables raw mode.
///
/// Please have a look at the [raw mode](./#raw-mode) section.
pub fn disable_raw_mode() -> Result<()> {
sys::disable_raw_mode()
}
/// Exits the current application.
pub fn exit() {
sys::exit();
}
/// Returns the terminal size `(columns, rows)`.
///
/// The top left cell is represented `(1, 1)`.
pub fn size() -> Result<(u16, u16)> {
sys::size()
}
/// A command that switches to alternate screen.
///
/// # Notes
///
/// * Commands must be executed/queued for execution otherwise they do nothing.
/// * Use [LeaveAlternateScreen](./struct.LeaveAlternateScreen.html) command to leave the entered alternate screen.
///
/// # Examples
///
/// ```no_run
/// use std::io::{stdout, Write};
/// use crossterm::{execute, Result, terminal::{EnterAlternateScreen, LeaveAlternateScreen}};
///
/// fn main() -> Result<()> {
/// execute!(stdout(), EnterAlternateScreen)?;
///
/// // Do anything on the alternate screen
///
/// execute!(stdout(), LeaveAlternateScreen)
/// }
/// ```
///
pub struct EnterAlternateScreen;
impl Command for EnterAlternateScreen {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
ansi::ENTER_ALTERNATE_SCREEN_CSI_SEQUENCE
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
let alternate_screen = ScreenBuffer::create();
alternate_screen.show()?;
Ok(())
}
}
/// A command that switches back to the main screen.
///
/// # Notes
///
/// * Commands must be executed/queued for execution otherwise they do nothing.
/// * Use [EnterAlternateScreen](./struct.EnterAlternateScreen.html) to enter the alternate screen.
///
/// # Examples
///
/// ```no_run
/// use std::io::{stdout, Write};
/// use crossterm::{execute, Result, terminal::{EnterAlternateScreen, LeaveAlternateScreen}};
///
/// fn main() -> Result<()> {
/// execute!(stdout(), EnterAlternateScreen)?;
///
/// // Do anything on the alternate screen
///
/// execute!(stdout(), LeaveAlternateScreen)
/// }
/// ```
///
pub struct LeaveAlternateScreen;
impl Command for LeaveAlternateScreen {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
ansi::LEAVE_ALTERNATE_SCREEN_CSI_SEQUENCE
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
let screen_buffer = ScreenBuffer::from(Handle::current_out_handle()?);
screen_buffer.show()?;
Ok(())
}
}
/// Different ways to clear the terminal buffer.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum ClearType {

View File

@ -7,6 +7,8 @@ pub(crate) const CLEAR_FROM_CURSOR_DOWN_CSI_SEQUENCE: &str = csi!("J");
pub(crate) const CLEAR_FROM_CURSOR_UP_CSI_SEQUENCE: &str = csi!("1J");
pub(crate) const CLEAR_FROM_CURRENT_LINE_CSI_SEQUENCE: &str = csi!("2K");
pub(crate) const CLEAR_UNTIL_NEW_LINE_CSI_SEQUENCE: &str = csi!("K");
pub(crate) const ENTER_ALTERNATE_SCREEN_CSI_SEQUENCE: &str = csi!("?1049h");
pub(crate) const LEAVE_ALTERNATE_SCREEN_CSI_SEQUENCE: &str = csi!("?1049l");
pub(crate) fn scroll_up_csi_sequence(count: u16) -> String {
format!(csi!("{}S"), count)

View File

@ -1,14 +1,14 @@
//! This module provides platform related functions.
#[cfg(unix)]
pub use self::unix::{exit, size};
pub(crate) use self::unix::{disable_raw_mode, enable_raw_mode, exit, is_raw_mode_enabled, size};
#[cfg(windows)]
pub(crate) use self::windows::{clear, scroll_down, scroll_up, set_size};
#[cfg(windows)]
pub use self::windows::{exit, size};
pub(crate) use self::windows::{
clear, disable_raw_mode, enable_raw_mode, exit, scroll_down, scroll_up, set_size, size,
};
#[cfg(windows)]
pub(crate) mod windows;
mod windows;
#[cfg(unix)]
pub(crate) mod unix;
mod unix;

View File

@ -1,15 +1,76 @@
//! UNIX related logic for terminal manipulation.
use std::process;
use std::{mem, process, sync::Mutex};
use libc::{ioctl, winsize, STDOUT_FILENO, TIOCGWINSZ};
use libc::{
cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDIN_FILENO,
STDOUT_FILENO, TCSANOW, TIOCGWINSZ,
};
use lazy_static::lazy_static;
use crate::utils::{sys::unix::wrap_with_result, Result};
/// Exits the current application.
pub fn exit() {
lazy_static! {
// Some(Termios) -> we're in the raw mode and this is the previous mode
// None -> we're not in the raw mode
static ref TERMINAL_MODE_PRIOR_RAW_MODE: Mutex<Option<Termios>> = Mutex::new(None);
}
pub(crate) fn is_raw_mode_enabled() -> bool {
TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap().is_some()
}
pub(crate) fn exit() {
::std::process::exit(0);
}
pub(crate) fn size() -> Result<(u16, u16)> {
// http://rosettacode.org/wiki/Terminal_control/Dimensions#Library:_BSD_libc
let mut size = winsize {
ws_row: 0,
ws_col: 0,
ws_xpixel: 0,
ws_ypixel: 0,
};
if let Ok(true) = wrap_with_result(unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut size) }) {
Ok((size.ws_col, size.ws_row))
} else {
tput_size().ok_or_else(|| std::io::Error::last_os_error().into())
}
}
pub(crate) fn enable_raw_mode() -> Result<()> {
let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap();
if original_mode.is_some() {
return Ok(());
}
let mut ios = get_terminal_attr()?;
let original_mode_ios = ios;
raw_terminal_attr(&mut ios);
set_terminal_attr(&ios)?;
// Keep it last - set the original mode only if we were able to switch to the raw mode
*original_mode = Some(original_mode_ios);
Ok(())
}
pub(crate) fn disable_raw_mode() -> Result<()> {
let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap();
if let Some(original_mode_ios) = original_mode.as_ref() {
set_terminal_attr(original_mode_ios)?;
// Keep it last - remove the original mode only if we were able to switch back
*original_mode = None;
}
Ok(())
}
/// execute tput with the given argument and parse
/// the output as a u16.
///
@ -32,7 +93,7 @@ fn tput_value(arg: &str) -> Option<u16> {
}
}
/// return the size of the screen as determined by tput
/// Returns the size of the screen as determined by tput.
///
/// This alternate way of computing the size is useful
/// when in a subshell.
@ -43,21 +104,19 @@ fn tput_size() -> Option<(u16, u16)> {
}
}
/// Returns the terminal size `(columns, rows)`.
///
/// The top left cell is represented `1,1`.
pub fn size() -> Result<(u16, u16)> {
// http://rosettacode.org/wiki/Terminal_control/Dimensions#Library:_BSD_libc
let mut size = winsize {
ws_row: 0,
ws_col: 0,
ws_xpixel: 0,
ws_ypixel: 0,
};
// Transform the given mode into an raw mode (non-canonical) mode.
fn raw_terminal_attr(termios: &mut Termios) {
unsafe { cfmakeraw(termios) }
}
if let Ok(true) = wrap_with_result(unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut size) }) {
Ok((size.ws_col, size.ws_row))
} else {
tput_size().ok_or_else(|| std::io::Error::last_os_error().into())
fn get_terminal_attr() -> Result<Termios> {
unsafe {
let mut termios = mem::zeroed();
wrap_with_result(tcgetattr(STDIN_FILENO, &mut termios))?;
Ok(termios)
}
}
fn set_terminal_attr(termios: &Termios) -> Result<bool> {
wrap_with_result(unsafe { tcsetattr(STDIN_FILENO, TCSANOW, termios) })
}

View File

@ -1,18 +1,43 @@
//! WinApi related logic for terminal manipulation.
use crossterm_winapi::{Console, Coord, Handle, ScreenBuffer, Size};
use crossterm_winapi::{Console, ConsoleMode, Coord, Handle, ScreenBuffer, Size};
use winapi::{
shared::minwindef::DWORD,
um::wincon::{ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT},
};
use crate::{cursor, terminal::ClearType, utils::Result, ErrorKind};
/// Exits the current application.
pub fn exit() {
const RAW_MODE_MASK: DWORD = ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT;
pub(crate) fn enable_raw_mode() -> Result<()> {
let console_mode = ConsoleMode::from(Handle::input_handle()?);
let dw_mode = console_mode.mode()?;
let new_mode = dw_mode & !RAW_MODE_MASK;
console_mode.set_mode(new_mode)?;
Ok(())
}
pub(crate) fn disable_raw_mode() -> Result<()> {
let console_mode = ConsoleMode::from(Handle::input_handle()?);
let dw_mode = console_mode.mode()?;
let new_mode = dw_mode | RAW_MODE_MASK;
console_mode.set_mode(new_mode)?;
Ok(())
}
pub(crate) fn exit() {
::std::process::exit(256);
}
/// Returns the terminal size `(columns, rows)`.
///
/// The top left cell is represented `1,1`.
pub fn size() -> Result<(u16, u16)> {
pub(crate) fn size() -> Result<(u16, u16)> {
let terminal_size = ScreenBuffer::current()?.info()?.terminal_size();
// windows starts counting at 0, unix at 1, add one to replicated unix behaviour.
Ok((
@ -73,7 +98,6 @@ pub(crate) fn scroll_down(row_count: u16) -> Result<()> {
Ok(())
}
/// Set the current terminal size
pub(crate) fn set_size(width: u16, height: u16) -> Result<()> {
if width <= 1 {
return Err(ErrorKind::ResizingTerminalFailure(String::from(

View File

@ -1,76 +1,13 @@
//! This module contains all `unix` specific terminal related logic.
use std::{io, mem, sync::Mutex};
pub use libc::termios as Termios;
use libc::{cfmakeraw, tcgetattr, tcsetattr, STDIN_FILENO, TCSANOW};
use lazy_static::lazy_static;
use std::io;
use super::super::error::{ErrorKind, Result};
lazy_static! {
// Some(Termios) -> we're in the raw mode and this is the previous mode
// None -> we're not in the raw mode
static ref TERMINAL_MODE_PRIOR_RAW_MODE: Mutex<Option<Termios>> = Mutex::new(None);
}
pub(crate) fn is_raw_mode_enabled() -> bool {
TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap().is_some()
}
pub(crate) fn wrap_with_result(result: i32) -> Result<bool> {
pub fn wrap_with_result(result: i32) -> Result<bool> {
if result == -1 {
Err(ErrorKind::IoError(io::Error::last_os_error()))
} else {
Ok(true)
}
}
/// Transform the given mode into an raw mode (non-canonical) mode.
pub(crate) fn raw_terminal_attr(termios: &mut Termios) {
unsafe { cfmakeraw(termios) }
}
pub(crate) fn get_terminal_attr() -> Result<Termios> {
unsafe {
let mut termios = mem::zeroed();
wrap_with_result(tcgetattr(STDIN_FILENO, &mut termios))?;
Ok(termios)
}
}
pub(crate) fn set_terminal_attr(termios: &Termios) -> Result<bool> {
wrap_with_result(unsafe { tcsetattr(STDIN_FILENO, TCSANOW, termios) })
}
pub(crate) fn enable_raw_mode() -> Result<()> {
let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap();
if original_mode.is_some() {
return Ok(());
}
let mut ios = get_terminal_attr()?;
let original_mode_ios = ios;
raw_terminal_attr(&mut ios);
set_terminal_attr(&ios)?;
// Keep it last - set the original mode only if we were able to switch to the raw mode
*original_mode = Some(original_mode_ios);
Ok(())
}
pub(crate) fn disable_raw_mode() -> Result<()> {
let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap();
if let Some(original_mode_ios) = original_mode.as_ref() {
set_terminal_attr(original_mode_ios)?;
// Keep it last - remove the original mode only if we were able to switch back
*original_mode = None;
}
Ok(())
}