pager: add filter command, esc to clear filter
parent
25579d8807
commit
e7b9d2963c
|
@ -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> {
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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(),
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
Loading…
Reference in New Issue