From 32d2efdb5fcd2d9639bbf4966358c9392fbba2bf Mon Sep 17 00:00:00 2001
From: Ethan <36282608+etbyrd@users.noreply.github.com>
Date: Wed, 4 Feb 2026 21:30:41 -0800
Subject: feat(dotfiles): add sort and filter options to alias/var list (#3131)
groundwork for #2155. These functions can be reused when building the
interactive TUI.
Adds `--sort-by`, `--reverse`, `--name`, and `--value` flags to `atuin
dotfiles alias list` and `atuin dotfiles var list`. Also adds
`--exports-only` and `--shell-only` for vars.
happy to expand (or reduce?) on this if needed.
**Demo/Examples:**
`$atuin dotfiles alias list --reverse`
```bash
vim=nvim
py=python3
ll=ls -la
k=kubectl
gs=git status
gp=git push
gd=git diff
gc=git commit
dc=docker-compose
d=docker
```
`$atuin dotfiles alias list --sort-by value`
```bash
d=docker
dc=docker-compose
gc=git commit
gd=git diff
gp=git push
gs=git status
k=kubectl
ll=ls -la
vim=nvim
py=python3
```
`$atuin dotfiles alias list -n g`
```bash
gc=git commit
gd=git diff
gp=git push
gs=git status
```
`$atuin dotfiles var list --exports-only`
```bash
export EDITOR=vim
export LANG=en_US.UTF-8
export PAGER=less
export RUST_BACKTRACE=1
```
## 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
---
crates/atuin/src/command/client/dotfiles/alias.rs | 81 ++++++++++++++-
crates/atuin/src/command/client/dotfiles/var.rs | 114 ++++++++++++++++++++--
2 files changed, 180 insertions(+), 15 deletions(-)
(limited to 'crates')
diff --git a/crates/atuin/src/command/client/dotfiles/alias.rs b/crates/atuin/src/command/client/dotfiles/alias.rs
index 2a916b06..983c67f1 100644
--- a/crates/atuin/src/command/client/dotfiles/alias.rs
+++ b/crates/atuin/src/command/client/dotfiles/alias.rs
@@ -1,10 +1,19 @@
-use clap::Subcommand;
+use clap::{Subcommand, ValueEnum};
use eyre::{Context, Result, eyre};
use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings};
use atuin_dotfiles::{shell::Alias, store::AliasStore};
+#[derive(Clone, Copy, Debug, Default, ValueEnum)]
+pub enum SortBy {
+ /// Sort by alias name
+ #[default]
+ Name,
+ /// Sort by alias value
+ Value,
+}
+
#[derive(Subcommand, Debug)]
#[command(infer_subcommands = true)]
pub enum Cmd {
@@ -15,7 +24,23 @@ pub enum Cmd {
Delete { name: String },
/// List all aliases
- List,
+ List {
+ /// Sort results by field
+ #[arg(long, value_enum, default_value_t = SortBy::Name)]
+ sort_by: SortBy,
+
+ /// Sort in reverse (descending) order
+ #[arg(long, short)]
+ reverse: bool,
+
+ /// Filter aliases by name (substring match)
+ #[arg(long, short)]
+ name: Option,
+
+ /// Filter aliases by value (substring match)
+ #[arg(long, short)]
+ value: Option,
+ },
/// Delete all aliases
Clear,
@@ -47,8 +72,40 @@ impl Cmd {
Ok(())
}
- async fn list(&self, store: &AliasStore) -> Result<()> {
- let aliases = store.aliases().await?;
+ async fn list(
+ &self,
+ store: &AliasStore,
+ sort_by: SortBy,
+ reverse: bool,
+ name_filter: Option,
+ value_filter: Option,
+ ) -> Result<()> {
+ let mut aliases = store.aliases().await?;
+
+ // Apply filters
+ if let Some(ref name_pattern) = name_filter {
+ let pattern = name_pattern.to_lowercase();
+ aliases.retain(|a| a.name.to_lowercase().contains(&pattern));
+ }
+ if let Some(ref value_pattern) = value_filter {
+ let pattern = value_pattern.to_lowercase();
+ aliases.retain(|a| a.value.to_lowercase().contains(&pattern));
+ }
+
+ // Apply sorting
+ match sort_by {
+ SortBy::Name => {
+ aliases.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
+ }
+ SortBy::Value => {
+ aliases.sort_by(|a, b| a.value.to_lowercase().cmp(&b.value.to_lowercase()));
+ }
+ }
+
+ // Apply reverse if requested
+ if reverse {
+ aliases.reverse();
+ }
for i in aliases {
println!("{}={}", i.name, i.value);
@@ -109,7 +166,21 @@ impl Cmd {
match self {
Self::Set { name, value } => self.set(&alias_store, name.clone(), value.clone()).await,
Self::Delete { name } => self.delete(&alias_store, name.clone()).await,
- Self::List => self.list(&alias_store).await,
+ Self::List {
+ sort_by,
+ reverse,
+ name,
+ value,
+ } => {
+ self.list(
+ &alias_store,
+ *sort_by,
+ *reverse,
+ name.clone(),
+ value.clone(),
+ )
+ .await
+ }
Self::Clear => self.clear(&alias_store).await,
}
}
diff --git a/crates/atuin/src/command/client/dotfiles/var.rs b/crates/atuin/src/command/client/dotfiles/var.rs
index b48ceaa4..a63231ec 100644
--- a/crates/atuin/src/command/client/dotfiles/var.rs
+++ b/crates/atuin/src/command/client/dotfiles/var.rs
@@ -1,10 +1,19 @@
-use clap::Subcommand;
+use clap::{Subcommand, ValueEnum};
use eyre::{Context, Result};
use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings};
use atuin_dotfiles::{shell::Var, store::var::VarStore};
+#[derive(Clone, Copy, Debug, Default, ValueEnum)]
+pub enum SortBy {
+ /// Sort by variable name
+ #[default]
+ Name,
+ /// Sort by variable value
+ Value,
+}
+
#[derive(Subcommand, Debug)]
#[command(infer_subcommands = true)]
pub enum Cmd {
@@ -21,7 +30,31 @@ pub enum Cmd {
Delete { name: String },
/// List all variables
- List,
+ List {
+ /// Sort results by field
+ #[arg(long, value_enum, default_value_t = SortBy::Name)]
+ sort_by: SortBy,
+
+ /// Sort in reverse (descending) order
+ #[arg(long, short)]
+ reverse: bool,
+
+ /// Filter variables by name (substring match)
+ #[arg(long, short)]
+ name: Option,
+
+ /// Filter variables by value (substring match)
+ #[arg(long, short)]
+ value: Option,
+
+ /// Show only exported variables
+ #[arg(long, conflicts_with = "shell_only")]
+ exports_only: bool,
+
+ /// Show only non-exported (shell) variables
+ #[arg(long, conflicts_with = "exports_only")]
+ shell_only: bool,
+ },
}
impl Cmd {
@@ -34,7 +67,7 @@ impl Cmd {
println!("Setting '{show_export}{name}={value}'.");
} else {
println!(
- "Overwriting alias '{show_export}{name}={}' with '{name}={value}'.",
+ "Overwriting var '{show_export}{name}={}' with '{name}={value}'.",
found[0].value
);
}
@@ -44,15 +77,58 @@ impl Cmd {
Ok(())
}
- async fn list(&self, store: VarStore) -> Result<()> {
- let vars = store.vars().await?;
+ #[allow(clippy::too_many_arguments)]
+ async fn list(
+ &self,
+ store: VarStore,
+ sort_by: SortBy,
+ reverse: bool,
+ name_filter: Option,
+ value_filter: Option,
+ exports_only: bool,
+ shell_only: bool,
+ ) -> Result<()> {
+ let mut vars = store.vars().await?;
+
+ // Apply export/shell filters
+ if exports_only {
+ vars.retain(|v| v.export);
+ }
+ if shell_only {
+ vars.retain(|v| !v.export);
+ }
+
+ // Apply name/value filters
+ if let Some(ref name_pattern) = name_filter {
+ let pattern = name_pattern.to_lowercase();
+ vars.retain(|v| v.name.to_lowercase().contains(&pattern));
+ }
+ if let Some(ref value_pattern) = value_filter {
+ let pattern = value_pattern.to_lowercase();
+ vars.retain(|v| v.value.to_lowercase().contains(&pattern));
+ }
- for i in vars.iter().filter(|v| !v.export) {
- println!("{}={}", i.name, i.value);
+ // Apply sorting
+ match sort_by {
+ SortBy::Name => {
+ vars.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
+ }
+ SortBy::Value => {
+ vars.sort_by(|a, b| a.value.to_lowercase().cmp(&b.value.to_lowercase()));
+ }
}
- for i in vars.iter().filter(|v| v.export) {
- println!("export {}={}", i.name, i.value);
+ // Apply reverse if requested
+ if reverse {
+ vars.reverse();
+ }
+
+ for i in vars {
+ if i.export {
+ println!("export {}={}", i.name, i.value);
+ } else {
+ println!("{}={}", i.name, i.value);
+ }
}
Ok(())
@@ -97,7 +173,25 @@ impl Cmd {
.await
}
Self::Delete { name } => self.delete(var_store, name.clone()).await,
- Self::List => self.list(var_store).await,
+ Self::List {
+ sort_by,
+ reverse,
+ name,
+ value,
+ exports_only,
+ shell_only,
+ } => {
+ self.list(
+ var_store,
+ *sort_by,
+ *reverse,
+ name.clone(),
+ value.clone(),
+ *exports_only,
+ *shell_only,
+ )
+ .await
+ }
}
}
}
--
cgit v1.3.1