Terminal remove Deprecated Api (#293)

This commit is contained in:
Timon 2019-10-29 09:14:47 +01:00 committed by GitHub
parent ea7130a419
commit 3ab5b170aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 471 additions and 649 deletions

View File

@ -14,7 +14,12 @@
- 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`.

View File

@ -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 {

View File

@ -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]

View File

@ -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;

View File

@ -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<D: fmt::Display>(&self, value: D) -> Result<usize> {
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
}
}

21
src/terminal/ansi.rs Normal file
View File

@ -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)
}

View File

@ -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;

View File

@ -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())
}

View File

@ -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,
))
}

325
src/terminal/sys/windows.rs Normal file
View File

@ -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);
}
}

View File

@ -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<()>;
}

View File

@ -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
}
}

View File

@ -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<WinApiTerminal> {
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());
}
}

View File

@ -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 {