Skip to content

Commit

Permalink
Linked filters feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Avarel committed Aug 11, 2024
1 parent ecee0ed commit 46a5657
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 126 deletions.
128 changes: 91 additions & 37 deletions crates/cli/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ pub enum PromptMode {
Search { escaped: bool },
}

#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Clone, Copy)]
#[serde(tag = "delta")]
pub enum ViewDelta {
Number(u16),
Expand Down Expand Up @@ -147,10 +147,20 @@ impl<'term> App<'term> {
};
let viewer = self.mux.active_viewer_mut().unwrap();
match filter_sets.first() {
Some(export) => viewer.import_user_filters(export.clone()),
Some(export) => viewer.import_user_filters(export),
None => {}
}
}
if self.linked_filters {
if let Some(source) = self.mux.active_viewer_mut() {
let export = source.compositor_mut().export_user_filters();
let cursor = *source.compositor_mut().cursor();

let viewer = self.mux.viewers_mut().last_mut().unwrap();
viewer.import_user_filters(&export);
viewer.compositor_mut().set_cursor(cursor)
}
}

Ok(())
}
Expand Down Expand Up @@ -195,10 +205,6 @@ impl<'term> App<'term> {
Ok(())
}

fn toggle_linked_filters(&mut self) {
self.linked_filters = !self.linked_filters;
}

