Skip to content

Commit

Permalink
great progress
Browse files Browse the repository at this point in the history
  • Loading branch information
alexpasmantier committed Sep 21, 2024
1 parent a45a4f7 commit 18ea6a4
Show file tree
Hide file tree
Showing 13 changed files with 357 additions and 360 deletions.
8 changes: 4 additions & 4 deletions .config/config.json5
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"keybindings": {
"Home": {
"Input": {
"<q>": "Quit", // Quit the application
"<Ctrl-d>": "Quit", // Another way to quit
"<Ctrl-c>": "Quit", // Yet another way to quit
"<Ctrl-z>": "Suspend" // Suspend the application
"<ctrl-d>": "Quit", // Another way to quit
"<ctrl-c>": "Quit", // Yet another way to quit
"<Ctrl-z>": "Suspend", // Suspend the application
},
}
}
12 changes: 12 additions & 0 deletions .config/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FIXME: this doesn't work
[keybindings]
Input = {
"<q>": "Quit", // Quit the application
"<ctrl-d>": "Quit", // Another way to quit
"<ctrl-c>": "Quit", // Yet another way to quit
"<Ctrl-z>": "Suspend", // Suspend the application
},

Help = {
"<q>" = "Quit" # Quit the help panel
}
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ strum = { version = "0.26.3", features = ["derive"] }
tokio = { version = "1.39.3", features = ["full"] }
tokio-stream = "0.1.16"
tokio-util = "0.7.11"
toml = "0.8.19"
tracing = "0.1.40"
tracing-error = "0.2.0"
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "serde"] }
Expand Down
1 change: 1 addition & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- shell history
- grep (maybe also inside pdfs and other files (see rga))
- fd
- recent directories
- git
- makefile commands
-
13 changes: 13 additions & 0 deletions src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,26 @@ use strum::Display;

#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
pub enum Action {
KeyPress(char),
Tick,
Render,
Resize(u16, u16),
SelectNextEntry,
SelectPreviousEntry,
GoToPaneUp,
GoToPaneDown,
GoToPaneLeft,
GoToPaneRight,
ScrollPreviewUp,
ScrollPreviewDown,
ScrollPreviewHalfPageUp,
ScrollPreviewHalfPageDown,
OpenEntry,
Suspend,
Resume,
Quit,
ClearScreen,
Error(String),
Help,
NoOp,
}
192 changes: 67 additions & 125 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,17 @@
use std::sync::{Arc, Mutex};

use color_eyre::Result;
use crossterm::event::KeyEvent;
use ratatui::prelude::Rect;
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc;
use tracing::{debug, info};
use tracing::debug;

use crate::{
action::Action,
components::{fps::FpsCounter, home::Home, Component},
config::Config,
tui::{Event, Tui},
event::{Event, EventLoop, Key},
render::{render, RenderingTask},
tui::Tui,
};

pub struct App {
Expand All @@ -49,23 +49,28 @@ pub struct App {
should_quit: bool,
should_suspend: bool,
mode: Mode,
last_tick_key_events: Vec<KeyEvent>,
action_tx: mpsc::UnboundedSender<Action>,
action_rx: mpsc::UnboundedReceiver<Action>,
event_rx: mpsc::UnboundedReceiver<Event<Key>>,
event_abort_tx: mpsc::UnboundedSender<()>,
render_tx: mpsc::UnboundedSender<RenderingTask>,
render_rx: mpsc::UnboundedReceiver<RenderingTask>,
}

#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Mode {
#[default]
Home,
Help,
Input,
Preview,
Results,
}

