Add jump to search function WIP

jump_to
Manos Pitsidianakis 3 weeks ago
parent e7e33b7a85
commit 5f188e6aec
No known key found for this signature in database
GPG Key ID: 7729C7707F7E09D0

@ -170,6 +170,7 @@ shortcut_key_values! { "listing",
toggle_mailbox_collapse |> "Toggle mailbox collapse in menu." |> Key::Char(' '),
prev_page |> "Go to previous page." |> Key::PageUp,
search |> "Search within list of e-mails." |> Key::Char('/'),
jump_to |> "Search and jump to entry matches." |> Key::Char(' '),
refresh |> "Manually request a mailbox refresh." |> Key::F(5),
set_seen |> "Set thread as seen." |> Key::Char('n'),
union_modifier |> "Union modifier." |> Key::Ctrl('u'),

@ -1005,6 +1005,8 @@ pub trait ListingTrait: Component {
),
}
}
fn jump_to(&mut self, _text: &str, _context: &mut Context) {}
}
#[derive(Debug)]
@ -1028,11 +1030,12 @@ impl From<(String, JoinHandle<Result<SmallVec<[EnvelopeHash; 512]>>>)> for Backg
struct BackgroundJobs {
pub search_job: Option<BackgroundSearch>,
pub select_job: Option<BackgroundSearch>,
pub jump_to_job: Option<BackgroundSearch>,
}
impl BackgroundJobs {
pub fn is_match(&self, job_id: &JobId) -> bool {
[&self.search_job, &self.select_job].iter().any(
[&self.search_job, &self.select_job, &self.jump_to_job].iter().any(
|bg| matches!(bg, Some(BackgroundSearch { handle, .. }) if handle.job_id == *job_id),
)
}
@ -1123,6 +1126,66 @@ enum ShowMenuScrollbar {
False,
}
#[derive(Debug)]
enum CmdBuf {
Command(String),
JumpTo(String),
}
impl CmdBuf {
#[inline]
fn clone(&self) -> String {
match self {
Self::Command(ref v) | Self::JumpTo(ref v) => v.clone(),
}
}
#[inline]
fn pop(&mut self) -> Option<char> {
match self {
Self::Command(ref mut v) | Self::JumpTo(ref mut v) => v.pop(),
}
}
#[inline]
fn push(&mut self, c: char) -> bool {
match self {
Self::Command(_) if !c.is_ascii_digit() => false,
Self::Command(ref mut v) | Self::JumpTo(ref mut v) => {
v.push(c);
true
}
}
}
#[inline]
fn is_empty(&self) -> bool {
match self {
Self::Command(ref v) | Self::JumpTo(ref v) => v.is_empty(),
}
}
#[inline]
fn clear(&mut self) {
*self = Self::Command({
match self {
Self::Command(ref mut v) | Self::JumpTo(ref mut v) => {
v.clear();
std::mem::take(v)
}
}
});
}
#[inline]
fn parse_usize(&self) -> Option<usize> {
let Self::Command(ref v) = self else {
return None;
};
v.parse::<usize>().ok()
}
}
#[derive(Debug)]
pub struct Listing {
component: ListingComponent,
@ -1142,7 +1205,7 @@ pub struct Listing {
sidebar_divider_theme: ThemeAttribute,
menu_visibility: bool,
cmd_buf: String,
cmd_buf: CmdBuf,
/// This is the width of the right container to the entire width.
ratio: usize, // right/(container width) * 100
prev_ratio: usize,
@ -1617,7 +1680,7 @@ impl Component for Listing {
{
let amount = if self.cmd_buf.is_empty() {
1
} else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
} else if let Some(amount) = self.cmd_buf.parse_usize() {
self.cmd_buf.clear();
self.component.set_modifier_active(false);
context
@ -1673,7 +1736,7 @@ impl Component for Listing {
{
let amount = if self.cmd_buf.is_empty() {
1
} else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
} else if let Some(amount) = self.cmd_buf.parse_usize() {
self.cmd_buf.clear();
self.component.set_modifier_active(false);
context
@ -1756,6 +1819,35 @@ impl Component for Listing {
if self.status.is_none() {
match event {
UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Alt(''))
if !self.cmd_buf.is_empty() =>
{
self.cmd_buf.clear();
self.component.set_modifier_active(false);
if let CmdBuf::JumpTo(ref s) = self.cmd_buf {
self.component.jump_to(s, context);
}
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
return true;
}
UIEvent::Input(Key::Char(c))
if matches!(self.cmd_buf, CmdBuf::JumpTo(_))
|| matches!(self.cmd_buf, CmdBuf::Command(_) if c.is_ascii_digit()) =>
{
self.cmd_buf.push(*c);
self.component.set_modifier_active(true);
if let CmdBuf::JumpTo(ref s) = self.cmd_buf {
self.component.jump_to(s, context);
}
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufSet(
self.cmd_buf.clone(),
)));
return true;
}
UIEvent::Action(ref action) => match action {
Action::Listing(ListingAction::SetPlain) => {
self.set_index_style(IndexStyle::Plain, context);
@ -1843,7 +1935,7 @@ impl Component for Listing {
{
let amount = if self.cmd_buf.is_empty() {
1
} else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
} else if let Some(amount) = self.cmd_buf.parse_usize() {
self.cmd_buf.clear();
self.component.set_modifier_active(false);
context
@ -1866,7 +1958,7 @@ impl Component for Listing {
{
let amount = if self.cmd_buf.is_empty() {
1
} else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
} else if let Some(amount) = self.cmd_buf.parse_usize() {
self.cmd_buf.clear();
self.component.set_modifier_active(false);
context
@ -1889,7 +1981,7 @@ impl Component for Listing {
{
let amount = if self.cmd_buf.is_empty() {
1
} else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
} else if let Some(amount) = self.cmd_buf.parse_usize() {
self.cmd_buf.clear();
self.component.set_modifier_active(false);
context
@ -1912,7 +2004,7 @@ impl Component for Listing {
{
let amount = if self.cmd_buf.is_empty() {
1
} else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
} else if let Some(amount) = self.cmd_buf.parse_usize() {
self.cmd_buf.clear();
self.component.set_modifier_active(false);
context
@ -1935,7 +2027,7 @@ impl Component for Listing {
{
let mult = if self.cmd_buf.is_empty() {
1
} else if let Ok(mult) = self.cmd_buf.parse::<usize>() {
} else if let Some(mult) = self.cmd_buf.parse_usize() {
self.cmd_buf.clear();
self.component.set_modifier_active(false);
context
@ -1958,7 +2050,7 @@ impl Component for Listing {
{
let mult = if self.cmd_buf.is_empty() {
1
} else if let Ok(mult) = self.cmd_buf.parse::<usize>() {
} else if let Some(mult) = self.cmd_buf.parse_usize() {
self.cmd_buf.clear();
self.component.set_modifier_active(false);
context
@ -2071,6 +2163,15 @@ impl Component for Listing {
self.component.prev_entry(context);
return true;
}
UIEvent::Input(ref k)
if shortcut!(k == shortcuts[Shortcuts::LISTING]["jump_to"]) =>
{
if let CmdBuf::Command(ref mut v) = self.cmd_buf {
v.clear();
self.cmd_buf = CmdBuf::JumpTo(std::mem::take(v));
}
return true;
}
UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Alt(''))
if !self.component.unfocused() =>
{
@ -2086,26 +2187,6 @@ impl Component for Listing {
self.component.set_dirty(true);
return true;
}
UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Alt(''))
if !self.cmd_buf.is_empty() =>
{
self.cmd_buf.clear();
self.component.set_modifier_active(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
return true;
}
UIEvent::Input(Key::Char(c)) if c.is_ascii_digit() => {
self.cmd_buf.push(*c);
self.component.set_modifier_active(true);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufSet(
self.cmd_buf.clone(),
)));
return true;
}
_ => {}
}
}
@ -2192,7 +2273,7 @@ impl Component for Listing {
{
let mut amount = if self.cmd_buf.is_empty() {
1
} else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
} else if let Some(amount) = self.cmd_buf.parse_usize() {
self.cmd_buf.clear();
self.component.set_modifier_active(false);
context
@ -2320,7 +2401,7 @@ impl Component for Listing {
{
let amount = if self.cmd_buf.is_empty() {
1
} else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
} else if let Some(amount) = self.cmd_buf.parse_usize() {
self.cmd_buf.clear();
self.component.set_modifier_active(false);
context
@ -2382,7 +2463,7 @@ impl Component for Listing {
{
let amount = if self.cmd_buf.is_empty() {
1
} else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
} else if let Some(amount) = self.cmd_buf.parse_usize() {
self.cmd_buf.clear();
self.component.set_modifier_active(false);
context
@ -2618,7 +2699,8 @@ impl Component for Listing {
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
return true;
}
UIEvent::Input(Key::Char(c)) if c.is_ascii_digit() => {
UIEvent::Input(Key::Char(c)) if matches!(self.cmd_buf, CmdBuf::JumpTo(_) | CmdBuf::Command(_) if c.is_ascii_digit()) =>
{
self.cmd_buf.push(c);
self.component.set_modifier_active(true);
context
@ -2850,7 +2932,7 @@ impl Listing {
prev_ratio: *account_settings!(context[first_account_hash].listing.sidebar_ratio),
menu_width: WidgetWidth::Unset,
focus: ListingFocus::Mailbox,
cmd_buf: String::with_capacity(4),
cmd_buf: CmdBuf::Command(String::with_capacity(8)),
};
ret.component.realize(ret.id().into(), context);
ret.change_account(context);

@ -19,7 +19,7 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::{collections::BTreeMap, convert::TryInto, iter::FromIterator};
use std::{collections::BTreeMap, convert::TryInto, iter::FromIterator, time::Instant};
use indexmap::IndexSet;
use melib::{Address, SortField, SortOrder, TagHash, Threads};
@ -855,6 +855,34 @@ impl ListingTrait for CompactListing {
fn focus(&self) -> Focus {
self.focus
}
fn jump_to(&mut self, text: &str, context: &mut Context) {
if text.is_empty() {
self.bg_jobs.jump_to_job = None;
return;
}
match context.accounts[&self.cursor_pos.0].search(
&format!("subject:\"{}\"", text),
self.sort,
self.cursor_pos.1,
) {
Ok(job) => {
let handle = context.accounts[&self.cursor_pos.0]
.main_loop_handler
.job_executor
.spawn_specialized("search".into(), job);
self.bg_jobs.jump_to_job = Some((text.to_string(), handle).into());
}
Err(err) => {
context.replies.push_back(UIEvent::Notification {
title: Some("Could not perform search".into()),
body: err.to_string().into(),
kind: Some(crate::types::NotificationType::Error(err.kind)),
source: Some(err),
});
}
};
}
}
impl std::fmt::Display for CompactListing {
@ -2123,6 +2151,54 @@ impl Component for CompactListing {
return false;
}
self.bg_jobs.select_job = job;
let job = self.bg_jobs.jump_to_job.take();
if job
.as_ref()
.map(|bg| bg.handle.job_id == *job_id)
.unwrap_or(false)
{
let BackgroundSearch { mut handle, .. } = job.unwrap();
match handle.chan.try_recv() {
Err(_) => { /* search was canceled */ }
Ok(None) => { /* something happened, perhaps a worker thread panicked */ }
Ok(Some(Err(err))) => {
context.replies.push_back(UIEvent::Notification {
title: Some("Could not perform search".into()),
body: err.to_string().into(),
kind: Some(crate::types::NotificationType::Error(err.kind)),
source: Some(err),
});
}
Ok(Some(Ok(results))) => {
let account = &context.accounts[&self.cursor_pos.0];
let threads = account.collection.get_threads(self.cursor_pos.1);
for env_hash in results.into_iter().rev() {
if !account.collection.contains_key(&env_hash) {
continue;
}
let env_thread_node_hash =
account.collection.get_env(env_hash).thread();
if !threads.thread_nodes.contains_key(&env_thread_node_hash) {
continue;
}
let thread = threads
.find_group(threads.thread_nodes[&env_thread_node_hash].group);
if self.filtered_order.contains_key(&thread) {
continue;
}
if self.rows.all_threads.contains(&thread) {
self.new_cursor_pos.2 = self.rows.thread_order[&thread];
self.force_draw = true;
self.dirty = true;
break;
}
}
}
}
self.set_dirty(true);
return false;
}
self.bg_jobs.jump_to_job = job;
}
_ => {}
}

Loading…
Cancel
Save