use std::sync::Arc;
use std::time::Duration;
use rich::measure::Measurement;
use rich::{Console, ConsoleOptions, Renderable, Segment};
use rich_ext::data::{copy_text, display_value, Explorer, Node, Path, PathSegment, Value};
use crate::clipboard;
use crate::component::{Component, Context, Flow, View};
use crate::components::{PreviewLayout, Theme, TreeSelect};
use crate::event::{Event, Key};
use crate::item::{Item, Preview};
use crate::keymap::{keys, Keymap};
use crate::policy::{LineIo, NotInteractive};
pub fn explore_keymap() -> Keymap {
Keymap::new("explore").bind("copy-value", keys("alt+y"), "copy the value")
}
pub fn json_path(path: &Path) -> String {
let shown = path.to_string();
if shown.is_empty() {
"$".into()
} else if shown.starts_with('[') {
format!("${shown}")
} else {
format!("$.{shown}")
}
}
struct Shape {
root: Arc<Node>,
parents: Vec<Option<usize>>,
positions: Vec<usize>,
}
impl Shape {
fn route(&self, index: usize) -> Vec<usize> {
let mut route = vec![self.positions[index]];
let mut at = self.parents[index];
while let Some(parent) = at {
route.push(self.positions[parent]);
at = self.parents[parent];
}
route.pop();
route.reverse();
route
}
fn node(&self, index: usize) -> (Path, &Node) {
let mut node: &Node = &self.root;
let mut path = Path::root();
for position in self.route(index) {
let (segment, child) = match &node.value {
Value::Seq(items) => (PathSegment::Index(position), &items[position]),
Value::Map(entries) => {
let (key, child) = &entries[position];
(PathSegment::Key(key.clone()), child)
}
_ => unreachable!("a route only steps into containers"),
};
path.push(segment);
node = child;
}
(path, node)
}
fn path(&self, index: usize) -> Path {
self.node(index).0
}
}
fn name(segment: Option<&PathSegment>) -> String {
match segment {
None => "$".to_string(),
Some(PathSegment::Key(key)) => key.clone(),
Some(PathSegment::Index(index)) => format!("[{index}]"),
}
}
fn walk<'a>(root: &'a Node, mut visit: impl FnMut(&[PathSegment], usize, &'a Node)) {
visit(&[], 0, root);
let mut segments: Vec<PathSegment> = Vec::new();
let mut stack: Vec<(&'a Node, usize)> = vec![(root, 0)];
while let Some((node, next)) = stack.last_mut() {
let position = *next;
let child = match &node.value {
Value::Seq(items) => items
.get(position)
.map(|child| (PathSegment::Index(position), child)),
Value::Map(entries) => entries
.get(position)
.map(|(key, child)| (PathSegment::Key(key.clone()), child)),
_ => None,
};
let Some((segment, child)) = child else {
stack.pop();
segments.pop();
continue;
};
*next += 1;
segments.push(segment);
visit(&segments, position, child);
stack.push((child, 0));
}
}
struct Subtree {
shape: Arc<Shape>,
index: usize,
depth: usize,
}
impl Subtree {
fn view(&self, rows: usize) -> Explorer<'_> {
let (path, node) = self.shape.node(self.index);
Explorer::new(node)
.max_depth(self.depth)
.max_length(rows.max(1))
.root_label(name(path.last()))
}
}
impl Renderable for Subtree {
fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
self.view(options.max_height).rich_render(console, options)
}
fn measure(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
self.view(options.max_height).measure(console, options)
}
}
pub struct DataExplorer {
tree: TreeSelect<usize>,
shape: Arc<Shape>,
keymap: Keymap,
}
impl DataExplorer {
pub fn new(prompt: impl Into<String>, node: Node) -> DataExplorer {
let root = Arc::new(node);
let mut nodes: Vec<(usize, Item<usize>)> = Vec::new();
let mut crumbs = Vec::new();
let mut parents = Vec::new();
let mut positions = Vec::new();
let mut open: Vec<usize> = Vec::new();
walk(&root, |segments, position, node| {
let depth = segments.len();
let name = name(segments.last());
let value = display_value(node);
let label = if value.is_empty() {
name.clone()
} else if depth == 0 {
format!("{name} {value}")
} else {
format!("{name}: {value}")
};
let index = nodes.len();
open.truncate(depth);
parents.push(open.last().copied());
open.push(index);
positions.push(position);
nodes.push((depth, Item::new(index, label)));
crumbs.push(name);
});
let shape = Arc::new(Shape {
root,
parents,
positions,
});
for (index, (_, item)) in nodes.iter_mut().enumerate() {
item.preview = Some(Preview::Renderable(Arc::new(Subtree {
shape: Arc::clone(&shape),
index,
depth: 3,
})));
}
let paths = Arc::clone(&shape);
let mut tree = TreeSelect::new(prompt, nodes)
.crumbs(crumbs)
.paths_with(move |index| json_path(&paths.path(index)))
.breadcrumbs(true)
.fold_below(1);
tree.set_hints(Some("←→ fold · ctrl+y path · alt+y value".into()));
DataExplorer {
tree,
shape,
keymap: explore_keymap(),
}
}
pub fn icons(mut self, icon: impl Fn(&Path, &Node) -> Option<rich::Text>) -> Self {
let mut icons = Vec::new();
walk(&self.shape.root, |segments, _, node| {
icons.push(icon(&Path::from(segments.to_vec()), node));
});
self.tree.set_icons(icons);
self
}
pub fn height(mut self, rows: usize) -> Self {
self.tree = self.tree.height(rows);
self
}
pub fn preview(mut self, layout: PreviewLayout) -> Self {
self.tree = self.tree.preview(layout);
self
}
pub fn theme(mut self, theme: Theme) -> Self {
self.tree = self.tree.theme(theme);
self
}
pub fn query(mut self, query: impl Into<String>) -> Self {
self.tree = self.tree.query(query);
self
}
pub fn fold_below(mut self, depth: usize) -> Self {
self.tree = self.tree.fold_below(depth);
self
}
pub fn with_mouse(mut self, on: bool) -> Self {
self.tree = self.tree.with_mouse(on);
self
}
pub fn rebind(mut self, action: &str, keys: impl IntoIterator<Item = Key>) -> Self {
self.keymap.rebind(action, keys);
self
}
pub fn document(&self) -> &Node {
&self.shape.root
}
pub fn focused(&self) -> Option<Path> {
self.tree.focused().map(|index| self.shape.path(index))
}
pub fn tree(&self) -> &TreeSelect<usize> {
&self.tree
}
fn copy_value(&mut self) {
let Some(index) = self.tree.focused() else {
return;
};
let result = clipboard::copy(copy_text(self.shape.node(index).1));
self.tree
.set_status(Some(clipboard::report("value", &result)));
}
}
impl Component for DataExplorer {
type Output = Path;
fn keymap(&self) -> Keymap {
let mut keymap = self.keymap.clone();
keymap.extend(self.tree.keymap());
keymap
}
fn handle(&mut self, event: &Event, context: &Context<'_>) -> Flow<Path> {
if let Some(key) = event.key() {
if self.keymap.is(key, "copy-value") && self.tree.select().menu().is_none() {
self.tree.set_status(None);
self.copy_value();
return Flow::Continue;
}
}
match self.tree.handle(event, context) {
Flow::Done(index) => Flow::Done(self.shape.path(index)),
Flow::Continue => Flow::Continue,
Flow::Cancel => Flow::Cancel,
Flow::Ignored => Flow::Ignored,
Flow::Handoff(command) => Flow::Handoff(command),
}
}
fn render(&self, context: &Context<'_>) -> View {
self.tree.render(context)
}
fn tick(&self) -> Option<Duration> {
self.tree.tick()
}
fn mouse(&self) -> bool {
self.tree.mouse()
}
fn prompt(&mut self, io: &mut dyn LineIo) -> Result<Option<Path>, NotInteractive> {
Ok(self.tree.prompt(io)?.map(|index| self.shape.path(index)))
}
}