binocular/preview/structured_log/
actions.rs1use crate::preview::types::LogPreview;
2use crossterm::event::{KeyCode, KeyEvent};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub(crate) enum LogViewerOutcome {
6 None,
7 ExitApp,
8 FocusSearch,
9}
10
11pub(crate) enum LogViewerAction {
12 Filter(FilterAction),
13 Cursor(CursorAction),
14 Column(ColumnAction),
15 Modal(ModalAction),
16 TogglePause,
17 ToggleMark,
18 Copy { raw: bool },
19 ResetView,
20 Exit,
21}
22
23pub(crate) enum FilterAction {
24 StartEditing,
25 StopEditing,
26 Backspace,
27 Insert(char),
28}
29
30pub(crate) enum CursorAction {
31 ToNewest,
32 ToOldest,
33 Down(usize),
34 Up(usize),
35}
36
37pub(crate) enum ColumnAction {
38 MoveLeft,
39 MoveRight,
40 HideSelected,
41 IsolateSelected,
42 OpenPicker,
43 Resize(i32),
44}
45
46pub(crate) enum ModalAction {
47 Close,
48 Apply,
49 Down,
50 Up,
51 Toggle { advance: bool },
52}
53
54pub(crate) fn action_for_key(lp: &LogPreview, key: KeyEvent) -> Option<LogViewerAction> {
55 if lp.filter_state.col_modal.is_some() {
56 return modal_action_for_key(key).map(LogViewerAction::Modal);
57 }
58
59 if lp.filter_state.input_active {
60 return filter_action_for_key(key).map(LogViewerAction::Filter);
61 }
62
63 match key.code {
64 KeyCode::Char('/') => Some(LogViewerAction::Filter(FilterAction::StartEditing)),
65 KeyCode::Char('r') => Some(LogViewerAction::ResetView),
66 KeyCode::Char('G') => Some(LogViewerAction::Cursor(CursorAction::ToOldest)),
67 KeyCode::Char('g') => Some(LogViewerAction::Cursor(CursorAction::ToNewest)),
68 KeyCode::Char('j') | KeyCode::Down => Some(LogViewerAction::Cursor(CursorAction::Down(1))),
69 KeyCode::Char('k') | KeyCode::Up => Some(LogViewerAction::Cursor(CursorAction::Up(1))),
70 KeyCode::Char('d') => Some(LogViewerAction::Cursor(CursorAction::Down(20))),
71 KeyCode::Char('u') => Some(LogViewerAction::Cursor(CursorAction::Up(20))),
72 KeyCode::Char('h') | KeyCode::Left => Some(LogViewerAction::Column(ColumnAction::MoveLeft)),
73 KeyCode::Char('l') | KeyCode::Right => {
74 Some(LogViewerAction::Column(ColumnAction::MoveRight))
75 }
76 KeyCode::Char('H') => Some(LogViewerAction::Column(ColumnAction::HideSelected)),
77 KeyCode::Char('o') => Some(LogViewerAction::Column(ColumnAction::IsolateSelected)),
78 KeyCode::Char('a') => Some(LogViewerAction::Column(ColumnAction::OpenPicker)),
79 KeyCode::Char('<') => Some(LogViewerAction::Column(ColumnAction::Resize(-5))),
80 KeyCode::Char('>') => Some(LogViewerAction::Column(ColumnAction::Resize(5))),
81 KeyCode::Char('p') => Some(LogViewerAction::TogglePause),
82 KeyCode::Tab => Some(LogViewerAction::ToggleMark),
83 KeyCode::Char('y') => Some(LogViewerAction::Copy { raw: false }),
84 KeyCode::Char('Y') => Some(LogViewerAction::Copy { raw: true }),
85 KeyCode::Esc | KeyCode::Char('q') => Some(LogViewerAction::Exit),
86 _ => None,
87 }
88}
89
90fn filter_action_for_key(key: KeyEvent) -> Option<FilterAction> {
91 match key.code {
92 KeyCode::Esc | KeyCode::Enter => Some(FilterAction::StopEditing),
93 KeyCode::Backspace => Some(FilterAction::Backspace),
94 KeyCode::Char(c) => Some(FilterAction::Insert(c)),
95 _ => None,
96 }
97}
98
99fn modal_action_for_key(key: KeyEvent) -> Option<ModalAction> {
100 match key.code {
101 KeyCode::Esc => Some(ModalAction::Close),
102 KeyCode::Enter => Some(ModalAction::Apply),
103 KeyCode::Char('j') | KeyCode::Down => Some(ModalAction::Down),
104 KeyCode::Char('k') | KeyCode::Up => Some(ModalAction::Up),
105 KeyCode::Char(' ') => Some(ModalAction::Toggle { advance: false }),
106 KeyCode::Tab => Some(ModalAction::Toggle { advance: true }),
107 _ => None,
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use crate::preview::structured_log::{preview_content, LogEntry, LogFormat, StructuredLog};
115 use crate::preview::PreviewContent;
116 use crossterm::event::KeyModifiers;
117
118 fn entry(fields: &[(&str, &str)], raw: &str) -> LogEntry {
119 LogEntry {
120 fields: fields
121 .iter()
122 .map(|(key, value)| (key.to_string(), value.to_string()))
123 .collect(),
124 raw: raw.to_string(),
125 }
126 }
127
128 #[test]
129 fn normal_mode_keys_map_to_explicit_actions() {
130 let PreviewContent::StructuredLog(preview) = preview_content(StructuredLog {
131 entries: vec![entry(&[("level", "info")], "level=info")],
132 total_lines: 1,
133 all_fields: vec!["level".to_string()],
134 format: LogFormat::Logfmt,
135 }) else {
136 panic!("expected structured log preview");
137 };
138
139 let key = KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE);
140 assert!(matches!(
141 action_for_key(&preview, key),
142 Some(LogViewerAction::Cursor(CursorAction::Down(20)))
143 ));
144
145 let key = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE);
146 assert!(matches!(
147 action_for_key(&preview, key),
148 Some(LogViewerAction::Column(ColumnAction::OpenPicker))
149 ));
150 }
151}