commands: prevent usize underflow when navigating empty entry list

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I0432dcc88b22226772f6bb6e05cc64d36a6a6964
This commit is contained in:
raf 2026-01-22 13:28:43 +03:00
commit 3165543580
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF

View file

@ -247,10 +247,13 @@ impl SqliteClipboardDb {
.map_err(|e| StashError::ListDecode(e.to_string().into()))? .map_err(|e| StashError::ListDecode(e.to_string().into()))?
&& let Event::Key(key) = event::read() && let Event::Key(key) = event::read()
.map_err(|e| StashError::ListDecode(e.to_string().into()))? .map_err(|e| StashError::ListDecode(e.to_string().into()))?
{ {
match (key.code, key.modifiers) { match (key.code, key.modifiers) {
(KeyCode::Char('q') | KeyCode::Esc, _) => break, (KeyCode::Char('q') | KeyCode::Esc, _) => break,
(KeyCode::Down | KeyCode::Char('j'), _) => { (KeyCode::Down | KeyCode::Char('j'), _) => {
if entries.is_empty() {
state.select(None);
} else {
let i = match state.selected() { let i = match state.selected() {
Some(i) => { Some(i) => {
if i >= entries.len() - 1 { if i >= entries.len() - 1 {
@ -262,8 +265,12 @@ impl SqliteClipboardDb {
None => 0, None => 0,
}; };
state.select(Some(i)); state.select(Some(i));
}, }
(KeyCode::Up | KeyCode::Char('k'), _) => { },
(KeyCode::Up | KeyCode::Char('k'), _) => {
if entries.is_empty() {
state.select(None);
} else {
let i = match state.selected() { let i = match state.selected() {
Some(i) => { Some(i) => {
if i == 0 { if i == 0 {
@ -275,94 +282,89 @@ impl SqliteClipboardDb {
None => 0, None => 0,
}; };
state.select(Some(i)); state.select(Some(i));
}, }
(KeyCode::Enter, _) => { },
if let Some(idx) = state.selected() (KeyCode::Enter, _) => {
&& let Some((id, ..)) = entries.get(idx) { if let Some(idx) = state.selected()
match self.copy_entry(*id) { && let Some((id, ..)) = entries.get(idx)
Ok((new_id, contents, mime)) => { {
if new_id != *id { match self.copy_entry(*id) {
entries[idx] = ( Ok((new_id, contents, mime)) => {
new_id, if new_id != *id {
entries[idx].1.clone(), entries[idx] = (
entries[idx].2.clone(), new_id,
); entries[idx].1.clone(),
} entries[idx].2.clone(),
let opts = Options::new(); );
let mime_type = match mime { }
Some(ref m) if m == "text/plain" => MimeType::Text, let opts = Options::new();
Some(ref m) => { let mime_type = match mime {
MimeType::Specific(m.clone().to_owned()) Some(ref m) if m == "text/plain" => MimeType::Text,
}, Some(ref m) => MimeType::Specific(m.clone().to_owned()),
None => MimeType::Text, None => MimeType::Text,
}; };
let copy_result = opts.copy( let copy_result = opts
Source::Bytes(contents.clone().into()), .copy(Source::Bytes(contents.clone().into()), mime_type);
mime_type, match copy_result {
); Ok(()) => {
match copy_result {
Ok(()) => {
let _ = Notification::new()
.summary("Stash")
.body("Copied entry to clipboard")
.show();
},
Err(e) => {
log::error!(
"Failed to copy entry to clipboard: {e}"
);
let _ = Notification::new()
.summary("Stash")
.body(&format!(
"Failed to copy to clipboard: {e}"
))
.show();
},
}
},
Err(e) => {
log::error!("Failed to fetch entry {id}: {e}");
let _ = Notification::new() let _ = Notification::new()
.summary("Stash") .summary("Stash")
.body(&format!("Failed to fetch entry: {e}")) .body("Copied entry to clipboard")
.show();
},
Err(e) => {
log::error!("Failed to copy entry to clipboard: {e}");
let _ = Notification::new()
.summary("Stash")
.body(&format!("Failed to copy to clipboard: {e}"))
.show(); .show();
}, },
} }
} },
}, Err(e) => {
(KeyCode::Char('D'), KeyModifiers::SHIFT) => { log::error!("Failed to fetch entry {id}: {e}");
if let Some(idx) = state.selected()
&& let Some((id, ..)) = entries.get(idx) {
// Delete entry from DB
self
.conn
.execute(
"DELETE FROM clipboard WHERE id = ?1",
rusqlite::params![id],
)
.map_err(|e| {
StashError::DeleteEntry(*id, e.to_string().into())
})?;
// Remove from entries and update selection
entries.remove(idx);
let new_len = entries.len();
if new_len == 0 {
state.select(None);
} else if idx >= new_len {
state.select(Some(new_len - 1));
} else {
state.select(Some(idx));
}
// Show notification
let _ = Notification::new() let _ = Notification::new()
.summary("Stash") .summary("Stash")
.body("Deleted entry") .body(&format!("Failed to fetch entry: {e}"))
.show(); .show();
} },
}, }
_ => {}, }
} },
(KeyCode::Char('D'), KeyModifiers::SHIFT) => {
if let Some(idx) = state.selected()
&& let Some((id, ..)) = entries.get(idx)
{
// Delete entry from DB
self
.conn
.execute(
"DELETE FROM clipboard WHERE id = ?1",
rusqlite::params![id],
)
.map_err(|e| {
StashError::DeleteEntry(*id, e.to_string().into())
})?;
// Remove from entries and update selection
entries.remove(idx);
let new_len = entries.len();
if new_len == 0 {
state.select(None);
} else if idx >= new_len {
state.select(Some(new_len - 1));
} else {
state.select(Some(idx));
}
// Show notification
let _ = Notification::new()
.summary("Stash")
.body("Deleted entry")
.show();
}
},
_ => {},
} }
}
} }
Ok(()) Ok(())
})(); })();