Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
lib: Restructure terminal and task modules
Erik Kundt committed 2 years ago
commit c8e4c752f1c0ee319d47aaa8aee05e761123ce28
parent 8d9962c8ffad74d5b68ae07923f13f5efe7b5cc2
9 files changed +138 -127
modified bin/commands/inbox/flux/select.rs
@@ -14,7 +14,7 @@ use radicle_tui as tui;

use tui::common::cob::inbox::{self};
use tui::flux::store::{State, Store};
-
use tui::flux::termination::{self, Interrupted};
+
use tui::flux::task::{self, Interrupted};
use tui::flux::ui::cob::NotificationItem;
use tui::flux::ui::Frontend;
use tui::Exit;
@@ -186,7 +186,7 @@ impl App {
    }

    pub async fn run(&self) -> Result<Option<Selection>> {
-
        let (terminator, mut interrupt_rx) = termination::create_termination();
+
        let (terminator, mut interrupt_rx) = task::create_termination();
        let (store, state_rx) = Store::<Action, InboxState, Selection>::new();
        let (frontend, action_rx) = Frontend::<Action>::new();
        let state = InboxState::try_from(&self.context)?;
modified bin/commands/issue/flux/select.rs
@@ -11,7 +11,7 @@ use radicle_tui as tui;

use tui::common::cob::issue::{self, Filter};
use tui::flux::store::{State, Store};
-
use tui::flux::termination::{self, Interrupted};
+
use tui::flux::task::{self, Interrupted};
use tui::flux::ui::cob::IssueItem;
use tui::flux::ui::Frontend;
use tui::Exit;
@@ -115,7 +115,7 @@ impl App {
    }

    pub async fn run(&self) -> Result<Option<Selection>> {
-
        let (terminator, mut interrupt_rx) = termination::create_termination();
+
        let (terminator, mut interrupt_rx) = task::create_termination();
        let (store, state_rx) = Store::<Action, IssuesState, Selection>::new();
        let (frontend, action_rx) = Frontend::<Action>::new();
        let state = IssuesState::try_from(&self.context)?;
modified bin/commands/patch/flux/select.rs
@@ -11,7 +11,7 @@ use radicle_tui as tui;

use tui::common::cob::patch::{self, Filter};
use tui::flux::store::{State, Store};
-
use tui::flux::termination::{self, Interrupted};
+
use tui::flux::task::{self, Interrupted};
use tui::flux::ui::cob::PatchItem;
use tui::flux::ui::Frontend;
use tui::Exit;
@@ -116,7 +116,7 @@ impl App {
    }

    pub async fn run(&self) -> Result<Option<Selection>> {
-
        let (terminator, mut interrupt_rx) = termination::create_termination();
+
        let (terminator, mut interrupt_rx) = task::create_termination();
        let (store, state_rx) = Store::<Action, PatchesState, Selection>::new();
        let (frontend, action_rx) = Frontend::<Action>::new();
        let state = PatchesState::try_from(&self.context)?;
modified src/flux.rs
@@ -1,4 +1,5 @@
pub mod event;
pub mod store;
-
pub mod termination;
+
pub mod task;
+
pub mod terminal;
pub mod ui;
modified src/flux/store.rs
@@ -7,7 +7,7 @@ use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};

use crate::Exit;

-
use super::termination::{Interrupted, Terminator};
+
use super::task::{Interrupted, Terminator};

const STORE_TICK_RATE: Duration = Duration::from_millis(1000);

added src/flux/task.rs
@@ -0,0 +1,66 @@
+
use std::fmt::Debug;
+

+
#[cfg(unix)]
+
use tokio::signal::unix::signal;
+
use tokio::sync::broadcast;
+

+
#[derive(Debug, Clone)]
+
pub enum Interrupted<P>
+
where
+
    P: Clone + Send + Sync + Debug,
+
{
+
    OsSignal,
+
    User { payload: Option<P> },
+
}
+

+
#[derive(Debug, Clone)]
+
pub struct Terminator<P>
+
where
+
    P: Clone + Send + Sync + Debug,
+
{
+
    interrupt_tx: broadcast::Sender<Interrupted<P>>,
+
}
+

+
impl<P> Terminator<P>
+
where
+
    P: Clone + Send + Sync + Debug + 'static,
+
{
+
    pub fn new(interrupt_tx: broadcast::Sender<Interrupted<P>>) -> Self {
+
        Self { interrupt_tx }
+
    }
+

+
    pub fn terminate(&mut self, interrupted: Interrupted<P>) -> anyhow::Result<()> {
+
        self.interrupt_tx.send(interrupted)?;
+

+
        Ok(())
+
    }
+
}
+

+
#[cfg(unix)]
+
async fn terminate_by_unix_signal<P>(mut terminator: Terminator<P>)
+
where
+
    P: Clone + Send + Sync + Debug + 'static,
+
{
+
    let mut interrupt_signal = signal(tokio::signal::unix::SignalKind::interrupt())
+
        .expect("failed to create interrupt signal stream");
+

+
    interrupt_signal.recv().await;
+

+
    terminator
+
        .terminate(Interrupted::OsSignal)
+
        .expect("failed to send interrupt signal");
+
}
+

+
// create a broadcast channel for retrieving the application kill signal
+
pub fn create_termination<P>() -> (Terminator<P>, broadcast::Receiver<Interrupted<P>>)
+
where
+
    P: Clone + Send + Sync + Debug + 'static,
+
{
+
    let (tx, rx) = broadcast::channel(1);
+
    let terminator = Terminator::new(tx);
+

+
    #[cfg(unix)]
+
    tokio::spawn(terminate_by_unix_signal(terminator.clone()));
+

+
    (terminator, rx)
+
}
added src/flux/terminal.rs
@@ -0,0 +1,55 @@
+
use std::io::{self, Write};
+
use std::thread;
+

+
use termion::input::TermRead;
+
use termion::raw::{IntoRawMode, RawTerminal};
+

+
use ratatui::prelude::*;
+

+
use tokio::sync::mpsc::{self};
+

+
use super::event::Event;
+

+
type Backend = TermionBackendExt<RawTerminal<io::Stdout>>;
+

+
pub fn setup(height: usize) -> anyhow::Result<Terminal<Backend>> {
+
    let stdout = io::stdout().into_raw_mode()?;
+
    let options = TerminalOptions {
+
        viewport: Viewport::Inline(height as u16),
+
    };
+

+
    Ok(Terminal::with_options(
+
        TermionBackendExt::new(stdout),
+
        options,
+
    )?)
+
}
+

+
pub fn restore(terminal: &mut Terminal<Backend>) -> anyhow::Result<()> {
+
    terminal.clear()?;
+
    Ok(())
+
}
+

+
pub fn events() -> mpsc::UnboundedReceiver<Event> {
+
    let (tx, rx) = mpsc::unbounded_channel();
+
    let events_tx = tx.clone();
+
    thread::spawn(move || {
+
        let stdin = io::stdin();
+
        for key in stdin.keys().flatten() {
+
            if events_tx.send(Event::Key(key)).is_err() {
+
                return;
+
            }
+
        }
+
    });
+

+
    let events_tx = tx.clone();
+
    if let Ok(mut signals) = signal_hook::iterator::Signals::new([libc::SIGWINCH]) {
+
        thread::spawn(move || {
+
            for _ in signals.forever() {
+
                if events_tx.send(Event::Resize).is_err() {
+
                    return;
+
                }
+
            }
+
        });
+
    }
+
    rx
+
}
deleted src/flux/termination.rs
@@ -1,66 +0,0 @@
-
use std::fmt::Debug;
-

-
#[cfg(unix)]
-
use tokio::signal::unix::signal;
-
use tokio::sync::broadcast;
-

-
#[derive(Debug, Clone)]
-
pub enum Interrupted<P>
-
where
-
    P: Clone + Send + Sync + Debug,
-
{
-
    OsSignal,
-
    User { payload: Option<P> },
-
}
-

-
#[derive(Debug, Clone)]
-
pub struct Terminator<P>
-
where
-
    P: Clone + Send + Sync + Debug,
-
{
-
    interrupt_tx: broadcast::Sender<Interrupted<P>>,
-
}
-

-
impl<P> Terminator<P>
-
where
-
    P: Clone + Send + Sync + Debug + 'static,
-
{
-
    pub fn new(interrupt_tx: broadcast::Sender<Interrupted<P>>) -> Self {
-
        Self { interrupt_tx }
-
    }
-

-
    pub fn terminate(&mut self, interrupted: Interrupted<P>) -> anyhow::Result<()> {
-
        self.interrupt_tx.send(interrupted)?;
-

-
        Ok(())
-
    }
-
}
-

-
#[cfg(unix)]
-
async fn terminate_by_unix_signal<P>(mut terminator: Terminator<P>)
-
where
-
    P: Clone + Send + Sync + Debug + 'static,
-
{
-
    let mut interrupt_signal = signal(tokio::signal::unix::SignalKind::interrupt())
-
        .expect("failed to create interrupt signal stream");
-

-
    interrupt_signal.recv().await;
-

-
    terminator
-
        .terminate(Interrupted::OsSignal)
-
        .expect("failed to send interrupt signal");
-
}
-

-
// create a broadcast channel for retrieving the application kill signal
-
pub fn create_termination<P>() -> (Terminator<P>, broadcast::Receiver<Interrupted<P>>)
-
where
-
    P: Clone + Send + Sync + Debug + 'static,
-
{
-
    let (tx, rx) = broadcast::channel(1);
-
    let terminator = Terminator::new(tx);
-

-
    #[cfg(unix)]
-
    tokio::spawn(terminate_by_unix_signal(terminator.clone()));
-

-
    (terminator, rx)
-
}
modified src/flux/ui.rs
@@ -8,27 +8,23 @@ pub mod widget;

use std::fmt::Debug;
use std::io::{self};
-
use std::thread;
use std::time::Duration;

-
// use termion::event::Event;
-
use termion::input::TermRead;
-
use termion::raw::{IntoRawMode, RawTerminal};
-

-
use ratatui::prelude::*;
+
use termion::raw::RawTerminal;

use tokio::sync::broadcast;
use tokio::sync::mpsc::{self, UnboundedReceiver};

use super::event::Event;
use super::store::State;
-
use super::termination::Interrupted;
+
use super::task::Interrupted;
+
use super::terminal;
use super::ui::widget::{Render, Widget};

type Backend = TermionBackend<RawTerminal<io::Stdout>>;

const RENDERING_TICK_RATE: Duration = Duration::from_millis(250);
-
const INLINE_HEIGHT: u16 = 20;
+
const INLINE_HEIGHT: usize = 20;

pub struct Frontend<A> {
    action_tx: mpsc::UnboundedSender<A>,
@@ -51,9 +47,10 @@ impl<A> Frontend<A> {
        W: Widget<S, A> + Render<()>,
        P: Clone + Send + Sync + Debug,
    {
-
        let mut terminal = setup_terminal()?;
        let mut ticker = tokio::time::interval(RENDERING_TICK_RATE);
-
        let mut events_rx = events();
+

+
        let mut terminal = terminal::setup(INLINE_HEIGHT)?;
+
        let mut events_rx = terminal::events();

        let mut root = {
            let state = state_rx.recv().await.unwrap();
@@ -84,50 +81,8 @@ impl<A> Frontend<A> {
            terminal.draw(|frame| root.render::<Backend>(frame, frame.size(), ()))?;
        };

-
        restore_terminal(&mut terminal)?;
+
        terminal::restore(&mut terminal)?;

        result
    }
}
-

-
fn setup_terminal() -> anyhow::Result<Terminal<Backend>> {
-
    let stdout = io::stdout().into_raw_mode()?;
-
    let options = TerminalOptions {
-
        viewport: Viewport::Inline(INLINE_HEIGHT),
-
    };
-

-
    Ok(Terminal::with_options(
-
        TermionBackend::new(stdout),
-
        options,
-
    )?)
-
}
-

-
fn restore_terminal(terminal: &mut Terminal<Backend>) -> anyhow::Result<()> {
-
    terminal.clear()?;
-
    Ok(())
-
}
-

-
fn events() -> mpsc::UnboundedReceiver<Event> {
-
    let (tx, rx) = mpsc::unbounded_channel();
-
    let events_tx = tx.clone();
-
    thread::spawn(move || {
-
        let stdin = io::stdin();
-
        for key in stdin.keys().flatten() {
-
            if events_tx.send(Event::Key(key)).is_err() {
-
                return;
-
            }
-
        }
-
    });
-

-
    let events_tx = tx.clone();
-
    if let Ok(mut signals) = signal_hook::iterator::Signals::new([libc::SIGWINCH]) {
-
        thread::spawn(move || {
-
            for _ in signals.forever() {
-
                if events_tx.send(Event::Resize).is_err() {
-
                    return;
-
                }
-
            }
-
        });
-
    }
-
    rx
-
}