mirror of
https://github.com/NotAShelf/stash.git
synced 2026-04-12 22:17:41 +00:00
commands/list: allow printing in reversed order with --reverse
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I305cfdc68d877dc5d5083a76dccc62db6a6a6964
This commit is contained in:
parent
181edcefb1
commit
ffdc13e8f5
3 changed files with 83 additions and 32 deletions
|
|
@ -11,6 +11,7 @@ pub trait ListCommand {
|
||||||
out: impl Write,
|
out: impl Write,
|
||||||
preview_width: u32,
|
preview_width: u32,
|
||||||
include_expired: bool,
|
include_expired: bool,
|
||||||
|
reverse: bool,
|
||||||
) -> Result<(), StashError>;
|
) -> Result<(), StashError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -20,9 +21,10 @@ impl ListCommand for SqliteClipboardDb {
|
||||||
out: impl Write,
|
out: impl Write,
|
||||||
preview_width: u32,
|
preview_width: u32,
|
||||||
include_expired: bool,
|
include_expired: bool,
|
||||||
|
reverse: bool,
|
||||||
) -> Result<(), StashError> {
|
) -> Result<(), StashError> {
|
||||||
self
|
self
|
||||||
.list_entries(out, preview_width, include_expired)
|
.list_entries(out, preview_width, include_expired, reverse)
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -52,6 +54,9 @@ struct TuiState {
|
||||||
|
|
||||||
/// Whether we're currently in search input mode.
|
/// Whether we're currently in search input mode.
|
||||||
search_mode: bool,
|
search_mode: bool,
|
||||||
|
|
||||||
|
/// Whether to show entries in reverse order (oldest first).
|
||||||
|
reverse: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TuiState {
|
impl TuiState {
|
||||||
|
|
@ -61,6 +66,7 @@ impl TuiState {
|
||||||
include_expired: bool,
|
include_expired: bool,
|
||||||
window_size: usize,
|
window_size: usize,
|
||||||
preview_width: u32,
|
preview_width: u32,
|
||||||
|
reverse: bool,
|
||||||
) -> Result<Self, StashError> {
|
) -> Result<Self, StashError> {
|
||||||
let total = db.count_entries(include_expired, None)?;
|
let total = db.count_entries(include_expired, None)?;
|
||||||
let window = if total > 0 {
|
let window = if total > 0 {
|
||||||
|
|
@ -70,6 +76,7 @@ impl TuiState {
|
||||||
window_size,
|
window_size,
|
||||||
preview_width,
|
preview_width,
|
||||||
None,
|
None,
|
||||||
|
reverse,
|
||||||
)?
|
)?
|
||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
|
|
@ -83,6 +90,7 @@ impl TuiState {
|
||||||
dirty: false,
|
dirty: false,
|
||||||
search_query: String::new(),
|
search_query: String::new(),
|
||||||
search_mode: false,
|
search_mode: false,
|
||||||
|
reverse,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,6 +236,7 @@ impl TuiState {
|
||||||
self.window_size,
|
self.window_size,
|
||||||
preview_width,
|
preview_width,
|
||||||
search,
|
search,
|
||||||
|
self.reverse,
|
||||||
)?
|
)?
|
||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
|
|
@ -266,6 +275,7 @@ impl SqliteClipboardDb {
|
||||||
&self,
|
&self,
|
||||||
preview_width: u32,
|
preview_width: u32,
|
||||||
include_expired: bool,
|
include_expired: bool,
|
||||||
|
reverse: bool,
|
||||||
) -> Result<(), StashError> {
|
) -> Result<(), StashError> {
|
||||||
use std::io::stdout;
|
use std::io::stdout;
|
||||||
|
|
||||||
|
|
@ -316,8 +326,13 @@ impl SqliteClipboardDb {
|
||||||
.unwrap_or(24);
|
.unwrap_or(24);
|
||||||
let initial_height = initial_height.max(1);
|
let initial_height = initial_height.max(1);
|
||||||
|
|
||||||
let mut tui =
|
let mut tui = TuiState::new(
|
||||||
TuiState::new(self, include_expired, initial_height, preview_width)?;
|
self,
|
||||||
|
include_expired,
|
||||||
|
initial_height,
|
||||||
|
preview_width,
|
||||||
|
reverse,
|
||||||
|
)?;
|
||||||
|
|
||||||
// ratatui ListState; only tracks selection within the *window* slice.
|
// ratatui ListState; only tracks selection within the *window* slice.
|
||||||
let mut list_state = ListState::default();
|
let mut list_state = ListState::default();
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ pub trait ClipboardDb {
|
||||||
out: impl Write,
|
out: impl Write,
|
||||||
preview_width: u32,
|
preview_width: u32,
|
||||||
include_expired: bool,
|
include_expired: bool,
|
||||||
|
reverse: bool,
|
||||||
) -> Result<usize, StashError>;
|
) -> Result<usize, StashError>;
|
||||||
fn decode_entry(
|
fn decode_entry(
|
||||||
&self,
|
&self,
|
||||||
|
|
@ -362,17 +363,27 @@ impl SqliteClipboardDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SqliteClipboardDb {
|
impl SqliteClipboardDb {
|
||||||
pub fn list_json(&self, include_expired: bool) -> Result<String, StashError> {
|
pub fn list_json(
|
||||||
|
&self,
|
||||||
|
include_expired: bool,
|
||||||
|
reverse: bool,
|
||||||
|
) -> Result<String, StashError> {
|
||||||
|
let order = if reverse { "ASC" } else { "DESC" };
|
||||||
let query = if include_expired {
|
let query = if include_expired {
|
||||||
"SELECT id, contents, mime FROM clipboard ORDER BY \
|
format!(
|
||||||
COALESCE(last_accessed, 0) DESC, id DESC"
|
"SELECT id, contents, mime FROM clipboard ORDER BY \
|
||||||
|
COALESCE(last_accessed, 0) {order}, id {order}"
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
"SELECT id, contents, mime FROM clipboard WHERE (is_expired IS NULL OR \
|
format!(
|
||||||
is_expired = 0) ORDER BY COALESCE(last_accessed, 0) DESC, id DESC"
|
"SELECT id, contents, mime FROM clipboard WHERE (is_expired IS NULL \
|
||||||
|
OR is_expired = 0) ORDER BY COALESCE(last_accessed, 0) {order}, id \
|
||||||
|
{order}"
|
||||||
|
)
|
||||||
};
|
};
|
||||||
let mut stmt = self
|
let mut stmt = self
|
||||||
.conn
|
.conn
|
||||||
.prepare(query)
|
.prepare(&query)
|
||||||
.map_err(|e| StashError::ListDecode(e.to_string().into()))?;
|
.map_err(|e| StashError::ListDecode(e.to_string().into()))?;
|
||||||
let mut rows = stmt
|
let mut rows = stmt
|
||||||
.query([])
|
.query([])
|
||||||
|
|
@ -594,17 +605,24 @@ impl ClipboardDb for SqliteClipboardDb {
|
||||||
mut out: impl Write,
|
mut out: impl Write,
|
||||||
preview_width: u32,
|
preview_width: u32,
|
||||||
include_expired: bool,
|
include_expired: bool,
|
||||||
|
reverse: bool,
|
||||||
) -> Result<usize, StashError> {
|
) -> Result<usize, StashError> {
|
||||||
|
let order = if reverse { "ASC" } else { "DESC" };
|
||||||
let query = if include_expired {
|
let query = if include_expired {
|
||||||
"SELECT id, contents, mime FROM clipboard ORDER BY \
|
format!(
|
||||||
COALESCE(last_accessed, 0) DESC, id DESC"
|
"SELECT id, contents, mime FROM clipboard ORDER BY \
|
||||||
|
COALESCE(last_accessed, 0) {order}, id {order}"
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
"SELECT id, contents, mime FROM clipboard WHERE (is_expired IS NULL OR \
|
format!(
|
||||||
is_expired = 0) ORDER BY COALESCE(last_accessed, 0) DESC, id DESC"
|
"SELECT id, contents, mime FROM clipboard WHERE (is_expired IS NULL \
|
||||||
|
OR is_expired = 0) ORDER BY COALESCE(last_accessed, 0) {order}, id \
|
||||||
|
{order}"
|
||||||
|
)
|
||||||
};
|
};
|
||||||
let mut stmt = self
|
let mut stmt = self
|
||||||
.conn
|
.conn
|
||||||
.prepare(query)
|
.prepare(&query)
|
||||||
.map_err(|e| StashError::ListDecode(e.to_string().into()))?;
|
.map_err(|e| StashError::ListDecode(e.to_string().into()))?;
|
||||||
let mut rows = stmt
|
let mut rows = stmt
|
||||||
.query([])
|
.query([])
|
||||||
|
|
@ -818,38 +836,48 @@ impl SqliteClipboardDb {
|
||||||
limit: usize,
|
limit: usize,
|
||||||
preview_width: u32,
|
preview_width: u32,
|
||||||
search: Option<&str>,
|
search: Option<&str>,
|
||||||
|
reverse: bool,
|
||||||
) -> Result<Vec<(i64, String, String)>, StashError> {
|
) -> Result<Vec<(i64, String, String)>, StashError> {
|
||||||
let search_pattern = search.map(|s| {
|
let search_pattern = search.map(|s| {
|
||||||
let escaped = s.replace('!', "!!").replace('%', "!%").replace('_', "!_");
|
let escaped = s.replace('!', "!!").replace('%', "!%").replace('_', "!_");
|
||||||
format!("%{escaped}%")
|
format!("%{escaped}%")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let order = if reverse { "ASC" } else { "DESC" };
|
||||||
let query = match (include_expired, search_pattern.as_deref()) {
|
let query = match (include_expired, search_pattern.as_deref()) {
|
||||||
(true, None) => {
|
(true, None) => {
|
||||||
"SELECT id, contents, mime FROM clipboard ORDER BY \
|
format!(
|
||||||
COALESCE(last_accessed, 0) DESC, id DESC LIMIT ?1 OFFSET ?2"
|
"SELECT id, contents, mime FROM clipboard ORDER BY \
|
||||||
|
COALESCE(last_accessed, 0) {order}, id {order} LIMIT ?1 OFFSET ?2"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
(true, Some(_)) => {
|
(true, Some(_)) => {
|
||||||
"SELECT id, contents, mime FROM clipboard WHERE (LOWER(CAST(contents \
|
format!(
|
||||||
AS TEXT)) LIKE LOWER(?3) ESCAPE '!') ORDER BY COALESCE(last_accessed, \
|
"SELECT id, contents, mime FROM clipboard WHERE \
|
||||||
0) DESC, id DESC LIMIT ?1 OFFSET ?2"
|
(LOWER(CAST(contents AS TEXT)) LIKE LOWER(?3) ESCAPE '!') ORDER BY \
|
||||||
|
COALESCE(last_accessed, 0) {order}, id {order} LIMIT ?1 OFFSET ?2"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
(false, None) => {
|
(false, None) => {
|
||||||
"SELECT id, contents, mime FROM clipboard WHERE (is_expired IS NULL OR \
|
format!(
|
||||||
is_expired = 0) ORDER BY COALESCE(last_accessed, 0) DESC, id DESC \
|
"SELECT id, contents, mime FROM clipboard WHERE (is_expired IS NULL \
|
||||||
LIMIT ?1 OFFSET ?2"
|
OR is_expired = 0) ORDER BY COALESCE(last_accessed, 0) {order}, id \
|
||||||
|
{order} LIMIT ?1 OFFSET ?2"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
(false, Some(_)) => {
|
(false, Some(_)) => {
|
||||||
"SELECT id, contents, mime FROM clipboard WHERE (is_expired IS NULL OR \
|
format!(
|
||||||
is_expired = 0) AND (LOWER(CAST(contents AS TEXT)) LIKE LOWER(?3) \
|
"SELECT id, contents, mime FROM clipboard WHERE (is_expired IS NULL \
|
||||||
ESCAPE '!') ORDER BY COALESCE(last_accessed, 0) DESC, id DESC LIMIT \
|
OR is_expired = 0) AND (LOWER(CAST(contents AS TEXT)) LIKE \
|
||||||
?1 OFFSET ?2"
|
LOWER(?3) ESCAPE '!') ORDER BY COALESCE(last_accessed, 0) {order}, \
|
||||||
|
id {order} LIMIT ?1 OFFSET ?2"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut stmt = self
|
let mut stmt = self
|
||||||
.conn
|
.conn
|
||||||
.prepare(query)
|
.prepare(&query)
|
||||||
.map_err(|e| StashError::ListDecode(e.to_string().into()))?;
|
.map_err(|e| StashError::ListDecode(e.to_string().into()))?;
|
||||||
|
|
||||||
let mut rows = if let Some(pattern) = search_pattern.as_deref() {
|
let mut rows = if let Some(pattern) = search_pattern.as_deref() {
|
||||||
|
|
|
||||||
18
src/main.rs
18
src/main.rs
|
|
@ -91,6 +91,10 @@ enum Command {
|
||||||
/// Show only expired entries (diagnostic, does not remove them)
|
/// Show only expired entries (diagnostic, does not remove them)
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
expired: bool,
|
expired: bool,
|
||||||
|
|
||||||
|
/// Reverse the order of entries (oldest first instead of newest first)
|
||||||
|
#[arg(long)]
|
||||||
|
reverse: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Decode and output clipboard entry by id
|
/// Decode and output clipboard entry by id
|
||||||
|
|
@ -245,16 +249,20 @@ fn main() -> color_eyre::eyre::Result<()> {
|
||||||
"failed to store entry",
|
"failed to store entry",
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
Some(Command::List { format, expired }) => {
|
Some(Command::List {
|
||||||
|
format,
|
||||||
|
expired,
|
||||||
|
reverse,
|
||||||
|
}) => {
|
||||||
match format.as_deref() {
|
match format.as_deref() {
|
||||||
Some("tsv") => {
|
Some("tsv") => {
|
||||||
report_error(
|
report_error(
|
||||||
db.list(io::stdout(), cli.preview_width, expired),
|
db.list(io::stdout(), cli.preview_width, expired, reverse),
|
||||||
"failed to list entries",
|
"failed to list entries",
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
Some("json") => {
|
Some("json") => {
|
||||||
match db.list_json(expired) {
|
match db.list_json(expired, reverse) {
|
||||||
Ok(json) => {
|
Ok(json) => {
|
||||||
println!("{json}");
|
println!("{json}");
|
||||||
},
|
},
|
||||||
|
|
@ -269,12 +277,12 @@ fn main() -> color_eyre::eyre::Result<()> {
|
||||||
None => {
|
None => {
|
||||||
if std::io::stdout().is_terminal() {
|
if std::io::stdout().is_terminal() {
|
||||||
report_error(
|
report_error(
|
||||||
db.list_tui(cli.preview_width, expired),
|
db.list_tui(cli.preview_width, expired, reverse),
|
||||||
"failed to list entries in TUI",
|
"failed to list entries in TUI",
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
report_error(
|
report_error(
|
||||||
db.list(io::stdout(), cli.preview_width, expired),
|
db.list(io::stdout(), cli.preview_width, expired, reverse),
|
||||||
"failed to list entries",
|
"failed to list entries",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue