use super::{App, DISPLAY_EVENT_DURATION_MS};
use crate::provider::{decrement_detail_level, increment_detail_level};
use anyhow::Result;
use arboard::Clipboard;
use crossterm::event::{self, KeyCode, KeyEvent, KeyEventKind, MouseEvent, MouseEventKind};
use ratatui::prelude::*;
use std::time::Duration;
impl App {
pub(super) fn handle_mouse_event(&mut self, mouse: &MouseEvent) -> Result<()> {
match mouse.kind {
MouseEventKind::ScrollDown => {
if let Some(block_under_mouse) = self.get_block_under_mouse(mouse) {
if mouse.modifiers.contains(event::KeyModifiers::SHIFT) {
self.handle_horizontal_scrolling(block_under_mouse, true)?;
} else {
if block_under_mouse == self.logs_block.id() {
self.handle_logs_view_scrolling(true)?;
} else if block_under_mouse == self.details_block.id() {
self.handle_details_block_scrolling(true)?;
} else if block_under_mouse == self.debug_block.id() {
self.handle_debug_logs_scrolling(true)?;
}
}
}
}
MouseEventKind::ScrollUp => {
if let Some(block_under_mouse) = self.get_block_under_mouse(mouse) {
if mouse.modifiers.contains(event::KeyModifiers::SHIFT) {
self.handle_horizontal_scrolling(block_under_mouse, false)?;
} else {
if block_under_mouse == self.logs_block.id() {
self.handle_logs_view_scrolling(false)?;
} else if block_under_mouse == self.details_block.id() {
self.handle_details_block_scrolling(false)?;
} else if block_under_mouse == self.debug_block.id() {
self.handle_debug_logs_scrolling(false)?;
}
}
}
}
MouseEventKind::ScrollLeft => {
log::debug!("ScrollLeft (touchpad)");
if let Some(block_under_mouse) = self.get_block_under_mouse(mouse) {
self.handle_horizontal_scrolling(block_under_mouse, false)?;
}
}
MouseEventKind::ScrollRight => {
log::debug!("ScrollRight (touchpad)");
if let Some(block_under_mouse) = self.get_block_under_mouse(mouse) {
self.handle_horizontal_scrolling(block_under_mouse, true)?;
}
}
MouseEventKind::Moved => {}
_ => {}
}
Ok(())
}
pub(super) fn yank_current_log(&mut self) -> Result<()> {
let (indices, state) = (&self.displaying_logs.indices, &self.displaying_logs.state);
let Some(i) = state.selected() else {
log::debug!("No log item selected for yanking");
return Ok(());
};
let raw_idx = indices[i];
let item = &self.raw_logs[raw_idx];
let mut clipboard = Clipboard::new()?;
let yank_content = self.parser.make_yank_content(item);
clipboard.set_text(&yank_content)?;
log::debug!("Copied {} chars to clipboard", yank_content.len());
self.set_display_event(
"Selected log copied to clipboard".to_string(),
Duration::from_millis(DISPLAY_EVENT_DURATION_MS),
None, );
Ok(())
}
pub(super) fn yank_all_displayed_logs(&mut self) -> Result<()> {
let indices = &self.displaying_logs.indices;
if indices.is_empty() {
log::debug!("No log items to yank");
return Ok(());
}
let mut yank_contents = Vec::new();
for &raw_idx in indices.iter() {
let item = &self.raw_logs[raw_idx];
yank_contents.push(self.parser.make_yank_content(item));
}
let combined_content = yank_contents.join("\n\n");
let mut clipboard = Clipboard::new()?;
clipboard.set_text(&combined_content)?;
log::debug!(
"Copied {} log items ({} chars) to clipboard",
yank_contents.len(),
combined_content.len()
);
self.set_display_event(
format!("{} logs copied to clipboard", yank_contents.len()),
Duration::from_millis(DISPLAY_EVENT_DURATION_MS),
None, );
Ok(())
}
pub(super) fn clear_logs(&mut self) {
self.raw_logs.clear();
self.filter_engine.reset();
self.apply_filter();
}
pub(super) fn handle_key(&mut self, key: KeyEvent) -> Result<()> {
if key.kind != KeyEventKind::Press {
return Ok(());
}
if self.show_help_popup {
match key.code {
KeyCode::Char('?') | KeyCode::Esc => {
self.show_help_popup = false;
return Ok(());
}
KeyCode::Char('q') => {
}
_ => return Ok(()), }
}
if !self.filter_input.is_empty() && self.filter_focused {
match key.code {
KeyCode::Esc => {
self.filter_focused = false;
self.filter_input.clear();
self.apply_filter();
return Ok(());
}
KeyCode::Char(c) => {
self.filter_input.push(c);
self.apply_filter();
return Ok(());
}
KeyCode::Backspace => {
self.filter_input.pop();
if self.filter_input.is_empty() {
self.filter_focused = false;
self.apply_filter();
} else {
self.apply_filter();
}
return Ok(());
}
KeyCode::Enter => {
self.filter_focused = false;
if self.filter_input.len() == 1 {
self.filter_input.clear();
self.apply_filter();
}
return Ok(());
}
_ => {}
}
}
match key.code {
KeyCode::Char('q') => {
log::debug!("Quit key pressed");
self.is_exiting = true;
Ok(())
}
KeyCode::Esc => {
if !self.filter_input.is_empty() && !self.filter_focused {
self.filter_input.clear();
self.apply_filter();
}
Ok(())
}
KeyCode::Char('c') => {
if key.modifiers.contains(event::KeyModifiers::CONTROL) {
self.is_exiting = true;
} else {
self.clear_logs();
}
Ok(())
}
KeyCode::Char('j') | KeyCode::Down => {
let focused_block = self.get_display_focused_block();
if focused_block == self.details_block.id() {
self.handle_details_block_scrolling(true)?;
} else if focused_block == self.debug_block.id() {
self.handle_debug_logs_scrolling(true)?;
} else {
self.handle_log_item_scrolling(true, true)?;
}
Ok(())
}
KeyCode::Char('k') | KeyCode::Up => {
let focused_block = self.get_display_focused_block();
if focused_block == self.details_block.id() {
self.handle_details_block_scrolling(false)?;
} else if focused_block == self.debug_block.id() {
self.handle_debug_logs_scrolling(false)?;
} else {
self.handle_log_item_scrolling(false, true)?;
}
Ok(())
}
KeyCode::Char('/') => {
self.filter_input = "/".to_string();
self.filter_focused = true;
self.apply_filter();
Ok(())
}
KeyCode::Char('[') => {
self.detail_level = decrement_detail_level(self.detail_level);
self.filter_engine.reset();
self.rebuild_filtered_list();
self.update_selection_by_uuid();
self.set_display_event(
format!("Detail level: {}", self.detail_level),
Duration::from_millis(DISPLAY_EVENT_DURATION_MS),
None,
);
Ok(())
}
KeyCode::Char(']') => {
let max = self.parser.max_detail_level();
self.detail_level = increment_detail_level(self.detail_level, max);
self.filter_engine.reset();
self.rebuild_filtered_list();
self.update_selection_by_uuid();
self.set_display_event(
format!("Detail level: {}", self.detail_level),
Duration::from_millis(DISPLAY_EVENT_DURATION_MS),
None,
);
Ok(())
}
KeyCode::Char('y') => {
if let Err(e) = self.yank_current_log() {
log::debug!("Failed to yank log content: {}", e);
}
Ok(())
}
KeyCode::Char('a') => {
if let Err(e) = self.yank_all_displayed_logs() {
log::debug!("Failed to yank all displayed logs: {}", e);
}
Ok(())
}
KeyCode::Char('1') => {
self.set_hard_focused_block(self.logs_block.id());
Ok(())
}
KeyCode::Char('2') => {
self.set_hard_focused_block(self.details_block.id());
Ok(())
}
KeyCode::Char('3') => {
if self.show_debug_logs {
self.set_hard_focused_block(self.debug_block.id());
}
Ok(())
}
KeyCode::Char('w') => {
self.text_wrapping_enabled = !self.text_wrapping_enabled;
log::debug!("Text wrapping toggled: {}", self.text_wrapping_enabled);
let message = if self.text_wrapping_enabled {
"Text wrapping enabled"
} else {
"Text wrapping disabled"
};
self.set_display_event(
message.to_string(),
Duration::from_millis(DISPLAY_EVENT_DURATION_MS),
None,
);
Ok(())
}
KeyCode::Char('b') => {
self.show_debug_logs = !self.show_debug_logs;
log::debug!("Debug logs visibility toggled: {}", self.show_debug_logs);
Ok(())
}
KeyCode::Char('h') | KeyCode::Left => {
let focused_block = self.get_display_focused_block();
self.handle_horizontal_scrolling(focused_block, false)?;
Ok(())
}
KeyCode::Char('l') | KeyCode::Right => {
let focused_block = self.get_display_focused_block();
self.handle_horizontal_scrolling(focused_block, true)?;
Ok(())
}
KeyCode::Char('?') => {
self.show_help_popup = !self.show_help_popup;
Ok(())
}
KeyCode::Char(' ') => {
self.after_selection_change()?;
Ok(())
}
KeyCode::Char('d') => {
self.displaying_logs.select_last();
self.update_selected_uuid();
let total_items = self.displaying_logs.len();
let viewport_height = if let Some(area) = self.last_logs_area {
let is_focused = self.is_log_block_focused().unwrap_or(false);
let [main_content_area, _] =
Layout::horizontal([Constraint::Fill(1), Constraint::Length(1)])
.margin(0)
.areas(area);
let [content_area, _] =
Layout::vertical([Constraint::Fill(1), Constraint::Length(1)])
.margin(0)
.areas(main_content_area);
let inner_area = self.logs_block.get_content_rect(content_area, is_focused);
inner_area.height as usize
} else {
1 };
let max_scroll = total_items.saturating_sub(viewport_height);
self.logs_block.set_scroll_position(max_scroll);
self.update_autoscroll_state();
self.update_logs_scrollbar_state();
Ok(())
}
_ => Ok(()),
}
}
}