conciliator 0.3.10

[WIP] Library for interactive CLI programs
Documentation
use std::io::{
	Write,
	Result as IoResult
};
use std::ops::{
	Deref,
	DerefMut
};

use crate::Buffer;
use crate::term::EmitEscapes;
use super::Spinner;



/// Wrap a [`Buffer`] to print it and a newline when dropped ([`Spinner`] version)
///
/// This struct "borrows" (figuratively) a [`Buffer`] from a [`Spinner`].
/// It holds a reference to this [`Spinner`], and the **destructor uses this reference to print the buffer** to the terminal.
///
/// Before the buffer is printed, a **newline character is appended** to it.
/// This can be avoided by using [`Line::no_newline`] to turn it into a [`NoNewLine`].
pub struct Line<'s> {
	/// `Option` because we might need to discard it
	buffer: Option<Buffer>,
	spinner: &'s Spinner<'s>
}

/// Wrap a [`Buffer`] to print it *without* a newline when dropped ([`Spinner`] version)
///
/// This struct is obtained from [`Line::no_newline`] and apart from *not* appending the newline automatically, it is equivalent to [`Line`].
/// This means it also **prints itself when dropped**.
///
/// Be careful with this: output that doesn't end with a newline will be overwritten by the [`Spinner`] animation.
pub struct NoNewLine<'s> {
	/// `Option` because we might need to discard it
	buffer: Option<Buffer>,
	spinner: &'s Spinner<'s>
}

/// Special [`Buffer`] that sets the [`Spinner`] message when dropped
///
/// Make sure that the [`Buffer`] does not contain any newlines and that it is short enough to fit onto a single line.
/// If you don't, the output will get messed up, and the user might not be able to read all of it (since some parts might get overwritten).
///
/// There's no (easy) way to guarantee this doesn't happen, the user can resize their terminal however they see fit, at any point.
/// In the future, there may be support for automatically truncating messages that are too long to fit into a single line, but currently there is not (it's quite a bit more complicated than just checking the length of the [`Buffer`]).
///
/// So, for the time being, just make sure that, together with the width of the animation (+3 for the brackets), the total stays **well** under the 80 columns rule.
pub struct Message<'s> {
	/// `Option` because we might need to discard it
	buffer: Option<Buffer>,
	spinner: &'s Spinner<'s>
}



/*
 *	LINE
 */

impl<'s> Line<'s> {
	pub(crate) fn new(spinner: &'s Spinner) -> Self {
		let buffer = Some(spinner.claw.out.buffer());
		Self {buffer, spinner}
	}
	/// Print this `Line` into the [`Spinner`]. This is equivalent to dropping.
	pub fn print(self) {}
	/// Discard and drop this buffer without printing it
	pub fn discard(mut self) {self.buffer.take();}
	/// No longer add the newline when dropped
	///
	/// Make sure to end every chunk of output with a newline, otherwise the last line will get overwritten by the [`Spinner`] message!  
	/// Additionally, be careful with using this and sending a newline in another [`Buffer`] afterwards: if you get unlucky, the [`Spinner`] thread might print and overwrite the contents of this one before it gets to the one that contains the newline.
	/// Just *how* unlucky you would have to be is hard to say, and it probably depends on the refresh-rate of the animation you use, but in my estimation it is extremely unlikely.
	pub fn no_newline(mut self) -> NoNewLine<'s> {
		let buffer = self.buffer.take().unwrap();
		NoNewLine {spinner: self.spinner, buffer: Some(buffer)}
	}
}

impl<'s> Drop for Line<'s> {
	/// The `Line` prints itself to the [`Spinner`] when dropped
	fn drop(&mut self) {
		if let Some(mut buffer) = self.buffer.take() {
			writeln!(buffer, ).unwrap();
			self.spinner.print_buffer(buffer);
		}
	}
}
impl<'s> Deref for Line<'s> {
	type Target = Buffer;
	fn deref(&self) -> &Self::Target {self.buffer.as_ref().unwrap()}
}
impl<'s> DerefMut for Line<'s> {
	fn deref_mut(&mut self) -> &mut Self::Target {self.buffer.as_mut().unwrap()}
}
impl<'s> Write for Line<'s> {
	fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
		self.deref_mut().write(buf)
	}
	fn flush(&mut self) -> IoResult<()> {
		self.deref_mut().flush()
	}
}

impl<'s> EmitEscapes for Line<'s> {
	fn escapes_recognized(&self) -> bool {
		self.buffer.as_ref().unwrap().escapes_recognized()
	}
}


/*
 *	NO NEW LINE
 */

impl<'s> NoNewLine<'s> {
	/// Print this `NoNewLine` into the [`Spinner`]. This is equivalent to dropping.
	pub fn print(self) {}
	/// Discard and drop this buffer without printing it
	///
	/// This also elides flushing the [`Spinner`].
	pub fn discard(mut self) {self.buffer.take();}
}

impl<'s> Drop for NoNewLine<'s> {
	/// The `NoNewLine` prints itself to the [`Spinner`] when dropped
	fn drop(&mut self) {
		if let Some(buffer) = self.buffer.take() {
			self.spinner.print_buffer(buffer);
		}
	}
}
impl<'s> Deref for NoNewLine<'s> {
	type Target = Buffer;
	fn deref(&self) -> &Self::Target {self.buffer.as_ref().unwrap()}
}
impl<'s> DerefMut for NoNewLine<'s> {
	fn deref_mut(&mut self) -> &mut Self::Target {self.buffer.as_mut().unwrap()}
}


/*
 *	MESSAGE
 */

impl<'s> Message<'s> {
	pub(crate) fn new(spinner: &'s Spinner) -> Self {
		let buffer = Some(spinner.claw.err.buffer());
		Self {buffer, spinner}
	}
	/// Set the contents of this buffer as the persistent [`Spinner`] status message.
	/// This is equivalent to dropping.
	pub fn set(self) {}
	/// Discard and drop this buffer without changing the [`Spinner`] status message
	pub fn discard(mut self) {self.buffer.take();}
}

impl<'s> Drop for Message<'s> {
	/// The `Message` sends itself to the [`Spinner`] thread when dropped
	fn drop(&mut self) {
		if let Some(buffer) = self.buffer.take() {
			self.spinner.set_message(buffer);
		}
	}
}
impl<'s> Deref for Message<'s> {
	type Target = Buffer;
	fn deref(&self) -> &Self::Target {self.buffer.as_ref().unwrap()}
}
impl<'s> DerefMut for Message<'s> {
	fn deref_mut(&mut self) -> &mut Self::Target {self.buffer.as_mut().unwrap()}
}
impl<'s> Write for Message<'s> {
	fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
		self.deref_mut().write(buf)
	}
	fn flush(&mut self) -> IoResult<()> {
		self.deref_mut().flush()
	}
}

impl<'s> EmitEscapes for Message<'s> {
	fn escapes_recognized(&self) -> bool {
		self.buffer.as_ref().unwrap().escapes_recognized()
	}
}