From b4241e01076ff99d2b95eef2172f336da1eb111d Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 4 Dec 2019 17:40:11 +0100 Subject: [PATCH] Refactored Screen Module (#336) --- CHANGELOG.md | 13 +- examples/event-poll-read.rs | 13 +- examples/event-read.rs | 7 +- examples/event-stream-async-std.rs | 13 +- examples/event-stream-tokio.rs | 13 +- src/cursor/sys/unix.rs | 6 +- src/event/read.rs | 8 +- src/event/source/unix.rs | 3 +- src/event/source/windows.rs | 3 +- src/event/sys/windows.rs | 30 ++--- src/lib.rs | 7 +- src/screen.rs | 209 ----------------------------- src/screen/alternate.rs | 30 ----- src/screen/alternate/ansi.rs | 26 ---- src/screen/alternate/windows.rs | 21 --- src/screen/raw.rs | 128 ------------------ src/screen/sys.rs | 5 - src/screen/sys/unix.rs | 22 --- src/screen/sys/winapi.rs | 50 ------- src/terminal.rs | 168 +++++++++++++++++++++-- src/terminal/ansi.rs | 2 + src/terminal/sys.rs | 12 +- src/terminal/sys/unix.rs | 101 +++++++++++--- src/terminal/sys/windows.rs | 42 ++++-- src/utils/sys/unix.rs | 67 +-------- 25 files changed, 342 insertions(+), 657 deletions(-) delete mode 100644 src/screen.rs delete mode 100644 src/screen/alternate.rs delete mode 100644 src/screen/alternate/ansi.rs delete mode 100644 src/screen/alternate/windows.rs delete mode 100644 src/screen/raw.rs delete mode 100644 src/screen/sys.rs delete mode 100644 src/screen/sys/unix.rs delete mode 100644 src/screen/sys/winapi.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 912bd64..2bd7c1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,15 @@ documentation - Replace `docs/UPGRADE.md` with the [Upgrade Paths](https://github.com/crossterm-rs/crossterm/wiki#upgrade-paths) documentation -- Add `MoveToColumn`, `MoveToPreviousLine`, `MoveToNextLine` commands -- Replace `utils::Output` command with `style::Print` command -- Fix enable/disable mouse capture commands on Windows. - + - 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. + # Version 0.13.3 - Remove thread from AsyncReader on Windows. diff --git a/examples/event-poll-read.rs b/examples/event-poll-read.rs index e18d583..ead03a8 100644 --- a/examples/event-poll-read.rs +++ b/examples/event-poll-read.rs @@ -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() } diff --git a/examples/event-read.rs b/examples/event-read.rs index 4f02603..e1954ba 100644 --- a/examples/event-read.rs +++ b/examples/event-read.rs @@ -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() } diff --git a/examples/event-stream-async-std.rs b/examples/event-stream-async-std.rs index 5804f7d..231f463 100644 --- a/examples/event-stream-async-std.rs +++ b/examples/event-stream-async-std.rs @@ -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() } diff --git a/examples/event-stream-tokio.rs b/examples/event-stream-tokio.rs index debdd85..2a06c38 100644 --- a/examples/event-stream-tokio.rs +++ b/examples/event-stream-tokio.rs @@ -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() } diff --git a/src/cursor/sys/unix.rs b/src/cursor/sys/unix.rs index 00d7154..9cd16d4 100644 --- a/src/cursor/sys/unix.rs +++ b/src/cursor/sys/unix.rs @@ -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). diff --git a/src/event/read.rs b/src/event/read.rs index dc1e001..e57a70c 100644 --- a/src/event/read.rs +++ b/src/event/read.rs @@ -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] diff --git a/src/event/source/unix.rs b/src/event/source/unix.rs index 31a4c8a..ab6ba31 100644 --- a/src/event/source/unix.rs +++ b/src/event/source/unix.rs @@ -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; diff --git a/src/event/source/windows.rs b/src/event/source/windows.rs index 8555376..aa6ac1d 100644 --- a/src/event/source/windows.rs +++ b/src/event/source/windows.rs @@ -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, diff --git a/src/event/sys/windows.rs b/src/event/sys/windows.rs index d8d57e9..c0b9bd1 100644 --- a/src/event/sys/windows.rs +++ b/src/event/sys/windows.rs @@ -1,26 +1,24 @@ //! 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::{ - 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, - }, - 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 winapi::{ + shared::winerror::WAIT_TIMEOUT, + um::{ + synchapi::WaitForMultipleObjects, + winbase::{INFINITE, WAIT_ABANDONED_0, WAIT_FAILED, WAIT_OBJECT_0}, + wincon::{ + 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, + }, }, }; diff --git a/src/lib.rs b/src/lib.rs index 4a24d9c..1335210 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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. diff --git a/src/screen.rs b/src/screen.rs deleted file mode 100644 index 6b38875..0000000 --- a/src/screen.rs +++ /dev/null @@ -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, -} - -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 { - 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() - } -} diff --git a/src/screen/alternate.rs b/src/screen/alternate.rs deleted file mode 100644 index 3fbfd12..0000000 --- a/src/screen/alternate.rs +++ /dev/null @@ -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 { - if supports_ansi() { - Box::new(AnsiAlternateScreen) - } else { - Box::new(WinApiAlternateScreen) - } -} - -#[cfg(unix)] -pub(crate) fn alternate_screen() -> AnsiAlternateScreen { - AnsiAlternateScreen -} diff --git a/src/screen/alternate/ansi.rs b/src/screen/alternate/ansi.rs deleted file mode 100644 index c9ff443..0000000 --- a/src/screen/alternate/ansi.rs +++ /dev/null @@ -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(()) - } -} diff --git a/src/screen/alternate/windows.rs b/src/screen/alternate/windows.rs deleted file mode 100644 index 3f19200..0000000 --- a/src/screen/alternate/windows.rs +++ /dev/null @@ -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(()) - } -} diff --git a/src/screen/raw.rs b/src/screen/raw.rs deleted file mode 100644 index 3ee57cc..0000000 --- a/src/screen/raw.rs +++ /dev/null @@ -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 { - #[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; -} - -impl IntoRawMode for Stdout { - fn into_raw_mode(self) -> Result { - 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(); - } - } -} diff --git a/src/screen/sys.rs b/src/screen/sys.rs deleted file mode 100644 index b70442f..0000000 --- a/src/screen/sys.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[cfg(unix)] -pub(crate) mod unix; - -#[cfg(windows)] -pub(crate) mod winapi; diff --git a/src/screen/sys/unix.rs b/src/screen/sys/unix.rs deleted file mode 100644 index fbb50c6..0000000 --- a/src/screen/sys/unix.rs +++ /dev/null @@ -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(()) - } -} diff --git a/src/screen/sys/winapi.rs b/src/screen/sys/winapi.rs deleted file mode 100644 index 554517f..0000000 --- a/src/screen/sys/winapi.rs +++ /dev/null @@ -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(()) - } -} diff --git a/src/terminal.rs b/src/terminal.rs index d5d9ac8..99e0839 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -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 { diff --git a/src/terminal/ansi.rs b/src/terminal/ansi.rs index 9b09977..cf3843c 100644 --- a/src/terminal/ansi.rs +++ b/src/terminal/ansi.rs @@ -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) diff --git a/src/terminal/sys.rs b/src/terminal/sys.rs index f788278..a0caef3 100644 --- a/src/terminal/sys.rs +++ b/src/terminal/sys.rs @@ -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; diff --git a/src/terminal/sys/unix.rs b/src/terminal/sys/unix.rs index 924772c..fb8cc43 100644 --- a/src/terminal/sys/unix.rs +++ b/src/terminal/sys/unix.rs @@ -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> = 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,10 +93,10 @@ fn tput_value(arg: &str) -> Option { } } -/// 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. +/// when in a subshell. fn tput_size() -> Option<(u16, u16)> { match (tput_value("cols"), tput_value("lines")) { (Some(w), Some(h)) => Some((w, h)), @@ -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 { + unsafe { + let mut termios = mem::zeroed(); + wrap_with_result(tcgetattr(STDIN_FILENO, &mut termios))?; + Ok(termios) } } + +fn set_terminal_attr(termios: &Termios) -> Result { + wrap_with_result(unsafe { tcsetattr(STDIN_FILENO, TCSANOW, termios) }) +} diff --git a/src/terminal/sys/windows.rs b/src/terminal/sys/windows.rs index 149313d..0f73d03 100644 --- a/src/terminal/sys/windows.rs +++ b/src/terminal/sys/windows.rs @@ -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( diff --git a/src/utils/sys/unix.rs b/src/utils/sys/unix.rs index 6340741..50e49fb 100644 --- a/src/utils/sys/unix.rs +++ b/src/utils/sys/unix.rs @@ -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> = 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 { +pub fn wrap_with_result(result: i32) -> Result { 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 { - 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 { - 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(()) -}