aboutsummaryrefslogtreecommitdiffstats
path: root/crates/atuin-client/src/theme.rs
diff options
context:
space:
mode:
authorP T Weir <phil.weir@flaxandteal.co.uk>2025-10-20 21:02:40 +0100
committerGitHub <noreply@github.com>2025-10-20 13:02:40 -0700
commitd53ad84e5727f7cad09eee09a2444da81a88b643 (patch)
treea8d551e1007c26d682d681356faa4e056160ca1d /crates/atuin-client/src/theme.rs
parentfeat(bash): use Readline's accept-line for enter_accept (#2953) (diff)
downloadatuin-d53ad84e5727f7cad09eee09a2444da81a88b643.zip
feat: Interactive Inspector (#2319)
### What does this PR do? Adds simple navigation to the inspector, to explore a session starting from a single command. This creates a new user flow, where a user can find a history entry in the interactive view (in, say, Global mode), and hit Ctrl+o to navigate back and forward through that command's session. IMAGINED USE-CASE: I remembered that I did a sequence of git steps but I can't remember the order and forgot to document it. I remember that `reflog` was involved and want to see the actual sequence, and only those commands. IMAGINED USE-CASE: I used a curl command to get my IP address for greenlisting before I connected to the bastion server `abc.xyz` over SSH - I could easily find the SSH command with abc.xyz, and go back one step in the session, but without this change, scrolling through all my curl commands ever run to find a forgotten URL/domain would be too much work. Since this gives the inspector tab a broader purpose than viewing analytics, it needs to function even when there are not enough screen rows for charts -- hence, this PR also introduces an ultracompact mode for the inspector that _just_ shows the neighbouring history commands (as simple scrolling three-entry list, with no panes) if there are fewer than `auto_hide_height` rows (default: 8). Otherwise, the inspector behaves as normal, except that Up / Down will change the focused command by navigating through the session. That means there is no "compact" mode for the inspector - when the interactive search is compact (but not ultracompact), the inspector shows its usual chart view. The UX for this could be improved - to keep this PR as lean as it realistically can be, I have tried to keep the flow very minimal, but a follow-up PR could introduce some tooltips, nicer ultracompact formatting, etc. A minor QoL improvement that comes with this - since I had to deal with bold text and would otherwise have need a theming exception, I took the opportunity to ensure the theme engine sets styles completely (so a theme can have bold), not just colours. To limit scope creep, I do not add TOML syntax so (for now) you can only customize colours from config files, but it means that default-bold text (etc.) can now use the theming engine if the code-defined default Meaning is bolded. Key changes: * introduces a simplified inspector tab, with only previous-current-next commands as rows, in compact mode * allows navigation through session history within the inspector (so compact inspector view is still useful) It also (see comments below): * makes `compact` into `compactness`, an enum (to better standardize across inspector/interactive) * makes the inspector _only_ change layout for ultracompact mode, which is still compact+(height<8) * clippy's complexity limit wanted draw split up a little, so not sure if this is a reasonable minimal way to do so for now * adds a `(none)` theme to the theming to enable output testing without styling * ~~additional tests, although keen for input on how best to do these~~ one functional test, as a starting point * ~~documentation~~ [minor doc changes only](https://github.com/atuinsh/docs/pull/72), as I am not sure there is much to say <!-- Thank you for making a PR! Bug fixes are always welcome, but if you're adding a new feature or changing an existing one, we'd really appreciate if you open an issue, post on the forum, or drop in on Discord --> _Was stacked on #2357, which is now in `main`_ ## Checks - [x] I am happy for maintainers to push small adjustments to this PR, to speed up the review cycle - [x] I have checked that there are no existing pull requests for the same thing
Diffstat (limited to 'crates/atuin-client/src/theme.rs')
-rw-r--r--crates/atuin-client/src/theme.rs37
1 files changed, 31 insertions, 6 deletions
diff --git a/crates/atuin-client/src/theme.rs b/crates/atuin-client/src/theme.rs
index fc15bfd8..76ddbb22 100644
--- a/crates/atuin-client/src/theme.rs
+++ b/crates/atuin-client/src/theme.rs
@@ -51,7 +51,7 @@ pub struct ThemeDefinitionConfigBlock {
pub parent: Option<String>,
}
-use crossterm::style::{Color, ContentStyle};
+use crossterm::style::{Attribute, Attributes, Color, ContentStyle};
// For now, a theme is loaded as a mapping of meanings to colors, but it may be desirable to
// expand that in the future to general styles, so we populate a Meaning->ContentStyle hashmap.
@@ -228,6 +228,14 @@ impl StyleFactory {
..ContentStyle::default()
}
}
+
+ fn from_fg_color_and_attributes(color: Color, attributes: Attributes) -> ContentStyle {
+ ContentStyle {
+ foreground_color: Some(color),
+ attributes,
+ ..ContentStyle::default()
+ }
+ }
}
// Built-in themes. Rather than having extra files added before any theming
@@ -275,7 +283,10 @@ lazy_static! {
),
(
Meaning::Important,
- StyleFactory::from_fg_color(Color::White),
+ StyleFactory::from_fg_color_and_attributes(
+ Color::White,
+ Attributes::from(Attribute::Bold),
+ ),
),
(Meaning::Muted, StyleFactory::from_fg_color(Color::Grey)),
(Meaning::Base, ContentStyle::default()),
@@ -286,6 +297,19 @@ lazy_static! {
HashMap::from([
("default", HashMap::new()),
(
+ "(none)",
+ HashMap::from([
+ (Meaning::AlertError, ContentStyle::default()),
+ (Meaning::AlertWarn, ContentStyle::default()),
+ (Meaning::AlertInfo, ContentStyle::default()),
+ (Meaning::Annotation, ContentStyle::default()),
+ (Meaning::Guidance, ContentStyle::default()),
+ (Meaning::Important, ContentStyle::default()),
+ (Meaning::Muted, ContentStyle::default()),
+ (Meaning::Base, ContentStyle::default()),
+ ]),
+ ),
+ (
"autumn",
HashMap::from([
(
@@ -430,7 +454,7 @@ impl ThemeManager {
}
Some(self.load_theme(parent_name.as_str(), Some(max_depth - 1)))
}
- None => None,
+ None => Some(self.load_theme("default", Some(max_depth - 1))),
};
if debug && name != theme_config.theme.name {
@@ -461,7 +485,7 @@ impl ThemeManager {
Ok(theme) => theme,
Err(err) => {
log::warn!("Could not load theme {name}: {err}");
- built_ins.get("default").unwrap()
+ built_ins.get("(none)").unwrap()
}
},
}
@@ -669,7 +693,8 @@ mod theme_tests {
testing_logger::validate(|captured_logs| assert_eq!(captured_logs.len(), 0));
- // If the parent is not found, we end up with the base theme colors
+ // If the parent is not found, we end up with the no theme colors or styling
+ // as this is considered a (soft) error state.
let nunsolarized = Config::builder()
.add_source(ConfigFile::from_str(
"
@@ -692,7 +717,7 @@ mod theme_tests {
nunsolarized_theme
.as_style(Meaning::Guidance)
.foreground_color,
- Some(Color::DarkBlue)
+ None
);
testing_logger::validate(|captured_logs| {