use crate::model::composite_buffer::CompositeBuffer;
use crate::model::event::BufferId;
use crate::view::composite_view::CompositeViewState;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
#[derive(Debug, Clone)]
pub enum RoutedEvent {
CompositeScroll(ScrollAction),
SwitchPane(Direction),
NavigateHunk(Direction),
ToSourceBuffer {
buffer_id: BufferId,
action: BufferAction,
},
PaneCursor(CursorAction),
Selection(SelectionAction),
Yank,
Blocked(&'static str),
Close,
Unhandled,
}
#[derive(Debug, Clone, Copy)]
pub enum SelectionAction {
StartVisual,
StartVisualLine,
ClearSelection,
ExtendUp,
ExtendDown,
ExtendLeft,
ExtendRight,
}
#[derive(Debug, Clone, Copy)]
pub enum ScrollAction {
Up(usize),
Down(usize),
PageUp,
PageDown,
ToTop,
ToBottom,
ToRow(usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Next,
Prev,
}
#[derive(Debug, Clone)]
pub enum BufferAction {
Insert(char),
InsertString(String),
Delete,
Backspace,
NewLine,
}
#[derive(Debug, Clone, Copy)]
pub enum CursorAction {
Up,
Down,
Left,
Right,
LineStart,
LineEnd,
WordLeft,
WordRight,
Top,
Bottom,
}
pub struct CompositeInputRouter;
impl CompositeInputRouter {
pub fn route_key_event(
_composite: &CompositeBuffer,
_view_state: &CompositeViewState,
event: &KeyEvent,
) -> RoutedEvent {
match (event.modifiers, event.code) {
(KeyModifiers::NONE, KeyCode::Char('j')) => {
RoutedEvent::CompositeScroll(ScrollAction::Down(1))
}
(KeyModifiers::NONE, KeyCode::Char('k')) => {
RoutedEvent::CompositeScroll(ScrollAction::Up(1))
}
(KeyModifiers::NONE, KeyCode::Tab) => RoutedEvent::SwitchPane(Direction::Next),
(KeyModifiers::SHIFT, KeyCode::BackTab) => RoutedEvent::SwitchPane(Direction::Prev),
_ => RoutedEvent::Unhandled,
}
}
pub fn display_to_source(
composite: &CompositeBuffer,
_view_state: &CompositeViewState,
display_row: usize,
display_col: usize,
pane_index: usize,
) -> Option<SourceCoordinate> {
let aligned_row = composite.alignment.get_row(display_row)?;
let source_ref = aligned_row.get_pane_line(pane_index)?;
Some(SourceCoordinate {
buffer_id: composite.sources.get(pane_index)?.buffer_id,
byte_offset: source_ref.byte_range.start + display_col,
line: source_ref.line,
column: display_col,
})
}
pub fn click_to_pane(
view_state: &CompositeViewState,
click_x: u16,
area_x: u16,
) -> Option<usize> {
let mut x = area_x;
for (i, &width) in view_state.pane_widths.iter().enumerate() {
if click_x >= x && click_x < x + width {
return Some(i);
}
x += width + 1; }
None
}
pub fn navigate_to_hunk(
composite: &CompositeBuffer,
view_state: &mut CompositeViewState,
direction: Direction,
) -> bool {
let current_row = view_state.scroll_row;
let new_row = match direction {
Direction::Next => composite.alignment.next_hunk_row(current_row),
Direction::Prev => composite.alignment.prev_hunk_row(current_row),
};
if let Some(row) = new_row {
view_state.scroll_row = row;
true
} else {
false
}
}
}
#[derive(Debug, Clone)]
pub struct SourceCoordinate {
pub buffer_id: BufferId,
pub byte_offset: usize,
pub line: usize,
pub column: usize,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::composite_buffer::{CompositeLayout, SourcePane};
fn create_test_composite() -> (CompositeBuffer, CompositeViewState) {
let sources = vec![
SourcePane::new(BufferId(1), "OLD", false),
SourcePane::new(BufferId(2), "NEW", true),
];
let composite = CompositeBuffer::new(
BufferId(0),
"Test Diff".to_string(),
"diff-view".to_string(),
CompositeLayout::default(),
sources,
);
let view_state = CompositeViewState::new(BufferId(0), 2);
(composite, view_state)
}
#[test]
fn test_scroll_routing() {
let (composite, view_state) = create_test_composite();
let event = KeyEvent::new(KeyCode::Down, KeyModifiers::NONE);
let result = CompositeInputRouter::route_key_event(&composite, &view_state, &event);
matches!(result, RoutedEvent::CompositeScroll(ScrollAction::Down(1)));
}
#[test]
fn test_pane_switch_routing() {
let (composite, view_state) = create_test_composite();
let event = KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE);
let result = CompositeInputRouter::route_key_event(&composite, &view_state, &event);
matches!(result, RoutedEvent::SwitchPane(Direction::Next));
}
#[test]
fn test_readonly_blocking() {
let (composite, view_state) = create_test_composite();
let event = KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE);
let result = CompositeInputRouter::route_key_event(&composite, &view_state, &event);
matches!(result, RoutedEvent::Blocked(_));
}
#[test]
fn test_editable_routing() {
let (composite, mut view_state) = create_test_composite();
view_state.focused_pane = 1;
let event = KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE);
let result = CompositeInputRouter::route_key_event(&composite, &view_state, &event);
matches!(
result,
RoutedEvent::ToSourceBuffer {
buffer_id: BufferId(2),
action: BufferAction::Insert('x'),
}
);
}
}