Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
92 changes: 72 additions & 20 deletions src/flatjson.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
);

impl FlatJson {
Expand All @@ -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;
}
Expand All @@ -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 {
Expand All @@ -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(&current_row_query)
}

pub fn expand(&mut self, index: Index) {
if let OptionIndex::Index(pair) = self.0[index].pair_index() {
self.0[pair].expand();
Expand Down Expand Up @@ -609,12 +661,12 @@ impl Value {

pub fn parse_top_level_json(json: String) -> Result<FlatJson, String> {
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<FlatJson, String> {
let (rows, pretty, depth) = yamlparser::parse(yaml)?;
Ok(FlatJson(rows, pretty, depth))
Ok(FlatJson(rows, pretty, depth, vec![]))
}

#[cfg(test)]
Expand Down
9 changes: 9 additions & 0 deletions src/jless.help
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
37 changes: 37 additions & 0 deletions src/viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ pub enum Action {
ExpandNodeAndSiblings,
DeepExpandNodeAndSiblings,

HideItem,
UndoHideItem,
UndoHideItemAll,

ToggleMode,

ResizeViewerDimensions(TTYDimensions),
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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() {
Expand Down