use super::*;
impl App {
pub(super) fn handle_key_event(&mut self, key: crossterm::event::KeyEvent) -> Result<()> {
if key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL) {
self.action_tx.send(Action::Quit)?;
return Ok(());
}
if self.confirm_dialog.visible {
if let Some(action) = self.confirm_dialog.handle_key_event(key)? {
self.action_tx.send(action)?;
}
return Ok(());
}
if self.path_input.visible {
if let Some(action) = self.path_input.handle_key_event(key)? {
self.action_tx.send(action)?;
}
return Ok(());
}
if self.theme_picker.visible {
if let Some(action) = self.theme_picker.handle_key_event(key)? {
self.action_tx.send(action)?;
}
return Ok(());
}
if self.picker.visible {
if let Some(action) = self.picker.handle_key_event(key)? {
self.action_tx.send(action)?;
}
return Ok(());
}
if self.focus == FocusPanel::Graph && self.git_graph.search_visible() {
self.git_graph.handle_search_key(key)?;
return Ok(());
}
if self.context_menu.visible {
if let Some(action) = self.context_menu.handle_key_event(key)? {
self.action_tx.send(action)?;
}
return Ok(());
}
if key.code == KeyCode::Char('?') {
self.show_help = !self.show_help;
return Ok(());
} else if self.show_help {
self.show_help = false;
return Ok(());
}
match key.code {
KeyCode::Char('q') => {
if self.focus == FocusPanel::Changes && self.file_list.viewing_diff() {
self.file_list.handle_key_event(key)?;
return Ok(());
}
self.action_tx.send(Action::Quit)?;
}
KeyCode::Esc => {
if self.focus == FocusPanel::Changes && self.file_list.viewing_diff() {
self.file_list.handle_key_event(key)?;
} else if self.focus == FocusPanel::Graph && self.git_graph.has_detail() {
self.git_graph.handle_key_event(key)?;
} else {
match self.focus {
FocusPanel::Graph => self.focus = FocusPanel::Changes,
FocusPanel::Changes => self.focus = FocusPanel::Repos,
FocusPanel::Repos => self.action_tx.send(Action::Quit)?,
}
}
}
KeyCode::Tab => {
self.focus = match self.focus {
FocusPanel::Repos => FocusPanel::Changes,
FocusPanel::Changes => FocusPanel::Graph,
FocusPanel::Graph => FocusPanel::Repos,
};
}
KeyCode::BackTab => {
self.focus = match self.focus {
FocusPanel::Repos => FocusPanel::Graph,
FocusPanel::Changes => FocusPanel::Repos,
FocusPanel::Graph => FocusPanel::Changes,
};
}
KeyCode::Char('r') => {
self.action_tx.send(Action::RefreshAll)?;
}
KeyCode::Char('t') => {
self.action_tx.send(Action::OpenThemePicker)?;
}
KeyCode::Char('R') => {
self.action_tx.send(Action::RescanRepos)?;
}
KeyCode::Char('g') => {
self.action_tx.send(Action::ShowGitGraph)?;
}
KeyCode::Char('G') => {
self.action_tx.send(Action::GotoSessionSelected)?;
}
KeyCode::Char('o') => {
self.action_tx.send(Action::OpenSelected)?;
}
KeyCode::Char('v') => {
self.action_tx.send(Action::ReviewSelected)?;
}
KeyCode::Char('a') => {
self.action_tx.send(Action::OpenAddRepo)?;
}
KeyCode::Char('d') => {
if self.repo_list.selected_worktree().is_some() {
self.action_tx.send(Action::RemoveWorktreeSelected)?;
} else if let Some(idx) = self.repo_list.selected_index() {
let entry = &self.repo_list.repos[idx];
let name = entry.name.clone();
let repo_id = RepoId(entry.path.clone());
self.confirm_dialog
.show(format!("Remove {}?", name), Action::RemoveRepo(repo_id));
}
}
KeyCode::Char('s') => {
self.action_tx.send(Action::CycleSortOrder)?;
}
KeyCode::Char('y') => {
let text = match self.focus {
FocusPanel::Repos => self
.repo_list
.selected_repo()
.map(|e| e.path.to_string_lossy().to_string()),
FocusPanel::Changes => self.file_list.selected_path(),
FocusPanel::Graph => self.git_graph.selected_text(),
};
if let Some(text) = text {
use std::io::Write;
let encoded = base64_encode(text.as_bytes());
let _ = write!(std::io::stdout(), "\x1b]52;c;{}\x1b\\", encoded);
let _ = std::io::stdout().flush();
}
}
_ => {
match self.focus {
FocusPanel::Repos => {
if let Some(action) = self.repo_list.handle_key_event(key)? {
self.action_tx.send(action)?;
}
}
FocusPanel::Changes => {
if let Some(action) = self.file_list.handle_key_event(key)? {
self.action_tx.send(action)?;
}
}
FocusPanel::Graph => {
if let Some(action) = self.git_graph.handle_key_event(key)? {
self.action_tx.send(action)?;
}
}
}
}
}
Ok(())
}
pub(super) fn handle_mouse_event(&mut self, mouse: crossterm::event::MouseEvent) -> Result<()> {
use crossterm::event::{MouseButton, MouseEventKind};
if self.picker.visible
|| self.theme_picker.visible
|| self.path_input.visible
|| self.confirm_dialog.visible
{
return Ok(());
}
if self.context_menu.visible {
if let Some(action) = self.context_menu.handle_mouse_event(mouse)? {
self.action_tx.send(action)?;
} else if matches!(mouse.kind, MouseEventKind::Down(MouseButton::Left)) {
self.context_menu.hide();
}
return Ok(());
}
let pos = ratatui::layout::Position::new(mouse.column, mouse.row);
const GRAB_ZONE: u16 = 2;
if self.repo_area.width > 0 {
let (border1, border2, mouse_pos, total, origin) = if self.horizontal_layout {
(
self.repo_area.x + self.repo_area.width,
self.changes_area.x + self.changes_area.width,
mouse.column,
self.repo_area.width + self.changes_area.width + self.graph_area.width,
self.repo_area.x,
)
} else {
(
self.repo_area.y + self.repo_area.height,
self.changes_area.y + self.changes_area.height,
mouse.row,
self.repo_area.height + self.changes_area.height + self.graph_area.height,
self.repo_area.y,
)
};
match mouse.kind {
MouseEventKind::Down(MouseButton::Left) => {
let d1 = mouse_pos.abs_diff(border1);
let d2 = mouse_pos.abs_diff(border2);
if d1 <= GRAB_ZONE && (d1 <= d2 || d2 > GRAB_ZONE) {
self.dragging_border = Some(0);
} else if d2 <= GRAB_ZONE {
self.dragging_border = Some(1);
} else {
self.dragging_border = None;
}
}
MouseEventKind::Drag(MouseButton::Left) if self.dragging_border.is_some() => {
let rel = mouse_pos.saturating_sub(origin) as f64 / total as f64;
let min_f = 3.0 / total as f64;
match self.dragging_border {
Some(0) => {
self.border_frac[0] = rel.clamp(min_f, self.border_frac[1] - min_f);
}
Some(1) => {
self.border_frac[1] =
rel.clamp(self.border_frac[0] + min_f, 1.0 - min_f);
}
_ => {}
}
return Ok(());
}
MouseEventKind::Up(MouseButton::Left) if self.dragging_border.is_some() => {
self.dragging_border = None;
return Ok(());
}
_ => {}
}
}
if matches!(mouse.kind, MouseEventKind::Down(MouseButton::Left)) {
if self.repo_area.contains(pos) {
self.focus = FocusPanel::Repos;
} else if self.changes_area.contains(pos) {
self.focus = FocusPanel::Changes;
} else if self.graph_area.contains(pos) {
self.focus = FocusPanel::Graph;
}
}
if self.repo_area.contains(pos) {
if let Some(action) = self.repo_list.handle_mouse_event(mouse)? {
self.action_tx.send(action)?;
}
} else if self.changes_area.contains(pos) {
if let Some(action) = self.file_list.handle_mouse_event(mouse)? {
self.action_tx.send(action)?;
}
} else if self.graph_area.contains(pos)
&& let Some(action) = self.git_graph.handle_mouse_event(mouse)?
{
self.action_tx.send(action)?;
}
Ok(())
}
}