diff --git a/Cargo.lock b/Cargo.lock index 94c7aa1..1da20b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "ahash" version = "0.8.7" @@ -39,12 +54,27 @@ dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", - "anstyle-wincon", + "anstyle-wincon 1.0.1", "colorchoice", "is-terminal", "utf8parse", ] +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon 3.0.3", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.0" @@ -79,6 +109,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.71" @@ -176,6 +216,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.21.7" @@ -263,9 +318,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" [[package]] name = "cfg-if" @@ -290,7 +345,7 @@ version = "4.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" dependencies = [ - "anstream", + "anstream 0.3.2", "anstyle", "bitflags 1.3.2", "clap_lex", @@ -689,6 +744,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + [[package]] name = "h2" version = "0.4.2" @@ -830,6 +891,22 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "human-panic" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c5d0e9120f6bca6120d142c7ede1ba376dd6bf276d69dd3dbe6cbeb7824179" +dependencies = [ + "anstream 0.6.14", + "anstyle", + "backtrace", + "os_info", + "serde", + "serde_derive", + "toml", + "uuid", +] + [[package]] name = "hyper" version = "1.1.0" @@ -918,6 +995,7 @@ dependencies = [ "anyhow", "crossterm", "directories", + "human-panic", "hyperlog-core", "itertools", "ratatui", @@ -977,6 +1055,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.12.1" @@ -1114,6 +1198,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.8" @@ -1215,6 +1308,15 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -1227,6 +1329,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "os_info" +version = "3.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +dependencies = [ + "log", + "serde", + "windows-sys 0.52.0", +] + [[package]] name = "overload" version = "0.1.1" @@ -1514,6 +1627,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustix" version = "0.37.20" @@ -1639,6 +1758,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2251,6 +2379,39 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", +] + [[package]] name = "tower" version = "0.4.13" diff --git a/crates/hyperlog-core/src/log.rs b/crates/hyperlog-core/src/log.rs index fa3720d..fadbbcb 100644 --- a/crates/hyperlog-core/src/log.rs +++ b/crates/hyperlog-core/src/log.rs @@ -13,7 +13,7 @@ pub enum ItemState { Done, } -#[derive(Deserialize, Serialize, PartialEq, Eq, Clone)] +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug)] #[serde(tag = "type")] pub enum GraphItem { #[serde(rename = "user")] diff --git a/crates/hyperlog-tui/Cargo.toml b/crates/hyperlog-tui/Cargo.toml index 1bc5d6e..e43ed9e 100644 --- a/crates/hyperlog-tui/Cargo.toml +++ b/crates/hyperlog-tui/Cargo.toml @@ -2,6 +2,7 @@ name = "hyperlog-tui" version = "0.1.0" edition = "2021" +repository = "https://git.front.kjuulh.io/kjuulh/hyperlog" [dependencies] hyperlog-core.workspace = true @@ -16,6 +17,7 @@ itertools.workspace = true ratatui = "0.26.2" crossterm = { version = "0.27.0", features = ["event-stream"] } directories = "5.0.1" +human-panic = "2.0.0" [dev-dependencies] similar-asserts = "1.5.0" diff --git a/crates/hyperlog-tui/src/components.rs b/crates/hyperlog-tui/src/components.rs index 57b369e..b1d618d 100644 --- a/crates/hyperlog-tui/src/components.rs +++ b/crates/hyperlog-tui/src/components.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, ops::Deref}; +use std::ops::Deref; use anyhow::Result; use hyperlog_core::log::GraphItem; @@ -15,7 +15,7 @@ pub struct GraphExplorer<'a> { pub struct GraphExplorerState<'a> { current_path: Option<&'a str>, - current_postition: Vec, + current_position: Vec, graph: Option, } @@ -26,7 +26,7 @@ impl GraphExplorer<'_> { state, inner: GraphExplorerState::<'_> { current_path: None, - current_postition: Vec::new(), + current_position: Vec::new(), graph: None, }, } @@ -37,7 +37,8 @@ impl GraphExplorer<'_> { .state .querier .get( - "something", + // FIXME: Replace with a setting or default instead, probaby user + "kjuulh", self.inner .current_path .map(|p| p.split('.').collect::>()) @@ -61,11 +62,12 @@ impl GraphExplorer<'_> { /// Choses: 0.1.0.0 else nothing pub(crate) fn move_right(&mut self) -> Result<()> { if let Some(graph) = self.linearize_graph() { - let position_items = &self.inner.current_postition; + tracing::debug!("graph: {:?}", graph); + let position_items = &self.inner.current_position; if let Some(next_item) = graph.next_right(position_items) { - self.inner.current_postition.push(next_item.index); - tracing::trace!("found next item: {:?}", self.inner.current_postition); + self.inner.current_position.push(next_item.index); + tracing::trace!("found next item: {:?}", self.inner.current_position); } } @@ -75,13 +77,13 @@ impl GraphExplorer<'_> { /// Will only incrmeent to the next level /// /// Current: 0.1.0 - /// Available: 0.[0.1.2].0 + /// Available: 0.[0,1,2].0 /// Choses: 0.1 else nothing pub(crate) fn move_left(&mut self) -> Result<()> { - if let Some(last) = self.inner.current_postition.pop() { + if let Some(last) = self.inner.current_position.pop() { tracing::trace!( "found last item: {:?}, popped: {}", - self.inner.current_postition, + self.inner.current_position, last ); } @@ -89,15 +91,147 @@ impl GraphExplorer<'_> { Ok(()) } - pub(crate) fn move_up(&self) -> Result<()> { + /// Will move up if a sibling exists, or up to the most common sibling between sections + /// + /// Current: 0.1.1 + /// Available: 0.[0.[0,1],1.[0,1]] + /// Chose: 0.1.0 again 0.0 We don't choose a subitem in the next three instead we just find the most common sibling + pub(crate) fn move_up(&mut self) -> Result<()> { + if let Some(graph) = self.linearize_graph() { + let position_items = &self.inner.current_position; + + if let Some(next_item) = graph.next_up(position_items) { + self.inner.current_position = next_item; + tracing::trace!("found next up: {:?}", self.inner.current_position) + } + } + Ok(()) } - pub(crate) fn move_down(&self) -> Result<()> { + /// Will move down if a sibling exists, or down to the most common sibling between sections + /// + /// Current: 0.0.0 + /// Available: 0.[0.[0,1],1.[0,1]] + /// Chose: 0.0.1 again 0.1 + pub(crate) fn move_down(&mut self) -> Result<()> { + if let Some(graph) = self.linearize_graph() { + let position_items = &self.inner.current_position; + + if let Some(next_item) = graph.next_down(position_items) { + self.inner.current_position = next_item; + tracing::trace!("found next down: {:?}", self.inner.current_position) + } + } + Ok(()) } } +trait RenderGraph { + fn render_graph(&self, items: &[usize]) -> Vec; + fn render_graph_spans(&self, items: &[usize]) -> Vec>; +} + +impl RenderGraph for MovementGraph { + /// render_graph takes each level of items, renders them, and finally renders a strongly set selector for the current item the user is on + /// This is done from buttom up, and composed via. string padding + fn render_graph(&self, items: &[usize]) -> Vec { + // Gets the inner content of the strings + + let mut lines = Vec::new(); + + for item in &self.items { + match items.split_first().map(|(first, rest)| { + if item.index == *first { + (true, rest) + } else { + (false, rest) + } + }) { + Some((true, rest)) => { + if rest.is_empty() { + lines + .push(Line::raw(format!("- {}", item.name)).style(Style::new().bold())); + } else { + lines.push(Line::raw(format!("- {}", item.name))); + } + + lines.push("".into()); + + let embedded_sections = item.values.render_graph_spans(rest); + for section in &embedded_sections { + let mut line = vec![Span::raw(" ")]; + line.extend_from_slice(section); + lines.push(Line::from(line)); + } + } + _ => { + lines.push(Line::raw(format!("- {}", item.name))); + + lines.push("".into()); + + let embedded_sections = item.values.render_graph_spans(&[]); + for section in &embedded_sections { + let mut line = vec![Span::raw(" ")]; + line.extend_from_slice(section); + lines.push(Line::from(line)); + } + } + } + } + + lines + } + + fn render_graph_spans(&self, items: &[usize]) -> Vec> { + let mut lines = Vec::new(); + + for item in &self.items { + match items.split_first().map(|(first, rest)| { + if item.index == *first { + (true, rest) + } else { + (false, rest) + } + }) { + Some((true, rest)) => { + let mut line = Vec::new(); + if rest.is_empty() { + line.push(Span::raw(format!("- {}", item.name)).style(Style::new().bold())); + } else { + line.push(Span::raw(format!("- {}", item.name))); + } + + lines.push(line); + lines.push(vec!["".into()]); + + let embedded_sections = item.values.render_graph_spans(rest); + for section in &embedded_sections { + let mut line = vec![Span::raw(" ")]; + line.extend_from_slice(section); + lines.push(line); + } + } + _ => { + lines.push(vec![Span::raw(format!("- {}", item.name))]); + + lines.push(vec!["".into()]); + + let embedded_sections = item.values.render_graph_spans(&[]); + for section in &embedded_sections { + let mut line = vec![Span::raw(" ")]; + line.extend_from_slice(section); + lines.push(line); + } + } + } + } + + lines + } +} + impl<'a> StatefulWidget for GraphExplorer<'a> { type State = GraphExplorerState<'a>; @@ -106,17 +240,10 @@ impl<'a> StatefulWidget for GraphExplorer<'a> { let height = height as usize; if let Some(graph) = &state.graph { - if let Ok(graph) = serde_json::to_string_pretty(graph) { - let lines = graph - .split('\n') - .take(height) - .map(Line::raw) - .collect::>(); - - let para = Paragraph::new(lines); - - para.render(area, buf); - } + let movement_graph: MovementGraph = graph.clone().into(); + let lines = movement_graph.render_graph(&state.current_position); + let para = Paragraph::new(lines); + para.render(area, buf); } } } @@ -143,6 +270,53 @@ impl MovementGraph { None => self.items.first().cloned(), } } + + fn next_up(&self, items: &[usize]) -> Option> { + match items.split_last() { + Some((0, _)) => None, + Some((current_index, rest)) => { + let mut vec = rest.to_vec(); + vec.push(current_index - 1); + + Some(vec) + } + // May need to reduce this to an Some(Vec::default()) instead + //None => Some(self.items.iter().map(|i| i.index).collect_vec()), + None => None, + } + } + + fn next_down(&self, items: &[usize]) -> Option> { + match items.split_last() { + Some((current_index, rest)) => { + if let Some(current_item) = self.get_graph_item(rest) { + if *current_index + 1 < current_item.items.len() { + let mut vec = rest.to_vec(); + vec.push(current_index + 1); + + Some(vec) + } else { + None + } + } else { + None + } + } + // May need to reduce this to an Some(Vec::default()) instead + //None => Some(self.items.iter().map(|i| i.index).collect_vec()), + None => None, + } + } + + fn get_graph_item(&self, items: &[usize]) -> Option<&MovementGraph> { + match items.split_first() { + Some((first, rest)) => match self.items.get(*first).map(|s| &s.values) { + Some(next_graph) => next_graph.get_graph_item(rest), + None => Some(self), + }, + None => Some(self), + } + } } impl From> for MovementGraph { @@ -259,7 +433,7 @@ mod test { MovementGraphItem { index: 0, name: "00".into(), - values: MovementGraph::default() + values: MovementGraph::default(), }, MovementGraphItem { index: 1, @@ -286,4 +460,234 @@ mod test { actual ); } + + #[test] + fn test_get_graph_item() -> anyhow::Result<()> { + let graph = MovementGraph { + items: vec![ + MovementGraphItem { + index: 0, + name: "0".into(), + values: MovementGraph { + items: vec![ + MovementGraphItem { + index: 0, + name: "0".into(), + values: MovementGraph::default(), + }, + MovementGraphItem { + index: 1, + name: "0".into(), + values: MovementGraph::default(), + }, + ], + }, + }, + MovementGraphItem { + index: 1, + name: "0".into(), + values: MovementGraph { + items: vec![ + MovementGraphItem { + index: 0, + name: "0".into(), + values: MovementGraph::default(), + }, + MovementGraphItem { + index: 1, + name: "0".into(), + values: MovementGraph::default(), + }, + MovementGraphItem { + index: 2, + name: "0".into(), + values: MovementGraph::default(), + }, + ], + }, + }, + MovementGraphItem { + index: 2, + name: "0".into(), + values: MovementGraph { + items: vec![ + MovementGraphItem { + index: 0, + name: "0".into(), + values: MovementGraph::default(), + }, + MovementGraphItem { + index: 1, + name: "0".into(), + values: MovementGraph::default(), + }, + ], + }, + }, + ], + }; + + let actual_default = graph.get_graph_item(&[]); + assert_eq!(Some(&graph), actual_default); + + let actual_first = graph.get_graph_item(&[0]); + assert_eq!(graph.items.first().map(|i| &i.values), actual_first); + + let actual_second = graph.get_graph_item(&[1]); + assert_eq!(graph.items.get(1).map(|i| &i.values), actual_second); + + let actual_nested = graph.get_graph_item(&[0, 0]); + assert_eq!( + graph + .items + .first() + .and_then(|i| i.values.items.first()) + .map(|i| &i.values), + actual_nested + ); + + let actual_nested = graph.get_graph_item(&[0, 1]); + assert_eq!( + graph + .items + .first() + .and_then(|i| i.values.items.get(1)) + .map(|i| &i.values), + actual_nested + ); + + let actual_nested = graph.get_graph_item(&[1, 2]); + assert_eq!( + graph + .items + .get(1) + .and_then(|i| i.values.items.get(2)) + .map(|i| &i.values), + actual_nested + ); + + Ok(()) + } + + #[test] + fn can_next_down() -> anyhow::Result<()> { + let graph = MovementGraph { + items: vec![ + MovementGraphItem { + index: 0, + name: "0".into(), + values: MovementGraph { + items: vec![MovementGraphItem { + index: 0, + name: "0".into(), + values: MovementGraph::default(), + }], + }, + }, + MovementGraphItem { + index: 1, + name: "1".into(), + values: MovementGraph { + items: vec![ + MovementGraphItem { + index: 0, + name: "0".into(), + values: MovementGraph::default(), + }, + MovementGraphItem { + index: 1, + name: "1".into(), + values: MovementGraph::default(), + }, + ], + }, + }, + MovementGraphItem { + index: 2, + name: "2".into(), + values: MovementGraph { + items: vec![ + MovementGraphItem { + index: 0, + name: "0".into(), + values: MovementGraph::default(), + }, + MovementGraphItem { + index: 1, + name: "1".into(), + values: MovementGraph::default(), + }, + MovementGraphItem { + index: 2, + name: "2".into(), + values: MovementGraph::default(), + }, + ], + }, + }, + ], + }; + + let actual = graph.next_down(&[]); + assert_eq!(None, actual); + + let actual = graph.next_down(&[0]); + assert_eq!(Some(vec![1]), actual); + + let actual = graph.next_down(&[1]); + assert_eq!(Some(vec![2]), actual); + + let actual = graph.next_down(&[2]); + assert_eq!(None, actual); + + let graph = MovementGraph { + items: vec![ + MovementGraphItem { + index: 0, + name: "other".into(), + values: MovementGraph { + items: vec![MovementGraphItem { + index: 0, + name: "other".into(), + values: MovementGraph { + items: vec![MovementGraphItem { + index: 0, + name: "other".into(), + values: MovementGraph { items: vec![] }, + }], + }, + }], + }, + }, + MovementGraphItem { + index: 1, + name: "some".into(), + values: MovementGraph { items: vec![] }, + }, + MovementGraphItem { + index: 2, + name: "something".into(), + values: MovementGraph { + items: vec![ + MovementGraphItem { + index: 0, + name: "else".into(), + values: MovementGraph { items: vec![] }, + }, + MovementGraphItem { + index: 1, + name: "third".into(), + values: MovementGraph { items: vec![] }, + }, + ], + }, + }, + ], + }; + + let actual = graph.next_down(&[0]); + assert_eq!(Some(vec![1]), actual); + + Ok(()) + } } diff --git a/crates/hyperlog-tui/src/logging.rs b/crates/hyperlog-tui/src/logging.rs index 5a6fd44..252eabc 100644 --- a/crates/hyperlog-tui/src/logging.rs +++ b/crates/hyperlog-tui/src/logging.rs @@ -47,12 +47,9 @@ pub fn initialize_panic_handler() -> anyhow::Result<()> { #[cfg(not(debug_assertions))] { use human_panic::{handle_dump, print_msg, Metadata}; - let meta = Metadata { - version: env!("CARGO_PKG_VERSION").into(), - name: env!("CARGO_PKG_NAME").into(), - authors: env!("CARGO_PKG_AUTHORS").replace(':', ", ").into(), - homepage: env!("CARGO_PKG_HOMEPAGE").into(), - }; + let meta = Metadata::new(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")) + .authors(env!("CARGO_PKG_AUTHORS").replace(':', ", ")) + .homepage(env!("CARGO_PKG_HOMEPAGE")); let file_path = handle_dump(&meta, panic_info); // prints human-panic message diff --git a/crates/hyperlog/Cargo.toml b/crates/hyperlog/Cargo.toml index a289125..3bade87 100644 --- a/crates/hyperlog/Cargo.toml +++ b/crates/hyperlog/Cargo.toml @@ -2,6 +2,7 @@ name = "hyperlog" version = "0.1.0" edition = "2021" +repository = "https://git.front.kjuulh.io/kjuulh/hyperlog" [dependencies] hyperlog-core.workspace = true