style: reformat code for improved readability and consistency

This commit is contained in:
2026-07-14 11:39:14 +08:00
parent c78f1b7c08
commit 200c81ce61
23 changed files with 279 additions and 171 deletions
+12 -4
View File
@@ -83,10 +83,18 @@ impl CredFormState {
}
impl FormNav for CredFormState {
fn nav_next(&mut self) { self.next_field(); }
fn nav_prev(&mut self) { self.prev_field(); }
fn active_is_toggle(&self) -> bool { self.active.is_toggle() }
fn active_is_text(&self) -> bool { self.active.is_text() }
fn nav_next(&mut self) {
self.next_field();
}
fn nav_prev(&mut self) {
self.prev_field();
}
fn active_is_toggle(&self) -> bool {
self.active.is_toggle()
}
fn active_is_text(&self) -> bool {
self.active.is_text()
}
}
impl TextEditing for CredFormState {
+12 -4
View File
@@ -212,10 +212,18 @@ impl FormState {
}
impl FormNav for FormState {
fn nav_next(&mut self) { self.next_field(); }
fn nav_prev(&mut self) { self.prev_field(); }
fn active_is_toggle(&self) -> bool { self.active.is_toggle() }
fn active_is_text(&self) -> bool { self.active.is_text() }
fn nav_next(&mut self) {
self.next_field();
}
fn nav_prev(&mut self) {
self.prev_field();
}
fn active_is_toggle(&self) -> bool {
self.active.is_toggle()
}
fn active_is_text(&self) -> bool {
self.active.is_text()
}
}
impl TextEditing for FormState {
+2 -1
View File
@@ -46,7 +46,8 @@ impl App {
self.session.import.candidates = crate::import::load_candidates(&self.config)?;
self.session.import.selected = vec![false; self.session.import.candidates.len()];
self.session.import.shell_candidates = self.config.local_shell_candidates();
self.session.import.shell_selected = vec![false; self.session.import.shell_candidates.len()];
self.session.import.shell_selected =
vec![false; self.session.import.shell_candidates.len()];
self.session.import.cursor = 0;
self.session.mode = Mode::ImportSelector;
Ok(())
+24 -8
View File
@@ -20,7 +20,11 @@ pub enum SettingsField {
impl SettingsField {
pub fn visible_fields(backend: SyncBackend) -> Vec<SettingsField> {
let mut fields = vec![SettingsField::SyncPassword, SettingsField::Backend, SettingsField::SyncOnStart];
let mut fields = vec![
SettingsField::SyncPassword,
SettingsField::Backend,
SettingsField::SyncOnStart,
];
match backend {
SyncBackend::Gist => fields.push(SettingsField::GistId),
SyncBackend::Webdav => {
@@ -43,7 +47,7 @@ impl SettingsField {
Self::WebdavUrl => "URL",
Self::WebdavUser => "Username",
Self::WebdavPassword => "Password",
}
}
}
pub fn is_toggle(self) -> bool {
@@ -110,10 +114,18 @@ impl SettingsState {
}
impl FormNav for SettingsState {
fn nav_next(&mut self) { self.next_field(); }
fn nav_prev(&mut self) { self.prev_field(); }
fn active_is_toggle(&self) -> bool { self.active.is_toggle() }
fn active_is_text(&self) -> bool { self.active.is_text() }
fn nav_next(&mut self) {
self.next_field();
}
fn nav_prev(&mut self) {
self.prev_field();
}
fn active_is_toggle(&self) -> bool {
self.active.is_toggle()
}
fn active_is_text(&self) -> bool {
self.active.is_text()
}
}
impl Default for SettingsState {
@@ -178,8 +190,12 @@ impl App {
self.config.settings.webdav_url.clone().unwrap_or_default();
self.session.settings.webdav_user =
self.config.settings.webdav_user.clone().unwrap_or_default();
self.session.settings.webdav_password =
self.config.settings.webdav_password.clone().unwrap_or_default();
self.session.settings.webdav_password = self
.config
.settings
.webdav_password
.clone()
.unwrap_or_default();
self.session.settings.active = SettingsField::SyncPassword;
self.session.settings.cursor = char_len(self.session.settings.active_text());
self.session.mode = Mode::Settings;
-1
View File
@@ -251,7 +251,6 @@ impl Default for CredentialSession {
}
}
#[cfg(test)]
mod tests {
use super::*;
+6 -11
View File
@@ -1,4 +1,6 @@
use crate::config::{ConnectionType, CredentialEntry, SshellConfig, SyncBackend, config_path, find_binary};
use crate::config::{
ConnectionType, CredentialEntry, SshellConfig, SyncBackend, config_path, find_binary,
};
use crate::sync;
use crate::{connection, import, ui};
use anyhow::{Context, Result, bail};
@@ -19,14 +21,10 @@ pub struct Cli {
#[derive(Debug, Subcommand)]
enum Command {
Tui,
Connect {
name: String,
},
Connect { name: String },
Import,
Sync,
Doctor {
name: Option<String>,
},
Doctor { name: Option<String> },
ConfigPath,
}
pub fn run() -> Result<()> {
@@ -138,10 +136,7 @@ fn check_connection(
"ssh command: ssh -o StrictHostKeyChecking=accept-new -p {port} {user}@{host}"
);
}
ConnectionType::Shell {
command,
..
} => {
ConnectionType::Shell { command, .. } => {
println!("type: shell");
let merged_args = profile.merged_shell_args();
println!("command: {command} {}", merged_args.join(" "));
+5 -1
View File
@@ -253,7 +253,11 @@ impl ConnectionProfile {
/// Returns an empty vec for SSH connections.
pub fn merged_shell_args(&self) -> Vec<String> {
match &self.kind {
ConnectionType::Shell { sync_args, local_args, .. } => {
ConnectionType::Shell {
sync_args,
local_args,
..
} => {
let mut out = sync_args.clone();
out.extend(local_args.iter().cloned());
out
+20 -12
View File
@@ -1,4 +1,6 @@
use super::{ConnectionProfile, ConnectionSource, ConnectionType, ShellCandidate, ShellScanConflict};
use super::{
ConnectionProfile, ConnectionSource, ConnectionType, ShellCandidate, ShellScanConflict,
};
use anyhow::{Result, bail};
#[cfg(unix)]
use std::fs;
@@ -44,13 +46,13 @@ impl super::SshellConfig {
}) {
continue;
}
let conflict = self
.connections
.contains_key(&format!("${name}"))
.then(|| ShellScanConflict {
name: name.clone(),
path: wsl_path.clone(),
});
let conflict =
self.connections
.contains_key(&format!("${name}"))
.then(|| ShellScanConflict {
name: name.clone(),
path: wsl_path.clone(),
});
out.push(ShellCandidate {
name,
path: wsl_path.clone(),
@@ -176,7 +178,11 @@ fn local_shell_paths() -> Vec<PathBuf> {
let system_root = std::env::var_os("SystemRoot").unwrap_or_else(|| r"C:\Windows".into());
for path in [
PathBuf::from(&system_root).join("System32").join("WindowsPowerShell").join("v1.0").join("powershell.exe"),
PathBuf::from(&system_root)
.join("System32")
.join("WindowsPowerShell")
.join("v1.0")
.join("powershell.exe"),
PathBuf::from(&system_root).join("System32").join("cmd.exe"),
] {
if path.is_file() && !out.iter().any(|existing| same_file_name(existing, &path)) {
@@ -199,7 +205,8 @@ fn local_shell_paths() -> Vec<PathBuf> {
#[cfg(not(unix))]
fn same_file_name(a: &Path, b: &Path) -> bool {
a.file_name().is_some_and(|a_name| {
b.file_name().is_some_and(|b_name| a_name.eq_ignore_ascii_case(b_name))
b.file_name()
.is_some_and(|b_name| a_name.eq_ignore_ascii_case(b_name))
})
}
@@ -252,7 +259,9 @@ fn wsl_distributions() -> Vec<String> {
if raw.len() < 2 {
return Vec::new();
}
let u16_iter = raw.chunks_exact(2).map(|c| u16::from_le_bytes([c[0], c[1]]));
let u16_iter = raw
.chunks_exact(2)
.map(|c| u16::from_le_bytes([c[0], c[1]]));
let decoded = String::from_utf16_lossy(&u16_iter.collect::<Vec<u16>>());
decoded
.lines()
@@ -262,4 +271,3 @@ fn wsl_distributions() -> Vec<String> {
.filter(|line| !line.is_empty())
.collect()
}
+7 -8
View File
@@ -9,7 +9,8 @@ fn require_binary(name: &str) -> Result<()> {
return Ok(());
}
let hint = match name {
"ssh" => "\n\
"ssh" => {
"\n\
sshell requires `ssh` to connect via SSH.\n\
\n\
Install it with:\n\
@@ -18,8 +19,9 @@ fn require_binary(name: &str) -> Result<()> {
Arch: sudo pacman -S openssh\n\
Fedora: sudo dnf install openssh-clients\n\
Windows: Settings → Apps → Optional Features → OpenSSH Client"
,
"sshpass" => "\n\
}
"sshpass" => {
"\n\
Password-based SSH login requires `sshpass`.\n\
Consider switching to private-key auth instead, or install it:\n\
macOS: brew install hudochenkov/sshpass/sshpass\n\
@@ -27,7 +29,7 @@ fn require_binary(name: &str) -> Result<()> {
Arch: sudo pacman -S sshpass\n\
Fedora: sudo dnf install sshpass\n\
Windows: not available — use private-key auth"
,
}
_ => "",
};
bail!("command not found: `{name}`{hint}");
@@ -47,10 +49,7 @@ pub fn connect(name: &str, cfg: &SshellConfig) -> Result<()> {
auth_ref,
..
} => connect_ssh(cfg, host, *port, user, auth_ref),
ConnectionType::Shell {
command,
..
} => {
ConnectionType::Shell { command, .. } => {
let merged_args = profile.merged_shell_args();
exec_shell(command, &merged_args)
}
+3 -4
View File
@@ -88,10 +88,9 @@ pub fn import_candidates(cfg: &mut SshellConfig, candidates: &[ImportCandidate])
} else {
match key_content {
Some(content) => {
cfg.credentials.entries.insert(
auth_ref.clone(),
CredentialEntry::private_key(content),
);
cfg.credentials
.entries
.insert(auth_ref.clone(), CredentialEntry::private_key(content));
tags.push("key".to_string());
}
None => {
+8 -20
View File
@@ -56,10 +56,7 @@ struct RemotePayload {
deleted: IndexMap<String, u64>,
}
fn parse_remote_payload(
remote: toml::Value,
sync_password: Option<&str>,
) -> Result<RemotePayload> {
fn parse_remote_payload(remote: toml::Value, sync_password: Option<&str>) -> Result<RemotePayload> {
let mut connections = IndexMap::new();
if let Some(conns) = remote.get("connections").and_then(|v| v.as_table()) {
for (name, profile_val) in conns {
@@ -124,9 +121,7 @@ pub(crate) fn bidirectional_merge(
report.skipped += 1;
}
}
Some(local_profile)
if remote_profile.modified_at > local_profile.modified_at =>
{
Some(local_profile) if remote_profile.modified_at > local_profile.modified_at => {
// Remote is newer → update local (preserve local-only fields)
let mut p = remote_profile.clone();
p.local_tags = local_profile.local_tags.clone();
@@ -148,12 +143,7 @@ pub(crate) fn bidirectional_merge(
}
if let ConnectionType::Shell { .. } = &p.kind {
// Re-localize the shell profile for this machine
let preserved = (
p.local_tags.clone(),
p.usage_count,
p.added_order,
p.source,
);
let preserved = (p.local_tags.clone(), p.usage_count, p.added_order, p.source);
if localize_shell_profile(cfg, name, &mut p) {
p.local_tags = preserved.0;
p.usage_count = preserved.1;
@@ -290,9 +280,10 @@ pub(crate) fn build_sync_payload(
.filter_map(|(_, profile)| profile.auth_ref().map(String::from))
.collect();
payload.credentials.entries.retain(|name, _| {
name != GIST_TOKEN_REF && synced_refs.iter().any(|r| r == name)
});
payload
.credentials
.entries
.retain(|name, _| name != GIST_TOKEN_REF && synced_refs.iter().any(|r| r == name));
let encrypted = if !payload.credentials.entries.is_empty() {
if let Some(pw) = sync_password {
@@ -365,10 +356,7 @@ pub(crate) fn build_sync_payload(
}
pub(crate) fn count_synced(cfg: &SshellConfig) -> usize {
cfg.connections
.iter()
.filter(|(_, p)| p.sync())
.count()
cfg.connections.iter().filter(|(_, p)| p.sync()).count()
}
pub(crate) fn to_toml_value<T: serde::Serialize>(val: &T) -> Result<toml::Value> {
+2 -4
View File
@@ -1,5 +1,5 @@
use super::{GIST_TOKEN_REF, SyncReport, bidirectional_merge, build_sync_payload, count_synced};
use crate::config::{CredentialEntry, SshellConfig};
use super::{GIST_TOKEN_REF, SyncReport, build_sync_payload, bidirectional_merge, count_synced};
use anyhow::{Context, Result, bail};
use reqwest::blocking::Client;
use serde_json::json;
@@ -20,9 +20,7 @@ pub fn sync(cfg: &mut SshellConfig) -> Result<SyncReport> {
if response.status().is_success() {
let value: serde_json::Value = response.json()?;
if let Some(content) = value["files"][FILE_NAME]["content"].as_str() {
Some(
toml::from_str(content).with_context(|| "failed to parse remote config")?,
)
Some(toml::from_str(content).with_context(|| "failed to parse remote config")?)
} else {
None
}
+2 -5
View File
@@ -1,5 +1,5 @@
use crate::config::SshellConfig;
use super::{SyncReport, bidirectional_merge, build_sync_payload, count_synced};
use crate::config::SshellConfig;
use anyhow::{Context, Result, bail};
use reqwest::blocking::Client;
use reqwest::header::{ACCEPT, CONTENT_TYPE};
@@ -29,10 +29,7 @@ pub fn sync(cfg: &mut SshellConfig) -> Result<SyncReport> {
.send()?;
if response.status().is_success() {
let content = response.text()?;
Some(
toml::from_str(&content)
.with_context(|| "failed to parse remote config")?,
)
Some(toml::from_str(&content).with_context(|| "failed to parse remote config")?)
} else {
None
}
+6 -1
View File
@@ -54,7 +54,12 @@ pub fn draw(frame: &mut Frame<'_>, app: &mut crate::app::App) {
.fg(ACCENT)
.alignment(Alignment::Center)
.render(
Rect { x: 0, y: area.height / 2, width: area.width, height: 1 },
Rect {
x: 0,
y: area.height / 2,
width: area.width,
height: 1,
},
frame.buffer_mut(),
);
return;
+14 -8
View File
@@ -132,10 +132,13 @@ fn spawn_latency_probes(app: &App) {
// Mark as "in-flight" by inserting a fresh entry
{
let mut cache = app.session.latency.lock().unwrap();
cache.insert(key.clone(), CacheEntry {
status: crate::app::latency::LatencyStatus::Unknown,
checked_at: now,
});
cache.insert(
key.clone(),
CacheEntry {
status: crate::app::latency::LatencyStatus::Unknown,
checked_at: now,
},
);
}
let cache_clone = app.session.latency.clone();
let host_port = key.clone();
@@ -147,10 +150,13 @@ fn spawn_latency_probes(app: &App) {
};
let status = crate::app::latency::probe(host, port);
if let Ok(mut cache) = cache_clone.lock() {
cache.insert(host_port, CacheEntry {
status,
checked_at: Instant::now(),
});
cache.insert(
host_port,
CacheEntry {
status,
checked_at: Instant::now(),
},
);
}
});
}
+52 -13
View File
@@ -74,23 +74,62 @@ pub fn handle_form_nav<F: FormNav>(form: &mut F, key: KeyEvent) -> Option<FormAc
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
match key.code {
KeyCode::Down => { form.nav_next(); None }
KeyCode::Up => { form.nav_prev(); None }
KeyCode::Down => {
form.nav_next();
None
}
KeyCode::Up => {
form.nav_prev();
None
}
KeyCode::Tab if form.active_is_toggle() => Some(FormAction::Toggle),
KeyCode::Enter => Some(FormAction::Save),
KeyCode::Esc => Some(FormAction::Cancel),
KeyCode::Backspace if form.active_is_text() => { form.delete_char(); None }
KeyCode::Delete if form.active_is_text() => { form.delete_next_char(); None }
KeyCode::Left if form.active_is_text() => { form.move_cursor_left(); None }
KeyCode::Right if form.active_is_text() => { form.move_cursor_right(); None }
KeyCode::Home if form.active_is_text() => { form.cursor_home(); None }
KeyCode::End if form.active_is_text() => { form.cursor_end(); None }
KeyCode::Char('a') if ctrl && form.active_is_text() => { form.cursor_home(); None }
KeyCode::Char('e') if ctrl && form.active_is_text() => { form.cursor_end(); None }
KeyCode::Char('u') if ctrl && form.active_is_text() => { form.clear_field(); None }
KeyCode::Char(' ') if form.active_is_text() => { form.insert_char(' '); None }
KeyCode::Char(c) if !ctrl && form.active_is_text() => { form.insert_char(c); None }
KeyCode::Backspace if form.active_is_text() => {
form.delete_char();
None
}
KeyCode::Delete if form.active_is_text() => {
form.delete_next_char();
None
}
KeyCode::Left if form.active_is_text() => {
form.move_cursor_left();
None
}
KeyCode::Right if form.active_is_text() => {
form.move_cursor_right();
None
}
KeyCode::Home if form.active_is_text() => {
form.cursor_home();
None
}
KeyCode::End if form.active_is_text() => {
form.cursor_end();
None
}
KeyCode::Char('a') if ctrl && form.active_is_text() => {
form.cursor_home();
None
}
KeyCode::Char('e') if ctrl && form.active_is_text() => {
form.cursor_end();
None
}
KeyCode::Char('u') if ctrl && form.active_is_text() => {
form.clear_field();
None
}
KeyCode::Char(' ') if form.active_is_text() => {
form.insert_char(' ');
None
}
KeyCode::Char(c) if !ctrl && form.active_is_text() => {
form.insert_char(c);
None
}
_ => None,
}
}
+4 -6
View File
@@ -24,13 +24,11 @@ const ACTIONS: &[(&str, &str)] = &[
pub struct ActionMenuView;
impl View for ActionMenuView {
fn title(&self) -> &'static str { "Actions" }
fn title(&self) -> &'static str {
"Actions"
}
fn hints(&self) -> Vec<(&'static str, &'static str)> {
vec![
("j/k", "move"),
("Enter", "select"),
("Esc", "cancel"),
]
vec![("j/k", "move"), ("Enter", "select"), ("Esc", "cancel")]
}
fn draw(&self, frame: &mut Frame<'_>, _app: &App, area: Rect) {
+13 -10
View File
@@ -11,7 +11,9 @@ use ratatui::{Frame, layout::Rect};
pub struct CredFormView;
impl View for CredFormView {
fn title(&self) -> &'static str { "Cred Editor" }
fn title(&self) -> &'static str {
"Cred Editor"
}
fn hints(&self) -> Vec<(&'static str, &'static str)> {
vec![
("↑/↓", "move"),
@@ -57,16 +59,17 @@ impl View for CredFormView {
});
} else {
let raw = form.field_value(field).to_string();
let (display, secret_cursor) = if matches!(field, CredFormField::Value) && !raw.is_empty() {
if active && matches!(form.kind, AuthKind::Password) {
let d: String = "*".repeat(raw.chars().count());
(d, form.cursor)
let (display, secret_cursor) =
if matches!(field, CredFormField::Value) && !raw.is_empty() {
if active && matches!(form.kind, AuthKind::Password) {
let d: String = "*".repeat(raw.chars().count());
(d, form.cursor)
} else {
("<set>".into(), 0)
}
} else {
("<set>".into(), 0)
}
} else {
(raw, form.cursor)
};
(raw, form.cursor)
};
let cursor = if active && field.is_text() {
secret_cursor.min(char_len(&display))
} else {
+3 -1
View File
@@ -17,7 +17,9 @@ use ratatui::{
pub struct CredListView;
impl View for CredListView {
fn title(&self) -> &'static str { "Credentials" }
fn title(&self) -> &'static str {
"Credentials"
}
fn hints(&self) -> Vec<(&'static str, &'static str)> {
vec![
("j/k", "move"),
+3 -1
View File
@@ -11,7 +11,9 @@ use ratatui::{Frame, layout::Rect};
pub struct FormView;
impl View for FormView {
fn title(&self) -> &'static str { "Editor" }
fn title(&self) -> &'static str {
"Editor"
}
fn hints(&self) -> Vec<(&'static str, &'static str)> {
vec![
("↑/↓", "move"),
+35 -30
View File
@@ -1,12 +1,12 @@
use crate::app::{App, Mode};
use crate::app::display_name;
use crate::app::latency::LatencyStatus;
use crate::app::{App, Mode};
use crate::config::{ConnectionSource, ConnectionType, CredentialEntry};
use crate::ui::component::{badge_span, draw_input, panel, tag_badge};
use crate::ui::{ACCENT, BLUE, GREEN, MUTED, PANEL_ALT, PURPLE, RED, SELECTED_BG, TEXT, YELLOW};
use super::View;
use super::{section_row, scroll_indexed_rows};
use super::{scroll_indexed_rows, section_row};
use anyhow::Result;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
@@ -21,7 +21,9 @@ use ratatui::{
pub struct HomeListView;
impl View for HomeListView {
fn title(&self) -> &'static str { "Home" }
fn title(&self) -> &'static str {
"Home"
}
fn hints(&self) -> Vec<(&'static str, &'static str)> {
vec![
("j/k", "move"),
@@ -226,10 +228,7 @@ fn connection_row(
format!("{user}@{host}:{port}")
}
}
ConnectionType::Shell {
command,
..
} => {
ConnectionType::Shell { command, .. } => {
let merged_args = profile.merged_shell_args();
if merged_args.is_empty() {
command.clone()
@@ -263,16 +262,24 @@ fn connection_row(
};
let badge_style = if selected {
Style::default().fg(badge_color).bg(SELECTED_BG).add_modifier(Modifier::BOLD)
Style::default()
.fg(badge_color)
.bg(SELECTED_BG)
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(crate::ui::BG).bg(badge_color).add_modifier(Modifier::BOLD)
Style::default()
.fg(crate::ui::BG)
.bg(badge_color)
.add_modifier(Modifier::BOLD)
};
Row::new([
Cell::from(format!("{marker} {}", display_name(name))).style(row_style),
Cell::from(Line::from(vec![
Span::styled(format!(" {} ", type_badge), badge_style),
])).style(row_style),
Cell::from(Line::from(vec![Span::styled(
format!(" {} ", type_badge),
badge_style,
)]))
.style(row_style),
Cell::from(target).style(row_style),
Cell::from(ping_text).style(if selected {
Style::default().fg(ping_color).bg(SELECTED_BG)
@@ -312,7 +319,10 @@ pub fn draw_detail_panel(frame: &mut Frame<'_>, app: &App, area: Rect) {
Span::raw(" "),
badge_span(badge, badge_color),
Span::raw(" "),
Span::styled(display_name(name).to_string(), Style::default().fg(TEXT).bold()),
Span::styled(
display_name(name).to_string(),
Style::default().fg(TEXT).bold(),
),
]));
lines.push(Line::from(vec![
Span::raw(" "),
@@ -466,13 +476,11 @@ fn handle_home(app: &mut App, key: KeyEvent) -> Result<()> {
pub struct SearchView;
impl View for SearchView {
fn title(&self) -> &'static str { "Search" }
fn title(&self) -> &'static str {
"Search"
}
fn hints(&self) -> Vec<(&'static str, &'static str)> {
vec![
("type", "filter"),
("j/k", "move"),
("Esc", "close"),
]
vec![("type", "filter"), ("j/k", "move"), ("Esc", "close")]
}
fn draw(&self, frame: &mut Frame<'_>, app: &App, area: Rect) {
@@ -509,13 +517,11 @@ impl View for SearchView {
pub struct QuickSelectView;
impl View for QuickSelectView {
fn title(&self) -> &'static str { "Quick Select" }
fn title(&self) -> &'static str {
"Quick Select"
}
fn hints(&self) -> Vec<(&'static str, &'static str)> {
vec![
("1-9", "connect"),
("Tab", "sort"),
("Esc", "cancel"),
]
vec![("1-9", "connect"), ("Tab", "sort"), ("Esc", "cancel")]
}
fn draw(&self, frame: &mut Frame<'_>, app: &App, area: Rect) {
@@ -567,12 +573,11 @@ impl View for QuickSelectView {
pub struct DeleteConfirmView;
impl View for DeleteConfirmView {
fn title(&self) -> &'static str { "Delete" }
fn title(&self) -> &'static str {
"Delete"
}
fn hints(&self) -> Vec<(&'static str, &'static str)> {
vec![
("Y", "yes"),
("N", "no"),
]
vec![("Y", "yes"), ("N", "no")]
}
fn draw(&self, frame: &mut Frame<'_>, app: &App, area: Rect) {
+35 -10
View File
@@ -3,7 +3,7 @@ use crate::ui::component::{ListAction, handle_list_nav, panel, panel_with_subtit
use crate::ui::{BLUE, GREEN, MUTED, SELECTED_BG, TEXT};
use super::View;
use super::{section_row, scroll_indexed_rows};
use super::{scroll_indexed_rows, section_row};
use anyhow::Result;
use crossterm::event::{KeyCode, KeyEvent};
@@ -17,7 +17,9 @@ use ratatui::{
pub struct ImportView;
impl View for ImportView {
fn title(&self) -> &'static str { "Import" }
fn title(&self) -> &'static str {
"Import"
}
fn hints(&self) -> Vec<(&'static str, &'static str)> {
vec![
("j/k", "move"),
@@ -62,10 +64,19 @@ fn draw_import(frame: &mut Frame<'_>, app: &App, area: Rect) {
for (idx, item) in app.session.import.shell_candidates.iter().enumerate() {
entry_row.push(rows.len());
let selected_row = idx == app.session.import.cursor;
let checked = app.session.import.shell_selected.get(idx).copied().unwrap_or(false);
let checked = app
.session
.import
.shell_selected
.get(idx)
.copied()
.unwrap_or(false);
let has_conflict = item.conflict.is_some();
let style = if selected_row {
Style::default().bg(SELECTED_BG).fg(TEXT).add_modifier(Modifier::BOLD)
Style::default()
.bg(SELECTED_BG)
.fg(TEXT)
.add_modifier(Modifier::BOLD)
} else if has_conflict {
Style::default().fg(MUTED)
} else {
@@ -87,7 +98,8 @@ fn draw_import(frame: &mut Frame<'_>, app: &App, area: Rect) {
Style::default()
};
rows.push(Row::new([
Cell::from(if checked { " [x]" } else { " [ ]" }).style(check_style.patch(check_cell_style)),
Cell::from(if checked { " [x]" } else { " [ ]" })
.style(check_style.patch(check_cell_style)),
Cell::from(item.name.clone()).style(style),
Cell::from(item.path.display().to_string()).style(style),
Cell::from(status).style(style),
@@ -104,9 +116,18 @@ fn draw_import(frame: &mut Frame<'_>, app: &App, area: Rect) {
for (idx, item) in app.session.import.candidates.iter().enumerate() {
entry_row.push(rows.len());
let selected_row = (shell_len + idx) == app.session.import.cursor;
let checked = app.session.import.selected.get(idx).copied().unwrap_or(false);
let checked = app
.session
.import
.selected
.get(idx)
.copied()
.unwrap_or(false);
let style = if selected_row {
Style::default().bg(SELECTED_BG).fg(TEXT).add_modifier(Modifier::BOLD)
Style::default()
.bg(SELECTED_BG)
.fg(TEXT)
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(TEXT)
};
@@ -121,7 +142,8 @@ fn draw_import(frame: &mut Frame<'_>, app: &App, area: Rect) {
Style::default()
};
rows.push(Row::new([
Cell::from(if checked { " [x]" } else { " [ ]" }).style(check_style.patch(check_cell_style)),
Cell::from(if checked { " [x]" } else { " [ ]" })
.style(check_style.patch(check_cell_style)),
Cell::from(item.name.clone()).style(style),
Cell::from(format!("{}@{}:{}", item.user, item.host, item.port)).style(style),
Cell::from(
@@ -219,8 +241,11 @@ fn handle_import(app: &mut App, key: KeyEvent) -> Result<()> {
.get(app.session.import.cursor)
.is_some_and(|c| c.conflict.is_none());
if can_toggle
&& let Some(v) =
app.session.import.shell_selected.get_mut(app.session.import.cursor)
&& let Some(v) = app
.session
.import
.shell_selected
.get_mut(app.session.import.cursor)
{
*v = !*v;
}
+11 -8
View File
@@ -12,7 +12,9 @@ use ratatui::{Frame, layout::Rect};
pub struct SettingsView;
impl View for SettingsView {
fn title(&self) -> &'static str { "Settings" }
fn title(&self) -> &'static str {
"Settings"
}
fn hints(&self) -> Vec<(&'static str, &'static str)> {
vec![
("↑/↓", "move"),
@@ -41,11 +43,13 @@ impl View for SettingsView {
SyncBackend::Gist => badge_span("Gist", ACCENT),
SyncBackend::Webdav => badge_span("WebDAV", ORANGE),
},
SettingsField::SyncOnStart => if settings.sync_on_start {
badge_span("on", GREEN)
} else {
badge_span("off", MUTED)
},
SettingsField::SyncOnStart => {
if settings.sync_on_start {
badge_span("on", GREEN)
} else {
badge_span("off", MUTED)
}
}
_ => unreachable!(),
};
rows.push(FormRow::Toggle {
@@ -57,8 +61,7 @@ impl View for SettingsView {
let raw = settings.field_text(field).to_string();
let is_secret = matches!(
field,
SettingsField::SyncPassword
| SettingsField::WebdavPassword
SettingsField::SyncPassword | SettingsField::WebdavPassword
);
let (display, secret_cursor) = if is_secret {
if raw.is_empty() {