impl App {
pub fn new(tick_rate: f64, frame_rate: f64) -> Result<Self> {
let (action_tx, action_rx) = mpsc::unbounded_channel();
let (render_tx, mut render_rx) = mpsc::unbounded_channel();
let (render_tx, _) = mpsc::unbounded_channel();
let event_loop = EventLoop::new(Some(std::time::Duration::from_millis(250)), true);

Ok(Self {
tick_rate,
frame_rate,
Expand All @@ -76,92 +81,72 @@ impl App {
should_quit: false,
should_suspend: false,
config: Config::new()?,
mode: Mode::Home,
last_tick_key_events: Vec::new(),
mode: Mode::Input,
action_tx,
action_rx,
event_rx: event_loop.rx,
event_abort_tx: event_loop.abort_tx,
render_tx,
render_rx,
})
}

pub async fn run(&mut self) -> Result<()> {
let mut tui = Tui::new()?
.tick_rate(self.tick_rate)
.frame_rate(self.frame_rate);
let mut tui = Tui::new()?.frame_rate(self.frame_rate);
// this starts the event handling loop in Tui
tui.enter();

// Rendering loop
let (render_tx, render_rx) = mpsc::unbounded_channel();
self.render_tx = render_tx.clone();
let action_tx_r = self.action_tx.clone();
let config_r = self.config.clone();
let components_r = self.components.clone();
let frame_rate = self.frame_rate;
tokio::spawn(async move {
self.render(tui).await;
render(
&mut tui,
render_rx,
render_tx,
action_tx_r,
config_r,
components_r,
frame_rate,
)
.await
});

// Action handling loop
// event handling loop
let action_tx = self.action_tx.clone();
loop {
self.handle_events(&mut tui).await?;
// handle event and convert to action
if let Some(event) = self.event_rx.recv().await {
let action = self.convert_event_to_action(event);
action_tx.send(action)?;
}

self.handle_actions()?;
if self.should_suspend {
tui.suspend()?;
action_tx.send(Action::Resume)?;
action_tx.send(Action::ClearScreen)?;
// tui.mouse(true);
tui.enter()?;
} else if self.should_quit {
tui.stop()?;

if self.should_quit {
self.event_abort_tx.send(())?;
break;
}
}
Ok(())
}

/// Handle incoming events and produce actions if necessary. These actions are then sent to the
/// action channel for processing.
async fn handle_events(&mut self, tui: &mut Tui) -> Result<()> {
let Some(event) = tui.next_event().await else {
return Ok(());
};
let action_tx = self.action_tx.clone();
fn convert_event_to_action(&self, event: Event<Key>) -> Action {
match event {
Event::Quit => action_tx.send(Action::Quit)?,
Event::Tick => action_tx.send(Action::Tick)?,
Event::Render => action_tx.send(Action::Render)?,
Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?,
Event::Key(key) => self.handle_key_event(key)?,
_ => {}
}
for component in self.components.lock().unwrap().iter_mut() {
if let Some(action) = component.handle_events(Some(event.clone()))? {
action_tx.send(action)?;
}
}
Ok(())
}

fn handle_key_event(&mut self, key: KeyEvent) -> Result<()> {
let action_tx = self.action_tx.clone();
let Some(keymap) = self.config.keybindings.get(&self.mode) else {
return Ok(());
};
match keymap.get(&vec![key]) {
Some(action) => {
info!("Got action: {action:?}");
action_tx.send(action.clone())?;
}
_ => {
// If the key was not handled as a single key action,
// then consider it for multi-key combinations.
self.last_tick_key_events.push(key);

// Check for multi-key combinations
if let Some(action) = keymap.get(&self.last_tick_key_events) {
info!("Got action: {action:?}");
action_tx.send(action.clone())?;
}
}
Event::Input(keycode) => self
.config
.keybindings
.get(&self.mode)
.and_then(|keymap| keymap.get(&keycode).cloned())
.unwrap_or(Action::NoOp),
Event::Tick => Action::Tick,
Event::Resize(x, y) => Action::Resize(x, y),
Event::FocusGained => Action::Resume,
Event::FocusLost => Action::Suspend,
_ => Action::NoOp,
}
Ok(())
}

fn handle_actions(&mut self) -> Result<()> {
Expand All @@ -170,12 +155,19 @@ impl App {
debug!("{action:?}");
}
match action {
Action::Tick => {
self.last_tick_key_events.drain(..);
Action::Tick => {}
Action::Quit => {
self.should_quit = true;
self.render_tx.send(RenderingTask::Quit)?
}
Action::Suspend => {
self.should_suspend = true;
self.render_tx.send(RenderingTask::Suspend)?
}
Action::Resume => {
self.should_suspend = false;
self.render_tx.send(RenderingTask::Resume)?
}
Action::Quit => self.should_quit = true,
Action::Suspend => self.should_suspend = true,
Action::Resume => self.should_suspend = false,
Action::ClearScreen => self.render_tx.send(RenderingTask::ClearScreen)?,
Action::Resize(w, h) => self.render_tx.send(RenderingTask::Resize(w, h))?,
Action::Render => self.render_tx.send(RenderingTask::Render)?,
Expand All @@ -189,54 +181,4 @@ impl App {
}
Ok(())
}

async fn render(&mut self, tui: Tui) -> Result<()> {
let components = self.components.clone();
for component in components.lock().unwrap().iter_mut() {
component.register_action_handler(self.action_tx.clone());
}
for component in components.lock().unwrap().iter_mut() {
component.register_config_handler(self.config.clone());
}
for component in components.lock().unwrap().iter_mut() {
component.init(tui.size().unwrap());
}

// Rendering loop
loop {
if let Some(task) = self.render_rx.recv().await {
match task {
RenderingTask::ClearScreen => {
tui.terminal.clear()?;
}
RenderingTask::Render => {
let mut c = components.lock().unwrap();
tui.terminal.draw(|frame| {
for component in c.iter_mut() {
if let Err(err) = component.draw(frame, frame.area()) {
let _ = self
.action_tx
.send(Action::Error(format!("Failed to draw: {:?}", err)));
}
}
});
}
RenderingTask::Resize(w, h) => {
tui.resize(Rect::new(0, 0, w, h))?;
let _ = self.action_tx.send(Action::Render);
}
RenderingTask::Quit => {
break Ok(());
}
}
}
}
}
}

enum RenderingTask {
ClearScreen,
Render,
Resize(u16, u16),
Quit,
}
Loading

0 comments on commit 18ea6a4

Please sign in to comment.