use crossterm::{QueueableCommand, cursor};
use crossterm::event::{Event, EventStream, KeyCode, KeyEvent, KeyModifiers};
use crossterm::style::Print;
use crossterm::terminal::{Clear, ClearType};
use futures::{select, StreamExt};
use futures::future::FutureExt;
use futures::channel::mpsc;
use std::io::Write;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("interrupted")]
Interrupted,
#[error("end of file")]
Eof,
#[error("io error: {0}")]
IoError(#[from] std::io::Error),
}
pub struct Editor {
history: Vec<String>,
history_receiver: mpsc::UnboundedReceiver<String>,
events: EventStream,
}
impl Editor {
pub fn new() -> (Self, mpsc::UnboundedSender<String>) {
let (tx, rx) = mpsc::unbounded();
let editor = Self {
history: vec![],
history_receiver: rx,
events: EventStream::new(),
};
(editor, tx)
}
pub async fn readline(&mut self) -> (String, Result<(), Error>) {
let mut buffer = String::new();
if let Err(e) = self.output(&buffer) {
return (buffer, Err(e));
}
loop {
let mut event = self.events.next().fuse();
select! {
line = self.history_receiver.next() => match line {
Some(line) => self.history.push(line),
None => continue,
},
maybe_event = event => match maybe_event {
Some(Ok(Event::Key(key_event))) => {
if key_event == KeyCode::Enter.into() {
return (buffer, Ok(()));
} else if key_event == KeyCode::Backspace.into() {
let _ = buffer.pop();
} else if let KeyEvent { code: KeyCode::Char(c), modifiers } = key_event {
if c == 'c' && modifiers == KeyModifiers::CONTROL {
return (buffer, Err(Error::Interrupted));
} else if c == 'd' && modifiers == KeyModifiers::CONTROL {
return (buffer, Err(Error::Eof));
} else {
buffer.push(c);
}
} else {
continue;
}
}
Some(Ok(_)) => continue,
Some(Err(e)) => return (buffer, Err(e.into())),
None => return (buffer, Ok(())),
}
}
if let Err(e) = self.output(&buffer) {
return (buffer, Err(e));
}
}
}
fn output(&self, buffer: &str) -> Result<(), Error> {
let mut stdout = std::io::stdout();
stdout.queue(Clear(ClearType::All))?.queue(cursor::MoveTo(0, 0))?;
let (_cols, rows) = crossterm::terminal::size()?;
let max_history = rows as usize - 2;
let total_history = self.history.len();
let _to_show = max_history.min(total_history);
for line in &self.history {
stdout.queue(Print(line))?.queue(Print("\n\r"))?;
}
stdout.queue(Print(">> "))?.queue(Print(&buffer))?;
stdout.flush()?;
Ok(())
}
}
pub fn enable_raw_mode() -> Result<(), std::io::Error> {
crossterm::terminal::enable_raw_mode()
}
pub fn disable_raw_mode() -> Result<(), std::io::Error> {
crossterm::terminal::disable_raw_mode()
}