...
 
Commits (37)
target
Cargo.lock
.idea
\ No newline at end of file
image: "rust:latest"
image: "redoxos/redoxer"
stages:
- build
- test
before_script:
- rustup toolchain add $toolchain
- build
- test
cache:
paths:
- target/
build:stable:
stage: build
variables:
toolchain: stable
script:
- cargo +stable build --verbose
- cargo +stable build --release --verbose
test:stable:
stage: test
variables:
toolchain: stable
dependencies:
- build:stable
script:
- script -q -c "cargo +stable test --verbose"
- script -q -c "cargo +stable test --release --verbose"
build:beta:
stage: build
variables:
toolchain: beta
script:
- cargo +beta build --verbose
- cargo +beta build --release --verbose
test:beta:
stage: test
variables:
toolchain: beta
dependencies:
- build:beta
script:
- script -q -c "cargo +beta test --verbose"
- script -q -c "cargo +beta test --release --verbose"
build:nightly:
stage: build
variables:
toolchain: nightly
script:
- cargo +nightly build --verbose
- cargo +nightly build --release --verbose
test:nightly:
stage: test
variables:
toolchain: nightly
dependencies:
- build:nightly
script:
- script -q -c "cargo +nightly test --verbose"
- script -q -c "cargo +nightly test --release --verbose"
\ No newline at end of file
paths:
- target/
build:linux:stable:
stage: build
script:
- rustup update stable
- cargo +stable build --verbose
build:linux:
stage: build
script: cargo +nightly build --verbose
build:redox:
stage: build
script: redoxer build --verbose
test:linux:stable:
stage: test
dependencies:
- build:linux:stable
script:
- rustup update stable
- script -c "cargo +stable test --verbose"
test:linux:
stage: test
dependencies:
- build:linux
script: script -c "cargo +nightly test --verbose"
test:redox:
stage: test
dependencies:
- build:redox
script: redoxer test --verbose
[package]
name = "termion"
version = "1.5.1"
version = "1.5.5"
authors = ["ticki <Ticki@users.noreply.github.com>", "gycos <alexandre.bury@gmail.com>", "IGI-111 <igi-111@protonmail.com>"]
description = "A bindless library for manipulating terminals."
repository = "https://gitlab.redox-os.org/redox-os/termion"
......@@ -12,9 +12,13 @@ exclude = ["target", "CHANGELOG.md", "image.png", "Cargo.lock"]
[dependencies]
numtoa = { version = "0.1.0", features = ["std"]}
[target.'cfg(not(target_os = "redox"))'.dependencies]
[target.'cfg(all(not(target_os = "redox"), not(windows)))'.dependencies]
libc = "0.2.8"
[target.'cfg(target_os = "redox")'.dependencies]
redox_syscall = "0.1"
redox_termios = "0.1"
redox_termios = "0.1"
[target.'cfg(windows)'.dependencies]
crossterm_winapi = "0.6.1"
extern crate termion;
use std::fs;
use std::{fs, io};
fn main() {
if termion::is_tty(&fs::File::create("/dev/stdout").unwrap()) {
#[cfg(not(windows))]
let stream = fs::File::create("/dev/stdout").unwrap();
#[cfg(windows)]
let stream = io::stdin();
if termion::is_tty(&stream) {
println!("This is a TTY!");
} else {
println!("This is not a TTY :(");
......
......@@ -24,7 +24,7 @@ pub fn async_stdin_until(delimiter: u8) -> AsyncReader {
}
});
AsyncReader { recv: recv }
AsyncReader { recv }
}
/// Construct an asynchronous handle to the TTY standard input.
......@@ -46,7 +46,7 @@ pub fn async_stdin() -> AsyncReader {
}
});
AsyncReader { recv: recv }
AsyncReader { recv }
}
/// An asynchronous reader.
......
......@@ -18,10 +18,11 @@ use std::io::{self, Write, Read};
use std::time::{SystemTime, Duration};
use async::async_stdin;
use std::env;
use std::fmt::Debug;
use numtoa::NumToA;
/// A terminal color.
pub trait Color {
pub trait Color: Debug {
/// Write the foreground version of this color.
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result;
/// Write the background version of this color.
......@@ -75,7 +76,7 @@ derive_color!("High-intensity light magenta.", LightMagenta, "13");
derive_color!("High-intensity light cyan.", LightCyan, "14");
derive_color!("High-intensity light white.", LightWhite, "15");
impl<'a> Color for &'a Color {
impl<'a> Color for &'a dyn Color {
#[inline]
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
(*self).write_fg(f)
......@@ -282,7 +283,7 @@ impl<W: Write> DetectColors for W {
}
/// Detect a color using OSC 4.
fn detect_color(stdout: &mut Write, stdin: &mut Read, color: u16) -> io::Result<bool> {
fn detect_color(stdout: &mut dyn Write, stdin: &mut dyn Read, color: u16) -> io::Result<bool> {
// Is the color available?
// Use `ESC ] 4 ; color ; ? BEL`.
write!(stdout, "\x1B]4;{};?\x07", color)?;
......
//! Cursor movement.
use std::fmt;
use std::ops;
use std::io::{self, Write, Error, ErrorKind, Read};
use async::async_stdin_until;
use std::time::{SystemTime, Duration};
......@@ -13,6 +14,13 @@ derive_csi_sequence!("Show the cursor.", Show, "?25h");
derive_csi_sequence!("Restore the cursor.", Restore, "u");
derive_csi_sequence!("Save the cursor.", Save, "s");
derive_csi_sequence!("Change the cursor style to blinking block", BlinkingBlock, "\x31 q");
derive_csi_sequence!("Change the cursor style to steady block", SteadyBlock, "\x32 q");
derive_csi_sequence!("Change the cursor style to blinking underline", BlinkingUnderline, "\x33 q");
derive_csi_sequence!("Change the cursor style to steady underline", SteadyUnderline, "\x34 q");
derive_csi_sequence!("Change the cursor style to blinking bar", BlinkingBar, "\x35 q");
derive_csi_sequence!("Change the cursor style to steady bar", SteadyBar, "\x36 q");
/// Goto some position ((1,1)-based).
///
/// # Why one-based?
......@@ -49,7 +57,7 @@ impl Default for Goto {
impl fmt::Display for Goto {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
debug_assert!(self != &Goto(0, 0), "Goto is one-based.");
f.write_str(&String::from(*self))
write!(f, "\x1B[{};{}H", self.1, self.0)
}
}
......@@ -66,7 +74,7 @@ impl From<Left> for String {
impl fmt::Display for Left {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&String::from(*self))
write!(f, "\x1B[{}D", self.0)
}
}
......@@ -83,7 +91,7 @@ impl From<Right> for String {
impl fmt::Display for Right {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&String::from(*self))
write!(f, "\x1B[{}C", self.0)
}
}
......@@ -100,7 +108,7 @@ impl From<Up> for String {
impl fmt::Display for Up {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&String::from(*self))
write!(f, "\x1B[{}A", self.0)
}
}
......@@ -117,7 +125,7 @@ impl From<Down> for String {
impl fmt::Display for Down {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&String::from(*self))
write!(f, "\x1B[{}B", self.0)
}
}
......@@ -174,3 +182,48 @@ impl<W: Write> DetectCursorPos for W {
Ok((cx, cy))
}
}
/// Hide the cursor for the lifetime of this struct.
/// It will hide the cursor on creation with from() and show it back on drop().
pub struct HideCursor<W: Write> {
/// The output target.
output: W,
}
impl<W: Write> HideCursor<W> {
/// Create a hide cursor wrapper struct for the provided output and hides the cursor.
pub fn from(mut output: W) -> Self {
write!(output, "{}", Hide).expect("hide the cursor");
HideCursor { output: output }
}
}
impl<W: Write> Drop for HideCursor<W> {
fn drop(&mut self) {
write!(self, "{}", Show).expect("show the cursor");
}
}
impl<W: Write> ops::Deref for HideCursor<W> {
type Target = W;
fn deref(&self) -> &W {
&self.output
}
}
impl<W: Write> ops::DerefMut for HideCursor<W> {
fn deref_mut(&mut self) -> &mut W {
&mut self.output
}
}
impl<W: Write> Write for HideCursor<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.output.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.output.flush()
}
}
......@@ -71,6 +71,8 @@ pub enum Key {
PageUp,
/// Page Down key.
PageDown,
/// Backward Tab key.
BackTab,
/// Delete key.
Delete,
/// Insert key.
......@@ -108,7 +110,7 @@ pub fn parse_event<I>(item: u8, iter: &mut I) -> Result<Event, Error>
Some(Ok(b'O')) => {
match iter.next() {
// F1-F4
Some(Ok(val @ b'P'...b'S')) => Event::Key(Key::F(1 + val - b'P')),
Some(Ok(val @ b'P'..=b'S')) => Event::Key(Key::F(1 + val - b'P')),
_ => return Err(error),
}
}
......@@ -117,8 +119,8 @@ pub fn parse_event<I>(item: u8, iter: &mut I) -> Result<Event, Error>
parse_csi(iter).ok_or(error)?
}
Some(Ok(c)) => {
let ch = parse_utf8_char(c, iter);
Event::Key(Key::Alt(try!(ch)))
let ch = parse_utf8_char(c, iter)?;
Event::Key(Key::Alt(ch))
}
Some(Err(_)) | None => return Err(error),
})
......@@ -126,13 +128,13 @@ pub fn parse_event<I>(item: u8, iter: &mut I) -> Result<Event, Error>
b'\n' | b'\r' => Ok(Event::Key(Key::Char('\n'))),
b'\t' => Ok(Event::Key(Key::Char('\t'))),
b'\x7F' => Ok(Event::Key(Key::Backspace)),
c @ b'\x01'...b'\x1A' => Ok(Event::Key(Key::Ctrl((c as u8 - 0x1 + b'a') as char))),
c @ b'\x1C'...b'\x1F' => Ok(Event::Key(Key::Ctrl((c as u8 - 0x1C + b'4') as char))),
c @ b'\x01'..=b'\x1A' => Ok(Event::Key(Key::Ctrl((c as u8 - 0x1 + b'a') as char))),
c @ b'\x1C'..=b'\x1F' => Ok(Event::Key(Key::Ctrl((c as u8 - 0x1C + b'4') as char))),
b'\0' => Ok(Event::Key(Key::Null)),
c => {
Ok({
let ch = parse_utf8_char(c, iter);
Event::Key(Key::Char(try!(ch)))
let ch = parse_utf8_char(c, iter)?;
Event::Key(Key::Char(ch))
})
}
}
......@@ -146,7 +148,7 @@ fn parse_csi<I>(iter: &mut I) -> Option<Event>
{
Some(match iter.next() {
Some(Ok(b'[')) => match iter.next() {
Some(Ok(val @ b'A'...b'E')) => Event::Key(Key::F(1 + val - b'A')),
Some(Ok(val @ b'A'..=b'E')) => Event::Key(Key::F(1 + val - b'A')),
_ => return None,
},
Some(Ok(b'D')) => Event::Key(Key::Left),
......@@ -155,6 +157,7 @@ fn parse_csi<I>(iter: &mut I) -> Option<Event>
Some(Ok(b'B')) => Event::Key(Key::Down),
Some(Ok(b'H')) => Event::Key(Key::Home),
Some(Ok(b'F')) => Event::Key(Key::End),
Some(Ok(b'Z')) => Event::Key(Key::BackTab),
Some(Ok(b'M')) => {
// X10 emulation mouse encoding: ESC [ CB Cx Cy (6 characters only).
let mut next = || iter.next().unwrap().unwrap();
......@@ -212,7 +215,7 @@ fn parse_csi<I>(iter: &mut I) -> Option<Event>
.unwrap();
let event = match cb {
0...2 | 64...65 => {
0..=2 | 64..=65 => {
let button = match cb {
0 => MouseButton::Left,
1 => MouseButton::Middle,
......@@ -234,7 +237,7 @@ fn parse_csi<I>(iter: &mut I) -> Option<Event>
Event::Mouse(event)
}
Some(Ok(c @ b'0'...b'9')) => {
Some(Ok(c @ b'0'..=b'9')) => {
// Numbered escape code.
let mut buf = Vec::new();
buf.push(c);
......@@ -295,9 +298,9 @@ fn parse_csi<I>(iter: &mut I) -> Option<Event>
4 | 8 => Event::Key(Key::End),
5 => Event::Key(Key::PageUp),
6 => Event::Key(Key::PageDown),
v @ 11...15 => Event::Key(Key::F(v - 10)),
v @ 17...21 => Event::Key(Key::F(v - 11)),
v @ 23...24 => Event::Key(Key::F(v - 12)),
v @ 11..=15 => Event::Key(Key::F(v - 10)),
v @ 17..=21 => Event::Key(Key::F(v - 11)),
v @ 23..=24 => Event::Key(Key::F(v - 12)),
_ => return None,
}
}
......
......@@ -19,7 +19,7 @@ impl<R: Read> Iterator for Keys<R> {
match self.iter.next() {
Some(Ok(Event::Key(k))) => return Some(Ok(k)),
Some(Ok(_)) => continue,
e @ Some(Err(_)) => e,
Some(Err(e)) => return Some(Err(e)),
None => return None,
};
}
......@@ -71,7 +71,7 @@ impl<R: Read> Iterator for EventsAndRaw<R> {
}
}
Ok(2) => {
let mut option_iter = &mut Some(buf[1]).into_iter();
let option_iter = &mut Some(buf[1]).into_iter();
let result = {
let mut iter = option_iter.map(|c| Ok(c)).chain(source.bytes());
parse_event(buf[0], &mut iter)
......@@ -121,7 +121,7 @@ pub trait TermRead {
/// EOT and ETX will abort the prompt, returning `None`. Newline or carriage return will
/// complete the input.
fn read_passwd<W: Write>(&mut self, writer: &mut W) -> io::Result<Option<String>> {
let _raw = try!(writer.into_raw_mode());
let _raw = writer.into_raw_mode()?;
self.read_line()
}
}
......@@ -152,8 +152,8 @@ impl<R: Read + TermReadEventsAndRaw> TermRead for R {
}
}
let string = try!(String::from_utf8(buf)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)));
let string = String::from_utf8(buf)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
Ok(Some(string))
}
}
......
......@@ -17,11 +17,17 @@ extern crate numtoa;
#[path="sys/redox/mod.rs"]
mod sys;
#[cfg(unix)]
#[cfg(all(unix, not(target_os = "redox")))]
#[path="sys/unix/mod.rs"]
mod sys;
#[cfg(windows)]
#[path="sys/windows/mod.rs"]
mod sys;
pub use sys::size::terminal_size;
#[cfg(all(unix, not(target_os = "redox")))]
pub use sys::size::terminal_size_pixels;
pub use sys::tty::{is_tty, get_tty};
mod async;
......
......@@ -95,18 +95,20 @@ impl<W: Write> IntoRawMode for W {
set_terminal_attr(&ios)?;
Ok(RawTerminal {
prev_ios: prev_ios,
prev_ios,
output: self,
})
}
}
impl<W: Write> RawTerminal<W> {
/// Temporarily switch to original mode
pub fn suspend_raw_mode(&self) -> io::Result<()> {
set_terminal_attr(&self.prev_ios)?;
Ok(())
}
/// Temporarily switch to raw mode
pub fn activate_raw_mode(&self) -> io::Result<()> {
let mut ios = get_terminal_attr()?;
raw_terminal_attr(&mut ios);
......
......@@ -5,7 +5,7 @@ use super::{cvt, syscall, Termios};
pub fn get_terminal_attr() -> io::Result<Termios> {
let mut termios = Termios::default();
let fd = cvt(syscall::dup(0, b"termios"))?;
let fd = cvt(syscall::dup(1, b"termios"))?;
let res = cvt(syscall::read(fd, &mut termios));
let _ = syscall::close(fd);
......@@ -17,7 +17,7 @@ pub fn get_terminal_attr() -> io::Result<Termios> {
}
pub fn set_terminal_attr(termios: &Termios) -> io::Result<()> {
let fd = cvt(syscall::dup(0, b"termios"))?;
let fd = cvt(syscall::dup(1, b"termios"))?;
let res = cvt(syscall::write(fd, termios));
let _ = syscall::close(fd);
......
use std::{env, fs, io};
use std::{env, fs, io::{self, Read, Write}};
use std::os::unix::io::AsRawFd;
use super::syscall;
/// Is this stream a TTY?
pub fn is_tty<T: AsRawFd>(stream: &T) -> bool {
if let Ok(fd) = syscall::dup(stream.as_raw_fd(), b"termios") {
if let Ok(fd) = syscall::dup(stream.as_raw_fd() as _, b"termios") {
let _ = syscall::close(fd);
true
} else {
......@@ -16,7 +16,7 @@ pub fn is_tty<T: AsRawFd>(stream: &T) -> bool {
/// Get the TTY device.
///
/// This allows for getting stdio representing _only_ the TTY, and not other streams.
pub fn get_tty() -> io::Result<fs::File> {
pub fn get_tty() -> io::Result<impl Read + Write> {
let tty = try!(env::var("TTY").map_err(|x| io::Error::new(io::ErrorKind::NotFound, x)));
fs::OpenOptions::new().read(true).write(true).open(tty)
}
......@@ -9,7 +9,7 @@ pub fn get_terminal_attr() -> io::Result<Termios> {
}
unsafe {
let mut termios = mem::zeroed();
cvt(tcgetattr(0, &mut termios))?;
cvt(tcgetattr(1, &mut termios))?;
Ok(termios)
}
}
......@@ -18,7 +18,7 @@ pub fn set_terminal_attr(termios: &Termios) -> io::Result<()> {
extern "C" {
pub fn tcsetattr(fd: c_int, opt: c_int, termptr: *const Termios) -> c_int;
}
cvt(unsafe { tcsetattr(0, 0, termios) }).and(Ok(()))
cvt(unsafe { tcsetattr(1, 0, termios) }).and(Ok(()))
}
pub fn raw_terminal_attr(termios: &mut Termios) {
......
......@@ -7,14 +7,23 @@ use super::libc::{c_ushort, ioctl, STDOUT_FILENO, TIOCGWINSZ};
struct TermSize {
row: c_ushort,
col: c_ushort,
_x: c_ushort,
_y: c_ushort,
x: c_ushort,
y: c_ushort,
}
/// Get the size of the terminal.
pub fn terminal_size() -> io::Result<(u16, u16)> {
unsafe {
let mut size: TermSize = mem::zeroed();
cvt(ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut size as *mut _))?;
cvt(ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut size as *mut _))?;
Ok((size.col as u16, size.row as u16))
}
}
/// Get the size of the terminal, in pixels
pub fn terminal_size_pixels() -> io::Result<(u16, u16)> {
unsafe {
let mut size: TermSize = mem::zeroed();
cvt(ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut size as *mut _))?;
Ok((size.x as u16, size.y as u16))
}
}
use std::{fs, io};
use std::{fs, io::{self, Read, Write}};
use std::os::unix::io::AsRawFd;
use super::libc;
......@@ -12,6 +12,6 @@ pub fn is_tty<T: AsRawFd>(stream: &T) -> bool {
/// Get the TTY device.
///
/// This allows for getting stdio representing _only_ the TTY, and not other streams.
pub fn get_tty() -> io::Result<fs::File> {
pub fn get_tty() -> io::Result<impl Read + Write> {
fs::OpenOptions::new().read(true).write(true).open("/dev/tty")
}
use std::{io, mem};
use super::crossterm_winapi::{ConsoleMode, Handle};
use super::Termios;
pub fn get_terminal_attr() -> io::Result<Termios> {
let console_mode = ConsoleMode::from(Handle::current_in_handle()?);
let mode = console_mode.mode()?;
Ok(Termios(mode))
}
pub fn set_terminal_attr(termios: &Termios) -> io::Result<()> {
let console_mode = ConsoleMode::from(Handle::current_in_handle()?);
console_mode.set_mode(termios.0)?;
Ok(())
}
pub fn raw_terminal_attr(termios: &mut Termios) {
// These are copied from the MSDocs.
// Yes, technically, not the best, but Windows won't change these for obvious reasons.
// We could link in winapi explicitly, as crossterm_winapi is already doing that, but
// I feel it just adds a bit too much cruft, when we can just do this.
//
// https://docs.microsoft.com/en-us/windows/console/setconsolemode#parameters
const ENABLE_PROCESSED_INPUT: u32 = 0x0001;
const ENABLE_LINE_INPUT: u32 = 0x0002;
const ENABLE_ECHO_INPUT: u32 = 0x0004;
const RAW_MODE_MASK: u32 = ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT;
termios.0 = termios.0 & !RAW_MODE_MASK;
}
extern crate crossterm_winapi;
#[derive(Clone, Copy, Debug)]
pub struct Termios(u32);
pub mod attr;
pub mod size;
pub mod tty;
\ No newline at end of file
use std::{io, mem};
use super::crossterm_winapi::ScreenBuffer;
/// Get the size of the terminal.
pub fn terminal_size() -> io::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,
))
}
use std::io::{self, Read, Result, Stdin, Stdout, Write};
use std::os::windows::io::AsRawHandle;
/// Is this stream a TTY?
pub fn is_tty<T: AsRawHandle>(stream: &T) -> bool {
// @MAYBE Jezza - 17 Dec. 2018: Is this the correct implementation?
// I just check against this program's stdin or stdout handle, and if they're the same, then the given
// handle must be a tty for something... I guess...
let raw = stream.as_raw_handle();
raw == io::stdin().as_raw_handle() || raw == io::stdout().as_raw_handle()
}
/// Get the TTY device.
///
/// This allows for getting stdio representing _only_ the TTY, and not other streams.
pub fn get_tty() -> io::Result<impl Read + Write> {
let stdin = io::stdin();
let stdout = io::stdout();
Ok(TerminalHandle {
stdin,
stdout,
})
}
struct TerminalHandle {
stdin: Stdin,
stdout: Stdout,
}
impl Read for TerminalHandle {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
self.stdin.read(buf)
}
}
impl Write for TerminalHandle {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
self.stdout.write(buf)
}
fn flush(&mut self) -> Result<()> {
self.stdout.flush()
}
}