diff --git a/bin/commands.rs b/bin/commands.rs index b277dbf5..03da9c3d 100644 --- a/bin/commands.rs +++ b/bin/commands.rs @@ -1,4 +1,6 @@ #[path = "commands/help.rs"] pub mod tui_help; +#[path = "commands/issue.rs"] +pub mod tui_issue; #[path = "commands/patch.rs"] pub mod tui_patch; diff --git a/bin/commands/issue.rs b/bin/commands/issue.rs index 96291058..de066baa 100644 --- a/bin/commands/issue.rs +++ b/bin/commands/issue.rs @@ -1,397 +1,67 @@ -pub mod event; -pub mod page; -pub mod subscription; +#[path = "issue/suite.rs"] +mod suite; -use anyhow::Result; +use std::ffi::OsString; -use radicle::cob::issue::IssueId; -use radicle::cob::patch::PatchId; -use radicle::identity::{Id, Project}; -use radicle::prelude::Signer; -use radicle::profile::Profile; +use anyhow::anyhow; -use radicle_tui::ui::widget; -use tuirealm::application::PollStrategy; -use tuirealm::{Application, Frame, NoUserEvent, Sub, SubClause}; +use crate::terminal; +use crate::terminal::args::{Args, Error, Help}; -use radicle_tui::ui::context::Context; -use radicle_tui::ui::theme::{self, Theme}; -use radicle_tui::Tui; -use radicle_tui::{cob, ui}; +pub const HELP: Help = Help { + name: "issue", + description: "Terminal interfaces for patches", + version: env!("CARGO_PKG_VERSION"), + usage: r#" +Usage -use page::{HomeView, PatchView}; + rad-tui patch -use self::page::{IssuePage, PageStack}; +General options -#[derive(Debug, Eq, PartialEq, Clone, Hash)] -pub enum ListCid { - Header, - Dashboard, - IssueBrowser, - PatchBrowser, - Context, - Shortcuts, -} - -#[derive(Debug, Eq, PartialEq, Clone, Hash)] -pub enum PatchCid { - Header, - Activity, - Files, - Context, - Shortcuts, -} - -#[derive(Debug, Eq, PartialEq, Clone, Hash)] -pub enum IssueCid { - Header, - List, - Details, - Context, - Form, - Shortcuts, -} - -/// All component ids known to this application. -#[derive(Debug, Eq, PartialEq, Clone, Hash)] -pub enum Cid { - Home(HomeCid), - Issue(IssueCid), - Patch(PatchCid), - GlobalListener, - Popup, -} - -/// Messages handled by this application. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum HomeMessage { - RefreshIssues(Option), -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum IssueCobMessage { - Create { - title: String, - tags: String, - assignees: String, - description: String, - }, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum IssueMessage { - Show(Option), - Changed(IssueId), - Focus(IssueCid), - Created(IssueId), - Cob(IssueCobMessage), - OpenForm, - HideForm, - Leave(Option), -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum PatchMessage { - Show(PatchId), - Leave, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum PopupMessage { - Info(String), - Warning(String), - Error(String), - Hide, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Message { - Home(HomeMessage), - Issue(IssueMessage), - Patch(PatchMessage), - NavigationChanged(u16), - FormSubmitted(String), - Popup(PopupMessage), - Tick, - Quit, - Batch(Vec), -} + --help Print help +"#, +}; #[allow(dead_code)] -pub struct App { - context: Context, - pages: PageStack, - theme: Theme, - quit: bool, +pub struct Options { + op: Operation, } -/// Creates a new application using a tui-realm-application, mounts all -/// components and sets focus to a default one. -impl App { - pub fn new(profile: Profile, id: Id, project: Project, signer: Box) -> Self { - Self { - context: Context::new(profile, id, project, signer), - pages: PageStack::default(), - theme: theme::default_dark(), - quit: false, - } - } +pub enum Operation { + Suite, +} - fn view_home( - &mut self, - app: &mut Application, - theme: &Theme, - ) -> Result<()> { - let home = Box::new(HomeView::new(theme.clone())); - self.pages.push(home, app, &self.context, theme)?; +#[derive(Default, PartialEq, Eq)] +pub enum OperationName { + #[default] + Suite, +} - Ok(()) - } +impl Args for Options { + fn from_args(args: Vec) -> anyhow::Result<(Self, Vec)> { + use lexopt::prelude::*; - fn view_patch( - &mut self, - app: &mut Application, - id: PatchId, - theme: &Theme, - ) -> Result<()> { - let repo = self.context.repository(); + let mut parser = lexopt::Parser::from_args(args); + let op: Option = None; - if let Some(patch) = cob::patch::find(repo, &id)? { - let view = Box::new(PatchView::new(theme.clone(), (id, patch))); - self.pages.push(view, app, &self.context, theme)?; - - Ok(()) - } else { - Err(anyhow::anyhow!( - "Could not mount 'page::PatchView'. Patch not found." - )) - } - } - - fn view_issue( - &mut self, - app: &mut Application, - id: Option, - theme: &Theme, - ) -> Result<()> { - let repo = self.context.repository(); - match id { - Some(id) => { - if let Some(issue) = cob::issue::find(repo, &id)? { - let view = Box::new(IssuePage::new(&self.context, theme, Some((id, issue)))); - self.pages.push(view, app, &self.context, theme)?; - - Ok(()) - } else { - Err(anyhow::anyhow!( - "Could not mount 'page::IssueView'. Issue not found." - )) + #[allow(clippy::never_loop)] + while let Some(arg) = parser.next()? { + match arg { + Long("help") | Short('h') => { + return Err(Error::Help.into()); } - } - None => { - let view = Box::new(IssuePage::new(&self.context, theme, None)); - self.pages.push(view, app, &self.context, theme)?; - - Ok(()) + _ => return Err(anyhow!(arg.unexpected())), } } - } - fn process( - &mut self, - app: &mut Application, - message: Message, - ) -> Result> { - let theme = theme::default_dark(); - match message { - Message::Batch(messages) => { - let mut results = vec![]; - for message in messages { - if let Some(result) = self.process(app, message)? { - results.push(result); - } - } - match results.len() { - 0 => Ok(None), - 1 => Ok(Some(results[0].to_owned())), - _ => Ok(Some(Message::Batch(results))), - } - } - Message::Issue(IssueMessage::Cob(IssueCobMessage::Create { - title, - tags, - assignees, - description, - })) => match self.create_issue(title, description, tags, assignees) { - Ok(id) => { - self.context.reload(); - - Ok(Some(Message::Batch(vec![ - Message::Issue(IssueMessage::HideForm), - Message::Issue(IssueMessage::Created(id)), - ]))) - } - Err(err) => { - let error = format!("{:?}", err); - self.show_error_popup(app, &theme, &error)?; - - Ok(None) - } - }, - Message::Issue(IssueMessage::Show(id)) => { - self.view_issue(app, id, &theme)?; - Ok(None) - } - Message::Issue(IssueMessage::Leave(id)) => { - self.pages.pop(app)?; - Ok(Some(Message::Home(HomeMessage::RefreshIssues(id)))) - } - Message::Patch(PatchMessage::Show(id)) => { - self.view_patch(app, id, &theme)?; - Ok(None) - } - Message::Patch(PatchMessage::Leave) => { - self.pages.pop(app)?; - Ok(None) - } - Message::Popup(PopupMessage::Info(info)) => { - self.show_info_popup(app, &theme, &info)?; - Ok(None) - } - Message::Popup(PopupMessage::Warning(warning)) => { - self.show_warning_popup(app, &theme, &warning)?; - Ok(None) - } - Message::Popup(PopupMessage::Error(error)) => { - self.show_error_popup(app, &theme, &error)?; - Ok(None) - } - Message::Popup(PopupMessage::Hide) => { - self.hide_popup(app)?; - Ok(None) - } - Message::Quit => { - self.quit = true; - Ok(None) - } - _ => self - .pages - .peek_mut()? - .update(app, &self.context, &theme, message), - } - } - - fn show_info_popup( - &mut self, - app: &mut Application, - theme: &Theme, - message: &str, - ) -> Result<()> { - let popup = widget::common::info(theme, message); - app.remount(Cid::Popup, popup.to_boxed(), vec![])?; - app.active(&Cid::Popup)?; - - Ok(()) - } - - fn show_warning_popup( - &mut self, - app: &mut Application, - theme: &Theme, - message: &str, - ) -> Result<()> { - let popup = widget::common::warning(theme, message); - app.remount(Cid::Popup, popup.to_boxed(), vec![])?; - app.active(&Cid::Popup)?; - - Ok(()) - } - - fn show_error_popup( - &mut self, - app: &mut Application, - theme: &Theme, - message: &str, - ) -> Result<()> { - let popup = widget::common::error(theme, message); - app.remount(Cid::Popup, popup.to_boxed(), vec![])?; - app.active(&Cid::Popup)?; - - Ok(()) - } - - fn hide_popup(&mut self, app: &mut Application) -> Result<()> { - app.blur()?; - app.umount(&Cid::Popup)?; - - Ok(()) - } - - fn create_issue( - &mut self, - title: String, - description: String, - labels: String, - assignees: String, - ) -> Result { - let repository = self.context.repository(); - let signer = self.context.signer(); - - let labels = cob::parse_labels(labels)?; - let assignees = cob::parse_assignees(assignees)?; - - cob::issue::create( - repository, - signer, - title, - description, - labels.as_slice(), - assignees.as_slice(), - ) + let op = match op.unwrap_or_default() { + OperationName::Suite => Operation::Suite, + }; + Ok((Options { op }, vec![])) } } -impl Tui for App { - fn init(&mut self, app: &mut Application) -> Result<()> { - self.view_home(app, &self.theme.clone())?; - - // Add global key listener and subscribe to key events - let global = ui::widget::common::global_listener().to_boxed(); - app.mount( - Cid::GlobalListener, - global, - vec![Sub::new(subscription::global_clause(), SubClause::Always)], - )?; - - Ok(()) - } - - fn view(&mut self, app: &mut Application, frame: &mut Frame) { - if let Ok(page) = self.pages.peek_mut() { - page.view(app, frame); - } - - if app.mounted(&Cid::Popup) { - app.view(&Cid::Popup, frame, frame.size()); - } - } - - fn update(&mut self, app: &mut Application) -> Result { - match app.tick(PollStrategy::Once) { - Ok(messages) if !messages.is_empty() => { - for message in messages { - let mut msg = Some(message); - while msg.is_some() { - msg = self.process(app, msg.unwrap())?; - } - } - Ok(true) - } - _ => Ok(false), - } - } - - fn quit(&self) -> bool { - self.quit - } +pub fn run(_options: Options, _ctx: impl terminal::Context) -> anyhow::Result<()> { + Ok(()) } diff --git a/bin/commands/issue/main.rs b/bin/commands/issue/main.rs deleted file mode 100644 index 9ecc1acb..00000000 --- a/bin/commands/issue/main.rs +++ /dev/null @@ -1,87 +0,0 @@ -mod app; - -use std::process; - -use anyhow::anyhow; - -use radicle::profile; - -use log::info; -use log::LevelFilter; - -use radicle_term as term; -use radicle_tui as tui; - -use tui::context; -use tui::Window; - -pub const NAME: &str = "rad-issue-tui"; -pub const VERSION: &str = env!("CARGO_PKG_VERSION"); -pub const GIT_HEAD: &str = env!("GIT_HEAD"); -pub const FPS: u64 = 60; - -pub const HELP: &str = r#" -Usage - - rad-issue-tui [