added unittests, fixed getting terminal position and tested code
This commit is contained in:
parent
61778a23a6
commit
5dcc6387f1
@ -16,7 +16,6 @@ mod some_types;
|
|||||||
mod input;
|
mod input;
|
||||||
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::{thread,time};
|
|
||||||
|
|
||||||
use crossterm::style::{style, Color, DisplayableObject};
|
use crossterm::style::{style, Color, DisplayableObject};
|
||||||
use crossterm::terminal::terminal;
|
use crossterm::terminal::terminal;
|
||||||
@ -25,16 +24,28 @@ use crossterm::Screen;
|
|||||||
use crossterm::output::TerminalOutput;
|
use crossterm::output::TerminalOutput;
|
||||||
use crossterm::cursor::TerminalCursor;
|
use crossterm::cursor::TerminalCursor;
|
||||||
|
|
||||||
|
use crossterm::terminal::Terminal;
|
||||||
|
use std::{thread,time};
|
||||||
|
|
||||||
fn main()
|
fn main()
|
||||||
{
|
{
|
||||||
let screen = Screen::default();
|
let screen = Screen::new(false);
|
||||||
let cursor = TerminalCursor::new(&screen.stdout);
|
let terminal = Terminal::new(&screen.stdout);
|
||||||
|
|
||||||
cursor.goto(5, 5);
|
// get terminal size
|
||||||
let (x, y) = cursor.pos();
|
let (x, y) = terminal.terminal_size();
|
||||||
|
|
||||||
assert_eq!(x, 5);
|
// set size to 30, 50
|
||||||
assert_eq!(y, 5);
|
terminal.set_size(30,50);
|
||||||
|
|
||||||
println!("x: {} y: {}", x,y);
|
// if we uncomment the line below the code will work perfectly fine and we will get the new dimensions.
|
||||||
|
// if we comment this line the terminal dimensions gotten from terminal_size() are equal to the old dimensions.
|
||||||
|
|
||||||
|
// thread::sleep(time::Duration::from_millis(20));
|
||||||
|
|
||||||
|
// get new dimensions
|
||||||
|
let (x_new, y_new) = terminal.terminal_size();
|
||||||
|
|
||||||
|
println!("old width: {} old height: {}", x, y);
|
||||||
|
println!("new width: {} new height: {}", x_new, y_new);
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,8 @@ impl NoncanonicalModeCommand {
|
|||||||
"Could not set console mode when enabling raw mode",
|
"Could not set console mode when enabling raw mode",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe { terminal::RAW_MODE_ENABLED_BY_USER = true }
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,6 +58,7 @@ impl NoncanonicalModeCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe { terminal::RAW_MODE_ENABLED_BY_USER = false }
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,12 +75,16 @@ impl RawModeCommand {
|
|||||||
/// Enables raw mode.
|
/// Enables raw mode.
|
||||||
pub fn enable(&mut self) -> Result<()> {
|
pub fn enable(&mut self) -> Result<()> {
|
||||||
terminal::into_raw_mode();
|
terminal::into_raw_mode();
|
||||||
|
|
||||||
|
unsafe { terminal::RAW_MODE_ENABLED_BY_USER = true }
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disables raw mode.
|
/// Disables raw mode.
|
||||||
pub fn disable(&mut self) -> Result<()> {
|
pub fn disable(&mut self) -> Result<()> {
|
||||||
terminal::disable_raw_mode();
|
terminal::disable_raw_mode();
|
||||||
|
|
||||||
|
unsafe { terminal::RAW_MODE_ENABLED_BY_USER = false }
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,8 @@ pub fn get_terminal_size() -> (u16, u16) {
|
|||||||
/// Get the cursor position based on the current platform.
|
/// Get the cursor position based on the current platform.
|
||||||
pub fn get_cursor_position(stdout: &Arc<TerminalOutput>) -> (u16, u16) {
|
pub fn get_cursor_position(stdout: &Arc<TerminalOutput>) -> (u16, u16) {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
return pos(stdout);
|
return pos().expect("Valide position");
|
||||||
|
// return pos().unwrap_or_else(|x| { return (0,0) });
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
return pos();
|
return pos();
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
//! This module contains all `unix` specific terminal related logic.
|
//! This module contains all `unix` specific terminal related logic.
|
||||||
|
|
||||||
use self::libc::{c_int, c_ushort, ioctl, STDOUT_FILENO, TIOCGWINSZ};
|
use self::libc::{c_int, c_ushort, ioctl, STDOUT_FILENO, TIOCGWINSZ, TIOCSWINSZ, signal, SIG_IGN, SIGWINCH};
|
||||||
use common::commands::unix_command::NoncanonicalModeCommand;
|
|
||||||
|
use common::commands::unix_command::RawModeCommand;
|
||||||
use {libc, TerminalOutput, Screen};
|
use {libc, TerminalOutput, Screen};
|
||||||
pub use libc::termios;
|
pub use libc::termios;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::io::{self, Error, ErrorKind, Read};
|
use std::io::{self, Error, ErrorKind, Read, Write};
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
use std::{fs, mem};
|
use std::{fs, mem};
|
||||||
use termios::{cfmakeraw, tcsetattr, Termios, TCSADRAIN};
|
use termios::{cfmakeraw, tcsetattr, Termios, TCSADRAIN, CREAD, ECHO, ICANON, TCSAFLUSH};
|
||||||
|
|
||||||
|
const FD_STDIN: ::std::os::unix::io::RawFd = 1;
|
||||||
|
|
||||||
use Crossterm;
|
use Crossterm;
|
||||||
|
use input::input;
|
||||||
|
|
||||||
/// A representation of the size of the current terminal.
|
/// A representation of the size of the current terminal.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@ -35,58 +39,115 @@ pub fn terminal_size() -> (u16, u16) {
|
|||||||
ws_xpixel: 0,
|
ws_xpixel: 0,
|
||||||
ws_ypixel: 0,
|
ws_ypixel: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let r = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ, &us) };
|
let r = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ, &us) };
|
||||||
|
|
||||||
if r == 0 {
|
if r == 0 {
|
||||||
// because crossterm works starts counting at 0 and unix terminal starts at cell 1 you have subtract one to get 0-based results.
|
// because crossterm works starts counting at 0 and unix terminal starts at cell 1 you have subtract one to get 0-based results.
|
||||||
(us.cols - 1, us.rows - 1)
|
(us.cols, us.rows)
|
||||||
} else {
|
} else {
|
||||||
(0, 0)
|
(0,0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current cursor position.
|
// maybe this coudl be used over ANSI escape code
|
||||||
pub fn pos(stdout: &Arc<TerminalOutput>) -> (u16, u16) {
|
//pub fn set_terminal_size() -> io::Result<(u16,u16)>
|
||||||
let mut crossterm = Crossterm::new(&Screen::default());
|
//{
|
||||||
let input = crossterm.input();
|
// let new_size = UnixSize {
|
||||||
|
// rows: 40,
|
||||||
|
// cols: 40,
|
||||||
|
// ws_xpixel: 0,
|
||||||
|
// ws_ypixel: 0,
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// let r = unsafe { ioctl(STDOUT_FILENO, TIOCSWINSZ, &new_size) };
|
||||||
|
//
|
||||||
|
// if r == 0 {
|
||||||
|
// // because crossterm works starts counting at 0 and unix terminal starts at cell 1 you have subtract one to get 0-based results.
|
||||||
|
// (us.cols, us.rows)
|
||||||
|
// } else {
|
||||||
|
// Err(ErrorKind::Other, "Could not resize try ansi escape codes")
|
||||||
|
// (0, 0)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
let delimiter = b'R';
|
pub fn pos() -> io::Result<(u16, u16)>
|
||||||
let mut stdin = input.read_until_async(delimiter);
|
{
|
||||||
|
let screen = Screen::new(false);
|
||||||
|
|
||||||
// Where is the cursor?
|
// if we enable raw modes with screen, this could cause problems if raw mode is already enabled in applicaition.
|
||||||
// Use `ESC [ 6 n`.
|
// I am not completely happy with this approach so feel free to find an other way.
|
||||||
stdout.write_str("\x1B[6n");
|
|
||||||
|
|
||||||
let mut buf: [u8; 1] = [0];
|
unsafe {
|
||||||
let mut read_chars = Vec::new();
|
if !RAW_MODE_ENABLED_BY_USER || !RAW_MODE_ENABLED_BY_SYSTEM {
|
||||||
|
// set this boolean so that we know that the systems has enabled raw mode.
|
||||||
let timeout = Duration::from_millis(2000);
|
RAW_MODE_ENABLED_BY_SYSTEM = true;
|
||||||
let now = SystemTime::now();
|
into_raw_mode();
|
||||||
|
|
||||||
// Either consume all data up to R or wait for a timeout.
|
|
||||||
while buf[0] != delimiter && now.elapsed().unwrap() < timeout {
|
|
||||||
if let Ok(c) = stdin.read(&mut buf) {
|
|
||||||
if c >= 0 {
|
|
||||||
read_chars.push(buf[0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if read_chars.len() == 0 {
|
// Where is the cursor?
|
||||||
return (0, 0);
|
// Use `ESC [ 6 n`.
|
||||||
|
let mut stdout = io::stdout();
|
||||||
|
|
||||||
|
// Write command
|
||||||
|
stdout.write(b"\x1B[6n")?;
|
||||||
|
stdout.flush()?;
|
||||||
|
|
||||||
|
let mut buf = [0u8; 2];
|
||||||
|
|
||||||
|
// Expect `ESC[`
|
||||||
|
io::stdin().read_exact(&mut buf)?;
|
||||||
|
if buf[0] != 0x1B || buf[1] as char != '[' {
|
||||||
|
return Err(Error::new(ErrorKind::Other, "test"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// The answer will look like `ESC [ Cy ; Cx R`.
|
// Read rows and cols through a ad-hoc integer parsing function
|
||||||
|
let read_num: fn() -> Result<(i32, char), Error> = || -> Result<(i32, char), Error> {
|
||||||
|
let mut num = 0;
|
||||||
|
let mut c: char;
|
||||||
|
|
||||||
read_chars.pop(); // remove trailing R.
|
loop {
|
||||||
let read_str = String::from_utf8(read_chars).unwrap();
|
let mut buf = [0u8; 1];
|
||||||
let beg = read_str.rfind('[').unwrap();
|
io::stdin().read_exact(&mut buf)?;
|
||||||
let coords: String = read_str.chars().skip(beg + 1).collect();
|
c = buf[0] as char;
|
||||||
let mut nums = coords.split(';');
|
if let Some(d) = c.to_digit(10) {
|
||||||
|
num = if num == 0 { 0 } else { num * 10 };
|
||||||
|
num += d as i32;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let cy = nums.next().unwrap().parse::<u16>().unwrap();
|
Ok((num, c))
|
||||||
let cx = nums.next().unwrap().parse::<u16>().unwrap();
|
};
|
||||||
|
|
||||||
(cx, cy)
|
// Read rows and expect `;`
|
||||||
|
let (rows, c) = read_num()?;
|
||||||
|
if c != ';' {
|
||||||
|
return Err(Error::new(ErrorKind::Other, "test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read cols
|
||||||
|
let (cols, c) = read_num()?;
|
||||||
|
|
||||||
|
// Expect `R`
|
||||||
|
let res = if c == 'R' {
|
||||||
|
Ok(((cols -1) as u16, (rows -1) as u16))
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(ErrorKind::Other, "test"));
|
||||||
|
};
|
||||||
|
|
||||||
|
// If raw mode is enabled from else where in the application (by the user) we do not want to disable raw modes.
|
||||||
|
// I am not completely happy with this approach so feel free to find an other way.
|
||||||
|
unsafe {
|
||||||
|
if RAW_MODE_ENABLED_BY_SYSTEM && !RAW_MODE_ENABLED_BY_USER {
|
||||||
|
RAW_MODE_ENABLED_BY_SYSTEM = false;
|
||||||
|
disable_raw_mode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the terminal mode to the given mode.
|
/// Set the terminal mode to the given mode.
|
||||||
@ -106,6 +167,8 @@ pub fn make_raw(termios: &mut Termios) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static mut ORIGINAL_TERMINAL_MODE: Option<Termios> = None;
|
static mut ORIGINAL_TERMINAL_MODE: Option<Termios> = None;
|
||||||
|
static mut RAW_MODE_ENABLED_BY_SYSTEM: bool = false;
|
||||||
|
pub static mut RAW_MODE_ENABLED_BY_USER: bool = false;
|
||||||
|
|
||||||
pub fn into_raw_mode() -> io::Result<()>
|
pub fn into_raw_mode() -> io::Result<()>
|
||||||
{
|
{
|
||||||
@ -132,6 +195,8 @@ pub fn into_raw_mode() -> io::Result<()>
|
|||||||
|
|
||||||
make_raw(&mut termios);
|
make_raw(&mut termios);
|
||||||
tcsetattr(fd, TCSADRAIN, &termios)?;
|
tcsetattr(fd, TCSADRAIN, &termios)?;
|
||||||
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use modules::cursor::winapi_cursor::WinApiCursor;
|
|
||||||
use modules::cursor::ansi_cursor::AnsiCursor;
|
use modules::cursor::ansi_cursor::AnsiCursor;
|
||||||
|
|
||||||
use modules::cursor::ITerminalCursor;
|
use modules::cursor::ITerminalCursor;
|
||||||
@ -8,6 +7,7 @@ use Screen;
|
|||||||
/* ======================== WinApi =========================== */
|
/* ======================== WinApi =========================== */
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
mod winapi_tests {
|
mod winapi_tests {
|
||||||
|
use modules::cursor::winapi_cursor::WinApiCursor;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -79,12 +79,14 @@ fn goto_ansi()
|
|||||||
|
|
||||||
fn try_enable_ansi() -> bool
|
fn try_enable_ansi() -> bool
|
||||||
{
|
{
|
||||||
if cfg!(target_os = "windows") {
|
#[cfg(windows)]
|
||||||
#[cfg(windows)]
|
{
|
||||||
use kernel::windows_kernel::ansi_support::try_enable_ansi_support;
|
if cfg!(target_os = "windows") {
|
||||||
|
use kernel::windows_kernel::ansi_support::try_enable_ansi_support;
|
||||||
|
|
||||||
if !try_enable_ansi_support()
|
if !try_enable_ansi_support()
|
||||||
{ return false; }
|
{ return false; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -46,11 +46,6 @@ pub struct AsyncReader {
|
|||||||
recv: mpsc::Receiver<io::Result<u8>>,
|
recv: mpsc::Receiver<io::Result<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncReader
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for AsyncReader {
|
impl Read for AsyncReader {
|
||||||
/// Read from the byte stream.
|
/// Read from the byte stream.
|
||||||
///
|
///
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use modules::output::winapi_output::WinApiOutput;
|
|
||||||
use modules::output::ansi_output::AnsiOutput;
|
use modules::output::ansi_output::AnsiOutput;
|
||||||
|
|
||||||
use modules::output::IStdout;
|
use modules::output::IStdout;
|
||||||
@ -7,7 +6,7 @@ use Screen;
|
|||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
mod winapi_tests {
|
mod winapi_tests {
|
||||||
|
use modules::output::winapi_output::WinApiOutput;
|
||||||
use super::*;
|
use super::*;
|
||||||
/* ======================== WinApi =========================== */
|
/* ======================== WinApi =========================== */
|
||||||
#[test]
|
#[test]
|
||||||
@ -66,13 +65,15 @@ fn is_valid_write(result: ::std::io::Result<usize>, str_length: usize)
|
|||||||
|
|
||||||
fn try_enable_ansi() -> bool
|
fn try_enable_ansi() -> bool
|
||||||
{
|
{
|
||||||
if cfg!(target_os = "windows") {
|
#[cfg(windows)]
|
||||||
#[cfg(windows)]
|
{
|
||||||
use kernel::windows_kernel::ansi_support::try_enable_ansi_support;
|
if cfg!(target_os = "windows") {
|
||||||
|
use kernel::windows_kernel::ansi_support::try_enable_ansi_support;
|
||||||
|
|
||||||
if !try_enable_ansi_support()
|
if !try_enable_ansi_support()
|
||||||
{ return false; }
|
{ return false; }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
@ -7,6 +7,7 @@ use super::*;
|
|||||||
pub struct AnsiTerminal;
|
pub struct AnsiTerminal;
|
||||||
use cursor::TerminalCursor;
|
use cursor::TerminalCursor;
|
||||||
|
|
||||||
|
|
||||||
impl AnsiTerminal {
|
impl AnsiTerminal {
|
||||||
pub fn new() -> AnsiTerminal {
|
pub fn new() -> AnsiTerminal {
|
||||||
AnsiTerminal {}
|
AnsiTerminal {}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use modules::terminal::winapi_terminal::WinApiTerminal;
|
|
||||||
use modules::terminal::ansi_terminal::AnsiTerminal;
|
use modules::terminal::ansi_terminal::AnsiTerminal;
|
||||||
|
|
||||||
use modules::terminal::ITerminal;
|
use modules::terminal::ITerminal;
|
||||||
@ -8,6 +7,7 @@ use Screen;
|
|||||||
/* ======================== WinApi =========================== */
|
/* ======================== WinApi =========================== */
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
mod winapi_tests {
|
mod winapi_tests {
|
||||||
|
use modules::terminal::winapi_terminal::WinApiTerminal;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -29,28 +29,34 @@ mod winapi_tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn resize_ansi()
|
fn resize_ansi()
|
||||||
{
|
{
|
||||||
|
use std::{thread, time};
|
||||||
if try_enable_ansi() {
|
if try_enable_ansi() {
|
||||||
let screen = Screen::default();
|
let screen = Screen::default();
|
||||||
let terminal = WinApiTerminal::new();
|
let terminal = AnsiTerminal::new();
|
||||||
|
|
||||||
terminal.set_size(10,10, &screen.stdout);
|
terminal.set_size(50,50, &screen.stdout);
|
||||||
|
|
||||||
|
// see issue: https://github.com/eminence/terminal-size/issues/11
|
||||||
|
thread::sleep(time::Duration::from_millis(30));
|
||||||
|
|
||||||
let (x, y) = terminal.terminal_size(&screen.stdout);
|
let (x, y) = terminal.terminal_size(&screen.stdout);
|
||||||
|
|
||||||
assert_eq!(x, 10);
|
assert_eq!(x, 50);
|
||||||
assert_eq!(y, 10);
|
assert_eq!(y, 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_enable_ansi() -> bool
|
fn try_enable_ansi() -> bool
|
||||||
{
|
{
|
||||||
if cfg!(target_os = "windows") {
|
#[cfg(windows)]
|
||||||
#[cfg(windows)]
|
{
|
||||||
use kernel::windows_kernel::ansi_support::try_enable_ansi_support;
|
if cfg!(target_os = "windows") {
|
||||||
|
use kernel::windows_kernel::ansi_support::try_enable_ansi_support;
|
||||||
|
|
||||||
if !try_enable_ansi_support()
|
if !try_enable_ansi_support()
|
||||||
{ return false; }
|
{ return false; }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user