correction + regexp
This commit is contained in:
156
src/app.rs
156
src/app.rs
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user