Skip to content
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

feat: make Input streamable #127

Merged
merged 1 commit into from
Sep 9, 2023
Merged
Show file tree
Hide file tree
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
11 changes: 6 additions & 5 deletions core/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use std::{collections::BTreeMap, ffi::OsString};
use anyhow::Result;
use config::{keymap::{Control, KeymapLayer}, open::Opener};
use crossterm::event::KeyEvent;
use shared::{RoCell, Url};
use tokio::sync::{mpsc::UnboundedSender, oneshot};
use shared::{InputError, RoCell, Url};
use tokio::sync::{mpsc::{self, UnboundedSender}, oneshot};

use super::{files::{File, FilesOp}, input::InputOpt, select::SelectOpt};
use crate::manager::PreviewLock;
Expand Down Expand Up @@ -32,7 +32,7 @@ pub enum Event {

// Input
Select(SelectOpt, oneshot::Sender<Result<usize>>),
Input(InputOpt, oneshot::Sender<Result<String>>),
Input(InputOpt, mpsc::UnboundedSender<Result<String, InputError>>),

// Tasks
Open(Vec<(OsString, String)>, Option<Opener>),
Expand Down Expand Up @@ -104,8 +104,9 @@ macro_rules! emit {
$crate::Event::Select($opt, tx).wait(rx)
}};
(Input($opt:expr)) => {{
let (tx, rx) = tokio::sync::oneshot::channel();
$crate::Event::Input($opt, tx).wait(rx)
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
$crate::Event::Input($opt, tx).emit();
rx
}};

