pager: add filter command, esc to clear filter

pull/144/head
Manos Pitsidianakis 2021-09-12 17:39:51 +03:00
parent 25579d8807
commit e7b9d2963c
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
5 changed files with 169 additions and 104 deletions

View File

@ -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 "], { tags: ["add-attachment ", "add-attachment-file-picker "],
desc: "add-attachment PATH", desc: "add-attachment PATH",
tokens: &[One( tokens: &[One(
@ -885,7 +900,7 @@ fn account_action(input: &[u8]) -> IResult<&[u8], Action> {
} }
fn view(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<Action, MeliError> { pub fn parse_command(input: &[u8]) -> Result<Action, MeliError> {

View File

@ -75,6 +75,7 @@ pub enum MailingListAction {
#[derive(Debug)] #[derive(Debug)]
pub enum ViewAction { pub enum ViewAction {
Pipe(String, Vec<String>), Pipe(String, Vec<String>),
Filter(String),
SaveAttachment(usize, String), SaveAttachment(usize, String),
ExportMail(String), ExportMail(String),
} }

View File

@ -53,6 +53,7 @@ pub struct StatusBar {
container: Box<dyn Component>, container: Box<dyn Component>,
status: String, status: String,
status_message: String, status_message: String,
substatus_message: String,
ex_buffer: Field, ex_buffer: Field,
ex_buffer_cmd_history_pos: Option<usize>, ex_buffer_cmd_history_pos: Option<usize>,
display_buffer: String, display_buffer: String,
@ -96,6 +97,7 @@ impl StatusBar {
container, container,
status: String::with_capacity(256), status: String::with_capacity(256),
status_message: 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: Field::Text(UText::new(String::with_capacity(256)), None),
ex_buffer_cmd_history_pos: None, ex_buffer_cmd_history_pos: None,
display_buffer: String::with_capacity(8), display_buffer: String::with_capacity(8),
@ -198,6 +200,31 @@ impl StatusBar {
context.dirty_areas.push_back(area); 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) { fn draw_command_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
clear_area(grid, area, crate::conf::value(context, "theme_default")); clear_area(grid, area, crate::conf::value(context, "theme_default"));
let (_, y) = write_string_to_grid( let (_, y) = write_string_to_grid(
@ -685,42 +712,19 @@ impl Component for StatusBar {
UIEvent::StatusEvent(StatusEvent::UpdateStatus(ref mut s)) => { UIEvent::StatusEvent(StatusEvent::UpdateStatus(ref mut s)) => {
self.status_message.clear(); self.status_message.clear();
self.status_message.push_str(s.as_str()); self.status_message.push_str(s.as_str());
self.status = format!( self.substatus_message.clear();
"{} {}| {}", self.update_status(context);
self.mode, self.dirty = true;
if self.mouse { }
context UIEvent::StatusEvent(StatusEvent::UpdateSubStatus(ref mut s)) => {
.settings self.substatus_message.clear();
.terminal self.substatus_message.push_str(s.as_str());
.mouse_flag self.update_status(context);
.as_ref()
.map(|s| s.as_str())
.unwrap_or("🖱️ ")
} else {
""
},
&self.status_message,
);
self.dirty = true; self.dirty = true;
} }
UIEvent::StatusEvent(StatusEvent::SetMouse(val)) => { UIEvent::StatusEvent(StatusEvent::SetMouse(val)) => {
self.mouse = *val; self.mouse = *val;
self.status = format!( self.update_status(context);
"{} {}| {}",
self.mode,
if self.mouse {
context
.settings
.terminal
.mouse_flag
.as_ref()
.map(|s| s.as_str())
.unwrap_or("🖱️ ")
} else {
""
},
&self.status_message,
);
self.dirty = true; self.dirty = true;
} }
UIEvent::StatusEvent(StatusEvent::JobCanceled(ref job_id)) UIEvent::StatusEvent(StatusEvent::JobCanceled(ref job_id))

View File

@ -40,7 +40,7 @@ pub struct Pager {
initialised: bool, initialised: bool,
show_scrollbar: bool, show_scrollbar: bool,
content: CellBuffer, content: CellBuffer,
raw: bool, filtered_content: Option<(String, Result<CellBuffer>)>,
text_lines: Vec<String>, text_lines: Vec<String>,
line_breaker: LineBreakText, line_breaker: LineBreakText,
movement: Option<PageMovement>, movement: Option<PageMovement>,
@ -145,35 +145,7 @@ impl Pager {
} }
} }
if let Some(content) = pager_filter.and_then(|bin| { let mut ret = Pager {
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 {
text, text,
text_lines: vec![], text_lines: vec![],
reflow, reflow,
@ -184,26 +156,54 @@ impl Pager {
initialised: false, initialised: false,
dirty: true, dirty: true,
id: ComponentId::new_v4(), id: ComponentId::new_v4(),
raw: false, filtered_content: None,
colors, colors,
..Default::default() ..Default::default()
};
if let Some(bin) = pager_filter {
ret.filter(bin);
} }
ret
} }
pub fn from_buf(content: CellBuffer, cursor_pos: Option<usize>) -> Self { pub fn filter(&mut self, cmd: &str) {
let (width, height) = content.size(); let _f = |bin: &str, text: &str| -> Result<CellBuffer> {
Pager { use std::io::Write;
text: String::new(), use std::process::{Command, Stdio};
cursor: (0, cursor_pos.unwrap_or(0)), let mut filter_child = Command::new("sh")
height, .args(&["-c", bin])
width, .stdin(Stdio::piped())
dirty: true, .stdout(Stdio::piped())
content, .spawn()
raw: true, .chain_err_summary(|| "Failed to start pager filter process")?;
initialised: false, let stdin = filter_child
id: ComponentId::new_v4(), .stdin
..Default::default() .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 { pub fn cursor_pos(&self) -> usize {
@ -219,7 +219,7 @@ impl Pager {
if width < self.minimum_width { if width < self.minimum_width {
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)) { if self.line_breaker.width() != Some(width.saturating_sub(4)) {
let line_breaker = LineBreakText::new( let line_breaker = LineBreakText::new(
self.text.clone(), self.text.clone(),
@ -296,26 +296,45 @@ impl Pager {
} }
fn draw_page(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw_page(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if self.raw { if let Some((ref cmd, ref filtered_content)) = self.filtered_content {
copy_area( match filtered_content {
grid, Ok(ref content) => {
&self.content, copy_area(
area, grid,
( &content,
( area,
std::cmp::min( (
self.cursor.0.saturating_sub(width!(area)), (
self.content.size().0.saturating_sub(width!(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)), context
self.content.size().1.saturating_sub(height!(area)), .replies
), .push_back(UIEvent::StatusEvent(StatusEvent::UpdateSubStatus(
), cmd.to_string(),
pos_dec(self.content.size(), (1, 1)), )));
), return;
); }
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; let (mut upper_left, bottom_right) = area;
@ -507,7 +526,14 @@ impl Component for Pager {
if cols < 2 || rows < 2 { if cols < 2 || rows < 2 {
return; 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 { if self.show_scrollbar && rows < height {
cols -= 1; cols -= 1;
rows -= 1; rows -= 1;
@ -569,7 +595,7 @@ impl Component for Pager {
context: ScrollContext { context: ScrollContext {
shown_lines, shown_lines,
total_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 search.cursor + 1
}, },
total_results = search.positions.len(), total_results = search.positions.len(),
has_more_lines = if self.line_breaker.is_finished() { has_more_lines = if !has_more_lines { "" } else { "(+)" }
""
} else {
"(+)"
}
); );
let mut attribute = crate::conf::value(context, "status.bar"); let mut attribute = crate::conf::value(context, "status.bar");
if !context.settings.terminal.use_color() { if !context.settings.terminal.use_color() {
@ -716,6 +738,12 @@ impl Component for Pager {
)))); ))));
return true; 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))) => { UIEvent::Action(Action::Listing(ListingAction::Search(pattern))) => {
self.search = Some(SearchPattern { self.search = Some(SearchPattern {
pattern: pattern.to_string(), pattern: pattern.to_string(),
@ -759,6 +787,17 @@ impl Component for Pager {
self.dirty = true; self.dirty = true;
return 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 => { UIEvent::Resize => {
self.initialised = false; self.initialised = false;
self.dirty = true; self.dirty = true;
@ -769,6 +808,11 @@ impl Component for Pager {
.push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate( .push_back(UIEvent::StatusEvent(StatusEvent::ScrollUpdate(
ScrollUpdate::End(self.id), ScrollUpdate::End(self.id),
))); )));
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateSubStatus(
String::new(),
)));
} }
_ => {} _ => {}
} }

View File

@ -53,6 +53,7 @@ pub enum StatusEvent {
BufClear, BufClear,
BufSet(String), BufSet(String),
UpdateStatus(String), UpdateStatus(String),
UpdateSubStatus(String),
NewJob(JobId), NewJob(JobId),
JobFinished(JobId), JobFinished(JobId),
JobCanceled(JobId), JobCanceled(JobId),