fn toggle_mouse_capture(&mut self) -> Result<()> {
self.mouse_capture = !self.mouse_capture;
if self.mouse_capture {
Expand Down Expand Up @@ -272,6 +278,17 @@ impl<'term> App<'term> {
}
}

fn process_filter_action<F>(&mut self, mut f: F)
where
F: FnMut(&mut Instance),
{
if self.linked_filters {
self.mux.viewers_mut().iter_mut().for_each(f);
} else if let Some(viewer) = self.mux.active_viewer_mut() {
f(viewer);
}
}

fn process_action(&mut self, action: Action) -> Result<bool> {
match action {
Action::Exit => return Ok(false),
Expand Down Expand Up @@ -348,26 +365,32 @@ impl<'term> App<'term> {
select,
delta,
} => {
if let Some(viewer) = self.mux.active_viewer_mut() {
self.process_filter_action(|viewer| {
viewer
.compositor_mut()
.move_select(direction, select, delta)
}
});
}
actions::FilterAction::ToggleSelectedFilter => {
if let Some(viewer) = self.mux.active_viewer_mut() {
viewer.toggle_select_filters();
}
self.process_filter_action(|viewer| {
let selected_filters = viewer.selected_filters();
viewer.toggle_filters(selected_filters);
});
}
actions::FilterAction::RemoveSelectedFilter => {
if let Some(viewer) = self.mux.active_viewer_mut() {
viewer.remove_select_filter();
}
self.process_filter_action(|viewer| {
let selected_filters = viewer.selected_filters();
viewer.remove_filters(selected_filters);
});
}
actions::FilterAction::ToggleFilter {
target_view,
filter_index,
} => {
if self.linked_filters {
// TODO: handle this
return Ok(true);
}
if let Some(viewer) = self.mux.viewers_mut().get_mut(target_view) {
viewer.toggle_filter(filter_index)
}
Expand Down Expand Up @@ -473,6 +496,23 @@ impl<'term> App<'term> {
}
}

fn replicate_filters_on_all_viewers(&mut self) {
if let Some(source) = self.mux.active_viewer_mut() {
let export = source.compositor_mut().export_user_filters();
let cursor = *source.compositor_mut().cursor();
let active = self.mux.active();
self.mux
.viewers_mut()
.iter_mut()
.enumerate()
.filter(|(i, _)| *i != active)
.for_each(|(_, viewer)| {
viewer.import_user_filters(&export);
viewer.compositor_mut().set_cursor(cursor)
});
}
}

fn process_shell(&mut self, command: &str, terminate: bool, pipe: bool) -> Result<bool> {
let Ok(expanded) = shellexpand::env_with_context(command, |s| self.context(s)) else {
self.status.msg("shell: expansion failed".to_string());
Expand Down Expand Up @@ -533,16 +573,21 @@ impl<'term> App<'term> {
}

fn process_search(&mut self, pat: &str, escaped: bool) -> bool {
if let Some(viewer) = self.mux.active_viewer_mut() {
let mut e = None;
self.process_filter_action(|viewer: &mut Instance| {
if let Err(err) = viewer.add_search_filter(pat, escaped) {
self.status.msg(match err {
regex::Error::Syntax(err) => format!("{pat}: syntax ({err})"),
regex::Error::CompiledTooBig(sz) => {
format!("{pat}: regex surpassed size limit ({sz} bytes)")
}
_ => format!("{pat}: {err}"),
});
e.get_or_insert(err);
};
});

if let Some(err) = e {
self.status.msg(match err {
regex::Error::Syntax(err) => format!("{pat}: syntax ({err})"),
regex::Error::CompiledTooBig(sz) => {
format!("{pat}: regex surpassed size limit ({sz} bytes)")
}
_ => format!("{pat}: {err}"),
});
}

true
Expand Down Expand Up @@ -609,7 +654,10 @@ impl<'term> App<'term> {
},
Some("filter" | "find" | "f") => match parts.next() {
Some("link") => {
self.toggle_linked_filters();
self.linked_filters = !self.linked_filters;
if self.linked_filters {
self.replicate_filters_on_all_viewers();
}
return true;
}
Some("persist") => {
Expand Down Expand Up @@ -659,7 +707,7 @@ impl<'term> App<'term> {
return true;
};

target.import_user_filters(export);
target.import_user_filters(&export);
}
Some("save") => {
let Some(source) = self.mux.active_viewer_mut() else {
Expand All @@ -675,10 +723,6 @@ impl<'term> App<'term> {
}

Some("load") => {
let Some(viewer) = self.mux.active_viewer_mut() else {
return true;
};

let filter_sets = match self.filter_data.filters() {
Ok(filters) => filters,
Err(err) => {
Expand All @@ -688,7 +732,14 @@ impl<'term> App<'term> {
};

match filter_sets.first() {
Some(export) => viewer.import_user_filters(export.clone()),
Some(export) => {
// Can get rid of this clone if process_filter_actions was part of mux
let export = export.clone();
self.process_filter_action(|viewer| {
viewer.clear_filters();
viewer.import_user_filters(&export);
});
}
None => {
self.status.msg("filter load: no saved filters".to_string());
}
Expand All @@ -703,19 +754,19 @@ impl<'term> App<'term> {
return self.process_search(&pat, true);
}
Some("clear") => {
if let Some(viewer) = self.mux.active_viewer_mut() {
viewer.compositor_mut().filters_mut().clear();
}
self.process_filter_action(|viewer| {
viewer.clear_filters();
});
}
Some("union" | "u" | "||" | "|") => {
if let Some(viewer) = self.mux.active_viewer_mut() {
self.process_filter_action(|viewer| {
viewer.set_composite_strategy(CompositeStrategy::Union);
}
});
}
Some("intersect" | "i" | "&&" | "&") => {
if let Some(viewer) = self.mux.active_viewer_mut() {
self.process_filter_action(|viewer| {
viewer.set_composite_strategy(CompositeStrategy::Intersection);
}
});
}
Some(cmd) => {
self.status.msg(format!("filter {cmd}: invalid subcommand"));
Expand Down Expand Up @@ -791,7 +842,10 @@ impl<'term> App<'term> {
mode: self.mode,
gutter: self.gutter,
linked_filters: self.linked_filters,
regex: self.regex_cache.as_ref().and_then(|cache| cache.regex.as_ref()),
regex: self
.regex_cache
.as_ref()
.and_then(|cache| cache.regex.as_ref()),
}
.render(mux_chunk, f.buffer_mut(), handler);

Expand Down
121 changes: 67 additions & 54 deletions crates/cli/app/widgets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,16 +230,20 @@ pub struct MultiplexerPane<'a> {
impl MultiplexerPane<'_> {
const FILTER_MAX_HEIGHT: u16 = 10;

fn render_filter_pane(area: &mut Rect, buf: &mut Buffer, view_index: usize, viewer: &mut Instance, handler: &mut MouseHandler) {
let [view_chunk, filter_chunk] =
MultiplexerWidget::split_bottom(*area, Self::FILTER_MAX_HEIGHT);
FilterViewerWidget {
view_index,
viewer,
}
.render(filter_chunk, buf, handler);
*area = view_chunk;
}

pub fn render(self, mut area: Rect, buf: &mut Buffer, handler: &mut MouseHandler) {
if self.show_filter_on_pane {
let [view_chunk, filter_chunk] =
MultiplexerWidget::split_bottom(area, Self::FILTER_MAX_HEIGHT);
FilterViewerWidget {
view_index: self.view_index,
viewer: self.viewer,
}
.render(filter_chunk, buf, handler);
area = view_chunk;
Self::render_filter_pane(&mut area, buf, self.view_index, self.viewer, handler);
}

LineViewerWidget {
Expand Down Expand Up @@ -290,56 +294,43 @@ impl MultiplexerWidget<'_> {
[view_chunk, filter_chunk]
}

pub fn render(self, area: Rect, buf: &mut Buffer, handler: &mut MouseHandler) {
let [mux_chunk, status_chunk] = Self::split_bottom(area, 1);
fn render_mux(&mut self, mut area: Rect, buf: &mut Buffer, handler: &mut MouseHandler) {
let active = self.mux.active();

if !self.mux.is_empty() {
let active = self.mux.active();

let show_filter_on_pane = self.mode == InputMode::Filter && !self.linked_filters;

let [tab_chunk, view_chunk] = Self::split_top(mux_chunk, 1);
let split_chunks = Self::split_horizontal(mux_chunk, self.mux.len());

for (view_index, (chunk, viewer)) in split_chunks
.iter()
.map(|&chunk| tab_chunk.intersection(chunk))
.zip(self.mux.viewers_mut())
.enumerate()
{
TabWidget {
view_index,
name: viewer.name(),
active: active == view_index,
}
.render(chunk, buf, handler);
}
let show_filter_on_pane = self.mode == InputMode::Filter && !self.linked_filters;
let show_filter_on_mux = self.mode == InputMode::Filter && self.linked_filters;

match self.mux.mode() {
MultiplexerMode::Panes => {
for (view_index, (pane_chunk, viewer)) in split_chunks
.iter()
.map(|&chunk| view_chunk.intersection(chunk))
.zip(self.mux.viewers_mut())
.enumerate()
{
MultiplexerPane {
view_index,
viewer,
show_filter_on_pane,
show_selection: self.mode == InputMode::Visual,
gutter: self.gutter,
regex: self.regex,
}
.render(pane_chunk, buf, handler);
}
}
MultiplexerMode::Tabs => {
let viewer = self.mux.active_viewer_mut().unwrap();
let pane_chunk = view_chunk;
if show_filter_on_mux {
MultiplexerPane::render_filter_pane(&mut area, buf, active, self.mux.active_viewer_mut().unwrap(), handler);
}

let [tab_chunk, view_chunk] = Self::split_top(area, 1);
let split_chunks = Self::split_horizontal(area, self.mux.len());

for (view_index, (chunk, viewer)) in split_chunks
.iter()
.map(|&chunk| tab_chunk.intersection(chunk))
.zip(self.mux.viewers_mut())
.enumerate()
{
TabWidget {
view_index,
name: viewer.name(),
active: active == view_index,
}
.render(chunk, buf, handler);
}

match self.mux.mode() {
MultiplexerMode::Panes => {
for (view_index, (pane_chunk, viewer)) in split_chunks
.iter()
.map(|&chunk| view_chunk.intersection(chunk))
.zip(self.mux.viewers_mut())
.enumerate()
{
MultiplexerPane {
view_index: active,
view_index,
viewer,
show_filter_on_pane,
show_selection: self.mode == InputMode::Visual,
Expand All @@ -349,6 +340,28 @@ impl MultiplexerWidget<'_> {
.render(pane_chunk, buf, handler);
}
}
MultiplexerMode::Tabs => {
let viewer = self.mux.active_viewer_mut().unwrap();
let pane_chunk = view_chunk;

MultiplexerPane {
view_index: active,
viewer,
show_filter_on_pane,
show_selection: self.mode == InputMode::Visual,
gutter: self.gutter,
regex: self.regex,
}
.render(pane_chunk, buf, handler);
}
}
}

pub fn render(mut self, area: Rect, buf: &mut Buffer, handler: &mut MouseHandler) {
let [mux_chunk, status_chunk] = Self::split_bottom(area, 1);

if !self.mux.is_empty() {
self.render_mux(mux_chunk, buf, handler);
} else {
const BG_BLOCK: OnceLock<Block> = OnceLock::new();
BG_BLOCK
Expand Down
1 change: 1 addition & 0 deletions crates/cli/components/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ impl Cursor {
}
}

#[derive(Clone, Copy)]
pub struct CursorState {
state: Cursor,
}
Expand Down
Loading

0 comments on commit 46a5657

Please sign in to comment.