1use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
2use std::time::Duration;
3
4use crate::app::{App, AppMode};
5use crate::config::Config;
6use crate::feeds::get_all_feeds;
7
8pub enum AppAction {
9 None,
10 Refresh,
11 FeedChanged,
12 Resize,
13}
14
15pub fn handle_events(app: &mut App, config: &Config) -> anyhow::Result<AppAction> {
16 if event::poll(Duration::from_millis(100))? {
17 match event::read()? {
18 Event::Key(key) if key.kind == KeyEventKind::Press => {
19 return handle_key_event(app, key, config);
20 }
21 Event::Resize(_, _) => {
22 return Ok(AppAction::Resize);
23 }
24 _ => {}
25 }
26 }
27 Ok(AppAction::None)
28}
29
30fn handle_key_event(app: &mut App, key: KeyEvent, config: &Config) -> anyhow::Result<AppAction> {
31 let kb = &config.keybindings;
32
33 if app.mode == AppMode::Help {
35 match key.code {
36 KeyCode::Char('?') | KeyCode::Esc => app.toggle_help_menu(),
37 _ => {}
38 }
39 return Ok(AppAction::None);
40 }
41
42 if app.mode == AppMode::FeedMenu {
44 match key.code {
45 KeyCode::Char('j') | KeyCode::Down => {
46 let feeds = get_all_feeds();
47 app.feed_menu_next(feeds.len());
48 }
49 KeyCode::Char('k') | KeyCode::Up => app.feed_menu_previous(),
50 KeyCode::Enter => {
51 let feeds = get_all_feeds();
52 if let Some(feed) = feeds.get(app.feed_menu_selected) {
53 app.select_feed(feed.clone());
54 return Ok(AppAction::FeedChanged);
55 }
56 }
57 KeyCode::Char('f') | KeyCode::Esc => app.toggle_feed_menu(),
58 _ => {}
59 }
60 return Ok(AppAction::None);
61 }
62
63 if app.show_full_article {
65 match key.code {
66 KeyCode::Up | KeyCode::Char('k') => app.scroll_article_up(),
67 KeyCode::Down | KeyCode::Char('j') => app.scroll_article_down(),
68 KeyCode::Char(c) if c == kb.open => app.open_selected()?,
69 KeyCode::Enter | KeyCode::Tab | KeyCode::Esc => app.toggle_article_view(),
70 _ => {}
71 }
72 return Ok(AppAction::None);
73 }
74
75 if app.show_preview && !app.show_full_article {
78 let is_scroll_key = matches!(
79 key.code,
80 KeyCode::Down | KeyCode::Up
81 ) || matches!(
82 key.code,
83 KeyCode::Char(c) if c == kb.scroll_down || c == kb.scroll_up || c == kb.scroll_bottom || c == kb.latest
84 );
85
86 if is_scroll_key {
87 while event::poll(Duration::ZERO)? {
90 if let Event::Key(next_key) = event::read()? {
91 if next_key.kind == KeyEventKind::Press {
92 let is_next_scroll = matches!(
94 next_key.code,
95 KeyCode::Down | KeyCode::Up
96 ) || matches!(
97 next_key.code,
98 KeyCode::Char(c) if c == kb.scroll_down || c == kb.scroll_up || c == kb.scroll_bottom || c == kb.latest
99 );
100
101 if !is_next_scroll {
102 return handle_key_event(app, next_key, config);
104 }
105 }
107 }
108 }
109 return Ok(AppAction::None);
111 }
112 }
113
114 match key.code {
116 KeyCode::Char(c) if c == kb.quit => app.quit(),
117 KeyCode::Char(c) if c == kb.scroll_down => app.next(),
118 KeyCode::Char(c) if c == kb.scroll_up => app.previous(),
119 KeyCode::Char(c) if c == kb.scroll_bottom => app.scroll_to_bottom(),
120 KeyCode::Char(c) if c == kb.open => app.open_selected()?,
121 KeyCode::Char(c) if c == kb.open_new_tab => app.open_selected_new_tab()?,
122 KeyCode::Char(c) if c == kb.latest => app.scroll_to_top(),
123 KeyCode::Char(c) if c == kb.refresh => return Ok(AppAction::Refresh),
124 KeyCode::Char('f') => app.toggle_feed_menu(),
125 KeyCode::Char('s') => app.cycle_sort_order(),
126 KeyCode::Char('t') => app.toggle_date_format(),
127 KeyCode::Char('T') => app.cycle_theme(),
128 KeyCode::Char('p') => app.cycle_image_protocol(),
129 KeyCode::Char('?') => app.toggle_help_menu(),
130 KeyCode::Char('a') => app.fetch_and_show_article(),
131 KeyCode::Char(' ') => {
132 if app.jump_to_ticker_article() {
134 return Ok(AppAction::FeedChanged);
135 }
136 }
137 KeyCode::Tab => app.toggle_preview(),
138 KeyCode::Enter => app.fetch_and_show_article(),
139 KeyCode::Down => app.next(),
141 KeyCode::Up => app.previous(),
142 KeyCode::Esc => app.quit(),
143 _ => {}
144 }
145
146 Ok(AppAction::None)
147}