huge opti
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2487,6 +2487,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"uuid",
|
||||
"wayland-clipboard-listener",
|
||||
"x11rb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
1
rklipd/Cargo.lock
generated
1
rklipd/Cargo.lock
generated
@ -1445,6 +1445,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"uuid",
|
||||
"wayland-clipboard-listener",
|
||||
"x11rb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@ -15,6 +15,7 @@ serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
base64 = "0.22.1"
|
||||
aes-gcm = "0.10.3"
|
||||
x11rb = "0.13.2"
|
||||
|
||||
[features]
|
||||
x11 = []
|
||||
|
||||
@ -8,6 +8,11 @@ use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use uuid::Uuid;
|
||||
use x11rb::connection::Connection;
|
||||
use x11rb::protocol::Event;
|
||||
use x11rb::protocol::xfixes::{ConnectionExt as XfixesExt, SelectionEventMask};
|
||||
use x11rb::protocol::xproto::{ConnectionExt as XprotoExt, CreateWindowAux, WindowClass};
|
||||
use x11rb::rust_connection::RustConnection;
|
||||
|
||||
const MAX_IMAGE_PIXELS: usize = 3840 * 2160;
|
||||
|
||||
@ -18,81 +23,128 @@ fn hash_bytes(data: &[u8]) -> u64 {
|
||||
}
|
||||
|
||||
pub fn start(db: Arc<Mutex<Database>>, mut clipboard: Clipboard) -> Result<(), Box<dyn Error>> {
|
||||
println!("Clipboard monitor started (X11 polling mode)...");
|
||||
let (conn, screen_num) =
|
||||
RustConnection::connect(None).map_err(|e| format!("Connexion X11 impossible : {e}"))?;
|
||||
|
||||
let root = conn.setup().roots[screen_num].root;
|
||||
|
||||
let win = conn.generate_id()?;
|
||||
conn.create_window(
|
||||
0,
|
||||
win,
|
||||
root,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
WindowClass::INPUT_ONLY,
|
||||
0,
|
||||
&CreateWindowAux::new(),
|
||||
)?
|
||||
.check()?;
|
||||
|
||||
conn.xfixes_query_version(5, 0)
|
||||
.map_err(|e| format!("Extension XFIXES indisponible : {e}"))?
|
||||
.reply()?;
|
||||
|
||||
let clipboard_atom = conn.intern_atom(false, b"CLIPBOARD")?.reply()?.atom;
|
||||
|
||||
conn.xfixes_select_selection_input(
|
||||
win,
|
||||
clipboard_atom,
|
||||
SelectionEventMask::SET_SELECTION_OWNER,
|
||||
)?
|
||||
.check()?;
|
||||
|
||||
conn.flush()?;
|
||||
println!("Clipboard monitor démarré (X11 XFIXES — zéro polling)");
|
||||
|
||||
let mut last_text: Option<String> = None;
|
||||
let mut last_image_hash: Option<u64> = None;
|
||||
|
||||
loop {
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
let event = conn.wait_for_event()?;
|
||||
|
||||
if let Event::XfixesSelectionNotify(_) = event {
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
handle_clipboard_event(&mut clipboard, &db, &mut last_text, &mut last_image_hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_clipboard_event(
|
||||
clipboard: &mut Clipboard,
|
||||
db: &Arc<Mutex<Database>>,
|
||||
last_text: &mut Option<String>,
|
||||
last_image_hash: &mut Option<u64>,
|
||||
) {
|
||||
match clipboard.get_text() {
|
||||
Ok(raw) => {
|
||||
let text = raw.trim_end_matches('\n').to_string();
|
||||
if text.is_empty() || Some(&text) == last_text.as_ref() {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
*last_text = Some(text.clone());
|
||||
*last_image_hash = None;
|
||||
println!("Clipboard update (texte)");
|
||||
|
||||
last_text = Some(text.clone());
|
||||
last_image_hash = None;
|
||||
println!("Clipboard update (text)!");
|
||||
|
||||
let entry = ClipboardEntry {
|
||||
spawn_db_write(
|
||||
Arc::clone(db),
|
||||
ClipboardEntry {
|
||||
content: ClipboardData::Text(text),
|
||||
timestamp: SystemTime::now(),
|
||||
};
|
||||
spawn_db_write(Arc::clone(&db), entry);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Err(_) => {
|
||||
let Ok(img_data) = clipboard.get_image() else {
|
||||
continue;
|
||||
return;
|
||||
};
|
||||
|
||||
let pixel_count = img_data.width * img_data.height;
|
||||
if pixel_count > MAX_IMAGE_PIXELS {
|
||||
eprintln!(
|
||||
"Image ignorée : {}×{} ({} Mpx > limite {}×{})",
|
||||
"Image ignorée : {}×{} ({} Mpx > limite 4K)",
|
||||
img_data.width,
|
||||
img_data.height,
|
||||
pixel_count / 1_000_000,
|
||||
3840,
|
||||
2160
|
||||
pixel_count / 1_000_000
|
||||
);
|
||||
last_image_hash = Some(pixel_count as u64);
|
||||
last_text = None;
|
||||
continue;
|
||||
*last_image_hash = Some(pixel_count as u64);
|
||||
*last_text = None;
|
||||
return;
|
||||
}
|
||||
|
||||
let hash = hash_bytes(&img_data.bytes);
|
||||
if Some(hash) == last_image_hash {
|
||||
continue;
|
||||
if Some(hash) == *last_image_hash {
|
||||
return;
|
||||
}
|
||||
*last_image_hash = Some(hash);
|
||||
*last_text = None;
|
||||
println!("Clipboard update (image)");
|
||||
|
||||
last_image_hash = Some(hash);
|
||||
last_text = None;
|
||||
println!("Clipboard update (image)!");
|
||||
|
||||
let entry = ClipboardEntry {
|
||||
content: ClipboardData::Image(Image {
|
||||
spawn_db_write(
|
||||
Arc::clone(db),
|
||||
ClipboardEntry {
|
||||
content: ClipboardData::Image(crate::models::Image {
|
||||
raw_pixels: Some(img_data.bytes.into_owned()),
|
||||
width: img_data.width as u32,
|
||||
height: img_data.height as u32,
|
||||
id: Uuid::new_v4(),
|
||||
}),
|
||||
timestamp: SystemTime::now(),
|
||||
};
|
||||
spawn_db_write(Arc::clone(&db), entry);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_db_write(db: Arc<Mutex<Database>>, entry: ClipboardEntry) {
|
||||
thread::spawn(move || {
|
||||
let db_lock = db.lock().unwrap();
|
||||
if let Err(e) = db_lock.append(entry) {
|
||||
eprintln!("SQLite writing error: {}", e);
|
||||
let lock = db.lock().unwrap();
|
||||
if let Err(e) = lock.append(entry) {
|
||||
eprintln!("SQLite write error: {e}");
|
||||
} else {
|
||||
println!("SQLite updated!");
|
||||
}
|
||||
|
||||
237
src/app.rs
237
src/app.rs
@ -2,9 +2,11 @@ use crate::crypto::Crypto;
|
||||
use crate::ipc::{self, HistoryItem};
|
||||
use chrono::{Local, NaiveDate, TimeZone};
|
||||
use fuzzy_matcher::{FuzzyMatcher, skim::SkimMatcherV2};
|
||||
use image::DynamicImage;
|
||||
use ratatui::widgets::ListState;
|
||||
use ratatui_image::{picker::Picker, protocol};
|
||||
use regex::Regex;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::time::{Duration, Instant};
|
||||
use syntect::highlighting::ThemeSet;
|
||||
use syntect::parsing::SyntaxSet;
|
||||
@ -12,6 +14,10 @@ use syntect::parsing::SyntaxSet;
|
||||
const PREVIEW_MAX_WIDTH: u32 = 1280;
|
||||
const PREVIEW_MAX_HEIGHT: u32 = 720;
|
||||
|
||||
const IMAGE_CACHE_MAX: usize = 8;
|
||||
|
||||
const PAGE_SIZE: usize = 50;
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum Mode {
|
||||
Normal,
|
||||
@ -48,6 +54,10 @@ pub struct App {
|
||||
pub syntax_set: SyntaxSet,
|
||||
pub theme_set: ThemeSet,
|
||||
pub type_filter: TypeFilter,
|
||||
pub loaded_count: usize,
|
||||
pub has_more: bool,
|
||||
image_cache: HashMap<String, DynamicImage>,
|
||||
image_cache_order: VecDeque<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@ -77,7 +87,8 @@ impl TypeFilter {
|
||||
|
||||
impl App {
|
||||
pub fn new() -> Self {
|
||||
let items = ipc::fetch_history(200).unwrap_or_default();
|
||||
let items = ipc::fetch_history(PAGE_SIZE).unwrap_or_default();
|
||||
let has_more = items.len() == PAGE_SIZE;
|
||||
let mut list_state = ListState::default();
|
||||
list_state.select(if items.is_empty() { None } else { Some(0) });
|
||||
|
||||
@ -107,11 +118,82 @@ impl App {
|
||||
syntax_set: SyntaxSet::load_defaults_newlines(),
|
||||
theme_set: ThemeSet::load_defaults(),
|
||||
type_filter: TypeFilter::All,
|
||||
loaded_count: PAGE_SIZE,
|
||||
has_more,
|
||||
image_cache: HashMap::new(),
|
||||
image_cache_order: VecDeque::new(),
|
||||
};
|
||||
app.update_preview();
|
||||
app
|
||||
}
|
||||
|
||||
fn try_load_more(&mut self) -> bool {
|
||||
if !self.has_more {
|
||||
return false;
|
||||
}
|
||||
|
||||
let new_limit = self.loaded_count + PAGE_SIZE;
|
||||
let Some(items) = ipc::fetch_history(new_limit) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if items.len() <= self.all_items.len() {
|
||||
self.has_more = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
self.has_more = items.len() == new_limit;
|
||||
self.loaded_count = new_limit;
|
||||
|
||||
let selected_content = self.get_selected_item().map(|i| i.content.clone());
|
||||
self.all_items = items;
|
||||
self.update_search();
|
||||
|
||||
if let Some(content) = selected_content {
|
||||
if let Some(pos) = self
|
||||
.filtered_items
|
||||
.iter()
|
||||
.position(|x| x.content == content)
|
||||
{
|
||||
self.list_state.select(Some(pos));
|
||||
self.last_selected_index = None;
|
||||
self.update_preview();
|
||||
}
|
||||
}
|
||||
|
||||
self.set_status(format!("{} entrées chargées", self.all_items.len()));
|
||||
true
|
||||
}
|
||||
|
||||
fn get_cached_image(
|
||||
&mut self,
|
||||
filename: &str,
|
||||
base_dir: &std::path::Path,
|
||||
) -> Option<DynamicImage> {
|
||||
if !self.image_cache.contains_key(filename) {
|
||||
let path = base_dir.join("images").join(filename);
|
||||
if !path.exists() {
|
||||
return None;
|
||||
}
|
||||
let img = image::open(&path).ok()?;
|
||||
let img = if img.width() > PREVIEW_MAX_WIDTH || img.height() > PREVIEW_MAX_HEIGHT {
|
||||
img.thumbnail(PREVIEW_MAX_WIDTH, PREVIEW_MAX_HEIGHT)
|
||||
} else {
|
||||
img
|
||||
};
|
||||
|
||||
if self.image_cache.len() >= IMAGE_CACHE_MAX {
|
||||
if let Some(oldest) = self.image_cache_order.pop_front() {
|
||||
self.image_cache.remove(&oldest);
|
||||
}
|
||||
}
|
||||
self.image_cache_order.push_back(filename.to_string());
|
||||
self.image_cache.insert(filename.to_string(), img);
|
||||
}
|
||||
|
||||
self.image_cache.get(filename).cloned()
|
||||
}
|
||||
|
||||
pub fn format_timestamp(ts_ms: i64) -> String {
|
||||
let secs = ts_ms / 1000;
|
||||
let nsecs = ((ts_ms % 1000) * 1_000_000) as u32;
|
||||
@ -222,14 +304,59 @@ impl App {
|
||||
self.update_preview();
|
||||
}
|
||||
|
||||
pub fn next(&mut self) {
|
||||
if self.filtered_items.is_empty() {
|
||||
return;
|
||||
}
|
||||
let current = self.list_state.selected().unwrap_or(0);
|
||||
let last = self.filtered_items.len() - 1;
|
||||
|
||||
if current >= last {
|
||||
if self.try_load_more() {
|
||||
// try_load_more restaure la sélection sur le même item ;
|
||||
// on peut maintenant avancer d'un cran
|
||||
let current = self.list_state.selected().unwrap_or(0);
|
||||
if current + 1 < self.filtered_items.len() {
|
||||
self.list_state.select(Some(current + 1));
|
||||
self.update_preview();
|
||||
}
|
||||
// Sinon (le filtre actif masque les nouveaux items) : on reste
|
||||
} else {
|
||||
// Fin réelle — wrap vers le haut
|
||||
self.list_state.select(Some(0));
|
||||
self.update_preview();
|
||||
}
|
||||
} else {
|
||||
self.list_state.select(Some(current + 1));
|
||||
self.update_preview();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn previous(&mut self) {
|
||||
if self.filtered_items.is_empty() {
|
||||
return;
|
||||
}
|
||||
let i = self.list_state.selected().map_or(0, |i| {
|
||||
if i == 0 {
|
||||
self.filtered_items.len() - 1
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
});
|
||||
self.list_state.select(Some(i));
|
||||
self.update_preview();
|
||||
}
|
||||
|
||||
pub fn delete_selected(&mut self) {
|
||||
if let Some(i) = self.list_state.selected() {
|
||||
if i < self.filtered_items.len() {
|
||||
let item = self.filtered_items.remove(i);
|
||||
// On stocke juste l'item (plus l'index filtré qui était faux)
|
||||
self.undo_stack.push(item.clone());
|
||||
self.all_items.retain(|x| x.content != item.content);
|
||||
|
||||
if item.content.ends_with(".jpg") || item.content.ends_with(".png") {
|
||||
self.image_cache.remove(&item.content);
|
||||
self.image_cache_order.retain(|k| k != &item.content);
|
||||
}
|
||||
let new_sel = if self.filtered_items.is_empty() {
|
||||
None
|
||||
} else if i >= self.filtered_items.len() {
|
||||
@ -246,15 +373,14 @@ impl App {
|
||||
pub fn undo_delete(&mut self) {
|
||||
if let Some(item) = self.undo_stack.pop() {
|
||||
ipc::add_entry(item.content.clone());
|
||||
|
||||
if let Some(new_items) = ipc::fetch_history(200) {
|
||||
// Re-sync depuis le daemon pour avoir l'ordre chronologique correct
|
||||
if let Some(new_items) = ipc::fetch_history(self.loaded_count) {
|
||||
self.has_more = new_items.len() == self.loaded_count;
|
||||
self.all_items = new_items;
|
||||
} else {
|
||||
self.all_items.insert(0, item.clone());
|
||||
}
|
||||
|
||||
self.update_search();
|
||||
|
||||
if let Some(pos) = self
|
||||
.filtered_items
|
||||
.iter()
|
||||
@ -272,16 +398,13 @@ impl App {
|
||||
Some(i) => i.content.clone(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
if Crypto::is_legacy_encrypted(&content) {
|
||||
self.set_error(
|
||||
"Entrée chiffrée avec l'ancienne clé machine — non modifiable ici".into(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
self.crypto = None;
|
||||
|
||||
if Crypto::is_password_encrypted(&content) {
|
||||
self.pending_action = Some(PendingAction::DecryptSelected);
|
||||
} else {
|
||||
@ -319,14 +442,12 @@ impl App {
|
||||
Some(i) => i.content.clone(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
let encrypt_result = match &self.crypto {
|
||||
Some(key) => key.encrypt(&content),
|
||||
let result = match &self.crypto {
|
||||
Some(k) => k.encrypt(&content),
|
||||
None => return,
|
||||
};
|
||||
self.crypto = None;
|
||||
|
||||
match encrypt_result {
|
||||
match result {
|
||||
Ok(enc) => {
|
||||
if ipc::update_entry(content.clone(), enc.clone()) {
|
||||
self.replace_content(&content, enc);
|
||||
@ -344,14 +465,12 @@ impl App {
|
||||
Some(i) => i.content.clone(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
let decrypt_result = match &self.crypto {
|
||||
Some(key) => key.decrypt(&content),
|
||||
let result = match &self.crypto {
|
||||
Some(k) => k.decrypt(&content),
|
||||
None => return,
|
||||
};
|
||||
self.crypto = None;
|
||||
|
||||
match decrypt_result {
|
||||
match result {
|
||||
Ok(plain) => {
|
||||
if ipc::update_entry(content.clone(), plain.clone()) {
|
||||
self.replace_content(&content, plain);
|
||||
@ -360,11 +479,9 @@ impl App {
|
||||
self.set_error("Erreur mise à jour BDD".into());
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
self.set_error(
|
||||
Err(_) => self.set_error(
|
||||
"Déchiffrement échoué — mauvais mot de passe. Réessayez avec [e].".into(),
|
||||
);
|
||||
}
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@ -373,14 +490,12 @@ impl App {
|
||||
Some(i) => i.content.clone(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
let decrypt_result = match &self.crypto {
|
||||
Some(key) => key.decrypt(&content),
|
||||
let result = match &self.crypto {
|
||||
Some(k) => k.decrypt(&content),
|
||||
None => return,
|
||||
};
|
||||
self.crypto = None;
|
||||
|
||||
match decrypt_result {
|
||||
match result {
|
||||
Ok(plain) => {
|
||||
if ipc::set_clipboard(plain) {
|
||||
self.should_quit = true;
|
||||
@ -388,11 +503,9 @@ impl App {
|
||||
self.set_error("Erreur : impossible de définir le presse-papier".into());
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
self.set_error(
|
||||
Err(_) => self.set_error(
|
||||
"Déchiffrement échoué — mauvais mot de passe. Réessayez avec Entrée.".into(),
|
||||
);
|
||||
}
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@ -430,6 +543,10 @@ impl App {
|
||||
self.undo_stack.clear();
|
||||
self.list_state.select(None);
|
||||
self.current_image = None;
|
||||
self.image_cache.clear();
|
||||
self.image_cache_order.clear();
|
||||
self.loaded_count = PAGE_SIZE;
|
||||
self.has_more = false;
|
||||
self.set_status("Historique effacé".into());
|
||||
} else {
|
||||
self.set_error("Erreur lors de l'effacement".into());
|
||||
@ -452,22 +569,14 @@ impl App {
|
||||
|
||||
if content.ends_with(".jpg") || content.ends_with(".png") {
|
||||
if let Some(dirs) = directories::ProjectDirs::from("com", "zefad", "rklipd") {
|
||||
let path = dirs.data_dir().join("images").join(&content);
|
||||
if path.exists() {
|
||||
if let Ok(img) = image::open(&path) {
|
||||
let img = if img.width() > PREVIEW_MAX_WIDTH
|
||||
|| img.height() > PREVIEW_MAX_HEIGHT
|
||||
{
|
||||
img.thumbnail(PREVIEW_MAX_WIDTH, PREVIEW_MAX_HEIGHT)
|
||||
} else {
|
||||
img
|
||||
};
|
||||
if let Some(img) = self.get_cached_image(&content, dirs.data_dir()) {
|
||||
// new_resize_protocol attend un DynamicImage — on clone depuis le cache
|
||||
// (l'image est déjà redimensionnée ≤ 1280×720, clone = ~3,5 Mo max)
|
||||
self.current_image = Some(self.picker.new_resize_protocol(img));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_preview_down(&mut self) {
|
||||
self.preview_scroll = self.preview_scroll.saturating_add(3);
|
||||
@ -476,36 +585,6 @@ impl App {
|
||||
self.preview_scroll = self.preview_scroll.saturating_sub(3);
|
||||
}
|
||||
|
||||
pub fn next(&mut self) {
|
||||
if self.filtered_items.is_empty() {
|
||||
return;
|
||||
}
|
||||
let i = self.list_state.selected().map_or(0, |i| {
|
||||
if i >= self.filtered_items.len() - 1 {
|
||||
0
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
});
|
||||
self.list_state.select(Some(i));
|
||||
self.update_preview();
|
||||
}
|
||||
|
||||
pub fn previous(&mut self) {
|
||||
if self.filtered_items.is_empty() {
|
||||
return;
|
||||
}
|
||||
let i = self.list_state.selected().map_or(0, |i| {
|
||||
if i == 0 {
|
||||
self.filtered_items.len() - 1
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
});
|
||||
self.list_state.select(Some(i));
|
||||
self.update_preview();
|
||||
}
|
||||
|
||||
pub fn get_selected_item(&self) -> Option<&HistoryItem> {
|
||||
self.list_state
|
||||
.selected()
|
||||
@ -513,7 +592,13 @@ impl App {
|
||||
}
|
||||
|
||||
pub fn sync_with_daemon(&mut self) {
|
||||
if let Some(new) = ipc::fetch_history(200) {
|
||||
let Some(new) = ipc::fetch_history(self.loaded_count) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Mise à jour du flag has_more lors de chaque sync
|
||||
self.has_more = new.len() == self.loaded_count;
|
||||
|
||||
let changed = self.all_items.len() != new.len()
|
||||
|| self
|
||||
.all_items
|
||||
@ -523,7 +608,6 @@ impl App {
|
||||
|
||||
if changed {
|
||||
let selected_content = self.get_selected_item().map(|i| i.content.clone());
|
||||
|
||||
self.all_items = new;
|
||||
self.update_search();
|
||||
|
||||
@ -540,7 +624,6 @@ impl App {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_error(&mut self, msg: String) {
|
||||
self.error_message = Some((msg, Instant::now()));
|
||||
|
||||
11
src/main.rs
11
src/main.rs
@ -58,11 +58,7 @@ fn run_app(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>, app: &mut App)
|
||||
}
|
||||
|
||||
match app.mode {
|
||||
// Dans la boucle, Mode::Normal :
|
||||
Mode::Normal => {
|
||||
// FIX: les deux blocs match étaient redondants et mal nommés.
|
||||
// On fusionne la logique en un seul bloc clair.
|
||||
match key.code {
|
||||
Mode::Normal => match key.code {
|
||||
KeyCode::Enter => app.paste_selected(),
|
||||
KeyCode::Char('j') | KeyCode::Down => app.next(),
|
||||
KeyCode::Char('k') | KeyCode::Up => app.previous(),
|
||||
@ -129,8 +125,7 @@ fn run_app(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>, app: &mut App)
|
||||
last_d = false;
|
||||
last_g = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Mode::Search => match key.code {
|
||||
KeyCode::Esc => {
|
||||
@ -169,7 +164,6 @@ fn run_app(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>, app: &mut App)
|
||||
match cmd.as_str() {
|
||||
"q" | "quit" => app.should_quit = true,
|
||||
"clear" => app.clear_history(),
|
||||
// :p pour définir/changer le mot de passe
|
||||
"p" | "password" => {
|
||||
app.pending_action = None;
|
||||
app.mode = Mode::PasswordInput;
|
||||
@ -215,7 +209,6 @@ fn run_app(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>, app: &mut App)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Idle : synchronisation avec le daemon
|
||||
app.sync_with_daemon();
|
||||
}
|
||||
|
||||
|
||||
@ -237,7 +237,11 @@ pub fn render(f: &mut Frame, app: &mut App) {
|
||||
} else {
|
||||
app.list_state.selected().unwrap_or(0) + 1
|
||||
};
|
||||
let counter = format!(" {}/{} ", current, total);
|
||||
let counter = if app.has_more {
|
||||
format!(" {}/{}+ ", current, total)
|
||||
} else {
|
||||
format!(" {}/{} ", current, total)
|
||||
};
|
||||
let clen = counter.len() as u16;
|
||||
|
||||
let status_cols = Layout::default()
|
||||
|
||||
Reference in New Issue
Block a user