summaryrefslogtreecommitdiffstats
path: root/src/shell/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/shell/mod.rs')
-rw-r--r--src/shell/mod.rs497
1 files changed, 158 insertions, 339 deletions
diff --git a/src/shell/mod.rs b/src/shell/mod.rs
index f7080a4..fa7147b 100644
--- a/src/shell/mod.rs
+++ b/src/shell/mod.rs
@@ -1,16 +1,16 @@
use crate::shell::prelude::*;
-use notify::Watcher as _;
use textmode::Textmode as _;
mod event;
-mod git;
mod history;
+mod inputs;
+mod old_history;
mod prelude;
mod readline;
-pub async fn main() -> anyhow::Result<i32> {
- let mut input = textmode::Input::new().await?;
+pub async fn main() -> Result<i32> {
+ let mut input = textmode::blocking::Input::new()?;
let mut output = textmode::Output::new().await?;
// avoid the guards getting stuck in a task that doesn't run to
@@ -18,163 +18,40 @@ pub async fn main() -> anyhow::Result<i32> {
let _input_guard = input.take_raw_guard();
let _output_guard = output.take_screen_guard();
- let (event_w, event_r) = async_std::channel::unbounded();
+ let (event_w, event_r) = event::channel();
- {
- // nix::sys::signal::Signal is repr(i32)
- #[allow(clippy::as_conversions)]
- let signals = signal_hook_async_std::Signals::new(&[
- nix::sys::signal::Signal::SIGWINCH as i32,
- ])?;
- let event_w = event_w.clone();
- async_std::task::spawn(async move {
- // nix::sys::signal::Signal is repr(i32)
- #[allow(clippy::as_conversions)]
- let mut signals = async_std::stream::once(
- nix::sys::signal::Signal::SIGWINCH as i32,
- )
- .chain(signals);
- while signals.next().await.is_some() {
- event_w
- .send(Event::Resize(
- terminal_size::terminal_size().map_or(
- (24, 80),
- |(
- terminal_size::Width(w),
- terminal_size::Height(h),
- )| { (h, w) },
- ),
- ))
- .await
- .unwrap();
- }
- });
- }
-
- {
- let event_w = event_w.clone();
- async_std::task::spawn(async move {
- while let Some(key) = input.read_key().await.unwrap() {
- event_w.send(Event::Key(key)).await.unwrap();
- }
- });
- }
-
- // redraw the clock every second
- {
- let event_w = event_w.clone();
- async_std::task::spawn(async move {
- let first_sleep = 1_000_000_000_u64.saturating_sub(
- time::OffsetDateTime::now_utc().nanosecond().into(),
- );
- async_std::task::sleep(std::time::Duration::from_nanos(
- first_sleep,
- ))
- .await;
- let mut interval = async_std::stream::interval(
- std::time::Duration::from_secs(1),
- );
- event_w.send(Event::ClockTimer).await.unwrap();
- while interval.next().await.is_some() {
- event_w.send(Event::ClockTimer).await.unwrap();
- }
- });
- }
-
- let (git_w, git_r): (async_std::channel::Sender<std::path::PathBuf>, _) =
- async_std::channel::unbounded();
- {
- let event_w = event_w.clone();
- let mut _active_watcher = None;
- async_std::task::spawn(async move {
- while let Ok(mut dir) = git_r.recv().await {
- while let Ok(newer_dir) = git_r.try_recv() {
- dir = newer_dir;
- }
- let repo = git2::Repository::discover(&dir).ok();
- if repo.is_some() {
- let (sync_watch_w, sync_watch_r) =
- std::sync::mpsc::channel();
- let (watch_w, watch_r) = async_std::channel::unbounded();
- let mut watcher = notify::RecommendedWatcher::new(
- sync_watch_w,
- std::time::Duration::from_millis(100),
- )
- .unwrap();
- watcher
- .watch(&dir, notify::RecursiveMode::Recursive)
- .unwrap();
- async_std::task::spawn(blocking::unblock(move || {
- while let Ok(event) = sync_watch_r.recv() {
- let watch_w = watch_w.clone();
- let send_failed =
- async_std::task::block_on(async move {
- watch_w.send(event).await.is_err()
- });
- if send_failed {
- break;
- }
- }
- }));
- let event_w = event_w.clone();
- async_std::task::spawn(async move {
- while watch_r.recv().await.is_ok() {
- let repo = git2::Repository::discover(&dir).ok();
- let info = blocking::unblock(|| {
- repo.map(|repo| git::Info::new(&repo))
- })
- .await;
- if event_w
- .send(Event::GitInfo(info))
- .await
- .is_err()
- {
- break;
- }
- }
- });
- _active_watcher = Some(watcher);
- } else {
- _active_watcher = None;
- }
- let info = blocking::unblock(|| {
- repo.map(|repo| git::Info::new(&repo))
- })
- .await;
- event_w.send(Event::GitInfo(info)).await.unwrap();
- }
- });
- }
+ let inputs = inputs::Handler::new(input, event_w.clone()).unwrap();
let mut shell = Shell::new(crate::info::get_offset())?;
let mut prev_dir = shell.env.pwd().to_path_buf();
- git_w.send(prev_dir.clone()).await.unwrap();
- let event_reader = event::Reader::new(event_r);
- while let Some(event) = event_reader.recv().await {
- let dir = shell.env().pwd();
- if dir != prev_dir {
- prev_dir = dir.to_path_buf();
- git_w.send(dir.to_path_buf()).await.unwrap();
- }
- match shell.handle_event(event, &event_w).await {
+ inputs.new_dir(prev_dir.clone());
+ while let Some(event) = event_r.recv().await {
+ match shell.handle_event(event, &event_w) {
Some(Action::Refresh) => {
- shell.render(&mut output).await?;
+ shell.render(&mut output)?;
output.refresh().await?;
}
Some(Action::HardRefresh) => {
- shell.render(&mut output).await?;
+ shell.render(&mut output)?;
output.hard_refresh().await?;
}
Some(Action::Resize(rows, cols)) => {
output.set_size(rows, cols);
- shell.render(&mut output).await?;
+ shell.render(&mut output)?;
output.hard_refresh().await?;
}
Some(Action::Quit) => break,
None => {}
}
+ let dir = shell.env().pwd();
+ if dir != prev_dir {
+ prev_dir = dir.to_path_buf();
+ inputs.new_dir(dir.to_path_buf());
+ }
}
+ shell.history.save().await;
+
Ok(0)
}
@@ -201,8 +78,9 @@ pub enum Action {
pub struct Shell {
readline: readline::Readline,
history: history::History,
+ old_history: old_history::History,
env: Env,
- git: Option<git::Info>,
+ git: Option<inputs::GitInfo>,
focus: Focus,
scene: Scene,
escape: bool,
@@ -211,13 +89,14 @@ pub struct Shell {
}
impl Shell {
- pub fn new(offset: time::UtcOffset) -> anyhow::Result<Self> {
+ pub fn new(offset: time::UtcOffset) -> Result<Self> {
let mut env = Env::new()?;
env.set_var("SHELL", std::env::current_exe()?);
env.set_var("TERM", "screen");
Ok(Self {
readline: readline::Readline::new(),
history: history::History::new(),
+ old_history: old_history::History::new(),
env,
git: None,
focus: Focus::Readline,
@@ -228,87 +107,76 @@ impl Shell {
})
}
- pub async fn render(
- &self,
- out: &mut impl textmode::Textmode,
- ) -> anyhow::Result<()> {
+ pub fn render(&self, out: &mut impl textmode::Textmode) -> Result<()> {
out.clear();
out.write(&vt100::Parser::default().screen().input_mode_formatted());
match self.scene {
Scene::Readline => match self.focus {
Focus::Readline => {
- self.history
- .render(
+ self.history.render(
+ out,
+ self.readline.lines(),
+ None,
+ false,
+ self.offset,
+ );
+ self.readline.render(
+ out,
+ &self.env,
+ self.git.as_ref(),
+ true,
+ self.offset,
+ )?;
+ }
+ Focus::History(idx) => {
+ if self.hide_readline {
+ self.history.render(
+ out,
+ 0,
+ Some(idx),
+ false,
+ self.offset,
+ );
+ } else {
+ self.history.render(
out,
self.readline.lines(),
- None,
+ Some(idx),
false,
self.offset,
- )
- .await?;
- self.readline
- .render(
+ );
+ let pos = out.screen().cursor_position();
+ self.readline.render(
out,
&self.env,
self.git.as_ref(),
- true,
+ false,
self.offset,
- )
- .await?;
- }
- Focus::History(idx) => {
- if self.hide_readline {
- self.history
- .render(out, 0, Some(idx), false, self.offset)
- .await?;
- } else {
- self.history
- .render(
- out,
- self.readline.lines(),
- Some(idx),
- false,
- self.offset,
- )
- .await?;
- let pos = out.screen().cursor_position();
- self.readline
- .render(
- out,
- &self.env,
- self.git.as_ref(),
- false,
- self.offset,
- )
- .await?;
+ )?;
out.move_to(pos.0, pos.1);
}
}
Focus::Scrolling(idx) => {
- self.history
- .render(
- out,
- self.readline.lines(),
- idx,
- true,
- self.offset,
- )
- .await?;
- self.readline
- .render(
- out,
- &self.env,
- self.git.as_ref(),
- idx.is_none(),
- self.offset,
- )
- .await?;
+ self.history.render(
+ out,
+ self.readline.lines(),
+ idx,
+ true,
+ self.offset,
+ );
+ self.readline.render(
+ out,
+ &self.env,
+ self.git.as_ref(),
+ idx.is_none(),
+ self.offset,
+ )?;
out.hide_cursor(true);
}
},
Scene::Fullscreen => {
if let Focus::History(idx) = self.focus {
- self.history.render_fullscreen(out, idx).await;
+ self.history.entry(idx).render_fullscreen(out);
} else {
unreachable!();
}
@@ -317,79 +185,72 @@ impl Shell {
Ok(())
}
- pub async fn handle_event(
+ pub fn handle_event(
&mut self,
event: Event,
- event_w: &async_std::channel::Sender<Event>,
+ event_w: &crate::shell::event::Writer,
) -> Option<Action> {
match event {
Event::Key(key) => {
return if self.escape {
self.escape = false;
- self.handle_key_escape(key, event_w.clone()).await
+ self.handle_key_escape(&key, event_w.clone())
} else if key == textmode::Key::Ctrl(b'e') {
self.escape = true;
None
} else {
match self.focus {
Focus::Readline => {
- self.handle_key_readline(key, event_w.clone())
- .await
+ self.handle_key_readline(&key, event_w.clone())
}
Focus::History(idx) => {
- self.handle_key_history(key, idx).await;
+ self.handle_key_history(key, idx);
None
}
Focus::Scrolling(_) => {
- self.handle_key_escape(key, event_w.clone()).await
+ self.handle_key_escape(&key, event_w.clone())
}
}
};
}
Event::Resize(new_size) => {
- self.readline.resize(new_size).await;
- self.history.resize(new_size).await;
+ self.readline.resize(new_size);
+ self.history.resize(new_size);
return Some(Action::Resize(new_size.0, new_size.1));
}
Event::PtyOutput => {
// the number of visible lines may have changed, so make sure
// the focus is still visible
- self.history
- .make_focus_visible(
- self.readline.lines(),
- self.focus_idx(),
- matches!(self.focus, Focus::Scrolling(_)),
- )
- .await;
- self.scene = self.default_scene(self.focus, None).await;
- }
- Event::PtyClose => {
- if let Some(idx) = self.focus_idx() {
- let entry = self.history.entry(idx).await;
- if !entry.running() {
+ self.history.make_focus_visible(
+ self.readline.lines(),
+ self.focus_idx(),
+ matches!(self.focus, Focus::Scrolling(_)),
+ );
+ self.scene = self.default_scene(self.focus);
+ }
+ Event::ChildExit(idx, exit_info, env) => {
+ self.history.entry_mut(idx).exited(exit_info);
+ if self.focus_idx() == Some(idx) {
+ if let Some(env) = env {
if self.hide_readline {
let idx = self.env.idx();
- self.env = entry.env().clone();
+ self.env = env;
self.env.set_idx(idx);
}
- self.set_focus(
- if self.hide_readline {
- Focus::Readline
- } else {
- Focus::Scrolling(Some(idx))
- },
- Some(entry),
- )
- .await;
}
+ self.set_focus(if self.hide_readline {
+ Focus::Readline
+ } else {
+ Focus::Scrolling(Some(idx))
+ });
}
}
Event::ChildRunPipeline(idx, span) => {
- self.history.entry(idx).await.set_span(span);
+ self.history.entry_mut(idx).set_span(span);
}
Event::ChildSuspend(idx) => {
if self.focus_idx() == Some(idx) {
- self.set_focus(Focus::Readline, None).await;
+ self.set_focus(Focus::Readline);
}
}
Event::GitInfo(info) => {
@@ -400,18 +261,17 @@ impl Shell {
Some(Action::Refresh)
}
- async fn handle_key_escape(
+ fn handle_key_escape(
&mut self,
- key: textmode::Key,
- event_w: async_std::channel::Sender<Event>,
+ key: &textmode::Key,
+ event_w: crate::shell::event::Writer,
) -> Option<Action> {
match key {
textmode::Key::Ctrl(b'd') => {
return Some(Action::Quit);
}
textmode::Key::Ctrl(b'e') => {
- self.set_focus(Focus::Scrolling(self.focus_idx()), None)
- .await;
+ self.set_focus(Focus::Scrolling(self.focus_idx()));
}
textmode::Key::Ctrl(b'l') => {
return Some(Action::HardRefresh);
@@ -419,48 +279,37 @@ impl Shell {
textmode::Key::Ctrl(b'm') => {
if let Some(idx) = self.focus_idx() {
self.readline.clear_input();
- let entry = self.history.entry(idx).await;
- let input = entry.cmd();
- let idx = self
- .history
- .run(input, &self.env, event_w.clone())
- .await
- .unwrap();
- self.set_focus(Focus::History(idx), Some(entry)).await;
+ self.history.run(
+ self.history.entry(idx).cmd().to_string(),
+ self.env.clone(),
+ event_w,
+ );
+ let idx = self.history.entry_count() - 1;
+ self.set_focus(Focus::History(idx));
self.hide_readline = true;
self.env.set_idx(idx + 1);
} else {
- self.set_focus(Focus::Readline, None).await;
+ self.set_focus(Focus::Readline);
}
}
textmode::Key::Char(' ') => {
- let idx = self.focus_idx();
- let (focus, entry) = if let Some(idx) = idx {
- let entry = self.history.entry(idx).await;
- (entry.running(), Some(entry))
+ if let Some(idx) = self.focus_idx() {
+ if self.history.entry(idx).running() {
+ self.set_focus(Focus::History(idx));
+ }
} else {
- (true, None)
- };
- if focus {
- self.set_focus(
- idx.map_or(Focus::Readline, |idx| {
- Focus::History(idx)
- }),
- entry,
- )
- .await;
+ self.set_focus(Focus::Readline);
}
}
textmode::Key::Char('e') => {
if let Focus::History(idx) = self.focus {
- self.handle_key_history(textmode::Key::Ctrl(b'e'), idx)
- .await;
+ self.handle_key_history(textmode::Key::Ctrl(b'e'), idx);
}
}
textmode::Key::Char('f') => {
if let Some(idx) = self.focus_idx() {
- let mut entry = self.history.entry(idx).await;
let mut focus = Focus::History(idx);
+ let entry = self.history.entry_mut(idx);
if let Focus::Scrolling(_) = self.focus {
entry.set_fullscreen(true);
} else {
@@ -469,38 +318,30 @@ impl Shell {
focus = Focus::Scrolling(Some(idx));
}
}
- self.set_focus(focus, Some(entry)).await;
+ self.set_focus(focus);
}
}
textmode::Key::Char('i') => {
if let Some(idx) = self.focus_idx() {
- let entry = self.history.entry(idx).await;
- self.readline.set_input(entry.cmd());
- self.set_focus(Focus::Readline, Some(entry)).await;
+ self.readline
+ .set_input(self.history.entry(idx).cmd().to_string());
+ self.set_focus(Focus::Readline);
}
}
textmode::Key::Char('j') | textmode::Key::Down => {
- self.set_focus(
- Focus::Scrolling(self.scroll_down(self.focus_idx())),
- None,
- )
- .await;
+ self.set_focus(Focus::Scrolling(self.scroll_down()));
}
textmode::Key::Char('k') | textmode::Key::Up => {
- self.set_focus(
- Focus::Scrolling(self.scroll_up(self.focus_idx())),
- None,
- )
- .await;
+ self.set_focus(Focus::Scrolling(self.scroll_up()));
}
textmode::Key::Char('n') => {
- self.set_focus(self.next_running().await, None).await;
+ self.set_focus(self.next_running());
}
textmode::Key::Char('p') => {
- self.set_focus(self.prev_running().await, None).await;
+ self.set_focus(self.prev_running());
}
textmode::Key::Char('r') => {
- self.set_focus(Focus::Readline, None).await;
+ self.set_focus(Focus::Readline);
}
_ => {
return None;
@@ -509,10 +350,10 @@ impl Shell {
Some(Action::Refresh)
}
- async fn handle_key_readline(
+ fn handle_key_readline(
&mut self,
- key: textmode::Key,
- event_w: async_std::channel::Sender<Event>,
+ key: &textmode::Key,
+ event_w: crate::shell::event::Writer,
) -> Option<Action> {
match key {
textmode::Key::Char(c) => {
@@ -528,12 +369,13 @@ impl Shell {
textmode::Key::Ctrl(b'm') => {
let input = self.readline.input();
if !input.is_empty() {
- let idx = self
- .history
- .run(input, &self.env, event_w.clone())
- .await
- .unwrap();
- self.set_focus(Focus::History(idx), None).await;
+ self.history.run(
+ input.to_string(),
+ self.env.clone(),
+ event_w,
+ );
+ let idx = self.history.entry_count() - 1;
+ self.set_focus(Focus::History(idx));
self.hide_readline = true;
self.env.set_idx(idx + 1);
self.readline.clear_input();
@@ -546,11 +388,7 @@ impl Shell {
textmode::Key::Up => {
let entry_count = self.history.entry_count();
if entry_count > 0 {
- self.set_focus(
- Focus::Scrolling(Some(entry_count - 1)),
- None,
- )
- .await;
+ self.set_focus(Focus::Scrolling(Some(entry_count - 1)));
}
}
_ => return None,
@@ -558,24 +396,15 @@ impl Shell {
Some(Action::Refresh)
}
- async fn handle_key_history(&mut self, key: textmode::Key, idx: usize) {
- self.history.send_input(idx, key.into_bytes()).await;
+ fn handle_key_history(&mut self, key: textmode::Key, idx: usize) {
+ self.history.entry(idx).input(key.into_bytes());
}
- async fn default_scene(
- &self,
- focus: Focus,
- entry: Option<crate::mutex::Guard<history::Entry>>,
- ) -> Scene {
+ fn default_scene(&self, focus: Focus) -> Scene {
match focus {
Focus::Readline | Focus::Scrolling(_) => Scene::Readline,
Focus::History(idx) => {
- let fullscreen = if let Some(entry) = entry {
- entry.should_fullscreen()
- } else {
- self.history.entry(idx).await.should_fullscreen()
- };
- if fullscreen {
+ if self.history.entry(idx).should_fullscreen() {
Scene::Fullscreen
} else {
Scene::Readline
@@ -584,25 +413,15 @@ impl Shell {
}
}
- async fn set_focus(
- &mut self,
- new_focus: Focus,
- entry: Option<crate::mutex::Guard<history::Entry>>,
- ) {
+ fn set_focus(&mut self, new_focus: Focus) {
self.focus = new_focus;
self.hide_readline = false;
- self.scene = self.default_scene(new_focus, entry).await;
- // passing entry into default_scene above consumes it, which means
- // that the mutex lock will be dropped before we call into
- // make_focus_visible, which is important because otherwise we might
- // get a deadlock depending on what is visible
- self.history
- .make_focus_visible(
- self.readline.lines(),
- self.focus_idx(),
- matches!(self.focus, Focus::Scrolling(_)),
- )
- .await;
+ self.scene = self.default_scene(new_focus);
+ self.history.make_focus_visible(
+ self.readline.lines(),
+ self.focus_idx(),
+ matches!(self.focus, Focus::Scrolling(_)),
+ );
}
fn env(&self) -> &Env {
@@ -617,8 +436,8 @@ impl Shell {
}
}
- fn scroll_up(&self, idx: Option<usize>) -> Option<usize> {
- idx.map_or_else(
+ fn scroll_up(&self) -> Option<usize> {
+ self.focus_idx().map_or_else(
|| {
let count = self.history.entry_count();
if count == 0 {
@@ -631,8 +450,8 @@ impl Shell {
)
}
- fn scroll_down(&self, idx: Option<usize>) -> Option<usize> {
- idx.and_then(|idx| {
+ fn scroll_down(&self) -> Option<usize> {
+ self.focus_idx().and_then(|idx| {
if idx >= self.history.entry_count() - 1 {
None
} else {
@@ -641,25 +460,25 @@ impl Shell {
})
}
- async fn next_running(&self) -> Focus {
+ fn next_running(&self) -> Focus {
let count = self.history.entry_count();
let cur = self.focus_idx().unwrap_or(count);
for idx in ((cur + 1)..count).chain(0..cur) {
- if self.history.entry(idx).await.running() {
+ if self.history.entry(idx).running() {
return Focus::History(idx);
}
}
- self.focus_idx().map_or(Focus::Readline, Focus::History)
+ self.focus
}
- async fn prev_running(&self) -> Focus {
+ fn prev_running(&self) -> Focus {
let count = self.history.entry_count();
let cur = self.focus_idx().unwrap_or(count);
for idx in ((cur + 1)..count).chain(0..cur).rev() {
- if self.history.entry(idx).await.running() {
+ if self.history.entry(idx).running() {
return Focus::History(idx);
}
}
- self.focus_idx().map_or(Focus::Readline, Focus::History)
+ self.focus
}
}