Skip to content

Allow creating a Term from an arbitrary Read/Write pair. #93

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 67 additions & 10 deletions src/term.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::fmt::Display;
use std::io;
use std::io::Write;
use std::fmt::{Debug, Display};
use std::io::{self, Read, Write};
use std::sync::{Arc, Mutex};

#[cfg(unix)]
Expand All @@ -10,11 +9,31 @@ use std::os::windows::io::{AsRawHandle, RawHandle};

use crate::{kb::Key, utils::Style};

#[cfg(unix)]
trait TermWrite: Write + Debug + AsRawFd + Send {}
#[cfg(unix)]
impl<T: Write + Debug + AsRawFd + Send> TermWrite for T {}

#[cfg(unix)]
trait TermRead: Read + Debug + AsRawFd + Send {}
#[cfg(unix)]
impl<T: Read + Debug + AsRawFd + Send> TermRead for T {}

#[cfg(unix)]
#[derive(Debug, Clone)]
pub struct ReadWritePair {
read: Arc<Mutex<dyn TermRead>>,
write: Arc<Mutex<dyn TermWrite>>,
style: Style,
}

/// Where the term is writing.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[derive(Debug, Clone)]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The derivation of Copy just isn't possible in a safe way for arbitrary Read/Write destinations. And allowing for equality checks is of questionable value, although we can add it back if need be.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add it back to maintain as much backward compatibility as possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy cannot be added back, since no useful file handles are Copy (without unsafe).

Eq/PartialEq might be possible to add back, but it would need a strange/custom implementation (ie, assigning the Term a unique id?), because neither of those traits is object safe:

error[E0038]: the trait `TermWrite` cannot be made into an object
  --> src/term.rs:28:22
   |
28 |     write: Arc<Mutex<dyn TermWrite>>,
   |                      ^^^^^^^^^^^^^ `TermWrite` cannot be made into an object
   |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
  --> src/term.rs:15:44
   |
15 | trait TermWrite: Write + Debug + AsRawFd + PartialEq + Send {}
   |       ---------                            ^^^^^^^^^ ...because it uses `Self` as a type parameter
   |       |
   |       this trait cannot be made into an object...

pub enum TermTarget {
Stdout,
Stderr,
#[cfg(unix)]
ReadWritePair(ReadWritePair),
}

#[derive(Debug)]
Expand Down Expand Up @@ -157,19 +176,48 @@ impl Term {
})
}

/// Return a terminal for the given Read/Write pair styled-like Stderr.
#[cfg(unix)]
pub fn read_write_pair<R, W>(read: R, write: W) -> Term
where
R: Read + Debug + AsRawFd + Send + 'static,
W: Write + Debug + AsRawFd + Send + 'static,
{
Self::read_write_pair_with_style(read, write, Style::new().for_stderr())
}

/// Return a terminal for the given Read/Write pair.
#[cfg(unix)]
pub fn read_write_pair_with_style<R, W>(read: R, write: W, style: Style) -> Term
where
R: Read + Debug + AsRawFd + Send + 'static,
W: Write + Debug + AsRawFd + Send + 'static,
{
Term::with_inner(TermInner {
target: TermTarget::ReadWritePair(ReadWritePair {
read: Arc::new(Mutex::new(read)),
write: Arc::new(Mutex::new(write)),
style,
}),
buffer: None,
})
}

/// Returns the style for the term
#[inline]
pub fn style(&self) -> Style {
match self.target() {
match self.inner.target {
TermTarget::Stderr => Style::new().for_stderr(),
TermTarget::Stdout => Style::new().for_stdout(),
#[cfg(unix)]
TermTarget::ReadWritePair(ReadWritePair { ref style, .. }) => style.clone(),
}
}

/// Returns the target
#[inline]
pub fn target(&self) -> TermTarget {
self.inner.target
self.inner.target.clone()
}

#[doc(hidden)]
Expand Down Expand Up @@ -465,6 +513,12 @@ impl Term {
io::stderr().write_all(bytes)?;
io::stderr().flush()?;
}
#[cfg(unix)]
TermTarget::ReadWritePair(ReadWritePair { ref write, .. }) => {
let mut write = write.lock().unwrap();
write.write_all(bytes)?;
write.flush()?;
}
}
Ok(())
}
Expand Down Expand Up @@ -496,6 +550,9 @@ impl AsRawFd for Term {
match self.inner.target {
TermTarget::Stdout => libc::STDOUT_FILENO,
TermTarget::Stderr => libc::STDERR_FILENO,
TermTarget::ReadWritePair(ReadWritePair { ref write, .. }) => {
write.lock().unwrap().as_raw_fd()
}
}
}
}
Expand All @@ -515,7 +572,7 @@ impl AsRawHandle for Term {
}
}

impl io::Write for Term {
impl Write for Term {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self.inner.buffer {
Some(ref buffer) => buffer.lock().unwrap().write_all(buf),
Expand All @@ -529,7 +586,7 @@ impl io::Write for Term {
}
}

impl<'a> io::Write for &'a Term {
impl<'a> Write for &'a Term {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self.inner.buffer {
Some(ref buffer) => buffer.lock().unwrap().write_all(buf),
Expand All @@ -543,13 +600,13 @@ impl<'a> io::Write for &'a Term {
}
}

impl io::Read for Term {
impl Read for Term {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
io::stdin().read(buf)
}
}

impl<'a> io::Read for &'a Term {
impl<'a> Read for &'a Term {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
io::stdin().read(buf)
}
Expand Down