From ded8bf47741a4d8b713785c1ddfccf95eab017dd Mon Sep 17 00:00:00 2001 From: ynqa Date: Sat, 21 Feb 2026 00:27:33 +0900 Subject: [PATCH 01/35] feat: view the initial pane on failure or null --- src/json.rs | 48 +++++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/json.rs b/src/json.rs index fd8adac..bd7a26d 100644 --- a/src/json.rs +++ b/src/json.rs @@ -27,6 +27,7 @@ use crate::{ // #[derive(Clone)] pub struct Json { state: jsonstream::State, + cached_state: jsonstream::State, json: &'static [serde_json::Value], keybinds: JsonViewerKeybinds, } @@ -37,13 +38,16 @@ impl Json { input_stream: &'static [serde_json::Value], keybinds: JsonViewerKeybinds, ) -> anyhow::Result { + let state = jsonstream::State { + stream: JsonStream::new(input_stream.iter()), + formatter, + lines: Default::default(), + }; + Ok(Self { json: input_stream, - state: jsonstream::State { - stream: JsonStream::new(input_stream.iter()), - formatter, - lines: Default::default(), - }, + cached_state: state.clone(), + state, keybinds, }) } @@ -123,27 +127,33 @@ impl Visualizer for Json { }, ..Default::default() }.create_pane(area.0, area.1)); + + return (guide, Some(self.cached_state.create_pane(area.0, area.1))); } self.state.stream = JsonStream::new(ret.iter()); (guide, Some(self.state.create_pane(area.0, area.1))) } - Err(e) => ( - Some( - text::State { - text: Text::from(format!("jq failed: `{e}`")), - style: ContentStyle { - foreground_color: Some(Color::Red), - attributes: Attributes::from(Attribute::Bold), + Err(e) => { + self.state.stream = self.cached_state.stream.clone(); + + ( + Some( + text::State { + text: Text::from(format!("jq failed: `{e}`")), + style: ContentStyle { + foreground_color: Some(Color::Red), + attributes: Attributes::from(Attribute::Bold), + ..Default::default() + }, ..Default::default() - }, - ..Default::default() - } - .create_pane(area.0, area.1), - ), - None, - ), + } + .create_pane(area.0, area.1), + ), + Some(self.cached_state.create_pane(area.0, area.1)), + ) + } } } } From cf41408e4b8fcc14440bf3718af46c73df700f08 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sun, 22 Feb 2026 15:28:29 +0900 Subject: [PATCH 02/35] feat: replace stream with init on failure or null --- src/json.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/json.rs b/src/json.rs index bd7a26d..ecd50f4 100644 --- a/src/json.rs +++ b/src/json.rs @@ -27,7 +27,7 @@ use crate::{ // #[derive(Clone)] pub struct Json { state: jsonstream::State, - cached_state: jsonstream::State, + init_stream: JsonStream, json: &'static [serde_json::Value], keybinds: JsonViewerKeybinds, } @@ -38,15 +38,17 @@ impl Json { input_stream: &'static [serde_json::Value], keybinds: JsonViewerKeybinds, ) -> anyhow::Result { + let init_stream = JsonStream::new(input_stream.iter()); + let state = jsonstream::State { - stream: JsonStream::new(input_stream.iter()), + stream: init_stream.clone(), formatter, lines: Default::default(), }; Ok(Self { json: input_stream, - cached_state: state.clone(), + init_stream, state, keybinds, }) @@ -128,15 +130,15 @@ impl Visualizer for Json { ..Default::default() }.create_pane(area.0, area.1)); - return (guide, Some(self.cached_state.create_pane(area.0, area.1))); + self.state.stream = self.init_stream.clone(); + } else { + self.state.stream = JsonStream::new(ret.iter()); } - self.state.stream = JsonStream::new(ret.iter()); - (guide, Some(self.state.create_pane(area.0, area.1))) } Err(e) => { - self.state.stream = self.cached_state.stream.clone(); + self.state.stream = self.init_stream.clone(); ( Some( @@ -151,7 +153,7 @@ impl Visualizer for Json { } .create_pane(area.0, area.1), ), - Some(self.cached_state.create_pane(area.0, area.1)), + Some(self.state.create_pane(area.0, area.1)), ) } } From af605229def53321ac5598b766a6a2abb0f89870 Mon Sep 17 00:00:00 2001 From: ynqa Date: Mon, 23 Feb 2026 03:53:23 +0900 Subject: [PATCH 03/35] fix: already could have generated jsonstream by given variables --- src/json.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/json.rs b/src/json.rs index ecd50f4..8fa0e9d 100644 --- a/src/json.rs +++ b/src/json.rs @@ -27,7 +27,6 @@ use crate::{ // #[derive(Clone)] pub struct Json { state: jsonstream::State, - init_stream: JsonStream, json: &'static [serde_json::Value], keybinds: JsonViewerKeybinds, } @@ -38,17 +37,14 @@ impl Json { input_stream: &'static [serde_json::Value], keybinds: JsonViewerKeybinds, ) -> anyhow::Result { - let init_stream = JsonStream::new(input_stream.iter()); - let state = jsonstream::State { - stream: init_stream.clone(), + stream: JsonStream::new(input_stream.iter()), formatter, lines: Default::default(), }; Ok(Self { json: input_stream, - init_stream, state, keybinds, }) @@ -130,7 +126,7 @@ impl Visualizer for Json { ..Default::default() }.create_pane(area.0, area.1)); - self.state.stream = self.init_stream.clone(); + self.state.stream = JsonStream::new(self.json.iter()); } else { self.state.stream = JsonStream::new(ret.iter()); } @@ -138,7 +134,7 @@ impl Visualizer for Json { (guide, Some(self.state.create_pane(area.0, area.1))) } Err(e) => { - self.state.stream = self.init_stream.clone(); + self.state.stream = JsonStream::new(self.json.iter()); ( Some( From ec189d2e58289da5da54efec10a4a29f1317f7d2 Mon Sep 17 00:00:00 2001 From: ynqa Date: Mon, 23 Feb 2026 22:06:27 +0900 Subject: [PATCH 04/35] on-err: use `termcfg` for configurations --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + 2 files changed, 11 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 075903d..00fa6f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -858,6 +858,7 @@ dependencies = [ "jaq-std", "promkit-widgets", "serde", + "termcfg", "tokio", "tokio-stream", "toml", @@ -1434,6 +1435,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcfg" +version = "0.1.0" +dependencies = [ + "crossterm", + "serde", + "thiserror 2.0.18", +] + [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index ba27d7b..422e269 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ dirs = "6.0.0" futures = "0.3.32" promkit-widgets = { version = "0.2.0", features = ["jsonstream", "listbox", "text", "texteditor"] } serde = "1.0.228" +termcfg = { path = "../termcfg", features = ["crossterm_0_29_0"] } tokio = { version = "1.49.0", features = ["full"] } tokio-stream = "0.1.18" toml = "0.9.8" From 25498ca68b8d6f1b54edfb2b98c1d3010a7099d7 Mon Sep 17 00:00:00 2001 From: ynqa Date: Mon, 23 Feb 2026 22:32:29 +0900 Subject: [PATCH 05/35] on-err: replace EventDefSet with HashSet --- src/config.rs | 271 ++++++++++---------------------------------------- src/main.rs | 20 ++-- 2 files changed, 63 insertions(+), 228 deletions(-) diff --git a/src/config.rs b/src/config.rs index a8009f9..c862f3c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,21 +1,18 @@ -use std::collections::HashSet; +use std::{collections::HashSet, hash::Hash}; use promkit_widgets::{ core::crossterm::{ - event::{KeyCode, KeyModifiers}, + event::{Event, KeyCode, KeyModifiers}, style::{Attribute, Attributes, Color, ContentStyle}, }, text_editor::Mode, }; use serde::{Deserialize, Serialize}; +use termcfg::crossterm_config::{content_style_serde, event_set_serde}; use tokio::time::Duration; -mod content_style; -use content_style::content_style_serde; mod duration; use duration::duration_serde; -pub mod event; -use event::{EventDef, EventDefSet, KeyEventDef}; mod text_editor; use text_editor::text_editor_mode_serde; @@ -42,43 +39,6 @@ pub struct EditorTheme { pub inactive_char_style: ContentStyle, } -impl Default for EditorConfig { - fn default() -> Self { - Self { - theme_on_focus: EditorTheme { - prefix: String::from("❯❯ "), - prefix_style: ContentStyle { - foreground_color: Some(Color::Blue), - ..Default::default() - }, - active_char_style: ContentStyle { - background_color: Some(Color::Magenta), - ..Default::default() - }, - inactive_char_style: ContentStyle::default(), - }, - theme_on_defocus: EditorTheme { - prefix: String::from("▼ "), - prefix_style: ContentStyle { - foreground_color: Some(Color::Blue), - attributes: Attributes::from(Attribute::Dim), - ..Default::default() - }, - active_char_style: ContentStyle { - attributes: Attributes::from(Attribute::Dim), - ..Default::default() - }, - inactive_char_style: ContentStyle { - attributes: Attributes::from(Attribute::Dim), - ..Default::default() - }, - }, - mode: Mode::Insert, - word_break_chars: HashSet::from(['.', '|', '(', ')', '[', ']']), - } - } -} - #[derive(Serialize, Deserialize)] pub struct JsonConfig { pub max_streams: Option, @@ -111,39 +71,6 @@ pub struct JsonTheme { pub null_value_style: ContentStyle, } -impl Default for JsonConfig { - fn default() -> Self { - Self { - max_streams: None, - theme: JsonTheme { - indent: 2, - curly_brackets_style: ContentStyle { - attributes: Attributes::from(Attribute::Bold), - ..Default::default() - }, - square_brackets_style: ContentStyle { - attributes: Attributes::from(Attribute::Bold), - ..Default::default() - }, - key_style: ContentStyle { - foreground_color: Some(Color::Cyan), - ..Default::default() - }, - string_value_style: ContentStyle { - foreground_color: Some(Color::Green), - ..Default::default() - }, - number_value_style: ContentStyle::default(), - boolean_value_style: ContentStyle::default(), - null_value_style: ContentStyle { - foreground_color: Some(Color::Grey), - ..Default::default() - }, - }, - } - } -} - #[derive(Serialize, Deserialize)] pub struct CompletionConfig { pub lines: Option, @@ -158,156 +85,72 @@ pub struct CompletionConfig { pub inactive_item_style: ContentStyle, } -impl Default for CompletionConfig { - fn default() -> Self { - Self { - lines: Some(3), - cursor: String::from("❯ "), - search_result_chunk_size: 100, - search_load_chunk_size: 50000, - active_item_style: ContentStyle { - foreground_color: Some(Color::Grey), - background_color: Some(Color::Yellow), - ..Default::default() - }, - inactive_item_style: ContentStyle { - foreground_color: Some(Color::Grey), - ..Default::default() - }, - } - } -} - // TODO: remove Clone derive #[derive(Clone, Serialize, Deserialize)] pub struct Keybinds { - pub exit: EventDefSet, - pub copy_query: EventDefSet, - pub copy_result: EventDefSet, - pub switch_mode: EventDefSet, + #[serde(with = "event_set_serde")] + pub exit: HashSet, + #[serde(with = "event_set_serde")] + pub copy_query: HashSet, + #[serde(with = "event_set_serde")] + pub copy_result: HashSet, + #[serde(with = "event_set_serde")] + pub switch_mode: HashSet, pub on_editor: EditorKeybinds, pub on_json_viewer: JsonViewerKeybinds, } #[derive(Clone, Serialize, Deserialize)] pub struct EditorKeybinds { - pub backward: EventDefSet, - pub forward: EventDefSet, - pub move_to_head: EventDefSet, - pub move_to_tail: EventDefSet, - pub move_to_previous_nearest: EventDefSet, - pub move_to_next_nearest: EventDefSet, - pub erase: EventDefSet, - pub erase_all: EventDefSet, - pub erase_to_previous_nearest: EventDefSet, - pub erase_to_next_nearest: EventDefSet, - pub completion: EventDefSet, + #[serde(with = "event_set_serde")] + pub backward: HashSet, + #[serde(with = "event_set_serde")] + pub forward: HashSet, + #[serde(with = "event_set_serde")] + pub move_to_head: HashSet, + #[serde(with = "event_set_serde")] + pub move_to_tail: HashSet, + #[serde(with = "event_set_serde")] + pub move_to_previous_nearest: HashSet, + #[serde(with = "event_set_serde")] + pub move_to_next_nearest: HashSet, + #[serde(with = "event_set_serde")] + pub erase: HashSet, + #[serde(with = "event_set_serde")] + pub erase_all: HashSet, + #[serde(with = "event_set_serde")] + pub erase_to_previous_nearest: HashSet, + #[serde(with = "event_set_serde")] + pub erase_to_next_nearest: HashSet, + #[serde(with = "event_set_serde")] + pub completion: HashSet, pub on_completion: CompletionKeybinds, } #[derive(Clone, Serialize, Deserialize)] pub struct CompletionKeybinds { - pub up: EventDefSet, - pub down: EventDefSet, + #[serde(with = "event_set_serde")] + pub up: HashSet, + #[serde(with = "event_set_serde")] + pub down: HashSet, } #[derive(Clone, Serialize, Deserialize)] pub struct JsonViewerKeybinds { - pub up: EventDefSet, - pub down: EventDefSet, - pub move_to_head: EventDefSet, - pub move_to_tail: EventDefSet, - pub toggle: EventDefSet, - pub expand: EventDefSet, - pub collapse: EventDefSet, -} - -impl Default for Keybinds { - fn default() -> Self { - Self { - exit: EventDefSet::from(KeyEventDef::new(KeyCode::Char('c'), KeyModifiers::CONTROL)), - copy_query: EventDefSet::from(KeyEventDef::new( - KeyCode::Char('q'), - KeyModifiers::CONTROL, - )), - copy_result: EventDefSet::from(KeyEventDef::new( - KeyCode::Char('o'), - KeyModifiers::CONTROL, - )), - switch_mode: EventDefSet::from_iter([ - EventDef::Key(KeyEventDef::new(KeyCode::Down, KeyModifiers::SHIFT)), - EventDef::Key(KeyEventDef::new(KeyCode::Up, KeyModifiers::SHIFT)), - ]), - on_editor: EditorKeybinds { - backward: EventDefSet::from(KeyEventDef::new(KeyCode::Left, KeyModifiers::NONE)), - forward: EventDefSet::from(KeyEventDef::new(KeyCode::Right, KeyModifiers::NONE)), - move_to_head: EventDefSet::from(KeyEventDef::new( - KeyCode::Char('a'), - KeyModifiers::CONTROL, - )), - move_to_tail: EventDefSet::from(KeyEventDef::new( - KeyCode::Char('e'), - KeyModifiers::CONTROL, - )), - move_to_next_nearest: EventDefSet::from(KeyEventDef::new( - KeyCode::Char('f'), - KeyModifiers::ALT, - )), - move_to_previous_nearest: EventDefSet::from(KeyEventDef::new( - KeyCode::Char('b'), - KeyModifiers::ALT, - )), - erase: EventDefSet::from(KeyEventDef::new(KeyCode::Backspace, KeyModifiers::NONE)), - erase_all: EventDefSet::from(KeyEventDef::new( - KeyCode::Char('u'), - KeyModifiers::CONTROL, - )), - erase_to_previous_nearest: EventDefSet::from(KeyEventDef::new( - KeyCode::Char('w'), - KeyModifiers::CONTROL, - )), - erase_to_next_nearest: EventDefSet::from(KeyEventDef::new( - KeyCode::Char('d'), - KeyModifiers::ALT, - )), - completion: EventDefSet::from(KeyEventDef::new(KeyCode::Tab, KeyModifiers::NONE)), - on_completion: CompletionKeybinds { - up: EventDefSet::from(KeyEventDef::new(KeyCode::Up, KeyModifiers::NONE)), - down: EventDefSet::from_iter([ - EventDef::Key(KeyEventDef::new(KeyCode::Tab, KeyModifiers::NONE)), - EventDef::Key(KeyEventDef::new(KeyCode::Down, KeyModifiers::NONE)), - ]), - }, - }, - on_json_viewer: JsonViewerKeybinds { - up: EventDefSet::from_iter([ - EventDef::Key(KeyEventDef::new(KeyCode::Char('k'), KeyModifiers::CONTROL)), - EventDef::Key(KeyEventDef::new(KeyCode::Up, KeyModifiers::NONE)), - ]), - down: EventDefSet::from_iter([ - EventDef::Key(KeyEventDef::new(KeyCode::Char('j'), KeyModifiers::CONTROL)), - EventDef::Key(KeyEventDef::new(KeyCode::Down, KeyModifiers::NONE)), - ]), - move_to_head: EventDefSet::from(KeyEventDef::new( - KeyCode::Char('l'), - KeyModifiers::CONTROL, - )), - move_to_tail: EventDefSet::from(KeyEventDef::new( - KeyCode::Char('h'), - KeyModifiers::CONTROL, - )), - toggle: EventDefSet::from(KeyEventDef::new(KeyCode::Enter, KeyModifiers::NONE)), - expand: EventDefSet::from(KeyEventDef::new( - KeyCode::Char('p'), - KeyModifiers::CONTROL, - )), - collapse: EventDefSet::from(KeyEventDef::new( - KeyCode::Char('n'), - KeyModifiers::CONTROL, - )), - }, - } - } + #[serde(with = "event_set_serde")] + pub up: HashSet, + #[serde(with = "event_set_serde")] + pub down: HashSet, + #[serde(with = "event_set_serde")] + pub move_to_head: HashSet, + #[serde(with = "event_set_serde")] + pub move_to_tail: HashSet, + #[serde(with = "event_set_serde")] + pub toggle: HashSet, + #[serde(with = "event_set_serde")] + pub expand: HashSet, + #[serde(with = "event_set_serde")] + pub collapse: HashSet, } #[derive(Serialize, Deserialize)] @@ -322,15 +165,7 @@ pub struct ReactivityControl { pub spin_duration: Duration, } -impl Default for ReactivityControl { - fn default() -> Self { - Self { - query_debounce_duration: Duration::from_millis(600), - resize_debounce_duration: Duration::from_millis(200), - spin_duration: Duration::from_millis(300), - } - } -} +pub static DEFAULT_CONFIG: &str = include_str!("../default.toml"); /// Note that the config struct and the `.toml` configuration file are /// managed separately because the current toml crate @@ -348,7 +183,7 @@ impl Default for ReactivityControl { /// The main challenge is that, for nested structs, /// it is not able to wrap every leaf field with Option<>. /// https://github.com/colin-kiegel/rust-derive-builder/issues/254 -#[derive(Default, Serialize, Deserialize)] +#[derive(Serialize, Deserialize)] pub struct Config { pub no_hint: bool, pub reactivity_control: ReactivityControl, diff --git a/src/main.rs b/src/main.rs index 7c7ffb2..e3aa45d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,7 @@ mod prompt; mod search; use search::{IncrementalSearcher, SearchProvider}; -static DEFAULT_CONFIG: &str = include_str!("../default.toml"); +use crate::config::DEFAULT_CONFIG; /// JSON navigator and interactive filter leveraging jq #[derive(Parser)] @@ -148,15 +148,15 @@ async fn main() -> anyhow::Result<()> { let args = Args::parse(); let input = parse_input(&args)?; - let mut config = Config::default(); - if let Ok(config_file) = determine_config_file(args.config_file) { - // Note that the configuration file absolutely exists. - let content = std::fs::read_to_string(&config_file) - // TODO: output the message as the initial guide pane. - .map_err(|e| anyhow!("Failed to read configuration file: {e}"))?; - config = Config::load_from(&content) - .map_err(|e| anyhow!("Failed to deserialize configuration file: {e}"))?; - } + let config = determine_config_file(args.config_file) + .and_then(|config_file| { + std::fs::read_to_string(&config_file) + .map_err(|e| anyhow!("Failed to read configuration file: {e}")) + }) + .and_then(|content| Config::load_from(&content)) + .unwrap_or_else(|e| { + Config::load_from(DEFAULT_CONFIG).expect("Failed to load default configuration") + }); let listbox_state = listbox::State { listbox: Listbox::default(), From 5b79f19f0b92d667bbaf46064b8b9d916250bfec Mon Sep 17 00:00:00 2001 From: ynqa Date: Mon, 23 Feb 2026 22:35:19 +0900 Subject: [PATCH 06/35] chore: replace EventDefSet with HashSet --- src/config.rs | 7 +-- src/config/content_style.rs | 65 ---------------------- src/config/event.rs | 104 ------------------------------------ src/editor.rs | 32 ++++++----- src/json.rs | 16 +++--- src/main.rs | 2 +- src/prompt.rs | 4 +- 7 files changed, 31 insertions(+), 199 deletions(-) delete mode 100644 src/config/content_style.rs delete mode 100644 src/config/event.rs diff --git a/src/config.rs b/src/config.rs index c862f3c..172cc7c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,10 +1,7 @@ -use std::{collections::HashSet, hash::Hash}; +use std::collections::HashSet; use promkit_widgets::{ - core::crossterm::{ - event::{Event, KeyCode, KeyModifiers}, - style::{Attribute, Attributes, Color, ContentStyle}, - }, + core::crossterm::{event::Event, style::ContentStyle}, text_editor::Mode, }; use serde::{Deserialize, Serialize}; diff --git a/src/config/content_style.rs b/src/config/content_style.rs deleted file mode 100644 index 8edaed4..0000000 --- a/src/config/content_style.rs +++ /dev/null @@ -1,65 +0,0 @@ -use promkit_widgets::core::crossterm::style::{Attribute, Attributes, Color, ContentStyle}; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize)] -struct ContentStyleDef { - foreground: Option, - background: Option, - underline: Option, - attributes: Option>, -} - -impl From<&ContentStyle> for ContentStyleDef { - fn from(style: &ContentStyle) -> Self { - ContentStyleDef { - foreground: style.foreground_color, - background: style.background_color, - underline: style.underline_color, - attributes: if style.attributes.is_empty() { - None - } else { - Some( - Attribute::iterator() - .filter(|x| style.attributes.has(*x)) - .collect(), - ) - }, - } - } -} - -impl From for ContentStyle { - fn from(style_def: ContentStyleDef) -> Self { - let mut style = ContentStyle::new(); - style.foreground_color = style_def.foreground; - style.background_color = style_def.background; - style.underline_color = style_def.underline; - if let Some(attributes) = style_def.attributes { - style.attributes = attributes - .into_iter() - .fold(Attributes::default(), |acc, x| acc | x); - } - style - } -} - -pub mod content_style_serde { - use super::*; - use serde::{Deserializer, Serializer}; - - pub fn serialize(style: &ContentStyle, serializer: S) -> Result - where - S: Serializer, - { - let style_def = ContentStyleDef::from(style); - style_def.serialize(serializer) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let style_def = ContentStyleDef::deserialize(deserializer)?; - Ok(ContentStyle::from(style_def)) - } -} diff --git a/src/config/event.rs b/src/config/event.rs deleted file mode 100644 index 7ef8c13..0000000 --- a/src/config/event.rs +++ /dev/null @@ -1,104 +0,0 @@ -use std::collections::HashSet; - -use promkit_widgets::core::crossterm::event::{ - Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind, -}; -use serde::{Deserialize, Serialize}; - -pub trait Matcher { - fn matches(&self, other: &T) -> bool; -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct EventDefSet(HashSet); - -impl Matcher for EventDefSet { - fn matches(&self, other: &Event) -> bool { - self.0.iter().any(|event_def| event_def.matches(other)) - } -} - -impl FromIterator for EventDefSet { - fn from_iter>(iter: I) -> Self { - EventDefSet(iter.into_iter().collect()) - } -} - -impl From for EventDefSet { - fn from(key_event_def: KeyEventDef) -> Self { - EventDefSet(HashSet::from_iter([EventDef::Key(key_event_def)])) - } -} - -impl From for EventDefSet { - fn from(mouse_event_def: MouseEventDef) -> Self { - EventDefSet(HashSet::from_iter([EventDef::Mouse(mouse_event_def)])) - } -} - -/// A part of `crossterm::event::Event`. -/// It is used for parsing from a config file or -/// for comparison with crossterm::event::Event. -/// https://docs.rs/crossterm/0.28.1/crossterm/event/enum.Event.html -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub enum EventDef { - Key(KeyEventDef), - Mouse(MouseEventDef), -} - -impl Matcher for EventDef { - fn matches(&self, other: &Event) -> bool { - match (self, other) { - (EventDef::Key(key_def), Event::Key(key_event)) => key_def.matches(key_event), - (EventDef::Mouse(mouse_def), Event::Mouse(mouse_event)) => { - mouse_def.matches(mouse_event) - } - _ => false, - } - } -} - -/// A part of `crossterm::event::KeyEvent`. -/// It is used for parsing from a config file or -/// for comparison with crossterm::event::KeyEvent. -/// https://docs.rs/crossterm/0.28.1/crossterm/event/struct.KeyEvent.html -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub struct KeyEventDef { - code: KeyCode, - modifiers: KeyModifiers, -} - -impl KeyEventDef { - pub fn new(code: KeyCode, modifiers: KeyModifiers) -> Self { - KeyEventDef { code, modifiers } - } -} - -impl Matcher for KeyEventDef { - fn matches(&self, other: &KeyEvent) -> bool { - self.code == other.code && self.modifiers == other.modifiers - } -} - -/// A part of `crossterm::event::MouseEvent`. -/// It is used for parsing from a config file or -/// for comparison with crossterm::event::MouseEvent. -/// https://docs.rs/crossterm/0.28.1/crossterm/event/struct.MouseEvent.html -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub struct MouseEventDef { - kind: MouseEventKind, - modifiers: KeyModifiers, -} - -impl MouseEventDef { - #[allow(dead_code)] - pub fn new(kind: MouseEventKind, modifiers: KeyModifiers) -> Self { - MouseEventDef { kind, modifiers } - } -} - -impl Matcher for MouseEventDef { - fn matches(&self, other: &MouseEvent) -> bool { - self.kind == other.kind && self.modifiers == other.modifiers - } -} diff --git a/src/editor.rs b/src/editor.rs index d422fea..25a93be 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -13,7 +13,7 @@ use promkit_widgets::{ }; use crate::{ - config::{event::Matcher, EditorKeybinds, EditorTheme}, + config::{EditorKeybinds, EditorTheme}, search::IncrementalSearcher, }; @@ -104,7 +104,7 @@ pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Resul editor.guide.text = Default::default(); match event { - key if editor.editor_keybinds.completion.matches(key) => { + key if editor.editor_keybinds.completion.contains(key) => { let prefix = editor.state.texteditor.text_without_cursor().to_string(); match editor.searcher.start_search(&prefix) { Ok(result) => match result.head_item { @@ -151,27 +151,31 @@ pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Resul } // Move cursor. - key if editor.editor_keybinds.backward.matches(key) => { + key if editor.editor_keybinds.backward.contains(key) => { editor.state.texteditor.backward(); } - key if editor.editor_keybinds.forward.matches(key) => { + key if editor.editor_keybinds.forward.contains(key) => { editor.state.texteditor.forward(); } - key if editor.editor_keybinds.move_to_head.matches(key) => { + key if editor.editor_keybinds.move_to_head.contains(key) => { editor.state.texteditor.move_to_head(); } - key if editor.editor_keybinds.move_to_tail.matches(key) => { + key if editor.editor_keybinds.move_to_tail.contains(key) => { editor.state.texteditor.move_to_tail(); } // Move cursor to the nearest character. - key if editor.editor_keybinds.move_to_previous_nearest.matches(key) => { + key if editor + .editor_keybinds + .move_to_previous_nearest + .contains(key) => + { editor .state .texteditor .move_to_previous_nearest(&editor.state.word_break_chars); } - key if editor.editor_keybinds.move_to_next_nearest.matches(key) => { + key if editor.editor_keybinds.move_to_next_nearest.contains(key) => { editor .state .texteditor @@ -179,10 +183,10 @@ pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Resul } // Erase char(s). - key if editor.editor_keybinds.erase.matches(key) => { + key if editor.editor_keybinds.erase.contains(key) => { editor.state.texteditor.erase(); } - key if editor.editor_keybinds.erase_all.matches(key) => { + key if editor.editor_keybinds.erase_all.contains(key) => { editor.state.texteditor.erase_all(); } @@ -190,14 +194,14 @@ pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Resul key if editor .editor_keybinds .erase_to_previous_nearest - .matches(key) => + .contains(key) => { editor .state .texteditor .erase_to_previous_nearest(&editor.state.word_break_chars); } - key if editor.editor_keybinds.erase_to_next_nearest.matches(key) => { + key if editor.editor_keybinds.erase_to_next_nearest.contains(key) => { editor .state .texteditor @@ -228,7 +232,7 @@ pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Resul pub async fn search<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Result<()> { match event { - key if editor.editor_keybinds.on_completion.down.matches(key) => { + key if editor.editor_keybinds.on_completion.down.contains(key) => { editor.searcher.down_with_load(); editor .state @@ -236,7 +240,7 @@ pub async fn search<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Res .replace(&editor.searcher.get_current_item()); } - key if editor.editor_keybinds.on_completion.up.matches(key) => { + key if editor.editor_keybinds.on_completion.up.contains(key) => { editor.searcher.up(); editor .state diff --git a/src/json.rs b/src/json.rs index fd8adac..1c7941c 100644 --- a/src/json.rs +++ b/src/json.rs @@ -19,7 +19,7 @@ use promkit_widgets::{ }; use crate::{ - config::{event::Matcher, JsonViewerKeybinds}, + config::JsonViewerKeybinds, processor::{ViewProvider, Visualizer}, search::SearchProvider, }; @@ -51,35 +51,35 @@ impl Json { fn operate(&mut self, event: &Event) { match event { // Move up. - event if self.keybinds.up.matches(event) => { + event if self.keybinds.up.contains(event) => { self.state.stream.up(); } // Move down. - event if self.keybinds.down.matches(event) => { + event if self.keybinds.down.contains(event) => { self.state.stream.down(); } // Move to head - event if self.keybinds.move_to_head.matches(event) => { + event if self.keybinds.move_to_head.contains(event) => { self.state.stream.head(); } // Move to tail - event if self.keybinds.move_to_tail.matches(event) => { + event if self.keybinds.move_to_tail.contains(event) => { self.state.stream.tail(); } // Toggle collapse/expand - event if self.keybinds.toggle.matches(event) => { + event if self.keybinds.toggle.contains(event) => { self.state.stream.toggle(); } - event if self.keybinds.expand.matches(event) => { + event if self.keybinds.expand.contains(event) => { self.state.stream.set_nodes_visibility(false); } - event if self.keybinds.collapse.matches(event) => { + event if self.keybinds.collapse.contains(event) => { self.state.stream.set_nodes_visibility(true); } diff --git a/src/main.rs b/src/main.rs index e3aa45d..34bbe4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -154,7 +154,7 @@ async fn main() -> anyhow::Result<()> { .map_err(|e| anyhow!("Failed to read configuration file: {e}")) }) .and_then(|content| Config::load_from(&content)) - .unwrap_or_else(|e| { + .unwrap_or_else(|_e| { Config::load_from(DEFAULT_CONFIG).expect("Failed to load default configuration") }); diff --git a/src/prompt.rs b/src/prompt.rs index c69df3c..83d57fb 100644 --- a/src/prompt.rs +++ b/src/prompt.rs @@ -25,7 +25,7 @@ use tokio::{ }; use crate::{ - config::{event::Matcher, Keybinds, ReactivityControl}, + config::{Keybinds, ReactivityControl}, Context, ContextMonitor, Editor, Processor, SearchProvider, SpinnerSpawner, ViewInitializer, ViewProvider, Visualizer, }; @@ -191,7 +191,7 @@ pub async fn run( Event::Resize(width, height) => { debounce_resize_tx.send((width, height)).await?; }, - event if keybinds.exit.matches(&event) => { + event if keybinds.exit.contains(&event) => { break 'main }, Event::Key(KeyEvent { From c55ac1025f2769e2d6d762c7e765c94a963f1654 Mon Sep 17 00:00:00 2001 From: ynqa Date: Mon, 23 Feb 2026 23:19:33 +0900 Subject: [PATCH 07/35] feat: set style and keybind using easy readable strings --- default.toml | 134 +++++++++++++++++---------------------------------- 1 file changed, 43 insertions(+), 91 deletions(-) diff --git a/default.toml b/default.toml index b0b2327..514838a 100644 --- a/default.toml +++ b/default.toml @@ -34,22 +34,22 @@ word_break_chars = [".", "|", "(", ")", "[", "]"] # Prefix shown before the cursor prefix = "❯❯ " # Style for the prefix -prefix_style = { foreground = "blue" } +prefix_style = "fg=blue" # Style for the character under the cursor -active_char_style = { background = "magenta" } +active_char_style = "bg=magenta" # Style for all other characters -inactive_char_style = {} +inactive_char_style = "" # Theme settings when the editor is unfocused [editor.theme_on_defocus] # Prefix shown when focus is lost prefix = "▼ " # Style for the prefix when unfocused -prefix_style = { foreground = "blue", attributes = ["Dim"] } +prefix_style = "fg=blue,attr=dim" # Style for the character under the cursor when unfocused -active_char_style = { attributes = ["Dim"] } +active_char_style = "attr=dim" # Style for all other characters when unfocused -inactive_char_style = { attributes = ["Dim"] } +inactive_char_style = "attr=dim" # JSON display settings [json] @@ -63,19 +63,19 @@ inactive_char_style = { attributes = ["Dim"] } # Number of spaces to use for indentation indent = 2 # Style for curly brackets {} -curly_brackets_style = { attributes = ["Bold"] } +curly_brackets_style = "attr=bold" # Style for square brackets [] -square_brackets_style = { attributes = ["Bold"] } +square_brackets_style = "attr=bold" # Style for JSON keys -key_style = { foreground = "cyan" } +key_style = "fg=cyan" # Style for string values -string_value_style = { foreground = "green" } +string_value_style = "fg=green" # Style for number values -number_value_style = {} +number_value_style = "" # Style for boolean values -boolean_value_style = {} +boolean_value_style = "" # Style for null values -null_value_style = { foreground = "grey" } +null_value_style = "fg=grey" # Completion feature settings [completion] @@ -84,9 +84,9 @@ lines = 3 # Cursor character shown before the selected candidate cursor = "❯ " # Style for the selected candidate -active_item_style = { foreground = "grey", background = "yellow" } +active_item_style = "fg=grey,bg=yellow" # Style for unselected candidates -inactive_item_style = { foreground = "grey" } +inactive_item_style = "fg=grey" # Settings for background loading of completion candidates # @@ -101,111 +101,63 @@ search_load_chunk_size = 50000 # Keybinding settings [keybinds] # Key to exit the application -exit = [ - { Key = { modifiers = "CONTROL", code = { Char = "c" } } } -] +exit = ["Ctrl+C"] # Key to copy the query to the clipboard -copy_query = [ - { Key = { modifiers = "CONTROL", code = { Char = "q" } } } -] +copy_query = ["Ctrl+Q"] # Key to copy the result to the clipboard -copy_result = [ - { Key = { modifiers = "CONTROL", code = { Char = "o" } } } -] +copy_result = ["Ctrl+O"] # Keys to switch focus between editor and JSON viewer -switch_mode = [ - { Key = { code = "Down", modifiers = "SHIFT" } }, - { Key = { code = "Up", modifiers = "SHIFT" } } -] +switch_mode = ["Shift+Down", "Shift+Up"] # Keybindings for editor operations [keybinds.on_editor] # Move cursor left -backward = [ - { Key = { code = "Left", modifiers = "" } } -] +backward = ["Left"] + # Move cursor right -forward = [ - { Key = { code = "Right", modifiers = "" } } -] +forward = ["Right"] + # Move cursor to beginning of line -move_to_head = [ - { Key = { modifiers = "CONTROL", code = { Char = "a" } } } -] +move_to_head = ["Ctrl+A"] # Move cursor to end of line -move_to_tail = [ - { Key = { modifiers = "CONTROL", code = { Char = "e" } } } -] +move_to_tail = ["Ctrl+E"] # Move cursor to previous word boundary -move_to_previous_nearest = [ - { Key = { modifiers = "ALT", code = { Char = "b" } } } -] +move_to_previous_nearest = ["Alt+B"] # Move cursor to next word boundary -move_to_next_nearest = [ - { Key = { modifiers = "ALT", code = { Char = "f" } } } -] +move_to_next_nearest = ["Alt+F"] # Delete character at the cursor -erase = [ - { Key = { code = "Backspace", modifiers = "" } } -] +erase = ["Backspace"] + # Delete all input -erase_all = [ - { Key = { modifiers = "CONTROL", code = { Char = "u" } } } -] +erase_all = ["Ctrl+U"] + # Delete from cursor to previous word boundary -erase_to_previous_nearest = [ - { Key = { modifiers = "CONTROL", code = { Char = "w" } } } -] +erase_to_previous_nearest = ["Ctrl+W"] # Delete from cursor to next word boundary -erase_to_next_nearest = [ - { Key = { modifiers = "ALT", code = { Char = "d" } } } -] +erase_to_next_nearest = ["Alt+D"] # Trigger completion -completion = [ - { Key = { code = "Tab", modifiers = "" } } -] +completion = ["Tab"] # Move up in the completion list -on_completion.up = [ - { Key = { code = "Up", modifiers = "" } } -] +on_completion.up = ["Up"] # Move down in the completion list -on_completion.down = [ - { Key = { code = "Down", modifiers = "" } }, - { Key = { code = "Tab", modifiers = "" } } -] +on_completion.down = ["Down", "Tab"] # Keybindings for JSON viewer operations [keybinds.on_json_viewer] # Move up in JSON viewer -up = [ - { Key = { code = "Up", modifiers = "" } }, - { Key = { modifiers = "CONTROL", code = { Char = "k" } } } -] +up = ["Up", "Ctrl+K"] # Move down in JSON viewer -down = [ - { Key = { modifiers = "CONTROL", code = { Char = "j" } } }, - { Key = { code = "Down", modifiers = "" } } -] +down = ["Down", "Ctrl+J"] # Move to the top of JSON viewer -move_to_head = [ - { Key = { modifiers = "CONTROL", code = { Char = "l" } } } -] +move_to_head = ["Ctrl+L"] # Move to the bottom of JSON viewer -move_to_tail = [ - { Key = { modifiers = "CONTROL", code = { Char = "h" } } } -] +move_to_tail = ["Ctrl+H"] # Toggle expand/collapse of JSON nodes -toggle = [ - { Key = { code = "Enter", modifiers = "" } } -] +toggle = ["Enter"] # Expand all JSON nodes -expand = [ - { Key = { modifiers = "CONTROL", code = { Char = "p" } } } -] +expand = ["Ctrl+P"] # Collapse all JSON nodes -collapse = [ - { Key = { modifiers = "CONTROL", code = { Char = "n" } } } -] +collapse = ["Ctrl+N"] # Application reactivity settings [reactivity_control] From ac742b6081be150e0678446f93bbc35327df366e Mon Sep 17 00:00:00 2001 From: ynqa Date: Mon, 23 Feb 2026 23:48:33 +0900 Subject: [PATCH 08/35] docs: update new syntax for configurations --- README.md | 193 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 148 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index e8fd8ad..3b9301a 100644 --- a/README.md +++ b/README.md @@ -206,87 +206,190 @@ it will be automatically created on first run. ### Configuration Options +> [!IMPORTANT] +> The syntax in TOML configurations +> like [default.toml](./default.toml) was revamped in v0.8.0, +> and the configuration shown below reflects the new format. +> A migration tool is not provided for this change. +> Please manually replace/update your local +> `config.toml` to match the new syntax. + The following settings are available in `config.toml`: ```toml -# Whether to hide the hint message +# Whether to hide hint messages no_hint = false # Editor settings [editor] -# Editor mode ("Insert" or "Overwrite") +# Editor mode +# "Insert": Insert characters at the cursor position +# "Overwrite": Replace characters at the cursor position with new ones mode = "Insert" -# Word break characters + +# Characters considered as word boundaries +# These are used to define word movement and deletion behavior in the editor word_break_chars = [".", "|", "(", ")", "[", "]"] -# Theme when editor is focused +# How to configure colors and text attributes +# +# Color specification methods: +# 1. By name: "black", "red", etc. +# 2. By RGB value: "rgb_(255,0,0)" or "#ff0000" +# 3. By ANSI value: "ansi_(16)" +# +# Text attribute specification: +# attributes = ["Bold"], etc. +# +# Configuration example: +# style = { foreground = "blue", background = "magenta", attributes = ["Bold"] } +# +# Detailed information: +# - Color: https://docs.rs/crossterm/0.28.1/crossterm/style/enum.Color.html +# - Attribute: https://docs.rs/crossterm/0.28.1/crossterm/style/enum.Attribute.html + +# Theme settings when the editor is focused [editor.theme_on_focus] +# Prefix shown before the cursor prefix = "❯❯ " -prefix_style = { foreground = "blue" } -active_char_style = { background = "magenta" } -inactive_char_style = {} - -# Theme when editor is not focused +# Style for the prefix +prefix_style = "fg=blue" +# Style for the character under the cursor +active_char_style = "bg=magenta" +# Style for all other characters +inactive_char_style = "" + +# Theme settings when the editor is unfocused [editor.theme_on_defocus] +# Prefix shown when focus is lost prefix = "▼ " -prefix_style = { foreground = "blue", attributes = ["Dim"] } -active_char_style = { attributes = ["Dim"] } -inactive_char_style = { attributes = ["Dim"] } +# Style for the prefix when unfocused +prefix_style = "fg=blue,attr=dim" +# Style for the character under the cursor when unfocused +active_char_style = "attr=dim" +# Style for all other characters when unfocused +inactive_char_style = "attr=dim" # JSON display settings [json] -# Maximum number of JSON objects to read from stream -# max_streams = +# Maximum number of JSON objects to read from streams (e.g., JSON Lines format) +# Limits how many objects are processed to reduce memory usage when handling large data streams +# No limit if unset +# max_streams = -# JSON theme settings +# JSON display theme [json.theme] +# Number of spaces to use for indentation indent = 2 -curly_brackets_style = { attributes = ["Bold"] } -square_brackets_style = { attributes = ["Bold"] } -key_style = { foreground = "cyan" } -string_value_style = { foreground = "green" } -number_value_style = {} -boolean_value_style = {} -null_value_style = { foreground = "grey" } +# Style for curly brackets {} +curly_brackets_style = "attr=bold" +# Style for square brackets [] +square_brackets_style = "attr=bold" +# Style for JSON keys +key_style = "fg=cyan" +# Style for string values +string_value_style = "fg=green" +# Style for number values +number_value_style = "" +# Style for boolean values +boolean_value_style = "" +# Style for null values +null_value_style = "fg=grey" # Completion feature settings [completion] +# Number of lines to display for completion candidates lines = 3 +# Cursor character shown before the selected candidate cursor = "❯ " -active_item_style = { foreground = "grey", background = "yellow" } -inactive_item_style = { foreground = "grey" } +# Style for the selected candidate +active_item_style = "fg=grey,bg=yellow" +# Style for unselected candidates +inactive_item_style = "fg=grey" + +# Settings for background loading of completion candidates +# +# Number of candidates loaded per chunk for search results +# A larger value displays results faster but uses more memory search_result_chunk_size = 100 + +# Number of items loaded per batch during background loading +# A larger value finishes loading sooner but uses more memory temporarily search_load_chunk_size = 50000 -# Keybind settings +# Keybinding settings [keybinds] -# Application exit key -exit = [{ Key = { modifiers = "CONTROL", code = { Char = "c" } } }] -# Copy query to clipboard key -copy_query = [{ Key = { modifiers = "CONTROL", code = { Char = "q" } } }] -# Copy result to clipboard key -copy_result = [{ Key = { modifiers = "CONTROL", code = { Char = "o" } } }] -# Mode switch keys -switch_mode = [ - { Key = { code = "Down", modifiers = "SHIFT" } }, - { Key = { code = "Up", modifiers = "SHIFT" } } -] - -# Editor operation keybinds +# Key to exit the application +exit = ["Ctrl+C"] +# Key to copy the query to the clipboard +copy_query = ["Ctrl+Q"] +# Key to copy the result to the clipboard +copy_result = ["Ctrl+O"] +# Keys to switch focus between editor and JSON viewer +switch_mode = ["Shift+Down", "Shift+Up"] + +# Keybindings for editor operations [keybinds.on_editor] -# (Details omitted) - -# JSON viewer keybinds +# Move cursor left +backward = ["Left"] + +# Move cursor right +forward = ["Right"] + +# Move cursor to beginning of line +move_to_head = ["Ctrl+A"] +# Move cursor to end of line +move_to_tail = ["Ctrl+E"] +# Move cursor to previous word boundary +move_to_previous_nearest = ["Alt+B"] +# Move cursor to next word boundary +move_to_next_nearest = ["Alt+F"] +# Delete character at the cursor +erase = ["Backspace"] + +# Delete all input +erase_all = ["Ctrl+U"] + +# Delete from cursor to previous word boundary +erase_to_previous_nearest = ["Ctrl+W"] +# Delete from cursor to next word boundary +erase_to_next_nearest = ["Alt+D"] +# Trigger completion +completion = ["Tab"] +# Move up in the completion list +on_completion.up = ["Up"] +# Move down in the completion list +on_completion.down = ["Down", "Tab"] + +# Keybindings for JSON viewer operations [keybinds.on_json_viewer] -# (Details omitted) +# Move up in JSON viewer +up = ["Up", "Ctrl+K"] +# Move down in JSON viewer +down = ["Down", "Ctrl+J"] +# Move to the top of JSON viewer +move_to_head = ["Ctrl+L"] +# Move to the bottom of JSON viewer +move_to_tail = ["Ctrl+H"] +# Toggle expand/collapse of JSON nodes +toggle = ["Enter"] +# Expand all JSON nodes +expand = ["Ctrl+P"] +# Collapse all JSON nodes +collapse = ["Ctrl+N"] # Application reactivity settings [reactivity_control] -# Delay time after query input +# Delay before processing query input +# Prevents excessive updates while user is typing query_debounce_duration = "600ms" -# Redraw delay time after window resize + +# Delay before redrawing after window resize +# Prevents frequent redraws during continuous resizing resize_debounce_duration = "200ms" -# Spinner animation update interval + +# Interval for spinner animation updates +# Controls the speed of the loading spinner spin_duration = "300ms" ``` From 4ea3ae844d316c439f2330cfdfdfa2c7f55917c7 Mon Sep 17 00:00:00 2001 From: ynqa Date: Wed, 25 Feb 2026 07:21:31 +0900 Subject: [PATCH 09/35] chore: use official version for termcfg --- Cargo.lock | 2 ++ Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 00fa6f5..2028174 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1438,6 +1438,8 @@ dependencies = [ [[package]] name = "termcfg" version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8c057661e9a795bc9cb18a7d2924b00d0aa3c5a9e61dfe7de32cea8a1d8fe7" dependencies = [ "crossterm", "serde", diff --git a/Cargo.toml b/Cargo.toml index 422e269..1111a6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ dirs = "6.0.0" futures = "0.3.32" promkit-widgets = { version = "0.2.0", features = ["jsonstream", "listbox", "text", "texteditor"] } serde = "1.0.228" -termcfg = { path = "../termcfg", features = ["crossterm_0_29_0"] } +termcfg = { version = "0.1.0", features = ["crossterm_0_29_0"] } tokio = { version = "1.49.0", features = ["full"] } tokio-stream = "0.1.18" toml = "0.9.8" From 826ce642d40f831e73c8175103c1ff88d8dc34b0 Mon Sep 17 00:00:00 2001 From: ynqa Date: Wed, 25 Feb 2026 00:25:34 +0900 Subject: [PATCH 10/35] debug: use local promkit-widgets --- Cargo.lock | 18 ++++++++++++------ Cargo.toml | 3 ++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2028174..1730733 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -858,7 +858,7 @@ dependencies = [ "jaq-std", "promkit-widgets", "serde", - "termcfg", + "termcfg 0.1.0", "tokio", "tokio-stream", "toml", @@ -1157,8 +1157,6 @@ dependencies = [ [[package]] name = "promkit-core" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed89f85398b2590095afe8fb4852c177d09f568836c206a9bed823bf1e70a051" dependencies = [ "anyhow", "crossbeam-skiplist", @@ -1169,15 +1167,14 @@ dependencies = [ [[package]] name = "promkit-widgets" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7ef81079760b198d5dde773c78b94a72edc2ebd057be386382c379e0d854fb6" +version = "0.3.0" dependencies = [ "anyhow", "promkit-core", "rayon", "serde", "serde_json", + "termcfg 0.2.0", "tokio", ] @@ -1446,6 +1443,15 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "termcfg" +version = "0.2.0" +dependencies = [ + "crossterm", + "serde", + "thiserror 2.0.18", +] + [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index 1111a6d..adccc6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,8 @@ duration-string = { version = "0.5.3", features = ["serde"] } derive_builder = "0.20.2" dirs = "6.0.0" futures = "0.3.32" -promkit-widgets = { version = "0.2.0", features = ["jsonstream", "listbox", "text", "texteditor"] } +# promkit-widgets = { version = "0.2.0", features = ["jsonstream", "listbox", "text", "texteditor"] } +promkit-widgets = { path = "../promkit/promkit-widgets", version = "0.3.0", features = ["jsonstream", "listbox", "text", "texteditor"] } serde = "1.0.228" termcfg = { version = "0.1.0", features = ["crossterm_0_29_0"] } tokio = { version = "1.49.0", features = ["full"] } From 043e40d86f3284f1c7f5da7a253a72b865fe705e Mon Sep 17 00:00:00 2001 From: ynqa Date: Wed, 25 Feb 2026 02:06:56 +0900 Subject: [PATCH 11/35] chore: use promkit-widgets v0.2.0 => v0.3.0 --- default.toml | 40 +++++++++++++---------- src/config.rs | 67 ++++----------------------------------- src/config/text_editor.rs | 33 ------------------- src/editor.rs | 53 +++++++++++++------------------ src/json.rs | 43 +++++++++++++------------ src/main.rs | 37 ++++----------------- src/prompt.rs | 35 ++++++++++++++------ src/search.rs | 6 ++-- 8 files changed, 108 insertions(+), 206 deletions(-) delete mode 100644 src/config/text_editor.rs diff --git a/default.toml b/default.toml index 514838a..7c8e13f 100644 --- a/default.toml +++ b/default.toml @@ -2,11 +2,13 @@ no_hint = false # Editor settings -[editor] +# Uses promkit_widgets::text_editor::Config directly +[editor.on_focus] + # Editor mode # "Insert": Insert characters at the cursor position # "Overwrite": Replace characters at the cursor position with new ones -mode = "Insert" +edit_mode = "Insert" # Characters considered as word boundaries # These are used to define word movement and deletion behavior in the editor @@ -29,8 +31,6 @@ word_break_chars = [".", "|", "(", ")", "[", "]"] # - Color: https://docs.rs/crossterm/0.28.1/crossterm/style/enum.Color.html # - Attribute: https://docs.rs/crossterm/0.28.1/crossterm/style/enum.Attribute.html -# Theme settings when the editor is focused -[editor.theme_on_focus] # Prefix shown before the cursor prefix = "❯❯ " # Style for the prefix @@ -41,7 +41,7 @@ active_char_style = "bg=magenta" inactive_char_style = "" # Theme settings when the editor is unfocused -[editor.theme_on_defocus] +[editor.on_defocus] # Prefix shown when focus is lost prefix = "▼ " # Style for the prefix when unfocused @@ -58,8 +58,9 @@ inactive_char_style = "attr=dim" # No limit if unset # max_streams = -# JSON display theme -[json.theme] +# JSON display settings +# Uses promkit_widgets::jsonstream::Config directly +[json.stream] # Number of spaces to use for indentation indent = 2 # Style for curly brackets {} @@ -76,18 +77,13 @@ number_value_style = "" boolean_value_style = "" # Style for null values null_value_style = "fg=grey" +# Attribute for the selected row and unselected rows +active_item_attribute = "bold" +# Attribute for unselected rows +inactive_item_attribute = "dim" # Completion feature settings [completion] -# Number of lines to display for completion candidates -lines = 3 -# Cursor character shown before the selected candidate -cursor = "❯ " -# Style for the selected candidate -active_item_style = "fg=grey,bg=yellow" -# Style for unselected candidates -inactive_item_style = "fg=grey" - # Settings for background loading of completion candidates # # Number of candidates loaded per chunk for search results @@ -98,6 +94,18 @@ search_result_chunk_size = 100 # A larger value finishes loading sooner but uses more memory temporarily search_load_chunk_size = 50000 +# Completion UI settings +# Uses promkit_widgets::listbox::Config directly +[completion.listbox] +# Number of lines to display for completion candidates +lines = 3 +# Cursor character shown before the selected candidate +cursor = "❯ " +# Style for the selected candidate +active_item_style = "fg=grey,bg=yellow" +# Style for unselected candidates +inactive_item_style = "fg=grey" + # Keybinding settings [keybinds] # Key to exit the application diff --git a/src/config.rs b/src/config.rs index 172cc7c..fb8ee63 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,85 +1,30 @@ use std::collections::HashSet; -use promkit_widgets::{ - core::crossterm::{event::Event, style::ContentStyle}, - text_editor::Mode, -}; +use promkit_widgets::{core::crossterm::event::Event, jsonstream, listbox, text_editor}; use serde::{Deserialize, Serialize}; -use termcfg::crossterm_config::{content_style_serde, event_set_serde}; +use termcfg::crossterm_config::event_set_serde; use tokio::time::Duration; mod duration; use duration::duration_serde; -mod text_editor; -use text_editor::text_editor_mode_serde; #[derive(Serialize, Deserialize)] pub struct EditorConfig { - pub theme_on_focus: EditorTheme, - pub theme_on_defocus: EditorTheme, - #[serde(with = "text_editor_mode_serde")] - pub mode: Mode, - pub word_break_chars: HashSet, -} - -#[derive(Serialize, Deserialize)] -pub struct EditorTheme { - pub prefix: String, - - #[serde(with = "content_style_serde")] - pub prefix_style: ContentStyle, - - #[serde(with = "content_style_serde")] - pub active_char_style: ContentStyle, - - #[serde(with = "content_style_serde")] - pub inactive_char_style: ContentStyle, + pub on_focus: text_editor::Config, + pub on_defocus: text_editor::Config, } #[derive(Serialize, Deserialize)] pub struct JsonConfig { pub max_streams: Option, - pub theme: JsonTheme, -} - -#[derive(Serialize, Deserialize)] -pub struct JsonTheme { - pub indent: usize, - - #[serde(with = "content_style_serde")] - pub curly_brackets_style: ContentStyle, - - #[serde(with = "content_style_serde")] - pub square_brackets_style: ContentStyle, - - #[serde(with = "content_style_serde")] - pub key_style: ContentStyle, - - #[serde(with = "content_style_serde")] - pub string_value_style: ContentStyle, - - #[serde(with = "content_style_serde")] - pub number_value_style: ContentStyle, - - #[serde(with = "content_style_serde")] - pub boolean_value_style: ContentStyle, - - #[serde(with = "content_style_serde")] - pub null_value_style: ContentStyle, + pub stream: jsonstream::Config, } #[derive(Serialize, Deserialize)] pub struct CompletionConfig { - pub lines: Option, - pub cursor: String, + pub listbox: listbox::Config, pub search_result_chunk_size: usize, pub search_load_chunk_size: usize, - - #[serde(with = "content_style_serde")] - pub active_item_style: ContentStyle, - - #[serde(with = "content_style_serde")] - pub inactive_item_style: ContentStyle, } // TODO: remove Clone derive diff --git a/src/config/text_editor.rs b/src/config/text_editor.rs deleted file mode 100644 index ff39cb0..0000000 --- a/src/config/text_editor.rs +++ /dev/null @@ -1,33 +0,0 @@ -use promkit_widgets::text_editor::Mode; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -pub mod text_editor_mode_serde { - use super::*; - - pub fn serialize(mode: &Mode, serializer: S) -> Result - where - S: Serializer, - { - let mode_str = match mode { - Mode::Insert => "Insert", - Mode::Overwrite => "Overwrite", - // Add other variants if they exist - }; - mode_str.serialize(serializer) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let mode_str = String::deserialize(deserializer)?; - match mode_str.as_str() { - "Insert" => Ok(Mode::Insert), - "Overwrite" => Ok(Mode::Overwrite), - // Add other variants if they exist - _ => Err(serde::de::Error::custom(format!( - "Unknown Mode variant: {mode_str}", - ))), - } - } -} diff --git a/src/editor.rs b/src/editor.rs index 25a93be..792f3a7 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -12,16 +12,13 @@ use promkit_widgets::{ text_editor, }; -use crate::{ - config::{EditorKeybinds, EditorTheme}, - search::IncrementalSearcher, -}; +use crate::{config::EditorKeybinds, search::IncrementalSearcher}; pub struct Editor { handler: Handler, state: text_editor::State, - focus_theme: EditorTheme, - defocus_theme: EditorTheme, + focus_config: text_editor::Config, + defocus_config: text_editor::Config, guide: text::State, searcher: IncrementalSearcher, editor_keybinds: EditorKeybinds, @@ -31,15 +28,15 @@ impl Editor { pub fn new( state: text_editor::State, searcher: IncrementalSearcher, - focus_theme: EditorTheme, - defocus_theme: EditorTheme, + focus_config: text_editor::Config, + defocus_config: text_editor::Config, editor_keybinds: EditorKeybinds, ) -> Self { Self { handler: BOXED_EDITOR_HANDLER, state, - focus_theme, - defocus_theme, + focus_config, + defocus_config, guide: text::State::default(), searcher, editor_keybinds, @@ -47,17 +44,11 @@ impl Editor { } pub fn focus(&mut self) { - self.state.prefix = self.focus_theme.prefix.clone(); - self.state.prefix_style = self.focus_theme.prefix_style; - self.state.inactive_char_style = self.focus_theme.inactive_char_style; - self.state.active_char_style = self.focus_theme.active_char_style; + self.state.config = self.focus_config.clone(); } pub fn defocus(&mut self) { - self.state.prefix = self.defocus_theme.prefix.clone(); - self.state.prefix_style = self.defocus_theme.prefix_style; - self.state.inactive_char_style = self.defocus_theme.inactive_char_style; - self.state.active_char_style = self.defocus_theme.active_char_style; + self.state.config = self.defocus_config.clone(); self.searcher.leave_search(); self.handler = BOXED_EDITOR_HANDLER; @@ -114,19 +105,19 @@ pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Resul "Loaded all ({}) suggestions", result.load_state.loaded_item_len )); - editor.guide.style = ContentStyle { + editor.guide.config.style = Some(ContentStyle { foreground_color: Some(Color::Green), ..Default::default() - }; + }); } else { editor.guide.text = Text::from(format!( "Loaded partially ({}) suggestions", result.load_state.loaded_item_len )); - editor.guide.style = ContentStyle { + editor.guide.config.style = Some(ContentStyle { foreground_color: Some(Color::Green), ..Default::default() - }; + }); } editor.state.texteditor.replace(&head); editor.handler = BOXED_SEARCHER_HANDLER; @@ -134,18 +125,18 @@ pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Resul None => { editor.guide.text = Text::from(format!("No suggestion found for '{prefix}'")); - editor.guide.style = ContentStyle { + editor.guide.config.style = Some(ContentStyle { foreground_color: Some(Color::Yellow), ..Default::default() - }; + }); } }, Err(e) => { editor.guide.text = Text::from(format!("Failed to lookup suggestions: {e}")); - editor.guide.style = ContentStyle { + editor.guide.config.style = Some(ContentStyle { foreground_color: Some(Color::Yellow), ..Default::default() - }; + }); } } } @@ -173,13 +164,13 @@ pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Resul editor .state .texteditor - .move_to_previous_nearest(&editor.state.word_break_chars); + .move_to_previous_nearest(&editor.state.config.word_break_chars); } key if editor.editor_keybinds.move_to_next_nearest.contains(key) => { editor .state .texteditor - .move_to_next_nearest(&editor.state.word_break_chars); + .move_to_next_nearest(&editor.state.config.word_break_chars); } // Erase char(s). @@ -199,13 +190,13 @@ pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Resul editor .state .texteditor - .erase_to_previous_nearest(&editor.state.word_break_chars); + .erase_to_previous_nearest(&editor.state.config.word_break_chars); } key if editor.editor_keybinds.erase_to_next_nearest.contains(key) => { editor .state .texteditor - .erase_to_next_nearest(&editor.state.word_break_chars); + .erase_to_next_nearest(&editor.state.config.word_break_chars); } // Input char. @@ -220,7 +211,7 @@ pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Resul modifiers: KeyModifiers::SHIFT, kind: KeyEventKind::Press, state: KeyEventState::NONE, - }) => match editor.state.edit_mode { + }) => match editor.state.config.edit_mode { text_editor::Mode::Insert => editor.state.texteditor.insert(*ch), text_editor::Mode::Overwrite => editor.state.texteditor.overwrite(*ch), }, diff --git a/src/json.rs b/src/json.rs index 5fcdd49..a332643 100644 --- a/src/json.rs +++ b/src/json.rs @@ -13,7 +13,7 @@ use promkit_widgets::{ pane::Pane, PaneFactory, }, - jsonstream::{self, format::RowFormatter, jsonz, JsonStream}, + jsonstream::{self, config::Config as JsonStreamConfig, jsonz, JsonStream}, serde_json::{self, Deserializer, Value}, text::{self, Text}, }; @@ -33,19 +33,17 @@ pub struct Json { impl Json { pub fn new( - formatter: RowFormatter, + formatter: JsonStreamConfig, input_stream: &'static [serde_json::Value], keybinds: JsonViewerKeybinds, ) -> anyhow::Result { - let state = jsonstream::State { - stream: JsonStream::new(input_stream.iter()), - formatter, - lines: Default::default(), - }; Ok(Self { json: input_stream, - state, + state: jsonstream::State { + stream: JsonStream::new(input_stream.iter()), + config: formatter, + }, keybinds, }) } @@ -93,9 +91,7 @@ impl Json { #[async_trait::async_trait] impl Visualizer for Json { async fn content_to_copy(&self) -> String { - self.state - .formatter - .format_raw_json(self.state.stream.rows()) + self.state.config.format_raw_json(self.state.stream.rows()) } async fn create_init_pane(&mut self, area: (u16, u16)) -> Pane { @@ -118,9 +114,12 @@ impl Visualizer for Json { if ret.iter().all(|val| *val == Value::Null) { guide = Some(text::State { text: Text::from(format!("jq returned 'null', which may indicate a typo or incorrect filter: `{input}`")), - style: ContentStyle { - foreground_color: Some(Color::Yellow), - attributes: Attributes::from(Attribute::Bold), + config: text::Config { + style: Some(ContentStyle { + foreground_color: Some(Color::Yellow), + attributes: Attributes::from(Attribute::Bold), + ..Default::default() + }), ..Default::default() }, ..Default::default() @@ -140,12 +139,14 @@ impl Visualizer for Json { Some( text::State { text: Text::from(format!("jq failed: `{e}`")), - style: ContentStyle { - foreground_color: Some(Color::Red), - attributes: Attributes::from(Attribute::Bold), + config: text::Config { + style: Some(ContentStyle { + foreground_color: Some(Color::Red), + attributes: Attributes::from(Attribute::Bold), + ..Default::default() + }), ..Default::default() - }, - ..Default::default() + } } .create_pane(area.0, area.1), ), @@ -194,12 +195,12 @@ fn run_jaq( #[derive(Clone)] pub struct JsonStreamProvider { - formatter: RowFormatter, + formatter: JsonStreamConfig, max_streams: Option, } impl JsonStreamProvider { - pub fn new(formatter: RowFormatter, max_streams: Option) -> Self { + pub fn new(formatter: JsonStreamConfig, max_streams: Option) -> Self { Self { formatter, max_streams, diff --git a/src/main.rs b/src/main.rs index 34bbe4b..6d900b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,8 +8,6 @@ use anyhow::anyhow; use clap::Parser; use config::Config; use promkit_widgets::{ - core::crossterm::style::Attribute, - jsonstream::format::RowFormatter, listbox::{self, Listbox}, text_editor::{self, TextEditor}, }; @@ -160,10 +158,7 @@ async fn main() -> anyhow::Result<()> { let listbox_state = listbox::State { listbox: Listbox::default(), - cursor: config.completion.cursor, - active_item_style: Some(config.completion.active_item_style), - inactive_item_style: Some(config.completion.inactive_item_style), - lines: config.completion.lines, + config: config.completion.listbox.clone(), }; let searcher = @@ -176,31 +171,11 @@ async fn main() -> anyhow::Result<()> { Default::default() }, history: Default::default(), - prefix: config.editor.theme_on_focus.prefix.clone(), - mask: Default::default(), - prefix_style: config.editor.theme_on_focus.prefix_style, - active_char_style: config.editor.theme_on_focus.active_char_style, - inactive_char_style: config.editor.theme_on_focus.inactive_char_style, - edit_mode: config.editor.mode, - word_break_chars: config.editor.word_break_chars, - lines: Default::default(), + config: config.editor.on_focus.clone(), }; - let provider = &mut JsonStreamProvider::new( - RowFormatter { - curly_brackets_style: config.json.theme.curly_brackets_style, - square_brackets_style: config.json.theme.square_brackets_style, - key_style: config.json.theme.key_style, - string_value_style: config.json.theme.string_value_style, - number_value_style: config.json.theme.number_value_style, - boolean_value_style: config.json.theme.boolean_value_style, - null_value_style: config.json.theme.null_value_style, - active_item_attribute: Attribute::Bold, - inactive_item_attribute: Attribute::Dim, - indent: config.json.theme.indent, - }, - config.json.max_streams, - ); + let provider = + &mut JsonStreamProvider::new(config.json.stream.clone(), config.json.max_streams); let item = Box::leak(input.into_boxed_str()); @@ -213,8 +188,8 @@ async fn main() -> anyhow::Result<()> { let editor = Editor::new( text_editor_state, searcher, - config.editor.theme_on_focus, - config.editor.theme_on_defocus, + config.editor.on_focus, + config.editor.on_defocus, // TODO: remove clones config.keybinds.on_editor.clone(), ); diff --git a/src/prompt.rs b/src/prompt.rs index 83d57fb..afdb8af 100644 --- a/src/prompt.rs +++ b/src/prompt.rs @@ -62,16 +62,22 @@ fn copy_to_clipboard(content: &str) -> text::State { Ok(mut clipboard) => match clipboard.set_text(content) { Ok(_) => text::State { text: Text::from("Copied to clipboard"), - style: ContentStyle { - foreground_color: Some(Color::Green), + config: text::Config { + style: Some(ContentStyle { + foreground_color: Some(Color::Green), + ..Default::default() + }), ..Default::default() }, ..Default::default() }, Err(e) => text::State { text: Text::from(format!("Failed to copy to clipboard: {e}")), - style: ContentStyle { - foreground_color: Some(Color::Red), + config: text::Config { + style: Some(ContentStyle { + foreground_color: Some(Color::Red), + ..Default::default() + }), ..Default::default() }, ..Default::default() @@ -82,8 +88,11 @@ fn copy_to_clipboard(content: &str) -> text::State { // https://github.com/1Password/arboard/issues/153 Err(e) => text::State { text: Text::from(format!("Failed to setup clipboard: {e}")), - style: ContentStyle { - foreground_color: Some(Color::Red), + config: text::Config { + style: Some(ContentStyle { + foreground_color: Some(Color::Red), + ..Default::default() + }), ..Default::default() }, ..Default::default() @@ -217,8 +226,11 @@ pub async fn run( Index::Guide, text::State { text: Text::from("Failed to copy while rendering is in progress.".to_string()), - style: ContentStyle { - foreground_color: Some(Color::Yellow), + config: text::Config { + style: Some(ContentStyle { + foreground_color: Some(Color::Yellow), + ..Default::default() + }), ..Default::default() }, ..Default::default() @@ -250,8 +262,11 @@ pub async fn run( Index::Guide, text::State { text: Text::from("Failed to switch pane while rendering is in progress.".to_string()), - style: ContentStyle { - foreground_color: Some(Color::Yellow), + config: text::Config { + style: Some(ContentStyle { + foreground_color: Some(Color::Yellow), + ..Default::default() + }), ..Default::default() }, ..Default::default() diff --git a/src/search.rs b/src/search.rs index 75004f0..93ce6a9 100644 --- a/src/search.rs +++ b/src/search.rs @@ -101,7 +101,7 @@ impl IncrementalSearcher { .listbox .len() .saturating_sub(self.state.listbox.position()) - < self.state.lines.unwrap_or(1) + < self.state.config.lines.unwrap_or(1) { self.load_more(); } @@ -116,7 +116,7 @@ impl IncrementalSearcher { } pub fn leave_search(&mut self) { - self.state.listbox = Listbox::from_displayable(Vec::::new()); + self.state.listbox = Listbox::from(Vec::::new()); self.search_chunk_remaining = Vec::::new(); } @@ -141,7 +141,7 @@ impl IncrementalSearcher { .drain(..self.search_result_chunk_size.min(items.len())) .collect::>(); self.search_chunk_remaining = items; - self.state.listbox = Listbox::from_displayable(used); + self.state.listbox = Listbox::from(used); Ok(StartSearchResult { head_item: Some(self.state.listbox.get().to_string()), load_state: state.clone(), From 472dfded715017c28055bb342119aa37fe66ef54 Mon Sep 17 00:00:00 2001 From: ynqa Date: Wed, 25 Feb 2026 02:13:33 +0900 Subject: [PATCH 12/35] docs: how to set .toml configs --- README.md | 90 +++++++++++++++++++++++++++++++--------------------- default.toml | 32 +++++++++++-------- 2 files changed, 72 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 3b9301a..48414ad 100644 --- a/README.md +++ b/README.md @@ -214,42 +214,55 @@ it will be automatically created on first run. > Please manually replace/update your local > `config.toml` to match the new syntax. -The following settings are available in `config.toml`: +> [!WARNING] +> Depending on the type of terminal and environment, +> characters and styles may not be displayed properly. +> Specific key bindings and decorative characters may not +> display or function correctly in certain terminal emulators. + +
+The following settings are available in config.toml ```toml # Whether to hide hint messages no_hint = false # Editor settings -[editor] +# Uses promkit_widgets::text_editor::Config directly +[editor.on_focus] + # Editor mode # "Insert": Insert characters at the cursor position # "Overwrite": Replace characters at the cursor position with new ones -mode = "Insert" +edit_mode = "Insert" # Characters considered as word boundaries # These are used to define word movement and deletion behavior in the editor word_break_chars = [".", "|", "(", ")", "[", "]"] -# How to configure colors and text attributes -# -# Color specification methods: -# 1. By name: "black", "red", etc. -# 2. By RGB value: "rgb_(255,0,0)" or "#ff0000" -# 3. By ANSI value: "ansi_(16)" +# Style notation (termcfg) +# Format: "fg=,bg=,ul=,attr=" +# Examples: +# - "fg=blue" +# - "fg=#00FF00,bg=black,attr=bold|underlined" +# - "attr=dim" # -# Text attribute specification: -# attributes = ["Bold"], etc. +# Color tokens: +# - reset, black, red, green, yellow, blue, magenta, cyan, white +# - darkgrey, darkred, darkgreen, darkyellow, darkblue, darkmagenta, darkcyan, grey +# - #RRGGBB # -# Configuration example: -# style = { foreground = "blue", background = "magenta", attributes = ["Bold"] } +# Attribute tokens (examples): +# - bold, italic, underlined, dim, reverse, crossedout, nounderline, nobold # -# Detailed information: -# - Color: https://docs.rs/crossterm/0.28.1/crossterm/style/enum.Color.html -# - Attribute: https://docs.rs/crossterm/0.28.1/crossterm/style/enum.Attribute.html +# Notes: +# - ANSI 256-color index tokens (0..255, e.g. "200") are currently out of notation scope. +# - See termcfg notation reference for full token list. +# +# References: +# - https://github.com/ynqa/termcfg/blob/main/Notations.md +# - https://github.com/ynqa/termcfg -# Theme settings when the editor is focused -[editor.theme_on_focus] # Prefix shown before the cursor prefix = "❯❯ " # Style for the prefix @@ -260,7 +273,7 @@ active_char_style = "bg=magenta" inactive_char_style = "" # Theme settings when the editor is unfocused -[editor.theme_on_defocus] +[editor.on_defocus] # Prefix shown when focus is lost prefix = "▼ " # Style for the prefix when unfocused @@ -277,8 +290,9 @@ inactive_char_style = "attr=dim" # No limit if unset # max_streams = -# JSON display theme -[json.theme] +# JSON display settings +# Uses promkit_widgets::jsonstream::Config directly +[json.stream] # Number of spaces to use for indentation indent = 2 # Style for curly brackets {} @@ -295,18 +309,13 @@ number_value_style = "" boolean_value_style = "" # Style for null values null_value_style = "fg=grey" +# Attribute for the selected row and unselected rows +active_item_attribute = "bold" +# Attribute for unselected rows +inactive_item_attribute = "dim" # Completion feature settings [completion] -# Number of lines to display for completion candidates -lines = 3 -# Cursor character shown before the selected candidate -cursor = "❯ " -# Style for the selected candidate -active_item_style = "fg=grey,bg=yellow" -# Style for unselected candidates -inactive_item_style = "fg=grey" - # Settings for background loading of completion candidates # # Number of candidates loaded per chunk for search results @@ -317,6 +326,18 @@ search_result_chunk_size = 100 # A larger value finishes loading sooner but uses more memory temporarily search_load_chunk_size = 50000 +# Completion UI settings +# Uses promkit_widgets::listbox::Config directly +[completion.listbox] +# Number of lines to display for completion candidates +lines = 3 +# Cursor character shown before the selected candidate +cursor = "❯ " +# Style for the selected candidate +active_item_style = "fg=grey,bg=yellow" +# Style for unselected candidates +inactive_item_style = "fg=grey" + # Keybinding settings [keybinds] # Key to exit the application @@ -393,13 +414,8 @@ resize_debounce_duration = "200ms" spin_duration = "300ms" ``` -For more details on configuration, please refer to [default.toml](./default.toml) - -> [!WARNING] -> Depending on the type of terminal and environment, -> characters and styles may not be displayed properly. -> Specific key bindings and decorative characters may not -> display or function correctly in certain terminal emulators. +
## Stargazers over time + [![Stargazers over time](https://starchart.cc/ynqa/jnv.svg?variant=adaptive)](https://starchart.cc/ynqa/jnv) diff --git a/default.toml b/default.toml index 7c8e13f..0ecd218 100644 --- a/default.toml +++ b/default.toml @@ -14,22 +14,28 @@ edit_mode = "Insert" # These are used to define word movement and deletion behavior in the editor word_break_chars = [".", "|", "(", ")", "[", "]"] -# How to configure colors and text attributes -# -# Color specification methods: -# 1. By name: "black", "red", etc. -# 2. By RGB value: "rgb_(255,0,0)" or "#ff0000" -# 3. By ANSI value: "ansi_(16)" +# Style notation (termcfg) +# Format: "fg=,bg=,ul=,attr=" +# Examples: +# - "fg=blue" +# - "fg=#00FF00,bg=black,attr=bold|underlined" +# - "attr=dim" # -# Text attribute specification: -# attributes = ["Bold"], etc. +# Color tokens: +# - reset, black, red, green, yellow, blue, magenta, cyan, white +# - darkgrey, darkred, darkgreen, darkyellow, darkblue, darkmagenta, darkcyan, grey +# - #RRGGBB # -# Configuration example: -# style = { foreground = "blue", background = "magenta", attributes = ["Bold"] } +# Attribute tokens (examples): +# - bold, italic, underlined, dim, reverse, crossedout, nounderline, nobold # -# Detailed information: -# - Color: https://docs.rs/crossterm/0.28.1/crossterm/style/enum.Color.html -# - Attribute: https://docs.rs/crossterm/0.28.1/crossterm/style/enum.Attribute.html +# Notes: +# - ANSI 256-color index tokens (0..255, e.g. "200") are currently out of notation scope. +# - See termcfg notation reference for full token list. +# +# References: +# - https://github.com/ynqa/termcfg/blob/main/Notations.md +# - https://github.com/ynqa/termcfg # Prefix shown before the cursor prefix = "❯❯ " From 3be5b0641dda885f38233e5256e163bd17e9efca Mon Sep 17 00:00:00 2001 From: ynqa Date: Wed, 25 Feb 2026 21:16:14 +0900 Subject: [PATCH 13/35] feat: use wrap for multiple lines for one field --- README.md | 4 ++++ default.toml | 4 ++++ src/json.rs | 3 +-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 48414ad..1a59532 100644 --- a/README.md +++ b/README.md @@ -313,6 +313,10 @@ null_value_style = "fg=grey" active_item_attribute = "bold" # Attribute for unselected rows inactive_item_attribute = "dim" +# Behavior when JSON content exceeds the available width +# "Wrap": Wrap content to the next line +# "Truncate": Truncate content with an ellipsis (...) +overflow_mode = "Wrap" # Completion feature settings [completion] diff --git a/default.toml b/default.toml index 0ecd218..db9aa08 100644 --- a/default.toml +++ b/default.toml @@ -87,6 +87,10 @@ null_value_style = "fg=grey" active_item_attribute = "bold" # Attribute for unselected rows inactive_item_attribute = "dim" +# Behavior when JSON content exceeds the available width +# "Wrap": Wrap content to the next line +# "Truncate": Truncate content with an ellipsis (...) +overflow_mode = "Wrap" # Completion feature settings [completion] diff --git a/src/json.rs b/src/json.rs index a332643..1bd93c2 100644 --- a/src/json.rs +++ b/src/json.rs @@ -37,7 +37,6 @@ impl Json { input_stream: &'static [serde_json::Value], keybinds: JsonViewerKeybinds, ) -> anyhow::Result { - Ok(Self { json: input_stream, state: jsonstream::State { @@ -146,7 +145,7 @@ impl Visualizer for Json { ..Default::default() }), ..Default::default() - } + }, } .create_pane(area.0, area.1), ), From 9ca58ea11f55d9b77b429a0cb78890f503f05263 Mon Sep 17 00:00:00 2001 From: ynqa Date: Wed, 25 Feb 2026 21:40:00 +0900 Subject: [PATCH 14/35] chore: use official v0.2.0 for termcfg --- Cargo.lock | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 1730733..e6b6da4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1446,6 +1446,8 @@ dependencies = [ [[package]] name = "termcfg" version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e444ec0de07571e26f31f6ac5006c03540b61d35bec105031e82c6d6cbb5fab" dependencies = [ "crossterm", "serde", From da3c871fc36ad3a81a2398253331c053e1e3eb26 Mon Sep 17 00:00:00 2001 From: ynqa Date: Wed, 25 Feb 2026 21:49:31 +0900 Subject: [PATCH 15/35] docs: config.toml will be revamped in v0.7.0 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a59532..bea138c 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ it will be automatically created on first run. > [!IMPORTANT] > The syntax in TOML configurations -> like [default.toml](./default.toml) was revamped in v0.8.0, +> like [default.toml](./default.toml) was revamped in v0.7.0, > and the configuration shown below reflects the new format. > A migration tool is not provided for this change. > Please manually replace/update your local From 310a843af0db559dabc7ac435327a003a3f749e7 Mon Sep 17 00:00:00 2001 From: ynqa Date: Wed, 25 Feb 2026 21:57:41 +0900 Subject: [PATCH 16/35] FIX: apply keybinds for copies and switch-mode --- src/prompt.rs | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/src/prompt.rs b/src/prompt.rs index afdb8af..e92986c 100644 --- a/src/prompt.rs +++ b/src/prompt.rs @@ -6,9 +6,7 @@ use promkit_widgets::{ core::{ crossterm::{ cursor, - event::{ - Event, EventStream, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, - }, + event::{Event, EventStream}, execute, style::{Color, ContentStyle}, terminal::{self, disable_raw_mode, enable_raw_mode}, @@ -203,20 +201,10 @@ pub async fn run( event if keybinds.exit.contains(&event) => { break 'main }, - Event::Key(KeyEvent { - code: KeyCode::Char('q'), - modifiers: KeyModifiers::CONTROL, - kind: KeyEventKind::Press, - state: KeyEventState::NONE, - }) => { + event if keybinds.copy_query.contains(&event) => { editor_copy_tx.send(()).await?; }, - Event::Key(KeyEvent { - code: KeyCode::Char('o'), - modifiers: KeyModifiers::CONTROL, - kind: KeyEventKind::Press, - state: KeyEventState::NONE, - }) => { + event if keybinds.copy_result.contains(&event) => { if context_monitor.is_idle().await { processor_copy_tx.send(()).await?; } else if !no_hint{ @@ -239,17 +227,7 @@ pub async fn run( ]).render().await?; } }, - Event::Key(KeyEvent { - code: KeyCode::Down, - modifiers: KeyModifiers::SHIFT, - kind: KeyEventKind::Press, - state: KeyEventState::NONE, - }) | Event::Key(KeyEvent { - code: KeyCode::Up, - modifiers: KeyModifiers::SHIFT, - kind: KeyEventKind::Press, - state: KeyEventState::NONE, - }) => { + event if keybinds.switch_mode.contains(&event) => { match focus { Focus::Editor => { if context_monitor.is_idle().await { From c05b72db6b5baf68f52abc315182bad412483c46 Mon Sep 17 00:00:00 2001 From: ynqa Date: Wed, 25 Feb 2026 22:31:46 +0900 Subject: [PATCH 17/35] feat: enable to scroll using mouse --- default.toml | 4 ++-- src/prompt.rs | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/default.toml b/default.toml index db9aa08..05f892e 100644 --- a/default.toml +++ b/default.toml @@ -163,9 +163,9 @@ on_completion.down = ["Down", "Tab"] # Keybindings for JSON viewer operations [keybinds.on_json_viewer] # Move up in JSON viewer -up = ["Up", "Ctrl+K"] +up = ["Up", "Ctrl+K", "ScrollUp"] # Move down in JSON viewer -down = ["Down", "Ctrl+J"] +down = ["Down", "Ctrl+J", "ScrollDown"] # Move to the top of JSON viewer move_to_head = ["Ctrl+L"] # Move to the bottom of JSON viewer diff --git a/src/prompt.rs b/src/prompt.rs index e92986c..911d67d 100644 --- a/src/prompt.rs +++ b/src/prompt.rs @@ -6,7 +6,10 @@ use promkit_widgets::{ core::{ crossterm::{ cursor, - event::{Event, EventStream}, + event::{ + DisableMouseCapture, EnableMouseCapture, Event, EventStream, MouseEvent, + MouseEventKind, + }, execute, style::{Color, ContentStyle}, terminal::{self, disable_raw_mode, enable_raw_mode}, @@ -194,6 +197,25 @@ pub async fn run( 'main: loop { tokio::select! { Some(Ok(event)) = stream.next() => { + // Note: `HashSet::contains` compares full mouse events (including `column`/`row`), + // so wheel events are normalized to `(0, 0)` to match configured `ScrollUp`/`ScrollDown` bindings. + let event = match event { + Event::Mouse(mouse) + if matches!( + mouse.kind, + MouseEventKind::ScrollUp | MouseEventKind::ScrollDown + ) => + { + Event::Mouse(MouseEvent { + kind: mouse.kind, + column: 0, + row: 0, + modifiers: mouse.modifiers, + }) + } + other => other, + }; + match event { Event::Resize(width, height) => { debounce_resize_tx.send((width, height)).await?; @@ -233,6 +255,11 @@ pub async fn run( if context_monitor.is_idle().await { focus = Focus::Processor; editor_focus_tx.send(false).await?; + execute!( + io::stdout(), + terminal::EnterAlternateScreen, + EnableMouseCapture, + )?; } else if !no_hint{ let size = terminal::size()?; shared_renderer.update([ @@ -255,6 +282,11 @@ pub async fn run( Focus::Processor => { focus = Focus::Editor; editor_focus_tx.send(true).await?; + execute!( + io::stdout(), + terminal::LeaveAlternateScreen, + DisableMouseCapture, + )?; }, } }, @@ -435,7 +467,7 @@ pub async fn run( editor_task.abort(); processor_task.abort(); - execute!(io::stdout(), cursor::Show)?; + execute!(io::stdout(), cursor::Show, DisableMouseCapture)?; disable_raw_mode()?; Ok(()) From 3076255fafc188df22e2eba300d63591651250bc Mon Sep 17 00:00:00 2001 From: ynqa Date: Thu, 26 Feb 2026 00:16:27 +0900 Subject: [PATCH 18/35] chore: use official v0.3.0 for promkit-widgets --- Cargo.lock | 5 ++++- Cargo.toml | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6b6da4..a123b6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1157,6 +1157,8 @@ dependencies = [ [[package]] name = "promkit-core" version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed89f85398b2590095afe8fb4852c177d09f568836c206a9bed823bf1e70a051" dependencies = [ "anyhow", "crossbeam-skiplist", @@ -1168,6 +1170,8 @@ dependencies = [ [[package]] name = "promkit-widgets" version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b665ae2f09cfe22290386f73e642922980cb6a5edd83188989dcb1e914dc4a7b" dependencies = [ "anyhow", "promkit-core", @@ -1175,7 +1179,6 @@ dependencies = [ "serde", "serde_json", "termcfg 0.2.0", - "tokio", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index adccc6b..1e65d47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,7 @@ duration-string = { version = "0.5.3", features = ["serde"] } derive_builder = "0.20.2" dirs = "6.0.0" futures = "0.3.32" -# promkit-widgets = { version = "0.2.0", features = ["jsonstream", "listbox", "text", "texteditor"] } -promkit-widgets = { path = "../promkit/promkit-widgets", version = "0.3.0", features = ["jsonstream", "listbox", "text", "texteditor"] } +promkit-widgets = { version = "0.3.0", features = ["jsonstream", "listbox", "serde", "text", "texteditor"], default-features = false } serde = "1.0.228" termcfg = { version = "0.1.0", features = ["crossterm_0_29_0"] } tokio = { version = "1.49.0", features = ["full"] } From 986c14b05d223e41835c5f62b32b51ef3ec353e1 Mon Sep 17 00:00:00 2001 From: ynqa Date: Thu, 26 Feb 2026 00:19:35 +0900 Subject: [PATCH 19/35] fix: use dependencies.promkit-widgets instead --- Cargo.lock | 32 ++++++++++++++++---------------- Cargo.toml | 6 +++++- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a123b6c..588c6d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -177,9 +177,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "num-traits", @@ -866,9 +866,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "14dc6f6450b3f6d4ed5b16327f38fed626d375a886159ca555bd7822c0c3a5a6" dependencies = [ "once_cell", "wasm-bindgen", @@ -898,9 +898,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litrs" @@ -1271,9 +1271,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", @@ -1644,9 +1644,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "60722a937f594b7fde9adb894d7c092fc1bb6612897c46368d18e7a20208eff2" dependencies = [ "cfg-if", "once_cell", @@ -1657,9 +1657,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "0fac8c6395094b6b91c4af293f4c79371c163f9a6f56184d2c9a85f5a95f3950" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1667,9 +1667,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "ab3fabce6159dc20728033842636887e4877688ae94382766e00b180abac9d60" dependencies = [ "bumpalo", "proc-macro2", @@ -1680,9 +1680,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "de0e091bdb824da87dc01d967388880d017a0a9bc4f3bdc0d86ee9f9336e3bb5" dependencies = [ "unicode-ident", ] diff --git a/Cargo.toml b/Cargo.toml index 1e65d47..93f185a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ duration-string = { version = "0.5.3", features = ["serde"] } derive_builder = "0.20.2" dirs = "6.0.0" futures = "0.3.32" -promkit-widgets = { version = "0.3.0", features = ["jsonstream", "listbox", "serde", "text", "texteditor"], default-features = false } serde = "1.0.228" termcfg = { version = "0.1.0", features = ["crossterm_0_29_0"] } tokio = { version = "1.49.0", features = ["full"] } @@ -29,6 +28,11 @@ jaq-core = "2.2.1" jaq-json = { version = "1.1.3", features = ["serde_json"] } jaq-std = "2.1.2" +[dependencies.promkit-widgets] +version = "0.3.0" +features = ["jsonstream", "listbox", "serde", "text", "texteditor"] +default-features = false + # The profile that 'cargo dist' will build with [profile.dist] inherits = "release" From 5cc580031b51d728ad068ff9673093947d34a399 Mon Sep 17 00:00:00 2001 From: ynqa Date: Thu, 26 Feb 2026 00:21:27 +0900 Subject: [PATCH 20/35] fix: use termcfg v0.2.0 --- Cargo.lock | 15 ++------------- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 588c6d8..460d149 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -858,7 +858,7 @@ dependencies = [ "jaq-std", "promkit-widgets", "serde", - "termcfg 0.1.0", + "termcfg", "tokio", "tokio-stream", "toml", @@ -1178,7 +1178,7 @@ dependencies = [ "rayon", "serde", "serde_json", - "termcfg 0.2.0", + "termcfg", ] [[package]] @@ -1435,17 +1435,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "termcfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c057661e9a795bc9cb18a7d2924b00d0aa3c5a9e61dfe7de32cea8a1d8fe7" -dependencies = [ - "crossterm", - "serde", - "thiserror 2.0.18", -] - [[package]] name = "termcfg" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 93f185a..eef4861 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ derive_builder = "0.20.2" dirs = "6.0.0" futures = "0.3.32" serde = "1.0.228" -termcfg = { version = "0.1.0", features = ["crossterm_0_29_0"] } +termcfg = { version = "0.2.0", features = ["crossterm_0_29_0"] } tokio = { version = "1.49.0", features = ["full"] } tokio-stream = "0.1.18" toml = "0.9.8" From 0908e1a6ae47074230375ba90c0f11e23845d438 Mon Sep 17 00:00:00 2001 From: ynqa Date: Thu, 26 Feb 2026 00:39:25 +0900 Subject: [PATCH 21/35] feat: --write-to-stdout to write result to stdout (UNIX only) --- Cargo.lock | 1 + Cargo.toml | 1 + README.md | 5 +++ src/main.rs | 102 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/prompt.rs | 16 ++++++-- 5 files changed, 118 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 460d149..2a80d21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -856,6 +856,7 @@ dependencies = [ "jaq-core", "jaq-json", "jaq-std", + "libc", "promkit-widgets", "serde", "termcfg", diff --git a/Cargo.toml b/Cargo.toml index eef4861..ab0accf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ duration-string = { version = "0.5.3", features = ["serde"] } derive_builder = "0.20.2" dirs = "6.0.0" futures = "0.3.32" +libc = "0.2.177" serde = "1.0.228" termcfg = { version = "0.2.0", features = ["crossterm_0_29_0"] } tokio = { version = "1.49.0", features = ["full"] } diff --git a/README.md b/README.md index bea138c..5f461be 100644 --- a/README.md +++ b/README.md @@ -111,8 +111,12 @@ cargo install jnv ```bash cat data.json | jnv + # or jnv data.json + +# or write current result to stdout on exit (UNIX only) +cat data.json | jnv --write-to-stdout | some-command ``` ## Keymap @@ -180,6 +184,7 @@ Arguments: Options: -c, --config Path to the configuration file. --default-filter Default jq filter to apply to the input data + --write-to-stdout Write the current JSON result to stdout when exiting -h, --help Print help (see more with '--help') -V, --version Print version ``` diff --git a/src/main.rs b/src/main.rs index 6d900b4..1801a56 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ +#[cfg(unix)] +use std::os::fd::{AsRawFd, FromRawFd, OwnedFd}; use std::{ fs::File, - io::{self, Read, Write}, + io::{self, IsTerminal, Read, Write}, path::PathBuf, }; @@ -70,6 +72,12 @@ pub struct Args { " )] default_filter: Option, + + #[arg( + long = "write-to-stdout", + help = "Write the current JSON result to stdout when exiting" + )] + write_to_stdout: bool, } /// Parses the input based on the provided arguments. @@ -141,6 +149,80 @@ fn determine_config_file(config_path: Option) -> anyhow::Result, +} + +impl StdoutRedirect { + fn for_tui(write_to_stdout: bool) -> anyhow::Result { + if !write_to_stdout || io::stdout().is_terminal() { + return Ok(Self { + #[cfg(unix)] + saved_stdout: None, + }); + } + + #[cfg(unix)] + { + let tty = File::options() + .read(true) + .write(true) + .open("/dev/tty") + .map_err(|e| anyhow!("Failed to open /dev/tty for TUI rendering: {e}"))?; + + let saved_fd = unsafe { libc::dup(libc::STDOUT_FILENO) }; + if saved_fd < 0 { + return Err(anyhow!( + "Failed to duplicate stdout: {}", + io::Error::last_os_error() + )); + } + + let redirected = unsafe { libc::dup2(tty.as_raw_fd(), libc::STDOUT_FILENO) }; + if redirected < 0 { + let _ = unsafe { libc::close(saved_fd) }; + return Err(anyhow!( + "Failed to redirect stdout to /dev/tty: {}", + io::Error::last_os_error() + )); + } + + Ok(Self { + saved_stdout: Some(unsafe { OwnedFd::from_raw_fd(saved_fd) }), + }) + } + + #[cfg(not(unix))] + { + Err(anyhow!( + "`--write-to-stdout` with piped stdout is not supported on this platform" + )) + } + } + + fn restore(&mut self) -> anyhow::Result<()> { + #[cfg(unix)] + if let Some(saved_stdout) = self.saved_stdout.take() { + let restored = unsafe { libc::dup2(saved_stdout.as_raw_fd(), libc::STDOUT_FILENO) }; + if restored < 0 { + return Err(anyhow!( + "Failed to restore stdout: {}", + io::Error::last_os_error() + )); + } + } + + Ok(()) + } +} + +impl Drop for StdoutRedirect { + fn drop(&mut self) { + let _ = self.restore(); + } +} + #[tokio::main] async fn main() -> anyhow::Result<()> { let args = Args::parse(); @@ -194,8 +276,10 @@ async fn main() -> anyhow::Result<()> { config.keybinds.on_editor.clone(), ); + let mut stdout_redirect = StdoutRedirect::for_tui(args.write_to_stdout)?; + // TODO: put all logics here. - prompt::run( + let maybe_output = prompt::run( item, config.reactivity_control, provider, @@ -203,8 +287,20 @@ async fn main() -> anyhow::Result<()> { loading_suggestions_task, config.no_hint, config.keybinds, + args.write_to_stdout, ) - .await?; + .await; + + stdout_redirect.restore()?; + let maybe_output = maybe_output?; + + if let Some(output) = maybe_output { + let mut stdout = io::stdout(); + stdout.write_all(output.as_bytes())?; + if !output.ends_with('\n') { + stdout.write_all(b"\n")?; + } + } Ok(()) } diff --git a/src/prompt.rs b/src/prompt.rs index 911d67d..2f55685 100644 --- a/src/prompt.rs +++ b/src/prompt.rs @@ -123,7 +123,8 @@ pub async fn run( loading_suggestions_task: JoinHandle>, no_hint: bool, keybinds: Keybinds, -) -> anyhow::Result<()> { + write_to_stdout: bool, +) -> anyhow::Result> { enable_raw_mode()?; execute!(io::stdout(), cursor::Hide)?; @@ -385,11 +386,11 @@ pub async fn run( }) }; + let shared_visualizer = Arc::new(Mutex::new(initializing.await?)); let processor_task: JoinHandle> = { let shared_renderer = shared_renderer.clone(); let shared_editor = shared_editor.clone(); - let visualizer = initializing.await?; - let shared_visualizer = Arc::new(Mutex::new(visualizer)); + let shared_visualizer = shared_visualizer.clone(); tokio::spawn(async move { loop { tokio::select! { @@ -460,6 +461,13 @@ pub async fn run( main_task.await??; + let output = if write_to_stdout { + let visualizer = shared_visualizer.lock().await; + Some(visualizer.content_to_copy().await) + } else { + None + }; + loading_suggestions_task.abort(); spinning.abort(); query_debouncer.abort(); @@ -470,5 +478,5 @@ pub async fn run( execute!(io::stdout(), cursor::Show, DisableMouseCapture)?; disable_raw_mode()?; - Ok(()) + Ok(output) } From d8686c3334fb5b6f21ce8b02c87914d5480bf6b1 Mon Sep 17 00:00:00 2001 From: ynqa Date: Thu, 26 Feb 2026 01:54:26 +0900 Subject: [PATCH 22/35] chore: use rustix instead of libc --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/main.rs | 38 ++++++++++++-------------------------- 3 files changed, 14 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a80d21..95f0730 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -856,8 +856,8 @@ dependencies = [ "jaq-core", "jaq-json", "jaq-std", - "libc", "promkit-widgets", + "rustix", "serde", "termcfg", "tokio", diff --git a/Cargo.toml b/Cargo.toml index ab0accf..5d5743e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ duration-string = { version = "0.5.3", features = ["serde"] } derive_builder = "0.20.2" dirs = "6.0.0" futures = "0.3.32" -libc = "0.2.177" +rustix = { version = "1.1.4", features = ["stdio"] } serde = "1.0.228" termcfg = { version = "0.2.0", features = ["crossterm_0_29_0"] } tokio = { version = "1.49.0", features = ["full"] } diff --git a/src/main.rs b/src/main.rs index 1801a56..d2c56dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,3 @@ -#[cfg(unix)] -use std::os::fd::{AsRawFd, FromRawFd, OwnedFd}; use std::{ fs::File, io::{self, IsTerminal, Read, Write}, @@ -14,6 +12,14 @@ use promkit_widgets::{ text_editor::{self, TextEditor}, }; +#[cfg(unix)] +use std::os::fd::OwnedFd; +#[cfg(unix)] +use rustix::{ + io::dup, + stdio::{dup2_stdout, stdout}, +}; + mod editor; use editor::Editor; mod config; @@ -171,25 +177,11 @@ impl StdoutRedirect { .open("/dev/tty") .map_err(|e| anyhow!("Failed to open /dev/tty for TUI rendering: {e}"))?; - let saved_fd = unsafe { libc::dup(libc::STDOUT_FILENO) }; - if saved_fd < 0 { - return Err(anyhow!( - "Failed to duplicate stdout: {}", - io::Error::last_os_error() - )); - } - - let redirected = unsafe { libc::dup2(tty.as_raw_fd(), libc::STDOUT_FILENO) }; - if redirected < 0 { - let _ = unsafe { libc::close(saved_fd) }; - return Err(anyhow!( - "Failed to redirect stdout to /dev/tty: {}", - io::Error::last_os_error() - )); - } + let saved_fd = dup(stdout()).map_err(|e| anyhow!("Failed to duplicate stdout: {e}"))?; + dup2_stdout(&tty).map_err(|e| anyhow!("Failed to redirect stdout to /dev/tty: {e}"))?; Ok(Self { - saved_stdout: Some(unsafe { OwnedFd::from_raw_fd(saved_fd) }), + saved_stdout: Some(saved_fd), }) } @@ -204,13 +196,7 @@ impl StdoutRedirect { fn restore(&mut self) -> anyhow::Result<()> { #[cfg(unix)] if let Some(saved_stdout) = self.saved_stdout.take() { - let restored = unsafe { libc::dup2(saved_stdout.as_raw_fd(), libc::STDOUT_FILENO) }; - if restored < 0 { - return Err(anyhow!( - "Failed to restore stdout: {}", - io::Error::last_os_error() - )); - } + dup2_stdout(&saved_stdout).map_err(|e| anyhow!("Failed to restore stdout: {e}"))?; } Ok(()) From aba97927733fd2cb12284147c31028c9ae59bddf Mon Sep 17 00:00:00 2001 From: ynqa Date: Thu, 26 Feb 2026 02:10:21 +0900 Subject: [PATCH 23/35] chore: move StdoutRedirect to stdout_redirect.rs --- src/main.rs | 68 +++-------------------------------- src/stdout_redirect.rs | 82 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 64 deletions(-) create mode 100644 src/stdout_redirect.rs diff --git a/src/main.rs b/src/main.rs index d2c56dd..393d505 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use std::{ fs::File, - io::{self, IsTerminal, Read, Write}, + io::{self, Read, Write}, path::PathBuf, }; @@ -12,19 +12,13 @@ use promkit_widgets::{ text_editor::{self, TextEditor}, }; -#[cfg(unix)] -use std::os::fd::OwnedFd; -#[cfg(unix)] -use rustix::{ - io::dup, - stdio::{dup2_stdout, stdout}, -}; - mod editor; use editor::Editor; mod config; mod json; use json::JsonStreamProvider; +mod stdout_redirect; +use stdout_redirect::StdoutRedirect; mod processor; use processor::{ init::ViewInitializer, monitor::ContextMonitor, spinner::SpinnerSpawner, Context, Processor, @@ -155,60 +149,6 @@ fn determine_config_file(config_path: Option) -> anyhow::Result, -} - -impl StdoutRedirect { - fn for_tui(write_to_stdout: bool) -> anyhow::Result { - if !write_to_stdout || io::stdout().is_terminal() { - return Ok(Self { - #[cfg(unix)] - saved_stdout: None, - }); - } - - #[cfg(unix)] - { - let tty = File::options() - .read(true) - .write(true) - .open("/dev/tty") - .map_err(|e| anyhow!("Failed to open /dev/tty for TUI rendering: {e}"))?; - - let saved_fd = dup(stdout()).map_err(|e| anyhow!("Failed to duplicate stdout: {e}"))?; - dup2_stdout(&tty).map_err(|e| anyhow!("Failed to redirect stdout to /dev/tty: {e}"))?; - - Ok(Self { - saved_stdout: Some(saved_fd), - }) - } - - #[cfg(not(unix))] - { - Err(anyhow!( - "`--write-to-stdout` with piped stdout is not supported on this platform" - )) - } - } - - fn restore(&mut self) -> anyhow::Result<()> { - #[cfg(unix)] - if let Some(saved_stdout) = self.saved_stdout.take() { - dup2_stdout(&saved_stdout).map_err(|e| anyhow!("Failed to restore stdout: {e}"))?; - } - - Ok(()) - } -} - -impl Drop for StdoutRedirect { - fn drop(&mut self) { - let _ = self.restore(); - } -} - #[tokio::main] async fn main() -> anyhow::Result<()> { let args = Args::parse(); @@ -262,7 +202,7 @@ async fn main() -> anyhow::Result<()> { config.keybinds.on_editor.clone(), ); - let mut stdout_redirect = StdoutRedirect::for_tui(args.write_to_stdout)?; + let mut stdout_redirect = StdoutRedirect::try_new_for_tui(args.write_to_stdout)?; // TODO: put all logics here. let maybe_output = prompt::run( diff --git a/src/stdout_redirect.rs b/src/stdout_redirect.rs new file mode 100644 index 0000000..a5d9b4f --- /dev/null +++ b/src/stdout_redirect.rs @@ -0,0 +1,82 @@ +use std::{ + fs::File, + io::{self, IsTerminal}, +}; + +use anyhow::anyhow; + +#[cfg(unix)] +use rustix::{ + io::dup, + stdio::{dup2_stdout, stdout}, +}; +#[cfg(unix)] +use std::os::fd::OwnedFd; + +/// Redirects `stdout` to the controlling TTY while the TUI is running. +/// +/// This is needed when `--write-to-stdout` is used with piped stdout, e.g. +/// `cat data.json | jnv --write-to-stdout | pbcopy`. +/// During interactive rendering, cursor controls and screen output must go to +/// a terminal, not to the downstream pipe. +/// +/// Unix flow: +/// 1. Save current `fd=1` with `dup` (`saved_stdout`). +/// 2. Replace `fd=1` with `/dev/tty` via `dup2_stdout` for TUI rendering. +/// 3. Restore the original `fd=1` on exit. +/// +/// After restore, writing to `io::stdout()` again goes to the original pipe +/// (for example `pbcopy`) so the final JSON can be emitted there. +pub(crate) struct StdoutRedirect { + #[cfg(unix)] + saved_stdout: Option, +} + +impl StdoutRedirect { + pub(crate) fn try_new_for_tui(write_to_stdout: bool) -> anyhow::Result { + if !write_to_stdout || io::stdout().is_terminal() { + return Ok(Self { + #[cfg(unix)] + saved_stdout: None, + }); + } + + #[cfg(unix)] + { + let tty = File::options() + .read(true) + .write(true) + .open("/dev/tty") + .map_err(|e| anyhow!("Failed to open /dev/tty for TUI rendering: {e}"))?; + + let saved_fd = dup(stdout()).map_err(|e| anyhow!("Failed to duplicate stdout: {e}"))?; + dup2_stdout(&tty).map_err(|e| anyhow!("Failed to redirect stdout to /dev/tty: {e}"))?; + + Ok(Self { + saved_stdout: Some(saved_fd), + }) + } + + #[cfg(not(unix))] + { + Err(anyhow!( + "`--write-to-stdout` with piped stdout is not supported on this platform" + )) + } + } + + pub(crate) fn restore(&mut self) -> anyhow::Result<()> { + #[cfg(unix)] + if let Some(saved_stdout) = self.saved_stdout.take() { + dup2_stdout(&saved_stdout).map_err(|e| anyhow!("Failed to restore stdout: {e}"))?; + } + + Ok(()) + } +} + +impl Drop for StdoutRedirect { + fn drop(&mut self) { + let _ = self.restore(); + } +} From 074eccfbd0fd592268d42c57bda11f4b11a330c3 Mon Sep 17 00:00:00 2001 From: ynqa Date: Thu, 26 Feb 2026 02:21:40 +0900 Subject: [PATCH 24/35] docs: enable to write output to file --- README.md | 2 ++ src/stdout_redirect.rs | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5f461be..6962146 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,8 @@ jnv data.json # or write current result to stdout on exit (UNIX only) cat data.json | jnv --write-to-stdout | some-command +# and also output to file +cat data.json | jnv -- --write-to-stdout > result.json ``` ## Keymap diff --git a/src/stdout_redirect.rs b/src/stdout_redirect.rs index a5d9b4f..21575ba 100644 --- a/src/stdout_redirect.rs +++ b/src/stdout_redirect.rs @@ -27,13 +27,13 @@ use std::os::fd::OwnedFd; /// /// After restore, writing to `io::stdout()` again goes to the original pipe /// (for example `pbcopy`) so the final JSON can be emitted there. -pub(crate) struct StdoutRedirect { +pub struct StdoutRedirect { #[cfg(unix)] saved_stdout: Option, } impl StdoutRedirect { - pub(crate) fn try_new_for_tui(write_to_stdout: bool) -> anyhow::Result { + pub fn try_new_for_tui(write_to_stdout: bool) -> anyhow::Result { if !write_to_stdout || io::stdout().is_terminal() { return Ok(Self { #[cfg(unix)] @@ -65,7 +65,7 @@ impl StdoutRedirect { } } - pub(crate) fn restore(&mut self) -> anyhow::Result<()> { + pub fn restore(&mut self) -> anyhow::Result<()> { #[cfg(unix)] if let Some(saved_stdout) = self.saved_stdout.take() { dup2_stdout(&saved_stdout).map_err(|e| anyhow!("Failed to restore stdout: {e}"))?; From 522d04770e8285e6c59d945c2492898bcc10cd09 Mon Sep 17 00:00:00 2001 From: ynqa Date: Thu, 26 Feb 2026 02:26:15 +0900 Subject: [PATCH 25/35] docs: what is stdout --- src/stdout_redirect.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/stdout_redirect.rs b/src/stdout_redirect.rs index 21575ba..dc0e67d 100644 --- a/src/stdout_redirect.rs +++ b/src/stdout_redirect.rs @@ -27,6 +27,13 @@ use std::os::fd::OwnedFd; /// /// After restore, writing to `io::stdout()` again goes to the original pipe /// (for example `pbcopy`) so the final JSON can be emitted there. +/// +/// Note: +/// `stdout` is file descriptor 1 (FD 1), not the screen itself. +/// Its destination is chosen by the shell when the process starts: +/// terminal (`cmd`), file (`cmd > out.txt`), or pipe (`cmd | next`). +/// Therefore, we cannot just write to `stdout` for TUI rendering when it's piped. +/// Instead, we must write directly to the terminal device (`/dev/tty` on Unix). pub struct StdoutRedirect { #[cfg(unix)] saved_stdout: Option, From 4f884f5304376b915c363b3d7ef811f3d9037ec5 Mon Sep 17 00:00:00 2001 From: ynqa Date: Sun, 1 Mar 2026 17:50:04 +0900 Subject: [PATCH 26/35] docs: configuration --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bea138c..66467c5 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ the default configuration file location for each platform is as follows: If the configuration file does not exist, it will be automatically created on first run. -### Configuration Options +### Configuration > [!IMPORTANT] > The syntax in TOML configurations From 1d2b1ece86bb7c963d5be6027008a6ee0488404d Mon Sep 17 00:00:00 2001 From: ynqa Date: Mon, 2 Mar 2026 00:15:24 +0900 Subject: [PATCH 27/35] chore: use promkit-widgets v0.3.1 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 460d149..a5fd65f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1169,9 +1169,9 @@ dependencies = [ [[package]] name = "promkit-widgets" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b665ae2f09cfe22290386f73e642922980cb6a5edd83188989dcb1e914dc4a7b" +checksum = "0fe0c1c9d4a39811769fc7787df265e5705d2749fd6d768e1166b6dbced2927c" dependencies = [ "anyhow", "promkit-core", diff --git a/Cargo.toml b/Cargo.toml index eef4861..6f99376 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ jaq-json = { version = "1.1.3", features = ["serde_json"] } jaq-std = "2.1.2" [dependencies.promkit-widgets] -version = "0.3.0" +version = "0.3.1" features = ["jsonstream", "listbox", "serde", "text", "texteditor"] default-features = false From 7e78520a781d52cd78471eb3e442495e41dab8ac Mon Sep 17 00:00:00 2001 From: ynqa Date: Tue, 17 Mar 2026 22:45:03 +0900 Subject: [PATCH 28/35] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6962146..e6a0226 100644 --- a/README.md +++ b/README.md @@ -396,9 +396,9 @@ on_completion.down = ["Down", "Tab"] # Keybindings for JSON viewer operations [keybinds.on_json_viewer] # Move up in JSON viewer -up = ["Up", "Ctrl+K"] +up = ["Up", "Ctrl+K", "ScrollUp"] # Move down in JSON viewer -down = ["Down", "Ctrl+J"] +down = ["Down", "Ctrl+J", "ScrollDown"] # Move to the top of JSON viewer move_to_head = ["Ctrl+L"] # Move to the bottom of JSON viewer From 995f797e46c0072d9cb50bda67d22b1c5f4b87de Mon Sep 17 00:00:00 2001 From: ynqa Date: Mon, 16 Mar 2026 20:04:57 +0900 Subject: [PATCH 29/35] chore: bump up widget version to v0.5.0 (local) --- Cargo.lock | 175 +++++++++++++++++++-------------------- Cargo.toml | 9 +- src/editor.rs | 77 ++++++++--------- src/json.rs | 64 ++++++-------- src/processor.rs | 25 +++--- src/processor/spinner.rs | 4 +- src/prompt.rs | 102 ++++++++--------------- src/search.rs | 6 +- 8 files changed, 200 insertions(+), 262 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5fd6a7a..405bc1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -43,15 +43,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -161,9 +161,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" -version = "1.2.56" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "shlex", @@ -188,9 +188,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -198,9 +198,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -210,9 +210,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", @@ -222,9 +222,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "clipboard-win" @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "convert_case" @@ -447,9 +447,9 @@ dependencies = [ [[package]] name = "dispatch2" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ "bitflags", "objc2", @@ -766,9 +766,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "image" -version = "0.25.9" +version = "0.25.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" dependencies = [ "bytemuck", "byteorder-lite", @@ -867,9 +867,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.90" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dc6f6450b3f6d4ed5b16327f38fed626d375a886159ca555bd7822c0c3a5a6" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -877,9 +877,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libm" @@ -889,11 +889,10 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ - "bitflags", "libc", ] @@ -954,9 +953,9 @@ dependencies = [ [[package]] name = "moxcms" -version = "0.7.11" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" dependencies = [ "num-traits", "pxfm", @@ -982,9 +981,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ "objc2-encode", ] @@ -1055,9 +1054,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -1123,9 +1122,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" @@ -1157,9 +1156,7 @@ dependencies = [ [[package]] name = "promkit-core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed89f85398b2590095afe8fb4852c177d09f568836c206a9bed823bf1e70a051" +version = "0.3.0" dependencies = [ "anyhow", "crossbeam-skiplist", @@ -1170,9 +1167,7 @@ dependencies = [ [[package]] name = "promkit-widgets" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe0c1c9d4a39811769fc7787df265e5705d2749fd6d768e1166b6dbced2927c" +version = "0.5.0" dependencies = [ "anyhow", "promkit-core", @@ -1180,16 +1175,14 @@ dependencies = [ "serde", "serde_json", "termcfg", + "tokio", ] [[package]] name = "pxfm" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" -dependencies = [ - "num-traits", -] +checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" [[package]] name = "quick-error" @@ -1199,18 +1192,18 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.38.4" +version = "0.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -1411,12 +1404,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1489,9 +1482,9 @@ dependencies = [ [[package]] name = "tiff" -version = "0.10.3" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" dependencies = [ "fax", "flate2", @@ -1503,9 +1496,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -1520,9 +1513,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", @@ -1634,9 +1627,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60722a937f594b7fde9adb894d7c092fc1bb6612897c46368d18e7a20208eff2" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -1647,9 +1640,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac8c6395094b6b91c4af293f4c79371c163f9a6f56184d2c9a85f5a95f3950" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1657,9 +1650,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3fabce6159dc20728033842636887e4877688ae94382766e00b180abac9d60" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", @@ -1670,18 +1663,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0e091bdb824da87dc01d967388880d017a0a9bc4f3bdc0d86ee9f9336e3bb5" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] [[package]] name = "wayland-backend" -version = "0.3.12" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9" +checksum = "aa75f400b7f719bcd68b3f47cd939ba654cedeef690f486db71331eec4c6a406" dependencies = [ "cc", "downcast-rs", @@ -1692,9 +1685,9 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.12" +version = "0.31.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e6faa537fbb6c186cb9f1d41f2f811a4120d1b57ec61f50da451a0c5122bec" +checksum = "ab51d9f7c071abeee76007e2b742499e535148035bb835f97aaed1338cf516c3" dependencies = [ "bitflags", "rustix", @@ -1704,9 +1697,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.10" +version = "0.32.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baeda9ffbcfc8cd6ddaade385eaf2393bd2115a69523c735f12242353c3df4f3" +checksum = "b23b5df31ceff1328f06ac607591d5ba360cf58f90c8fad4ac8d3a55a3c4aec7" dependencies = [ "bitflags", "wayland-backend", @@ -1716,9 +1709,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9597cdf02cf0c34cd5823786dce6b5ae8598f05c2daf5621b6e178d4f7345f3" +checksum = "78248e4cc0eff8163370ba5c158630dcae1f3497a586b826eca2ef5f348d6235" dependencies = [ "bitflags", "wayland-backend", @@ -1729,9 +1722,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.8" +version = "0.31.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3" +checksum = "c86287151a309799b821ca709b7345a048a2956af05957c89cb824ab919fa4e3" dependencies = [ "proc-macro2", "quick-xml", @@ -1740,9 +1733,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.8" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd" +checksum = "374f6b70e8e0d6bf9461a32988fd553b59ff630964924dad6e4a4eb6bd538d17" dependencies = [ "pkg-config", ] @@ -1919,9 +1912,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] name = "wl-clipboard-rs" @@ -1960,18 +1953,18 @@ checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", @@ -1986,15 +1979,15 @@ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zune-core" -version = "0.4.12" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" [[package]] name = "zune-jpeg" -version = "0.4.21" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +checksum = "ec5f41c76397b7da451efd19915684f727d7e1d516384ca6bd0ec43ec94de23c" dependencies = [ "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index 8cd144f..3365fd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ readme = "README.md" anyhow = "1.0.102" arboard = { version = "3.6.1", features = ["wayland-data-control"] } async-trait = "0.1.89" -clap = { version = "4.5.60", features = ["derive"] } +clap = { version = "4.6.0", features = ["derive"] } duration-string = { version = "0.5.3", features = ["serde"] } derive_builder = "0.20.2" dirs = "6.0.0" @@ -20,7 +20,7 @@ futures = "0.3.32" rustix = { version = "1.1.4", features = ["stdio"] } serde = "1.0.228" termcfg = { version = "0.2.0", features = ["crossterm_0_29_0"] } -tokio = { version = "1.49.0", features = ["full"] } +tokio = { version = "1.50.0", features = ["full"] } tokio-stream = "0.1.18" toml = "0.9.8" @@ -30,8 +30,9 @@ jaq-json = { version = "1.1.3", features = ["serde_json"] } jaq-std = "2.1.2" [dependencies.promkit-widgets] -version = "0.3.1" -features = ["jsonstream", "listbox", "serde", "text", "texteditor"] +version = "0.5.0" +path = "../promkit/promkit-widgets" +features = ["jsonstream", "listbox", "serde", "spinner", "status", "texteditor"] default-features = false # The profile that 'cargo dist' will build with diff --git a/src/editor.rs b/src/editor.rs index 792f3a7..07c1c12 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -2,13 +2,11 @@ use std::{future::Future, pin::Pin}; use promkit_widgets::{ core::{ - crossterm::{ - event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers}, - style::{Color, ContentStyle}, - }, - Pane, PaneFactory, + Widget, + crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers}, + grapheme::StyledGraphemes, }, - text::{self, Text}, + status::{self, Severity}, text_editor, }; @@ -19,7 +17,7 @@ pub struct Editor { state: text_editor::State, focus_config: text_editor::Config, defocus_config: text_editor::Config, - guide: text::State, + guide: status::State, searcher: IncrementalSearcher, editor_keybinds: EditorKeybinds, } @@ -37,7 +35,7 @@ impl Editor { state, focus_config, defocus_config, - guide: text::State::default(), + guide: status::State::default(), searcher, editor_keybinds, } @@ -53,23 +51,23 @@ impl Editor { self.searcher.leave_search(); self.handler = BOXED_EDITOR_HANDLER; - self.guide.text = Default::default(); + self.guide = status::State::default(); } pub fn text(&self) -> String { self.state.texteditor.text_without_cursor().to_string() } - pub fn create_editor_pane(&self, width: u16, height: u16) -> Pane { - self.state.create_pane(width, height) + pub fn create_editor_pane(&self, width: u16, height: u16) -> StyledGraphemes { + self.state.create_graphemes(width, height) } - pub fn create_searcher_pane(&self, width: u16, height: u16) -> Pane { + pub fn create_searcher_pane(&self, width: u16, height: u16) -> StyledGraphemes { self.searcher.create_pane(width, height) } - pub fn create_guide_pane(&self, width: u16, height: u16) -> Pane { - self.guide.create_pane(width, height) + pub fn create_guide_pane(&self, width: u16, height: u16) -> StyledGraphemes { + self.guide.create_graphemes(width, height) } pub async fn operate(&mut self, event: &Event) -> anyhow::Result<()> { @@ -92,7 +90,7 @@ const BOXED_SEARCHER_HANDLER: Handler = }; pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Result<()> { - editor.guide.text = Default::default(); + editor.guide = status::State::default(); match event { key if editor.editor_keybinds.completion.contains(key) => { @@ -101,42 +99,37 @@ pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Resul Ok(result) => match result.head_item { Some(head) => { if result.load_state.loaded { - editor.guide.text = Text::from(format!( - "Loaded all ({}) suggestions", - result.load_state.loaded_item_len - )); - editor.guide.config.style = Some(ContentStyle { - foreground_color: Some(Color::Green), - ..Default::default() - }); + editor.guide = status::State::new( + format!( + "Loaded all ({}) suggestions", + result.load_state.loaded_item_len + ), + Severity::Success, + ); } else { - editor.guide.text = Text::from(format!( - "Loaded partially ({}) suggestions", - result.load_state.loaded_item_len - )); - editor.guide.config.style = Some(ContentStyle { - foreground_color: Some(Color::Green), - ..Default::default() - }); + editor.guide = status::State::new( + format!( + "Loaded partially ({}) suggestions", + result.load_state.loaded_item_len + ), + Severity::Success, + ); } editor.state.texteditor.replace(&head); editor.handler = BOXED_SEARCHER_HANDLER; } None => { - editor.guide.text = - Text::from(format!("No suggestion found for '{prefix}'")); - editor.guide.config.style = Some(ContentStyle { - foreground_color: Some(Color::Yellow), - ..Default::default() - }); + editor.guide = status::State::new( + format!("No suggestion found for '{prefix}'"), + Severity::Warning, + ); } }, Err(e) => { - editor.guide.text = Text::from(format!("Failed to lookup suggestions: {e}")); - editor.guide.config.style = Some(ContentStyle { - foreground_color: Some(Color::Yellow), - ..Default::default() - }); + editor.guide = status::State::new( + format!("Failed to lookup suggestions: {e}"), + Severity::Warning, + ); } } } diff --git a/src/json.rs b/src/json.rs index 1bd93c2..d4353fd 100644 --- a/src/json.rs +++ b/src/json.rs @@ -5,17 +5,10 @@ use jaq_core::{ use jaq_json::Val; use promkit_widgets::{ - core::{ - crossterm::{ - event::Event, - style::{Attribute, Attributes, Color, ContentStyle}, - }, - pane::Pane, - PaneFactory, - }, + core::{Widget, crossterm::event::Event, grapheme::StyledGraphemes}, jsonstream::{self, config::Config as JsonStreamConfig, jsonz, JsonStream}, serde_json::{self, Deserializer, Value}, - text::{self, Text}, + status::{self, Severity}, }; use crate::{ @@ -93,63 +86,54 @@ impl Visualizer for Json { self.state.config.format_raw_json(self.state.stream.rows()) } - async fn create_init_pane(&mut self, area: (u16, u16)) -> Pane { - self.state.create_pane(area.0, area.1) + async fn create_init_pane(&mut self, area: (u16, u16)) -> StyledGraphemes { + self.state.create_graphemes(area.0, area.1) } - async fn create_pane_from_event(&mut self, area: (u16, u16), event: &Event) -> Pane { + async fn create_pane_from_event( + &mut self, + area: (u16, u16), + event: &Event, + ) -> StyledGraphemes { self.operate(event); - self.state.create_pane(area.0, area.1) + self.state.create_graphemes(area.0, area.1) } async fn create_panes_from_query( &mut self, area: (u16, u16), input: String, - ) -> (Option, Option) { + ) -> (Option, Option) { match run_jaq(&input, self.json) { Ok(ret) => { let mut guide = None; if ret.iter().all(|val| *val == Value::Null) { - guide = Some(text::State { - text: Text::from(format!("jq returned 'null', which may indicate a typo or incorrect filter: `{input}`")), - config: text::Config { - style: Some(ContentStyle { - foreground_color: Some(Color::Yellow), - attributes: Attributes::from(Attribute::Bold), - ..Default::default() - }), - ..Default::default() - }, - ..Default::default() - }.create_pane(area.0, area.1)); + guide = Some( + status::State::new( + format!( + "jq returned 'null', which may indicate a typo or incorrect filter: `{input}`" + ), + Severity::Warning, + ) + .create_graphemes(area.0, area.1), + ); self.state.stream = JsonStream::new(self.json.iter()); } else { self.state.stream = JsonStream::new(ret.iter()); } - (guide, Some(self.state.create_pane(area.0, area.1))) + (guide, Some(self.state.create_graphemes(area.0, area.1))) } Err(e) => { self.state.stream = JsonStream::new(self.json.iter()); ( Some( - text::State { - text: Text::from(format!("jq failed: `{e}`")), - config: text::Config { - style: Some(ContentStyle { - foreground_color: Some(Color::Red), - attributes: Attributes::from(Attribute::Bold), - ..Default::default() - }), - ..Default::default() - }, - } - .create_pane(area.0, area.1), + status::State::new(format!("jq failed: `{e}`"), Severity::Error) + .create_graphemes(area.0, area.1), ), - Some(self.state.create_pane(area.0, area.1)), + Some(self.state.create_graphemes(area.0, area.1)), ) } } diff --git a/src/processor.rs b/src/processor.rs index e70db38..babc6e6 100644 --- a/src/processor.rs +++ b/src/processor.rs @@ -2,9 +2,7 @@ use std::sync::Arc; use async_trait::async_trait; use promkit_widgets::core::{ - crossterm::event::Event, - pane::{Pane, EMPTY_PANE}, - render::SharedRenderer, + crossterm::event::Event, grapheme::StyledGraphemes, render::SharedRenderer, }; use tokio::{sync::Mutex, task::JoinHandle}; @@ -15,6 +13,10 @@ use crate::prompt::Index; pub mod monitor; pub mod spinner; +fn empty_pane() -> StyledGraphemes { + StyledGraphemes::default() +} + #[derive(PartialEq)] enum State { Idle, @@ -25,13 +27,17 @@ enum State { #[async_trait] pub trait Visualizer: Send + Sync + 'static { async fn content_to_copy(&self) -> String; - async fn create_init_pane(&mut self, area: (u16, u16)) -> Pane; - async fn create_pane_from_event(&mut self, area: (u16, u16), event: &Event) -> Pane; + async fn create_init_pane(&mut self, area: (u16, u16)) -> StyledGraphemes; + async fn create_pane_from_event( + &mut self, + area: (u16, u16), + event: &Event, + ) -> StyledGraphemes; async fn create_panes_from_query( &mut self, area: (u16, u16), query: String, - ) -> (Option, Option); + ) -> (Option, Option); } pub struct Context { @@ -90,11 +96,8 @@ impl Processor { // TODO: error handling let _ = shared_renderer .update([ - (Index::Guide, maybe_guide.unwrap_or(EMPTY_PANE.to_owned())), - ( - Index::Processor, - maybe_resp.unwrap_or(EMPTY_PANE.to_owned()), - ), + (Index::Guide, maybe_guide.unwrap_or_else(empty_pane)), + (Index::Processor, maybe_resp.unwrap_or_else(empty_pane)), ]) .render() .await; diff --git a/src/processor/spinner.rs b/src/processor/spinner.rs index b0e36d5..2ed8f80 100644 --- a/src/processor/spinner.rs +++ b/src/processor/spinner.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use promkit_widgets::core::{grapheme::StyledGraphemes, render::SharedRenderer, Pane}; +use promkit_widgets::core::{grapheme::StyledGraphemes, render::SharedRenderer}; use tokio::{sync::Mutex, task::JoinHandle, time::Duration}; use crate::prompt::Index; @@ -39,7 +39,7 @@ impl SpinnerSpawner { frame_index = (frame_index + 1) % LOADING_FRAMES.len(); - let pane = Pane::new(vec![StyledGraphemes::from(LOADING_FRAMES[frame_index])], 0); + let pane = StyledGraphemes::from(LOADING_FRAMES[frame_index]); { // TODO: error handling let _ = shared_renderer diff --git a/src/prompt.rs b/src/prompt.rs index 2f55685..b616e59 100644 --- a/src/prompt.rs +++ b/src/prompt.rs @@ -4,6 +4,7 @@ use arboard::Clipboard; use futures::StreamExt; use promkit_widgets::{ core::{ + Widget, crossterm::{ cursor, event::{ @@ -11,14 +12,12 @@ use promkit_widgets::{ MouseEventKind, }, execute, - style::{Color, ContentStyle}, terminal::{self, disable_raw_mode, enable_raw_mode}, }, - pane::EMPTY_PANE, + grapheme::StyledGraphemes, render::{Renderer, SharedRenderer}, - PaneFactory, }, - text::{self, Text}, + status::{self, Severity}, }; use tokio::{ sync::{mpsc, Mutex, RwLock}, @@ -58,49 +57,25 @@ fn spawn_debouncer( }) } -fn copy_to_clipboard(content: &str) -> text::State { +fn copy_to_clipboard(content: &str) -> status::State { match Clipboard::new() { Ok(mut clipboard) => match clipboard.set_text(content) { - Ok(_) => text::State { - text: Text::from("Copied to clipboard"), - config: text::Config { - style: Some(ContentStyle { - foreground_color: Some(Color::Green), - ..Default::default() - }), - ..Default::default() - }, - ..Default::default() - }, - Err(e) => text::State { - text: Text::from(format!("Failed to copy to clipboard: {e}")), - config: text::Config { - style: Some(ContentStyle { - foreground_color: Some(Color::Red), - ..Default::default() - }), - ..Default::default() - }, - ..Default::default() - }, + Ok(_) => status::State::new("Copied to clipboard", Severity::Success), + Err(e) => { + status::State::new(format!("Failed to copy to clipboard: {e}"), Severity::Error) + } }, // arboard fails (in the specific environment like linux?) on Clipboard::new() // suppress the errors (but still show them) not to break the prompt // https://github.com/1Password/arboard/issues/153 - Err(e) => text::State { - text: Text::from(format!("Failed to setup clipboard: {e}")), - config: text::Config { - style: Some(ContentStyle { - foreground_color: Some(Color::Red), - ..Default::default() - }), - ..Default::default() - }, - ..Default::default() - }, + Err(e) => status::State::new(format!("Failed to setup clipboard: {e}"), Severity::Error), } } +fn empty_pane() -> StyledGraphemes { + StyledGraphemes::default() +} + enum Focus { Editor, Processor, @@ -131,12 +106,12 @@ pub async fn run( let size = terminal::size()?; let shared_renderer = SharedRenderer::new( - Renderer::try_new_with_panes( + Renderer::try_new_with_graphemes( [ (Index::Editor, editor.create_editor_pane(size.0, size.1)), - (Index::Guide, EMPTY_PANE.to_owned()), - (Index::Search, EMPTY_PANE.to_owned()), - (Index::Processor, EMPTY_PANE.to_owned()), + (Index::Guide, empty_pane()), + (Index::Search, empty_pane()), + (Index::Processor, empty_pane()), ] .into_iter(), true, @@ -235,17 +210,11 @@ pub async fn run( shared_renderer.update([ ( Index::Guide, - text::State { - text: Text::from("Failed to copy while rendering is in progress.".to_string()), - config: text::Config { - style: Some(ContentStyle { - foreground_color: Some(Color::Yellow), - ..Default::default() - }), - ..Default::default() - }, - ..Default::default() - }.create_pane(size.0, size.1), + status::State::new( + "Failed to copy while rendering is in progress.", + Severity::Warning, + ) + .create_graphemes(size.0, size.1), ), ]).render().await?; } @@ -266,17 +235,12 @@ pub async fn run( shared_renderer.update([ ( Index::Guide, - text::State { - text: Text::from("Failed to switch pane while rendering is in progress.".to_string()), - config: text::Config { - style: Some(ContentStyle { - foreground_color: Some(Color::Yellow), - ..Default::default() - }), - ..Default::default() - }, - ..Default::default() - }.create_pane(size.0, size.1)), + status::State::new( + "Failed to switch pane while rendering is in progress.", + Severity::Warning, + ) + .create_graphemes(size.0, size.1), + ), ]).render().await?; } }, @@ -333,7 +297,7 @@ pub async fn run( }; shared_renderer.update([ (Index::Editor, editor_pane), - (Index::Guide, if !no_hint { guide_pane } else { EMPTY_PANE.to_owned() }), + (Index::Guide, if !no_hint { guide_pane } else { empty_pane() }), ]).render().await?; } Some(()) = editor_copy_rx.recv() => { @@ -344,7 +308,7 @@ pub async fn run( let guide = copy_to_clipboard(&text); if !no_hint { let size = terminal::size()?; - let pane = guide.create_pane(size.0, size.1); + let pane = guide.create_graphemes(size.0, size.1); shared_renderer.update([ (Index::Guide, pane), ]).render().await?; @@ -372,7 +336,7 @@ pub async fn run( { shared_renderer.update([ (Index::Editor, editor_pane), - (Index::Guide, if !no_hint { guide_pane } else { EMPTY_PANE.to_owned() }), + (Index::Guide, if !no_hint { guide_pane } else { empty_pane() }), (Index::Search, searcher_pane), ]).render().await?; } @@ -399,7 +363,7 @@ pub async fn run( let guide = copy_to_clipboard(&visualizer.content_to_copy().await); if !no_hint { let size = terminal::size()?; - let pane = guide.create_pane(size.0, size.1); + let pane = guide.create_graphemes(size.0, size.1); shared_renderer.update([ (Index::Guide, pane), ]).render().await?; @@ -435,7 +399,7 @@ pub async fn run( { shared_renderer.update([ (Index::Editor, editor_pane), - (Index::Guide, if !no_hint { guide_pane } else { EMPTY_PANE.to_owned() }), + (Index::Guide, if !no_hint { guide_pane } else { empty_pane() }), (Index::Search, searcher_pane), ]).render().await?; } diff --git a/src/search.rs b/src/search.rs index 93ce6a9..da01ce6 100644 --- a/src/search.rs +++ b/src/search.rs @@ -3,7 +3,7 @@ use std::{collections::BTreeSet, sync::Arc}; use anyhow::anyhow; use async_trait::async_trait; use promkit_widgets::{ - core::{pane::Pane, PaneFactory}, + core::{Widget, grapheme::StyledGraphemes}, listbox::{self, Listbox}, }; use tokio::{ @@ -111,8 +111,8 @@ impl IncrementalSearcher { self.state.listbox.get().to_string() } - pub fn create_pane(&self, width: u16, height: u16) -> Pane { - self.state.create_pane(width, height) + pub fn create_pane(&self, width: u16, height: u16) -> StyledGraphemes { + self.state.create_graphemes(width, height) } pub fn leave_search(&mut self) { From b46252f969823227cb15655bd01690a3084d8f97 Mon Sep 17 00:00:00 2001 From: ynqa Date: Mon, 16 Mar 2026 22:02:59 +0900 Subject: [PATCH 30/35] chore: use promkit spinner instead --- src/main.rs | 2 +- src/processor.rs | 1 - src/processor/monitor.rs | 13 +++++++++- src/processor/spinner.rs | 53 ---------------------------------------- src/prompt.rs | 17 +++++++++---- 5 files changed, 25 insertions(+), 61 deletions(-) delete mode 100644 src/processor/spinner.rs diff --git a/src/main.rs b/src/main.rs index 393d505..627c09a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,7 @@ mod stdout_redirect; use stdout_redirect::StdoutRedirect; mod processor; use processor::{ - init::ViewInitializer, monitor::ContextMonitor, spinner::SpinnerSpawner, Context, Processor, + init::ViewInitializer, monitor::ContextMonitor, Context, Processor, ViewProvider, Visualizer, }; mod prompt; diff --git a/src/processor.rs b/src/processor.rs index babc6e6..575111f 100644 --- a/src/processor.rs +++ b/src/processor.rs @@ -11,7 +11,6 @@ pub use init::ViewProvider; use crate::prompt::Index; pub mod monitor; -pub mod spinner; fn empty_pane() -> StyledGraphemes { StyledGraphemes::default() diff --git a/src/processor/monitor.rs b/src/processor/monitor.rs index b72a68a..3b04421 100644 --- a/src/processor/monitor.rs +++ b/src/processor/monitor.rs @@ -1,5 +1,6 @@ -use std::sync::Arc; +use std::{future::Future, sync::Arc}; +use promkit_widgets::spinner; use tokio::sync::Mutex; use super::{Context, State}; @@ -18,3 +19,13 @@ impl ContextMonitor { context.state == State::Idle } } + +impl spinner::State for ContextMonitor { + fn is_idle(&self) -> impl Future + Send { + let shared = self.shared.clone(); + async move { + let context = shared.lock().await; + context.state == State::Idle + } + } +} diff --git a/src/processor/spinner.rs b/src/processor/spinner.rs deleted file mode 100644 index 2ed8f80..0000000 --- a/src/processor/spinner.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::sync::Arc; - -use promkit_widgets::core::{grapheme::StyledGraphemes, render::SharedRenderer}; -use tokio::{sync::Mutex, task::JoinHandle, time::Duration}; - -use crate::prompt::Index; - -use super::{Context, State}; - -const LOADING_FRAMES: [&str; 10] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; - -pub struct SpinnerSpawner { - shared: Arc>, -} - -impl SpinnerSpawner { - pub fn new(shared: Arc>) -> Self { - Self { shared } - } - - pub fn spawn_spin_task( - &self, - shared_renderer: SharedRenderer, - spin_duration: Duration, - ) -> JoinHandle<()> { - let shared = self.shared.clone(); - let mut frame_index = 0; - tokio::spawn(async move { - let mut interval = tokio::time::interval(spin_duration); - loop { - interval.tick().await; - - { - let shared_state = shared.lock().await; - if shared_state.state == State::Idle { - continue; - } - } - - frame_index = (frame_index + 1) % LOADING_FRAMES.len(); - - let pane = StyledGraphemes::from(LOADING_FRAMES[frame_index]); - { - // TODO: error handling - let _ = shared_renderer - .update([(Index::Processor, pane)]) - .render() - .await; - } - } - }) - } -} diff --git a/src/prompt.rs b/src/prompt.rs index b616e59..75be08a 100644 --- a/src/prompt.rs +++ b/src/prompt.rs @@ -17,6 +17,7 @@ use promkit_widgets::{ grapheme::StyledGraphemes, render::{Renderer, SharedRenderer}, }, + spinner::{self, Spinner}, status::{self, Severity}, }; use tokio::{ @@ -26,7 +27,7 @@ use tokio::{ use crate::{ config::{Keybinds, ReactivityControl}, - Context, ContextMonitor, Editor, Processor, SearchProvider, SpinnerSpawner, ViewInitializer, + Context, ContextMonitor, Editor, Processor, SearchProvider, ViewInitializer, ViewProvider, Visualizer, }; @@ -81,7 +82,7 @@ enum Focus { Processor, } -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Index { Editor = 0, Guide = 1, @@ -140,9 +141,15 @@ pub async fn run( reactivity_control.resize_debounce_duration, ); - let spinner_spawner = SpinnerSpawner::new(ctx.clone()); - let spinning = - spinner_spawner.spawn_spin_task(shared_renderer.clone(), reactivity_control.spin_duration); + let spinning = tokio::spawn({ + let shared_renderer = shared_renderer.clone(); + let state = ContextMonitor::new(ctx.clone()); + let spin_duration = reactivity_control.spin_duration; + async move { + let spinner = Spinner::default().duration(spin_duration); + let _ = spinner::run(&spinner, state, Index::Processor, shared_renderer).await; + } + }); let mut focus = Focus::Editor; let (editor_event_tx, mut editor_event_rx) = mpsc::channel::(1); From 39f8d892b5a2ecb42397a553e612ee916a3c6624 Mon Sep 17 00:00:00 2001 From: ynqa Date: Wed, 18 Mar 2026 00:43:38 +0900 Subject: [PATCH 31/35] chore: remove duplicated is_idle function --- src/processor/monitor.rs | 5 ----- src/prompt.rs | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/processor/monitor.rs b/src/processor/monitor.rs index 3b04421..7775d5f 100644 --- a/src/processor/monitor.rs +++ b/src/processor/monitor.rs @@ -13,11 +13,6 @@ impl ContextMonitor { pub fn new(shared: Arc>) -> Self { Self { shared } } - - pub async fn is_idle(&self) -> bool { - let context = self.shared.lock().await; - context.state == State::Idle - } } impl spinner::State for ContextMonitor { diff --git a/src/prompt.rs b/src/prompt.rs index 75be08a..442866d 100644 --- a/src/prompt.rs +++ b/src/prompt.rs @@ -17,7 +17,7 @@ use promkit_widgets::{ grapheme::StyledGraphemes, render::{Renderer, SharedRenderer}, }, - spinner::{self, Spinner}, + spinner::{self, Spinner, State}, status::{self, Severity}, }; use tokio::{ From a31d4f0063d5eb27806e7f7d02003377bbdeefc5 Mon Sep 17 00:00:00 2001 From: ynqa Date: Tue, 24 Mar 2026 21:56:36 +0900 Subject: [PATCH 32/35] chore: use official promkit versions --- Cargo.lock | 26 ++++++++++++++++++-------- Cargo.toml | 1 - 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 405bc1b..5f15978 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1157,6 +1157,8 @@ dependencies = [ [[package]] name = "promkit-core" version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26da2c38cb21f2c5470a00868b513916d904f3fd6aef947962a5a06350651c49" dependencies = [ "anyhow", "crossbeam-skiplist", @@ -1168,6 +1170,8 @@ dependencies = [ [[package]] name = "promkit-widgets" version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2863f757bfc30b08ba1e0f54c7e1fe3ea62b46ad44fa82cff0849f1293f29188" dependencies = [ "anyhow", "promkit-core", @@ -1340,9 +1344,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" dependencies = [ "serde_core", ] @@ -1545,7 +1549,7 @@ dependencies = [ "toml_datetime", "toml_parser", "toml_writer", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -1559,18 +1563,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.9+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" dependencies = [ - "winnow", + "winnow 1.0.0", ] [[package]] name = "toml_writer" -version = "1.0.6+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" +checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" [[package]] name = "tree_magic_mini" @@ -1916,6 +1920,12 @@ version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +[[package]] +name = "winnow" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" + [[package]] name = "wl-clipboard-rs" version = "0.9.3" diff --git a/Cargo.toml b/Cargo.toml index 3365fd0..ddd2b54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,6 @@ jaq-std = "2.1.2" [dependencies.promkit-widgets] version = "0.5.0" -path = "../promkit/promkit-widgets" features = ["jsonstream", "listbox", "serde", "spinner", "status", "texteditor"] default-features = false From 43a60721bbfb423cc8bf7e867e1db74e11c01b94 Mon Sep 17 00:00:00 2001 From: ynqa Date: Tue, 24 Mar 2026 21:58:57 +0900 Subject: [PATCH 33/35] cargo-fmt --- src/editor.rs | 2 +- src/json.rs | 8 ++------ src/main.rs | 3 +-- src/processor.rs | 6 +----- src/prompt.rs | 6 +++--- src/search.rs | 2 +- 6 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/editor.rs b/src/editor.rs index 07c1c12..7a39e60 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -2,9 +2,9 @@ use std::{future::Future, pin::Pin}; use promkit_widgets::{ core::{ - Widget, crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers}, grapheme::StyledGraphemes, + Widget, }, status::{self, Severity}, text_editor, diff --git a/src/json.rs b/src/json.rs index d4353fd..348b0d7 100644 --- a/src/json.rs +++ b/src/json.rs @@ -5,7 +5,7 @@ use jaq_core::{ use jaq_json::Val; use promkit_widgets::{ - core::{Widget, crossterm::event::Event, grapheme::StyledGraphemes}, + core::{crossterm::event::Event, grapheme::StyledGraphemes, Widget}, jsonstream::{self, config::Config as JsonStreamConfig, jsonz, JsonStream}, serde_json::{self, Deserializer, Value}, status::{self, Severity}, @@ -90,11 +90,7 @@ impl Visualizer for Json { self.state.create_graphemes(area.0, area.1) } - async fn create_pane_from_event( - &mut self, - area: (u16, u16), - event: &Event, - ) -> StyledGraphemes { + async fn create_pane_from_event(&mut self, area: (u16, u16), event: &Event) -> StyledGraphemes { self.operate(event); self.state.create_graphemes(area.0, area.1) } diff --git a/src/main.rs b/src/main.rs index 627c09a..38e2ee7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,8 +21,7 @@ mod stdout_redirect; use stdout_redirect::StdoutRedirect; mod processor; use processor::{ - init::ViewInitializer, monitor::ContextMonitor, Context, Processor, - ViewProvider, Visualizer, + init::ViewInitializer, monitor::ContextMonitor, Context, Processor, ViewProvider, Visualizer, }; mod prompt; mod search; diff --git a/src/processor.rs b/src/processor.rs index 575111f..3e588e0 100644 --- a/src/processor.rs +++ b/src/processor.rs @@ -27,11 +27,7 @@ enum State { pub trait Visualizer: Send + Sync + 'static { async fn content_to_copy(&self) -> String; async fn create_init_pane(&mut self, area: (u16, u16)) -> StyledGraphemes; - async fn create_pane_from_event( - &mut self, - area: (u16, u16), - event: &Event, - ) -> StyledGraphemes; + async fn create_pane_from_event(&mut self, area: (u16, u16), event: &Event) -> StyledGraphemes; async fn create_panes_from_query( &mut self, area: (u16, u16), diff --git a/src/prompt.rs b/src/prompt.rs index 442866d..52afd75 100644 --- a/src/prompt.rs +++ b/src/prompt.rs @@ -4,7 +4,6 @@ use arboard::Clipboard; use futures::StreamExt; use promkit_widgets::{ core::{ - Widget, crossterm::{ cursor, event::{ @@ -16,6 +15,7 @@ use promkit_widgets::{ }, grapheme::StyledGraphemes, render::{Renderer, SharedRenderer}, + Widget, }, spinner::{self, Spinner, State}, status::{self, Severity}, @@ -27,8 +27,8 @@ use tokio::{ use crate::{ config::{Keybinds, ReactivityControl}, - Context, ContextMonitor, Editor, Processor, SearchProvider, ViewInitializer, - ViewProvider, Visualizer, + Context, ContextMonitor, Editor, Processor, SearchProvider, ViewInitializer, ViewProvider, + Visualizer, }; fn spawn_debouncer( diff --git a/src/search.rs b/src/search.rs index da01ce6..b46869f 100644 --- a/src/search.rs +++ b/src/search.rs @@ -3,7 +3,7 @@ use std::{collections::BTreeSet, sync::Arc}; use anyhow::anyhow; use async_trait::async_trait; use promkit_widgets::{ - core::{Widget, grapheme::StyledGraphemes}, + core::{grapheme::StyledGraphemes, Widget}, listbox::{self, Listbox}, }; use tokio::{ From c2431a2d4ca905c1723806204dab8e318f966ea8 Mon Sep 17 00:00:00 2001 From: ynqa Date: Wed, 25 Mar 2026 19:03:29 +0900 Subject: [PATCH 34/35] bump up version to v0.7.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5f15978..d932e4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -843,7 +843,7 @@ dependencies = [ [[package]] name = "jnv" -version = "0.6.2" +version = "0.7.0" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index ddd2b54..ace8337 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jnv" -version = "0.6.2" +version = "0.7.0" authors = ["ynqa "] edition = "2021" description = "JSON navigator and interactive filter leveraging jq" From 972d5b6f5fcb088729298798ccd17515b5af401d Mon Sep 17 00:00:00 2001 From: ynqa Date: Wed, 25 Mar 2026 19:03:40 +0900 Subject: [PATCH 35/35] cargo-update --- Cargo.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d932e4e..f5a7f0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -796,9 +796,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jaq-core" @@ -889,9 +889,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ "libc", ] @@ -1601,9 +1601,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "da36089a805484bcccfffe0739803392c8298778a2d2f09febf76fac5ad9025b" [[package]] name = "unicode-width" @@ -1963,18 +1963,18 @@ checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" [[package]] name = "zerocopy" -version = "0.8.42" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.42" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" dependencies = [ "proc-macro2", "quote", @@ -1995,9 +1995,9 @@ checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" [[package]] name = "zune-jpeg" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec5f41c76397b7da451efd19915684f727d7e1d516384ca6bd0ec43ec94de23c" +checksum = "0b7a1c0af6e5d8d1363f4994b7a091ccf963d8b694f7da5b0b9cceb82da2c0a6" dependencies = [ "zune-core", ]