From 3a5306e9dd50e3fe369295d8a5fef350a1ce9d77 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Thu, 2 May 2024 16:31:47 +0300 Subject: [PATCH] View manpages in pager inside meli Signed-off-by: Manos Pitsidianakis --- meli/src/command.rs | 32 ++++++++++++++++++++++++++++++++ meli/src/command/actions.rs | 2 ++ meli/src/command/parser.rs | 35 ++++++++++++++++++++++++++++++++++- meli/src/manpages.rs | 16 ++++++++++++++++ meli/src/state.rs | 21 +++++++++++++++++++++ meli/src/utilities.rs | 2 +- meli/src/utilities/pager.rs | 8 ++++++++ 7 files changed, 114 insertions(+), 2 deletions(-) diff --git a/meli/src/command.rs b/meli/src/command.rs index 43745c81..63d0b32f 100644 --- a/meli/src/command.rs +++ b/meli/src/command.rs @@ -143,6 +143,11 @@ impl TokenStream { tokens.append(&mut m); } } + AlternativeStrings(v) => { + for t in v.iter() { + sugg.insert(format!("{}{}", if s.is_empty() { " " } else { "" }, t)); + } + } Seq(_s) => {} RestOfStringValue => { sugg.insert(String::new()); @@ -196,6 +201,20 @@ impl TokenStream { *s = ""; } } + AlternativeStrings(v) => { + for lit in v.iter() { + if lit.starts_with(*s) && lit.len() != s.len() { + sugg.insert(lit[s.len()..].to_string()); + tokens.push((s, *t.inner())); + return tokens; + } else if s.starts_with(lit) { + tokens.push((&s[..lit.len()], *t.inner())); + *s = &s[lit.len()..]; + } else { + return vec![]; + } + } + } Seq(_s) => { return vec![]; } @@ -251,6 +270,7 @@ pub enum Token { Literal(&'static str), Filepath, Alternatives(&'static [TokenStream]), + AlternativeStrings(&'static [&'static str]), Seq(&'static [TokenAdicity]), AccountName, MailboxPath, @@ -488,6 +508,18 @@ Alternatives(&[to_stream!(One(Literal("add-attachment")), One(Filepath)), to_str tokens: &[One(Literal("manage-mailboxes"))], parser: parser::manage_mailboxes }, + { tags: ["man"], + desc: "read documentation", + tokens: { + #[cfg(feature = "cli-docs")] + { + &[One(Literal("man")), One(AlternativeStrings(crate::manpages::POSSIBLE_VALUES))] + } + #[cfg(not(feature = "cli-docs"))] + { &[] } + }, + parser: parser::view_manpage + }, { tags: ["manage-jobs"], desc: "view and manage jobs", tokens: &[One(Literal("manage-jobs"))], diff --git a/meli/src/command/actions.rs b/meli/src/command/actions.rs index 1e6e6e69..60caf229 100644 --- a/meli/src/command/actions.rs +++ b/meli/src/command/actions.rs @@ -70,6 +70,8 @@ pub enum TabAction { New(Option>), ManageMailboxes, ManageJobs, + #[cfg(feature = "cli-docs")] + Man(crate::manpages::ManPages), } #[derive(Debug)] diff --git a/meli/src/command/parser.rs b/meli/src/command/parser.rs index a0b52b8b..4511834e 100644 --- a/meli/src/command/parser.rs +++ b/meli/src/command/parser.rs @@ -139,7 +139,7 @@ pub fn view(input: &[u8]) -> IResult<&[u8], Result> { } pub fn new_tab(input: &[u8]) -> IResult<&[u8], Result> { - alt((manage_mailboxes, manage_jobs, compose_action))(input) + alt((manage_mailboxes, manage_jobs, compose_action, view_manpage))(input) } pub fn parse_command(input: &[u8]) -> Result { @@ -999,6 +999,39 @@ pub fn manage_jobs(input: &[u8]) -> IResult<&[u8], Result> let (input, _) = eof(input)?; Ok((input, Ok(Tab(ManageJobs)))) } + +#[cfg(feature = "cli-docs")] +pub fn view_manpage(input: &[u8]) -> IResult<&[u8], Result> { + let mut check = arg_init! { min_arg:1, max_arg: 1, view_manpage }; + let (input, _) = tag("man")(input.trim())?; + arg_chk!(start check, input); + let (input, _) = is_a(" ")(input)?; + arg_chk!(inc check, input); + let (input, manpage) = map_res(not_line_ending, std::str::from_utf8)(input.trim())?; + let (input, _) = eof(input)?; + arg_chk!(finish check, input); + match crate::manpages::parse_manpage(manpage) { + Ok(m) => Ok((input, Ok(Tab(Man(m))))), + Err(err) => Ok(( + input, + Err(CommandError::BadValue { + inner: err.to_string().into(), + suggestions: Some(crate::manpages::POSSIBLE_VALUES), + }), + )), + } +} + +#[cfg(not(feature = "cli-docs"))] +pub fn view_manpage(input: &[u8]) -> IResult<&[u8], Result> { + Ok(( + input, + Err(CommandError::Other { + inner: "this meli binary has not been compiled with the cli-docs feature".into(), + }), + )) +} + pub fn quit(input: &[u8]) -> IResult<&[u8], Result> { let mut check = arg_init! { min_arg:0, max_arg: 0, quit}; let (input, _) = tag("quit")(input.trim())?; diff --git a/meli/src/manpages.rs b/meli/src/manpages.rs index c1b5aee6..9ca90126 100644 --- a/meli/src/manpages.rs +++ b/meli/src/manpages.rs @@ -172,4 +172,20 @@ impl ManPages { Ok(v) } + + /// Helper function to remove backspace markup from mandoc output. + pub fn remove_markup(input: &str) -> Result { + use std::{ + io::Write, + process::{Command, Stdio}, + }; + let mut child = Command::new("col") + .arg("-b") + .arg("-x") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn()?; + child.stdin.as_mut().unwrap().write_all(input.as_bytes())?; + Ok(String::from_utf8_lossy(&child.wait_with_output()?.stdout).to_string()) + } } diff --git a/meli/src/state.rs b/meli/src/state.rs index b5c4d029..43e5ff34 100644 --- a/meli/src/state.rs +++ b/meli/src/state.rs @@ -914,6 +914,27 @@ impl State { ))) .unwrap(); } + #[cfg(feature = "cli-docs")] + Tab(Man(manpage)) => match manpage + .read(false) + .map(|text| crate::manpages::ManPages::remove_markup(&text).unwrap_or(text)) + { + Ok(m) => self.rcv_event(UIEvent::Action(Tab(New(Some(Box::new( + Pager::from_string( + m, + Some(&self.context), + None, + None, + crate::conf::value(&self.context, "theme_default"), + ), + )))))), + Err(err) => self.context.replies.push_back(UIEvent::Notification { + title: None, + body: "Encountered an error while retrieving manual page.".into(), + source: Some(err), + kind: Some(NotificationType::Error(ErrorKind::Bug)), + }), + }, v => { self.rcv_event(UIEvent::Action(v)); } diff --git a/meli/src/utilities.rs b/meli/src/utilities.rs index c53190dc..86326c46 100644 --- a/meli/src/utilities.rs +++ b/meli/src/utilities.rs @@ -1386,7 +1386,7 @@ impl Component for Tabbed { self.dirty = true; return true; } - UIEvent::Action(Tab(New(ref mut e))) if e.is_some() => { + UIEvent::Action(Tab(New(ref mut e @ Some(_)))) => { self.add_component(e.take().unwrap(), context); self.children[self.cursor_pos] .process_event(&mut UIEvent::VisibilityChange(false), context); diff --git a/meli/src/utilities/pager.rs b/meli/src/utilities/pager.rs index 47af41ba..c86784d0 100644 --- a/meli/src/utilities/pager.rs +++ b/meli/src/utilities/pager.rs @@ -877,4 +877,12 @@ impl Component for Pager { fn id(&self) -> ComponentId { self.id } + + fn kill(&mut self, uuid: ComponentId, context: &mut Context) { + if self.id != uuid { + return; + } + + context.replies.push_back(UIEvent::Action(Tab(Kill(uuid)))); + } }