299 lines
9.5 KiB
Rust
299 lines
9.5 KiB
Rust
//! 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_cursor::sys::winapi::Cursor;
|
|
use crossterm_utils::{ErrorKind, Result};
|
|
use crossterm_winapi::{Console, Coord, Handle, ScreenBuffer, Size};
|
|
|
|
use crate::sys::winapi::get_terminal_size;
|
|
|
|
use super::{ClearType, ITerminal};
|
|
|
|
/// This struct is a winapi implementation for terminal related actions.
|
|
pub struct WinApiTerminal;
|
|
|
|
impl WinApiTerminal {
|
|
pub fn new() -> Box<WinApiTerminal> {
|
|
Box::from(WinApiTerminal {})
|
|
}
|
|
}
|
|
|
|
impl ITerminal 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.get_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, new_size.height) {
|
|
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;
|
|
window.right = window.left + width;
|
|
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, current_size.height) {
|
|
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(())
|
|
}
|
|
}
|
|
|
|
pub 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)
|
|
}
|
|
|
|
pub 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)
|
|
}
|
|
|
|
pub 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
|
|
let cursor = Cursor::new()?;
|
|
cursor.goto(0, 0)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub 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
|
|
let cursor = Cursor::new()?;
|
|
cursor.goto(0, location.y)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub 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
|
|
let cursor = Cursor::new()?;
|
|
cursor.goto(x, y)?;
|
|
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::{ITerminal, WinApiTerminal};
|
|
|
|
// TODO - Test is ignored, because it returns wrong result (31 != 30)
|
|
#[test]
|
|
#[ignore]
|
|
fn resize_winapi() {
|
|
let terminal = WinApiTerminal::new();
|
|
|
|
assert!(terminal.set_size(30, 30).is_ok());
|
|
|
|
let size = terminal.size();
|
|
assert!(size.is_ok());
|
|
let (x, y) = size.unwrap();
|
|
|
|
assert_eq!(x, 30);
|
|
assert_eq!(y, 30);
|
|
}
|
|
}
|