use crate::app::window_state::WindowState;
use crate::copy_mode::SearchDirection;
use winit::event::KeyEvent;
use winit::keyboard::{Key, NamedKey};
impl WindowState {
pub(crate) fn handle_copy_mode_search_key(&mut self, event: &KeyEvent) {
match &event.logical_key {
Key::Named(NamedKey::Escape) => {
self.copy_mode.cancel_search();
self.focus_state.needs_redraw = true;
self.request_redraw();
}
Key::Named(NamedKey::Enter) => {
self.copy_mode.is_searching = false;
self.execute_copy_mode_search(false);
}
Key::Named(NamedKey::Backspace) => {
self.copy_mode.search_backspace();
self.focus_state.needs_redraw = true;
self.request_redraw();
}
Key::Character(ch) => {
for c in ch.chars() {
self.copy_mode.search_input(c);
}
self.focus_state.needs_redraw = true;
self.request_redraw();
}
_ => {}
}
}
pub(crate) fn execute_copy_mode_search(&mut self, reverse: bool) {
if self.copy_mode.search_query.is_empty() {
return;
}
let query = self.copy_mode.search_query.clone();
let forward = match self.copy_mode.search_direction {
SearchDirection::Forward => !reverse,
SearchDirection::Backward => reverse,
};
let current_line = self.copy_mode.cursor_absolute_line;
let current_col = self.copy_mode.cursor_col;
let total = self.copy_mode.scrollback_len + self.copy_mode.rows;
let found = self
.tab_manager
.active_tab()
.and_then(|tab| {
tab.try_with_terminal_mut(|term| {
if forward {
self.search_lines_forward(term, &query, current_line, current_col, total)
} else {
self.search_lines_backward(term, &query, current_line, current_col)
}
})
})
.flatten();
if let Some((line, col)) = found {
self.copy_mode.cursor_absolute_line = line;
self.copy_mode.cursor_col = col;
self.after_copy_mode_motion();
crate::debug_info!("COPY_MODE", "Search found '{}' at {}:{}", query, line, col);
} else {
self.show_toast("Pattern not found");
self.focus_state.needs_redraw = true;
self.request_redraw();
}
}
pub(crate) fn search_lines_forward(
&self,
term: &crate::terminal::TerminalManager,
query: &str,
start_line: usize,
start_col: usize,
total_lines: usize,
) -> Option<(usize, usize)> {
let query_lower = query.to_lowercase();
for abs_line in start_line..total_lines {
if let Some(text) = term.line_text_at_absolute(abs_line) {
let search_start = if abs_line == start_line {
start_col + 1
} else {
0
};
let text_lower = text.to_lowercase();
if let Some(pos) = text_lower[search_start..].find(&query_lower) {
return Some((abs_line, search_start + pos));
}
}
}
for abs_line in 0..start_line {
if let Some(text) = term.line_text_at_absolute(abs_line) {
let text_lower = text.to_lowercase();
if let Some(pos) = text_lower.find(&query_lower) {
return Some((abs_line, pos));
}
}
}
None
}
pub(crate) fn search_lines_backward(
&self,
term: &crate::terminal::TerminalManager,
query: &str,
start_line: usize,
start_col: usize,
) -> Option<(usize, usize)> {
let query_lower = query.to_lowercase();
for abs_line in (0..=start_line).rev() {
if let Some(text) = term.line_text_at_absolute(abs_line) {
let text_lower = text.to_lowercase();
let search_end = if abs_line == start_line {
start_col
} else {
text_lower.len()
};
if let Some(pos) = text_lower[..search_end].rfind(&query_lower) {
return Some((abs_line, pos));
}
}
}
let total = self.copy_mode.scrollback_len + self.copy_mode.rows;
for abs_line in (start_line + 1..total).rev() {
if let Some(text) = term.line_text_at_absolute(abs_line) {
let text_lower = text.to_lowercase();
if let Some(pos) = text_lower.rfind(&query_lower) {
return Some((abs_line, pos));
}
}
}
None
}
pub(crate) fn get_copy_mode_line_text(&self) -> Option<String> {
let abs_line = self.copy_mode.cursor_absolute_line;
self.tab_manager
.active_tab()
.and_then(|tab| tab.try_with_terminal_mut(|term| term.line_text_at_absolute(abs_line)))
.flatten()
}
pub(crate) fn after_copy_mode_motion(&mut self) {
if self.copy_mode.pending_operator.is_some() {
self.copy_mode.pending_operator = None;
}
self.sync_copy_mode_selection();
self.follow_copy_mode_cursor();
self.focus_state.needs_redraw = true;
self.request_redraw();
}
pub(crate) fn sync_copy_mode_selection(&mut self) {
let scroll_offset = self
.with_active_tab(|t| t.active_scroll_state().offset)
.unwrap_or(0);
let selection = self.copy_mode.compute_selection(scroll_offset);
self.with_active_tab_mut(|tab| {
tab.selection_mouse_mut().selection = selection;
tab.active_cache_mut().cells = None; });
}
pub(crate) fn follow_copy_mode_cursor(&mut self) {
let current_offset = self
.with_active_tab(|t| t.active_scroll_state().offset)
.unwrap_or(0);
if let Some(new_offset) = self.copy_mode.required_scroll_offset(current_offset) {
self.set_scroll_target(new_offset);
self.sync_copy_mode_selection();
}
}
pub(crate) fn yank_copy_mode_selection(&mut self) {
if let Some(text) = self.get_selected_text_for_copy() {
let text_len = text.len();
let auto_exit = self.config.copy_mode.copy_mode_auto_exit_on_yank;
match self.input_handler.copy_to_clipboard(&text) {
Ok(()) => {
let line_count = text.lines().count();
let msg = if line_count > 1 {
format!("{} lines yanked", line_count)
} else {
format!("{} chars yanked", text_len)
};
if auto_exit {
self.exit_copy_mode();
} else {
self.copy_mode.visual_mode = crate::copy_mode::VisualMode::None;
self.copy_mode.selection_anchor = None;
self.with_active_tab_mut(|tab| {
tab.selection_mouse_mut().selection = None;
tab.active_cache_mut().cells = None;
});
self.focus_state.needs_redraw = true;
self.request_redraw();
}
self.show_toast(msg);
}
Err(e) => {
crate::debug_error!("COPY_MODE", "Failed to copy to clipboard: {}", e);
self.show_toast("Failed to copy to clipboard");
}
}
} else if self.config.copy_mode.copy_mode_auto_exit_on_yank {
self.exit_copy_mode();
}
}
}