Skip to content

Commit

Permalink
perf: make filtering go faster (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
micielski authored Aug 1, 2024
1 parent 494f767 commit 4b3673d
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 52 deletions.
4 changes: 2 additions & 2 deletions rm-main/src/ui/tabs/torrents/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl Component for TorrentsTab {
}
}
A::AddMagnet => self.task_manager.add_magnet(),
A::Search => self.task_manager.search(self.table_manager.filter.clone()),
A::Search => self.task_manager.search(&self.table_manager.filter),
A::MoveTorrent => {
if let Some(torrent) = self.table_manager.current_torrent() {
self.task_manager.move_torrent(torrent);
Expand All @@ -126,7 +126,7 @@ impl Component for TorrentsTab {
self.bottom_stats.set_free_space(free_space);
}
UpdateAction::SearchFilterApply(filter) => {
self.table_manager.filter.replace(filter);
self.table_manager.set_filter(filter);
self.table_manager.table.state.borrow_mut().select(Some(0));
self.table_manager.update_rows_number();
self.bottom_stats
Expand Down
68 changes: 62 additions & 6 deletions rm-main/src/ui/tabs/torrents/rustmission_torrent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,25 +43,81 @@ impl RustmissionTorrent {

pub fn to_row_with_higlighted_indices(
&self,
highlighted_indices: Vec<usize>,
highlighted_indices: &Vec<usize>,
highlight_style: Style,
headers: &Vec<Header>,
) -> ratatui::widgets::Row {
let mut torrent_name_line = Line::default();

for (index, char) in self.torrent_name.char_indices() {
if highlighted_indices.contains(&index) {
torrent_name_line.push_span(Span::styled(char.to_string(), highlight_style));
let char_indices: Vec<usize> = self.torrent_name.char_indices().map(|(i, _)| i).collect();
let mut last_end = 0;
let mut flush_line = |start: usize, end: usize| {
let mut start = char_indices[start as usize];
let mut end = char_indices[end as usize];
torrent_name_line.push_span(Span::styled(
&self.torrent_name[last_end..start],
self.style,
));

while !self.torrent_name.is_char_boundary(start) {
start -= 1;
}

while !self.torrent_name.is_char_boundary(end + 1) {
end += 1;
}

torrent_name_line.push_span(Span::styled(
&self.torrent_name[start..=end],
highlight_style,
));
last_end = end + 1;
};

let mut first: Option<usize> = None;
let mut second: Option<usize> = None;
for indice in highlighted_indices {
let fst = if let Some(fst) = first {
fst
} else {
torrent_name_line.push_span(Span::styled(char.to_string(), self.style))
first = Some(*indice);
continue;
};

let snd = if let Some(snd) = second {
snd
} else {
if fst + 1 == *indice {
second = Some(*indice);
} else {
flush_line(fst, fst);
first = Some(*indice);
}
continue;
};

if snd + 1 == *indice {
second = Some(*indice);
} else {
flush_line(fst, snd);
first = Some(*indice);
second = None;
}
}

if let (Some(first), None) = (first, second) {
flush_line(first, first);
} else if let (Some(first), Some(second)) = (first, second) {
flush_line(first, second);
}

torrent_name_line.push_span(Span::styled(&self.torrent_name[last_end..], self.style));

let mut cells = vec![];

for header in headers {
if *header == Header::Name {
cells.push(torrent_name_line.clone())
cells.push(std::mem::take(&mut torrent_name_line))
} else {
cells.push(self.header_to_line(*header).style(self.style))
}
Expand Down
79 changes: 43 additions & 36 deletions rm-main/src/ui/tabs/torrents/table_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@ pub struct TableManager {
ctx: app::Ctx,
pub table: GenericTable<RustmissionTorrent>,
pub widths: Vec<Constraint>,
pub filter: Option<String>,
pub filter: Option<Filter>,
pub torrents_displaying_no: u16,
headers: Vec<&'static str>,
}

pub struct Filter {
pub pattern: String,
indexes: Vec<u16>,
highlight_indices: Vec<Vec<usize>>,
}

impl TableManager {
pub fn new(ctx: app::Ctx) -> Self {
let table = GenericTable::new(vec![]);
Expand All @@ -37,16 +43,26 @@ impl TableManager {

pub fn update_rows_number(&mut self) {
if let Some(filter) = &self.filter {
let rows = self.filtered_torrents_rows(&self.table.items, filter);
self.table.overwrite_len(rows.len());
self.table.overwrite_len(filter.indexes.len());
} else {
self.table.items.len();
}
}

pub fn rows(&self) -> Vec<Row<'_>> {
if let Some(filter) = &self.filter {
let rows = self.filtered_torrents_rows(&self.table.items, filter);
let highlight_style = Style::default().fg(self.ctx.config.general.accent_color);
let headers = &self.ctx.config.torrents_tab.headers;
let mut rows = vec![];
for (i, which_torrent) in filter.indexes.iter().enumerate() {
let row = self.table.items[*which_torrent as usize].to_row_with_higlighted_indices(
&filter.highlight_indices[i],
highlight_style,
headers,
);
rows.push(row);
}

self.table.overwrite_len(rows.len());
rows
} else {
Expand All @@ -63,25 +79,19 @@ impl TableManager {
}

pub fn current_torrent(&mut self) -> Option<&mut RustmissionTorrent> {
let matcher = SkimMatcherV2::default();
let index = self.table.state.borrow().selected()?;
let selected_idx = self.table.state.borrow().selected()?;

if let Some(filter) = &self.filter {
let mut loop_index = 0;
for rustmission_torrent in &mut self.table.items {
if matcher
.fuzzy_match(&rustmission_torrent.torrent_name, filter)
.is_some()
{
if index == loop_index {
return Some(rustmission_torrent);
}
loop_index += 1;
}
if filter.indexes.is_empty() {
return None;
} else {
self.table
.items
.get_mut(filter.indexes[selected_idx] as usize)
}
return None;
} else {
self.table.items.get_mut(selected_idx)
}
self.table.items.get_mut(index)
}

pub fn set_new_rows(&mut self, rows: Vec<RustmissionTorrent>) {
Expand All @@ -90,27 +100,24 @@ impl TableManager {
self.update_rows_number();
}

fn filtered_torrents_rows<'a>(
&self,
torrents: &'a [RustmissionTorrent],
filter: &str,
) -> Vec<Row<'a>> {
pub fn set_filter(&mut self, filter: String) {
let matcher = SkimMatcherV2::default();
let mut rows = vec![];

let highlight_style = Style::default().fg(self.ctx.config.general.accent_color);

for torrent in torrents {
if let Some((_, indices)) = matcher.fuzzy_indices(&torrent.torrent_name, filter) {
rows.push(torrent.to_row_with_higlighted_indices(
indices,
highlight_style,
&self.ctx.config.torrents_tab.headers,
))
let mut indexes: Vec<u16> = vec![];
let mut highlight_indices = vec![];
for (i, torrent) in self.table.items.iter().enumerate() {
if let Some((_, indices)) = matcher.fuzzy_indices(&torrent.torrent_name, &filter) {
indexes.push(i as u16);
highlight_indices.push(indices);
}
}

rows
let filter = Filter {
pattern: filter,
indexes,
highlight_indices,
};

self.filter = Some(filter);
}

fn default_widths(headers: &Vec<Header>) -> Vec<Constraint> {
Expand Down
3 changes: 2 additions & 1 deletion rm-main/src/ui/tabs/torrents/task_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use rm_shared::{

use super::{
rustmission_torrent::RustmissionTorrent,
table_manager::Filter,
tasks::{
add_magnet::AddMagnetBar,
default::DefaultBar,
Expand Down Expand Up @@ -130,7 +131,7 @@ impl TaskManager {
self.ctx.send_update_action(UpdateAction::SwitchToInputMode);
}

pub fn search(&mut self, filter: Option<String>) {
pub fn search(&mut self, filter: &Option<Filter>) {
self.current_task = CurrentTask::FilterBar(FilterBar::new(self.ctx.clone(), filter));
self.ctx.send_update_action(UpdateAction::SwitchToInputMode);
}
Expand Down
18 changes: 11 additions & 7 deletions rm-main/src/ui/tabs/torrents/tasks/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
app,
ui::{
components::{Component, ComponentAction},
tabs::torrents::input_manager::InputManager,
tabs::torrents::{input_manager::InputManager, table_manager::Filter},
to_input_request,
},
};
Expand All @@ -17,12 +17,16 @@ pub struct FilterBar {
}

impl FilterBar {
pub fn new(ctx: app::Ctx, current_filter: Option<String>) -> Self {
let input = InputManager::new_with_value(
ctx.clone(),
"Search: ".to_string(),
current_filter.unwrap_or_default(),
);
pub fn new(ctx: app::Ctx, current_filter: &Option<Filter>) -> Self {
let filter = {
if let Some(current_filter) = current_filter {
current_filter.pattern.clone()
} else {
"".to_string()
}
};

let input = InputManager::new_with_value(ctx.clone(), "Search: ".to_string(), filter);
Self { ctx, input }
}
}
Expand Down

0 comments on commit 4b3673d

Please sign in to comment.