use crate::widgets::{Message, MessageType};
use crate::{app::App, editor::UIState};
use ratatui::Frame;
use ratatui::crossterm::event::{Event, KeyCode};
use ratatui::widgets::Paragraph;
use std::io::Result;
use tui_input::Input;
use tui_input::backend::crossterm::EventHandler;
#[derive(Default, Debug)]
pub struct Search {
pub input_text: Input,
pub mode: SearchMode,
pub direction: SearchDirection,
pub input_hex: Input,
}
#[derive(Default, Debug, PartialEq)]
pub enum SearchMode {
#[default]
Utf8,
Hex,
}
impl SearchMode {
pub fn next(&mut self) {
if *self == SearchMode::Utf8 {
*self = SearchMode::Hex;
} else {
*self = SearchMode::Utf8
}
}
}
#[derive(Default, PartialEq, Debug)]
pub enum SearchDirection {
#[default]
Forward,
Backward,
}
pub fn hex_string_to_u8(hex_string: &str) -> Option<Vec<u8>> {
if hex_string.is_empty() || !hex_string.len().is_multiple_of(2) {
return None;
}
let bytes = hex::decode(hex_string).unwrap();
Some(bytes)
}
pub fn search<T: AsRef<[u8]>>(app: &mut App, needle: T) -> Option<usize> {
let text = needle.as_ref();
let filesize = app.file_info.size;
let buffer = app.file_info.get_buffer();
if filesize == 0 || text.is_empty() {
return None;
}
let ofs = if app.hex_view.search.direction == SearchDirection::Forward {
let start = app.hex_view.offset.checked_add(1)?;
if start < filesize {
memchr::memmem::find(buffer.get(start..)?, text).map(|pos| start + pos)
} else {
None
}
} else {
let end = app.hex_view.offset;
if end > 0 {
memchr::memmem::rfind(buffer.get(..end)?, text)
} else {
None
}
};
if ofs.is_some() {
return ofs;
}
if app.config.search_wrap {
let ofs = if app.hex_view.search.direction == SearchDirection::Forward {
memchr::memmem::find(buffer, text)
} else {
memchr::memmem::rfind(buffer, text)
};
if ofs.is_some() {
return ofs;
}
}
crate::beep!();
None
}
pub fn dialog_search_draw(app: &mut App, frame: &mut Frame) {
let x;
let para;
let prompt_char = if app.hex_view.search.direction == SearchDirection::Forward {
'/'
} else {
'?'
};
match app.hex_view.search.mode {
SearchMode::Utf8 => {
para = Paragraph::new(format!(
"{}{}",
prompt_char,
app.hex_view.search.input_text.value()
));
x = app.hex_view.search.input_text.visual_cursor();
}
SearchMode::Hex => {
para = Paragraph::new(format!(
"{}{}",
prompt_char,
app.hex_view.search.input_hex.value()
));
x = app.hex_view.search.input_hex.visual_cursor();
}
};
frame.render_widget(para, app.command_area);
frame.set_cursor_position((app.command_area.x + 1 + x as u16, app.command_area.y));
}
pub fn dialog_search_events(app: &mut App, event: &Event) -> Result<bool> {
if let Event::Key(key) = event {
match key.code {
KeyCode::Esc => {
app.dialog_renderer = None;
app.state = UIState::Normal;
}
KeyCode::Backspace => match app.hex_view.search.mode {
SearchMode::Utf8 => {
if app.hex_view.search.input_text.value().is_empty() {
app.dialog_renderer = None;
app.state = UIState::Normal;
} else {
app.hex_view.search.input_text.handle_event(event);
}
}
SearchMode::Hex => {
if app.hex_view.search.input_hex.value().is_empty() {
app.dialog_renderer = None;
app.state = UIState::Normal;
} else {
app.hex_view.search.input_hex.handle_event(event);
}
}
},
KeyCode::Enter => {
match app.hex_view.search.mode {
SearchMode::Utf8 => {
let text = app.hex_view.search.input_text.value().to_string();
app.state = UIState::Normal;
if text.is_empty() {
app.dialog_renderer = None;
return Ok(false);
}
if let Some(ofs) = search(app, &text) {
app.goto(ofs);
app.dialog_renderer = None;
} else {
app.dialog_renderer = Some(dialog_search_error_draw);
}
}
SearchMode::Hex => {
let hex_string = app.hex_view.search.input_hex.value().to_string();
if let Some(bytes) = hex_string_to_u8(&hex_string) {
if let Some(ofs) = search(app, &bytes) {
app.goto(ofs);
app.dialog_renderer = None;
} else {
app.dialog_renderer = Some(dialog_search_error_draw);
}
app.state = UIState::Normal;
}
}
};
}
KeyCode::Tab => {
app.hex_view.search.mode.next();
}
KeyCode::Char(c) => {
match app.hex_view.search.mode {
SearchMode::Utf8 => app.hex_view.search.input_text.handle_event(event),
SearchMode::Hex => {
if c.is_ascii_hexdigit() {
app.hex_view.search.input_hex.handle_event(event)
} else {
None
}
}
};
}
_ => {
match app.hex_view.search.mode {
SearchMode::Utf8 => app.hex_view.search.input_text.handle_event(event),
SearchMode::Hex => app.hex_view.search.input_hex.handle_event(event),
};
}
}
}
Ok(false)
}
pub fn dialog_search_error_draw(app: &mut App, frame: &mut Frame) {
let mut dialog = Message::from("Pattern not found");
dialog.kind = MessageType::Error;
dialog.render(app, frame);
}