diff --git a/src/app.rs b/src/app.rs index 046c977..f8ccf09 100644 --- a/src/app.rs +++ b/src/app.rs @@ -443,6 +443,9 @@ impl App { Key::Char('C') => Some(Action::DeepCollapseNodeAndSiblings), Key::Char('e') => Some(Action::ExpandNodeAndSiblings), Key::Char('E') => Some(Action::DeepExpandNodeAndSiblings), + Key::Char('x') => Some(Action::HideItem), + Key::Char('u') => Some(Action::UndoHideItem), + Key::Char('U') => Some(Action::UndoHideItemAll), Key::Char(' ') => Some(Action::ToggleCollapsed), Key::Char('^') => Some(Action::FocusFirstSibling), Key::Char('$') => Some(Action::FocusLastSibling), diff --git a/src/flatjson.rs b/src/flatjson.rs index 68cc5de..231b7bd 100644 --- a/src/flatjson.rs +++ b/src/flatjson.rs @@ -59,6 +59,9 @@ pub struct FlatJson( pub String, // Max nesting depth. pub usize, + // List of queries for which any matching items will be hidden, constructed + // using `build_path_to_node`. + pub Vec, ); impl FlatJson { @@ -80,6 +83,15 @@ impl FlatJson { loop { let row = &self.0[last_index]; + if self.is_row_hidden(last_index) { + if row.is_closing_of_container() { + last_index = row.pair_index().unwrap() - 1; + } else { + last_index -= 1; + } + continue; + } + if row.is_primitive() { return last_index; } @@ -93,31 +105,65 @@ impl FlatJson { } pub fn prev_visible_row(&self, index: Index) -> OptionIndex { - if index == 0 { - return OptionIndex::Nil; - } + if index == 0 { return OptionIndex::Nil } - let row = &self.0[index - 1]; + let mut prev_index = index - 1; + loop { + let prev_row = &self.0[prev_index]; + if prev_row.is_closing_of_container() && prev_row.is_collapsed() { + prev_index = prev_row.pair_index().unwrap(); + } - if row.is_closing_of_container() && row.is_collapsed() { - row.pair_index() - } else { - OptionIndex::Index(index - 1) + // If the prev item is hidden and is a container, we skip to 1 before + // the opening of the container, otherwise we simply skip 1. + // + // If the prev item is not hidden, we break out the loop to return + // the item. + if self.is_row_hidden(prev_index) { + if prev_row.is_closing_of_container() { + prev_index = prev_row.pair_index().unwrap() - 1; + } else { + prev_index -= 1; + } + } else { + break; + } } + OptionIndex::Index(prev_index) } - pub fn next_visible_row(&self, mut index: Index) -> OptionIndex { - // If row is collapsed container, jump to closing char and move past there. - if self.0[index].is_opening_of_container() && self.0[index].is_collapsed() { - index = self.0[index].pair_index().unwrap(); - } + pub fn next_visible_row(&self, index: Index) -> OptionIndex { + let curr_row = &self.0[index]; + let is_collapsed_container = + curr_row.is_opening_of_container() && curr_row.is_collapsed(); + let mut next_index = if is_collapsed_container { + curr_row.pair_index().unwrap() + 1 + } else { + index + 1 + }; - // We can always go to the next row, unless we're at the end of the file. - if index == self.0.len() - 1 { - return OptionIndex::Nil; + loop { + // We have reached passed the last item, meaning there is no next + // item, so we return Nil. + if next_index == self.0.len() { return OptionIndex::Nil }; + + // If the next item is hidden and is a container, we skip to 1 past + // the closing of the container, otherwise we simply skip 1. + // + // If the next item is not hidden, we break out the loop to return + // the item. + if self.is_row_hidden(next_index) { + let next_row = &self.0[next_index]; + if next_row.is_opening_of_container() { + next_index = next_row.pair_index().unwrap() + 1; + } else { + next_index += 1; + } + } else { + break; + } } - - OptionIndex::Index(index + 1) + OptionIndex::Index(next_index) } pub fn prev_item(&self, mut index: Index) -> OptionIndex { @@ -144,6 +190,12 @@ impl FlatJson { OptionIndex::Nil } + pub fn is_row_hidden(&self, index: Index) -> bool { + let current_row_query = self.build_path_to_node(PathType::Query, index) + .unwrap_or_default(); + self.3.contains(¤t_row_query) + } + pub fn expand(&mut self, index: Index) { if let OptionIndex::Index(pair) = self.0[index].pair_index() { self.0[pair].expand(); @@ -609,12 +661,12 @@ impl Value { pub fn parse_top_level_json(json: String) -> Result { let (rows, pretty, depth) = jsonparser::parse(json)?; - Ok(FlatJson(rows, pretty, depth)) + Ok(FlatJson(rows, pretty, depth, vec![])) } pub fn parse_top_level_yaml(yaml: String) -> Result { let (rows, pretty, depth) = yamlparser::parse(yaml)?; - Ok(FlatJson(rows, pretty, depth)) + Ok(FlatJson(rows, pretty, depth, vec![])) } #[cfg(test)] diff --git a/src/jless.help b/src/jless.help index 6a7bb28..67550e9 100644 --- a/src/jless.help +++ b/src/jless.help @@ -120,6 +120,15 @@ yq pq Copy/print a path that can be used by jq to filter the input JSON and return the currently focused value. + HIDING + + jless supports hiding and unhiding nodes. + + x Hide the current node the cursor is on, and all other nodes with the + same JQ query. + u Unhide the last hidden node. + U Unhide all hidden nodes. + SEARCH jless supports full-text search over the input JSON. diff --git a/src/viewer.rs b/src/viewer.rs index 98190ea..8e20daa 100644 --- a/src/viewer.rs +++ b/src/viewer.rs @@ -132,6 +132,10 @@ pub enum Action { ExpandNodeAndSiblings, DeepExpandNodeAndSiblings, + HideItem, + UndoHideItem, + UndoHideItemAll, + ToggleMode, ResizeViewerDimensions(TTYDimensions), @@ -182,6 +186,9 @@ impl JsonViewer { Action::DeepCollapseNodeAndSiblings => self.deep_collapse_node_and_siblings(), Action::ExpandNodeAndSiblings => self.expand_node_and_siblings(), Action::DeepExpandNodeAndSiblings => self.deep_expand_node_and_siblings(), + Action::HideItem => self.hide_item(), + Action::UndoHideItem => self.undo_hide_item(), + Action::UndoHideItemAll => self.undo_hide_item_all(), Action::ToggleMode => self.toggle_mode(), Action::ResizeViewerDimensions(dims) => self.dimensions = dims, } @@ -731,6 +738,36 @@ impl JsonViewer { self.set_deep_collapse_state_on_node_and_siblings(false); } + fn hide_item(&mut self) { + if self.focused_row == 0 || self.focused_row == self.flatjson.0.len() - 1 { return } + + // Add new filter query + let filter_query = self.flatjson + .build_path_to_node(crate::flatjson::PathType::Query, self.focused_row); + if filter_query.is_err() { return } + self.flatjson.3.push(filter_query.unwrap()); + + // Adjust focused_row + let next_visible_row = match self.mode { + Mode::Line => self.flatjson.next_visible_row(self.focused_row - 1 as Index), + Mode::Data => self.flatjson.next_item(self.focused_row - 1 as Index), + }; + + if let OptionIndex::Index(i) = next_visible_row { + self.focused_row = i; + } else { + self.focused_row = 0; + } + } + + fn undo_hide_item(&mut self) { + self.flatjson.3.pop(); + } + + fn undo_hide_item_all(&mut self) { + self.flatjson.3.clear(); + } + fn switch_focus_to_opening_of_container_if_on_closing(&mut self) { let focused_row = &mut self.flatjson[self.focused_row]; if focused_row.is_closing_of_container() {