From e7b9d2963cc72aedc936f4afb6a38224f4e2a6bb Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sun, 12 Sep 2021 17:39:51 +0300 Subject: [PATCH] pager: add filter command, esc to clear filter --- src/command.rs | 17 ++- src/command/actions.rs | 1 + src/components/utilities.rs | 68 ++++++----- src/components/utilities/pager.rs | 186 ++++++++++++++++++------------ src/types.rs | 1 + 5 files changed, 169 insertions(+), 104 deletions(-) diff --git a/src/command.rs b/src/command.rs index 589c8839..a5d45fc6 100644 --- a/src/command.rs +++ b/src/command.rs @@ -510,6 +510,21 @@ define_commands!([ } ) }, + /* Filter pager contents through binary */ + { tags: ["filter "], + desc: "filter EXECUTABLE ARGS", + tokens: &[One(Literal("filter")), One(Filepath), ZeroOrMore(QuotedStringValue)], + parser:( + fn filter<'a>(input: &'a [u8]) -> IResult<&'a [u8], Action> { + let (input, _) = tag("filter")(input.trim())?; + let (input, _) = is_a(" ")(input)?; + let (input, cmd) = map_res(not_line_ending, std::str::from_utf8)(input)?; + Ok((input, { + View(Filter(cmd.to_string())) + })) + } + ) + }, { tags: ["add-attachment ", "add-attachment-file-picker "], desc: "add-attachment PATH", tokens: &[One( @@ -885,7 +900,7 @@ fn account_action(input: &[u8]) -> IResult<&[u8], Action> { } fn view(input: &[u8]) -> IResult<&[u8], Action> { - alt((pipe, save_attachment, export_mail))(input) + alt((filter, pipe, save_attachment, export_mail))(input) } pub fn parse_command(input: &[u8]) -> Result { diff --git a/src/command/actions.rs b/src/command/actions.rs index 00d7a707..66bd7a73 100644 --- a/src/command/actions.rs +++ b/src/command/actions.rs @@ -75,6 +75,7 @@ pub enum MailingListAction { #[derive(Debug)] pub enum ViewAction { Pipe(String, Vec), + Filter(String), SaveAttachment(usize, String), ExportMail(String), } diff --git a/src/components/utilities.rs b/src/components/utilities.rs index 406c7136..a99f0d3e 100644 --- a/src/components/utilities.rs +++ b/src/components/utilities.rs @@ -53,6 +53,7 @@ pub struct StatusBar { container: Box, status: String, status_message: String, + substatus_message: String, ex_buffer: Field, ex_buffer_cmd_history_pos: Option, display_buffer: String, @@ -96,6 +97,7 @@ impl StatusBar { container, status: String::with_capacity(256), status_message: String::with_capacity(256), + substatus_message: String::with_capacity(256), ex_buffer: Field::Text(UText::new(String::with_capacity(256)), None), ex_buffer_cmd_history_pos: None, display_buffer: String::with_capacity(8), @@ -198,6 +200,31 @@ impl StatusBar { context.dirty_areas.push_back(area); } + fn update_status(&mut self, context: &Context) { + self.status = format!( + "{} {}| {}{}{}", + self.mode, + if self.mouse { + context + .settings + .terminal + .mouse_flag + .as_ref() + .map(|s| s.as_str()) + .unwrap_or("🖱️ ") + } else { + "" + }, + &self.status_message, + if !self.substatus_message.is_empty() { + " | " + } else { + "" + }, + &self.substatus_message, + ); + } + fn draw_command_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { clear_area(grid, area, crate::conf::value(context, "theme_default")); let (_, y) = write_string_to_grid( @@ -685,42 +712,19 @@ impl Component for StatusBar { UIEvent::StatusEvent(StatusEvent::UpdateStatus(ref mut s)) => { self.status_message.clear(); self.status_message.push_str(s.as_str()); - self.status = format!( - "{} {}| {}", - self.mode, - if self.mouse { - context - .settings - .terminal - .mouse_flag - .as_ref() - .map(|s| s.as_str()) - .unwrap_or("🖱️ ") - } else { - "" - }, - &self.status_message, - ); + self.substatus_message.clear(); + self.update_status(context); + self.dirty = true; + } + UIEvent::StatusEvent(StatusEvent::UpdateSubStatus(ref mut s)) => { + self.substatus_message.clear(); + self.substatus_message.push_str(s.as_str()); + self.update_status(context); self.dirty = true; } UIEvent::StatusEvent(StatusEvent::SetMouse(val)) => { self.mouse = *val; - self.status = format!( - "{} {}| {}", - self.mode, - if self.mouse { - context - .settings - .terminal - .mouse_flag - .as_ref() - .map(|s| s.as_str()) - .unwrap_or("🖱️ ") - } else { - "" - }, - &self.status_message, - ); + self.update_status(context); self.dirty = true; } UIEvent::StatusEvent(StatusEvent::JobCanceled(ref job_id)) diff --git a/src/components/utilities/pager.rs b/src/components/utilities/pager.rs index 33fefcc0..167f07d2 100644 --- a/src/components/utilities/pager.rs +++ b/src/components/utilities/pager.rs @@ -40,7 +40,7 @@ pub struct Pager { initialised: bool, show_scrollbar: bool, content: CellBuffer, - raw: bool, + filtered_content: Option<(String, Result)>, text_lines: Vec, line_breaker: LineBreakText, movement: Option, @@ -145,35 +145,7 @@ impl Pager { } } - if let Some(content) = pager_filter.and_then(|bin| { - use std::io::Write; - use std::process::{Command, Stdio}; - let mut filter_child = Command::new("sh") - .args(&["-c", bin]) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .expect("Failed to start pager filter process"); - let stdin = filter_child.stdin.as_mut().expect("failed to open stdin"); - stdin - .write_all(text.as_bytes()) - .expect("Failed to write to stdin"); - let out = filter_child - .wait_with_output() - .expect("Failed to wait on filter") - .stdout; - let mut dev_null = std::fs::File::open("/dev/null").ok()?; - let mut embedded = crate::terminal::embed::EmbedGrid::new(); - embedded.set_terminal_size((80, 20)); - - for b in out { - embedded.process_byte(&mut dev_null, b); - } - Some(std::mem::replace(embedded.buffer_mut(), Default::default())) - }) { - return Pager::from_buf(content, cursor_pos); - } - Pager { + let mut ret = Pager { text, text_lines: vec![], reflow, @@ -184,26 +156,54 @@ impl Pager { initialised: false, dirty: true, id: ComponentId::new_v4(), - raw: false, + filtered_content: None, colors, ..Default::default() + }; + + if let Some(bin) = pager_filter { + ret.filter(bin); } + + ret } - pub fn from_buf(content: CellBuffer, cursor_pos: Option) -> Self { - let (width, height) = content.size(); - Pager { - text: String::new(), - cursor: (0, cursor_pos.unwrap_or(0)), - height, - width, - dirty: true, - content, - raw: true, - initialised: false, - id: ComponentId::new_v4(), - ..Default::default() + pub fn filter(&mut self, cmd: &str) { + let _f = |bin: &str, text: &str| -> Result { + use std::io::Write; + use std::process::{Command, Stdio}; + let mut filter_child = Command::new("sh") + .args(&["-c", bin]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .chain_err_summary(|| "Failed to start pager filter process")?; + let stdin = filter_child + .stdin + .as_mut() + .ok_or_else(|| "failed to open stdin")?; + stdin + .write_all(text.as_bytes()) + .chain_err_summary(|| "Failed to write to stdin")?; + let out = filter_child + .wait_with_output() + .chain_err_summary(|| "Failed to wait on filter")? + .stdout; + let mut dev_null = std::fs::File::open("/dev/null")?; + let mut embedded = crate::terminal::embed::EmbedGrid::new(); + embedded.set_terminal_size((80, 20)); + + for b in out { + embedded.process_byte(&mut dev_null, b); + } + Ok(std::mem::replace(embedded.buffer_mut(), Default::default())) + }; + let buf = _f(cmd, &self.text); + if let Some((width, height)) = buf.as_ref().ok().map(CellBuffer::size) { + self.width = width; + self.height = height; } + self.filtered_content = Some((cmd.to_string(), buf)); } pub fn cursor_pos(&self) -> usize { @@ -219,7 +219,7 @@ impl Pager { if width < self.minimum_width { width = self.minimum_width; } - if !self.raw { + if self.filtered_content.is_none() { if self.line_breaker.width() != Some(width.saturating_sub(4)) { let line_breaker = LineBreakText::new( self.text.clone(), @@ -296,26 +296,45 @@ impl Pager { } fn draw_page(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - if self.raw { - copy_area( - grid, - &self.content, - area, - ( - ( - std::cmp::min( - self.cursor.0.saturating_sub(width!(area)), - self.content.size().0.saturating_sub(width!(area)), + if let Some((ref cmd, ref filtered_content)) = self.filtered_content { + match filtered_content { + Ok(ref content) => { + copy_area( + grid, + &content, + area, + ( + ( + std::cmp::min( + self.cursor.0, + content.size().0.saturating_sub(width!(area)), + ), + std::cmp::min( + self.cursor.1, + content.size().1.saturating_sub(height!(area)), + ), + ), + pos_dec(content.size(), (1, 1)), ), - std::cmp::min( - self.cursor.1.saturating_sub(height!(area)), - self.content.size().1.saturating_sub(height!(area)), - ), - ), - pos_dec(self.content.size(), (1, 1)), - ), - ); - return; + ); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::UpdateSubStatus( + cmd.to_string(), + ))); + return; + } + Err(ref err) => { + let mut cmd = cmd.as_str(); + cmd.truncate_at_boundary(4); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::UpdateSubStatus(format!( + "{}: {}", + cmd, err + )))); + } + } } let (mut upper_left, bottom_right) = area; @@ -507,7 +526,14 @@ impl Component for Pager { if cols < 2 || rows < 2 { return; } - let (width, height) = (self.line_breaker.width().unwrap_or(cols), self.height); + let (has_more_lines, (width, height)) = if self.filtered_content.is_some() { + (false, (self.width, self.height)) + } else { + ( + !self.line_breaker.is_finished(), + (self.line_breaker.width().unwrap_or(cols), self.height), + ) + }; if self.show_scrollbar && rows < height { cols -= 1; rows -= 1; @@ -569,7 +595,7 @@ impl Component for Pager { context: ScrollContext { shown_lines, total_lines, - has_more_lines: !self.line_breaker.is_finished(), + has_more_lines, }, }, ))); @@ -591,11 +617,7 @@ impl Component for Pager { search.cursor + 1 }, total_results = search.positions.len(), - has_more_lines = if self.line_breaker.is_finished() { - "" - } else { - "(+)" - } + has_more_lines = if !has_more_lines { "" } else { "(+)" } ); let mut attribute = crate::conf::value(context, "status.bar"); if !context.settings.terminal.use_color() { @@ -716,6 +738,12 @@ impl Component for Pager { )))); return true; } + UIEvent::Action(View(Filter(ref cmd))) => { + self.filter(cmd); + self.initialised = false; + self.dirty = true; + return true; + } UIEvent::Action(Action::Listing(ListingAction::Search(pattern))) => { self.search = Some(SearchPattern { pattern: pattern.to_string(), @@ -759,6 +787,17 @@ impl Component for Pager { self.dirty = true; return true; } + UIEvent::Input(Key::Esc) if self.filtered_content.is_some() => { + self.filtered_content = None; + self.initialised = false; + self.dirty = true; + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::UpdateSubStatus( + String::new(), + ))); + return true; + } UIEvent::Resize => { self.initialised = false; self.dirty = true; @@ -769,6 +808,11 @@ impl Component for Pager { .push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate( ScrollUpdate::End(self.id), ))); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::UpdateSubStatus( + String::new(), + ))); } _ => {} } diff --git a/src/types.rs b/src/types.rs index 95c14c16..fb3711e8 100644 --- a/src/types.rs +++ b/src/types.rs @@ -53,6 +53,7 @@ pub enum StatusEvent { BufClear, BufSet(String), UpdateStatus(String), + UpdateSubStatus(String), NewJob(JobId), JobFinished(JobId), JobCanceled(JobId),