#[cfg(feature = "readline")]
use std::ffi::CString;
use std::future::Future;
use std::pin::Pin;
use async_trait::async_trait;
use gui::derive::Widget;
use gui::Cap;
use gui::Handleable;
use gui::Id;
use gui::MutCap;
use gui::Widget;
#[cfg(feature = "readline")]
use rline::Readline;
use crate::line::Line;
use super::event::Event;
use super::event::Key;
use super::message::Message;
use super::message::MessageExt;
use super::modal::Modal;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum InOut {
Saved,
Search(String),
Error(String),
Input(Line),
Clear,
}
#[cfg(feature = "readline")]
impl InOut {
fn is_input(&self) -> bool {
matches!(self, InOut::Input(..))
}
}
#[derive(Debug)]
struct InOutState {
in_out: InOut,
gen: usize,
}
impl InOutState {
fn get(&self) -> &InOut {
&self.in_out
}
fn set(&mut self, in_out: InOut) {
self.in_out = in_out;
self.bump()
}
fn bump(&mut self) {
self.gen += 1;
}
}
impl Default for InOutState {
fn default() -> Self {
Self {
in_out: InOut::Clear,
gen: 0,
}
}
}
pub struct InOutAreaData {
prev_focused: Option<Id>,
clear_gen: Option<usize>,
in_out: InOutState,
#[cfg(feature = "readline")]
readline: Readline,
}
impl InOutAreaData {
pub fn new() -> Self {
Self {
prev_focused: None,
clear_gen: None,
in_out: Default::default(),
#[cfg(feature = "readline")]
readline: Readline::new(),
}
}
fn change_state(&mut self, in_out: InOut) -> Option<Message> {
self.in_out.bump();
if in_out != *self.in_out.get() {
#[cfg(feature = "readline")]
{
if let InOut::Input(line) = &in_out {
let cstr = CString::new(line.as_str()).unwrap();
let clear_undo = !self.in_out.get().is_input();
self
.readline
.reset(cstr, line.selection_byte_index(), clear_undo);
}
}
self.in_out.set(in_out);
Some(Message::Updated)
} else {
None
}
}
}
#[derive(Debug, Widget)]
#[gui(Event = Event, Message = Message)]
pub struct InOutArea {
id: Id,
}
impl InOutArea {
pub fn new(id: Id, cap: &mut dyn MutCap<Event, Message>) -> Self {
cap.hook_events(id, Some(&InOutArea::handle_hooked_event));
Self { id }
}
fn handle_hooked_event<'f>(
widget: &'f dyn Widget<Event, Message>,
cap: &'f mut dyn MutCap<Event, Message>,
event: Option<&'f Event>,
) -> Pin<Box<dyn Future<Output = Option<Event>> + 'f>> {
Box::pin(async move {
let data = cap
.data_mut(widget.id())
.downcast_mut::<InOutAreaData>()
.unwrap();
if let Some(event) = event {
match event {
Event::Key(..) => {
data.clear_gen = Some(data.in_out.gen);
None
},
Event::Updated | Event::Quit => None,
}
} else {
if data.clear_gen.take() == Some(data.in_out.gen) {
match data.in_out.get() {
InOut::Saved | InOut::Search(_) | InOut::Error(_) => {
data.change_state(InOut::Clear).map(|_| Event::Updated)
},
InOut::Input(..) | InOut::Clear => None,
}
} else {
None
}
}
})
}
async fn finish_input(
&self,
cap: &mut dyn MutCap<Event, Message>,
string: Option<String>,
) -> Option<Message> {
let data = self.data_mut::<InOutAreaData>(cap);
let updated1 = data
.change_state(InOut::Clear)
.map(|m| m.is_updated())
.unwrap_or(false);
let widget = self.restore_focus(cap);
let message = if let Some(s) = string {
Message::EnteredText(s)
} else {
Message::InputCanceled
};
let updated2 = cap
.send(widget, message)
.await
.map(|m| m.is_updated())
.unwrap_or(false);
MessageExt::maybe_update(None, updated1 || updated2)
}
#[cfg(not(feature = "readline"))]
async fn handle_key(
&self,
cap: &mut dyn MutCap<Event, Message>,
mut line: Line,
key: Key,
_raw: &(),
) -> Option<Message> {
let data = self.data_mut::<InOutAreaData>(cap);
match key {
Key::Esc | Key::Char('\n') => {
let string = if key == Key::Char('\n') {
Some(line.into_string())
} else {
None
};
self.finish_input(cap, string).await
},
Key::Char(c) => {
let () = line.insert_char(c);
data.change_state(InOut::Input(line))
},
Key::Backspace => {
if line.selection() > 0 {
let mut line = line.select_prev();
let () = line.remove_char();
data.change_state(InOut::Input(line))
} else {
None
}
},
Key::Delete => {
if line.selection() < line.len() {
let () = line.remove_char();
data.change_state(InOut::Input(line))
} else {
None
}
},
Key::Left => {
if line.selection() > 0 {
data.change_state(InOut::Input(line.select_prev()))
} else {
None
}
},
Key::Right => {
if line.selection() < line.len() {
data.change_state(InOut::Input(line.select_next()))
} else {
None
}
},
Key::Home => {
if line.selection() != 0 {
data.change_state(InOut::Input(line.select_start()))
} else {
None
}
},
Key::End => {
if line.selection() != line.len() {
data.change_state(InOut::Input(line.select_end()))
} else {
None
}
},
_ => None,
}
}
#[cfg(feature = "readline")]
async fn handle_key(
&self,
cap: &mut dyn MutCap<Event, Message>,
line: Line,
key: Key,
raw: &[u8],
) -> Option<Message> {
let data = self.data_mut::<InOutAreaData>(cap);
match data.readline.feed(raw) {
Some(line) => {
self
.finish_input(cap, Some(line.into_string().unwrap()))
.await
},
None => {
let (s, idx) = data.readline.peek(|s, pos| (s.to_owned(), pos));
if key == Key::Esc && idx == line.selection_byte_index() {
data.readline = Readline::new();
self.finish_input(cap, None).await
} else {
let line = Line::from_string(s.to_string_lossy()).select_byte_index(idx);
data.change_state(InOut::Input(line))
}
},
}
}
pub fn state<'slf>(&'slf self, cap: &'slf dyn Cap) -> &'slf InOut {
let data = self.data::<InOutAreaData>(cap);
data.in_out.get()
}
}
impl Modal for InOutArea {
fn prev_focused(&self, cap: &dyn Cap) -> Option<Id> {
self.data::<InOutAreaData>(cap).prev_focused
}
fn set_prev_focused(&self, cap: &mut dyn MutCap<Event, Message>, focused: Option<Id>) {
let data = self.data_mut::<InOutAreaData>(cap);
data.prev_focused = focused;
}
}
#[async_trait(?Send)]
impl Handleable<Event, Message> for InOutArea {
async fn handle(&self, cap: &mut dyn MutCap<Event, Message>, event: Event) -> Option<Event> {
match event {
Event::Key(key, raw) => {
let data = self.data::<InOutAreaData>(cap);
let line = if let InOut::Input(line) = data.in_out.get() {
line.clone()
} else {
panic!("In/out area not used for input.");
};
self.handle_key(cap, line, key, &raw).await.into_event()
},
_ => Some(event),
}
}
async fn react(&self, message: Message, cap: &mut dyn MutCap<Event, Message>) -> Option<Message> {
match message {
Message::SetInOut(in_out) => {
if matches!(in_out, InOut::Input(..)) {
self.make_focused(cap);
};
let data = self.data_mut::<InOutAreaData>(cap);
data.change_state(in_out)
},
#[cfg(all(test, not(feature = "readline")))]
Message::GetInOut => {
let data = self.data::<InOutAreaData>(cap);
Some(Message::GotInOut(data.in_out.get().clone()))
},
m => panic!("Received unexpected message: {:?}", m),
}
}
}