use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
use crossterm::style::style;
use crate::ui::base::data::SearchProgress;
use crate::ui::base::search::{Direction, Needle, SearchResult, State};
use crate::ui::base::{search, shorten_line, Drawable, HandleEvent, StyledLine};
use crate::ui::input::InputLine;
use super::base::search::ResultManager;
#[allow(clippy::module_name_repetitions)]
pub struct SearchWidget {
input: InputLine,
needle: Option<Needle>,
goto: Option<SearchResult>,
capture: search::NeedleCapture,
direction: Direction,
results: ResultManager,
}
impl Default for SearchWidget {
fn default() -> Self {
Self {
input: InputLine::default(),
needle: None,
goto: None,
capture: search::NeedleCapture::default(),
direction: Direction::Forward,
results: ResultManager::default(),
}
}
}
impl SearchWidget {
pub fn render(&mut self, width: usize) -> StyledLine<String> {
let mut line = StyledLine {
content: Vec::with_capacity(3),
};
match self.direction {
Direction::Forward => {
line.content.push(style("/".to_owned()));
}
Direction::Backward => {
line.content.push(style("?".to_owned()));
}
}
line.content.push(style(self.input.text().to_string()));
line.content.push(style(format!(
"\tFound({}) / Seen({})",
self.results.results().len(),
self.results.seen()
)));
shorten_line(line, width)
}
#[must_use]
pub fn needle(&self) -> Needle {
Needle::smart_case(self.input.text(), self.direction)
}
pub fn search_value(&mut self) -> Option<Needle> {
let result = self.needle.clone();
self.needle = None;
result
}
pub fn selected(&mut self) -> Option<SearchResult> {
match self.capture.state() {
State::Search(_) => {
if self.goto.is_some() {
let result = self.goto.clone();
self.goto = None;
return result;
}
None
}
_ => None,
}
}
pub fn on_event(&mut self, event: &Event) -> HandleEvent {
match self.capture.state() {
State::Hidden => self.hiden_state_on_event(event),
State::CaptureNeedle(dir) => match self.input.on_event(event) {
HandleEvent::Handled => {
let text = self.input.text().clone();
if !text.is_empty() {
self.needle = Some(Needle::smart_case(&text, *dir));
}
self.results = ResultManager::default();
HandleEvent::Handled
}
HandleEvent::Ignored => match event {
Event::Key(KeyEvent {
code: KeyCode::Enter,
modifiers: KeyModifiers::NONE,
..
}) => {
let text = self.input.text().clone();
self.needle = Some(Needle::smart_case(&text, *dir));
self.results = ResultManager::default();
self.capture.on_event(search::Event::Text(text));
HandleEvent::Handled
}
Event::Key(KeyEvent {
code: KeyCode::Esc,
modifiers: KeyModifiers::NONE,
..
}) => {
self.capture.on_event(search::Event::Cancel);
self.reset();
HandleEvent::Handled
}
_ => HandleEvent::Ignored,
},
},
State::Search(_) => self.search_on_event(event),
}
}
fn search_on_event(&mut self, event: &Event) -> HandleEvent {
match event {
Event::Key(KeyEvent {
code: KeyCode::Esc,
modifiers: KeyModifiers::NONE,
..
}) => {
self.capture.on_event(search::Event::Cancel);
self.reset();
HandleEvent::Handled
}
Event::Key(KeyEvent {
code: KeyCode::Char('n'),
modifiers: KeyModifiers::NONE,
..
}) => {
self.results.next();
self.goto = self.results.selected();
HandleEvent::Handled
}
Event::Key(KeyEvent {
code: KeyCode::Char('N'),
modifiers: KeyModifiers::SHIFT,
..
}) => {
self.results.prev();
self.goto = self.results.selected();
HandleEvent::Handled
}
Event::Key(KeyEvent {
code: KeyCode::Char('/'),
modifiers: KeyModifiers::NONE,
..
}) => {
self.direction = Direction::Forward;
self.capture
.on_event(search::Event::Activate(search::Direction::Forward));
self.reset();
HandleEvent::Handled
}
Event::Key(KeyEvent {
code: KeyCode::Char('?'),
modifiers: KeyModifiers::NONE,
..
}) => {
self.direction = Direction::Backward;
self.capture
.on_event(search::Event::Activate(search::Direction::Backward));
self.reset();
HandleEvent::Handled
}
_ => HandleEvent::Ignored,
}
}
fn hiden_state_on_event(&mut self, event: &Event) -> HandleEvent {
match event {
Event::Key(KeyEvent {
code: KeyCode::Char('/'),
modifiers: KeyModifiers::NONE,
..
}) => {
self.capture
.on_event(search::Event::Activate(Direction::Forward));
self.direction = Direction::Forward;
HandleEvent::Handled
}
Event::Key(KeyEvent {
code: KeyCode::Char('?'),
modifiers: KeyModifiers::NONE,
..
}) => {
self.direction = Direction::Backward;
self.capture
.on_event(search::Event::Activate(Direction::Backward));
HandleEvent::Handled
}
_ => HandleEvent::Ignored,
}
}
pub fn consume(&mut self, event: SearchProgress) {
match event {
SearchProgress::Found(_) => {
let was_empty = self.results.results().is_empty();
self.results.consume(event);
if was_empty {
self.goto = self.results.selected();
}
}
_ => {
self.results.consume(event);
}
}
}
fn reset(&mut self) {
self.needle = None;
self.results = ResultManager::default();
self.input = InputLine::default();
}
pub fn is_visible(&self) -> bool {
*self.capture.state() != search::State::Hidden
}
}