use crossterm::style::{style, Attribute};
use crate::search::line_matches;
use crate::ui::base::search::{Direction, Needle, SearchResult};
use crate::ui::base::{Pos, StyledArea, StyledLine};
use std::num::NonZeroUsize;
use std::sync::mpsc;
use std::sync::mpsc::Receiver;
use std::thread;
use std::thread::JoinHandle;
#[allow(clippy::module_name_repetitions)]
pub trait DataAdapter<T> {
fn get_line(&mut self, i: Pos, selected: bool) -> StyledLine<String>;
fn get_data(&mut self, i: Pos) -> &T;
fn is_empty(&self) -> bool;
fn len(&self) -> NonZeroUsize;
fn search(&mut self, needle: Needle, start: usize) -> Receiver<SearchProgress>;
}
impl DataAdapter<String> for VecAdapter {
fn get_line(&mut self, i: usize, selected: bool) -> StyledLine<String> {
let text = &self.content[i];
let mut content = style(text.clone());
if selected {
content.style_mut().attributes.set(Attribute::Reverse);
}
StyledLine {
content: vec![content],
}
}
fn get_data(&mut self, i: usize) -> &String {
&self.content[i]
}
fn is_empty(&self) -> bool {
self.content.is_empty()
}
fn len(&self) -> NonZeroUsize {
NonZeroUsize::new(self.content.len()).expect("content length >= 1")
}
fn search(&mut self, _needle: Needle, _start: usize) -> Receiver<SearchProgress> {
let (_, tx) = mpsc::channel::<SearchProgress>();
tx
}
}
pub enum SearchProgress {
Searched(usize),
Found(SearchResult),
Finished,
}
pub struct VecAdapter {
content: Vec<String>,
}
impl VecAdapter {
#[must_use]
#[allow(dead_code)]
pub fn new(content: Vec<String>) -> Self {
Self { content }
}
}
#[cfg(test)]
mod test_vec_adapter {
use crossterm::style::Attribute;
use pretty_assertions::assert_eq;
use crate::ui::base::data::DataAdapter;
use crate::ui::base::data::VecAdapter;
use crate::ui::base::test_helpers::lore_ipsum_lines;
#[test]
fn foo() {
let adapter = &mut VecAdapter::new(lore_ipsum_lines(30));
assert_eq!(adapter.len().get(), 30);
assert!(!adapter.is_empty());
let line = adapter.get_line(0, false);
for sc in line.content {
assert!(!sc.style().attributes.has(Attribute::Reverse));
}
let selected_line = adapter.get_line(0, true);
for sc in selected_line.content {
assert!(sc.style().attributes.has(Attribute::Reverse));
}
}
}
pub struct StyledAreaAdapter {
pub content: StyledArea<String>,
pub thread: Option<JoinHandle<()>>,
}
impl DataAdapter<String> for StyledAreaAdapter {
fn get_line(&mut self, i: usize, selected: bool) -> StyledLine<String> {
let mut line: StyledLine<String> = self.content[i].clone();
if selected {
for c in &mut line.content {
c.style_mut().attributes.set(Attribute::Reverse);
}
}
line
}
#[allow(clippy::todo)]
fn get_data(&mut self, _i: usize) -> &String {
todo!()
}
fn is_empty(&self) -> bool {
self.content.is_empty()
}
fn len(&self) -> NonZeroUsize {
NonZeroUsize::new(self.content.len()).unwrap()
}
fn search(&mut self, needle: Needle, start: usize) -> Receiver<SearchProgress> {
let (rx, tx) = mpsc::channel::<SearchProgress>();
let cloned = self.content.clone();
let thread = thread::spawn(move || {
let mut range = (start..cloned.len()).collect::<Vec<usize>>();
let rest = (0..start).collect::<Vec<usize>>();
range.extend(rest);
if *needle.direction() == Direction::Backward {
range = range.into_iter().rev().collect::<Vec<_>>();
}
for i in range {
let line = &cloned[i];
let t = if line_matches(line, &needle) {
SearchProgress::Found(SearchResult(vec![i]))
} else {
SearchProgress::Searched(1)
};
if let Err(err) = rx.send(t) {
log::error!("Failed to send search progress\n{}", err);
return;
}
}
if let Err(err) = rx.send(SearchProgress::Finished) {
log::error!("Failed to send SearchProgress::Finished\n{}", err);
}
});
self.thread = Some(thread);
tx
}
}