diff --git a/Cargo.lock b/Cargo.lock
index 2028174..460d149 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"
@@ -1169,16 +1169,16 @@ dependencies = [
[[package]]
name = "promkit-widgets"
-version = "0.2.0"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7ef81079760b198d5dde773c78b94a72edc2ebd057be386382c379e0d854fb6"
+checksum = "b665ae2f09cfe22290386f73e642922980cb6a5edd83188989dcb1e914dc4a7b"
dependencies = [
"anyhow",
"promkit-core",
"rayon",
"serde",
"serde_json",
- "tokio",
+ "termcfg",
]
[[package]]
@@ -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",
@@ -1437,9 +1437,9 @@ dependencies = [
[[package]]
name = "termcfg"
-version = "0.1.0"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b8c057661e9a795bc9cb18a7d2924b00d0aa3c5a9e61dfe7de32cea8a1d8fe7"
+checksum = "6e444ec0de07571e26f31f6ac5006c03540b61d35bec105031e82c6d6cbb5fab"
dependencies = [
"crossterm",
"serde",
@@ -1633,9 +1633,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",
@@ -1646,9 +1646,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",
@@ -1656,9 +1656,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",
@@ -1669,9 +1669,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 1111a6d..eef4861 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,9 +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"] }
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"
@@ -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"
diff --git a/README.md b/README.md
index 3b9301a..bea138c 100644
--- a/README.md
+++ b/README.md
@@ -208,48 +208,61 @@ 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
> `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,17 @@ 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"
+# 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]
-# 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 +330,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 +418,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
+
[](https://starchart.cc/ynqa/jnv)
diff --git a/default.toml b/default.toml
index 514838a..05f892e 100644
--- a/default.toml
+++ b/default.toml
@@ -2,35 +2,41 @@
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"
+#
+# Color tokens:
+# - reset, black, red, green, yellow, blue, magenta, cyan, white
+# - darkgrey, darkred, darkgreen, darkyellow, darkblue, darkmagenta, darkcyan, grey
+# - #RRGGBB
#
-# Text attribute specification:
-# attributes = ["Bold"], etc.
+# Attribute tokens (examples):
+# - bold, italic, underlined, dim, reverse, crossedout, nounderline, nobold
#
-# Configuration example:
-# style = { foreground = "blue", background = "magenta", attributes = ["Bold"] }
+# 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.
#
-# 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
+# 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
@@ -41,7 +47,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 +64,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 +83,17 @@ 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"
+# 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]
-# 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 +104,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
@@ -145,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/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..1bd93c2 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,16 @@ 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 +90,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 +113,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 +138,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 +194,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..911d67d 100644
--- a/src/prompt.rs
+++ b/src/prompt.rs
@@ -7,7 +7,8 @@ use promkit_widgets::{
crossterm::{
cursor,
event::{
- Event, EventStream, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers,
+ DisableMouseCapture, EnableMouseCapture, Event, EventStream, MouseEvent,
+ MouseEventKind,
},
execute,
style::{Color, ContentStyle},
@@ -62,16 +63,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 +89,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()
@@ -187,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?;
@@ -194,20 +223,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{
@@ -217,8 +236,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()
@@ -227,22 +249,17 @@ 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 {
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([
@@ -250,8 +267,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()
@@ -262,6 +282,11 @@ pub async fn run(
Focus::Processor => {
focus = Focus::Editor;
editor_focus_tx.send(true).await?;
+ execute!(
+ io::stdout(),
+ terminal::LeaveAlternateScreen,
+ DisableMouseCapture,
+ )?;
},
}
},
@@ -442,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(())
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(),