Radish alpha
r
rad:z39mP9rQAaGmERfUMPULfPUi473tY
Radicle terminal user interface
Radicle
Git
lib: Refactor app return types
Erik Kundt committed 4 months ago
commit 284cb9410ec2e308cdda29bd1a813217410d2f1f
parent 6451dac
10 files changed +163 -212
modified bin/commands/inbox.rs
@@ -14,6 +14,7 @@ use radicle_cli::terminal::{Args, Error, Help};

use self::common::{Mode, RepositoryMode, SelectionMode};

+
use crate::commands::tui_inbox::common::InboxOperation;
use crate::ui::items::notification::filter::{NotificationFilter, SortBy};

pub const HELP: Help = Help {
@@ -226,17 +227,22 @@ pub async fn run(options: Options, ctx: impl terminal::Context) -> anyhow::Resul

                eprint!("{selection}");
            } else if let Some(selection) = selection {
-
                let mut args = vec![];
-

-
                if let Some(operation) = selection.operation {
-
                    args.push(operation.to_string());
-
                }
-
                if let Some(id) = selection.ids.first() {
-
                    args.push(format!("{id}"));
+
                if let Some(operation) = selection.operation.clone() {
+
                    match operation {
+
                        InboxOperation::Show { id } => {
+
                            let _ = crate::terminal::run_rad(
+
                                Some("inbox"),
+
                                &[OsString::from("show"), OsString::from(id.to_string())],
+
                            );
+
                        }
+
                        InboxOperation::Clear { id } => {
+
                            let _ = crate::terminal::run_rad(
+
                                Some("inbox"),
+
                                &[OsString::from("clear"), OsString::from(id.to_string())],
+
                            );
+
                        }
+
                    }
                }
-

-
                let args = args.into_iter().map(OsString::from).collect::<Vec<_>>();
-
                let _ = crate::terminal::run_rad(Some("inbox"), &args);
            }
        }
        Operation::Other { args } => {
modified bin/commands/inbox/common.rs
@@ -1,8 +1,6 @@
-
use std::fmt::Display;
-

use serde::Serialize;

-
use radicle::identity::RepoId;
+
use radicle::{identity::RepoId, node::notifications::NotificationId};

/// The application's subject. It tells the application
/// which widgets to render and which output to produce.
@@ -49,19 +47,6 @@ impl Mode {
/// selection widget.
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub enum InboxOperation {
-
    Show,
-
    Clear,
-
}
-

-
impl Display for InboxOperation {
-
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-
        match self {
-
            InboxOperation::Show => {
-
                write!(f, "show")
-
            }
-
            InboxOperation::Clear => {
-
                write!(f, "clear")
-
            }
-
        }
-
    }
+
    Show { id: NotificationId },
+
    Clear { id: NotificationId },
}
modified bin/commands/inbox/list.rs
@@ -1,5 +1,6 @@
use std::str::FromStr;
use std::sync::{Arc, Mutex};
+
use std::vec;

use anyhow::Result;

@@ -10,7 +11,6 @@ use ratatui::text::Span;
use ratatui::{Frame, Viewport};

use radicle::identity::Project;
-
use radicle::node::notifications::NotificationId;
use radicle::prelude::RepoId;
use radicle::storage::ReadStorage;
use radicle::Profile;
@@ -32,7 +32,7 @@ use crate::ui::items::filter::Filter;
use crate::ui::items::notification::filter::{NotificationFilter, SortBy};
use crate::ui::items::notification::Notification;

-
type Selection = tui::Selection<NotificationId>;
+
type Selection = tui::Selection<InboxOperation>;

const HELP: &str = r#"# Generic keybindings

@@ -194,10 +194,9 @@ impl store::Update<Message> for App {
                None
            }
            Message::Quit => Some(Exit { value: None }),
-
            Message::Exit { operation } => self.selected_notification().map(|issue| Exit {
+
            Message::Exit { operation } => Some(Exit {
                value: Some(Selection {
-
                    operation: operation.map(|op| op.to_string()),
-
                    ids: vec![issue.id],
+
                    operation,
                    args: vec![],
                }),
            }),
@@ -397,19 +396,26 @@ impl App {
            },
        );

-
        if ui.has_input(|key| key == Key::Enter) {
-
            ui.send_message(Message::Exit {
-
                operation: Some(InboxOperation::Show),
-
            });
-
        }
-
        if ui.has_input(|key| key == Key::Char('c')) {
-
            ui.send_message(Message::Exit {
-
                operation: Some(InboxOperation::Clear),
-
            });
-
        }
        if ui.has_input(|key| key == Key::Char('r')) {
            ui.send_message(Message::Reload);
        }
+

+
        if let Some(notification) = selected.and_then(|s| notifs.get(s)) {
+
            if ui.has_input(|key| key == Key::Enter) {
+
                ui.send_message(Message::Exit {
+
                    operation: Some(InboxOperation::Show {
+
                        id: notification.id,
+
                    }),
+
                });
+
            }
+
            if ui.has_input(|key| key == Key::Char('c')) {
+
                ui.send_message(Message::Exit {
+
                    operation: Some(InboxOperation::Clear {
+
                        id: notification.id,
+
                    }),
+
                });
+
            }
+
        }
    }

    fn show_browser_footer(&self, frame: &mut Frame, ui: &mut im::Ui<Message>) {
@@ -639,20 +645,6 @@ impl App {
            Some(Borders::None),
        );
    }
-

-
    pub fn selected_notification(&self) -> Option<Notification> {
-
        let patches = self.notifications.lock().unwrap();
-
        match self.state.patches.selected() {
-
            Some(selected) => patches
-
                .iter()
-
                .filter(|patch| self.state.filter.matches(patch))
-
                .collect::<Vec<_>>()
-
                .get(selected)
-
                .cloned()
-
                .cloned(),
-
            _ => None,
-
        }
-
    }
}