(Open($targets:expr, $opener:expr)) => {
Expand Down
41 changes: 31 additions & 10 deletions core/src/input/input.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use std::ops::Range;

use anyhow::{anyhow, Result};
use config::keymap::Key;
use crossterm::event::KeyCode;
use shared::CharKind;
use tokio::sync::oneshot::Sender;
use shared::{CharKind, InputError};
use tokio::sync::mpsc::UnboundedSender;
use unicode_width::UnicodeWidthStr;

use super::{mode::InputMode, op::InputOp, InputOpt, InputSnap, InputSnaps};
Expand All @@ -17,30 +16,36 @@ pub struct Input {

title: String,
pub position: Position,
callback: Option<Sender<Result<String>>>,

// Typing
callback: Option<UnboundedSender<Result<String, InputError>>>,
realtime: bool,

// Shell
pub(super) highlight: bool,
}

impl Input {
pub fn show(&mut self, opt: InputOpt, tx: Sender<Result<String>>) {
pub fn show(&mut self, opt: InputOpt, tx: UnboundedSender<Result<String, InputError>>) {
self.close(false);
self.snaps.reset(opt.value);
self.visible = true;

self.title = opt.title;
self.position = opt.position;

// Typing
self.callback = Some(tx);
self.realtime = opt.realtime;

// Shell
self.highlight = opt.highlight;
}

pub fn close(&mut self, submit: bool) -> bool {
if let Some(cb) = self.callback.take() {
let _ =
cb.send(if submit { Ok(self.snap_mut().value.clone()) } else { Err(anyhow!("canceled")) });
let value = self.snap_mut().value.clone();
let _ = cb.send(if submit { Ok(value) } else { Err(InputError::Canceled(value)) });
}

self.visible = false;
Expand Down Expand Up @@ -201,22 +206,26 @@ impl Input {
}

pub fn type_str(&mut self, s: &str) -> bool {
let snap = self.snap_mut();
let snap = self.snaps.current_mut();
if snap.cursor < 1 {
snap.value.insert_str(0, s);
} else {
snap.value.insert_str(snap.idx(snap.cursor).unwrap(), s);
}

self.flush_value();
self.move_(s.chars().count() as isize)
}

pub fn backspace(&mut self) -> bool {
let snap = self.snap_mut();
let snap = self.snaps.current_mut();
if snap.cursor < 1 {
return false;
} else {
snap.value.remove(snap.idx(snap.cursor - 1).unwrap());
}

self.flush_value();
self.move_(-1)
}

Expand Down Expand Up @@ -278,7 +287,7 @@ impl Input {

fn handle_op(&mut self, cursor: usize, include: bool) -> bool {
let old = self.snap().clone();
let snap = self.snap_mut();
let snap = self.snaps.current_mut();

match snap.op {
InputOp::None | InputOp::Select(_) => {
Expand All @@ -296,6 +305,10 @@ impl Input {
snap.op = InputOp::None;
snap.mode = if insert { InputMode::Insert } else { InputMode::Normal };
snap.cursor = range.start;

if self.realtime {
self.callback.as_ref().unwrap().send(Err(InputError::Typed(snap.value.clone()))).ok();
}
}
InputOp::Yank(_) => {
let range = snap.op.range(cursor, include).unwrap();
Expand All @@ -316,6 +329,14 @@ impl Input {
}
true
}

#[inline]
fn flush_value(&self) {
if self.realtime {
let value = self.snap().value.clone();
self.callback.as_ref().unwrap().send(Err(InputError::Typed(value))).ok();
}
}
}

impl Input {
Expand Down
11 changes: 11 additions & 0 deletions core/src/input/option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub struct InputOpt {
pub title: String,
pub value: String,
pub position: Position,
pub realtime: bool,
pub highlight: bool,
}

Expand All @@ -15,6 +16,7 @@ impl InputOpt {
title: title.as_ref().to_owned(),
value: String::new(),
position: Position::Top(/* TODO: hardcode */ Rect { x: 0, y: 2, width: 50, height: 3 }),
realtime: false,
highlight: false,
}
}
Expand All @@ -27,15 +29,24 @@ impl InputOpt {
// TODO: hardcode
Rect { x: 0, y: 1, width: 50, height: 3 },
),
realtime: false,
highlight: false,
}
}

#[inline]
pub fn with_value(mut self, value: impl AsRef<str>) -> Self {
self.value = value.as_ref().to_owned();
self
}

#[inline]
pub fn with_realtime(mut self) -> Self {
self.realtime = true;
self
}

#[inline]
pub fn with_highlight(mut self) -> Self {
self.highlight = true;
self
Expand Down
14 changes: 7 additions & 7 deletions core/src/manager/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@ impl Manager {
}

tokio::spawn(async move {
let result = emit!(Input(InputOpt::top(format!(
let mut result = emit!(Input(InputOpt::top(format!(
"There are {tasks} tasks running, sure to quit? (y/N)"
))));

if let Ok(choice) = result.await {
if choice.to_lowercase() == "y" {
if let Some(Ok(choice)) = result.recv().await {
if choice == "y" || choice == "Y" {
emit!(Quit);
}
}
Expand Down Expand Up @@ -179,9 +179,9 @@ impl Manager {
pub fn create(&self) -> bool {
let cwd = self.cwd().to_owned();
tokio::spawn(async move {
let result = emit!(Input(InputOpt::top("Create:")));
let mut result = emit!(Input(InputOpt::top("Create:")));

if let Ok(name) = result.await {
if let Some(Ok(name)) = result.recv().await {
let path = cwd.join(&name);
let hovered = path.components().take(cwd.components().count() + 1).collect::<PathBuf>();

Expand Down Expand Up @@ -212,11 +212,11 @@ impl Manager {
};

tokio::spawn(async move {
let result = emit!(Input(
let mut result = emit!(Input(
InputOpt::hovered("Rename:").with_value(hovered.file_name().unwrap().to_string_lossy())
));

if let Ok(new) = result.await {
if let Some(Ok(new)) = result.recv().await {
let to = hovered.parent().unwrap().join(new);
fs::rename(&hovered, to).await.ok();
}
Expand Down
18 changes: 10 additions & 8 deletions core/src/manager/tab.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{borrow::Cow, collections::{BTreeMap, BTreeSet}, ffi::{OsStr, OsString}, mem, time::Duration};

use anyhow::{Error, Result};
use anyhow::{bail, Error, Result};
use config::open::Opener;
use shared::{Defer, Url};
use tokio::{pin, task::JoinHandle};
Expand Down Expand Up @@ -123,10 +123,10 @@ impl Tab {

pub fn cd_interactive(&mut self, target: Url) -> bool {
tokio::spawn(async move {
let result =
let mut result =
emit!(Input(InputOpt::top("Change directory:").with_value(target.to_string_lossy())));

if let Ok(s) = result.await {
if let Some(Ok(s)) = result.recv().await {
emit!(Cd(Url::from(s)));
}
});
Expand Down Expand Up @@ -241,7 +241,9 @@ impl Tab {
let hidden = self.show_hidden;

self.search = Some(tokio::spawn(async move {
let subject = emit!(Input(InputOpt::top("Search:"))).await?;
let Some(Ok(subject)) = emit!(Input(InputOpt::top("Search:"))).recv().await else {
bail!("canceled")
};

let rx = if grep {
external::rg(external::RgOpt { cwd: cwd.clone(), hidden, subject })
Expand Down Expand Up @@ -309,10 +311,10 @@ impl Tab {
let mut exec = exec.to_owned();
tokio::spawn(async move {
if !confirm || exec.is_empty() {
let result = emit!(Input(InputOpt::top("Shell:").with_value(&exec).with_highlight()));
match result.await {
Ok(e) => exec = e,
Err(_) => return,
let mut result = emit!(Input(InputOpt::top("Shell:").with_value(&exec).with_highlight()));
match result.recv().await {
Some(Ok(e)) => exec = e,
_ => return,
}
}

Expand Down
6 changes: 3 additions & 3 deletions core/src/tasks/tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,14 @@ impl Tasks {
let scheduler = self.scheduler.clone();
tokio::spawn(async move {
let s = if targets.len() > 1 { "s" } else { "" };
let result = emit!(Input(InputOpt::hovered(if permanently {
let mut result = emit!(Input(InputOpt::hovered(if permanently {
format!("Delete selected file{s} permanently? (y/N)")
} else {
format!("Move selected file{s} to trash? (y/N)")
})));

if let Ok(choice) = result.await {
if choice.to_lowercase() != "y" {
if let Some(Ok(choice)) = result.recv().await {
if choice != "y" && choice != "Y" {
return;
}
for p in targets {
Expand Down
18 changes: 18 additions & 0 deletions shared/src/errors/input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use std::{error::Error, fmt::{self, Display}};

#[derive(Debug)]
pub enum InputError {
Typed(String),
Canceled(String),
}

impl Display for InputError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Typed(text) => write!(f, "Typed error: {text}"),
Self::Canceled(text) => write!(f, "Canceled error: {text}"),
}
}
}

impl Error for InputError {}
2 changes: 2 additions & 0 deletions shared/src/errors/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod input;
mod peek;

pub use input::*;
pub use peek::*;