added unittests, fixed getting terminal position and tested code

This commit is contained in:
= 2018-09-22 22:42:23 +02:00
parent 61778a23a6
commit 5dcc6387f1
9 changed files with 166 additions and 77 deletions

View File

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

View File

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

View File

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

View File

@ -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);
// if we enable raw modes with screen, this could cause problems if raw mode is already enabled in applicaition.
// I am not completely happy with this approach so feel free to find an other way.
unsafe {
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.
RAW_MODE_ENABLED_BY_SYSTEM = true;
into_raw_mode();
}
}
// Where is the cursor? // Where is the cursor?
// Use `ESC [ 6 n`. // Use `ESC [ 6 n`.
stdout.write_str("\x1B[6n"); let mut stdout = io::stdout();
let mut buf: [u8; 1] = [0]; // Write command
let mut read_chars = Vec::new(); stdout.write(b"\x1B[6n")?;
stdout.flush()?;
let timeout = Duration::from_millis(2000); let mut buf = [0u8; 2];
let now = SystemTime::now();
// Either consume all data up to R or wait for a timeout. // Expect `ESC[`
while buf[0] != delimiter && now.elapsed().unwrap() < timeout { io::stdin().read_exact(&mut buf)?;
if let Ok(c) = stdin.read(&mut buf) { if buf[0] != 0x1B || buf[1] as char != '[' {
if c >= 0 { return Err(Error::new(ErrorKind::Other, "test"));
read_chars.push(buf[0]);
}
}
} }
if read_chars.len() == 0 { // Read rows and cols through a ad-hoc integer parsing function
return (0, 0); let read_num: fn() -> Result<(i32, char), Error> = || -> Result<(i32, char), Error> {
let mut num = 0;
let mut c: char;
loop {
let mut buf = [0u8; 1];
io::stdin().read_exact(&mut buf)?;
c = buf[0] as char;
if let Some(d) = c.to_digit(10) {
num = if num == 0 { 0 } else { num * 10 };
num += d as i32;
} else {
break;
}
} }
// The answer will look like `ESC [ Cy ; Cx R`. Ok((num, c))
};
read_chars.pop(); // remove trailing R. // Read rows and expect `;`
let read_str = String::from_utf8(read_chars).unwrap(); let (rows, c) = read_num()?;
let beg = read_str.rfind('[').unwrap(); if c != ';' {
let coords: String = read_str.chars().skip(beg + 1).collect(); return Err(Error::new(ErrorKind::Other, "test"));
let mut nums = coords.split(';'); }
let cy = nums.next().unwrap().parse::<u16>().unwrap(); // Read cols
let cx = nums.next().unwrap().parse::<u16>().unwrap(); let (cols, c) = read_num()?;
(cx, cy) // 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(())
} }

View File

@ -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,13 +79,15 @@ fn goto_ansi()
fn try_enable_ansi() -> bool fn try_enable_ansi() -> bool
{ {
if cfg!(target_os = "windows") {
#[cfg(windows)] #[cfg(windows)]
{
if cfg!(target_os = "windows") {
use kernel::windows_kernel::ansi_support::try_enable_ansi_support; 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;
} }

View File

@ -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.
/// ///

View File

@ -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)]
{
if cfg!(target_os = "windows") {
use kernel::windows_kernel::ansi_support::try_enable_ansi_support; 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;
} }

View File

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

View File

@ -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)]
{
if cfg!(target_os = "windows") {
use kernel::windows_kernel::ansi_support::try_enable_ansi_support; 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;
} }