impl App {
modified bin/commands/issue.rs
@@ -16,6 +16,7 @@ use radicle_cli::terminal;
use radicle_cli::terminal::{Args, Error, Help};

use crate::cob;
+
use crate::commands::tui_issue::common::IssueOperation;
use crate::ui::TerminalInfo;

lazy_static! {
@@ -226,16 +227,22 @@ pub async fn run(options: Options, ctx: impl terminal::Context) -> anyhow::Resul

                eprint!("{selection}");
            } else if let Some(selection) = selection {
-
                let args = [
-
                    selection.operation.as_ref().cloned(),
-
                    selection.ids.first().map(ToString::to_string),
-
                ]
-
                .into_iter()
-
                .flatten()
-
                .map(OsString::from)
-
                .collect::<Vec<_>>();
-

-
                let _ = crate::terminal::run_rad(Some("issue"), &args);
+
                if let Some(operation) = selection.operation.clone() {
+
                    match operation {
+
                        IssueOperation::Show { id } => {
+
                            let _ = crate::terminal::run_rad(
+
                                Some("issue"),
+
                                &[OsString::from("show"), OsString::from(id.to_string())],
+
                            );
+
                        }
+
                        IssueOperation::Edit { id } => {
+
                            let _ = crate::terminal::run_rad(
+
                                Some("issue"),
+
                                &[OsString::from("edit"), OsString::from(id.to_string())],
+
                            );
+
                        }
+
                    }
+
                }
            }
        }
        Operation::Other { args } => {
modified bin/commands/issue/common.rs
@@ -1,7 +1,7 @@
-
use std::fmt::Display;
-

use serde::Serialize;

+
use radicle::issue::IssueId;
+

/// The application's mode. It tells the application
/// which widgets to render and which output to produce.
/// Depends on CLI arguments given by the user.
@@ -16,19 +16,6 @@ pub enum Mode {
/// selection widget.
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub enum IssueOperation {
-
    Edit,
-
    Show,
-
}
-

-
impl Display for IssueOperation {
-
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-
        match self {
-
            IssueOperation::Edit => {
-
                write!(f, "edit")
-
            }
-
            IssueOperation::Show => {
-
                write!(f, "show")
-
            }
-
        }
-
    }
+
    Edit { id: IssueId },
+
    Show { id: IssueId },
}
modified bin/commands/issue/list.rs
@@ -47,7 +47,7 @@ use self::ui::{Browser, BrowserProps};

use super::common::{IssueOperation, Mode};

-
type Selection = tui::Selection<IssueId>;
+
type Selection = tui::Selection<IssueOperation>;

pub struct Context {
    pub profile: Profile,
@@ -230,22 +230,41 @@ impl TryFrom<(&Context, &TerminalInfo)> for State {
}

#[derive(Clone, Debug)]
+
pub enum RequestedIssueOperation {
+
    Edit,
+
    Show,
+
}
+

+
#[derive(Clone, Debug)]
pub enum Message {
    Quit,
-
    Exit { operation: Option<IssueOperation> },
-
    ExitFromMode,
-
    SelectIssue { selected: Option<usize> },
+
    Exit {
+
        operation: Option<RequestedIssueOperation>,
+
    },
+
    SelectIssue {
+
        selected: Option<usize>,
+
    },
    OpenSearch,
-
    UpdateSearch { value: String },
+
    UpdateSearch {
+
        value: String,
+
    },
    ApplySearch,
    CloseSearch,
    TogglePreview,
-
    FocusSection { section: Option<Section> },
-
    SelectComment { selected: Option<Vec<CommentId>> },
-
    ScrollComment { state: TextViewState },
+
    FocusSection {
+
        section: Option<Section>,
+
    },
+
    SelectComment {
+
        selected: Option<Vec<CommentId>>,
+
    },
+
    ScrollComment {
+
        state: TextViewState,
+
    },
    OpenHelp,
    LeavePage,
-
    ScrollHelp { state: TextViewState },
+
    ScrollHelp {
+
        state: TextViewState,
+
    },
}

impl store::Update<Message> for State {
@@ -254,23 +273,20 @@ impl store::Update<Message> for State {
    fn update(&mut self, message: Message) -> Option<Exit<Selection>> {
        match message {
            Message::Quit => Some(Exit { value: None }),
-
            Message::Exit { operation } => self.browser.selected_item().map(|issue| Exit {
-
                value: Some(Selection {
-
                    operation: operation.map(|op| op.to_string()),
-
                    ids: vec![issue.id],
-
                    args: vec![],
-
                }),
-
            }),
-
            Message::ExitFromMode => {
-
                let operation = match self.mode {
-
                    Mode::Operation => Some(IssueOperation::Show.to_string()),
-
                    Mode::Id => None,
+
            Message::Exit { operation } => {
+
                let selected = self.browser.selected_item();
+
                let operation = match operation {
+
                    Some(RequestedIssueOperation::Show) => {
+
                        selected.map(|issue| IssueOperation::Show { id: issue.id })
+
                    }
+
                    Some(RequestedIssueOperation::Edit) => {
+
                        selected.map(|issue| IssueOperation::Edit { id: issue.id })
+
                    }
+
                    _ => None,
                };
-

-
                self.browser.selected_item().map(|issue| Exit {
+
                Some(Exit {
                    value: Some(Selection {
                        operation,
-
                        ids: vec![issue.id],
                        args: vec![],
                    }),
                })
@@ -437,16 +453,17 @@ fn browser_page(channel: &Channel<Message>) -> Widget<State, Message> {
            let props = props
                .and_then(|props| props.inner_ref::<PageProps>())
                .unwrap_or(&default);
-

            if props.handle_keys {
                if let Event::Key(key) = event {
                    match key {
                        Key::Char('q') | Key::Ctrl('c') => Some(Message::Quit),
                        Key::Char('p') => Some(Message::TogglePreview),
                        Key::Char('?') => Some(Message::OpenHelp),
-
                        Key::Enter => Some(Message::ExitFromMode),
+
                        Key::Enter => Some(Message::Exit {
+
                            operation: Some(RequestedIssueOperation::Show),
+
                        }),
                        Key::Char('e') => Some(Message::Exit {
-
                            operation: Some(IssueOperation::Edit),
+
                            operation: Some(RequestedIssueOperation::Edit),
                        }),
                        _ => None,
                    }
modified bin/commands/patch.rs
@@ -20,6 +20,7 @@ use radicle_cli::terminal::args::{string, Args, Error, Help};

use crate::cob::patch;
use crate::cob::patch::Filter;
+
use crate::commands::tui_patch::common::PatchOperation;

pub const HELP: Help = Help {
    name: "patch",
@@ -284,20 +285,28 @@ pub async fn run(options: Options, ctx: impl terminal::Context) -> anyhow::Resul
                eprint!("{selection}");
            } else if let Some(selection) = selection {
                if let Some(operation) = selection.operation.clone() {
-
                    let mut args = vec![operation.to_string()];
-

-
                    if let Some(id) = selection.ids.first() {
-
                        args.push(id.to_string());
-

-
                        match operation.as_str() {
-
                            "review" => {
-
                                let opts = ReviewOptions::default();
-
                                interface::review(opts, profile, rid, *id).await?;
-
                            }
-
                            _ => {
-
                                let args = args.into_iter().map(OsString::from).collect::<Vec<_>>();
-
                                let _ = crate::terminal::run_rad(Some("patch"), &args);
-
                            }
+
                    match operation {
+
                        PatchOperation::Show { id } => {
+
                            let _ = crate::terminal::run_rad(
+
                                Some("patch"),
+
                                &[OsString::from("show"), OsString::from(id.to_string())],
+
                            );
+
                        }
+
                        PatchOperation::Diff { id } => {
+
                            let _ = crate::terminal::run_rad(
+
                                Some("patch"),
+
                                &[OsString::from("diff"), OsString::from(id.to_string())],
+
                            );
+
                        }
+
                        PatchOperation::Checkout { id } => {
+
                            let _ = crate::terminal::run_rad(
+
                                Some("patch"),
+
                                &[OsString::from("checkout"), OsString::from(id.to_string())],
+
                            );
+
                        }
+
                        PatchOperation::_Review { id } => {
+
                            let opts = ReviewOptions::default();
+
                            interface::review(opts, profile, rid, id).await?;
                        }
                    }
                }
@@ -334,7 +343,6 @@ mod interface {
    use anyhow::anyhow;

    use radicle::cob;
-
    use radicle::cob::ObjectId;
    use radicle::identity::RepoId;
    use radicle::patch::PatchId;
    use radicle::patch::Verdict;
@@ -347,6 +355,7 @@ mod interface {
    use radicle_tui::Selection;

    use crate::cob::patch;
+
    use crate::commands::tui_patch::common::PatchOperation;
    use crate::tui_patch::list;
    use crate::tui_patch::review::builder::CommentBuilder;
    use crate::tui_patch::review::ReviewAction;
@@ -360,7 +369,7 @@ mod interface {
        opts: ListOptions,
        profile: Profile,
        rid: RepoId,
-
    ) -> anyhow::Result<Option<Selection<ObjectId>>> {
+
    ) -> anyhow::Result<Option<Selection<PatchOperation>>> {
        let repository = profile.storage.repository(rid).unwrap();

        log::info!("Starting patch selection interface in project {rid}..");
modified bin/commands/patch/common.rs
@@ -1,5 +1,4 @@
-
use std::fmt::Display;
-

+
use radicle::patch::PatchId;
use serde::Serialize;

/// The application's mode. It tells the application
@@ -17,23 +16,8 @@ pub enum Mode {
/// selection widget.
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub enum PatchOperation {
-
    Checkout,
-
    Diff,
-
    Show,
-
}
-

-
impl Display for PatchOperation {
-
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-
        match self {
-
            PatchOperation::Checkout => {
-
                write!(f, "checkout")
-
            }
-
            PatchOperation::Diff => {
-
                write!(f, "diff")
-
            }
-
            PatchOperation::Show => {
-
                write!(f, "show")
-
            }
-
        }
-
    }
+
    Checkout { id: PatchId },
+
    Diff { id: PatchId },
+
    Show { id: PatchId },
+
    _Review { id: PatchId },
}
modified bin/commands/patch/list.rs
@@ -3,7 +3,6 @@ use std::sync::{Arc, Mutex};

use anyhow::Result;

-
use radicle::patch::PatchId;
use radicle::storage::git::Repository;
use radicle::Profile;

@@ -24,8 +23,6 @@ use tui::ui::im::Show;
use tui::ui::{BufferedValue, Column, Spacing};
use tui::{Channel, Exit};

-
type Selection = tui::Selection<PatchId>;
-

use super::common::{Mode, PatchOperation};

use crate::cob::patch;
@@ -57,6 +54,8 @@ const HELP: &str = r#"# Generic keybindings
Pattern:    is:<state> | is:authored | authors:[<did>, <did>] | <search>
Example:    is:open is:authored improve"#;

+
type Selection = tui::Selection<PatchOperation>;
+

pub struct Context {
    pub profile: Profile,
    pub repository: Repository,
@@ -107,7 +106,6 @@ pub enum Message {
    ShowSearch,
    HideSearch { apply: bool },
    Exit { operation: Option<PatchOperation> },
-
    ExitFromMode,
    Quit,
}

@@ -180,27 +178,12 @@ impl store::Update<Message> for App {
    fn update(&mut self, message: Message) -> Option<tui::Exit<Selection>> {
        match message {
            Message::Quit => Some(Exit { value: None }),
-
            Message::Exit { operation } => self.selected_patch().map(|issue| Exit {
+
            Message::Exit { operation } => Some(Exit {
                value: Some(Selection {
-
                    operation: operation.map(|op| op.to_string()),
-
                    ids: vec![issue.id],
+
                    operation,
                    args: vec![],
                }),
            }),
-
            Message::ExitFromMode => {
-
                let operation = match self.state.mode {
-
                    Mode::Operation => Some(PatchOperation::Show.to_string()),
-
                    Mode::Id => None,
-
                };
-

-
                self.selected_patch().map(|issue| Exit {
-
                    value: Some(Selection {
-
                        operation,
-
                        ids: vec![issue.id],
-
                        args: vec![],
-
                    }),
-
                })
-
            }
            Message::ShowSearch => {
                self.state.main_group = ContainerState::new(3, None);
                self.state.show_search = true;
@@ -375,18 +358,23 @@ impl App {
        if ui.has_input(|key| key == Key::Char('/')) {
            ui.send_message(Message::ShowSearch);
        }
-
        if ui.has_input(|key| key == Key::Enter) {
-
            ui.send_message(Message::ExitFromMode);
-
        }
-
        if ui.has_input(|key| key == Key::Char('d')) {
-
            ui.send_message(Message::Exit {
-
                operation: Some(PatchOperation::Diff),
-
            });
-
        }
-
        if ui.has_input(|key| key == Key::Char('c')) {
-
            ui.send_message(Message::Exit {
-
                operation: Some(PatchOperation::Checkout),
-
            });
+

+
        if let Some(patch) = selected.and_then(|s| patches.get(s)) {
+
            if ui.has_input(|key| key == Key::Enter) {
+
                ui.send_message(Message::Exit {
+
                    operation: Some(PatchOperation::Show { id: patch.id }),
+
                });
+
            }
+
            if ui.has_input(|key| key == Key::Char('d')) {
+
                ui.send_message(Message::Exit {
+
                    operation: Some(PatchOperation::Diff { id: patch.id }),
+
                });
+
            }
+
            if ui.has_input(|key| key == Key::Char('c')) {
+
                ui.send_message(Message::Exit {
+
                    operation: Some(PatchOperation::Checkout { id: patch.id }),
+
                });
+
            }
        }
    }

@@ -632,18 +620,4 @@ impl App {
            Some(Borders::None),
        );
    }
-

-
    pub fn selected_patch(&self) -> Option<PatchItem> {
-
        let patches = self.patches.lock().unwrap();
-
        match self.state.patches.selected() {
-
            Some(selected) => patches
-
                .iter()
-
                .filter(|patch| self.state.filter.matches(patch))
-
                .collect::<Vec<_>>()
-
                .get(selected)
-
                .cloned()
-
                .cloned(),
-
            _ => None,
-
        }
-
    }
}
modified src/lib.rs
@@ -34,38 +34,32 @@ pub struct Exit<T> {

/// The output that is returned by all selection interfaces.
#[derive(Clone, Default, Debug, Eq, PartialEq)]
-
pub struct Selection<I>
+
pub struct Selection<O>
where
-
    I: ToString,
+
    O: Serialize,
{
-
    pub operation: Option<String>,
-
    pub ids: Vec<I>,
+
    pub operation: Option<O>,
    pub args: Vec<String>,
}

-
impl<I> Selection<I>
+
impl<O> Selection<O>
where
-
    I: ToString,
+
    O: Serialize,
{
-
    pub fn with_operation(mut self, operation: String) -> Self {
+
    pub fn with_operation(mut self, operation: O) -> Self {
        self.operation = Some(operation);
        self
    }

-
    pub fn with_id(mut self, id: I) -> Self {
-
        self.ids.push(id);
-
        self
-
    }
-

    pub fn with_args(mut self, arg: String) -> Self {
        self.args.push(arg);
        self
    }
}

-
impl<I> Serialize for Selection<I>
+
impl<O> Serialize for Selection<O>
where
-
    I: ToString,
+
    O: Serialize,
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
@@ -73,10 +67,6 @@ where
    {
        let mut state = serializer.serialize_struct("", 3)?;
        state.serialize_field("operation", &self.operation)?;
-
        state.serialize_field(
-
            "ids",
-
            &self.ids.iter().map(|id| id.to_string()).collect::<Vec<_>>(),
-
        )?;
        state.serialize_field("args", &self.args)?;
        state.end()
    }