Terminal remove Deprecated Api (#293)
This commit is contained in:
parent
ea7130a419
commit
3ab5b170aa
@ -14,12 +14,17 @@
|
|||||||
- Rename `ResetPos` to `ResetPosition`
|
- Rename `ResetPos` to `ResetPosition`
|
||||||
- Rename `SavePos` to `SavePosition`
|
- Rename `SavePos` to `SavePosition`
|
||||||
- Remove re-export cursor module types at root level, are now accessible from `crossterm::cursor`
|
- 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 `ObjectStyle` to `ContentStyle`. Now full names are used for methods.
|
||||||
- Rename `StyledObject` to `StyledContent` and made members private.
|
- Rename `StyledObject` to `StyledContent` and made members private.
|
||||||
- Rename `attr` method to `attribute`.
|
- Rename `attr` method to `attribute`.
|
||||||
- Rename `Attribute::NoInverse` to `NoReverse`
|
- Rename `Attribute::NoInverse` to `NoReverse`
|
||||||
|
|
||||||
# Version 0.12.1
|
# Version 0.12.1
|
||||||
|
|
||||||
- All the `crossterm_` crates code was moved to the `crossterm` crate
|
- All the `crossterm_` crates code was moved to the `crossterm` crate
|
||||||
|
@ -16,12 +16,6 @@ impl Crossterm {
|
|||||||
crate::input::TerminalInput::new()
|
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`.
|
/// Creates a new `TerminalColor`.
|
||||||
#[cfg(feature = "style")]
|
#[cfg(feature = "style")]
|
||||||
pub fn color(&self) -> crate::style::TerminalColor {
|
pub fn color(&self) -> crate::style::TerminalColor {
|
||||||
|
@ -45,11 +45,10 @@
|
|||||||
pub use sys::position;
|
pub use sys::position;
|
||||||
|
|
||||||
use crate::impl_display;
|
use crate::impl_display;
|
||||||
|
use crate::utils::Command;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use crate::utils::Result;
|
use crate::utils::Result;
|
||||||
|
|
||||||
use crate::utils::Command;
|
|
||||||
|
|
||||||
mod ansi;
|
mod ansi;
|
||||||
pub(crate) mod sys;
|
pub(crate) mod sys;
|
||||||
|
|
||||||
@ -296,11 +295,13 @@ impl_display!(for DisableBlinking);
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::io::{self, stdout, Write};
|
||||||
|
|
||||||
|
use crate::execute;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
position, MoveDown, MoveLeft, MoveRight, MoveTo, MoveUp, RestorePosition, SavePosition,
|
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 is disabled, because it's failing on Travis
|
||||||
#[test]
|
#[test]
|
||||||
|
18
src/lib.rs
18
src/lib.rs
@ -118,21 +118,21 @@
|
|||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use std::io::{stdout, Write};
|
//! use std::io::{stdout, Write};
|
||||||
//! use crossterm::{
|
//! use crossterm::{
|
||||||
//! ExecutableCommand, QueueableCommand, Color, PrintStyledFont,
|
//! ExecutableCommand, QueueableCommand, Color,
|
||||||
//! Colorize, Clear, ClearType, cursor::MoveTo, Result
|
//! Colorize, terminal, cursor, style, Result
|
||||||
//! };
|
//! };
|
||||||
//!
|
//!
|
||||||
//! fn main() -> Result<()> {
|
//! fn main() -> Result<()> {
|
||||||
//! let mut stdout = stdout();
|
//! let mut stdout = stdout();
|
||||||
//!
|
//!
|
||||||
//! stdout.execute(Clear(ClearType::All))?;
|
//! stdout.execute(terminal::Clear(terminal::ClearType::All))?;
|
||||||
//!
|
//!
|
||||||
//! for y in 0..40 {
|
//! for y in 0..40 {
|
||||||
//! for x in 0..150 {
|
//! for x in 0..150 {
|
||||||
//! if (y == 0 || y == 40 - 1) || (x == 0 || x == 150 - 1) {
|
//! if (y == 0 || y == 40 - 1) || (x == 0 || x == 150 - 1) {
|
||||||
//! stdout
|
//! stdout
|
||||||
//! .queue(MoveTo(x,y))?
|
//! .queue(cursor::MoveTo(x,y))?
|
||||||
//! .queue(PrintStyledFont( "█".magenta()))?;
|
//! .queue(style::PrintStyledFont( "█".magenta()))?;
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
@ -147,18 +147,18 @@
|
|||||||
//! use std::io::{stdout, Write};
|
//! use std::io::{stdout, Write};
|
||||||
//! use crossterm::{
|
//! use crossterm::{
|
||||||
//! execute, queue, Color, PrintStyledFont,
|
//! execute, queue, Color, PrintStyledFont,
|
||||||
//! Colorize, cursor::MoveTo, Clear, ClearType, Result
|
//! Colorize, cursor, terminal, style, Result
|
||||||
//! };
|
//! };
|
||||||
//!
|
//!
|
||||||
//! fn main() -> Result<()> {
|
//! fn main() -> Result<()> {
|
||||||
//! let mut stdout = stdout();
|
//! let mut stdout = stdout();
|
||||||
//!
|
//!
|
||||||
//! execute!(stdout, Clear(ClearType::All))?;
|
//! execute!(stdout, terminal::Clear(terminal::ClearType::All))?;
|
||||||
//!
|
//!
|
||||||
//! for y in 0..40 {
|
//! for y in 0..40 {
|
||||||
//! for x in 0..150 {
|
//! for x in 0..150 {
|
||||||
//! if (y == 0 || y == 40 - 1) || (x == 0 || x == 150 - 1) {
|
//! 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,
|
color, style, Attribute, Color, Colored, Colorize, ContentStyle, PrintStyledFont, ResetColor,
|
||||||
SetAttr, SetBg, SetFg, StyledContent, Styler, TerminalColor,
|
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 utils::{Command, ErrorKind, ExecutableCommand, Output, QueueableCommand, Result};
|
||||||
|
|
||||||
pub use self::crossterm::Crossterm;
|
pub use self::crossterm::Crossterm;
|
||||||
|
258
src/terminal.rs
258
src/terminal.rs
@ -1,42 +1,23 @@
|
|||||||
//! # Terminal
|
//! # 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
|
//! 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
|
//! obvious how to use this crate. Although, we do provide
|
||||||
//! [examples](https://github.com/crossterm-rs/examples) repository
|
//! [examples](https://github.com/crossterm-rs/examples) repository
|
||||||
//! to demonstrate the capabilities.
|
//! 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
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```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 std::io::{stdout, Write};
|
||||||
//! use crossterm::{execute, Result, ScrollUp, SetSize, Terminal};
|
//! use crossterm::{execute, Result, terminal::{ScrollUp, SetSize, size}};
|
||||||
//!
|
//!
|
||||||
//! fn main() -> Result<()> {
|
//! fn main() -> Result<()> {
|
||||||
//! // Get a terminal, save size
|
//! let (cols, rows) = size()?;
|
||||||
//! let terminal = Terminal::new();
|
|
||||||
//! let (cols, rows) = terminal.size()?;
|
|
||||||
//!
|
|
||||||
//! // Do something with the terminal
|
//! // Do something with the terminal
|
||||||
//! execute!(
|
//! execute!(
|
||||||
//! stdout(),
|
//! stdout(),
|
||||||
@ -45,27 +26,26 @@
|
|||||||
//! )?;
|
//! )?;
|
||||||
//!
|
//!
|
||||||
//! // Be a good citizen, cleanup
|
//! // 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")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[cfg(windows)]
|
use crate::impl_display;
|
||||||
use crate::utils::supports_ansi;
|
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
use crate::utils::{Command, Result};
|
use crate::utils::Command;
|
||||||
use crate::{impl_display, write_cout};
|
|
||||||
|
|
||||||
use self::terminal::ansi::AnsiTerminal;
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use self::terminal::winapi::WinApiTerminal;
|
use crate::utils::Result;
|
||||||
use self::terminal::Terminal as TerminalTrait;
|
|
||||||
|
|
||||||
|
mod ansi;
|
||||||
mod sys;
|
mod sys;
|
||||||
mod terminal;
|
|
||||||
|
|
||||||
/// Represents different options how to clear the terminal.
|
/// Represents different options how to clear the terminal.
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
@ -83,128 +63,7 @@ pub enum ClearType {
|
|||||||
UntilNewLine,
|
UntilNewLine,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A terminal.
|
/// A command that scrolls the terminal screen a given number of rows up.
|
||||||
///
|
|
||||||
/// 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.
|
|
||||||
///
|
///
|
||||||
/// # Notes
|
/// # Notes
|
||||||
///
|
///
|
||||||
@ -215,16 +74,16 @@ impl Command for ScrollUp {
|
|||||||
type AnsiType = String;
|
type AnsiType = String;
|
||||||
|
|
||||||
fn ansi_code(&self) -> Self::AnsiType {
|
fn ansi_code(&self) -> Self::AnsiType {
|
||||||
terminal::ansi::scroll_up_csi_sequence(self.0)
|
ansi::scroll_up_csi_sequence(self.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn execute_winapi(&self) -> Result<()> {
|
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
|
/// # Notes
|
||||||
///
|
///
|
||||||
@ -235,16 +94,16 @@ impl Command for ScrollDown {
|
|||||||
type AnsiType = String;
|
type AnsiType = String;
|
||||||
|
|
||||||
fn ansi_code(&self) -> Self::AnsiType {
|
fn ansi_code(&self) -> Self::AnsiType {
|
||||||
terminal::ansi::scroll_down_csi_sequence(self.0)
|
ansi::scroll_down_csi_sequence(self.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn execute_winapi(&self) -> Result<()> {
|
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.
|
/// See the [`ClearType`](enum.ClearType.html) enum.
|
||||||
///
|
///
|
||||||
@ -258,21 +117,21 @@ impl Command for Clear {
|
|||||||
|
|
||||||
fn ansi_code(&self) -> Self::AnsiType {
|
fn ansi_code(&self) -> Self::AnsiType {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
ClearType::All => terminal::ansi::CLEAR_ALL_CSI_SEQUENCE,
|
ClearType::All => ansi::CLEAR_ALL_CSI_SEQUENCE,
|
||||||
ClearType::FromCursorDown => terminal::ansi::CLEAR_FROM_CURSOR_DOWN_CSI_SEQUENCE,
|
ClearType::FromCursorDown => ansi::CLEAR_FROM_CURSOR_DOWN_CSI_SEQUENCE,
|
||||||
ClearType::FromCursorUp => terminal::ansi::CLEAR_FROM_CURSOR_UP_CSI_SEQUENCE,
|
ClearType::FromCursorUp => ansi::CLEAR_FROM_CURSOR_UP_CSI_SEQUENCE,
|
||||||
ClearType::CurrentLine => terminal::ansi::CLEAR_FROM_CURRENT_LINE_CSI_SEQUENCE,
|
ClearType::CurrentLine => ansi::CLEAR_FROM_CURRENT_LINE_CSI_SEQUENCE,
|
||||||
ClearType::UntilNewLine => terminal::ansi::CLEAR_UNTIL_NEW_LINE_CSI_SEQUENCE,
|
ClearType::UntilNewLine => ansi::CLEAR_UNTIL_NEW_LINE_CSI_SEQUENCE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn execute_winapi(&self) -> Result<()> {
|
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
|
/// # Notes
|
||||||
///
|
///
|
||||||
@ -283,12 +142,12 @@ impl Command for SetSize {
|
|||||||
type AnsiType = String;
|
type AnsiType = String;
|
||||||
|
|
||||||
fn ansi_code(&self) -> Self::AnsiType {
|
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)]
|
#[cfg(windows)]
|
||||||
fn execute_winapi(&self) -> Result<()> {
|
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 ScrollDown);
|
||||||
impl_display!(for SetSize);
|
impl_display!(for SetSize);
|
||||||
impl_display!(for Clear);
|
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
21
src/terminal/ansi.rs
Normal 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)
|
||||||
|
}
|
@ -1,10 +1,14 @@
|
|||||||
|
//! This module provides platform related functions.
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub(crate) use self::unix::{exit, get_terminal_size};
|
pub use self::unix::{exit, size};
|
||||||
#[cfg(windows)]
|
#[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)]
|
#[cfg(windows)]
|
||||||
pub(crate) mod winapi;
|
pub(crate) mod windows;
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub(crate) mod unix;
|
pub(crate) mod unix;
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
|
//! UNIX related logic for terminal manipulation.
|
||||||
|
|
||||||
use libc::{ioctl, winsize, STDOUT_FILENO, TIOCGWINSZ};
|
use libc::{ioctl, winsize, STDOUT_FILENO, TIOCGWINSZ};
|
||||||
|
|
||||||
|
use crate::utils::sys::unix::wrap_with_result;
|
||||||
use crate::utils::Result;
|
use crate::utils::Result;
|
||||||
|
|
||||||
pub(crate) fn exit() {
|
/// Exits the current application.
|
||||||
|
pub fn exit() {
|
||||||
::std::process::exit(0);
|
::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
|
// http://rosettacode.org/wiki/Terminal_control/Dimensions#Library:_BSD_libc
|
||||||
let mut size = winsize {
|
let mut size = winsize {
|
||||||
ws_row: 0,
|
ws_row: 0,
|
||||||
@ -14,10 +21,10 @@ pub(crate) fn get_terminal_size() -> Result<(u16, u16)> {
|
|||||||
ws_xpixel: 0,
|
ws_xpixel: 0,
|
||||||
ws_ypixel: 0,
|
ws_ypixel: 0,
|
||||||
};
|
};
|
||||||
let r = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut size) };
|
|
||||||
|
|
||||||
if r == 0 {
|
if let Ok(()) = wrap_with_result(unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut size) })
|
||||||
Ok((size.ws_col, size.ws_row))
|
{
|
||||||
|
return Ok((size.ws_col, size.ws_row));
|
||||||
} else {
|
} else {
|
||||||
Err(std::io::Error::last_os_error().into())
|
Err(std::io::Error::last_os_error().into())
|
||||||
}
|
}
|
||||||
|
@ -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
325
src/terminal/sys/windows.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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<()>;
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,7 +20,7 @@ pub fn is_raw_mode_enabled() -> bool {
|
|||||||
TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap().is_some()
|
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 {
|
if t == -1 {
|
||||||
Err(ErrorKind::IoError(io::Error::last_os_error()))
|
Err(ErrorKind::IoError(io::Error::last_os_error()))
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user