correction + regexp

This commit is contained in:
2026-05-20 23:26:01 +02:00
parent 8b07e305f0
commit d173db3342
11 changed files with 425 additions and 73 deletions

View File

@ -4,6 +4,7 @@ use chrono::{Local, NaiveDate, TimeZone};
use fuzzy_matcher::{FuzzyMatcher, skim::SkimMatcherV2};
use ratatui::widgets::ListState;
use ratatui_image::{picker::Picker, protocol};
use regex::Regex;
use std::time::{Duration, Instant};
use syntect::highlighting::ThemeSet;
use syntect::parsing::SyntaxSet;
@ -43,6 +44,32 @@ pub struct App {
pub status_message: Option<(String, Instant)>,
pub syntax_set: SyntaxSet,
pub theme_set: ThemeSet,
pub type_filter: TypeFilter,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypeFilter {
All,
Text,
Image,
}
impl TypeFilter {
pub fn next(self) -> Self {
match self {
Self::All => Self::Text,
Self::Text => Self::Image,
Self::Image => Self::All,
}
}
pub fn label(self) -> &'static str {
match self {
Self::All => "Tous",
Self::Text => "Texte",
Self::Image => "Image",
}
}
}
impl App {
@ -76,6 +103,7 @@ impl App {
status_message: None,
syntax_set: SyntaxSet::load_defaults_newlines(),
theme_set: ThemeSet::load_defaults(),
type_filter: TypeFilter::All,
};
app.update_preview();
app
@ -99,7 +127,14 @@ impl App {
}
}
pub fn cycle_type_filter(&mut self) {
self.type_filter = self.type_filter.next();
self.update_search();
}
pub fn update_search(&mut self) {
self.last_selected_index = None;
let query = self.input_buffer.trim().to_string();
let (date_before, date_after, text_query) = parse_date_filters(&query);
@ -123,22 +158,45 @@ impl App {
.cloned()
.collect();
let search_str = |item: &HistoryItem| -> String {
if Crypto::is_any_encrypted(&item.content) {
"[chiffré]".to_string()
} else if item.content.ends_with(".jpg") || item.content.ends_with(".png") {
format!("image {}", item.content)
} else {
item.content.clone()
}
};
let is_regex = text_query.starts_with('/') && text_query.len() > 1;
self.filtered_items = if text_query.is_empty() {
base
} else if is_regex {
let pattern = &text_query[1..];
match Regex::new(pattern) {
Ok(re) => base
.into_iter()
.filter(|item| re.is_match(&search_str(item)))
.collect(),
Err(e) => {
self.error_message = Some((
format!(
"Regex invalide : {}",
e.to_string().lines().next().unwrap_or("")
),
Instant::now(),
));
base
}
}
} else {
let matcher = SkimMatcherV2::default();
let mut matched: Vec<(i64, HistoryItem)> = base
.into_iter()
.filter_map(|item| {
let search_str = if Crypto::is_any_encrypted(&item.content) {
"[chiffré]".to_string()
} else if item.content.ends_with(".jpg") || item.content.ends_with(".png") {
format!("image {}", item.content)
} else {
item.content.clone()
};
matcher
.fuzzy_match(&search_str, &text_query)
.fuzzy_match(&search_str(&item), &text_query)
.map(|s| (s, item))
})
.collect();
@ -146,11 +204,18 @@ impl App {
matched.into_iter().map(|(_, i)| i).collect()
};
self.filtered_items.retain(|item| match self.type_filter {
TypeFilter::All => true,
TypeFilter::Text => !item.content.ends_with(".jpg") && !item.content.ends_with(".png"),
TypeFilter::Image => item.content.ends_with(".jpg") || item.content.ends_with(".png"),
});
self.list_state.select(if self.filtered_items.is_empty() {
None
} else {
Some(0)
});
self.update_preview();
}
@ -208,21 +273,14 @@ impl App {
return;
}
self.crypto = None;
if Crypto::is_password_encrypted(&content) {
if self.crypto.is_none() {
self.pending_action = Some(PendingAction::DecryptSelected);
self.enter_password_mode();
} else {
self.do_decrypt_selected();
}
self.pending_action = Some(PendingAction::DecryptSelected);
} else {
if self.crypto.is_none() {
self.pending_action = Some(PendingAction::EncryptSelected);
self.enter_password_mode();
} else {
self.do_encrypt_selected();
}
self.pending_action = Some(PendingAction::EncryptSelected);
}
self.enter_password_mode();
}
fn enter_password_mode(&mut self) {
@ -260,6 +318,8 @@ impl App {
None => return,
};
self.crypto = None;
match encrypt_result {
Ok(enc) => {
if ipc::update_entry(content.clone(), enc.clone()) {
@ -284,6 +344,8 @@ impl App {
None => return,
};
self.crypto = None;
match decrypt_result {
Ok(plain) => {
if ipc::update_entry(content.clone(), plain.clone()) {
@ -293,7 +355,11 @@ impl App {
self.set_error("Erreur mise à jour BDD".into());
}
}
Err(e) => self.set_error(format!("{e}")),
Err(_) => {
self.set_error(
"Déchiffrement échoué — mauvais mot de passe. Réessayez avec [e].".into(),
);
}
}
}
@ -308,12 +374,21 @@ impl App {
None => return,
};
self.crypto = None;
match decrypt_result {
Ok(plain) => {
ipc::set_clipboard(plain);
self.should_quit = true;
if ipc::set_clipboard(plain) {
self.should_quit = true;
} else {
self.set_error("Erreur : impossible de définir le presse-papier".into());
}
}
Err(_) => {
self.set_error(
"Déchiffrement échoué — mauvais mot de passe. Réessayez avec Entrée.".into(),
);
}
Err(e) => self.set_error(format!("{e}")),
}
}
@ -334,15 +409,17 @@ impl App {
None => return,
};
if Crypto::is_password_encrypted(&content) {
if self.crypto.is_none() {
self.pending_action = Some(PendingAction::PasteEncrypted);
self.enter_password_mode();
} else {
self.do_paste_encrypted();
}
self.crypto = None;
self.pending_action = Some(PendingAction::PasteEncrypted);
self.enter_password_mode();
} else {
ipc::set_clipboard(content);
self.should_quit = true;
if ipc::set_clipboard(content) {
self.should_quit = true;
} else {
self.set_error(
"Impossible de définir le presse-papier (daemon injoignable ?)".into(),
);
}
}
}
@ -436,9 +513,24 @@ impl App {
.iter()
.zip(&new)
.any(|(a, b)| a.content != b.content);
if changed {
let selected_content = self.get_selected_item().map(|i| i.content.clone());
self.all_items = new;
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();
}
}
}
}
}