diff options
| author | P T Weir <phil.weir@flaxandteal.co.uk> | 2025-10-20 21:02:40 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-10-20 13:02:40 -0700 |
| commit | d53ad84e5727f7cad09eee09a2444da81a88b643 (patch) | |
| tree | a8d551e1007c26d682d681356faa4e056160ca1d /crates/atuin-client/src | |
| parent | feat(bash): use Readline's accept-line for enter_accept (#2953) (diff) | |
| download | atuin-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')
| -rw-r--r-- | crates/atuin-client/src/theme.rs | 37 |
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| { |
