Skip to content

Commit

Permalink
Merge pull request #38 from HewlettPackard/filter-modes
Browse files Browse the repository at this point in the history
Support inverted filters; UI for filter modes and inversion
  • Loading branch information
timothyb89 authored Oct 28, 2019
2 parents 13c4978 + c619c46 commit 3819a2c
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 21 deletions.
74 changes: 60 additions & 14 deletions src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,74 @@ use simple_error::{SimpleError, SimpleResult};
use crate::parser::Message;

pub trait Filter {
fn new(query: &str) -> SimpleResult<Self> where Self: Sized;
fn filter(&self, message: &Message) -> bool;
fn new(query: &str, inverted: bool) -> SimpleResult<Self> where Self: Sized;

/// Determines if the filter is inverted
fn inverted(&self) -> bool;

/// Determines if the given message matches the filter without checking if the
/// filter is inverted or not
fn filter_pass(&self, message: &Message) -> bool;

/// Determines if the given matches the filter, inverting the result if
/// configured to do so.
fn filter(&self, message: &Message) -> bool {
let pass = self.filter_pass(message);

if self.inverted() {
!pass
} else {
pass
}
}
}

#[derive(Debug, Copy, Clone)]
pub enum FilterMode {
#[allow(dead_code)] Text,
Text,
Regex
}

impl FilterMode {
pub fn parse(self, filter: &str) -> SimpleResult<Box<dyn Filter>> {
pub fn parse(self, filter: &str, inverted: bool) -> SimpleResult<Box<dyn Filter>> {
Ok(match self {
FilterMode::Text => Box::new(FullTextFilter::new(filter)?),
FilterMode::Regex => Box::new(RegexFilter::new(filter)?)
FilterMode::Text => Box::new(FullTextFilter::new(filter, inverted)?),
FilterMode::Regex => Box::new(RegexFilter::new(filter, inverted)?)
})
}

/// Given a FilterMode, return a different FilterMode (e.g. toggling between
/// modes)
pub fn next(self) -> FilterMode {
// will probably need to be smarter if more modes are added
match self {
FilterMode::Text => FilterMode::Regex,
FilterMode::Regex => FilterMode::Text
}
}

pub fn name(self) -> &'static str {
match self {
FilterMode::Text => "text",
FilterMode::Regex => "regex"
}
}
}

pub struct FullTextFilter {
query: String
query: String,
inverted: bool
}

