From 3ab5b170aa026aec11d9de37013afd3587404aaf Mon Sep 17 00:00:00 2001 From: Timon Date: Tue, 29 Oct 2019 09:14:47 +0100 Subject: [PATCH] Terminal remove Deprecated Api (#293) --- CHANGELOG.md | 9 +- src/crossterm.rs | 6 - src/cursor.rs | 9 +- src/lib.rs | 18 +- src/terminal.rs | 258 +++++++++---------------- src/terminal/ansi.rs | 21 +++ src/terminal/sys.rs | 10 +- src/terminal/sys/unix.rs | 17 +- src/terminal/sys/winapi.rs | 16 -- src/terminal/sys/windows.rs | 325 ++++++++++++++++++++++++++++++++ src/terminal/terminal.rs | 30 --- src/terminal/terminal/ansi.rs | 117 ------------ src/terminal/terminal/winapi.rs | 282 --------------------------- src/utils/sys/unix.rs | 2 +- 14 files changed, 471 insertions(+), 649 deletions(-) create mode 100644 src/terminal/ansi.rs delete mode 100644 src/terminal/sys/winapi.rs create mode 100644 src/terminal/sys/windows.rs delete mode 100644 src/terminal/terminal.rs delete mode 100644 src/terminal/terminal/ansi.rs delete mode 100644 src/terminal/terminal/winapi.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 880f751..8a39860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,12 +14,17 @@ - Rename `ResetPos` to `ResetPosition` - Rename `SavePos` to `SavePosition` - Remove re-export cursor module types at root level, are now accessible from `crossterm::cursor` - - `style module` +- `terminal` + - Remove `Terminal`, `terminal`, `Crossterm::terminal()` + - Introduce static function `crossterm::terminal::size` in place of `Terminal::size` + - Introduce static function `crossterm::terminal::exit` in place of `Terminal::exit` + - Remove re-export terminal module types at root level, are move those to `crossterm::terminal` +- `style module` - Rename `ObjectStyle` to `ContentStyle`. Now full names are used for methods. - Rename `StyledObject` to `StyledContent` and made members private. - Rename `attr` method to `attribute`. - Rename `Attribute::NoInverse` to `NoReverse` - + # Version 0.12.1 - All the `crossterm_` crates code was moved to the `crossterm` crate diff --git a/src/crossterm.rs b/src/crossterm.rs index a755f50..12eb8f4 100644 --- a/src/crossterm.rs +++ b/src/crossterm.rs @@ -16,12 +16,6 @@ impl Crossterm { crate::input::TerminalInput::new() } - /// Creates a new `Terminal`. - #[cfg(feature = "terminal")] - pub fn terminal(&self) -> crate::terminal::Terminal { - crate::terminal::Terminal::new() - } - /// Creates a new `TerminalColor`. #[cfg(feature = "style")] pub fn color(&self) -> crate::style::TerminalColor { diff --git a/src/cursor.rs b/src/cursor.rs index 58b3f73..59ba732 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -45,11 +45,10 @@ pub use sys::position; use crate::impl_display; +use crate::utils::Command; #[cfg(windows)] use crate::utils::Result; -use crate::utils::Command; - mod ansi; pub(crate) mod sys; @@ -296,11 +295,13 @@ impl_display!(for DisableBlinking); #[cfg(test)] mod tests { + use std::io::{self, stdout, Write}; + + use crate::execute; + use super::{ position, MoveDown, MoveLeft, MoveRight, MoveTo, MoveUp, RestorePosition, SavePosition, }; - use crate::execute; - use std::io::{self, stdout, Write}; // Test is disabled, because it's failing on Travis #[test] diff --git a/src/lib.rs b/src/lib.rs index 116c0be..82135db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,21 +118,21 @@ //! ```no_run //! use std::io::{stdout, Write}; //! use crossterm::{ -//! ExecutableCommand, QueueableCommand, Color, PrintStyledFont, -//! Colorize, Clear, ClearType, cursor::MoveTo, Result +//! ExecutableCommand, QueueableCommand, Color, +//! Colorize, terminal, cursor, style, Result //! }; //! //! fn main() -> Result<()> { //! let mut stdout = stdout(); //! -//! stdout.execute(Clear(ClearType::All))?; +//! stdout.execute(terminal::Clear(terminal::ClearType::All))?; //! //! for y in 0..40 { //! for x in 0..150 { //! if (y == 0 || y == 40 - 1) || (x == 0 || x == 150 - 1) { //! stdout -//! .queue(MoveTo(x,y))? -//! .queue(PrintStyledFont( "█".magenta()))?; +//! .queue(cursor::MoveTo(x,y))? +//! .queue(style::PrintStyledFont( "█".magenta()))?; //! } //! } //! } @@ -147,18 +147,18 @@ //! use std::io::{stdout, Write}; //! use crossterm::{ //! execute, queue, Color, PrintStyledFont, -//! Colorize, cursor::MoveTo, Clear, ClearType, Result +//! Colorize, cursor, terminal, style, Result //! }; //! //! fn main() -> Result<()> { //! let mut stdout = stdout(); //! -//! execute!(stdout, Clear(ClearType::All))?; +//! execute!(stdout, terminal::Clear(terminal::ClearType::All))?; //! //! for y in 0..40 { //! for x in 0..150 { //! if (y == 0 || y == 40 - 1) || (x == 0 || x == 150 - 1) { -//! queue!(stdout, MoveTo(x,y), PrintStyledFont( "█".magenta()))?; +//! queue!(stdout, cursor::MoveTo(x,y), style::PrintStyledFont( "█".magenta()))?; //! } //! } //! } @@ -180,8 +180,6 @@ pub use style::{ color, style, Attribute, Color, Colored, Colorize, ContentStyle, PrintStyledFont, ResetColor, SetAttr, SetBg, SetFg, StyledContent, Styler, TerminalColor, }; -#[cfg(feature = "terminal")] -pub use terminal::{terminal, Clear, ClearType, ScrollDown, ScrollUp, SetSize, Terminal}; pub use utils::{Command, ErrorKind, ExecutableCommand, Output, QueueableCommand, Result}; pub use self::crossterm::Crossterm; diff --git a/src/terminal.rs b/src/terminal.rs index ccd958e..70e30c3 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -1,42 +1,23 @@ //! # Terminal //! -//! The `terminal` module provides a functionality to work with the terminal. +//! The `terminal` module provides functionality to work with the terminal. //! //! 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. //! +//! Terminal actions can be performed with commands. +//! Please have a look at [command documention](../index.html#command-api) for a more detailed documentation. +//! //! ## Examples //! //! ```no_run -//! use crossterm::{Result, Terminal}; -//! -//! fn main() -> Result<()> { -//! // Get a terminal, save size -//! let terminal = Terminal::new(); -//! let (cols, rows) = terminal.size()?; -//! -//! // Do something with the terminal -//! terminal.set_size(10, 10)?; -//! terminal.scroll_up(5)?; -//! -//! // Be a good citizen, cleanup -//! terminal.set_size(cols, rows) -//! } -//! ``` -//! -//! Commands: -//! -//! ```no_run //! use std::io::{stdout, Write}; -//! use crossterm::{execute, Result, ScrollUp, SetSize, Terminal}; +//! use crossterm::{execute, Result, terminal::{ScrollUp, SetSize, size}}; //! //! fn main() -> Result<()> { -//! // Get a terminal, save size -//! let terminal = Terminal::new(); -//! let (cols, rows) = terminal.size()?; -//! +//! let (cols, rows) = size()?; //! // Do something with the terminal //! execute!( //! stdout(), @@ -45,27 +26,26 @@ //! )?; //! //! // Be a good citizen, cleanup -//! terminal.set_size(cols, rows) +//! execute!(stdout(), SetSize(cols, rows))?; +//! Ok(()) //! } //! ``` -use std::fmt; +//! For manual execution control check out [crossterm::queue](../macro.queue.html). + +pub use sys::exit; +pub use sys::size; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -#[cfg(windows)] -use crate::utils::supports_ansi; +use crate::impl_display; #[doc(no_inline)] -use crate::utils::{Command, Result}; -use crate::{impl_display, write_cout}; - -use self::terminal::ansi::AnsiTerminal; +use crate::utils::Command; #[cfg(windows)] -use self::terminal::winapi::WinApiTerminal; -use self::terminal::Terminal as TerminalTrait; +use crate::utils::Result; +mod ansi; mod sys; -mod terminal; /// Represents different options how to clear the terminal. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -83,128 +63,7 @@ pub enum ClearType { UntilNewLine, } -/// A terminal. -/// -/// The `Terminal` instance is stateless and does not hold any data. -/// You can create as many instances as you want and they will always refer to the -/// same terminal. -/// -/// # Examples -/// -/// Basic usage: -/// -/// ```no_run -/// use crossterm::{Result, Terminal}; -/// -/// fn main() -> Result<()> { -/// let terminal = Terminal::new(); -/// let (cols, rows) = terminal.size()?; -/// -/// terminal.set_size(10, 10)?; -/// terminal.scroll_up(5)?; -/// -/// terminal.set_size(cols, rows) -/// } -/// ``` -pub struct Terminal { - #[cfg(windows)] - terminal: Box<(dyn TerminalTrait + Sync + Send)>, - #[cfg(unix)] - terminal: AnsiTerminal, -} - -impl Terminal { - /// Creates a new `Terminal`. - pub fn new() -> Terminal { - #[cfg(windows)] - let terminal = if supports_ansi() { - Box::from(AnsiTerminal::new()) as Box<(dyn TerminalTrait + Sync + Send)> - } else { - WinApiTerminal::new() as Box<(dyn TerminalTrait + Sync + Send)> - }; - - #[cfg(unix)] - let terminal = AnsiTerminal::new(); - - Terminal { terminal } - } - - /// Clears the terminal. - /// - /// See the [`ClearType`](enum.ClearType.html) enum to learn about - /// all ways how the terminal can be cleared. - pub fn clear(&self, clear_type: ClearType) -> Result<()> { - self.terminal.clear(clear_type) - } - - /// Returns the terminal size (`(columns, rows)`). - pub fn size(&self) -> Result<(u16, u16)> { - self.terminal.size() - } - - /// Scrolls the terminal `row_count` rows up. - pub fn scroll_up(&self, row_count: u16) -> Result<()> { - self.terminal.scroll_up(row_count) - } - - /// Scrolls the terminal `row_count` rows down. - pub fn scroll_down(&self, row_count: u16) -> Result<()> { - self.terminal.scroll_down(row_count) - } - - /// Sets the terminal size. - pub fn set_size(&self, columns: u16, rows: u16) -> Result<()> { - self.terminal.set_size(columns, rows) - } - - /// Exits the current process. - /// - /// # Platform-specific Behavior - /// - /// [`std::process::exit`](https://doc.rust-lang.org/std/process/fn.exit.html) is - /// called internally with platform specific exit codes. - /// - /// **Unix**: exit code 0. - /// - /// **Windows**: exit code 256. - pub fn exit(&self) { - crate::terminal::sys::exit(); - } - - /// Writes any displayable content to the current terminal and flushes - /// the standard output. - pub fn write(&self, value: D) -> Result { - write_cout!(format!("{}", value)) - } -} - -/// Creates a new `Terminal`. -/// -/// # Examples -/// -/// Basic usage: -/// -/// ```no_run -/// use crossterm::{terminal, Result}; -/// -/// fn main() -> Result<()> { -/// // Get a terminal, save size -/// let terminal = terminal(); -/// let (cols, rows) = terminal.size()?; -/// -/// // Do something with the terminal -/// terminal.set_size(10, 10)?; -/// terminal.scroll_up(5)?; -/// -/// // Be a good citizen, cleanup -/// terminal.set_size(cols, rows) -/// } -/// ``` -pub fn terminal() -> Terminal { - Terminal::new() -} - -/// A command to scroll the terminal given rows up. +/// A command that scrolls the terminal screen a given number of rows up. /// /// # Notes /// @@ -215,16 +74,16 @@ impl Command for ScrollUp { type AnsiType = String; fn ansi_code(&self) -> Self::AnsiType { - terminal::ansi::scroll_up_csi_sequence(self.0) + ansi::scroll_up_csi_sequence(self.0) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { - WinApiTerminal::new().scroll_up(self.0) + sys::scroll_up(self.0) } } -/// A command to scroll the terminal given rows down. +/// A command that scrolls the terminal screen a given number of rows down. /// /// # Notes /// @@ -235,16 +94,16 @@ impl Command for ScrollDown { type AnsiType = String; fn ansi_code(&self) -> Self::AnsiType { - terminal::ansi::scroll_down_csi_sequence(self.0) + ansi::scroll_down_csi_sequence(self.0) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { - WinApiTerminal::new().scroll_down(self.0) + sys::scroll_down(self.0) } } -/// A command to clear the terminal. +/// A command that clears the terminal screen buffer. /// /// See the [`ClearType`](enum.ClearType.html) enum. /// @@ -258,21 +117,21 @@ impl Command for Clear { fn ansi_code(&self) -> Self::AnsiType { match self.0 { - ClearType::All => terminal::ansi::CLEAR_ALL_CSI_SEQUENCE, - ClearType::FromCursorDown => terminal::ansi::CLEAR_FROM_CURSOR_DOWN_CSI_SEQUENCE, - ClearType::FromCursorUp => terminal::ansi::CLEAR_FROM_CURSOR_UP_CSI_SEQUENCE, - ClearType::CurrentLine => terminal::ansi::CLEAR_FROM_CURRENT_LINE_CSI_SEQUENCE, - ClearType::UntilNewLine => terminal::ansi::CLEAR_UNTIL_NEW_LINE_CSI_SEQUENCE, + ClearType::All => ansi::CLEAR_ALL_CSI_SEQUENCE, + ClearType::FromCursorDown => ansi::CLEAR_FROM_CURSOR_DOWN_CSI_SEQUENCE, + ClearType::FromCursorUp => ansi::CLEAR_FROM_CURSOR_UP_CSI_SEQUENCE, + ClearType::CurrentLine => ansi::CLEAR_FROM_CURRENT_LINE_CSI_SEQUENCE, + ClearType::UntilNewLine => ansi::CLEAR_UNTIL_NEW_LINE_CSI_SEQUENCE, } } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { - WinApiTerminal::new().clear(self.0.clone()) + sys::clear(self.0.clone()) } } -/// A command to set the terminal size (rows, columns). +/// A command that sets the terminal size `(columns, rows)`. /// /// # Notes /// @@ -283,12 +142,12 @@ impl Command for SetSize { type AnsiType = String; fn ansi_code(&self) -> Self::AnsiType { - terminal::ansi::set_size_csi_sequence(self.0, self.1) + ansi::set_size_csi_sequence(self.0, self.1) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { - WinApiTerminal::new().set_size(self.0, self.1) + sys::set_size(self.0, self.1) } } @@ -296,3 +155,56 @@ impl_display!(for ScrollUp); impl_display!(for ScrollDown); impl_display!(for SetSize); impl_display!(for Clear); + +#[cfg(test)] +mod tests { + use std::{ + io::{stdout, Write}, + thread, time, + }; + + use crate::execute; + + use super::{size, SetSize}; + + // Test is disabled, because it's failing on Travis CI + #[test] + #[ignore] + fn test_resize_ansi() { + try_enable_ansi(); + + let (width, height) = size().unwrap(); + + execute!(stdout(), SetSize(35, 35)).unwrap(); + + // see issue: https://github.com/eminence/terminal-size/issues/11 + thread::sleep(time::Duration::from_millis(30)); + + assert_eq!((35, 35), size().unwrap()); + + // reset to previous size + execute!(stdout(), SetSize(width, height)).unwrap(); + + // see issue: https://github.com/eminence/terminal-size/issues/11 + thread::sleep(time::Duration::from_millis(30)); + + assert_eq!((width, height), size().unwrap()); + } + + fn try_enable_ansi() -> bool { + #[cfg(windows)] + { + if cfg!(target_os = "windows") { + use crate::utils::sys::winapi::ansi::set_virtual_terminal_processing; + + // if it is not listed we should try with WinApi to check if we do support ANSI-codes. + match set_virtual_terminal_processing(true) { + Ok(_) => return true, + Err(_) => return false, + } + } + } + + true + } +} diff --git a/src/terminal/ansi.rs b/src/terminal/ansi.rs new file mode 100644 index 0000000..4acd153 --- /dev/null +++ b/src/terminal/ansi.rs @@ -0,0 +1,21 @@ +//! This module provides terminal related ANSI escape codes. + +use crate::csi; + +pub(crate) static CLEAR_ALL_CSI_SEQUENCE: &'static str = csi!("2J"); +pub(crate) static CLEAR_FROM_CURSOR_DOWN_CSI_SEQUENCE: &'static str = csi!("J"); +pub(crate) static CLEAR_FROM_CURSOR_UP_CSI_SEQUENCE: &'static str = csi!("1J"); +pub(crate) static CLEAR_FROM_CURRENT_LINE_CSI_SEQUENCE: &'static str = csi!("2K"); +pub(crate) static CLEAR_UNTIL_NEW_LINE_CSI_SEQUENCE: &'static str = csi!("K"); + +pub(crate) fn scroll_up_csi_sequence(count: u16) -> String { + format!(csi!("{}S"), count) +} + +pub(crate) fn scroll_down_csi_sequence(count: u16) -> String { + format!(csi!("{}T"), count) +} + +pub(crate) fn set_size_csi_sequence(width: u16, height: u16) -> String { + format!(csi!("8;{};{}t"), height, width) +} diff --git a/src/terminal/sys.rs b/src/terminal/sys.rs index d1a5e75..f788278 100644 --- a/src/terminal/sys.rs +++ b/src/terminal/sys.rs @@ -1,10 +1,14 @@ +//! This module provides platform related functions. + #[cfg(unix)] -pub(crate) use self::unix::{exit, get_terminal_size}; +pub use self::unix::{exit, size}; #[cfg(windows)] -pub(crate) use self::winapi::{exit, get_terminal_size}; +pub(crate) use self::windows::{clear, scroll_down, scroll_up, set_size}; +#[cfg(windows)] +pub use self::windows::{exit, size}; #[cfg(windows)] -pub(crate) mod winapi; +pub(crate) mod windows; #[cfg(unix)] pub(crate) mod unix; diff --git a/src/terminal/sys/unix.rs b/src/terminal/sys/unix.rs index 0061f6d..009a55f 100644 --- a/src/terminal/sys/unix.rs +++ b/src/terminal/sys/unix.rs @@ -1,12 +1,19 @@ +//! UNIX related logic for terminal manipulation. + use libc::{ioctl, winsize, STDOUT_FILENO, TIOCGWINSZ}; +use crate::utils::sys::unix::wrap_with_result; use crate::utils::Result; -pub(crate) fn exit() { +/// Exits the current application. +pub fn exit() { ::std::process::exit(0); } -pub(crate) fn get_terminal_size() -> Result<(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, @@ -14,10 +21,10 @@ pub(crate) fn get_terminal_size() -> Result<(u16, u16)> { ws_xpixel: 0, ws_ypixel: 0, }; - let r = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut size) }; - if r == 0 { - Ok((size.ws_col, size.ws_row)) + if let Ok(()) = wrap_with_result(unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut size) }) + { + return Ok((size.ws_col, size.ws_row)); } else { Err(std::io::Error::last_os_error().into()) } diff --git a/src/terminal/sys/winapi.rs b/src/terminal/sys/winapi.rs deleted file mode 100644 index 46c8f2f..0000000 --- a/src/terminal/sys/winapi.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crossterm_winapi::ScreenBuffer; - -use crate::utils::Result; - -pub(crate) fn exit() { - ::std::process::exit(256); -} - -pub(crate) fn get_terminal_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(( - (terminal_size.width + 1) as u16, - (terminal_size.height + 1) as u16, - )) -} diff --git a/src/terminal/sys/windows.rs b/src/terminal/sys/windows.rs new file mode 100644 index 0000000..2755986 --- /dev/null +++ b/src/terminal/sys/windows.rs @@ -0,0 +1,325 @@ +//! WinApi related logic for terminal manipulation. + +use crossterm_winapi::{Console, Coord, Handle, ScreenBuffer, Size}; + +use crate::terminal::ClearType; +use crate::utils::Result; +use crate::{cursor, ErrorKind}; + +/// Exits the current application. +pub 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)> { + let terminal_size = ScreenBuffer::current()?.info()?.terminal_size(); + // windows starts counting at 0, unix at 1, add one to replicated unix behaviour. + Ok(( + (terminal_size.width + 1) as u16, + (terminal_size.height + 1) as u16, + )) +} + +pub(crate) fn clear(clear_type: ClearType) -> Result<()> { + let screen_buffer = ScreenBuffer::current()?; + let csbi = screen_buffer.info()?; + + let pos = csbi.cursor_pos(); + let buffer_size = csbi.buffer_size(); + let current_attribute = csbi.attributes(); + + match clear_type { + ClearType::All => { + clear_entire_screen(buffer_size, current_attribute)?; + } + ClearType::FromCursorDown => clear_after_cursor(pos, buffer_size, current_attribute)?, + ClearType::FromCursorUp => clear_before_cursor(pos, buffer_size, current_attribute)?, + ClearType::CurrentLine => clear_current_line(pos, buffer_size, current_attribute)?, + ClearType::UntilNewLine => clear_until_line(pos, buffer_size, current_attribute)?, + }; + Ok(()) +} + +pub(crate) fn scroll_up(row_count: u16) -> Result<()> { + let csbi = ScreenBuffer::current()?; + let mut window = csbi.info()?.terminal_window(); + + // check whether the window is too close to the screen buffer top + let count = row_count as i16; + if window.top >= count { + window.top -= count; // move top down + window.bottom -= count; // move bottom down + + Console::new()?.set_console_info(true, window)?; + } + Ok(()) +} + +pub(crate) fn scroll_down(row_count: u16) -> Result<()> { + let screen_buffer = ScreenBuffer::current()?; + let csbi = screen_buffer.info()?; + let mut window = csbi.terminal_window(); + let buffer_size = csbi.buffer_size(); + + // check whether the window is too close to the screen buffer top + let count = row_count as i16; + if window.bottom < buffer_size.height - count { + window.top += count; // move top down + window.bottom += count; // move bottom down + + Console::new()?.set_console_info(true, window)?; + } + Ok(()) +} + +/// Set the current terminal size +pub(crate) fn set_size(width: u16, height: u16) -> Result<()> { + if width <= 0 { + return Err(ErrorKind::ResizingTerminalFailure(String::from( + "Cannot set the terminal width lower than 1.", + ))); + } + + if height <= 0 { + return Err(ErrorKind::ResizingTerminalFailure(String::from( + "Cannot set the terminal height lower then 1.", + ))); + } + + // get the position of the current console window + let screen_buffer = ScreenBuffer::current()?; + let console = Console::from(**screen_buffer.handle()); + let csbi = screen_buffer.info()?; + + let current_size = csbi.buffer_size(); + let window = csbi.terminal_window(); + + let mut new_size = Size::new(current_size.width, current_size.height); + + // If the buffer is smaller than this new window size, resize the + // buffer to be large enough. Include window position. + let mut resize_buffer = false; + + let width = width as i16; + if current_size.width < window.left + width { + if window.left >= i16::max_value() - width { + return Err(ErrorKind::ResizingTerminalFailure(String::from( + "Argument out of range when setting terminal width.", + ))); + } + + new_size.width = window.left + width; + resize_buffer = true; + } + let height = height as i16; + if current_size.height < window.top + height { + if window.top >= i16::max_value() - height { + return Err(ErrorKind::ResizingTerminalFailure(String::from( + "Argument out of range when setting terminal height.", + ))); + } + + new_size.height = window.top + height; + resize_buffer = true; + } + + if resize_buffer { + if let Err(_) = screen_buffer.set_size(new_size.width - 1, new_size.height - 1) { + return Err(ErrorKind::ResizingTerminalFailure(String::from( + "Something went wrong when setting screen buffer size.", + ))); + } + } + + let mut window = window.clone(); + + // preserve the position, but change the size. + window.bottom = window.top + height - 1; + window.right = window.left + width - 1; + console.set_console_info(true, window)?; + + // if we resized the buffer, un-resize it. + if resize_buffer { + if let Err(_) = screen_buffer.set_size(current_size.width - 1, current_size.height - 1) { + return Err(ErrorKind::ResizingTerminalFailure(String::from( + "Something went wrong when setting screen buffer size.", + ))); + } + } + + let bounds = console.largest_window_size(); + + if width > bounds.x { + return Err(ErrorKind::ResizingTerminalFailure(format!( + "Argument width: {} out of range when setting terminal width.", + width + ))); + } + if height > bounds.y { + return Err(ErrorKind::ResizingTerminalFailure(format!( + "Argument height: {} out of range when setting terminal height.", + width + ))); + } + + Ok(()) +} + +fn clear_after_cursor(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> { + let (mut x, mut y) = (location.x, location.y); + + // if cursor position is at the outer right position + if x as i16 > buffer_size.width { + y += 1; + x = 0; + } + + // location where to start clearing + let start_location = Coord::new(x, y); + + // get sum cells before cursor + let cells_to_write = buffer_size.width as u32 * buffer_size.height as u32; + + clear_winapi(start_location, cells_to_write, current_attribute) +} + +fn clear_before_cursor(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> { + let (xpos, ypos) = (location.x, location.y); + + // one cell after cursor position + let x = 0; + // one at row of cursor position + let y = 0; + + // location where to start clearing + let start_location = Coord::new(x, y); + + // get sum cells before cursor + let cells_to_write = (buffer_size.width as u32 * ypos as u32) + (xpos as u32 + 1); + + // clear everything before cursor position + clear_winapi(start_location, cells_to_write, current_attribute) +} + +fn clear_entire_screen(buffer_size: Size, current_attribute: u16) -> Result<()> { + // get sum cells before cursor + let cells_to_write = buffer_size.width as u32 * buffer_size.height as u32; + + // location where to start clearing + let start_location = Coord::new(0, 0); + + // clear the entire screen + clear_winapi(start_location, cells_to_write, current_attribute)?; + + // put the cursor back at cell 0,0 + cursor::sys::move_to(0, 0)?; + Ok(()) +} + +fn clear_current_line(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> { + // location where to start clearing + let start_location = Coord::new(0, location.y); + + // get sum cells before cursor + let cells_to_write = buffer_size.width as u32; + + // clear the whole current line + clear_winapi(start_location, cells_to_write, current_attribute)?; + + // put the cursor back at cell 1 on current row + cursor::sys::move_to(0, location.y as u16)?; + Ok(()) +} + +fn clear_until_line(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> { + let (x, y) = (location.x, location.y); + + // location where to start clearing + let start_location = Coord::new(x, y); + + // get sum cells before cursor + let cells_to_write = (buffer_size.width - x as i16) as u32; + + // clear until the current line + clear_winapi(start_location, cells_to_write, current_attribute)?; + + // put the cursor back at original cursor position before we did the clearing + cursor::sys::move_to(x as u16, y as u16)?; + Ok(()) +} + +fn clear_winapi(start_location: Coord, cells_to_write: u32, current_attribute: u16) -> Result<()> { + let console = Console::from(Handle::current_out_handle()?); + console.fill_whit_character(start_location, cells_to_write, ' ')?; + console.fill_whit_attribute(start_location, cells_to_write, current_attribute)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use crossterm_winapi::ScreenBuffer; + + use super::{scroll_down, scroll_up, set_size, size}; + + #[test] + fn test_resize_winapi() { + let (width, height) = size().unwrap(); + + set_size(30, 30).unwrap(); + assert_eq!((30, 30), size().unwrap()); + + // reset to previous size + set_size(width, height).unwrap(); + assert_eq!((width, height), size().unwrap()); + } + + // Test is disabled, because it's failing on Travis CI + #[test] + #[ignore] + fn test_scroll_down_winapi() { + let current_window = ScreenBuffer::current() + .unwrap() + .info() + .unwrap() + .terminal_window(); + + scroll_down(2).unwrap(); + + let new_window = ScreenBuffer::current() + .unwrap() + .info() + .unwrap() + .terminal_window(); + + assert_eq!(new_window.top, current_window.top + 2); + assert_eq!(new_window.bottom, current_window.bottom + 2); + } + + // Test is disabled, because it's failing on Travis CI + #[test] + #[ignore] + fn test_scroll_up_winapi() { + // move the terminal buffer down before moving it up + test_scroll_down_winapi(); + + let current_window = ScreenBuffer::current() + .unwrap() + .info() + .unwrap() + .terminal_window(); + + scroll_up(2).unwrap(); + + let new_window = ScreenBuffer::current() + .unwrap() + .info() + .unwrap() + .terminal_window(); + + assert_eq!(new_window.top, current_window.top - 2); + assert_eq!(new_window.bottom, current_window.bottom - 2); + } +} diff --git a/src/terminal/terminal.rs b/src/terminal/terminal.rs deleted file mode 100644 index 0a06666..0000000 --- a/src/terminal/terminal.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! A module that contains all the actions related to the terminal. like clearing, resizing, pausing -//! and scrolling the terminal. -use crate::utils::Result; - -use super::ClearType; - -pub(crate) mod ansi; -#[cfg(windows)] -pub(crate) mod winapi; - -/// This trait defines the actions that can be performed with the terminal color. -/// This trait can be implemented so that an concrete implementation of the ITerminalColor can fulfill. -/// the wishes to work on an specific platform. -/// -/// ## For example: -/// -/// This trait is implemented for `WinApi` (Windows specific) and `ANSI` (Unix specific), -/// so that terminal related actions can be performed on both Unix and Windows systems. -pub(crate) trait Terminal { - /// Clear the current cursor by specifying the clear type - fn clear(&self, clear_type: ClearType) -> Result<()>; - /// Get the terminal size (x,y) - fn size(&self) -> Result<(u16, u16)>; - /// Scroll `n` lines up in the current terminal. - fn scroll_up(&self, count: u16) -> Result<()>; - /// Scroll `n` lines down in the current terminal. - fn scroll_down(&self, count: u16) -> Result<()>; - /// Resize terminal to the given width and height. - fn set_size(&self, width: u16, height: u16) -> Result<()>; -} diff --git a/src/terminal/terminal/ansi.rs b/src/terminal/terminal/ansi.rs deleted file mode 100644 index 87c90b8..0000000 --- a/src/terminal/terminal/ansi.rs +++ /dev/null @@ -1,117 +0,0 @@ -//! This is an `ANSI escape code` specific implementation for terminal related action. -//! This module is used for windows 10 terminals and unix terminals by default. - -use crate::utils::Result; -use crate::{csi, cursor, write_cout}; - -use super::{super::sys::get_terminal_size, ClearType, Terminal}; - -pub(crate) static CLEAR_ALL_CSI_SEQUENCE: &'static str = csi!("2J"); -pub(crate) static CLEAR_FROM_CURSOR_DOWN_CSI_SEQUENCE: &'static str = csi!("J"); -pub(crate) static CLEAR_FROM_CURSOR_UP_CSI_SEQUENCE: &'static str = csi!("1J"); -pub(crate) static CLEAR_FROM_CURRENT_LINE_CSI_SEQUENCE: &'static str = csi!("2K"); -pub(crate) static CLEAR_UNTIL_NEW_LINE_CSI_SEQUENCE: &'static str = csi!("K"); - -pub(crate) fn scroll_up_csi_sequence(count: u16) -> String { - format!(csi!("{}S"), count) -} - -pub(crate) fn scroll_down_csi_sequence(count: u16) -> String { - format!(csi!("{}T"), count) -} - -pub(crate) fn set_size_csi_sequence(width: u16, height: u16) -> String { - format!(csi!("8;{};{}t"), height, width) -} - -/// This struct is an ansi escape code implementation for terminal related actions. -pub(crate) struct AnsiTerminal; - -impl AnsiTerminal { - pub(crate) fn new() -> AnsiTerminal { - AnsiTerminal - } -} - -impl Terminal for AnsiTerminal { - fn clear(&self, clear_type: ClearType) -> Result<()> { - match clear_type { - ClearType::All => write_cout!(CLEAR_ALL_CSI_SEQUENCE)?, - ClearType::FromCursorDown => write_cout!(CLEAR_FROM_CURSOR_DOWN_CSI_SEQUENCE)?, - ClearType::FromCursorUp => write_cout!(CLEAR_FROM_CURSOR_UP_CSI_SEQUENCE)?, - ClearType::CurrentLine => write_cout!(CLEAR_FROM_CURRENT_LINE_CSI_SEQUENCE)?, - ClearType::UntilNewLine => write_cout!(CLEAR_UNTIL_NEW_LINE_CSI_SEQUENCE)?, - }; - - if clear_type == ClearType::All { - write_cout!(cursor::MoveTo(0, 0))?; - } - - Ok(()) - } - - fn size(&self) -> Result<(u16, u16)> { - get_terminal_size() - } - - fn scroll_up(&self, count: u16) -> Result<()> { - write_cout!(scroll_up_csi_sequence(count))?; - Ok(()) - } - - fn scroll_down(&self, count: u16) -> Result<()> { - write_cout!(scroll_down_csi_sequence(count))?; - Ok(()) - } - - fn set_size(&self, width: u16, height: u16) -> Result<()> { - write_cout!(set_size_csi_sequence(width, height))?; - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use std::{thread, time}; - - use super::{AnsiTerminal, Terminal}; - - // TODO - Test is disabled, because it's failing on Travis CI - #[test] - #[ignore] - fn test_resize_ansi() { - if try_enable_ansi() { - let terminal = AnsiTerminal::new(); - - let (width, height) = terminal.size().unwrap(); - - terminal.set_size(35, 35).unwrap(); - // see issue: https://github.com/eminence/terminal-size/issues/11 - thread::sleep(time::Duration::from_millis(30)); - assert_eq!((35, 35), terminal.size().unwrap()); - - // reset to previous size - terminal.set_size(width, height).unwrap(); - // see issue: https://github.com/eminence/terminal-size/issues/11 - thread::sleep(time::Duration::from_millis(30)); - assert_eq!((width, height), terminal.size().unwrap()); - } - } - - fn try_enable_ansi() -> bool { - #[cfg(windows)] - { - if cfg!(target_os = "windows") { - use crate::utils::sys::winapi::ansi::set_virtual_terminal_processing; - - // if it is not listed we should try with WinApi to check if we do support ANSI-codes. - match set_virtual_terminal_processing(true) { - Ok(_) => return true, - Err(_) => return false, - } - } - } - - true - } -} diff --git a/src/terminal/terminal/winapi.rs b/src/terminal/terminal/winapi.rs deleted file mode 100644 index 20e890a..0000000 --- a/src/terminal/terminal/winapi.rs +++ /dev/null @@ -1,282 +0,0 @@ -//! This is a `WINAPI` specific implementation for terminal related action. -//! This module is used for non supporting `ANSI` windows terminals. -//! -//! Windows versions lower then windows 10 are not supporting ANSI codes. Those versions -//! will use this implementation instead. - -use crossterm_winapi::{Console, Coord, Handle, ScreenBuffer, Size}; - -use crate::cursor; -use crate::utils::{ErrorKind, Result}; - -use super::{super::sys::winapi::get_terminal_size, ClearType, Terminal}; - -/// This struct is a winapi implementation for terminal related actions. -pub(crate) struct WinApiTerminal; - -impl WinApiTerminal { - pub(crate) fn new() -> Box { - Box::from(WinApiTerminal {}) - } -} - -impl Terminal for WinApiTerminal { - fn clear(&self, clear_type: ClearType) -> Result<()> { - let screen_buffer = ScreenBuffer::current()?; - let csbi = screen_buffer.info()?; - - let pos = csbi.cursor_pos(); - let buffer_size = csbi.buffer_size(); - let current_attribute = csbi.attributes(); - - match clear_type { - ClearType::All => { - clear_entire_screen(buffer_size, current_attribute)?; - } - ClearType::FromCursorDown => clear_after_cursor(pos, buffer_size, current_attribute)?, - ClearType::FromCursorUp => clear_before_cursor(pos, buffer_size, current_attribute)?, - ClearType::CurrentLine => clear_current_line(pos, buffer_size, current_attribute)?, - ClearType::UntilNewLine => clear_until_line(pos, buffer_size, current_attribute)?, - }; - Ok(()) - } - - fn size(&self) -> Result<(u16, u16)> { - get_terminal_size() - } - - fn scroll_up(&self, count: u16) -> Result<()> { - let csbi = ScreenBuffer::current()?; - let mut window = csbi.info()?.terminal_window(); - - // Check whether the window is too close to the screen buffer top - let count = count as i16; - if window.top >= count { - window.top -= count; // move top down - window.bottom = count; // move bottom down - - Console::new()?.set_console_info(false, window)?; - } - Ok(()) - } - - fn scroll_down(&self, count: u16) -> Result<()> { - let screen_buffer = ScreenBuffer::current()?; - let csbi = screen_buffer.info()?; - let mut window = csbi.terminal_window(); - let buffer_size = csbi.buffer_size(); - - // Check whether the window is too close to the screen buffer top - let count = count as i16; - if window.bottom < buffer_size.height - count { - window.top += count; // move top down - window.bottom += count; // move bottom down - - Console::new()?.set_console_info(false, window)?; - } - Ok(()) - } - - /// Set the current terminal size - fn set_size(&self, width: u16, height: u16) -> Result<()> { - if width <= 0 { - return Err(ErrorKind::ResizingTerminalFailure(String::from( - "Cannot set the terminal width lower than 1", - ))); - } - - if height <= 0 { - return Err(ErrorKind::ResizingTerminalFailure(String::from( - "Cannot set the terminal height lower then 1", - ))); - } - - // Get the position of the current console window - let screen_buffer = ScreenBuffer::current()?; - let console = Console::from(**screen_buffer.handle()); - let csbi = screen_buffer.info()?; - - let current_size = csbi.buffer_size(); - let window = csbi.terminal_window(); - - let mut new_size = Size::new(current_size.width, current_size.height); - - // If the buffer is smaller than this new window size, resize the - // buffer to be large enough. Include window position. - let mut resize_buffer = false; - - let width = width as i16; - if current_size.width < window.left + width { - if window.left >= i16::max_value() - width { - return Err(ErrorKind::ResizingTerminalFailure(String::from( - "Argument out of range when setting terminal width.", - ))); - } - - new_size.width = window.left + width; - resize_buffer = true; - } - let height = height as i16; - if current_size.height < window.top + height { - if window.top >= i16::max_value() - height { - return Err(ErrorKind::ResizingTerminalFailure(String::from( - "Argument out of range when setting terminal height.", - ))); - } - - new_size.height = window.top + height; - resize_buffer = true; - } - - if resize_buffer { - if let Err(_) = screen_buffer.set_size(new_size.width - 1, new_size.height - 1) { - return Err(ErrorKind::ResizingTerminalFailure(String::from( - "Something went wrong when setting screen buffer size.", - ))); - } - } - - let mut window = window.clone(); - // Preserve the position, but change the size. - window.bottom = window.top + height - 1; - window.right = window.left + width - 1; - console.set_console_info(true, window)?; - - // If we resized the buffer, un-resize it. - if resize_buffer { - if let Err(_) = screen_buffer.set_size(current_size.width - 1, current_size.height - 1) - { - return Err(ErrorKind::ResizingTerminalFailure(String::from( - "Something went wrong when setting screen buffer size.", - ))); - } - } - - let bounds = console.largest_window_size(); - - if width > bounds.x { - return Err(ErrorKind::ResizingTerminalFailure(format!( - "Argument width: {} out of range when setting terminal width.", - width - ))); - } - if height > bounds.y { - return Err(ErrorKind::ResizingTerminalFailure(format!( - "Argument height: {} out of range when setting terminal height", - width - ))); - } - - Ok(()) - } -} - -fn clear_after_cursor(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> { - let (mut x, mut y) = (location.x, location.y); - - // if cursor position is at the outer right position - if x as i16 > buffer_size.width { - y += 1; - x = 0; - } - - // location where to start clearing - let start_location = Coord::new(x, y); - - // get sum cells before cursor - let cells_to_write = buffer_size.width as u32 * buffer_size.height as u32; - - clear(start_location, cells_to_write, current_attribute) -} - -fn clear_before_cursor(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> { - let (xpos, ypos) = (location.x, location.y); - - // one cell after cursor position - let x = 0; - // one at row of cursor position - let y = 0; - - // location where to start clearing - let start_location = Coord::new(x, y); - - // get sum cells before cursor - let cells_to_write = (buffer_size.width as u32 * ypos as u32) + (xpos as u32 + 1); - - // clear everything before cursor position - clear(start_location, cells_to_write, current_attribute) -} - -fn clear_entire_screen(buffer_size: Size, current_attribute: u16) -> Result<()> { - // get sum cells before cursor - let cells_to_write = buffer_size.width as u32 * buffer_size.height as u32; - - // location where to start clearing - let start_location = Coord::new(0, 0); - - // clear the entire screen - clear(start_location, cells_to_write, current_attribute)?; - - // put the cursor back at cell 0,0 - cursor::sys::move_to(0, 0)?; - Ok(()) -} - -fn clear_current_line(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> { - // location where to start clearing - let start_location = Coord::new(0, location.y); - - // get sum cells before cursor - let cells_to_write = buffer_size.width as u32; - - // clear the whole current line - clear(start_location, cells_to_write, current_attribute)?; - - // put the cursor back at cell 1 on current row - cursor::sys::move_to(0, location.y as u16)?; - Ok(()) -} - -fn clear_until_line(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> { - let (x, y) = (location.x, location.y); - - // location where to start clearing - let start_location = Coord::new(x, y); - - // get sum cells before cursor - let cells_to_write = (buffer_size.width - x as i16) as u32; - - // clear until the current line - clear(start_location, cells_to_write, current_attribute)?; - - // put the cursor back at original cursor position before we did the clearing - cursor::sys::move_to(x as u16, y as u16)?; - Ok(()) -} - -fn clear(start_location: Coord, cells_to_write: u32, current_attribute: u16) -> Result<()> { - let console = Console::from(Handle::current_out_handle()?); - console.fill_whit_character(start_location, cells_to_write, ' ')?; - console.fill_whit_attribute(start_location, cells_to_write, current_attribute)?; - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::{Terminal, WinApiTerminal}; - - #[test] - fn test_resize_winapi() { - let terminal = WinApiTerminal::new(); - - let (width, height) = terminal.size().unwrap(); - - terminal.set_size(30, 30).unwrap(); - assert_eq!((30, 30), terminal.size().unwrap()); - - // reset to previous size - terminal.set_size(width, height).unwrap(); - assert_eq!((width, height), terminal.size().unwrap()); - } -} diff --git a/src/utils/sys/unix.rs b/src/utils/sys/unix.rs index 419fe61..6b347c4 100644 --- a/src/utils/sys/unix.rs +++ b/src/utils/sys/unix.rs @@ -20,7 +20,7 @@ pub fn is_raw_mode_enabled() -> bool { TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap().is_some() } -fn wrap_with_result(t: i32) -> Result<()> { +pub fn wrap_with_result(t: i32) -> Result<()> { if t == -1 { Err(ErrorKind::IoError(io::Error::last_os_error())) } else {