From 579c01d6f74ca39a41a42501839f76057df0c7c4 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sun, 15 Dec 2024 14:39:49 +0900 Subject: [PATCH 01/44] bump up version to v0.6.0 --- README.md | 2 +- promkit/Cargo.toml | 2 +- promkit/src/lib.rs | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 9b03d022..28e289c3 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Put the package in your `Cargo.toml`. ```toml [dependencies] -promkit = "0.5.1" +promkit = "0.6.0" ``` ## Features diff --git a/promkit/Cargo.toml b/promkit/Cargo.toml index 81fb3ae4..b73096a9 100644 --- a/promkit/Cargo.toml +++ b/promkit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "promkit" -version = "0.5.1" +version = "0.6.0" authors = ["ynqa "] edition = "2021" description = "A toolkit for building your own interactive command-line tools" diff --git a/promkit/src/lib.rs b/promkit/src/lib.rs index 72922934..0b46234e 100644 --- a/promkit/src/lib.rs +++ b/promkit/src/lib.rs @@ -11,7 +11,7 @@ //! //! ```toml //! [dependencies] -//! promkit = "0.5.1" +//! promkit = "0.6.0" //! ``` //! //! ## Features @@ -19,13 +19,13 @@ //! - Support cross-platform both UNIX and Windows owing to [crossterm](https://github.com/crossterm-rs/crossterm) //! - Various building methods //! - Preset; Support for quickly setting up a UI by providing simple parameters. -//! - [Readline](https://github.com/ynqa/promkit/tree/v0.5.1#readline) -//! - [Confirm](https://github.com/ynqa/promkit/tree/v0.5.1#confirm) -//! - [Password](https://github.com/ynqa/promkit/tree/v0.5.1#password) -//! - [Select](https://github.com/ynqa/promkit/tree/v0.5.1#select) -//! - [QuerySelect](https://github.com/ynqa/promkit/tree/v0.5.1#queryselect) -//! - [Checkbox](https://github.com/ynqa/promkit/tree/v0.5.1#checkbox) -//! - [Tree](https://github.com/ynqa/promkit/tree/v0.5.1#tree) +//! - [Readline](https://github.com/ynqa/promkit/tree/v0.6.0#readline) +//! - [Confirm](https://github.com/ynqa/promkit/tree/v0.6.0#confirm) +//! - [Password](https://github.com/ynqa/promkit/tree/v0.6.0#password) +//! - [Select](https://github.com/ynqa/promkit/tree/v0.6.0#select) +//! - [QuerySelect](https://github.com/ynqa/promkit/tree/v0.6.0#queryselect) +//! - [Checkbox](https://github.com/ynqa/promkit/tree/v0.6.0#checkbox) +//! - [Tree](https://github.com/ynqa/promkit/tree/v0.6.0#tree) //! - Combining various UI components. //! - They are provided with the same interface, allowing users to choose and //! assemble them according to their preferences. @@ -39,7 +39,7 @@ //! //! ## Examples/Demos //! -//! See [here](https://github.com/ynqa/promkit/tree/v0.5.1#examplesdemos) +//! See [here](https://github.com/ynqa/promkit/tree/v0.6.0#examplesdemos) //! //! ## Why *promkit*? //! From af0e33d07282b735b1a3495ab5936c5f96c19b70 Mon Sep 17 00:00:00 2001 From: ynqa Date: Thu, 12 Dec 2024 22:27:11 +0900 Subject: [PATCH 02/44] init: jsonz to manage serde_json::Value as viewable --- promkit/src/jsonz.rs | 1 + promkit/src/lib.rs | 1 + 2 files changed, 2 insertions(+) create mode 100644 promkit/src/jsonz.rs diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/promkit/src/jsonz.rs @@ -0,0 +1 @@ + diff --git a/promkit/src/lib.rs b/promkit/src/lib.rs index 0b46234e..5d437dd5 100644 --- a/promkit/src/lib.rs +++ b/promkit/src/lib.rs @@ -128,6 +128,7 @@ pub use serde_json; mod core; pub use core::*; pub mod grapheme; +pub mod jsonz; pub mod pane; pub mod preset; pub mod style; From ab9b969189fecea884e36d2c100f0b011c794bd7 Mon Sep 17 00:00:00 2001 From: ynqa Date: Thu, 12 Dec 2024 23:53:01 +0900 Subject: [PATCH 03/44] chore: define `Row` for flatjson --- promkit/src/jsonz.rs | 60 +++++++++++++++++++++++++++++++++++++ promkit/tests/jsonz_test.rs | 9 ++++++ 2 files changed, 69 insertions(+) create mode 100644 promkit/tests/jsonz_test.rs diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index 8b137891..d76f3474 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -1 +1,61 @@ +#[derive(Clone, Debug, PartialEq)] +pub enum ContainerType { + Object, + Array, +} +impl ContainerType { + pub fn open_str(&self) -> &'static str { + match self { + ContainerType::Object => "{", + ContainerType::Array => "[", + } + } + + pub fn close_str(&self) -> &'static str { + match self { + ContainerType::Object => "}", + ContainerType::Array => "]", + } + } + + pub fn collapsed_preview(&self) -> &'static str { + match self { + ContainerType::Object => "{…}", + ContainerType::Array => "[…]", + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Value { + Null, + Boolean(bool), + Number(serde_json::Number), + String(String), + Empty { + typ: ContainerType, + }, + Open { + typ: ContainerType, + collapsed: bool, + close_index: usize, + }, + Close { + typ: ContainerType, + collapsed: bool, + open_index: usize, + }, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Row { + pub depth: usize, + pub k: Option, + pub v: Value, +} + +pub fn create_rows>(iter: T) -> Vec { + let mut rows = vec![]; + rows +} diff --git a/promkit/tests/jsonz_test.rs b/promkit/tests/jsonz_test.rs new file mode 100644 index 00000000..e83f94eb --- /dev/null +++ b/promkit/tests/jsonz_test.rs @@ -0,0 +1,9 @@ + +#[cfg(test)] +mod tests { + use promkit::jsonz::*; + + mod create_rows { + use super::*; + } +} From dc826d592ac8daf3d8c6cae423dff4c6666caf7b Mon Sep 17 00:00:00 2001 From: ynqa Date: Fri, 13 Dec 2024 00:19:15 +0900 Subject: [PATCH 04/44] chore: define tests for `create_rows` in advance --- promkit/tests/jsonz_create_rows_test.rs | 443 ++++++++++++++++++++++++ promkit/tests/jsonz_test.rs | 9 - 2 files changed, 443 insertions(+), 9 deletions(-) create mode 100644 promkit/tests/jsonz_create_rows_test.rs delete mode 100644 promkit/tests/jsonz_test.rs diff --git a/promkit/tests/jsonz_create_rows_test.rs b/promkit/tests/jsonz_create_rows_test.rs new file mode 100644 index 00000000..8ea35193 --- /dev/null +++ b/promkit/tests/jsonz_create_rows_test.rs @@ -0,0 +1,443 @@ +#[cfg(test)] +mod create_rows { + use std::str::FromStr; + + use promkit::jsonz::*; + use serde_json::Deserializer; + + #[test] + fn test_empty_containers() { + let inputs = Deserializer::from_str( + r#" + {} + [] + "#, + ) + .into_iter::() + .filter_map(serde_json::Result::ok); + + let rows = create_rows(inputs); + assert_eq!(rows.len(), 2); + + assert_eq!( + rows[0], + Row { + depth: 0, + k: None, + v: Value::Empty { + typ: ContainerType::Object + }, + } + ); + + assert_eq!( + rows[1], + Row { + depth: 0, + k: None, + v: Value::Empty { + typ: ContainerType::Array + }, + } + ); + } + + #[test] + fn test_nested_object() { + let input = serde_json::Value::from_str( + r#" + { + "a": { + "b": { + "c": "value" + } + } + } + "#, + ) + .unwrap(); + + let rows = create_rows([input]); + + assert_eq!( + rows[0], + Row { + depth: 0, + k: None, + v: Value::Open { + typ: ContainerType::Object, + collapsed: false, + close_index: 6, + }, + } + ); + + assert_eq!( + rows[1], + Row { + depth: 1, + k: Some("a".to_string()), + v: Value::Open { + typ: ContainerType::Object, + collapsed: false, + close_index: 5, + }, + } + ); + + assert_eq!( + rows[2], + Row { + depth: 2, + k: Some("b".to_string()), + v: Value::Open { + typ: ContainerType::Object, + collapsed: false, + close_index: 4, + }, + } + ); + + assert_eq!( + rows[3], + Row { + depth: 3, + k: Some("c".to_string()), + v: Value::String("value".to_string()), + } + ); + + assert_eq!( + rows[4], + Row { + depth: 2, + k: None, + v: Value::Close { + typ: ContainerType::Object, + collapsed: false, + open_index: 2, + }, + } + ); + + assert_eq!( + rows[5], + Row { + depth: 1, + k: None, + v: Value::Close { + typ: ContainerType::Object, + collapsed: false, + open_index: 1, + }, + } + ); + + assert_eq!( + rows[6], + Row { + depth: 0, + k: None, + v: Value::Close { + typ: ContainerType::Object, + collapsed: false, + open_index: 0, + }, + } + ); + } + + #[test] + fn test_nested_array() { + let input = serde_json::Value::from_str( + r#" + [ + [ + [ + 1, + 2, + 3 + ] + ] + ] + "#, + ) + .unwrap(); + + let rows = create_rows([input]); + + assert_eq!( + rows[0], + Row { + depth: 0, + k: None, + v: Value::Open { + typ: ContainerType::Array, + collapsed: false, + close_index: 8, + }, + } + ); + + assert_eq!( + rows[1], + Row { + depth: 1, + k: None, + v: Value::Open { + typ: ContainerType::Array, + collapsed: false, + close_index: 7, + }, + } + ); + + assert_eq!( + rows[2], + Row { + depth: 2, + k: None, + v: Value::Open { + typ: ContainerType::Array, + collapsed: false, + close_index: 6, + }, + } + ); + + for (i, num) in [1, 2, 3].iter().enumerate() { + assert_eq!( + rows[3 + i], + Row { + depth: 3, + k: None, + v: Value::Number(serde_json::Number::from(*num)), + } + ); + } + + assert_eq!( + rows[6], + Row { + depth: 2, + k: None, + v: Value::Close { + typ: ContainerType::Array, + collapsed: false, + open_index: 2, + }, + } + ); + + assert_eq!( + rows[7], + Row { + depth: 1, + k: None, + v: Value::Close { + typ: ContainerType::Array, + collapsed: false, + open_index: 1, + }, + } + ); + + assert_eq!( + rows[8], + Row { + depth: 0, + k: None, + v: Value::Close { + typ: ContainerType::Array, + collapsed: false, + open_index: 0, + }, + } + ); + } + + #[test] + fn test_mixed_containers() { + let input = serde_json::Value::from_str( + r#" + { + "array": [ + { + "key": "value" + }, + [ + 1, + 2, + 3 + ] + ], + "object": { + "nested": true + } + } + "#, + ) + .unwrap(); + + let rows = create_rows([input]); + + assert_eq!( + rows[0], + Row { + depth: 0, + k: None, + v: Value::Open { + typ: ContainerType::Object, + collapsed: false, + close_index: 14, + }, + } + ); + + assert_eq!( + rows[1], + Row { + depth: 1, + k: Some("array".to_string()), + v: Value::Open { + typ: ContainerType::Array, + collapsed: false, + close_index: 10, + }, + } + ); + + assert_eq!( + rows[2], + Row { + depth: 2, + k: None, + v: Value::Open { + typ: ContainerType::Object, + collapsed: false, + close_index: 4, + }, + } + ); + + assert_eq!( + rows[3], + Row { + depth: 3, + k: Some("key".to_string()), + v: Value::String("value".to_string()), + } + ); + + assert_eq!( + rows[4], + Row { + depth: 2, + k: None, + v: Value::Close { + typ: ContainerType::Object, + collapsed: false, + open_index: 2, + }, + } + ); + + assert_eq!( + rows[5], + Row { + depth: 2, + k: None, + v: Value::Open { + typ: ContainerType::Array, + collapsed: false, + close_index: 9, + }, + } + ); + + for (i, num) in [1, 2, 3].iter().enumerate() { + assert_eq!( + rows[6 + i], + Row { + depth: 3, + k: None, + v: Value::Number(serde_json::Number::from(*num)), + } + ); + } + + assert_eq!( + rows[9], + Row { + depth: 2, + k: None, + v: Value::Close { + typ: ContainerType::Array, + collapsed: false, + open_index: 5, + }, + } + ); + + assert_eq!( + rows[10], + Row { + depth: 1, + k: None, + v: Value::Close { + typ: ContainerType::Array, + collapsed: false, + open_index: 1, + }, + } + ); + + assert_eq!( + rows[11], + Row { + depth: 1, + k: Some("object".to_string()), + v: Value::Open { + typ: ContainerType::Object, + collapsed: false, + close_index: 13, + }, + } + ); + + assert_eq!( + rows[12], + Row { + depth: 2, + k: Some("nested".to_string()), + v: Value::Boolean(true), + } + ); + + assert_eq!( + rows[13], + Row { + depth: 1, + k: None, + v: Value::Close { + typ: ContainerType::Object, + collapsed: false, + open_index: 11, + }, + } + ); + + assert_eq!( + rows[14], + Row { + depth: 0, + k: None, + v: Value::Close { + typ: ContainerType::Object, + collapsed: false, + open_index: 0, + }, + } + ); + } +} diff --git a/promkit/tests/jsonz_test.rs b/promkit/tests/jsonz_test.rs deleted file mode 100644 index e83f94eb..00000000 --- a/promkit/tests/jsonz_test.rs +++ /dev/null @@ -1,9 +0,0 @@ - -#[cfg(test)] -mod tests { - use promkit::jsonz::*; - - mod create_rows { - use super::*; - } -} From 609e3d0b866126f934dd1481ff24437667931303 Mon Sep 17 00:00:00 2001 From: ynqa Date: Fri, 13 Dec 2024 00:39:28 +0900 Subject: [PATCH 05/44] feat: create_rows --- promkit/src/jsonz.rs | 141 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 1 deletion(-) diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index d76f3474..66c7c871 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -55,7 +55,146 @@ pub struct Row { pub v: Value, } +fn process_value( + value: &serde_json::Value, + rows: &mut Vec, + depth: usize, + key: Option, +) -> usize { + match value { + serde_json::Value::Null => { + rows.push(Row { + depth, + k: key, + v: Value::Null, + }); + rows.len() - 1 + } + serde_json::Value::Bool(b) => { + rows.push(Row { + depth, + k: key, + v: Value::Boolean(*b), + }); + rows.len() - 1 + } + serde_json::Value::Number(n) => { + rows.push(Row { + depth, + k: key, + v: Value::Number(n.clone()), + }); + rows.len() - 1 + } + serde_json::Value::String(s) => { + rows.push(Row { + depth, + k: key, + v: Value::String(s.clone()), + }); + rows.len() - 1 + } + serde_json::Value::Array(arr) => { + if arr.is_empty() { + rows.push(Row { + depth, + k: key, + v: Value::Empty { + typ: ContainerType::Array, + }, + }); + return rows.len() - 1; + } + + let open_index = rows.len(); + + rows.push(Row { + depth, + k: key, + v: Value::Open { + typ: ContainerType::Array, + collapsed: false, + close_index: 0, + }, + }); + + for value in arr { + process_value(value, rows, depth + 1, None); + } + + let close_index = rows.len(); + rows.push(Row { + depth, + k: None, + v: Value::Close { + typ: ContainerType::Array, + collapsed: false, + open_index, + }, + }); + + rows[open_index].v = Value::Open { + typ: ContainerType::Array, + collapsed: false, + close_index, + }; + + open_index + } + serde_json::Value::Object(obj) => { + if obj.is_empty() { + rows.push(Row { + depth, + k: key, + v: Value::Empty { + typ: ContainerType::Object, + }, + }); + return rows.len() - 1; + } + + let open_index = rows.len(); + + rows.push(Row { + depth, + k: key, + v: Value::Open { + typ: ContainerType::Object, + collapsed: false, + close_index: 0, + }, + }); + + for (key, value) in obj { + process_value(value, rows, depth + 1, Some(key.clone())); + } + + let close_index = rows.len(); + rows.push(Row { + depth, + k: None, + v: Value::Close { + typ: ContainerType::Object, + collapsed: false, + open_index, + }, + }); + + rows[open_index].v = Value::Open { + typ: ContainerType::Object, + collapsed: false, + close_index, + }; + + open_index + } + } +} + pub fn create_rows>(iter: T) -> Vec { - let mut rows = vec![]; + let mut rows = Vec::new(); + for value in iter { + process_value(&value, &mut rows, 0, None); + } rows } From 8fcd743c4280c3ece48accaf6698f454923cc2f6 Mon Sep 17 00:00:00 2001 From: ynqa Date: Fri, 13 Dec 2024 01:05:39 +0900 Subject: [PATCH 06/44] chore: define trait for Vec --- promkit/src/jsonz.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index 66c7c871..7075c97c 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -55,6 +55,16 @@ pub struct Row { pub v: Value, } +pub trait RowOperation { + fn up(&mut self, current: usize) -> usize; +} + +impl RowOperation for Vec { + fn up(&mut self, current: usize) -> usize { + todo!() + } +} + fn process_value( value: &serde_json::Value, rows: &mut Vec, From d770afe18dfc4aa0cde73020cf34cb8f5e5dc34d Mon Sep 17 00:00:00 2001 From: ynqa Date: Fri, 13 Dec 2024 01:32:25 +0900 Subject: [PATCH 07/44] chore: define tests for `up` in advance --- promkit/tests/jsonz_up_test.rs | 51 ++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 promkit/tests/jsonz_up_test.rs diff --git a/promkit/tests/jsonz_up_test.rs b/promkit/tests/jsonz_up_test.rs new file mode 100644 index 00000000..261f37e7 --- /dev/null +++ b/promkit/tests/jsonz_up_test.rs @@ -0,0 +1,51 @@ +#[cfg(test)] +mod up { + use std::str::FromStr; + + use promkit::jsonz::*; + + #[test] + fn test_collapsed_containers() { + let input = serde_json::Value::from_str( + r#" + { + "collapsed_object": { + "key": "value" + }, + "collapsed_array": [ + 1, + 2, + 3 + ] + } + "#, + ) + .unwrap(); + + let mut rows = create_rows([input]); + rows[1].v = Value::Open { + typ: ContainerType::Object, + collapsed: true, + close_index: 3, + }; + rows[3].v = Value::Close { + typ: ContainerType::Object, + collapsed: true, + open_index: 1, + }; + + rows[4].v = Value::Open { + typ: ContainerType::Array, + collapsed: true, + close_index: 8, + }; + rows[8].v = Value::Close { + typ: ContainerType::Array, + collapsed: true, + open_index: 4, + }; + assert_eq!(rows.up(1), 0); + assert_eq!(rows.up(4), 1); + assert_eq!(rows.up(9), 4); + } +} From e26867efddcc8e34c623e6b12004853e31bdb8ca Mon Sep 17 00:00:00 2001 From: ynqa Date: Fri, 13 Dec 2024 01:43:17 +0900 Subject: [PATCH 08/44] feat: up --- promkit/src/jsonz.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index 7075c97c..ff1b9bc4 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -61,7 +61,19 @@ pub trait RowOperation { impl RowOperation for Vec { fn up(&mut self, current: usize) -> usize { - todo!() + if current == 0 { + return 0; + } + + let prev = current - 1; + match &self[prev].v { + Value::Close { + collapsed, + open_index, + .. + } if *collapsed => *open_index, + _ => prev, + } } } From 698058eac4e49e7e233f72f55e93a9883cb00085 Mon Sep 17 00:00:00 2001 From: ynqa Date: Fri, 13 Dec 2024 02:12:59 +0900 Subject: [PATCH 09/44] chore: define tests for `down` in advance --- promkit/src/jsonz.rs | 5 ++++ promkit/tests/jsonz_down_test.rs | 51 ++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 promkit/tests/jsonz_down_test.rs diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index ff1b9bc4..9692c66a 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -57,6 +57,7 @@ pub struct Row { pub trait RowOperation { fn up(&mut self, current: usize) -> usize; + fn down(&mut self, current: usize) -> usize; } impl RowOperation for Vec { @@ -75,6 +76,10 @@ impl RowOperation for Vec { _ => prev, } } + + fn down(&mut self, current: usize) -> usize { + todo!(); + } } fn process_value( diff --git a/promkit/tests/jsonz_down_test.rs b/promkit/tests/jsonz_down_test.rs new file mode 100644 index 00000000..dc54be92 --- /dev/null +++ b/promkit/tests/jsonz_down_test.rs @@ -0,0 +1,51 @@ +#[cfg(test)] +mod down { + use std::str::FromStr; + + use promkit::jsonz::*; + + #[test] + fn test_collapsed_containers() { + let input = serde_json::Value::from_str( + r#" + { + "collapsed_object": { + "key": "value" + }, + "collapsed_array": [ + 1, + 2, + 3 + ] + } + "#, + ) + .unwrap(); + + let mut rows = create_rows([input]); + rows[1].v = Value::Open { + typ: ContainerType::Object, + collapsed: true, + close_index: 3, + }; + rows[3].v = Value::Close { + typ: ContainerType::Object, + collapsed: true, + open_index: 1, + }; + + rows[4].v = Value::Open { + typ: ContainerType::Array, + collapsed: true, + close_index: 8, + }; + rows[8].v = Value::Close { + typ: ContainerType::Array, + collapsed: true, + open_index: 4, + }; + assert_eq!(rows.down(0), 1); + assert_eq!(rows.down(1), 4); + assert_eq!(rows.down(4), 9); + } +} From 90bb91473a812c4dcebdd4fff3551e6955051d38 Mon Sep 17 00:00:00 2001 From: ynqa Date: Fri, 13 Dec 2024 02:19:45 +0900 Subject: [PATCH 10/44] feat: down --- promkit/src/jsonz.rs | 14 +++++++++++++- promkit/tests/jsonz_down_test.rs | 1 + promkit/tests/jsonz_up_test.rs | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index 9692c66a..3c73f545 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -78,7 +78,19 @@ impl RowOperation for Vec { } fn down(&mut self, current: usize) -> usize { - todo!(); + if current >= self.len() - 1 { + return current; + } + + let next = current + 1; + match &self[current].v { + Value::Open { + collapsed, + close_index, + .. + } if *collapsed => *close_index + 1, + _ => next, + } } } diff --git a/promkit/tests/jsonz_down_test.rs b/promkit/tests/jsonz_down_test.rs index dc54be92..10e79aa2 100644 --- a/promkit/tests/jsonz_down_test.rs +++ b/promkit/tests/jsonz_down_test.rs @@ -47,5 +47,6 @@ mod down { assert_eq!(rows.down(0), 1); assert_eq!(rows.down(1), 4); assert_eq!(rows.down(4), 9); + assert_eq!(rows.down(9), 9); } } diff --git a/promkit/tests/jsonz_up_test.rs b/promkit/tests/jsonz_up_test.rs index 261f37e7..98ea7980 100644 --- a/promkit/tests/jsonz_up_test.rs +++ b/promkit/tests/jsonz_up_test.rs @@ -44,6 +44,7 @@ mod up { collapsed: true, open_index: 4, }; + assert_eq!(rows.up(0), 0); assert_eq!(rows.up(1), 0); assert_eq!(rows.up(4), 1); assert_eq!(rows.up(9), 4); From 76ef7ffb7d853e10103ca03bf678b45273d09630 Mon Sep 17 00:00:00 2001 From: ynqa Date: Fri, 13 Dec 2024 02:25:52 +0900 Subject: [PATCH 11/44] fix: do not move down on last collapsed element --- promkit/src/jsonz.rs | 9 ++++++++- promkit/tests/jsonz_down_test.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index 3c73f545..25fed57e 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -88,7 +88,14 @@ impl RowOperation for Vec { collapsed, close_index, .. - } if *collapsed => *close_index + 1, + } if *collapsed => { + let next_pos = close_index + 1; + if next_pos >= self.len() { + current + } else { + next_pos + } + } _ => next, } } diff --git a/promkit/tests/jsonz_down_test.rs b/promkit/tests/jsonz_down_test.rs index 10e79aa2..64099885 100644 --- a/promkit/tests/jsonz_down_test.rs +++ b/promkit/tests/jsonz_down_test.rs @@ -49,4 +49,32 @@ mod down { assert_eq!(rows.down(4), 9); assert_eq!(rows.down(9), 9); } + + #[test] + fn test_down_on_last_collapsed() { + let input = serde_json::Value::from_str( + r#" + [ + 1, + 2, + 3 + ] + "#, + ) + .unwrap(); + + let mut rows = create_rows([input]); + rows[0].v = Value::Open { + typ: ContainerType::Object, + collapsed: true, + close_index: 4, + }; + rows[4].v = Value::Close { + typ: ContainerType::Object, + collapsed: true, + open_index: 0, + }; + + assert_eq!(rows.down(0), 0); + } } From 087c33a6210be231a3bf4e5c55d04a4cb7aa97be Mon Sep 17 00:00:00 2001 From: ynqa Date: Fri, 13 Dec 2024 21:57:01 +0900 Subject: [PATCH 12/44] chore: define tests for `toggle` in advance --- promkit/src/jsonz.rs | 5 + promkit/tests/jsonz_toggle_test.rs | 210 +++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 promkit/tests/jsonz_toggle_test.rs diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index 25fed57e..d59b611b 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -58,6 +58,7 @@ pub struct Row { pub trait RowOperation { fn up(&mut self, current: usize) -> usize; fn down(&mut self, current: usize) -> usize; + fn toggle(&mut self, current: usize) -> bool; } impl RowOperation for Vec { @@ -99,6 +100,10 @@ impl RowOperation for Vec { _ => next, } } + + fn toggle(&mut self, current: usize) -> bool { + todo!() + } } fn process_value( diff --git a/promkit/tests/jsonz_toggle_test.rs b/promkit/tests/jsonz_toggle_test.rs new file mode 100644 index 00000000..88455be7 --- /dev/null +++ b/promkit/tests/jsonz_toggle_test.rs @@ -0,0 +1,210 @@ +#[cfg(test)] +mod toggle { + use std::str::FromStr; + + use promkit::jsonz::*; + + #[test] + fn test_on_open() { + let input = serde_json::Value::from_str( + r#" + { + "object": { + "key": "value" + }, + "array": [ + 1, + 2, + 3 + ] + } + "#, + ) + .unwrap(); + + let mut rows = create_rows([input]); + + rows.toggle(1); + assert_eq!( + rows[1].v, + Value::Open { + typ: ContainerType::Object, + collapsed: true, + close_index: 3 + } + ); + assert_eq!( + rows[3].v, + Value::Close { + typ: ContainerType::Object, + collapsed: true, + open_index: 1 + } + ); + + rows.toggle(4); + assert_eq!( + rows[4].v, + Value::Open { + typ: ContainerType::Array, + collapsed: true, + close_index: 8 + } + ); + assert_eq!( + rows[8].v, + Value::Close { + typ: ContainerType::Array, + collapsed: true, + open_index: 4 + } + ); + + rows.toggle(0); + assert_eq!( + rows[0].v, + Value::Open { + typ: ContainerType::Object, + collapsed: true, + close_index: 9 + } + ); + assert_eq!( + rows[9].v, + Value::Close { + typ: ContainerType::Object, + collapsed: true, + open_index: 0 + } + ); + + rows.toggle(0); + assert_eq!( + rows[0].v, + Value::Open { + typ: ContainerType::Object, + collapsed: false, + close_index: 9 + } + ); + assert_eq!( + rows[9].v, + Value::Close { + typ: ContainerType::Object, + collapsed: false, + open_index: 0 + } + ); + + rows.toggle(4); + assert_eq!( + rows[4].v, + Value::Open { + typ: ContainerType::Array, + collapsed: false, + close_index: 8 + } + ); + assert_eq!( + rows[8].v, + Value::Close { + typ: ContainerType::Array, + collapsed: false, + open_index: 4 + } + ); + + rows.toggle(1); + assert_eq!( + rows[1].v, + Value::Open { + typ: ContainerType::Object, + collapsed: false, + close_index: 3 + } + ); + assert_eq!( + rows[3].v, + Value::Close { + typ: ContainerType::Object, + collapsed: false, + open_index: 1 + } + ); + } + + #[test] + fn test_on_close() { + let input = serde_json::Value::from_str( + r#" + { + "object": { + "key": "value" + }, + "array": [ + 1, + 2, + 3 + ] + } + "#, + ) + .unwrap(); + + let mut rows = create_rows([input]); + + rows.toggle(3); + assert_eq!( + rows[1].v, + Value::Open { + typ: ContainerType::Object, + collapsed: true, + close_index: 3 + } + ); + assert_eq!( + rows[3].v, + Value::Close { + typ: ContainerType::Object, + collapsed: true, + open_index: 1 + } + ); + + rows.toggle(8); + assert_eq!( + rows[4].v, + Value::Open { + typ: ContainerType::Array, + collapsed: true, + close_index: 8 + } + ); + assert_eq!( + rows[8].v, + Value::Close { + typ: ContainerType::Array, + collapsed: true, + open_index: 4 + } + ); + + rows.toggle(9); + assert_eq!( + rows[0].v, + Value::Open { + typ: ContainerType::Object, + collapsed: true, + close_index: 9 + } + ); + assert_eq!( + rows[9].v, + Value::Close { + typ: ContainerType::Object, + collapsed: true, + open_index: 0 + } + ); + } +} From 7ca9d5f4d2765adab535ebc5e1d5a1875ba13719 Mon Sep 17 00:00:00 2001 From: ynqa Date: Fri, 13 Dec 2024 22:00:47 +0900 Subject: [PATCH 13/44] feat: toggle --- promkit/src/jsonz.rs | 50 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index d59b611b..8cd1908a 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -102,7 +102,55 @@ impl RowOperation for Vec { } fn toggle(&mut self, current: usize) -> bool { - todo!() + match &self[current].v { + Value::Open { + typ, + collapsed, + close_index, + } => { + let new_collapsed = !collapsed; + let close_idx = *close_index; + let typ_clone = typ.clone(); + + self[current].v = Value::Open { + typ: typ_clone.clone(), + collapsed: new_collapsed, + close_index: close_idx, + }; + + self[close_idx].v = Value::Close { + typ: typ_clone, + collapsed: new_collapsed, + open_index: current, + }; + + true + } + Value::Close { + typ, + collapsed, + open_index, + } => { + let new_collapsed = !collapsed; + let open_idx = *open_index; + let typ_clone = typ.clone(); + + self[current].v = Value::Close { + typ: typ_clone.clone(), + collapsed: new_collapsed, + open_index: open_idx, + }; + + self[open_idx].v = Value::Open { + typ: typ_clone, + collapsed: new_collapsed, + close_index: current, + }; + + true + } + _ => false, + } } } From c2d044028fad63fa9d957ac555b8855b3160c4cc Mon Sep 17 00:00:00 2001 From: ynqa Date: Fri, 13 Dec 2024 22:25:00 +0900 Subject: [PATCH 14/44] chore: define tests for `extract` in advance --- promkit/src/jsonz.rs | 5 + promkit/tests/jsonz_extract_test.rs | 278 ++++++++++++++++++++++++++++ 2 files changed, 283 insertions(+) create mode 100644 promkit/tests/jsonz_extract_test.rs diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index 8cd1908a..79451fbf 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -59,6 +59,7 @@ pub trait RowOperation { fn up(&mut self, current: usize) -> usize; fn down(&mut self, current: usize) -> usize; fn toggle(&mut self, current: usize) -> bool; + fn extract(&self, current: usize, n: usize) -> Vec; } impl RowOperation for Vec { @@ -152,6 +153,10 @@ impl RowOperation for Vec { _ => false, } } + + fn extract(&self, current: usize, n: usize) -> Vec { + todo!() + } } fn process_value( diff --git a/promkit/tests/jsonz_extract_test.rs b/promkit/tests/jsonz_extract_test.rs new file mode 100644 index 00000000..96d73ffb --- /dev/null +++ b/promkit/tests/jsonz_extract_test.rs @@ -0,0 +1,278 @@ +#[cfg(test)] +mod extract { + use std::str::FromStr; + + use promkit::jsonz::*; + + #[test] + fn test_basic_extract() { + let input = serde_json::Value::from_str( + r#" + { + "a": 1, + "b": 2, + "c": 3 + } + "#, + ) + .unwrap(); + + let rows = create_rows([input]); + + let extracted = rows.extract(0, 2); + assert_eq!(extracted.len(), 2); + assert_eq!( + extracted[0], + Row { + depth: 0, + k: None, + v: Value::Open { + typ: ContainerType::Object, + collapsed: false, + close_index: 4, + }, + } + ); + assert_eq!( + extracted[1], + Row { + depth: 1, + k: Some("a".to_string()), + v: Value::Number(serde_json::Number::from(1)), + } + ); + + let extracted = rows.extract(2, 2); + assert_eq!(extracted.len(), 2); + assert_eq!( + extracted[0], + Row { + depth: 1, + k: Some("b".to_string()), + v: Value::Number(serde_json::Number::from(2)), + } + ); + assert_eq!( + extracted[1], + Row { + depth: 1, + k: Some("c".to_string()), + v: Value::Number(serde_json::Number::from(3)), + } + ); + } + + #[test] + fn test_extract_with_collapsed_open() { + let input = serde_json::Value::from_str( + r#" + { + "object": { + "a": 1, + "b": 2 + }, + "after": "value" + } + "#, + ) + .unwrap(); + + let mut rows = create_rows([input]); + + // { + // "object": {...}, + // "after": "value" + // } + rows.toggle(1); + + let extracted = rows.extract(1, 3); + assert_eq!(extracted.len(), 3); + assert_eq!( + extracted[0], + Row { + depth: 1, + k: Some("object".to_string()), + v: Value::Open { + typ: ContainerType::Object, + collapsed: true, + close_index: 4, + }, + } + ); + assert_eq!( + extracted[1], + Row { + depth: 1, + k: Some("after".to_string()), + v: Value::String("value".to_string()), + } + ); + assert_eq!( + extracted[2], + Row { + depth: 0, + k: None, + v: Value::Close { + typ: ContainerType::Object, + collapsed: false, + open_index: 0, + }, + } + ); + } + + #[test] + fn test_extract_nested_structure() { + let input = serde_json::Value::from_str( + r#" + { + "array": [ + { + "a": 1 + }, + { + "b": 2 + } + ] + } + "#, + ) + .unwrap(); + + let rows = create_rows([input]); + + let extracted = rows.extract(2, 3); + assert_eq!(extracted.len(), 3); + assert_eq!( + extracted[0], + Row { + depth: 2, + k: None, + v: Value::Open { + typ: ContainerType::Object, + collapsed: false, + close_index: 4, + }, + } + ); + assert_eq!( + extracted[1], + Row { + depth: 3, + k: Some("a".to_string()), + v: Value::Number(serde_json::Number::from(1)), + } + ); + assert_eq!( + extracted[2], + Row { + depth: 2, + k: None, + v: Value::Close { + typ: ContainerType::Object, + collapsed: false, + open_index: 2, + }, + } + ); + } + + #[test] + fn test_extract_boundary_cases() { + let input = serde_json::Value::from_str( + r#" + { + "a": 1, + "b": 2 + } + "#, + ) + .unwrap(); + + let rows = create_rows([input]); + + let extracted = rows.extract(0, 10); + assert_eq!(extracted.len(), 4); + + let extracted = rows.extract(3, 2); + assert_eq!(extracted.len(), 1); + + let extracted = rows.extract(10, 1); + assert_eq!(extracted.len(), 0); + } + + #[test] + fn test_extract_complex_nested_collapsed() { + let input = serde_json::Value::from_str( + r#" + { + "obj1": { + "arr1": [ + { + "a": 1, + "obj2": { + "b": 2, + "c": 3 + } + } + ], + "arr2": [ + { + "d": 4, + "obj3": { + "e": 5, + "f": 6 + } + } + ] + }, + "after": "value" + } + "#, + ) + .unwrap(); + + let mut rows = create_rows([input]); + + // { + // "obj1": { + // "arr1": [ + // {...} + // ], + // "arr2": [ + // { + // "d": 4, + // "obj3": {...} + // } + // ] + // }, + // "after": "value" + // } + rows.toggle(3); + rows.toggle(14); + + let extracted = rows.extract(2, 6); + assert_eq!(extracted.len(), 6); + + assert_eq!( + extracted[0], + Row { + depth: 2, + k: Some("arr1".to_string()), + v: Value::Open { + typ: ContainerType::Array, + collapsed: false, + close_index: 10, + }, + } + ); + assert_eq!( + extracted[5], + Row { + depth: 4, + k: Some("d".to_string()), + v: Value::Number(serde_json::Number::from(4)), + } + ); + } +} From adba1d0919a0107d9e5642b44d2a825d14d2a3f5 Mon Sep 17 00:00:00 2001 From: ynqa Date: Fri, 13 Dec 2024 22:36:35 +0900 Subject: [PATCH 15/44] feat: extract --- promkit/src/jsonz.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index 79451fbf..06d3478d 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -155,7 +155,29 @@ impl RowOperation for Vec { } fn extract(&self, current: usize, n: usize) -> Vec { - todo!() + let mut result = Vec::new(); + let mut i = current; + let mut remaining = n; + + while i < self.len() && remaining > 0 { + result.push(self[i].clone()); + remaining -= 1; + + match &self[i].v { + Value::Open { + collapsed: true, + close_index, + .. + } => { + i = *close_index + 1; + } + _ => { + i += 1; + } + } + } + + result } } From 635adc74b7dbf4151ae8da8745488952111d2e96 Mon Sep 17 00:00:00 2001 From: ynqa Date: Fri, 13 Dec 2024 22:42:18 +0900 Subject: [PATCH 16/44] fix: use toggle for up/down testings --- promkit/tests/jsonz_down_test.rs | 33 +++----------------------------- promkit/tests/jsonz_up_test.rs | 22 ++------------------- 2 files changed, 5 insertions(+), 50 deletions(-) diff --git a/promkit/tests/jsonz_down_test.rs b/promkit/tests/jsonz_down_test.rs index 64099885..4745bbc7 100644 --- a/promkit/tests/jsonz_down_test.rs +++ b/promkit/tests/jsonz_down_test.rs @@ -23,27 +23,9 @@ mod down { .unwrap(); let mut rows = create_rows([input]); - rows[1].v = Value::Open { - typ: ContainerType::Object, - collapsed: true, - close_index: 3, - }; - rows[3].v = Value::Close { - typ: ContainerType::Object, - collapsed: true, - open_index: 1, - }; + rows.toggle(1); + rows.toggle(4); - rows[4].v = Value::Open { - typ: ContainerType::Array, - collapsed: true, - close_index: 8, - }; - rows[8].v = Value::Close { - typ: ContainerType::Array, - collapsed: true, - open_index: 4, - }; assert_eq!(rows.down(0), 1); assert_eq!(rows.down(1), 4); assert_eq!(rows.down(4), 9); @@ -64,16 +46,7 @@ mod down { .unwrap(); let mut rows = create_rows([input]); - rows[0].v = Value::Open { - typ: ContainerType::Object, - collapsed: true, - close_index: 4, - }; - rows[4].v = Value::Close { - typ: ContainerType::Object, - collapsed: true, - open_index: 0, - }; + rows.toggle(0); assert_eq!(rows.down(0), 0); } diff --git a/promkit/tests/jsonz_up_test.rs b/promkit/tests/jsonz_up_test.rs index 98ea7980..1b85e9df 100644 --- a/promkit/tests/jsonz_up_test.rs +++ b/promkit/tests/jsonz_up_test.rs @@ -23,27 +23,9 @@ mod up { .unwrap(); let mut rows = create_rows([input]); - rows[1].v = Value::Open { - typ: ContainerType::Object, - collapsed: true, - close_index: 3, - }; - rows[3].v = Value::Close { - typ: ContainerType::Object, - collapsed: true, - open_index: 1, - }; + rows.toggle(1); + rows.toggle(4); - rows[4].v = Value::Open { - typ: ContainerType::Array, - collapsed: true, - close_index: 8, - }; - rows[8].v = Value::Close { - typ: ContainerType::Array, - collapsed: true, - open_index: 4, - }; assert_eq!(rows.up(0), 0); assert_eq!(rows.up(1), 0); assert_eq!(rows.up(4), 1); From f63d0a8b6ef30204c8e06208573eb8ebb96bdbef Mon Sep 17 00:00:00 2001 From: ynqa Date: Fri, 13 Dec 2024 22:49:38 +0900 Subject: [PATCH 17/44] fix: toggle returns open index --- promkit/src/jsonz.rs | 14 +++++++++----- promkit/tests/jsonz_toggle_test.rs | 18 ++++++++++++------ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index 06d3478d..b8d62dd5 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -58,7 +58,7 @@ pub struct Row { pub trait RowOperation { fn up(&mut self, current: usize) -> usize; fn down(&mut self, current: usize) -> usize; - fn toggle(&mut self, current: usize) -> bool; + fn toggle(&mut self, current: usize) -> usize; fn extract(&self, current: usize, n: usize) -> Vec; } @@ -102,7 +102,7 @@ impl RowOperation for Vec { } } - fn toggle(&mut self, current: usize) -> bool { + fn toggle(&mut self, current: usize) -> usize { match &self[current].v { Value::Open { typ, @@ -125,7 +125,7 @@ impl RowOperation for Vec { open_index: current, }; - true + current } Value::Close { typ, @@ -148,9 +148,13 @@ impl RowOperation for Vec { close_index: current, }; - true + if new_collapsed { + open_idx + } else { + current + } } - _ => false, + _ => current, } } diff --git a/promkit/tests/jsonz_toggle_test.rs b/promkit/tests/jsonz_toggle_test.rs index 88455be7..2800bddd 100644 --- a/promkit/tests/jsonz_toggle_test.rs +++ b/promkit/tests/jsonz_toggle_test.rs @@ -24,7 +24,8 @@ mod toggle { let mut rows = create_rows([input]); - rows.toggle(1); + let index = rows.toggle(1); + assert_eq!(index, 1); assert_eq!( rows[1].v, Value::Open { @@ -42,7 +43,8 @@ mod toggle { } ); - rows.toggle(4); + let index = rows.toggle(4); + assert_eq!(index, 4); assert_eq!( rows[4].v, Value::Open { @@ -60,7 +62,8 @@ mod toggle { } ); - rows.toggle(0); + let index = rows.toggle(0); + assert_eq!(index, 0); assert_eq!( rows[0].v, Value::Open { @@ -153,7 +156,8 @@ mod toggle { let mut rows = create_rows([input]); - rows.toggle(3); + let index = rows.toggle(3); + assert_eq!(index, 1); assert_eq!( rows[1].v, Value::Open { @@ -171,7 +175,8 @@ mod toggle { } ); - rows.toggle(8); + let index = rows.toggle(8); + assert_eq!(index, 4); assert_eq!( rows[4].v, Value::Open { @@ -189,7 +194,8 @@ mod toggle { } ); - rows.toggle(9); + let index = rows.toggle(9); + assert_eq!(index, 0); assert_eq!( rows[0].v, Value::Open { From 13ec261e42c44c4c89ce234fab53e91b23e20d10 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sat, 14 Dec 2024 00:16:52 +0900 Subject: [PATCH 18/44] chore: test for jsonl --- promkit/tests/jsonz_jsonl_test.rs | 271 ++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 promkit/tests/jsonz_jsonl_test.rs diff --git a/promkit/tests/jsonz_jsonl_test.rs b/promkit/tests/jsonz_jsonl_test.rs new file mode 100644 index 00000000..a6936424 --- /dev/null +++ b/promkit/tests/jsonz_jsonl_test.rs @@ -0,0 +1,271 @@ +#[cfg(test)] +mod jsonl { + use serde_json::Deserializer; + + use promkit::jsonz::*; + + #[test] + fn test_basic_jsonl() { + let inputs = Deserializer::from_str( + r#" + { + "name": "Alice", + "age": 30 + } + { + "name": "Bob", + "age": 25 + } + { + "name": "Charlie", + "age": 35 + } + "#, + ) + .into_iter::() + .filter_map(serde_json::Result::ok); + + let mut rows = create_rows(inputs); + + assert_eq!( + rows[0], + Row { + depth: 0, + k: None, + v: Value::Open { + typ: ContainerType::Object, + collapsed: false, + close_index: 3, + }, + } + ); + assert_eq!( + rows[1], + Row { + depth: 1, + k: Some("name".to_string()), + v: Value::String("Alice".to_string()), + } + ); + assert_eq!( + rows[2], + Row { + depth: 1, + k: Some("age".to_string()), + v: Value::Number(serde_json::Number::from(30)), + } + ); + assert_eq!( + rows[3], + Row { + depth: 0, + k: None, + v: Value::Close { + typ: ContainerType::Object, + collapsed: false, + open_index: 0, + }, + } + ); + + assert_eq!( + rows[4], + Row { + depth: 0, + k: None, + v: Value::Open { + typ: ContainerType::Object, + collapsed: false, + close_index: 7, + }, + } + ); + assert_eq!( + rows[5], + Row { + depth: 1, + k: Some("name".to_string()), + v: Value::String("Bob".to_string()), + } + ); + assert_eq!( + rows[6], + Row { + depth: 1, + k: Some("age".to_string()), + v: Value::Number(serde_json::Number::from(25)), + } + ); + assert_eq!( + rows[7], + Row { + depth: 0, + k: None, + v: Value::Close { + typ: ContainerType::Object, + collapsed: false, + open_index: 4, + }, + } + ); + + rows.toggle(0); + assert_eq!( + rows[0].v, + Value::Open { + typ: ContainerType::Object, + collapsed: true, + close_index: 3, + } + ); + assert_eq!( + rows[3].v, + Value::Close { + typ: ContainerType::Object, + collapsed: true, + open_index: 0, + } + ); + + assert_eq!(rows.up(4), 0); + assert_eq!(rows.down(0), 4); + } + + #[test] + fn test_mixed_jsonl() { + let inputs = Deserializer::from_str( + r#" + { + "array": [ + 1, + 2, + 3 + ] + } + [ + { + "nested": true + }, + { + "nested": false + } + ] + { + "empty": {} + } + "#, + ) + .into_iter::() + .filter_map(serde_json::Result::ok); + + let mut rows = create_rows(inputs); + + assert_eq!( + rows[0], + Row { + depth: 0, + k: None, + v: Value::Open { + typ: ContainerType::Object, + collapsed: false, + close_index: 6, + }, + } + ); + assert_eq!( + rows[1], + Row { + depth: 1, + k: Some("array".to_string()), + v: Value::Open { + typ: ContainerType::Array, + collapsed: false, + close_index: 5, + }, + } + ); + + for (i, num) in [1, 2, 3].iter().enumerate() { + assert_eq!( + rows[2 + i], + Row { + depth: 2, + k: None, + v: Value::Number(serde_json::Number::from(*num)), + } + ); + } + + let array_start = 7; + assert_eq!( + rows[array_start], + Row { + depth: 0, + k: None, + v: Value::Open { + typ: ContainerType::Array, + collapsed: false, + close_index: 14, + }, + } + ); + + assert_eq!( + rows[array_start + 1], + Row { + depth: 1, + k: None, + v: Value::Open { + typ: ContainerType::Object, + collapsed: false, + close_index: 10, + }, + } + ); + + let extracted = rows.extract(array_start + 1, 3); + assert_eq!(extracted.len(), 3); + assert_eq!( + extracted[0], + Row { + depth: 1, + k: None, + v: Value::Open { + typ: ContainerType::Object, + collapsed: false, + close_index: 10, + }, + } + ); + assert_eq!( + extracted[1], + Row { + depth: 2, + k: Some("nested".to_string()), + v: Value::Boolean(true), + } + ); + assert_eq!( + extracted[2], + Row { + depth: 1, + k: None, + v: Value::Close { + typ: ContainerType::Object, + collapsed: false, + open_index: array_start + 1, + }, + } + ); + + rows.toggle(array_start); + assert_eq!( + rows[array_start].v, + Value::Open { + typ: ContainerType::Array, + collapsed: true, + close_index: 14, + } + ); + } +} From f8db9f3da0255605e66e4ef0e86bdbb0a38c1807 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sat, 14 Dec 2024 11:00:35 +0900 Subject: [PATCH 19/44] on-error: use jsonz for json rendering (still old-version for jsonstream/state.rs) --- promkit/src/core.rs | 2 +- promkit/src/core/cursor.rs | 2 - promkit/src/core/cursor/composite.rs | 142 ------- promkit/src/core/json.rs | 169 -------- promkit/src/core/json/node.rs | 601 --------------------------- promkit/src/core/json/state.rs | 228 ---------- promkit/src/core/jsonstream.rs | 48 +++ promkit/src/core/jsonstream/state.rs | 69 +++ promkit/src/preset/json.rs | 8 +- promkit/src/preset/json/render.rs | 17 +- 10 files changed, 126 insertions(+), 1160 deletions(-) delete mode 100644 promkit/src/core/cursor/composite.rs delete mode 100644 promkit/src/core/json.rs delete mode 100644 promkit/src/core/json/node.rs delete mode 100644 promkit/src/core/json/state.rs create mode 100644 promkit/src/core/jsonstream.rs create mode 100644 promkit/src/core/jsonstream/state.rs diff --git a/promkit/src/core.rs b/promkit/src/core.rs index d318d61c..ed236913 100644 --- a/promkit/src/core.rs +++ b/promkit/src/core.rs @@ -1,7 +1,7 @@ pub mod checkbox; mod cursor; pub use cursor::Cursor; -pub mod json; +pub mod jsonstream; pub mod listbox; pub mod snapshot; pub mod text; diff --git a/promkit/src/core/cursor.rs b/promkit/src/core/cursor.rs index 8d908951..1540bcf3 100644 --- a/promkit/src/core/cursor.rs +++ b/promkit/src/core/cursor.rs @@ -1,7 +1,5 @@ mod len; use len::Len; -mod composite; -pub use composite::CompositeCursor; /// A generic cursor structure for navigating and manipulating collections. /// It maintains a position within the collection diff --git a/promkit/src/core/cursor/composite.rs b/promkit/src/core/cursor/composite.rs deleted file mode 100644 index 07475336..00000000 --- a/promkit/src/core/cursor/composite.rs +++ /dev/null @@ -1,142 +0,0 @@ -use super::Len; - -#[derive(Clone)] -pub struct CompositeCursor { - bundle: Vec, - cross_contents_position: usize, -} - -impl CompositeCursor { - pub fn new>(bundle_iter: I, position: usize) -> Self { - let bundle: Vec = bundle_iter.into_iter().collect(); - let total_len: usize = bundle.iter().map(|c| c.len()).sum(); - let adjusted_position = if position >= total_len { - total_len.saturating_sub(1) - } else { - position - }; - - Self { - bundle, - cross_contents_position: adjusted_position, - } - } - - pub fn bundle(&self) -> &Vec { - &self.bundle - } - - pub fn cross_contents_position(&self) -> usize { - self.cross_contents_position - } - - pub fn current_bundle_index_and_inner_position(&self) -> (usize, usize) { - let mut accumulated_len = 0; - for (bundle_index, c) in self.bundle.iter().enumerate() { - let c_len = c.len(); - if accumulated_len + c_len > self.cross_contents_position { - return (bundle_index, self.cross_contents_position - accumulated_len); - } - accumulated_len += c_len; - } - (self.bundle.len() - 1, self.bundle.last().unwrap().len() - 1) - } - - pub fn shift(&mut self, backward: usize, forward: usize) -> bool { - let total_len: usize = self.bundle.iter().map(|c| c.len()).sum(); - if backward > self.cross_contents_position { - false - } else { - let new_position = self.cross_contents_position - backward; - if new_position + forward < total_len { - self.cross_contents_position = new_position + forward; - true - } else { - false - } - } - } - - pub fn forward(&mut self) -> bool { - let total_len: usize = self.bundle.iter().map(|c| c.len()).sum(); - if self.cross_contents_position < total_len.saturating_sub(1) { - self.cross_contents_position += 1; - true - } else { - false - } - } - - pub fn backward(&mut self) -> bool { - if self.cross_contents_position > 0 { - self.cross_contents_position = self.cross_contents_position.saturating_sub(1); - true - } else { - false - } - } - - /// Moves the cursor to the head (start) of the bundle. - pub fn move_to_head(&mut self) { - self.cross_contents_position = 0 - } - - /// Moves the cursor to the tail (end) of the bundle. - pub fn move_to_tail(&mut self) { - let total_len: usize = self.bundle.iter().map(|c| c.len()).sum(); - if total_len == 0 { - self.cross_contents_position = 0 - } else { - self.cross_contents_position = total_len.saturating_sub(1); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - mod shift { - use super::*; - - #[test] - fn test_composite_forward() { - let mut cursor = CompositeCursor::new(vec![vec![1, 2], vec![3, 4, 5]], 0); - assert!(cursor.shift(0, 3)); // 0 -> 3 - assert_eq!(cursor.cross_contents_position(), 3); - } - - #[test] - fn test_composite_backward() { - let mut cursor = CompositeCursor::new(vec![vec![1, 2], vec![3, 4, 5]], 4); - assert!(cursor.shift(2, 0)); // 4 -> 2 - assert_eq!(cursor.cross_contents_position(), 2); - } - - #[test] - fn test_composite_forward_fail() { - let mut cursor = CompositeCursor::new(vec![vec![1, 2], vec![3, 4, 5]], 4); - assert!(!cursor.shift(0, 2)); // 4 -> fail, no wrap around - } - - #[test] - fn test_composite_backward_fail() { - let mut cursor = CompositeCursor::new(vec![vec![1, 2], vec![3, 4, 5]], 0); - assert!(!cursor.shift(1, 0)); // 0 -> fail, can't move backward - } - - #[test] - fn test_composite_forward_success() { - let mut cursor = CompositeCursor::new(vec![vec![1, 2], vec![3, 4, 5]], 2); - assert!(cursor.shift(0, 1)); // 2 -> 3 - assert_eq!(cursor.cross_contents_position(), 3); - } - - #[test] - fn test_composite_backward_success() { - let mut cursor = CompositeCursor::new(vec![vec![1, 2], vec![3, 4, 5]], 3); - assert!(cursor.shift(1, 0)); // 3 -> 2 - assert_eq!(cursor.cross_contents_position(), 2); - } - } -} diff --git a/promkit/src/core/json.rs b/promkit/src/core/json.rs deleted file mode 100644 index d6994e3f..00000000 --- a/promkit/src/core/json.rs +++ /dev/null @@ -1,169 +0,0 @@ -use crate::core::cursor::CompositeCursor; - -mod node; -pub use node::{JsonNode, JsonPath, JsonPathSegment, JsonSyntaxKind}; -mod state; -pub use state::State; - -/// Represents a stream of JSON data, allowing for efficient navigation and manipulation. -/// -/// `JsonStream` holds a collection of root JSON nodes and a cursor for navigating through -/// the JSON syntax kinds present in the stream. It supports operations like toggling visibility -/// of nodes, moving the cursor, and accessing specific nodes. -#[derive(Clone)] -pub struct JsonStream { - roots: Vec, - cursor: CompositeCursor>, -} - -impl JsonStream { - pub fn new>(iter: I, depth: Option) -> Self { - let roots: Vec = iter.into_iter().map(|v| JsonNode::new(v, depth)).collect(); - Self { - roots: roots.clone(), - cursor: CompositeCursor::new(roots.iter().map(|r| r.flatten_visibles()), 0), - } - } -} - -impl JsonStream { - /// Retrieves a reference to a root `JsonNode` by its index. - /// - /// # Arguments - /// - /// * `index` - The index of the root node to retrieve. - /// - /// # Returns - /// - /// An `Option` containing a reference to the `JsonNode` if it exists, or `None` if the index is out of bounds. - pub fn get_root(&self, index: usize) -> Option<&JsonNode> { - self.roots.get(index) - } - - /// Provides a reference to the vector of root `JsonNode`s. - /// - /// # Returns - /// - /// A reference to the vector containing all root nodes in the JSON stream. - pub fn roots(&self) -> &Vec { - &self.roots - } - - /// Flattens the visible JSON syntax kinds into a vector. - /// - /// This method traverses all visible nodes in the JSON stream and collects their syntax kinds into a flat vector. - /// - /// # Returns - /// - /// A `Vec` containing all visible syntax kinds from the JSON stream. - pub fn flatten_kinds(&self) -> Vec { - self.roots - .iter() - .flat_map(|root| root.flatten_visibles().into_iter()) - .collect() - } - - /// Retrieves the current root node and its path from the root based on the cursor's position. - /// - /// # Returns - /// - /// A tuple containing the current `JsonNode` and an `Option` indicating the path from the root to this node. - pub fn current_root_and_path_from_root(&self) -> (JsonNode, Option) { - let (index, inner) = self.cursor.current_bundle_index_and_inner_position(); - let kind = self.cursor.bundle()[index][inner].clone(); - (self.roots[index].clone(), kind.path().cloned()) - } - - /// Toggles the visibility of a node at the cursor's current position. - pub fn toggle(&mut self) { - let (index, inner) = self.cursor.current_bundle_index_and_inner_position(); - - let kind = self.cursor.bundle()[index][inner].clone(); - let route = match kind { - JsonSyntaxKind::ArrayStart { path, .. } => path, - JsonSyntaxKind::ArrayFolded { path, .. } => path, - JsonSyntaxKind::MapStart { path, .. } => path, - JsonSyntaxKind::MapFolded { path, .. } => path, - _ => return, - }; - - self.roots[index].toggle(&route); - self.cursor = CompositeCursor::new( - self.roots.iter().map(|r| r.flatten_visibles()), - self.cursor.cross_contents_position(), - ); - } - - /// Toggles the visibility of all nodes in the JSON tree. - /// - /// # Arguments - /// - /// * `expand` - A boolean indicating whether to expand (true) or collapse (false) all nodes. - fn toggle_all_visibility(&mut self, expand: bool) { - fn toggle_visibility(node: &mut JsonNode, expand: bool) { - match node { - JsonNode::Object { - children, - children_visible, - } => { - *children_visible = expand; - for child in children.values_mut() { - toggle_visibility(child, expand); - } - } - JsonNode::Array { - children, - children_visible, - } => { - *children_visible = expand; - for child in children.iter_mut() { - toggle_visibility(child, expand); - } - } - _ => {} - } - } - - for root in &mut self.roots { - toggle_visibility(root, expand); - } - self.cursor = CompositeCursor::new( - self.roots.iter().map(|r| r.flatten_visibles()), - self.cursor.cross_contents_position(), - ); - } - - /// Collapses all nodes in the JSON tree. - pub fn collapse_all(&mut self) { - self.toggle_all_visibility(false); - } - - /// Expands all nodes in the JSON tree. - pub fn expand_all(&mut self) { - self.toggle_all_visibility(true); - } - - pub fn shift(&mut self, backward: usize, forward: usize) -> bool { - self.cursor.shift(backward, forward) - } - - /// Moves the cursor backward through the JSON stream. - pub fn backward(&mut self) -> bool { - self.cursor.backward() - } - - /// Moves the cursor forward through the JSON stream. - pub fn forward(&mut self) -> bool { - self.cursor.forward() - } - - /// Moves the cursor to the head of the JSON stream. - pub fn move_to_head(&mut self) { - self.cursor.move_to_head() - } - - /// Moves the cursor to the tail of the JSON stream. - pub fn move_to_tail(&mut self) { - self.cursor.move_to_tail() - } -} diff --git a/promkit/src/core/json/node.rs b/promkit/src/core/json/node.rs deleted file mode 100644 index 715cd260..00000000 --- a/promkit/src/core/json/node.rs +++ /dev/null @@ -1,601 +0,0 @@ -use indexmap::IndexMap; - -use crate::serde_json; - -/// Represents the various kinds of syntax elements found in a JSON document. -/// This includes the start and end of maps and arrays, entries within maps and arrays, -/// and folded representations of maps and arrays for compact display. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum JsonSyntaxKind { - /// Represents the start of a map. Optionally contains the key if this map is an entry in another map, - /// the path to this map in the JSON document, and the indentation level for formatting. - MapStart { - key: Option, - path: JsonPath, - indent: usize, - }, - /// Represents the end of a map. Contains a flag indicating if this is the last element in its parent - /// and the indentation level for formatting. - MapEnd { is_last: bool, indent: usize }, - /// Represents a map that is folded (i.e., its contents are not displayed). Contains the same information as `MapStart` - /// plus a flag indicating if this is the last element in its parent. - MapFolded { - key: Option, - path: JsonPath, - is_last: bool, - indent: usize, - }, - /// Represents an entry in a map, containing the key-value pair, the path to this entry, - /// a flag indicating if this is the last element in its parent, and the indentation level for formatting. - MapEntry { - kv: (String, serde_json::Value), - path: JsonPath, - is_last: bool, - indent: usize, - }, - /// Represents the start of an array. Optionally contains the key if this array is an entry in a map, - /// the path to this array in the JSON document, and the indentation level for formatting. - ArrayStart { - key: Option, - path: JsonPath, - indent: usize, - }, - /// Represents the end of an array. Contains a flag indicating if this is the last element in its parent - /// and the indentation level for formatting. - ArrayEnd { is_last: bool, indent: usize }, - /// Represents an array that is folded (i.e., its contents are not displayed). Contains the same information as `ArrayStart` - /// plus a flag indicating if this is the last element in its parent. - ArrayFolded { - key: Option, - path: JsonPath, - is_last: bool, - indent: usize, - }, - /// Represents an entry in an array, containing the value, the path to this entry, - /// a flag indicating if this is the last element in its parent, and the indentation level for formatting. - ArrayEntry { - v: serde_json::Value, - path: JsonPath, - is_last: bool, - indent: usize, - }, -} - -impl JsonSyntaxKind { - /// Retrieves the path of the `JsonSyntaxKind` if available. - /// - /// # Returns - /// An `Option<&JsonPath>` representing the path of the syntax kind, or `None` if not applicable. - pub fn path(&self) -> Option<&JsonPath> { - match self { - JsonSyntaxKind::MapStart { path, .. } - | JsonSyntaxKind::MapFolded { path, .. } - | JsonSyntaxKind::MapEntry { path, .. } - | JsonSyntaxKind::ArrayStart { path, .. } - | JsonSyntaxKind::ArrayFolded { path, .. } - | JsonSyntaxKind::ArrayEntry { path, .. } => Some(path), - _ => None, - } - } -} - -pub type JsonPath = Vec; - -/// Represents a segment of a path in a JSON document, which can be either a key in an object -/// or an index in an array. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum JsonPathSegment { - /// Represents a key in a JSON object. - Key(String), - /// Represents an index in a JSON array. - Index(usize), -} - -/// Represents a node in a JSON structure, which can be an object, an array, or a leaf value. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum JsonNode { - Object { - children: IndexMap, - children_visible: bool, - }, - Array { - children: Vec, - children_visible: bool, - }, - /// Null, Bool(bool), Number(Number), String(String) - Leaf(serde_json::Value), -} - -impl JsonNode { - /// Creates a `JsonNode` from a `serde_json::Value` with visibility for all children set to true if `depth` is `None`, - /// or up to a specified depth if `depth` is `Some(usize)`. - /// - /// # Arguments - /// * `value` - The `serde_json::Value` to convert into a `JsonNode`. - /// * `depth` - An `Option` representing the depth up to which child nodes should be visible. - /// A depth of `Some(0)` means only the root is visible. `None` means all children are visible. - /// - /// # Returns - /// A `JsonNode` with children visibility set according to the specified depth. - pub fn new(value: serde_json::Value, depth: Option) -> Self { - match value { - serde_json::Value::Object(map) => { - let children = map - .into_iter() - .map(|(k, v)| (k, JsonNode::new(v, depth.map(|d| d.saturating_sub(1))))) - .collect(); - JsonNode::Object { - children, - children_visible: depth.map_or(true, |d| d > 0), - } - } - serde_json::Value::Array(vec) => { - let children = vec - .into_iter() - .map(|v| JsonNode::new(v, depth.map(|d| d.saturating_sub(1)))) - .collect(); - JsonNode::Array { - children, - children_visible: depth.map_or(true, |d| d > 0), - } - } - _ => JsonNode::Leaf(value), - } - } - - /// Retrieves a reference to a `JsonNode` at a specified JSON path. - /// - /// # Arguments - /// * `path` - A vector of `JsonPathSegment` indicating the path to the node. - /// - /// # Returns - /// An `Option` containing a reference to the found node, or `None` if not found. - pub fn get(&self, path: &JsonPath) -> Option<&JsonNode> { - let mut node = self; - for seg in path { - node = match seg { - JsonPathSegment::Key(s) => { - if let JsonNode::Object { children, .. } = node { - children.get(s)? - } else { - return None; - } - } - JsonPathSegment::Index(n) => { - if let JsonNode::Array { children, .. } = node { - children.get(*n)? - } else { - return None; - } - } - }; - } - Some(node) - } - - /// Retrieves a mutable reference to a `JsonNode` at a specified JSON path. - /// - /// # Arguments - /// * `path` - A vector of `JsonPathSegment` indicating the path to the node. - /// - /// # Returns - /// An `Option` containing a mutable reference to the found node, or `None` if not found. - pub fn get_mut(&mut self, path: &JsonPath) -> Option<&mut JsonNode> { - let mut node = self; - for seg in path { - node = match seg { - JsonPathSegment::Key(s) => { - if let JsonNode::Object { children, .. } = node { - children.get_mut(s)? - } else { - return None; - } - } - JsonPathSegment::Index(n) => { - if let JsonNode::Array { children, .. } = node { - children.get_mut(*n)? - } else { - return None; - } - } - }; - } - Some(node) - } - - /// Toggles the visibility of children for a `JsonNode` at a specified JSON path. - /// - /// # Arguments - /// * `path` - A vector of `JsonPathSegment` indicating the path to the node. - pub fn toggle(&mut self, path: &JsonPath) { - if let Some(node) = self.get_mut(path) { - match node { - JsonNode::Object { - children_visible, .. - } => *children_visible = !*children_visible, - JsonNode::Array { - children_visible, .. - } => *children_visible = !*children_visible, - _ => {} - } - } - } - - /// Flattens the visible parts of the JSON structure into a vector of `JsonSyntaxKind`. - /// - /// # Returns - /// A vector of `JsonSyntaxKind` representing the visible parts of the JSON structure. - pub fn flatten_visibles(&self) -> Vec { - fn dfs( - node: &JsonNode, - path: JsonPath, - ret: &mut Vec, - is_last: bool, - indent: usize, - ) { - match node { - JsonNode::Object { - children, - children_visible, - } => { - if *children_visible { - let start_kind = JsonSyntaxKind::MapStart { - key: path.last().and_then(|index| match index { - JsonPathSegment::Key(s) => Some(s.clone()), - _ => None, - }), - path: path.clone(), - indent, - }; - ret.push(start_kind); - - let keys = children.keys().collect::>(); - for (i, key) in keys.iter().enumerate() { - let child = children.get(*key).unwrap(); - let mut branch = path.clone(); - branch.push(JsonPathSegment::Key(key.to_string())); - let child_is_last = i == keys.len() - 1; - dfs(child, branch, ret, child_is_last, indent + 1); - } - - ret.push(JsonSyntaxKind::MapEnd { is_last, indent }); - } else { - ret.push(JsonSyntaxKind::MapFolded { - key: path.last().and_then(|index| match index { - JsonPathSegment::Key(s) => Some(s.clone()), - _ => None, - }), - path: path.clone(), - is_last, - indent, - }); - } - } - JsonNode::Array { - children, - children_visible, - } => { - if *children_visible { - let start_kind = JsonSyntaxKind::ArrayStart { - key: path.last().and_then(|index| match index { - JsonPathSegment::Key(s) => Some(s.clone()), - _ => None, - }), - path: path.clone(), - indent, - }; - ret.push(start_kind); - - for (i, child) in children.iter().enumerate() { - let mut branch = path.clone(); - branch.push(JsonPathSegment::Index(i)); - let child_is_last = i == children.len() - 1; - dfs(child, branch, ret, child_is_last, indent + 1); - } - - ret.push(JsonSyntaxKind::ArrayEnd { is_last, indent }); - } else { - ret.push(JsonSyntaxKind::ArrayFolded { - key: path.last().and_then(|index| match index { - JsonPathSegment::Key(s) => Some(s.clone()), - _ => None, - }), - path: path.clone(), - is_last, - indent, - }); - } - } - JsonNode::Leaf(value) => { - if let Some(JsonPathSegment::Key(key)) = path.last() { - ret.push(JsonSyntaxKind::MapEntry { - kv: (key.clone(), value.clone()), - path: path.clone(), - is_last, - indent, - }); - } else { - ret.push(JsonSyntaxKind::ArrayEntry { - v: value.clone(), - path: path.clone(), - is_last, - indent, - }); - } - } - } - } - - let mut ret = Vec::new(); - dfs(self, Vec::new(), &mut ret, true, 0); // Start with the root node being visible and is_last true, and indent 0 - ret - } -} - -#[cfg(test)] -mod test { - use crate::{json::JsonStream, serde_json::Deserializer}; - - use super::*; - - const JSON_STR: &str = r#"{ - "number": 1, - "map": { - "string1": "aaa" - }, - "list": [ - "abc" - ] - }"#; - - fn test_json_node() -> JsonNode { - let stream = Deserializer::from_str(JSON_STR) - .into_iter::() - .filter_map(Result::ok); - JsonStream::new(stream, None).get_root(0).unwrap().clone() - } - - fn as_object(node: &JsonNode) -> Option<(&IndexMap, bool)> { - if let JsonNode::Object { - children, - children_visible, - } = node - { - Some((children, *children_visible)) - } else { - None - } - } - - mod new { - use super::*; - use crate::serde_json::json; - - #[test] - fn test_one_level_depth() { - let value: serde_json::Value = serde_json::from_str(JSON_STR).unwrap(); - let node = JsonNode::new(value, Some(1)); - let expected = JsonNode::Object { - children: IndexMap::from_iter(vec![ - ("number".to_string(), JsonNode::Leaf(json!(1))), - ( - "map".to_string(), - JsonNode::Object { - children: IndexMap::from_iter(vec![( - "string1".to_string(), - JsonNode::Leaf(json!("aaa")), - )]), - children_visible: false, - }, - ), - ( - "list".to_string(), - JsonNode::Array { - children: vec![JsonNode::Leaf(json!("abc"))], - children_visible: false, - }, - ), - ]), - children_visible: true, - }; - assert_eq!(node, expected); - } - } - - mod flatten_visibles { - use super::*; - - #[test] - fn test() { - let node = test_json_node(); - assert_eq!( - vec![ - // { - JsonSyntaxKind::MapStart { - key: None, - path: vec![], - indent: 0, - }, - // "number": 1, - JsonSyntaxKind::MapEntry { - kv: ( - "number".to_string(), - serde_json::Value::Number(serde_json::Number::from(1)) - ), - path: vec![JsonPathSegment::Key("number".to_string())], - is_last: false, - indent: 1, - }, - // "map": { - JsonSyntaxKind::MapStart { - key: Some("map".to_string()), - path: vec![JsonPathSegment::Key("map".to_string())], - indent: 1, - }, - // "string1": "aaa", - JsonSyntaxKind::MapEntry { - kv: ( - "string1".to_string(), - serde_json::Value::String("aaa".to_string()) - ), - path: vec![ - JsonPathSegment::Key("map".to_string()), - JsonPathSegment::Key("string1".to_string()) - ], - is_last: true, - indent: 2, - }, - // }, - JsonSyntaxKind::MapEnd { - is_last: false, - indent: 1 - }, - // "list": [ - JsonSyntaxKind::ArrayStart { - key: Some("list".to_string()), - path: vec![JsonPathSegment::Key("list".to_string())], - indent: 1, - }, - // "abc", - JsonSyntaxKind::ArrayEntry { - v: serde_json::Value::String("abc".to_string()), - path: vec![ - JsonPathSegment::Key("list".to_string()), - JsonPathSegment::Index(0) - ], - is_last: true, - indent: 2, - }, - // ], - JsonSyntaxKind::ArrayEnd { - is_last: true, - indent: 1 - }, - // } - JsonSyntaxKind::MapEnd { - is_last: true, - indent: 0 - }, - ], - node.flatten_visibles(), - ); - } - - #[test] - fn test_after_toggle() { - let mut node = test_json_node(); - node.toggle(&vec![]); - assert_eq!( - vec![JsonSyntaxKind::MapFolded { - key: None, - path: vec![], - is_last: true, - indent: 0, - }], - node.flatten_visibles(), - ); - } - } - - mod toggle { - use super::*; - - #[test] - fn test() { - let mut node = test_json_node(); - node.toggle(&vec![JsonPathSegment::Key("map".to_string())]); - assert!( - !as_object( - node.get(&vec![JsonPathSegment::Key("map".to_string())]) - .unwrap() - ) - .unwrap() - .1 - ); - } - } - - mod get { - use super::*; - - #[test] - fn test() { - let node = test_json_node(); - assert_eq!(Some(&node.clone()), node.get(&vec![])); - } - - #[test] - fn test_with_invalid_path() { - let node = test_json_node(); - assert_eq!( - None, - node.get(&vec![ - JsonPathSegment::Key("map".to_string()), - JsonPathSegment::Key("invalid_segment".to_string()), - ],) - ); - } - } - - mod get_mut { - use super::*; - - #[test] - fn test() { - let mut node = test_json_node(); - assert_eq!(Some(&mut node.clone()), node.get_mut(&vec![])); - } - - #[test] - fn test_with_invalid_path() { - let mut node = test_json_node(); - assert_eq!( - None, - node.get_mut(&vec![ - JsonPathSegment::Key("map".to_string()), - JsonPathSegment::Key("invalid_segment".to_string()), - ],) - ); - } - } - - mod try_from { - use super::*; - use crate::serde_json::Number; - - #[test] - fn test() { - assert_eq!( - JsonNode::Object { - children: IndexMap::from_iter(vec![ - ( - String::from("number"), - JsonNode::Leaf(serde_json::Value::Number(Number::from(1))) - ), - ( - String::from("map"), - JsonNode::Object { - children: IndexMap::from_iter(vec![( - String::from("string1"), - JsonNode::Leaf(serde_json::Value::String(String::from("aaa"))) - ),]), - children_visible: true, - } - ), - ( - String::from("list"), - JsonNode::Array { - children: vec![JsonNode::Leaf(serde_json::Value::String( - String::from("abc") - )),], - children_visible: true, - } - ), - ]), - children_visible: true, - }, - test_json_node(), - ); - } - } -} diff --git a/promkit/src/core/json/state.rs b/promkit/src/core/json/state.rs deleted file mode 100644 index 1233ea86..00000000 --- a/promkit/src/core/json/state.rs +++ /dev/null @@ -1,228 +0,0 @@ -use crate::{ - crossterm::style::{Attribute, ContentStyle}, - grapheme::StyledGraphemes, - pane::Pane, - PaneFactory, -}; - -use super::{JsonStream, JsonSyntaxKind}; - -/// Represents the state of a JSON stream within the application. -/// -/// This struct holds the current JSON stream being processed and provides -/// methods to interact with and manipulate the stream according to the -/// application's needs. It also contains a theme configuration for styling -/// the JSON output. -#[derive(Clone)] -pub struct State { - pub stream: JsonStream, - - /// Style for {}. - pub curly_brackets_style: ContentStyle, - /// Style for []. - pub square_brackets_style: ContentStyle, - /// Style for "key". - pub key_style: ContentStyle, - /// Style for string values. - pub string_value_style: ContentStyle, - /// Style for number values. - pub number_value_style: ContentStyle, - /// Style for boolean values. - pub boolean_value_style: ContentStyle, - /// Style for null values. - pub null_value_style: ContentStyle, - - /// Attribute for the selected line. - pub active_item_attribute: Attribute, - /// Attribute for unselected lines. - pub inactive_item_attribute: Attribute, - - /// Number of lines available for rendering. - pub lines: Option, - - /// The number of spaces used for indentation in the rendered JSON structure. - /// This value multiplies with the indentation level of a JSON element to determine - /// the total indentation space. For example, an `indent` value of 4 means each - /// indentation level will be 4 spaces wide. - pub indent: usize, -} - -impl State { - pub fn indent_level(&self, kind: &JsonSyntaxKind) -> usize { - match kind { - JsonSyntaxKind::MapStart { indent, .. } - | JsonSyntaxKind::MapEnd { indent, .. } - | JsonSyntaxKind::MapFolded { indent, .. } - | JsonSyntaxKind::MapEntry { indent, .. } - | JsonSyntaxKind::ArrayFolded { indent, .. } - | JsonSyntaxKind::ArrayStart { indent, .. } - | JsonSyntaxKind::ArrayEnd { indent, .. } - | JsonSyntaxKind::ArrayEntry { indent, .. } => *indent * self.indent, - } - } - - fn format_value(&self, v: &serde_json::Value) -> StyledGraphemes { - match v { - serde_json::Value::String(s) => { - StyledGraphemes::from_str(format!("\"{}\"", s), self.string_value_style) - } - serde_json::Value::Number(n) => { - StyledGraphemes::from_str(n.to_string(), self.number_value_style) - } - serde_json::Value::Bool(b) => { - StyledGraphemes::from_str(b.to_string(), self.boolean_value_style) - } - serde_json::Value::Null => StyledGraphemes::from_str("null", self.null_value_style), - _ => StyledGraphemes::from(""), - } - } - - pub fn gen_syntax_style(&self, kind: &JsonSyntaxKind) -> StyledGraphemes { - match kind { - JsonSyntaxKind::MapStart { key, .. } => match key { - Some(key) => StyledGraphemes::from_iter([ - StyledGraphemes::from_str(format!("\"{}\"", key), self.key_style), - StyledGraphemes::from(": "), - StyledGraphemes::from_str("{", self.curly_brackets_style), - ]), - None => StyledGraphemes::from_str("{", self.curly_brackets_style), - }, - JsonSyntaxKind::MapEnd { is_last, .. } => { - if *is_last { - StyledGraphemes::from_str("}", self.curly_brackets_style) - } else { - StyledGraphemes::from_iter([ - StyledGraphemes::from_str("}", self.curly_brackets_style), - StyledGraphemes::from(","), - ]) - } - } - JsonSyntaxKind::MapFolded { key, is_last, .. } => { - let token = match key { - Some(key) => StyledGraphemes::from_iter([ - StyledGraphemes::from_str(format!("\"{}\"", key), self.key_style), - StyledGraphemes::from(": "), - StyledGraphemes::from_str("{...}", self.curly_brackets_style), - ]), - None => StyledGraphemes::from_str("{...}", self.curly_brackets_style), - }; - if *is_last { - token - } else { - StyledGraphemes::from_iter([token, StyledGraphemes::from(",")]) - } - } - JsonSyntaxKind::MapEntry { kv, is_last, .. } => { - let token = StyledGraphemes::from_iter([ - StyledGraphemes::from_str(format!("\"{}\"", kv.0), self.key_style), - StyledGraphemes::from(": "), - self.format_value(&kv.1), - ]); - if *is_last { - token - } else { - StyledGraphemes::from_iter([token, StyledGraphemes::from(",")]) - } - } - JsonSyntaxKind::ArrayStart { key, .. } => match key { - Some(key) => StyledGraphemes::from_iter([ - StyledGraphemes::from_str(format!("\"{}\"", key), self.key_style), - StyledGraphemes::from(": "), - StyledGraphemes::from_str("[", self.square_brackets_style), - ]), - None => StyledGraphemes::from_str("[", self.square_brackets_style), - }, - JsonSyntaxKind::ArrayEnd { is_last, .. } => { - if *is_last { - StyledGraphemes::from_str("]", self.square_brackets_style) - } else { - StyledGraphemes::from_iter([ - StyledGraphemes::from_str("]", self.square_brackets_style), - StyledGraphemes::from(","), - ]) - } - } - JsonSyntaxKind::ArrayFolded { key, is_last, .. } => { - let token = match key { - Some(key) => StyledGraphemes::from_iter([ - StyledGraphemes::from_str(format!("\"{}\"", key), self.key_style), - StyledGraphemes::from(": "), - StyledGraphemes::from_str("[...]", self.square_brackets_style), - ]), - None => StyledGraphemes::from_str("[...]", self.square_brackets_style), - }; - if *is_last { - token - } else { - StyledGraphemes::from_iter([token, StyledGraphemes::from(",")]) - } - } - JsonSyntaxKind::ArrayEntry { v, is_last, .. } => { - let token = StyledGraphemes::from_iter([self.format_value(v)]); - if *is_last { - token - } else { - StyledGraphemes::from_iter([token, StyledGraphemes::from(",")]) - } - } - } - } - - fn styled_json(&self) -> Vec { - self.stream - .flatten_kinds() - .iter() - .enumerate() - .map(|(i, kind)| { - if i == self.stream.cursor.cross_contents_position() { - StyledGraphemes::from_iter([ - StyledGraphemes::from(" ".repeat(self.indent_level(kind))), - self.gen_syntax_style(kind) - .apply_attribute(self.active_item_attribute), - ]) - } else { - StyledGraphemes::from_iter([ - StyledGraphemes::from(" ".repeat(self.indent_level(kind))), - self.gen_syntax_style(kind), - ]) - .apply_attribute(self.inactive_item_attribute) - } - }) - .collect() - } - - pub fn json_str(&self) -> String { - self.styled_json() - .into_iter() - .map(|s| s.to_string()) - .collect::>() - .join("\n") - } -} - -impl PaneFactory for State { - fn create_pane(&self, width: u16, height: u16) -> Pane { - let height = match self.lines { - Some(lines) => lines.min(height as usize), - None => height as usize, - }; - - let styled_json = self.styled_json(); - let matrix = styled_json - .into_iter() - .enumerate() - .filter(|(i, _)| { - *i >= self.stream.cursor.cross_contents_position() - && *i < self.stream.cursor.cross_contents_position() + height - }) - .fold((vec![], 0), |(mut acc, pos), (_, item)| { - let rows = item.matrixify(width as usize, height, 0).0; - if pos < self.stream.cursor.cross_contents_position() + height { - acc.extend(rows); - } - (acc, pos + 1) - }); - - Pane::new(matrix.0, 0) - } -} diff --git a/promkit/src/core/jsonstream.rs b/promkit/src/core/jsonstream.rs new file mode 100644 index 00000000..5bd06072 --- /dev/null +++ b/promkit/src/core/jsonstream.rs @@ -0,0 +1,48 @@ +mod state; +pub use state::State; + +use crate::jsonz::{self, Row, RowOperation}; + +/// Represents a stream of JSON data, allowing for efficient navigation and manipulation. +#[derive(Clone)] +pub struct JsonStream { + rows: Vec, + position: usize, +} + +impl JsonStream { + pub fn new>(iter: I) -> Self { + Self { + rows: jsonz::create_rows(iter), + position: 0, + } + } +} + +impl JsonStream { + pub fn extract_rows(&self, n: usize) -> Vec { + self.rows.extract(self.position, n) + } + + /// Toggles the visibility of a node at the cursor's current position. + pub fn toggle(&mut self) { + let index = self.rows.toggle(self.position); + self.position = index; + } + + /// Moves the cursor backward through JSON stream. + pub fn backward(&mut self) -> bool { + let index = self.rows.up(self.position); + let ret = index != self.position; + self.position = index; + ret + } + + /// Moves the cursor forward through JSON stream. + pub fn forward(&mut self) -> bool { + let index = self.rows.down(self.position); + let ret = index != self.position; + self.position = index; + ret + } +} diff --git a/promkit/src/core/jsonstream/state.rs b/promkit/src/core/jsonstream/state.rs new file mode 100644 index 00000000..984b823c --- /dev/null +++ b/promkit/src/core/jsonstream/state.rs @@ -0,0 +1,69 @@ +use crate::{ + crossterm::style::{Attribute, ContentStyle}, + grapheme::StyledGraphemes, + pane::Pane, + PaneFactory, +}; + +use super::JsonStream; + +/// Represents the state of a JSON stream within the application. +/// +/// This struct holds the current JSON stream being processed and provides +/// methods to interact with and manipulate the stream according to the +/// application's needs. It also contains a theme configuration for styling +/// the JSON output. +#[derive(Clone)] +pub struct State { + pub stream: JsonStream, + + /// Style for {}. + pub curly_brackets_style: ContentStyle, + /// Style for []. + pub square_brackets_style: ContentStyle, + /// Style for "key". + pub key_style: ContentStyle, + /// Style for string values. + pub string_value_style: ContentStyle, + /// Style for number values. + pub number_value_style: ContentStyle, + /// Style for boolean values. + pub boolean_value_style: ContentStyle, + /// Style for null values. + pub null_value_style: ContentStyle, + + /// Attribute for the selected line. + pub active_item_attribute: Attribute, + /// Attribute for unselected lines. + pub inactive_item_attribute: Attribute, + + /// Number of lines available for rendering. + pub lines: Option, + + /// The number of spaces used for indentation in the rendered JSON structure. + /// This value multiplies with the indentation level of a JSON element to determine + /// the total indentation space. For example, an `indent` value of 4 means each + /// indentation level will be 4 spaces wide. + pub indent: usize, +} + +impl State { + /// Formats a Vec into Vec with appropriate styling and width limits + fn format_rows(&self, rows: Vec, width: u16) -> Vec { + todo!() + } +} + +impl PaneFactory for State { + fn create_pane(&self, width: u16, height: u16) -> Pane { + let height = match self.lines { + Some(lines) => lines.min(height as usize), + None => height as usize, + }; + + let rows = self.stream.extract_rows(height); + let formatted_rows = self.format_rows(rows, width); + + Pane::new(formatted_rows, 0) + } +} diff --git a/promkit/src/preset/json.rs b/promkit/src/preset/json.rs index 2149664e..67bd9481 100644 --- a/promkit/src/preset/json.rs +++ b/promkit/src/preset/json.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use crate::{ crossterm::style::{Attribute, Attributes, Color, ContentStyle}, - json::{self, JsonStream}, + jsonstream::{self, JsonStream}, snapshot::Snapshot, style::StyleBuilder, switch::ActiveKeySwitcher, @@ -16,7 +16,7 @@ pub mod render; pub struct Json { keymap: ActiveKeySwitcher, title_state: text::State, - json_state: json::State, + json_state: jsonstream::State, } impl Json { @@ -28,7 +28,7 @@ impl Json { .attrs(Attributes::from(Attribute::Bold)) .build(), }, - json_state: json::State { + json_state: jsonstream::State { stream, curly_brackets_style: StyleBuilder::new() .attrs(Attributes::from(Attribute::Bold)) @@ -97,7 +97,7 @@ impl Json { renderer: render::Renderer { keymap: RefCell::new(self.keymap), title_snapshot: Snapshot::::new(self.title_state), - json_snapshot: Snapshot::::new(self.json_state), + json_snapshot: Snapshot::::new(self.json_state), }, }) } diff --git a/promkit/src/preset/json/render.rs b/promkit/src/preset/json/render.rs index 4b58d6e0..57831491 100644 --- a/promkit/src/preset/json/render.rs +++ b/promkit/src/preset/json/render.rs @@ -1,12 +1,7 @@ use std::cell::RefCell; use crate::{ - crossterm::event::Event, - json, - json::{JsonNode, JsonPath}, - pane::Pane, - snapshot::Snapshot, - switch::ActiveKeySwitcher, + crossterm::event::Event, jsonstream, pane::Pane, snapshot::Snapshot, switch::ActiveKeySwitcher, text, PaneFactory, PromptSignal, }; @@ -20,18 +15,14 @@ pub struct Renderer { /// Snapshot of the renderer used for the title. pub title_snapshot: Snapshot, /// Snapshot of the renderer used for JSON content. - pub json_snapshot: Snapshot, + pub json_snapshot: Snapshot, } impl crate::Finalizer for Renderer { - type Return = (JsonNode, Option); + type Return = (); fn finalize(&mut self) -> anyhow::Result { - Ok(self - .json_snapshot - .after() - .stream - .current_root_and_path_from_root()) + Ok(()) } } From 654317e6aac4f6a7af2782e590f42ef95bd2454d Mon Sep 17 00:00:00 2001 From: ynqa Date: Sat, 14 Dec 2024 12:45:47 +0900 Subject: [PATCH 20/44] bug: - active_item_attribute for line entire - comma for Open --- promkit/src/core/jsonstream.rs | 3 +- promkit/src/core/jsonstream/state.rs | 109 ++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 3 deletions(-) diff --git a/promkit/src/core/jsonstream.rs b/promkit/src/core/jsonstream.rs index 5bd06072..de6cbb83 100644 --- a/promkit/src/core/jsonstream.rs +++ b/promkit/src/core/jsonstream.rs @@ -20,7 +20,8 @@ impl JsonStream { } impl JsonStream { - pub fn extract_rows(&self, n: usize) -> Vec { + /// Extracts a specified number of rows from the current position in JSON stream. + pub fn extract_rows_from_current(&self, n: usize) -> Vec { self.rows.extract(self.position, n) } diff --git a/promkit/src/core/jsonstream/state.rs b/promkit/src/core/jsonstream/state.rs index 984b823c..7f73f1ab 100644 --- a/promkit/src/core/jsonstream/state.rs +++ b/promkit/src/core/jsonstream/state.rs @@ -50,7 +50,112 @@ pub struct State { impl State { /// Formats a Vec into Vec with appropriate styling and width limits fn format_rows(&self, rows: Vec, width: u16) -> Vec { - todo!() + let mut formatted = Vec::new(); + let width = width as usize; + + for (i, row) in rows.iter().enumerate() { + let mut parts = Vec::new(); + + parts.push(StyledGraphemes::from(" ".repeat(self.indent * row.depth))); + + if let Some(key) = &row.k { + parts.push( + StyledGraphemes::from(format!("\"{}\"", key)).apply_style(self.key_style), + ); + parts.push(StyledGraphemes::from(": ")); + } + + match &row.v { + super::jsonz::Value::Null => { + parts.push(StyledGraphemes::from("null").apply_style(self.null_value_style)); + } + super::jsonz::Value::Boolean(b) => { + parts.push( + StyledGraphemes::from(b.to_string()).apply_style(self.boolean_value_style), + ); + } + super::jsonz::Value::Number(n) => { + parts.push( + StyledGraphemes::from(n.to_string()).apply_style(self.number_value_style), + ); + } + super::jsonz::Value::String(s) => { + let escaped = s.replace('\n', "\\n"); + parts.push( + StyledGraphemes::from(format!("\"{}\"", escaped)) + .apply_style(self.string_value_style), + ); + } + super::jsonz::Value::Empty { typ } => { + let bracket_style = match typ { + super::jsonz::ContainerType::Object => self.curly_brackets_style, + super::jsonz::ContainerType::Array => self.square_brackets_style, + }; + parts.push( + StyledGraphemes::from(format!("{}{}", typ.open_str(), typ.close_str())) + .apply_style(bracket_style), + ); + } + super::jsonz::Value::Open { typ, collapsed, .. } => { + let bracket_style = match typ { + super::jsonz::ContainerType::Object => self.curly_brackets_style, + super::jsonz::ContainerType::Array => self.square_brackets_style, + }; + if *collapsed { + parts.push( + StyledGraphemes::from(typ.collapsed_preview()) + .apply_style(bracket_style), + ); + } else { + parts + .push(StyledGraphemes::from(typ.open_str()).apply_style(bracket_style)); + } + } + super::jsonz::Value::Close { typ, .. } => { + let bracket_style = match typ { + super::jsonz::ContainerType::Object => self.curly_brackets_style, + super::jsonz::ContainerType::Array => self.square_brackets_style, + }; + parts.push(StyledGraphemes::from(typ.close_str()).apply_style(bracket_style)); + } + } + + if i + 1 < rows.len() { + if let super::jsonz::Value::Close { .. } = rows[i + 1].v { + } else { + parts.push(StyledGraphemes::from(",")); + } + } + + let mut line: StyledGraphemes = parts.into_iter().collect(); + + if line.widths() > width { + let mut truncated = StyledGraphemes::default(); + let mut current_width = 0; + for g in line.iter() { + if current_width + g.width() + 3 > width { + break; + } + truncated.push_back(g.clone()); + current_width += g.width(); + } + let ellipsis: StyledGraphemes = StyledGraphemes::from("..."); + line = vec![truncated, ellipsis].into_iter().collect(); + } + + // Note that `extract_rows_from_current` + // returns rows starting from the current position, + // so the first row should always be highlighted as active + line = line.apply_attribute(if i == 0 { + self.active_item_attribute + } else { + self.inactive_item_attribute + }); + + formatted.push(line); + } + + formatted } } @@ -61,7 +166,7 @@ impl PaneFactory for State { None => height as usize, }; - let rows = self.stream.extract_rows(height); + let rows = self.stream.extract_rows_from_current(height); let formatted_rows = self.format_rows(rows, width); Pane::new(formatted_rows, 0) From fbca186635d9824d7ae6e2dc63c502f14628a9b3 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sat, 14 Dec 2024 12:57:20 +0900 Subject: [PATCH 21/44] resolve: comma for Open --- promkit/src/core/jsonstream/state.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/promkit/src/core/jsonstream/state.rs b/promkit/src/core/jsonstream/state.rs index 7f73f1ab..6dc7daf9 100644 --- a/promkit/src/core/jsonstream/state.rs +++ b/promkit/src/core/jsonstream/state.rs @@ -122,6 +122,7 @@ impl State { if i + 1 < rows.len() { if let super::jsonz::Value::Close { .. } = rows[i + 1].v { + } else if let super::jsonz::Value::Open { .. } = rows[i].v { } else { parts.push(StyledGraphemes::from(",")); } From cfdb9a4807b5fa9c10ce99e62504604cc1271143 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sat, 14 Dec 2024 13:00:43 +0900 Subject: [PATCH 22/44] =?UTF-8?q?chore:=20use=20=E2=80=A6=20instead=20of?= =?UTF-8?q?=20...=20for=20ellipsis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- promkit/src/core/jsonstream/state.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/promkit/src/core/jsonstream/state.rs b/promkit/src/core/jsonstream/state.rs index 6dc7daf9..3f5638f3 100644 --- a/promkit/src/core/jsonstream/state.rs +++ b/promkit/src/core/jsonstream/state.rs @@ -131,16 +131,16 @@ impl State { let mut line: StyledGraphemes = parts.into_iter().collect(); if line.widths() > width { + let ellipsis: StyledGraphemes = StyledGraphemes::from("…"); let mut truncated = StyledGraphemes::default(); let mut current_width = 0; for g in line.iter() { - if current_width + g.width() + 3 > width { + if current_width + g.width() + ellipsis.widths() > width { break; } truncated.push_back(g.clone()); current_width += g.width(); } - let ellipsis: StyledGraphemes = StyledGraphemes::from("..."); line = vec![truncated, ellipsis].into_iter().collect(); } From 07816c83841fb1a2bebe379fd7eeb73530332aaf Mon Sep 17 00:00:00 2001 From: ynqa Date: Sat, 14 Dec 2024 13:11:42 +0900 Subject: [PATCH 23/44] resolve: active_item_attribute for line entire --- promkit/src/core/jsonstream/state.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/promkit/src/core/jsonstream/state.rs b/promkit/src/core/jsonstream/state.rs index 3f5638f3..042dc251 100644 --- a/promkit/src/core/jsonstream/state.rs +++ b/promkit/src/core/jsonstream/state.rs @@ -54,10 +54,9 @@ impl State { let width = width as usize; for (i, row) in rows.iter().enumerate() { + let indent = StyledGraphemes::from(" ".repeat(self.indent * row.depth)); let mut parts = Vec::new(); - parts.push(StyledGraphemes::from(" ".repeat(self.indent * row.depth))); - if let Some(key) = &row.k { parts.push( StyledGraphemes::from(format!("\"{}\"", key)).apply_style(self.key_style), @@ -128,7 +127,18 @@ impl State { } } - let mut line: StyledGraphemes = parts.into_iter().collect(); + let mut content: StyledGraphemes = parts.into_iter().collect(); + + // Note that `extract_rows_from_current` + // returns rows starting from the current position, + // so the first row should always be highlighted as active + content = content.apply_attribute(if i == 0 { + self.active_item_attribute + } else { + self.inactive_item_attribute + }); + + let mut line: StyledGraphemes = vec![indent, content].into_iter().collect(); if line.widths() > width { let ellipsis: StyledGraphemes = StyledGraphemes::from("…"); @@ -144,15 +154,6 @@ impl State { line = vec![truncated, ellipsis].into_iter().collect(); } - // Note that `extract_rows_from_current` - // returns rows starting from the current position, - // so the first row should always be highlighted as active - line = line.apply_attribute(if i == 0 { - self.active_item_attribute - } else { - self.inactive_item_attribute - }); - formatted.push(line); } From 53641ee325490fcbf7049c9c538ff216824423e0 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sat, 14 Dec 2024 13:30:49 +0900 Subject: [PATCH 24/44] chore: mut is unnecessary for up/down --- promkit/src/jsonz.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index b8d62dd5..e380b386 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -56,14 +56,14 @@ pub struct Row { } pub trait RowOperation { - fn up(&mut self, current: usize) -> usize; - fn down(&mut self, current: usize) -> usize; + fn up(&self, current: usize) -> usize; + fn down(&self, current: usize) -> usize; fn toggle(&mut self, current: usize) -> usize; fn extract(&self, current: usize, n: usize) -> Vec; } impl RowOperation for Vec { - fn up(&mut self, current: usize) -> usize { + fn up(&self, current: usize) -> usize { if current == 0 { return 0; } @@ -79,7 +79,7 @@ impl RowOperation for Vec { } } - fn down(&mut self, current: usize) -> usize { + fn down(&self, current: usize) -> usize { if current >= self.len() - 1 { return current; } From 2abd7f61468f7424ea3477034835ef26289c4ab3 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sat, 14 Dec 2024 14:09:19 +0900 Subject: [PATCH 25/44] FIX: snapshot is too slow for large data because of clone-cost for `before` --- promkit/src/preset/checkbox.rs | 5 ++--- promkit/src/preset/checkbox/keymap.rs | 12 +++++------- promkit/src/preset/checkbox/render.rs | 19 +++++++++---------- promkit/src/preset/json.rs | 5 ++--- promkit/src/preset/json/keymap.rs | 8 +++----- promkit/src/preset/json/render.rs | 16 ++++++++-------- promkit/src/preset/listbox.rs | 5 ++--- promkit/src/preset/listbox/keymap.rs | 6 ++---- promkit/src/preset/listbox/render.rs | 14 +++++++------- promkit/src/preset/query_selector.rs | 2 +- promkit/src/preset/query_selector/render.rs | 6 +++--- promkit/src/preset/readline.rs | 2 +- promkit/src/preset/readline/render.rs | 6 +++--- promkit/src/preset/tree.rs | 5 ++--- promkit/src/preset/tree/keymap.rs | 12 +++++------- promkit/src/preset/tree/render.rs | 14 +++++++------- 16 files changed, 62 insertions(+), 75 deletions(-) diff --git a/promkit/src/preset/checkbox.rs b/promkit/src/preset/checkbox.rs index de4c6851..31cc1df7 100644 --- a/promkit/src/preset/checkbox.rs +++ b/promkit/src/preset/checkbox.rs @@ -3,7 +3,6 @@ use std::{cell::RefCell, fmt::Display}; use crate::{ checkbox, crossterm::style::{Attribute, Attributes, Color, ContentStyle}, - snapshot::Snapshot, style::StyleBuilder, switch::ActiveKeySwitcher, text, Prompt, @@ -126,8 +125,8 @@ impl Checkbox { Ok(Prompt { renderer: render::Renderer { keymap: RefCell::new(self.keymap), - title_snapshot: Snapshot::::new(self.title_state), - checkbox_snapshot: Snapshot::::new(self.checkbox_state), + title_state: self.title_state, + checkbox_state: self.checkbox_state, }, }) } diff --git a/promkit/src/preset/checkbox/keymap.rs b/promkit/src/preset/checkbox/keymap.rs index 24dde3eb..dfef8353 100644 --- a/promkit/src/preset/checkbox/keymap.rs +++ b/promkit/src/preset/checkbox/keymap.rs @@ -24,8 +24,6 @@ pub fn default( event: &Event, renderer: &mut preset::checkbox::render::Renderer, ) -> anyhow::Result { - let checkbox_after_mut = renderer.checkbox_snapshot.after_mut(); - match event { Event::Key(KeyEvent { code: KeyCode::Enter, @@ -47,7 +45,7 @@ pub fn default( kind: KeyEventKind::Press, state: KeyEventState::NONE, }) => { - checkbox_after_mut.checkbox.backward(); + renderer.checkbox_state.checkbox.backward(); } Event::Mouse(MouseEvent { kind: MouseEventKind::ScrollUp, @@ -55,7 +53,7 @@ pub fn default( row: _, modifiers: KeyModifiers::NONE, }) => { - checkbox_after_mut.checkbox.backward(); + renderer.checkbox_state.checkbox.backward(); } Event::Key(KeyEvent { @@ -64,7 +62,7 @@ pub fn default( kind: KeyEventKind::Press, state: KeyEventState::NONE, }) => { - checkbox_after_mut.checkbox.forward(); + renderer.checkbox_state.checkbox.forward(); } Event::Mouse(MouseEvent { kind: MouseEventKind::ScrollDown, @@ -72,7 +70,7 @@ pub fn default( row: _, modifiers: KeyModifiers::NONE, }) => { - checkbox_after_mut.checkbox.forward(); + renderer.checkbox_state.checkbox.forward(); } Event::Key(KeyEvent { @@ -80,7 +78,7 @@ pub fn default( modifiers: KeyModifiers::NONE, kind: KeyEventKind::Press, state: KeyEventState::NONE, - }) => checkbox_after_mut.checkbox.toggle(), + }) => renderer.checkbox_state.checkbox.toggle(), _ => (), } diff --git a/promkit/src/preset/checkbox/render.rs b/promkit/src/preset/checkbox/render.rs index 9901490f..25bfe7b5 100644 --- a/promkit/src/preset/checkbox/render.rs +++ b/promkit/src/preset/checkbox/render.rs @@ -1,8 +1,8 @@ use std::cell::RefCell; use crate::{ - checkbox, crossterm::event::Event, pane::Pane, snapshot::Snapshot, switch::ActiveKeySwitcher, - text, PaneFactory, PromptSignal, + checkbox, crossterm::event::Event, pane::Pane, switch::ActiveKeySwitcher, text, PaneFactory, + PromptSignal, }; use super::keymap; @@ -14,10 +14,10 @@ use super::keymap; pub struct Renderer { /// Manages key mappings for the renderer. pub keymap: RefCell>, - /// A snapshot of the title's renderer state. - pub title_snapshot: Snapshot, - /// A snapshot of the checkbox's renderer state. - pub checkbox_snapshot: Snapshot, + /// A title's renderer state. + pub title_state: text::State, + /// A checkbox's renderer state. + pub checkbox_state: checkbox::State, } impl crate::Finalizer for Renderer { @@ -25,8 +25,7 @@ impl crate::Finalizer for Renderer { fn finalize(&mut self) -> anyhow::Result { Ok(self - .checkbox_snapshot - .after() + .checkbox_state .checkbox .get() .iter() @@ -38,8 +37,8 @@ impl crate::Finalizer for Renderer { impl crate::Renderer for Renderer { fn create_panes(&self, width: u16, height: u16) -> Vec { vec![ - self.title_snapshot.create_pane(width, height), - self.checkbox_snapshot.create_pane(width, height), + self.title_state.create_pane(width, height), + self.checkbox_state.create_pane(width, height), ] } diff --git a/promkit/src/preset/json.rs b/promkit/src/preset/json.rs index 67bd9481..07e255d3 100644 --- a/promkit/src/preset/json.rs +++ b/promkit/src/preset/json.rs @@ -3,7 +3,6 @@ use std::cell::RefCell; use crate::{ crossterm::style::{Attribute, Attributes, Color, ContentStyle}, jsonstream::{self, JsonStream}, - snapshot::Snapshot, style::StyleBuilder, switch::ActiveKeySwitcher, text, Prompt, @@ -96,8 +95,8 @@ impl Json { Ok(Prompt { renderer: render::Renderer { keymap: RefCell::new(self.keymap), - title_snapshot: Snapshot::::new(self.title_state), - json_snapshot: Snapshot::::new(self.json_state), + title_state: self.title_state, + json_state: self.json_state, }, }) } diff --git a/promkit/src/preset/json/keymap.rs b/promkit/src/preset/json/keymap.rs index 0a5f5366..2fd20470 100644 --- a/promkit/src/preset/json/keymap.rs +++ b/promkit/src/preset/json/keymap.rs @@ -24,8 +24,6 @@ pub fn default( event: &Event, renderer: &mut preset::json::render::Renderer, ) -> anyhow::Result { - let json_after_mut = renderer.json_snapshot.after_mut(); - match event { Event::Key(KeyEvent { code: KeyCode::Enter, @@ -53,7 +51,7 @@ pub fn default( row: _, modifiers: KeyModifiers::NONE, }) => { - json_after_mut.stream.backward(); + renderer.json_state.stream.backward(); } Event::Key(KeyEvent { @@ -68,7 +66,7 @@ pub fn default( row: _, modifiers: KeyModifiers::NONE, }) => { - json_after_mut.stream.forward(); + renderer.json_state.stream.forward(); } // Fold/Unfold @@ -78,7 +76,7 @@ pub fn default( kind: KeyEventKind::Press, state: KeyEventState::NONE, }) => { - json_after_mut.stream.toggle(); + renderer.json_state.stream.toggle(); } _ => (), diff --git a/promkit/src/preset/json/render.rs b/promkit/src/preset/json/render.rs index 57831491..3ed18f32 100644 --- a/promkit/src/preset/json/render.rs +++ b/promkit/src/preset/json/render.rs @@ -1,8 +1,8 @@ use std::cell::RefCell; use crate::{ - crossterm::event::Event, jsonstream, pane::Pane, snapshot::Snapshot, switch::ActiveKeySwitcher, - text, PaneFactory, PromptSignal, + crossterm::event::Event, jsonstream, pane::Pane, switch::ActiveKeySwitcher, text, PaneFactory, + PromptSignal, }; use super::keymap; @@ -12,10 +12,10 @@ use super::keymap; pub struct Renderer { /// Manages key mappings specific to this renderer. pub keymap: RefCell>, - /// Snapshot of the renderer used for the title. - pub title_snapshot: Snapshot, - /// Snapshot of the renderer used for JSON content. - pub json_snapshot: Snapshot, + /// A renderer used for the title. + pub title_state: text::State, + /// A renderer used for JSON content. + pub json_state: jsonstream::State, } impl crate::Finalizer for Renderer { @@ -29,8 +29,8 @@ impl crate::Finalizer for Renderer { impl crate::Renderer for Renderer { fn create_panes(&self, width: u16, height: u16) -> Vec { vec![ - self.title_snapshot.create_pane(width, height), - self.json_snapshot.create_pane(width, height), + self.title_state.create_pane(width, height), + self.json_state.create_pane(width, height), ] } diff --git a/promkit/src/preset/listbox.rs b/promkit/src/preset/listbox.rs index e27f8f79..ba7997a3 100644 --- a/promkit/src/preset/listbox.rs +++ b/promkit/src/preset/listbox.rs @@ -3,7 +3,6 @@ use std::{cell::RefCell, fmt::Display}; use crate::{ crossterm::style::{Attribute, Attributes, Color, ContentStyle}, listbox, - snapshot::Snapshot, style::StyleBuilder, switch::ActiveKeySwitcher, text, Prompt, @@ -96,8 +95,8 @@ impl Listbox { Ok(Prompt { renderer: render::Renderer { keymap: RefCell::new(self.keymap), - title_snapshot: Snapshot::::new(self.title_state), - listbox_snapshot: Snapshot::::new(self.listbox_state), + title_state: self.title_state, + listbox_state: self.listbox_state, }, }) } diff --git a/promkit/src/preset/listbox/keymap.rs b/promkit/src/preset/listbox/keymap.rs index 1ec0492f..1c1d7613 100644 --- a/promkit/src/preset/listbox/keymap.rs +++ b/promkit/src/preset/listbox/keymap.rs @@ -23,8 +23,6 @@ pub fn default( event: &Event, renderer: &mut preset::listbox::render::Renderer, ) -> anyhow::Result { - let listbox_after_mut = renderer.listbox_snapshot.after_mut(); - match event { Event::Key(KeyEvent { code: KeyCode::Enter, @@ -52,7 +50,7 @@ pub fn default( row: _, modifiers: KeyModifiers::NONE, }) => { - listbox_after_mut.listbox.backward(); + renderer.listbox_state.listbox.backward(); } Event::Key(KeyEvent { @@ -67,7 +65,7 @@ pub fn default( row: _, modifiers: KeyModifiers::NONE, }) => { - listbox_after_mut.listbox.forward(); + renderer.listbox_state.listbox.forward(); } _ => (), diff --git a/promkit/src/preset/listbox/render.rs b/promkit/src/preset/listbox/render.rs index 1c8a6327..aae8fb09 100644 --- a/promkit/src/preset/listbox/render.rs +++ b/promkit/src/preset/listbox/render.rs @@ -1,31 +1,31 @@ use std::cell::RefCell; use crate::{ - crossterm::event::Event, listbox, pane::Pane, snapshot::Snapshot, switch::ActiveKeySwitcher, - text, PaneFactory, PromptSignal, + crossterm::event::Event, listbox, pane::Pane, switch::ActiveKeySwitcher, text, PaneFactory, + PromptSignal, }; use super::keymap; pub struct Renderer { pub keymap: RefCell>, - pub title_snapshot: Snapshot, - pub listbox_snapshot: Snapshot, + pub title_state: text::State, + pub listbox_state: listbox::State, } impl crate::Finalizer for Renderer { type Return = String; fn finalize(&mut self) -> anyhow::Result { - Ok(self.listbox_snapshot.after().listbox.get().to_string()) + Ok(self.listbox_state.listbox.get().to_string()) } } impl crate::Renderer for Renderer { fn create_panes(&self, width: u16, height: u16) -> Vec { vec![ - self.title_snapshot.create_pane(width, height), - self.listbox_snapshot.create_pane(width, height), + self.title_state.create_pane(width, height), + self.listbox_state.create_pane(width, height), ] } diff --git a/promkit/src/preset/query_selector.rs b/promkit/src/preset/query_selector.rs index a2c679a0..4d7356a4 100644 --- a/promkit/src/preset/query_selector.rs +++ b/promkit/src/preset/query_selector.rs @@ -161,7 +161,7 @@ impl QuerySelector { Ok(Prompt { renderer: render::Renderer { keymap: RefCell::new(self.keymap), - title_snapshot: Snapshot::::new(self.title_state), + title_state: self.title_state, text_editor_snapshot: Snapshot::::new(self.text_editor_state), listbox_snapshot: Snapshot::::new(self.listbox_state), filter: self.filter, diff --git a/promkit/src/preset/query_selector/render.rs b/promkit/src/preset/query_selector/render.rs index ab034fed..9018f13d 100644 --- a/promkit/src/preset/query_selector/render.rs +++ b/promkit/src/preset/query_selector/render.rs @@ -21,8 +21,8 @@ use super::keymap; pub struct Renderer { /// Manages key mappings specific to this renderer. pub keymap: RefCell>, - /// Snapshot of the title renderer. - pub title_snapshot: Snapshot, + /// A title renderer. + pub title_state: text::State, /// Snapshot of the text editor renderer. pub text_editor_snapshot: Snapshot, /// Snapshot of the listbox renderer. @@ -41,7 +41,7 @@ impl crate::Finalizer for Renderer { impl crate::Renderer for Renderer { fn create_panes(&self, width: u16, height: u16) -> Vec { vec![ - self.title_snapshot.create_pane(width, height), + self.title_state.create_pane(width, height), self.text_editor_snapshot.create_pane(width, height), self.listbox_snapshot.create_pane(width, height), ] diff --git a/promkit/src/preset/readline.rs b/promkit/src/preset/readline.rs index 85875c5b..37b880da 100644 --- a/promkit/src/preset/readline.rs +++ b/promkit/src/preset/readline.rs @@ -178,7 +178,7 @@ impl Readline { Ok(Prompt { renderer: render::Renderer { keymap: RefCell::new(self.keymap), - title_snapshot: Snapshot::::new(self.title_state), + title_state: self.title_state, text_editor_snapshot: Snapshot::::new(self.text_editor_state), suggest: self.suggest, suggest_snapshot: Snapshot::::new(self.suggest_state), diff --git a/promkit/src/preset/readline/render.rs b/promkit/src/preset/readline/render.rs index 6f5cf9e4..b00da219 100644 --- a/promkit/src/preset/readline/render.rs +++ b/promkit/src/preset/readline/render.rs @@ -13,8 +13,8 @@ use super::keymap; pub struct Renderer { /// Manages key bindings and their associated actions within the readline interface. pub keymap: RefCell>, - /// Holds a snapshot of the title's renderer state, used for rendering the title section. - pub title_snapshot: Snapshot, + /// Holds a title's renderer state, used for rendering the title section. + pub title_state: text::State, /// Holds a snapshot of the text editor's renderer state, used for rendering the text input area. pub text_editor_snapshot: Snapshot, /// Optional suggest component for autocomplete functionality. @@ -45,7 +45,7 @@ impl crate::Finalizer for Renderer { impl crate::Renderer for Renderer { fn create_panes(&self, width: u16, height: u16) -> Vec { vec![ - self.title_snapshot.create_pane(width, height), + self.title_state.create_pane(width, height), self.error_message_snapshot.create_pane(width, height), self.text_editor_snapshot.create_pane(width, height), self.suggest_snapshot.create_pane(width, height), diff --git a/promkit/src/preset/tree.rs b/promkit/src/preset/tree.rs index 0904ba85..41ff3f5a 100644 --- a/promkit/src/preset/tree.rs +++ b/promkit/src/preset/tree.rs @@ -2,7 +2,6 @@ use std::cell::RefCell; use crate::{ crossterm::style::{Attribute, Attributes, Color, ContentStyle}, - snapshot::Snapshot, style::StyleBuilder, switch::ActiveKeySwitcher, text, @@ -110,8 +109,8 @@ impl Tree { Ok(Prompt { renderer: render::Renderer { keymap: RefCell::new(self.keymap), - title_snapshot: Snapshot::::new(self.title_state), - tree_snapshot: Snapshot::::new(self.tree_state), + title_state: self.title_state, + tree_state: self.tree_state, }, }) } diff --git a/promkit/src/preset/tree/keymap.rs b/promkit/src/preset/tree/keymap.rs index a9b964c2..7b201563 100644 --- a/promkit/src/preset/tree/keymap.rs +++ b/promkit/src/preset/tree/keymap.rs @@ -24,8 +24,6 @@ pub fn default( event: &Event, renderer: &mut preset::tree::render::Renderer, ) -> anyhow::Result { - let tree_after_mut = renderer.tree_snapshot.after_mut(); - match event { Event::Key(KeyEvent { code: KeyCode::Enter, @@ -47,7 +45,7 @@ pub fn default( kind: KeyEventKind::Press, state: KeyEventState::NONE, }) => { - tree_after_mut.tree.backward(); + renderer.tree_state.tree.backward(); } Event::Mouse(MouseEvent { kind: MouseEventKind::ScrollUp, @@ -55,7 +53,7 @@ pub fn default( row: _, modifiers: KeyModifiers::NONE, }) => { - tree_after_mut.tree.backward(); + renderer.tree_state.tree.backward(); } Event::Key(KeyEvent { @@ -64,7 +62,7 @@ pub fn default( kind: KeyEventKind::Press, state: KeyEventState::NONE, }) => { - tree_after_mut.tree.forward(); + renderer.tree_state.tree.forward(); } Event::Mouse(MouseEvent { kind: MouseEventKind::ScrollDown, @@ -72,7 +70,7 @@ pub fn default( row: _, modifiers: KeyModifiers::NONE, }) => { - tree_after_mut.tree.forward(); + renderer.tree_state.tree.forward(); } // Fold/Unfold @@ -82,7 +80,7 @@ pub fn default( kind: KeyEventKind::Press, state: KeyEventState::NONE, }) => { - tree_after_mut.tree.toggle(); + renderer.tree_state.tree.toggle(); } _ => (), diff --git a/promkit/src/preset/tree/render.rs b/promkit/src/preset/tree/render.rs index a1aa47e6..b75ff708 100644 --- a/promkit/src/preset/tree/render.rs +++ b/promkit/src/preset/tree/render.rs @@ -1,8 +1,8 @@ use std::cell::RefCell; use crate::{ - crossterm::event::Event, pane::Pane, snapshot::Snapshot, switch::ActiveKeySwitcher, text, tree, - PaneFactory, PromptSignal, + crossterm::event::Event, pane::Pane, switch::ActiveKeySwitcher, text, tree, PaneFactory, + PromptSignal, }; use super::keymap; @@ -13,24 +13,24 @@ pub struct Renderer { /// Manages key mappings specific to this renderer. pub keymap: RefCell>, /// Snapshot of the title renderer. - pub title_snapshot: Snapshot, + pub title_state: text::State, /// Snapshot of the tree renderer. - pub tree_snapshot: Snapshot, + pub tree_state: tree::State, } impl crate::Finalizer for Renderer { type Return = Vec; fn finalize(&mut self) -> anyhow::Result { - Ok(self.tree_snapshot.after().tree.get()) + Ok(self.tree_state.tree.get()) } } impl crate::Renderer for Renderer { fn create_panes(&self, width: u16, height: u16) -> Vec { vec![ - self.title_snapshot.create_pane(width, height), - self.tree_snapshot.create_pane(width, height), + self.title_state.create_pane(width, height), + self.tree_state.create_pane(width, height), ] } From bf8b7923c115718b16deb0a9b26c226a1d8da93c Mon Sep 17 00:00:00 2001 From: ynqa Date: Sat, 14 Dec 2024 14:19:50 +0900 Subject: [PATCH 26/44] example: fixup json --- promkit/examples/json.rs | 251 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 234 insertions(+), 17 deletions(-) diff --git a/promkit/examples/json.rs b/promkit/examples/json.rs index b02b0d89..09d060b6 100644 --- a/promkit/examples/json.rs +++ b/promkit/examples/json.rs @@ -1,29 +1,246 @@ -use promkit::{json::JsonStream, preset::json::Json, serde_json::Deserializer}; +use promkit::{jsonstream::JsonStream, preset::json::Json}; +use serde_json::Deserializer; fn main() -> anyhow::Result<()> { let stream = JsonStream::new( Deserializer::from_str( - r#"{ - "number": 9, - "map": { - "entry1": "first", - "entry2": "second" - }, - "list": [ - "abc", - "def" - ] - }"#, + r#" + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "annotations": { + "kubeadm.kubernetes.io/etcd.advertise-client-urls": "https://172.18.0.2:2379", + "kubernetes.io/config.hash": "9c4c3ba79af7ad68d939c568f053bfff", + "kubernetes.io/config.mirror": "9c4c3ba79af7ad68d939c568f053bfff", + "kubernetes.io/config.seen": "2024-10-12T12:53:27.751706220Z", + "kubernetes.io/config.source": "file" + }, + "creationTimestamp": "2024-10-12T12:53:31Z", + "labels": { + "component": "etcd", + "tier": "control-plane" + }, + "name": "etcd-kind-control-plane", + "namespace": "kube-system", + "ownerReferences": [ + { + "apiVersion": "v1", + "controller": true, + "kind": "Node", + "name": "kind-control-plane", + "uid": "6cb2c3e5-1a73-4932-9cc5-6d69b80a9932" + } + ], + "resourceVersion": "192988", + "uid": "77465839-5a58-43b1-b754-55deed66d5ca" + }, + "spec": { + "containers": [ + { + "command": [ + "etcd", + "--advertise-client-urls=https://172.18.0.2:2379", + "--cert-file=/etc/kubernetes/pki/etcd/server.crt", + "--client-cert-auth=true", + "--data-dir=/var/lib/etcd", + "--experimental-initial-corrupt-check=true", + "--experimental-watch-progress-notify-interval=5s", + "--initial-advertise-peer-urls=https://172.18.0.2:2380", + "--initial-cluster=kind-control-plane=https://172.18.0.2:2380", + "--key-file=/etc/kubernetes/pki/etcd/server.key", + "--listen-client-urls=https://127.0.0.1:2379,https://172.18.0.2:2379", + "--listen-metrics-urls=http://127.0.0.1:2381", + "--listen-peer-urls=https://172.18.0.2:2380", + "--name=kind-control-plane", + "--peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt", + "--peer-client-cert-auth=true", + "--peer-key-file=/etc/kubernetes/pki/etcd/peer.key", + "--peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt", + "--snapshot-count=10000", + "--trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt" + ], + "image": "registry.k8s.io/etcd:3.5.15-0", + "imagePullPolicy": "IfNotPresent", + "livenessProbe": { + "failureThreshold": 8, + "httpGet": { + "host": "127.0.0.1", + "path": "/livez", + "port": 2381, + "scheme": "HTTP" + }, + "initialDelaySeconds": 10, + "periodSeconds": 10, + "successThreshold": 1, + "timeoutSeconds": 15 + }, + "name": "etcd", + "readinessProbe": { + "failureThreshold": 3, + "httpGet": { + "host": "127.0.0.1", + "path": "/readyz", + "port": 2381, + "scheme": "HTTP" + }, + "periodSeconds": 1, + "successThreshold": 1, + "timeoutSeconds": 15 + }, + "resources": { + "requests": { + "cpu": "100m", + "memory": "100Mi" + } + }, + "startupProbe": { + "failureThreshold": 24, + "httpGet": { + "host": "127.0.0.1", + "path": "/readyz", + "port": 2381, + "scheme": "HTTP" + }, + "initialDelaySeconds": 10, + "periodSeconds": 10, + "successThreshold": 1, + "timeoutSeconds": 15 + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/lib/etcd", + "name": "etcd-data" + }, + { + "mountPath": "/etc/kubernetes/pki/etcd", + "name": "etcd-certs" + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "hostNetwork": true, + "nodeName": "kind-control-plane", + "preemptionPolicy": "PreemptLowerPriority", + "priority": 2000001000, + "priorityClassName": "system-node-critical", + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": { + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "operator": "Exists" + } + ], + "volumes": [ + { + "hostPath": { + "path": "/etc/kubernetes/pki/etcd", + "type": "DirectoryOrCreate" + }, + "name": "etcd-certs" + }, + { + "hostPath": { + "path": "/var/lib/etcd", + "type": "DirectoryOrCreate" + }, + "name": "etcd-data" + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2024-12-06T13:28:35Z", + "status": "True", + "type": "PodReadyToStartContainers" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2024-12-06T13:28:34Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2024-12-06T13:28:50Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2024-12-06T13:28:50Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2024-12-06T13:28:34Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "containerd://de0d57479a3ac10e213df6ea4fc1d648ad4d70d4ddf1b95a7999d0050171a41e", + "image": "registry.k8s.io/etcd:3.5.15-0", + "imageID": "sha256:27e3830e1402783674d8b594038967deea9d51f0d91b34c93c8f39d2f68af7da", + "lastState": { + "terminated": { + "containerID": "containerd://28d1a65bd9cfa40624a0c17979208f66a5cc7f496a57fa9a879907bb936f57b3", + "exitCode": 255, + "finishedAt": "2024-12-06T13:28:31Z", + "reason": "Unknown", + "startedAt": "2024-11-04T15:14:19Z" + } + }, + "name": "etcd", + "ready": true, + "restartCount": 2, + "started": true, + "state": { + "running": { + "startedAt": "2024-12-06T13:28:35Z" + } + } + } + ], + "hostIP": "172.18.0.2", + "hostIPs": [ + { + "ip": "172.18.0.2" + } + ], + "phase": "Running", + "podIP": "172.18.0.2", + "podIPs": [ + { + "ip": "172.18.0.2" + } + ], + "qosClass": "Burstable", + "startTime": "2024-12-06T13:28:34Z" + } + } + "#, ) .into_iter::() .filter_map(serde_json::Result::ok), - None, ); - let mut p = Json::new(stream) - .title("JSON viewer") - .json_lines(5) - .prompt()?; + let mut p = Json::new(stream).title("JSON viewer").prompt()?; println!("result: {:?}", p.run()?); Ok(()) } From 740b90a36536b2fdad4d3fe6fe4fec43d9a1170d Mon Sep 17 00:00:00 2001 From: ynqa Date: Sat, 14 Dec 2024 20:52:50 +0900 Subject: [PATCH 27/44] chore: do not require clone() for create_rows --- promkit/examples/json.rs | 4 +++- promkit/src/core/jsonstream.rs | 2 +- promkit/src/jsonz.rs | 2 +- promkit/tests/jsonz_create_rows_test.rs | 13 +++++++------ promkit/tests/jsonz_down_test.rs | 4 ++-- promkit/tests/jsonz_extract_test.rs | 10 +++++----- promkit/tests/jsonz_jsonl_test.rs | 14 ++++++++------ promkit/tests/jsonz_toggle_test.rs | 4 ++-- promkit/tests/jsonz_up_test.rs | 2 +- 9 files changed, 30 insertions(+), 25 deletions(-) diff --git a/promkit/examples/json.rs b/promkit/examples/json.rs index 09d060b6..386bfa12 100644 --- a/promkit/examples/json.rs +++ b/promkit/examples/json.rs @@ -237,7 +237,9 @@ fn main() -> anyhow::Result<()> { "#, ) .into_iter::() - .filter_map(serde_json::Result::ok), + .filter_map(serde_json::Result::ok) + .collect::>() + .iter(), ); let mut p = Json::new(stream).title("JSON viewer").prompt()?; diff --git a/promkit/src/core/jsonstream.rs b/promkit/src/core/jsonstream.rs index de6cbb83..0b04245d 100644 --- a/promkit/src/core/jsonstream.rs +++ b/promkit/src/core/jsonstream.rs @@ -11,7 +11,7 @@ pub struct JsonStream { } impl JsonStream { - pub fn new>(iter: I) -> Self { + pub fn new<'a, I: IntoIterator>(iter: I) -> Self { Self { rows: jsonz::create_rows(iter), position: 0, diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index e380b386..9db631f7 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -321,7 +321,7 @@ fn process_value( } } -pub fn create_rows>(iter: T) -> Vec { +pub fn create_rows<'a, T: IntoIterator>(iter: T) -> Vec { let mut rows = Vec::new(); for value in iter { process_value(&value, &mut rows, 0, None); diff --git a/promkit/tests/jsonz_create_rows_test.rs b/promkit/tests/jsonz_create_rows_test.rs index 8ea35193..38c0fd3d 100644 --- a/promkit/tests/jsonz_create_rows_test.rs +++ b/promkit/tests/jsonz_create_rows_test.rs @@ -7,16 +7,17 @@ mod create_rows { #[test] fn test_empty_containers() { - let inputs = Deserializer::from_str( + let values: Vec<_> = Deserializer::from_str( r#" {} [] "#, ) .into_iter::() - .filter_map(serde_json::Result::ok); + .filter_map(serde_json::Result::ok) + .collect(); - let rows = create_rows(inputs); + let rows = create_rows(values.iter()); assert_eq!(rows.len(), 2); assert_eq!( @@ -57,7 +58,7 @@ mod create_rows { ) .unwrap(); - let rows = create_rows([input]); + let rows = create_rows([&input]); assert_eq!( rows[0], @@ -164,7 +165,7 @@ mod create_rows { ) .unwrap(); - let rows = create_rows([input]); + let rows = create_rows([&input]); assert_eq!( rows[0], @@ -279,7 +280,7 @@ mod create_rows { ) .unwrap(); - let rows = create_rows([input]); + let rows = create_rows([&input]); assert_eq!( rows[0], diff --git a/promkit/tests/jsonz_down_test.rs b/promkit/tests/jsonz_down_test.rs index 4745bbc7..49794efd 100644 --- a/promkit/tests/jsonz_down_test.rs +++ b/promkit/tests/jsonz_down_test.rs @@ -22,7 +22,7 @@ mod down { ) .unwrap(); - let mut rows = create_rows([input]); + let mut rows = create_rows([&input]); rows.toggle(1); rows.toggle(4); @@ -45,7 +45,7 @@ mod down { ) .unwrap(); - let mut rows = create_rows([input]); + let mut rows = create_rows([&input]); rows.toggle(0); assert_eq!(rows.down(0), 0); diff --git a/promkit/tests/jsonz_extract_test.rs b/promkit/tests/jsonz_extract_test.rs index 96d73ffb..535ae6c6 100644 --- a/promkit/tests/jsonz_extract_test.rs +++ b/promkit/tests/jsonz_extract_test.rs @@ -17,7 +17,7 @@ mod extract { ) .unwrap(); - let rows = create_rows([input]); + let rows = create_rows([&input]); let extracted = rows.extract(0, 2); assert_eq!(extracted.len(), 2); @@ -77,7 +77,7 @@ mod extract { ) .unwrap(); - let mut rows = create_rows([input]); + let mut rows = create_rows([&input]); // { // "object": {...}, @@ -139,7 +139,7 @@ mod extract { ) .unwrap(); - let rows = create_rows([input]); + let rows = create_rows([&input]); let extracted = rows.extract(2, 3); assert_eq!(extracted.len(), 3); @@ -189,7 +189,7 @@ mod extract { ) .unwrap(); - let rows = create_rows([input]); + let rows = create_rows([&input]); let extracted = rows.extract(0, 10); assert_eq!(extracted.len(), 4); @@ -232,7 +232,7 @@ mod extract { ) .unwrap(); - let mut rows = create_rows([input]); + let mut rows = create_rows([&input]); // { // "obj1": { diff --git a/promkit/tests/jsonz_jsonl_test.rs b/promkit/tests/jsonz_jsonl_test.rs index a6936424..5887d944 100644 --- a/promkit/tests/jsonz_jsonl_test.rs +++ b/promkit/tests/jsonz_jsonl_test.rs @@ -6,7 +6,7 @@ mod jsonl { #[test] fn test_basic_jsonl() { - let inputs = Deserializer::from_str( + let inputs: Vec<_> = Deserializer::from_str( r#" { "name": "Alice", @@ -23,9 +23,10 @@ mod jsonl { "#, ) .into_iter::() - .filter_map(serde_json::Result::ok); + .filter_map(serde_json::Result::ok) + .collect(); - let mut rows = create_rows(inputs); + let mut rows = create_rows(inputs.iter()); assert_eq!( rows[0], @@ -133,7 +134,7 @@ mod jsonl { #[test] fn test_mixed_jsonl() { - let inputs = Deserializer::from_str( + let inputs: Vec<_> = Deserializer::from_str( r#" { "array": [ @@ -156,9 +157,10 @@ mod jsonl { "#, ) .into_iter::() - .filter_map(serde_json::Result::ok); + .filter_map(serde_json::Result::ok) + .collect(); - let mut rows = create_rows(inputs); + let mut rows = create_rows(inputs.iter()); assert_eq!( rows[0], diff --git a/promkit/tests/jsonz_toggle_test.rs b/promkit/tests/jsonz_toggle_test.rs index 2800bddd..fa0dc415 100644 --- a/promkit/tests/jsonz_toggle_test.rs +++ b/promkit/tests/jsonz_toggle_test.rs @@ -22,7 +22,7 @@ mod toggle { ) .unwrap(); - let mut rows = create_rows([input]); + let mut rows = create_rows([&input]); let index = rows.toggle(1); assert_eq!(index, 1); @@ -154,7 +154,7 @@ mod toggle { ) .unwrap(); - let mut rows = create_rows([input]); + let mut rows = create_rows([&input]); let index = rows.toggle(3); assert_eq!(index, 1); diff --git a/promkit/tests/jsonz_up_test.rs b/promkit/tests/jsonz_up_test.rs index 1b85e9df..aa02d631 100644 --- a/promkit/tests/jsonz_up_test.rs +++ b/promkit/tests/jsonz_up_test.rs @@ -22,7 +22,7 @@ mod up { ) .unwrap(); - let mut rows = create_rows([input]); + let mut rows = create_rows([&input]); rows.toggle(1); rows.toggle(4); From 0b985aab4b3abbeecde8d66fc00306ef39f473c3 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sat, 14 Dec 2024 23:22:42 +0900 Subject: [PATCH 28/44] cargo clippy --- promkit/src/jsonz.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index 9db631f7..dc6631e7 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -324,7 +324,7 @@ fn process_value( pub fn create_rows<'a, T: IntoIterator>(iter: T) -> Vec { let mut rows = Vec::new(); for value in iter { - process_value(&value, &mut rows, 0, None); + process_value(value, &mut rows, 0, None); } rows } From 9352380b6ca13078e90636020b5398c6757511c6 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sat, 14 Dec 2024 23:53:19 +0900 Subject: [PATCH 29/44] chore: define tests for `get_all_paths` in advance --- promkit/src/jsonz.rs | 8 ++ promkit/tests/jsonz_get_all_paths_test.rs | 131 ++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 promkit/tests/jsonz_get_all_paths_test.rs diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index dc6631e7..99a95101 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + #[derive(Clone, Debug, PartialEq)] pub enum ContainerType { Object, @@ -328,3 +330,9 @@ pub fn create_rows<'a, T: IntoIterator>(iter: T) - } rows } + +pub fn get_all_paths<'a, T: IntoIterator>( + iter: T, +) -> HashSet<&'a str> { + todo!() +} diff --git a/promkit/tests/jsonz_get_all_paths_test.rs b/promkit/tests/jsonz_get_all_paths_test.rs new file mode 100644 index 00000000..cc1f0716 --- /dev/null +++ b/promkit/tests/jsonz_get_all_paths_test.rs @@ -0,0 +1,131 @@ +#[cfg(test)] +mod get_all_paths { + use std::{collections::HashSet, str::FromStr}; + + use promkit::jsonz; + use serde_json::Deserializer; + + #[test] + fn test_get_all_paths() { + let v = serde_json::Value::from_str( + r#"{ + "string": "value", + "number": 42, + "boolean": true, + "null": null, + "empty_object": {}, + "empty_array": [], + "array": ["a", "b", "c"], + "nested": { + "field1": "value1", + "field2": { + "inner": "value2" + } + }, + "mixed_array": [ + { + "name": "first", + "values": [1, 2, 3] + }, + { + "name": "second", + "values": [] + } + ], + "complex": { + "data": [ + { + "id": 1, + "items": [ + {"status": "active"}, + {"status": "inactive"} + ] + }, + { + "id": 2, + "items": [] + } + ] + } + }"#, + ) + .unwrap(); + + let actual = jsonz::get_all_paths([&v]); + let expected = HashSet::from_iter([ + ".", + ".array", + ".array[0]", + ".array[1]", + ".array[2]", + ".boolean", + ".complex", + ".complex.data", + ".complex.data[0]", + ".complex.data[0].id", + ".complex.data[0].items", + ".complex.data[0].items[0]", + ".complex.data[0].items[0].status", + ".complex.data[0].items[1]", + ".complex.data[0].items[1].status", + ".complex.data[1]", + ".complex.data[1].id", + ".complex.data[1].items", + ".empty_array", + ".empty_object", + ".mixed_array", + ".mixed_array[0]", + ".mixed_array[0].name", + ".mixed_array[0].values", + ".mixed_array[0].values[0]", + ".mixed_array[0].values[1]", + ".mixed_array[0].values[2]", + ".mixed_array[1]", + ".mixed_array[1].name", + ".mixed_array[1].values", + ".nested", + ".nested.field1", + ".nested.field2", + ".nested.field2.inner", + ".null", + ".number", + ".string", + ]); + + assert_eq!(actual, expected, "Paths do not match expected values"); + } + + #[test] + fn test_get_all_paths_for_jsonl() { + let binding = Deserializer::from_str( + r#" + {"user": "alice", "age": 30, "hobbies": ["reading", "gaming"]} + {"user": "bob", "age": 25, "settings": {"theme": "dark", "notifications": true}} + {"user": "carol", "age": 28, "address": {"city": "Tokyo", "details": {"street": "Sakura", "number": 123}}} + "#, + ) + .into_iter::() + .filter_map(serde_json::Result::ok) + .collect::>(); + + let actual = jsonz::get_all_paths(binding.iter()); + let expected = HashSet::from_iter([ + ".", + ".address", + ".address.city", + ".address.details", + ".address.details.number", + ".address.details.street", + ".age", + ".hobbies", + ".hobbies[0]", + ".hobbies[1]", + ".settings", + ".settings.notifications", + ".settings.theme", + ".user", + ]); + + assert_eq!(actual, expected, "Paths do not match expected values"); + } +} From bed0863e27ed853b4ca89651c98554134de1cbbe Mon Sep 17 00:00:00 2001 From: ynqa Date: Sun, 15 Dec 2024 00:09:44 +0900 Subject: [PATCH 30/44] feat: get_all_paths --- promkit/src/jsonz.rs | 32 +++- promkit/tests/jsonz_get_all_paths_test.rs | 188 ++++++++++++---------- 2 files changed, 129 insertions(+), 91 deletions(-) diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index 99a95101..1d3bd4e8 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -331,8 +331,36 @@ pub fn create_rows<'a, T: IntoIterator>(iter: T) - rows } +fn collect_paths(value: &serde_json::Value, current_path: &str, paths: &mut HashSet) { + paths.insert(current_path.to_string()); + + match value { + serde_json::Value::Object(obj) => { + for (key, val) in obj { + let new_path = if current_path == "." { + format!(".{}", key) + } else { + format!("{}.{}", current_path, key) + }; + collect_paths(val, &new_path, paths); + } + } + serde_json::Value::Array(arr) => { + for (i, val) in arr.iter().enumerate() { + let new_path = format!("{}[{}]", current_path, i); + collect_paths(val, &new_path, paths); + } + } + _ => {} + } +} + pub fn get_all_paths<'a, T: IntoIterator>( iter: T, -) -> HashSet<&'a str> { - todo!() +) -> HashSet { + let mut paths = HashSet::new(); + for value in iter { + collect_paths(value, ".", &mut paths); + } + paths } diff --git a/promkit/tests/jsonz_get_all_paths_test.rs b/promkit/tests/jsonz_get_all_paths_test.rs index cc1f0716..8bac089a 100644 --- a/promkit/tests/jsonz_get_all_paths_test.rs +++ b/promkit/tests/jsonz_get_all_paths_test.rs @@ -8,89 +8,95 @@ mod get_all_paths { #[test] fn test_get_all_paths() { let v = serde_json::Value::from_str( - r#"{ - "string": "value", - "number": 42, - "boolean": true, - "null": null, - "empty_object": {}, - "empty_array": [], - "array": ["a", "b", "c"], - "nested": { - "field1": "value1", - "field2": { - "inner": "value2" - } - }, - "mixed_array": [ - { - "name": "first", - "values": [1, 2, 3] + r#" + { + "string": "value", + "number": 42, + "boolean": true, + "null": null, + "empty_object": {}, + "empty_array": [], + "array": ["a", "b", "c"], + "nested": { + "field1": "value1", + "field2": { + "inner": "value2" + } }, - { - "name": "second", - "values": [] - } - ], - "complex": { - "data": [ + "mixed_array": [ { - "id": 1, - "items": [ - {"status": "active"}, - {"status": "inactive"} - ] + "name": "first", + "values": [1, 2, 3] }, { - "id": 2, - "items": [] + "name": "second", + "values": [] } - ] + ], + "complex": { + "data": [ + { + "id": 1, + "items": [ + {"status": "active"}, + {"status": "inactive"} + ] + }, + { + "id": 2, + "items": [] + } + ] + } } - }"#, + "#, ) .unwrap(); let actual = jsonz::get_all_paths([&v]); - let expected = HashSet::from_iter([ - ".", - ".array", - ".array[0]", - ".array[1]", - ".array[2]", - ".boolean", - ".complex", - ".complex.data", - ".complex.data[0]", - ".complex.data[0].id", - ".complex.data[0].items", - ".complex.data[0].items[0]", - ".complex.data[0].items[0].status", - ".complex.data[0].items[1]", - ".complex.data[0].items[1].status", - ".complex.data[1]", - ".complex.data[1].id", - ".complex.data[1].items", - ".empty_array", - ".empty_object", - ".mixed_array", - ".mixed_array[0]", - ".mixed_array[0].name", - ".mixed_array[0].values", - ".mixed_array[0].values[0]", - ".mixed_array[0].values[1]", - ".mixed_array[0].values[2]", - ".mixed_array[1]", - ".mixed_array[1].name", - ".mixed_array[1].values", - ".nested", - ".nested.field1", - ".nested.field2", - ".nested.field2.inner", - ".null", - ".number", - ".string", - ]); + let expected = HashSet::from_iter( + [ + ".", + ".array", + ".array[0]", + ".array[1]", + ".array[2]", + ".boolean", + ".complex", + ".complex.data", + ".complex.data[0]", + ".complex.data[0].id", + ".complex.data[0].items", + ".complex.data[0].items[0]", + ".complex.data[0].items[0].status", + ".complex.data[0].items[1]", + ".complex.data[0].items[1].status", + ".complex.data[1]", + ".complex.data[1].id", + ".complex.data[1].items", + ".empty_array", + ".empty_object", + ".mixed_array", + ".mixed_array[0]", + ".mixed_array[0].name", + ".mixed_array[0].values", + ".mixed_array[0].values[0]", + ".mixed_array[0].values[1]", + ".mixed_array[0].values[2]", + ".mixed_array[1]", + ".mixed_array[1].name", + ".mixed_array[1].values", + ".nested", + ".nested.field1", + ".nested.field2", + ".nested.field2.inner", + ".null", + ".number", + ".string", + ] + .into_iter() + .map(|e| e.to_string()), + ); assert_eq!(actual, expected, "Paths do not match expected values"); } @@ -109,22 +115,26 @@ mod get_all_paths { .collect::>(); let actual = jsonz::get_all_paths(binding.iter()); - let expected = HashSet::from_iter([ - ".", - ".address", - ".address.city", - ".address.details", - ".address.details.number", - ".address.details.street", - ".age", - ".hobbies", - ".hobbies[0]", - ".hobbies[1]", - ".settings", - ".settings.notifications", - ".settings.theme", - ".user", - ]); + let expected = HashSet::from_iter( + [ + ".", + ".address", + ".address.city", + ".address.details", + ".address.details.number", + ".address.details.street", + ".age", + ".hobbies", + ".hobbies[0]", + ".hobbies[1]", + ".settings", + ".settings.notifications", + ".settings.theme", + ".user", + ] + .into_iter() + .map(|e| e.to_string()), + ); assert_eq!(actual, expected, "Paths do not match expected values"); } From fd3b0cadabf2fb3da158304c9095da31bfa6d51d Mon Sep 17 00:00:00 2001 From: ynqa Date: Sun, 15 Dec 2024 10:41:46 +0900 Subject: [PATCH 31/44] chore: give &[] instread of Vec for format_rows --- promkit/src/core/jsonstream/state.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/promkit/src/core/jsonstream/state.rs b/promkit/src/core/jsonstream/state.rs index 042dc251..355ee81c 100644 --- a/promkit/src/core/jsonstream/state.rs +++ b/promkit/src/core/jsonstream/state.rs @@ -49,7 +49,7 @@ pub struct State { impl State { /// Formats a Vec into Vec with appropriate styling and width limits - fn format_rows(&self, rows: Vec, width: u16) -> Vec { + fn format_rows(&self, rows: &[super::jsonz::Row], width: u16) -> Vec { let mut formatted = Vec::new(); let width = width as usize; @@ -169,7 +169,7 @@ impl PaneFactory for State { }; let rows = self.stream.extract_rows_from_current(height); - let formatted_rows = self.format_rows(rows, width); + let formatted_rows = self.format_rows(&rows, width); Pane::new(formatted_rows, 0) } From f5f9778fcaa0c6ed91f2d1a8c3540d5a904a87e0 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sun, 15 Dec 2024 11:01:08 +0900 Subject: [PATCH 32/44] chore: separate formatter for Vec --- promkit/src/core/jsonstream/state.rs | 149 +------------------------- promkit/src/jsonz.rs | 2 + promkit/src/jsonz/format.rs | 152 +++++++++++++++++++++++++++ promkit/src/preset/json.rs | 37 ++++--- 4 files changed, 177 insertions(+), 163 deletions(-) create mode 100644 promkit/src/jsonz/format.rs diff --git a/promkit/src/core/jsonstream/state.rs b/promkit/src/core/jsonstream/state.rs index 355ee81c..5c19029a 100644 --- a/promkit/src/core/jsonstream/state.rs +++ b/promkit/src/core/jsonstream/state.rs @@ -1,9 +1,4 @@ -use crate::{ - crossterm::style::{Attribute, ContentStyle}, - grapheme::StyledGraphemes, - pane::Pane, - PaneFactory, -}; +use crate::{jsonz::format::RowFormatter, pane::Pane, PaneFactory}; use super::JsonStream; @@ -17,148 +12,10 @@ use super::JsonStream; pub struct State { pub stream: JsonStream, - /// Style for {}. - pub curly_brackets_style: ContentStyle, - /// Style for []. - pub square_brackets_style: ContentStyle, - /// Style for "key". - pub key_style: ContentStyle, - /// Style for string values. - pub string_value_style: ContentStyle, - /// Style for number values. - pub number_value_style: ContentStyle, - /// Style for boolean values. - pub boolean_value_style: ContentStyle, - /// Style for null values. - pub null_value_style: ContentStyle, - - /// Attribute for the selected line. - pub active_item_attribute: Attribute, - /// Attribute for unselected lines. - pub inactive_item_attribute: Attribute, + pub formatter: RowFormatter, /// Number of lines available for rendering. pub lines: Option, - - /// The number of spaces used for indentation in the rendered JSON structure. - /// This value multiplies with the indentation level of a JSON element to determine - /// the total indentation space. For example, an `indent` value of 4 means each - /// indentation level will be 4 spaces wide. - pub indent: usize, -} - -impl State { - /// Formats a Vec into Vec with appropriate styling and width limits - fn format_rows(&self, rows: &[super::jsonz::Row], width: u16) -> Vec { - let mut formatted = Vec::new(); - let width = width as usize; - - for (i, row) in rows.iter().enumerate() { - let indent = StyledGraphemes::from(" ".repeat(self.indent * row.depth)); - let mut parts = Vec::new(); - - if let Some(key) = &row.k { - parts.push( - StyledGraphemes::from(format!("\"{}\"", key)).apply_style(self.key_style), - ); - parts.push(StyledGraphemes::from(": ")); - } - - match &row.v { - super::jsonz::Value::Null => { - parts.push(StyledGraphemes::from("null").apply_style(self.null_value_style)); - } - super::jsonz::Value::Boolean(b) => { - parts.push( - StyledGraphemes::from(b.to_string()).apply_style(self.boolean_value_style), - ); - } - super::jsonz::Value::Number(n) => { - parts.push( - StyledGraphemes::from(n.to_string()).apply_style(self.number_value_style), - ); - } - super::jsonz::Value::String(s) => { - let escaped = s.replace('\n', "\\n"); - parts.push( - StyledGraphemes::from(format!("\"{}\"", escaped)) - .apply_style(self.string_value_style), - ); - } - super::jsonz::Value::Empty { typ } => { - let bracket_style = match typ { - super::jsonz::ContainerType::Object => self.curly_brackets_style, - super::jsonz::ContainerType::Array => self.square_brackets_style, - }; - parts.push( - StyledGraphemes::from(format!("{}{}", typ.open_str(), typ.close_str())) - .apply_style(bracket_style), - ); - } - super::jsonz::Value::Open { typ, collapsed, .. } => { - let bracket_style = match typ { - super::jsonz::ContainerType::Object => self.curly_brackets_style, - super::jsonz::ContainerType::Array => self.square_brackets_style, - }; - if *collapsed { - parts.push( - StyledGraphemes::from(typ.collapsed_preview()) - .apply_style(bracket_style), - ); - } else { - parts - .push(StyledGraphemes::from(typ.open_str()).apply_style(bracket_style)); - } - } - super::jsonz::Value::Close { typ, .. } => { - let bracket_style = match typ { - super::jsonz::ContainerType::Object => self.curly_brackets_style, - super::jsonz::ContainerType::Array => self.square_brackets_style, - }; - parts.push(StyledGraphemes::from(typ.close_str()).apply_style(bracket_style)); - } - } - - if i + 1 < rows.len() { - if let super::jsonz::Value::Close { .. } = rows[i + 1].v { - } else if let super::jsonz::Value::Open { .. } = rows[i].v { - } else { - parts.push(StyledGraphemes::from(",")); - } - } - - let mut content: StyledGraphemes = parts.into_iter().collect(); - - // Note that `extract_rows_from_current` - // returns rows starting from the current position, - // so the first row should always be highlighted as active - content = content.apply_attribute(if i == 0 { - self.active_item_attribute - } else { - self.inactive_item_attribute - }); - - let mut line: StyledGraphemes = vec![indent, content].into_iter().collect(); - - if line.widths() > width { - let ellipsis: StyledGraphemes = StyledGraphemes::from("…"); - let mut truncated = StyledGraphemes::default(); - let mut current_width = 0; - for g in line.iter() { - if current_width + g.width() + ellipsis.widths() > width { - break; - } - truncated.push_back(g.clone()); - current_width += g.width(); - } - line = vec![truncated, ellipsis].into_iter().collect(); - } - - formatted.push(line); - } - - formatted - } } impl PaneFactory for State { @@ -169,7 +26,7 @@ impl PaneFactory for State { }; let rows = self.stream.extract_rows_from_current(height); - let formatted_rows = self.format_rows(&rows, width); + let formatted_rows = self.formatter.format(&rows, width); Pane::new(formatted_rows, 0) } diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index 1d3bd4e8..57c962cb 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -1,5 +1,7 @@ use std::collections::HashSet; +pub mod format; + #[derive(Clone, Debug, PartialEq)] pub enum ContainerType { Object, diff --git a/promkit/src/jsonz/format.rs b/promkit/src/jsonz/format.rs new file mode 100644 index 00000000..2850e3bf --- /dev/null +++ b/promkit/src/jsonz/format.rs @@ -0,0 +1,152 @@ +use crate::{ + crossterm::style::{Attribute, ContentStyle}, + grapheme::StyledGraphemes, +}; + +use super::{ContainerType, Row, Value}; + +#[derive(Clone)] +pub struct RowFormatter { + /// Style for {}. + pub curly_brackets_style: ContentStyle, + /// Style for []. + pub square_brackets_style: ContentStyle, + /// Style for "key". + pub key_style: ContentStyle, + /// Style for string values. + pub string_value_style: ContentStyle, + /// Style for number values. + pub number_value_style: ContentStyle, + /// Style for boolean values. + pub boolean_value_style: ContentStyle, + /// Style for null values. + pub null_value_style: ContentStyle, + + /// Attribute for the selected line. + pub active_item_attribute: Attribute, + /// Attribute for unselected lines. + pub inactive_item_attribute: Attribute, + + /// The number of spaces used for indentation in the rendered JSON structure. + /// This value multiplies with the indentation level of a JSON element to determine + /// the total indentation space. For example, an `indent` value of 4 means each + /// indentation level will be 4 spaces wide. + pub indent: usize, +} + +impl RowFormatter { + /// Formats a Vec into Vec with appropriate styling and width limits + pub fn format(&self, rows: &[Row], width: u16) -> Vec { + let mut formatted = Vec::new(); + let width = width as usize; + + for (i, row) in rows.iter().enumerate() { + let indent = StyledGraphemes::from(" ".repeat(self.indent * row.depth)); + let mut parts = Vec::new(); + + if let Some(key) = &row.k { + parts.push( + StyledGraphemes::from(format!("\"{}\"", key)).apply_style(self.key_style), + ); + parts.push(StyledGraphemes::from(": ")); + } + + match &row.v { + Value::Null => { + parts.push(StyledGraphemes::from("null").apply_style(self.null_value_style)); + } + Value::Boolean(b) => { + parts.push( + StyledGraphemes::from(b.to_string()).apply_style(self.boolean_value_style), + ); + } + Value::Number(n) => { + parts.push( + StyledGraphemes::from(n.to_string()).apply_style(self.number_value_style), + ); + } + Value::String(s) => { + let escaped = s.replace('\n', "\\n"); + parts.push( + StyledGraphemes::from(format!("\"{}\"", escaped)) + .apply_style(self.string_value_style), + ); + } + Value::Empty { typ } => { + let bracket_style = match typ { + ContainerType::Object => self.curly_brackets_style, + ContainerType::Array => self.square_brackets_style, + }; + parts.push( + StyledGraphemes::from(format!("{}{}", typ.open_str(), typ.close_str())) + .apply_style(bracket_style), + ); + } + Value::Open { typ, collapsed, .. } => { + let bracket_style = match typ { + ContainerType::Object => self.curly_brackets_style, + ContainerType::Array => self.square_brackets_style, + }; + if *collapsed { + parts.push( + StyledGraphemes::from(typ.collapsed_preview()) + .apply_style(bracket_style), + ); + } else { + parts + .push(StyledGraphemes::from(typ.open_str()).apply_style(bracket_style)); + } + } + Value::Close { typ, .. } => { + let bracket_style = match typ { + ContainerType::Object => self.curly_brackets_style, + ContainerType::Array => self.square_brackets_style, + }; + parts.push(StyledGraphemes::from(typ.close_str()).apply_style(bracket_style)); + } + } + + if i + 1 < rows.len() { + if let Value::Close { .. } = rows[i + 1].v { + } else if let Value::Open { + collapsed: false, .. + } = rows[i].v + { + } else { + parts.push(StyledGraphemes::from(",")); + } + } + + let mut content: StyledGraphemes = parts.into_iter().collect(); + + // Note that `extract_rows_from_current` + // returns rows starting from the current position, + // so the first row should always be highlighted as active + content = content.apply_attribute(if i == 0 { + self.active_item_attribute + } else { + self.inactive_item_attribute + }); + + let mut line: StyledGraphemes = vec![indent, content].into_iter().collect(); + + if line.widths() > width { + let ellipsis: StyledGraphemes = StyledGraphemes::from("…"); + let mut truncated = StyledGraphemes::default(); + let mut current_width = 0; + for g in line.iter() { + if current_width + g.width() + ellipsis.widths() > width { + break; + } + truncated.push_back(g.clone()); + current_width += g.width(); + } + line = vec![truncated, ellipsis].into_iter().collect(); + } + + formatted.push(line); + } + + formatted + } +} diff --git a/promkit/src/preset/json.rs b/promkit/src/preset/json.rs index 07e255d3..df1e5dcb 100644 --- a/promkit/src/preset/json.rs +++ b/promkit/src/preset/json.rs @@ -3,6 +3,7 @@ use std::cell::RefCell; use crate::{ crossterm::style::{Attribute, Attributes, Color, ContentStyle}, jsonstream::{self, JsonStream}, + jsonz::format::RowFormatter, style::StyleBuilder, switch::ActiveKeySwitcher, text, Prompt, @@ -29,21 +30,23 @@ impl Json { }, json_state: jsonstream::State { stream, - curly_brackets_style: StyleBuilder::new() - .attrs(Attributes::from(Attribute::Bold)) - .build(), - square_brackets_style: StyleBuilder::new() - .attrs(Attributes::from(Attribute::Bold)) - .build(), - key_style: StyleBuilder::new().fgc(Color::DarkBlue).build(), - string_value_style: StyleBuilder::new().fgc(Color::DarkGreen).build(), - number_value_style: StyleBuilder::new().build(), - boolean_value_style: StyleBuilder::new().build(), - null_value_style: StyleBuilder::new().fgc(Color::DarkGrey).build(), - active_item_attribute: Attribute::Undercurled, - inactive_item_attribute: Attribute::Dim, + formatter: RowFormatter { + curly_brackets_style: StyleBuilder::new() + .attrs(Attributes::from(Attribute::Bold)) + .build(), + square_brackets_style: StyleBuilder::new() + .attrs(Attributes::from(Attribute::Bold)) + .build(), + key_style: StyleBuilder::new().fgc(Color::DarkBlue).build(), + string_value_style: StyleBuilder::new().fgc(Color::DarkGreen).build(), + number_value_style: StyleBuilder::new().build(), + boolean_value_style: StyleBuilder::new().build(), + null_value_style: StyleBuilder::new().fgc(Color::DarkGrey).build(), + active_item_attribute: Attribute::Undercurled, + inactive_item_attribute: Attribute::Dim, + indent: 2, + }, lines: Default::default(), - indent: 2, }, keymap: ActiveKeySwitcher::new("default", self::keymap::default), } @@ -69,19 +72,19 @@ impl Json { /// Sets the indentation level for rendering the JSON data. pub fn indent(mut self, indent: usize) -> Self { - self.json_state.indent = indent; + self.json_state.formatter.indent = indent; self } /// Sets the attribute for active (currently selected) items. pub fn active_item_attribute(mut self, attr: Attribute) -> Self { - self.json_state.active_item_attribute = attr; + self.json_state.formatter.active_item_attribute = attr; self } /// Sets the attribute for inactive (not currently selected) items. pub fn inactive_item_attribute(mut self, attr: Attribute) -> Self { - self.json_state.inactive_item_attribute = attr; + self.json_state.formatter.inactive_item_attribute = attr; self } From b911e1d73539ec6f814b3ed9d6e022ad24f39b10 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sun, 15 Dec 2024 11:16:30 +0900 Subject: [PATCH 33/44] chore: add empty_str for ContainerType --- promkit/src/jsonz.rs | 7 +++++++ promkit/src/jsonz/format.rs | 5 +---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index 57c962cb..228f08c5 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -23,6 +23,13 @@ impl ContainerType { } } + pub fn empty_str(&self) -> &'static str { + match self { + ContainerType::Object => "{}", + ContainerType::Array => "[]", + } + } + pub fn collapsed_preview(&self) -> &'static str { match self { ContainerType::Object => "{…}", diff --git a/promkit/src/jsonz/format.rs b/promkit/src/jsonz/format.rs index 2850e3bf..4a7898cf 100644 --- a/promkit/src/jsonz/format.rs +++ b/promkit/src/jsonz/format.rs @@ -77,10 +77,7 @@ impl RowFormatter { ContainerType::Object => self.curly_brackets_style, ContainerType::Array => self.square_brackets_style, }; - parts.push( - StyledGraphemes::from(format!("{}{}", typ.open_str(), typ.close_str())) - .apply_style(bracket_style), - ); + parts.push(StyledGraphemes::from(typ.empty_str()).apply_style(bracket_style)); } Value::Open { typ, collapsed, .. } => { let bracket_style = match typ { From 65ef732e4052e7aff88f2fddebfd6b0b6924604f Mon Sep 17 00:00:00 2001 From: ynqa Date: Sun, 15 Dec 2024 11:31:07 +0900 Subject: [PATCH 34/44] chore: add a comment about extract_rows and Close --- promkit/src/jsonz/format.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/promkit/src/jsonz/format.rs b/promkit/src/jsonz/format.rs index 4a7898cf..3f3f2f46 100644 --- a/promkit/src/jsonz/format.rs +++ b/promkit/src/jsonz/format.rs @@ -99,6 +99,9 @@ impl RowFormatter { ContainerType::Object => self.curly_brackets_style, ContainerType::Array => self.square_brackets_style, }; + // We don't need to check collapsed here because: + // 1. If the corresponding Open is collapsed, this Close will be skipped during `extract_rows` + // 2. If the Open is not collapsed, we want to show the closing bracket parts.push(StyledGraphemes::from(typ.close_str()).apply_style(bracket_style)); } } From d52a34c9d781daa284872f43c13dfe364d3a7b12 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sun, 15 Dec 2024 12:01:11 +0900 Subject: [PATCH 35/44] chore: define tests for `format_raw_json` in advance --- promkit/src/core/jsonstream/state.rs | 2 +- promkit/src/jsonz/format.rs | 75 +++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/promkit/src/core/jsonstream/state.rs b/promkit/src/core/jsonstream/state.rs index 5c19029a..34150711 100644 --- a/promkit/src/core/jsonstream/state.rs +++ b/promkit/src/core/jsonstream/state.rs @@ -26,7 +26,7 @@ impl PaneFactory for State { }; let rows = self.stream.extract_rows_from_current(height); - let formatted_rows = self.formatter.format(&rows, width); + let formatted_rows = self.formatter.format_for_terminal_display(&rows, width); Pane::new(formatted_rows, 0) } diff --git a/promkit/src/jsonz/format.rs b/promkit/src/jsonz/format.rs index 3f3f2f46..6a3d4cbd 100644 --- a/promkit/src/jsonz/format.rs +++ b/promkit/src/jsonz/format.rs @@ -34,9 +34,26 @@ pub struct RowFormatter { pub indent: usize, } +impl Default for RowFormatter { + fn default() -> Self { + Self { + curly_brackets_style: Default::default(), + square_brackets_style: Default::default(), + key_style: Default::default(), + string_value_style: Default::default(), + number_value_style: Default::default(), + boolean_value_style: Default::default(), + null_value_style: Default::default(), + active_item_attribute: Attribute::NoBold, + inactive_item_attribute: Attribute::NoBold, + indent: Default::default(), + } + } +} + impl RowFormatter { /// Formats a Vec into Vec with appropriate styling and width limits - pub fn format(&self, rows: &[Row], width: u16) -> Vec { + pub fn format_for_terminal_display(&self, rows: &[Row], width: u16) -> Vec { let mut formatted = Vec::new(); let width = width as usize; @@ -149,4 +166,60 @@ impl RowFormatter { formatted } + + /// Formats a slice of Rows to a raw JSON string, ignoring collapsed and truncated states + pub fn format_raw_json(&self, rows: &[Row]) -> String { + todo!() + } +} + +#[cfg(test)] +mod tests { + mod format_raw_json { + use std::str::FromStr; + + use crate::jsonz::{create_rows, format}; + + #[test] + fn test() { + let expected = r#" +{ + "array": [ + { + "key": "value" + }, + [ + 1, + 2, + 3 + ], + { + "nested": true + } + ], + "object": { + "array": [ + 1, + 2, + 3 + ], + "nested": { + "value": "test" + } + } +}"# + .trim(); + + assert_eq!( + format::RowFormatter { + indent: 4, + ..Default::default() + } + .format_raw_json(&create_rows([ + &serde_json::Value::from_str(&expected).unwrap() + ])), + expected, + ); + } + } } From 2d0394cb35e60db51c16f6a35552bb350956b4ba Mon Sep 17 00:00:00 2001 From: ynqa Date: Sun, 15 Dec 2024 12:21:57 +0900 Subject: [PATCH 36/44] feat: format_raw_json --- promkit/src/jsonz/format.rs | 73 ++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/promkit/src/jsonz/format.rs b/promkit/src/jsonz/format.rs index 6a3d4cbd..a9bb6be4 100644 --- a/promkit/src/jsonz/format.rs +++ b/promkit/src/jsonz/format.rs @@ -169,7 +169,78 @@ impl RowFormatter { /// Formats a slice of Rows to a raw JSON string, ignoring collapsed and truncated states pub fn format_raw_json(&self, rows: &[Row]) -> String { - todo!() + let mut result = String::new(); + let mut first_in_container = true; + + for (i, row) in rows.iter().enumerate() { + // Add indentation + if !matches!(row.v, Value::Close { .. }) { + if !result.is_empty() { + result.push('\n'); + } + result.push_str(&" ".repeat(self.indent * row.depth)); + } + + // Add key if present + if let Some(key) = &row.k { + result.push('"'); + result.push_str(key); + result.push_str("\": "); + } + + // Add value + match &row.v { + Value::Null => result.push_str("null"), + Value::Boolean(b) => result.push_str(&b.to_string()), + Value::Number(n) => result.push_str(&n.to_string()), + Value::String(s) => { + result.push('"'); + result.push_str(&s.replace('\n', "\\n")); + result.push('"'); + } + Value::Empty { typ } => { + result.push_str(match typ { + ContainerType::Object => "{}", + ContainerType::Array => "[]", + }); + } + Value::Open { typ, .. } => { + result.push(match typ { + ContainerType::Object => '{', + ContainerType::Array => '[', + }); + } + Value::Close { typ, .. } => { + if !first_in_container { + result.push('\n'); + result.push_str(&" ".repeat(self.indent * row.depth)); + } + result.push(match typ { + ContainerType::Object => '}', + ContainerType::Array => ']', + }); + } + } + + // Add comma if needed + if i + 1 < rows.len() { + if let Value::Close { .. } = rows[i + 1].v { + // Don't add comma before closing bracket + } else if let Value::Open { .. } = rows[i].v { + // Don't add comma after opening bracket + } else { + result.push(','); + } + } + + if let Value::Open { .. } = row.v { + first_in_container = true; + } else { + first_in_container = false; + } + } + + result } } From cb34e8169dbab2ea14a1ebd51eeed6d284d8c5bb Mon Sep 17 00:00:00 2001 From: ynqa Date: Sun, 15 Dec 2024 12:31:18 +0900 Subject: [PATCH 37/44] chore: define `rows` for JsonStream --- promkit/src/core/jsonstream.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/promkit/src/core/jsonstream.rs b/promkit/src/core/jsonstream.rs index 0b04245d..648c4d43 100644 --- a/promkit/src/core/jsonstream.rs +++ b/promkit/src/core/jsonstream.rs @@ -20,6 +20,10 @@ impl JsonStream { } impl JsonStream { + pub fn rows(&self) -> &[Row] { + &self.rows + } + /// Extracts a specified number of rows from the current position in JSON stream. pub fn extract_rows_from_current(&self, n: usize) -> Vec { self.rows.extract(self.position, n) From 1103a1aa1f5eb50f13f1f3ed2057b42cb4bce9bd Mon Sep 17 00:00:00 2001 From: ynqa Date: Sun, 15 Dec 2024 13:11:27 +0900 Subject: [PATCH 38/44] chore: define tests for `tail` in advance --- promkit/src/jsonz.rs | 5 +++ promkit/tests/jsonz_tail_test.rs | 62 ++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 promkit/tests/jsonz_tail_test.rs diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index 228f08c5..f139f5ba 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -69,6 +69,7 @@ pub struct Row { pub trait RowOperation { fn up(&self, current: usize) -> usize; fn down(&self, current: usize) -> usize; + fn tail(&self) -> usize; fn toggle(&mut self, current: usize) -> usize; fn extract(&self, current: usize, n: usize) -> Vec; } @@ -113,6 +114,10 @@ impl RowOperation for Vec { } } + fn tail(&self) -> usize { + todo!() + } + fn toggle(&mut self, current: usize) -> usize { match &self[current].v { Value::Open { diff --git a/promkit/tests/jsonz_tail_test.rs b/promkit/tests/jsonz_tail_test.rs new file mode 100644 index 00000000..f2b268bd --- /dev/null +++ b/promkit/tests/jsonz_tail_test.rs @@ -0,0 +1,62 @@ +#[cfg(test)] +mod tail { + use std::str::FromStr; + + use promkit::jsonz::*; + use serde_json::Deserializer; + + #[test] + fn test() { + let input = serde_json::Value::from_str( + r#" + { + "object": { + "key": "value" + }, + "array": [ + 1, + 2, + 3 + ] + } + "#, + ) + .unwrap(); + + let mut rows = create_rows([&input]); + assert_eq!(rows.tail(), 9); + rows.toggle(9); + assert_eq!(rows.tail(), 0); + } + + #[test] + fn test_for_jsonl() { + let inputs: Vec<_> = Deserializer::from_str( + r#" + { + "name": "Alice", + "age": 30 + } + { + "name": "Bob", + "age": 25 + } + { + "name": "Charlie", + "age": 35 + } + "#, + ) + .into_iter::() + .filter_map(serde_json::Result::ok) + .collect(); + + let mut rows = create_rows(inputs.iter()); + + assert_eq!(rows.tail(), 12); + rows.toggle(0); + assert_eq!(rows.tail(), 12); + rows.toggle(8); + assert_eq!(rows.tail(), 8); + } +} From f01146874c9eebd383a33d5b8c663d5a5110afca Mon Sep 17 00:00:00 2001 From: ynqa Date: Sun, 15 Dec 2024 13:22:31 +0900 Subject: [PATCH 39/44] feat: tail --- promkit/src/jsonz.rs | 17 ++++++++++++++++- promkit/tests/jsonz_tail_test.rs | 4 ++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index f139f5ba..a9c98f62 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -115,7 +115,22 @@ impl RowOperation for Vec { } fn tail(&self) -> usize { - todo!() + if self.is_empty() { + return 0; + } + + let mut last = self.len() - 1; + match &self[last].v { + Value::Close { + collapsed, + open_index, + .. + } if *collapsed => { + last = *open_index; + last + } + _ => last, + } } fn toggle(&mut self, current: usize) -> usize { diff --git a/promkit/tests/jsonz_tail_test.rs b/promkit/tests/jsonz_tail_test.rs index f2b268bd..b2af447c 100644 --- a/promkit/tests/jsonz_tail_test.rs +++ b/promkit/tests/jsonz_tail_test.rs @@ -53,9 +53,9 @@ mod tail { let mut rows = create_rows(inputs.iter()); - assert_eq!(rows.tail(), 12); + assert_eq!(rows.tail(), 11); rows.toggle(0); - assert_eq!(rows.tail(), 12); + assert_eq!(rows.tail(), 11); rows.toggle(8); assert_eq!(rows.tail(), 8); } From 33db62e541345c1cdcebecd0893f07129eab4fd3 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sun, 15 Dec 2024 13:31:07 +0900 Subject: [PATCH 40/44] feat: head --- promkit/src/jsonz.rs | 5 +++ promkit/tests/jsonz_head_test.rs | 62 ++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 promkit/tests/jsonz_head_test.rs diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index a9c98f62..0d5b5006 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -68,6 +68,7 @@ pub struct Row { pub trait RowOperation { fn up(&self, current: usize) -> usize; + fn head(&self) -> usize; fn down(&self, current: usize) -> usize; fn tail(&self) -> usize; fn toggle(&mut self, current: usize) -> usize; @@ -91,6 +92,10 @@ impl RowOperation for Vec { } } + fn head(&self) -> usize { + 0 + } + fn down(&self, current: usize) -> usize { if current >= self.len() - 1 { return current; diff --git a/promkit/tests/jsonz_head_test.rs b/promkit/tests/jsonz_head_test.rs new file mode 100644 index 00000000..1efad0d4 --- /dev/null +++ b/promkit/tests/jsonz_head_test.rs @@ -0,0 +1,62 @@ +#[cfg(test)] +mod head { + use std::str::FromStr; + + use promkit::jsonz::*; + use serde_json::Deserializer; + + #[test] + fn test() { + let input = serde_json::Value::from_str( + r#" + { + "object": { + "key": "value" + }, + "array": [ + 1, + 2, + 3 + ] + } + "#, + ) + .unwrap(); + + let mut rows = create_rows([&input]); + assert_eq!(rows.head(), 0); + rows.toggle(9); + assert_eq!(rows.head(), 0); + } + + #[test] + fn test_for_jsonl() { + let inputs: Vec<_> = Deserializer::from_str( + r#" + { + "name": "Alice", + "age": 30 + } + { + "name": "Bob", + "age": 25 + } + { + "name": "Charlie", + "age": 35 + } + "#, + ) + .into_iter::() + .filter_map(serde_json::Result::ok) + .collect(); + + let mut rows = create_rows(inputs.iter()); + + assert_eq!(rows.head(), 0); + rows.toggle(0); + assert_eq!(rows.head(), 0); + rows.toggle(8); + assert_eq!(rows.head(), 0); + } +} From 0c60005808cda0ccc7b5b0d8bd71747ab8e1a680 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sun, 15 Dec 2024 13:52:05 +0900 Subject: [PATCH 41/44] feat: set_nodes_visibility --- promkit/Cargo.toml | 1 + promkit/src/jsonz.rs | 27 +++++++++++++++++++++++++++ promkit/tests/jsonz_head_test.rs | 4 ++-- promkit/tests/jsonz_tail_test.rs | 4 ++-- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/promkit/Cargo.toml b/promkit/Cargo.toml index b73096a9..e219b951 100644 --- a/promkit/Cargo.toml +++ b/promkit/Cargo.toml @@ -17,6 +17,7 @@ anyhow = "1.0.81" crossterm = { version = "0.28.1", features = ["use-dev-tty"] } indexmap = "2.2.3" radix_trie = "0.2.1" +rayon = "1.10.0" serde = { version = "1.0.197" } serde_json = { version = "1.0.114", features = ["preserve_order"] } unicode-width = "0.1.8" diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index 0d5b5006..81433455 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -1,5 +1,7 @@ use std::collections::HashSet; +use rayon::prelude::*; + pub mod format; #[derive(Clone, Debug, PartialEq)] @@ -72,6 +74,7 @@ pub trait RowOperation { fn down(&self, current: usize) -> usize; fn tail(&self) -> usize; fn toggle(&mut self, current: usize) -> usize; + fn set_nodes_visibility(&mut self, collapsed: bool); fn extract(&self, current: usize, n: usize) -> Vec; } @@ -194,6 +197,30 @@ impl RowOperation for Vec { } } + fn set_nodes_visibility(&mut self, collapsed: bool) { + self.par_iter_mut().for_each(|row| { + if let Value::Open { + typ, close_index, .. + } = &row.v + { + row.v = Value::Open { + typ: typ.clone(), + collapsed, + close_index: *close_index, + }; + } else if let Value::Close { + typ, open_index, .. + } = &row.v + { + row.v = Value::Close { + typ: typ.clone(), + collapsed, + open_index: *open_index, + }; + } + }); + } + fn extract(&self, current: usize, n: usize) -> Vec { let mut result = Vec::new(); let mut i = current; diff --git a/promkit/tests/jsonz_head_test.rs b/promkit/tests/jsonz_head_test.rs index 1efad0d4..0565b092 100644 --- a/promkit/tests/jsonz_head_test.rs +++ b/promkit/tests/jsonz_head_test.rs @@ -6,7 +6,7 @@ mod head { use serde_json::Deserializer; #[test] - fn test() { + fn test_head_after_toggle() { let input = serde_json::Value::from_str( r#" { @@ -30,7 +30,7 @@ mod head { } #[test] - fn test_for_jsonl() { + fn test_jsonl() { let inputs: Vec<_> = Deserializer::from_str( r#" { diff --git a/promkit/tests/jsonz_tail_test.rs b/promkit/tests/jsonz_tail_test.rs index b2af447c..19517279 100644 --- a/promkit/tests/jsonz_tail_test.rs +++ b/promkit/tests/jsonz_tail_test.rs @@ -6,7 +6,7 @@ mod tail { use serde_json::Deserializer; #[test] - fn test() { + fn test_tail_after_toggle() { let input = serde_json::Value::from_str( r#" { @@ -30,7 +30,7 @@ mod tail { } #[test] - fn test_for_jsonl() { + fn test_jsonl() { let inputs: Vec<_> = Deserializer::from_str( r#" { From a367af60bef82ed262785460ef13a0bbba5572f8 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sun, 15 Dec 2024 13:57:15 +0900 Subject: [PATCH 42/44] chore: define tests for `set_nodes_visibility` --- .../tests/jsonz_set_nodes_visibility_test.rs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 promkit/tests/jsonz_set_nodes_visibility_test.rs diff --git a/promkit/tests/jsonz_set_nodes_visibility_test.rs b/promkit/tests/jsonz_set_nodes_visibility_test.rs new file mode 100644 index 00000000..157328de --- /dev/null +++ b/promkit/tests/jsonz_set_nodes_visibility_test.rs @@ -0,0 +1,57 @@ +#[cfg(test)] +mod set_nodes_visibility { + use serde_json::Deserializer; + + use promkit::jsonz::*; + + #[test] + fn test() { + let inputs: Vec<_> = Deserializer::from_str( + r#" + { + "array": [ + 1, + 2, + 3 + ] + } + [ + { + "nested": true + }, + { + "nested": false + } + ] + { + "empty": {} + } + "#, + ) + .into_iter::() + .filter_map(serde_json::Result::ok) + .collect(); + + let mut rows = create_rows(inputs.iter()); + + rows.set_nodes_visibility(true); + for row in &rows { + match &row.v { + Value::Open { collapsed, .. } | Value::Close { collapsed, .. } => { + assert!(collapsed, "Node should be collapsed"); + } + _ => {} + } + } + + rows.set_nodes_visibility(false); + for row in &rows { + match &row.v { + Value::Open { collapsed, .. } | Value::Close { collapsed, .. } => { + assert!(!collapsed, "Node should be expanded"); + } + _ => {} + } + } + } +} From 9364611f4eccee3c60c013653b868b0c881f2051 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sun, 15 Dec 2024 14:33:58 +0900 Subject: [PATCH 43/44] chore: use Vec methods for JsonStream --- promkit/src/core/jsonstream.rs | 19 +++++++++++++++++++ promkit/src/jsonz.rs | 4 ++-- ...t.rs => jsonz_set_rows_visibility_test.rs} | 4 ++-- 3 files changed, 23 insertions(+), 4 deletions(-) rename promkit/tests/{jsonz_set_nodes_visibility_test.rs => jsonz_set_rows_visibility_test.rs} (94%) diff --git a/promkit/src/core/jsonstream.rs b/promkit/src/core/jsonstream.rs index 648c4d43..89ae87c6 100644 --- a/promkit/src/core/jsonstream.rs +++ b/promkit/src/core/jsonstream.rs @@ -20,6 +20,7 @@ impl JsonStream { } impl JsonStream { + /// Returns a reference to the underlying vector of rows. pub fn rows(&self) -> &[Row] { &self.rows } @@ -35,6 +36,12 @@ impl JsonStream { self.position = index; } + /// Sets the visibility of all rows in JSON stream. + pub fn set_nodes_visibility(&mut self, collapsed: bool) { + self.rows.set_rows_visibility(collapsed); + self.position = 0; + } + /// Moves the cursor backward through JSON stream. pub fn backward(&mut self) -> bool { let index = self.rows.up(self.position); @@ -43,6 +50,12 @@ impl JsonStream { ret } + /// Moves the cursor to the head position in JSON stream. + pub fn head(&mut self) -> bool { + self.position = self.rows.head(); + true + } + /// Moves the cursor forward through JSON stream. pub fn forward(&mut self) -> bool { let index = self.rows.down(self.position); @@ -50,4 +63,10 @@ impl JsonStream { self.position = index; ret } + + /// Moves the cursor to the last position in JSON stream. + pub fn tail(&mut self) -> bool { + self.position = self.rows.tail(); + true + } } diff --git a/promkit/src/jsonz.rs b/promkit/src/jsonz.rs index 81433455..7f9c2831 100644 --- a/promkit/src/jsonz.rs +++ b/promkit/src/jsonz.rs @@ -74,7 +74,7 @@ pub trait RowOperation { fn down(&self, current: usize) -> usize; fn tail(&self) -> usize; fn toggle(&mut self, current: usize) -> usize; - fn set_nodes_visibility(&mut self, collapsed: bool); + fn set_rows_visibility(&mut self, collapsed: bool); fn extract(&self, current: usize, n: usize) -> Vec; } @@ -197,7 +197,7 @@ impl RowOperation for Vec { } } - fn set_nodes_visibility(&mut self, collapsed: bool) { + fn set_rows_visibility(&mut self, collapsed: bool) { self.par_iter_mut().for_each(|row| { if let Value::Open { typ, close_index, .. diff --git a/promkit/tests/jsonz_set_nodes_visibility_test.rs b/promkit/tests/jsonz_set_rows_visibility_test.rs similarity index 94% rename from promkit/tests/jsonz_set_nodes_visibility_test.rs rename to promkit/tests/jsonz_set_rows_visibility_test.rs index 157328de..c2cfc2f1 100644 --- a/promkit/tests/jsonz_set_nodes_visibility_test.rs +++ b/promkit/tests/jsonz_set_rows_visibility_test.rs @@ -34,7 +34,7 @@ mod set_nodes_visibility { let mut rows = create_rows(inputs.iter()); - rows.set_nodes_visibility(true); + rows.set_rows_visibility(true); for row in &rows { match &row.v { Value::Open { collapsed, .. } | Value::Close { collapsed, .. } => { @@ -44,7 +44,7 @@ mod set_nodes_visibility { } } - rows.set_nodes_visibility(false); + rows.set_rows_visibility(false); for row in &rows { match &row.v { Value::Open { collapsed, .. } | Value::Close { collapsed, .. } => { From fd7885069484f68818f72d2d4fa66cfe7ec82744 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sun, 15 Dec 2024 16:22:26 +0900 Subject: [PATCH 44/44] chore: backward/forward => up/down --- promkit/src/core/jsonstream.rs | 4 ++-- promkit/src/preset/json/keymap.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/promkit/src/core/jsonstream.rs b/promkit/src/core/jsonstream.rs index 89ae87c6..a6896105 100644 --- a/promkit/src/core/jsonstream.rs +++ b/promkit/src/core/jsonstream.rs @@ -43,7 +43,7 @@ impl JsonStream { } /// Moves the cursor backward through JSON stream. - pub fn backward(&mut self) -> bool { + pub fn up(&mut self) -> bool { let index = self.rows.up(self.position); let ret = index != self.position; self.position = index; @@ -57,7 +57,7 @@ impl JsonStream { } /// Moves the cursor forward through JSON stream. - pub fn forward(&mut self) -> bool { + pub fn down(&mut self) -> bool { let index = self.rows.down(self.position); let ret = index != self.position; self.position = index; diff --git a/promkit/src/preset/json/keymap.rs b/promkit/src/preset/json/keymap.rs index 2fd20470..499c543a 100644 --- a/promkit/src/preset/json/keymap.rs +++ b/promkit/src/preset/json/keymap.rs @@ -51,7 +51,7 @@ pub fn default( row: _, modifiers: KeyModifiers::NONE, }) => { - renderer.json_state.stream.backward(); + renderer.json_state.stream.up(); } Event::Key(KeyEvent { @@ -66,7 +66,7 @@ pub fn default( row: _, modifiers: KeyModifiers::NONE, }) => { - renderer.json_state.stream.forward(); + renderer.json_state.stream.down(); } // Fold/Unfold