impl Filter for FullTextFilter {
fn new(query: &str) -> SimpleResult<FullTextFilter> {
fn new(query: &str, inverted: bool) -> SimpleResult<FullTextFilter> {
Ok(FullTextFilter {
query: query.to_lowercase()
query: query.to_lowercase(),
inverted
})
}

fn filter(&self, message: &Message) -> bool {
fn filter_pass(&self, message: &Message) -> bool {
if message.kind.to_string().to_lowercase().contains(&self.query) {
return true;
}
Expand Down Expand Up @@ -67,20 +104,25 @@ impl Filter for FullTextFilter {

false
}

fn inverted(&self) -> bool {
self.inverted
}
}

pub struct RegexFilter {
re: Regex
re: Regex,
inverted: bool
}

impl Filter for RegexFilter {
fn new(expr: &str) -> SimpleResult<Self> {
fn new(expr: &str, inverted: bool) -> SimpleResult<Self> {
Regex::new(&expr)
.map_err(SimpleError::from)
.map(|re| RegexFilter { re })
.map(|re| RegexFilter { re, inverted })
}

fn filter(&self, message: &Message) -> bool {
fn filter_pass(&self, message: &Message) -> bool {
if self.re.find(&message.kind.to_string().to_lowercase()).is_some() {
return true;
}
Expand Down Expand Up @@ -109,4 +151,8 @@ impl Filter for RegexFilter {

false
}

fn inverted(&self) -> bool {
self.inverted
}
}
76 changes: 71 additions & 5 deletions src/renderer/interactive/filter_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ use super::InputAction;
#[derive(Clone)]
pub struct FilterBarState {
mode: FilterMode,
text: TextBuffer
text: TextBuffer,
inverted: bool
}

impl FilterBarState {
Expand All @@ -27,10 +28,31 @@ impl FilterBarState {
FilterBarState {
mode: FilterMode::Regex,
text: TextBuffer::new().with_styler(Some(styler)),
inverted: false
}
}
}

fn format_right(state: &RcState) -> String {
if state.width < 80 {
let inv = if state.filter.inverted { "y" } else { "n" };

format!(
"| m: {} (C-r), i: {} (C-e)",
state.filter.mode.name(),
inv
)
} else {
let inv = if state.filter.inverted { "yes" } else { "no" };

format!(
"| mode: {} (C-r), invert: {} (C-e)",
state.filter.mode.name(),
inv
)
}
}

pub fn render(
state: RcState, terminal: &Terminal, cursor: &TerminalCursor
) -> Result<RcState, Box<dyn Error>> {
Expand All @@ -48,6 +70,15 @@ pub fn render(
9, state.height - 1
)?;

// note: this will cover up excessively long user input (text module should
// support some form of horizontal scrolling?)
let right = format_right(&state);
let right_len = right.len();
if let Some(col) = state.width.checked_sub(right_len as u16) {
cursor.goto(col, state.height - 1)?;
terminal.write(&style.paint(right))?;
}

Ok(state)
}

Expand Down Expand Up @@ -80,7 +111,7 @@ pub fn input(mut state: RcState, key: &KeyEvent) -> (RcState, InputAction) {
a
},
TextInputAction::Submit(a, input) => {
match state.filter.mode.parse(&input) {
match state.filter.mode.parse(&input, state.filter.inverted) {
Ok(filter) => {
state = actions::clear_input(state);
state = bar::actions::set_active(state, BarType::Status);
Expand All @@ -102,7 +133,28 @@ pub fn input(mut state: RcState, key: &KeyEvent) -> (RcState, InputAction) {
}
};

(state, input_action)
let final_action = match input_action {
InputAction::Unhandled => match key {
KeyEvent::Ctrl('r') => {
state = actions::next_mode(state);
state = actions::update_highlight(state);
state = actions::update_style(state);

InputAction::Rerender
},
KeyEvent::Ctrl('e') => {
state = actions::toggle_inverted(state);
state = actions::update_highlight(state);
state = actions::update_style(state);

InputAction::Rerender
},
_ => InputAction::Unhandled
},
_ => input_action
};

(state, final_action)
}

pub mod actions {
Expand All @@ -113,7 +165,7 @@ pub mod actions {

let new_filter = if input.is_empty() {
None
} else if let Ok(parsed) = state.filter.mode.parse(&input) {
} else if let Ok(parsed) = state.filter.mode.parse(&input, state.filter.inverted) {
Some(Rc::new(parsed))
} else {
None
Expand All @@ -128,7 +180,7 @@ pub mod actions {
let input = &state_mut.filter.text.input;
let mode = &state_mut.filter.mode;

let styler = if input.is_empty() || mode.parse(input).is_ok() {
let styler = if input.is_empty() || mode.parse(input, state_mut.filter.inverted).is_ok() {
styler_base(StyleProfileKind::Selected)
} else {
styler_error(StyleProfileKind::Selected)
Expand All @@ -149,4 +201,18 @@ pub mod actions {

state
}

pub fn next_mode(mut state: RcState) -> RcState {
let state_mut = Rc::make_mut(&mut state);
state_mut.filter.mode = state_mut.filter.mode.next();

state
}

pub fn toggle_inverted(mut state: RcState) -> RcState {
let state_mut = Rc::make_mut(&mut state);
state_mut.filter.inverted = !state_mut.filter.inverted;

state
}
}
67 changes: 65 additions & 2 deletions src/renderer/interactive/search_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use super::InputAction;
pub struct SearchBarState {
mode: FilterMode,
text: TextBuffer,
inverted: bool,
filter: Option<Rc<Box<dyn Filter>>>
}

Expand All @@ -30,11 +31,32 @@ impl SearchBarState {
SearchBarState {
mode: FilterMode::Regex,
text: TextBuffer::new().with_styler(Some(styler)),
inverted: false,
filter: None
}
}
}

fn format_right(state: &RcState) -> String {
if state.width < 80 {
let inv = if state.search.inverted { "y" } else { "n" };

format!(
"| m: {} (C-r), i: {} (C-e)",
state.search.mode.name(),
inv
)
} else {
let inv = if state.search.inverted { "yes" } else { "no" };

format!(
"| mode: {} (C-r), invert: {} (C-e)",
state.search.mode.name(),
inv
)
}
}

pub fn render(
state: RcState, terminal: &Terminal, cursor: &TerminalCursor
) -> Result<RcState, Box<dyn Error>> {
Expand All @@ -58,6 +80,15 @@ pub fn render(
7, state.height - 1
)?;

// note: this will cover up excessively long user input (text module should
// support some form of horizontal scrolling?)
let right = format_right(&state);
let right_len = right.len();
if let Some(col) = state.width.checked_sub(right_len as u16) {
cursor.goto(col, state.height - 1)?;
terminal.write(&style.paint(right))?;
}

Ok(state)
}

Expand Down Expand Up @@ -119,6 +150,24 @@ pub fn input(mut state: RcState, key: &KeyEvent) -> (RcState, InputAction) {

InputAction::Rerender
},
KeyEvent::Ctrl('r') => {
state = actions::next_mode(state);
state = actions::update_filter(state);
state = actions::next_match(state, true);
state = actions::update_highlight(state);
state = actions::update_style(state);

InputAction::Rerender
},
KeyEvent::Ctrl('e') => {
state = actions::toggle_inverted(state);
state = actions::update_filter(state);
state = actions::next_match(state, true);
state = actions::update_highlight(state);
state = actions::update_style(state);

InputAction::Rerender
},
_ => InputAction::Unhandled
},
_ => input_action
Expand All @@ -136,7 +185,7 @@ pub mod actions {

let new_filter = if input.is_empty() {
None
} else if let Ok(parsed) = state.search.mode.parse(&input) {
} else if let Ok(parsed) = state.search.mode.parse(&input, state.search.inverted) {
Some(Rc::new(parsed))
} else {
None
Expand Down Expand Up @@ -170,7 +219,7 @@ pub mod actions {
let input = &state_mut.search.text.input;
let mode = &state_mut.search.mode;

let styler = if input.is_empty() || mode.parse(input).is_ok() {
let styler = if input.is_empty() || mode.parse(input, state_mut.search.inverted).is_ok() {
styler_base(StyleProfileKind::Selected)
} else {
styler_error(StyleProfileKind::Selected)
Expand Down Expand Up @@ -279,4 +328,18 @@ pub mod actions {
log::actions::clear_selection(state)
}
}

pub fn next_mode(mut state: RcState) -> RcState {
let state_mut = Rc::make_mut(&mut state);
state_mut.search.mode = state_mut.search.mode.next();

state
}

pub fn toggle_inverted(mut state: RcState) -> RcState {
let state_mut = Rc::make_mut(&mut state);
state_mut.search.inverted = !state_mut.search.inverted;

state
}
}

0 comments on commit 3819a2c

Please sign in to comment.