Skip to content

Commit

Permalink
Add changes for v0.1.4
Browse files Browse the repository at this point in the history
* Update README
* Prevent panic when clipboard creation fails (fixes #6)
* Replace unwrap calls
* Refactor password table scrollbar
* Fix clippy warning
* Improve help instructions
  • Loading branch information
kardwen committed Nov 20, 2024
1 parent 2819bad commit 48f99c2
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 77 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "passepartui"
description = "A TUI for pass"
version = "0.1.3"
version = "0.1.4"
edition = "2021"
authors = ["Karl Felix Schewe"]
readme = "README.md"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A TUI for pass
I started this project as a way to practice programming in Rust while reading the [Rust Book](https://doc.rust-lang.org/stable/book/title-page.html).
Therefore this project is still in an alpha version, however user interaction is mostly finished.

`passepartui` relies for all decryption operations on [pass](https://www.passwordstore.org/), one-time passwords (OTP) are handled by [`pass-otp`](https://github.com/tadfisher/pass-otp).
`passepartui` relies for all decryption operations on [pass](https://www.passwordstore.org/), one-time passwords (OTP) are handled by [pass-otp](https://github.com/tadfisher/pass-otp).
Currently no functionality for manipulating the password store, e.g. adding or deleting a password, is implemented. For those operations use `pass` directly from your terminal (refer to `man pass`).
More on the current state of development can be found below.

Expand All @@ -36,7 +36,7 @@ The name `passepartui` is a combination of "passepartout", French for "master ke
`passepartui` can be found on crates.io [here](https://crates.io/crates/passepartui).

```sh
cargo install passepartui
cargo install passepartui --locked
```

Type `passepartui` to run the app (provided that `~/.cargo/bin` has been added to `$PATH`).
Expand Down
2 changes: 1 addition & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl<'a> App<'a> {
}

fn handle_events(&mut self) -> Result<()> {
if event::poll(self.tick_rate).unwrap() {
if event::poll(self.tick_rate)? {
if let Ok(terminal_event) = event::read() {
match terminal_event {
TerminalEvent::Key(event) if event.kind == KeyEventKind::Press => {
Expand Down
10 changes: 5 additions & 5 deletions src/components/help_popup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ impl<'a> Widget for &mut HelpPopup<'a> {
let text = vec![
Line::from("Navigation".fg(theme.debug).italic()),
Line::default(),
Line::from("(↓), (↑), (j), (k) Select list entry".fg(theme.standard_fg)),
Line::from("(⇣), (⇡), (b), (f) Skip list entries".fg(theme.standard_fg)),
Line::from("(⇱), (g) Select first entry in list".fg(theme.standard_fg)),
Line::from("(⇲), (G) Select last entry in list".fg(theme.standard_fg)),
Line::from("(↓) (↑) (j) (k) Select list entry".fg(theme.standard_fg)),
Line::from("(⇣) (⇡) (f) (b) Skip list entries".fg(theme.standard_fg)),
Line::from("(⇱) (g) Select first entry in list".fg(theme.standard_fg)),
Line::from("(⇲) (G) Select last entry in list".fg(theme.standard_fg)),
Line::default(),
Line::from("(←) (h) (→) (l) (↵) Switch between view modes".fg(theme.standard_fg)),
Line::from("for password list, preview and secrets".fg(theme.standard_fg)),
Expand All @@ -78,7 +78,7 @@ impl<'a> Widget for &mut HelpPopup<'a> {
Line::default(),
Line::from("Search".fg(theme.debug).italic()),
Line::default(),
Line::from("(Esc), (↵) Suspend search".fg(theme.standard_fg)),
Line::from("(Esc) (↵) Suspend search".fg(theme.standard_fg)),
Line::from(
"Pressing (Esc) a second time clears the search and resets the filter."
.fg(theme.standard_fg),
Expand Down
46 changes: 25 additions & 21 deletions src/components/password_details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use ratatui::{
buffer::Buffer,
crossterm::event::MouseEvent,
layout::{Alignment, Constraint, Direction, Flex, Layout, Rect},
style::{Modifier, Style, Stylize},
style::{Style, Stylize},
symbols,
text::Line,
widgets::{Block, Borders, Paragraph, Widget, Wrap},
Expand Down Expand Up @@ -39,9 +39,10 @@ impl PasswordDetails<'_> {
let theme = Theme::new();
let pass_id_field = DetailsField::new(Line::from(vec![
"Password file"
.add_modifier(Modifier::ITALIC | Modifier::UNDERLINED)
.fg(theme.details_field_fg)
.bold(),
.underlined()
.italic()
.bold()
.fg(theme.details_field_fg),
" 🗐".fg(theme.details_field_fg),
]))
.button(
Expand All @@ -53,9 +54,10 @@ impl PasswordDetails<'_> {
);
let lines_field = DetailsField::new(Line::from(vec![
"Number of lines"
.add_modifier(Modifier::ITALIC | Modifier::UNDERLINED)
.fg(theme.details_field_fg)
.bold(),
.underlined()
.italic()
.bold()
.fg(theme.details_field_fg),
" 🗟".fg(theme.details_field_fg),
]))
.button(
Expand All @@ -67,9 +69,10 @@ impl PasswordDetails<'_> {
);
let password_field = DetailsField::new(Line::from(vec![
"Password"
.add_modifier(Modifier::ITALIC | Modifier::UNDERLINED)
.fg(theme.details_field_fg)
.bold(),
.underlined()
.italic()
.bold()
.fg(theme.details_field_fg),
" 🗝".fg(theme.details_field_fg),
]))
.placeholder("********")
Expand All @@ -82,9 +85,10 @@ impl PasswordDetails<'_> {
);
let otp_field = DetailsField::new(Line::from(vec![
"One-time password (OTP)"
.add_modifier(Modifier::ITALIC | Modifier::UNDERLINED)
.fg(theme.details_field_fg)
.bold(),
.underlined()
.italic()
.bold()
.fg(theme.details_field_fg),
" 🕰".fg(theme.details_field_fg),
]))
.placeholder("******")
Expand All @@ -104,9 +108,10 @@ impl PasswordDetails<'_> {
);
let login_field = DetailsField::new(Line::from(vec![
"Login"
.add_modifier(Modifier::ITALIC | Modifier::UNDERLINED)
.fg(theme.details_field_fg)
.bold(),
.underlined()
.italic()
.bold()
.fg(theme.details_field_fg),
" 🨂".fg(theme.details_field_fg),
]))
.button(
Expand Down Expand Up @@ -241,17 +246,16 @@ impl Widget for &mut PasswordDetails<'_> {
}

// One-time password field
if self.one_time_password.is_some() {
if let Some(ref otp) = self.one_time_password {
let field_area = right_areas.next().expect("counted before");
self.otp_field
.set_content(self.one_time_password.as_ref().unwrap());
self.otp_field.set_content(otp);
self.otp_field.render(*field_area, buf);
}

// Login field
if self.login.is_some() {
if let Some(ref login) = self.login {
let field_area = right_areas.next().expect("counted before");
self.login_field.set_content(self.login.as_ref().unwrap());
self.login_field.set_content(login);
self.login_field.render(*field_area, buf);
}
}
Expand Down
65 changes: 39 additions & 26 deletions src/components/password_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub struct PasswordStore<'a> {
status_bar: StatusBar,
event_tx: Sender<ChannelEvent>,
ops_map: HashMap<&'a str, (JoinHandle<()>, String)>,
clipboard: Clipboard,
clipboard: Option<Clipboard>,
pub app_state: AppState,
render_details: bool,
}
Expand Down Expand Up @@ -73,7 +73,7 @@ impl<'a> PasswordStore<'a> {
status_bar: StatusBar::new(),
event_tx,
ops_map: HashMap::new(),
clipboard: Clipboard::new().unwrap(),
clipboard: Clipboard::new().ok(),
app_state: AppState::default(),
render_details: true,
};
Expand Down Expand Up @@ -182,7 +182,7 @@ impl<'a> PasswordStore<'a> {
}

pub fn get_selected_info(&self) -> Option<&PasswordInfo> {
if self.password_subset.len() > 0 {
if !self.password_subset.is_empty() {
return match self.password_table.selected() {
Some(index) => self.passwords.get(self.password_subset[index]),
None => None,
Expand All @@ -194,15 +194,20 @@ impl<'a> PasswordStore<'a> {
fn copy_pass_id(&mut self) {
if let Some(info) = self.get_selected_info() {
let pass_id = info.pass_id();
match self.clipboard.set_text(pass_id) {
Ok(()) => {
let status_text = "Password file identifier copied to clipboard".into();
self.status_bar.display_message(status_text);
}
Err(e) => {
let status_text = format!("Failed to copy password file identifier: {e:?}");
self.status_bar.display_message(status_text);
if let Some(ref mut clipboard) = self.clipboard {
match clipboard.set_text(pass_id) {
Ok(()) => {
let status_text = "Password file identifier copied to clipboard".into();
self.status_bar.display_message(status_text);
}
Err(e) => {
let status_text = format!("Failed to copy password file identifier: {e:?}");
self.status_bar.display_message(status_text);
}
}
} else {
let status_text = String::from("✗ Clipboard not available");
self.status_bar.display_message(status_text);
}
} else {
let status_text = String::from("No entry selected");
Expand All @@ -229,7 +234,7 @@ impl<'a> PasswordStore<'a> {
format!("(pass) {status}")
};
let status_event = ChannelEvent::Status(message);
tx.send(status_event).unwrap();
tx.send(status_event).expect("receiver deallocated");
}

if run_once(
Expand Down Expand Up @@ -266,7 +271,7 @@ impl<'a> PasswordStore<'a> {
format!("✗ (pass) {status}")
};
let status_event = ChannelEvent::Status(message);
tx.send(status_event).unwrap();
tx.send(status_event).expect("receiver deallocated");
}

if run_once(
Expand Down Expand Up @@ -305,7 +310,7 @@ impl<'a> PasswordStore<'a> {
format!("✗ (pass) {status}")
};
let status_event = ChannelEvent::Status(message);
tx.send(status_event).unwrap();
tx.send(status_event).expect("receiver deallocated");
}

if run_once(
Expand Down Expand Up @@ -341,11 +346,13 @@ impl<'a> PasswordStore<'a> {
pass_id,
one_time_password,
})
.unwrap();
tx.send(ChannelEvent::ResetStatus).unwrap();
.expect("receiver deallocated");
tx.send(ChannelEvent::ResetStatus)
.expect("receiver deallocated");
} else {
let message = format!("✗ (pass) {}", String::from_utf8_lossy(&output.stderr));
tx.send(ChannelEvent::Status(message)).unwrap();
tx.send(ChannelEvent::Status(message))
.expect("receiver deallocated");
}
}

Expand Down Expand Up @@ -380,11 +387,13 @@ impl<'a> PasswordStore<'a> {
pass_id,
file_contents,
})
.unwrap();
tx.send(ChannelEvent::ResetStatus).unwrap();
.expect("receiver deallocated");
tx.send(ChannelEvent::ResetStatus)
.expect("receiver deallocated");
} else {
let message = format!("✗ (pass) {}", String::from_utf8_lossy(&output.stderr));
tx.send(ChannelEvent::Status(message)).unwrap();
tx.send(ChannelEvent::Status(message))
.expect("receiver deallocated");
};
}

Expand Down Expand Up @@ -444,8 +453,9 @@ impl<'a> PasswordStore<'a> {
return;
}

if pass_id != self.get_selected_info().unwrap().pass_id {
return;
match self.get_selected_info() {
Some(info) if pass_id == info.pass_id => (),
_ => return,
}

self.file_popup.set_content(&pass_id, &message.clone());
Expand Down Expand Up @@ -735,10 +745,13 @@ impl<'a> Component for PasswordStore<'a> {
Action::DisplayOneTimePassword {
pass_id,
one_time_password,
} if pass_id == self.get_selected_info().unwrap().pass_id => {
self.password_details.one_time_password = Some(one_time_password);
None
}
} => match self.get_selected_info() {
Some(info) if pass_id == info.pass_id => {
self.password_details.one_time_password = Some(one_time_password);
None
}
_ => None,
},
_ => None,
};
Ok(action)
Expand Down
37 changes: 18 additions & 19 deletions src/components/password_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ pub struct PasswordTable<'a> {
length: usize,
table_state: TableState,
pub highlight_pattern: Option<String>,
scrollbar: Scrollbar<'a>,
scrollbar_state: ScrollbarState,
area: Option<Rect>,
mouse_content_area: Option<Rect>,
Expand All @@ -38,24 +37,12 @@ impl<'a> PasswordTable<'a> {
let length = rows.len();
let table = Self::build_table(rows, &theme);
let scrollbar_state = ScrollbarState::new(length);
let scrollbar = Scrollbar::default()
.orientation(ScrollbarOrientation::VerticalRight)
.begin_style(Style::new().bg(theme.table_header_bg))
.track_style(
Style::new()
.fg(theme.table_track_fg)
.bg(theme.table_track_bg),
)
.thumb_style(Style::new().fg(theme.standard_fg).bg(theme.standard_bg))
.begin_symbol(Some(" "))
.end_symbol(None);
Self {
theme,
table,
length,
table_state: TableState::new(),
highlight_pattern: None,
scrollbar,
scrollbar_state,
area: None,
mouse_content_area: None,
Expand Down Expand Up @@ -131,7 +118,7 @@ impl<'a> PasswordTable<'a> {
Cell::from(Line::from(pass_id_parts)),
Cell::from(info.last_modified()),
])
.style(Style::default().bg(bg_color))
.style(Style::default().fg(self.theme.table_row_fg).bg(bg_color))
})
.collect()
} else {
Expand Down Expand Up @@ -207,8 +194,10 @@ impl<'a> PasswordTable<'a> {
impl<'a> Widget for &mut PasswordTable<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.area = Some(area);
let theme = self.theme;

let layout = Layout::horizontal([Constraint::Min(1), Constraint::Length(1)]).split(area);
let [table_area, s_area] =
Layout::horizontal([Constraint::Min(1), Constraint::Length(1)]).areas(area);

// Calculate areas for mouse interaction
let mouse_content_area = Rect {
Expand All @@ -225,10 +214,20 @@ impl<'a> Widget for &mut PasswordTable<'a> {
self.mouse_content_area = Some(mouse_content_area);
self.mouse_scrollbar_area = Some(mouse_scrollbar_area);

StatefulWidget::render(&self.table, layout[0], buf, &mut self.table_state);
self.scrollbar
.clone()
.render(layout[1], buf, &mut self.scrollbar_state);
StatefulWidget::render(&self.table, table_area, buf, &mut self.table_state);

Scrollbar::default()
.orientation(ScrollbarOrientation::VerticalRight)
.begin_style(Style::new().bg(theme.table_header_bg))
.track_style(
Style::new()
.fg(theme.table_track_fg)
.bg(theme.table_track_bg),
)
.thumb_style(Style::new().fg(theme.standard_fg).bg(theme.standard_bg))
.begin_symbol(Some(" "))
.end_symbol(None)
.render(s_area, buf, &mut self.scrollbar_state);
}
}

Expand Down
Loading

0 comments on commit 48f99c2

Please sign in to comment.