datui_lib/inspector/
inspector_keys.rs1use crate::app::form::ListMove;
5use crate::{
6 App, AppEvent, Overlay, app::modals::copy_modal, inspector::inspector_drill,
7 inspector::inspector_modal, inspector::inspector_reader,
8};
9use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
10
11impl App {
12 pub fn enter_inspects(&self) -> bool {
16 self.at_table()
17 && self
18 .data_table_state
19 .as_ref()
20 .is_some_and(|state| !state.can_drill_down())
21 }
22
23 pub(crate) fn open_inspector(&mut self) {
26 let Some(state) = self.data_table_state.as_ref() else {
27 return;
28 };
29 if state.inspect_row().is_none() {
30 self.flash_note("No row to inspect".to_string());
31 return;
32 }
33 self.inspector_modal
34 .open(state.inspect_fields(), state.current_column());
35 self.open_overlay(Overlay::Inspect);
36 }
37
38 pub(super) fn close_inspector(&mut self) {
39 self.close_overlay();
40 }
41
42 fn refresh_inspector_list(&mut self) {
45 if let Some(state) = self.data_table_state.as_ref() {
46 let row = state.inspect_row();
47 crate::widgets::inspector::refresh_list(&mut self.inspector_modal, state, row.as_ref());
48 }
49 }
50
51 pub(super) fn inspector_pane(&self) -> Option<crate::widgets::inspector::Pane> {
54 let modal = &self.inspector_modal;
55 if modal.drill.is_some() {
56 return modal.pane.as_ref().map(|(_, pane)| pane.clone());
57 }
58 let state = self.data_table_state.as_ref()?;
59 let row = state.inspect_row()?;
60 let field = modal.focused()?;
61 if let Some(pane) = modal.pane_for(row.frame, row.row, &field.name) {
62 return Some(pane.clone());
63 }
64 let shown = crate::widgets::inspector::shown(field, &row, modal.read.as_ref(), state);
65 Some(crate::widgets::inspector::pane(
66 &field.dtype,
67 &shown,
68 &crate::widgets::inspector::PaneAsk {
69 choice: modal.view,
70 width: modal
71 .pane
72 .as_ref()
73 .map_or(80, |(key, _)| key.width as usize),
74 table: None,
75 indented: crate::widgets::inspector::Indented::None,
76 not_json: modal.known_not_json(row.frame, row.row, &field.name),
77 unpacked: modal.unpacked(&(row.frame, row.row, field.name.clone())),
78 read_key: "Enter",
79 },
80 ))
81 }
82
83 pub(crate) fn inspector_key(&mut self, event: &KeyEvent) -> Option<AppEvent> {
86 if !event.is_press() {
87 return None;
88 }
89 let modal = &mut self.inspector_modal;
90 if modal.finding {
91 match event.code {
92 KeyCode::Esc => modal.clear_find(),
93 KeyCode::Enter | KeyCode::Tab | KeyCode::Down => modal.finding = false,
94 KeyCode::Up => {
95 modal.finding = false;
96 self.refresh_inspector_list();
97 self.inspector_modal.move_field(ListMove::Up);
98 return None;
99 }
100 KeyCode::Backspace => modal.find_backspace(),
101 KeyCode::Char(c) => modal.find_key(c, event.modifiers),
102 _ => {}
103 }
104 self.refresh_inspector_list();
107 return None;
108 }
109 if modal.value_find.as_ref().is_some_and(|f| f.editing) {
110 self.value_find_key(event);
111 return None;
112 }
113 if event
114 .modifiers
115 .intersects(KeyModifiers::CONTROL | KeyModifiers::ALT)
116 {
117 return None;
118 }
119 if modal.focus == inspector_modal::Focus::Value {
120 return self.inspector_value_key(event);
121 }
122 if modal.drill.is_some() {
123 return self.drill_key(event);
124 }
125 self.refresh_inspector_list();
126 let modal = &mut self.inspector_modal;
127 if let Some(step) = ListMove::from_key(event) {
128 modal.move_field(step);
129 return None;
130 }
131 match event.code {
132 KeyCode::Esc if !modal.filter.is_empty() => modal.clear_find(),
134 KeyCode::Esc if modal.compare => {
135 modal.compare = false;
136 modal.filled_only = false;
137 }
138 KeyCode::Esc | KeyCode::Char(' ') => self.close_inspector(),
139 KeyCode::Tab | KeyCode::BackTab => {
141 if modal.focused().is_some() {
142 modal.focus = inspector_modal::Focus::Value;
143 }
144 }
145 KeyCode::Char('/') => modal.finding = true,
146 KeyCode::Char('e') => self.inspector_view(),
147 KeyCode::Char('w') => self.inspector_wrap(),
148 KeyCode::Char('f') => {
149 modal.filled_only = !modal.filled_only;
150 modal.list_offset = 0;
151 }
152 KeyCode::Char('s') => {
153 modal.order = modal.order.next();
154 modal.list_offset = 0;
155 }
156 KeyCode::Char('c') => {
157 modal.compare = !modal.compare;
158 if !modal.compare {
159 modal.filled_only = false;
160 }
161 }
162 KeyCode::Char('m') => self.toggle_inspector_pin(),
163 KeyCode::Right | KeyCode::Char('l') => return self.scroll_key(crate::Scroll::Next),
164 KeyCode::Left | KeyCode::Char('h') => return self.scroll_key(crate::Scroll::Prev),
165 KeyCode::Char('y') => self.copy_inspected_field(),
166 KeyCode::Char('Y') => self.copy_inspected_row(),
167 KeyCode::Char('o') => self.open_inspected_value(),
168 KeyCode::Char('r') => self.read_focused_field(),
169 KeyCode::Enter => return self.inspector_enter(),
170 _ => {}
171 }
172 None
173 }
174
175 fn drill_key(&mut self, event: &KeyEvent) -> Option<AppEvent> {
178 let modal = &mut self.inspector_modal;
179 if let Some(step) = ListMove::from_key(event) {
180 modal.move_field(step);
181 return None;
182 }
183 match event.code {
184 KeyCode::Esc | KeyCode::Left | KeyCode::Char('h') => {
185 modal.drill_out();
186 }
187 KeyCode::Char(' ') => self.close_inspector(),
188 KeyCode::Tab | KeyCode::BackTab
190 if modal
191 .drill
192 .as_ref()
193 .is_some_and(|drill| drill.level().focused().is_some()) =>
194 {
195 modal.focus = inspector_modal::Focus::Value;
196 }
197 KeyCode::Char('e') => self.inspector_view(),
198 KeyCode::Char('w') => self.inspector_wrap(),
199 KeyCode::Char('y') => self.copy_drilled_item(),
200 KeyCode::Enter | KeyCode::Right | KeyCode::Char('l') => {
201 let drill = modal.drill.as_ref()?;
202 let (frame, row) = (drill.frame, drill.row);
203 let (label, node) = drill.level().focused()?;
204 let path = drill.item_key(&label);
205 if node.opens() && !modal.known_not_json(frame, row, &path) {
206 self.inspector_open(frame, row, label, path, node);
207 }
208 }
209 _ => {}
210 }
211 None
212 }
213
214 pub(super) fn inspector_view(&mut self) {
217 if let Some(view) = self.inspector_pane().and_then(|pane| pane.next_view()) {
218 self.inspector_modal.choose_view(view);
219 }
220 }
221
222 pub(super) fn inspector_wrap(&mut self) {
224 let modal = &mut self.inspector_modal;
225 modal.wrap = match modal.wrap {
226 inspector_reader::Wrap::Word => inspector_reader::Wrap::Hard,
227 inspector_reader::Wrap::Hard => inspector_reader::Wrap::Word,
228 };
229 }
230
231 fn toggle_inspector_pin(&mut self) {
233 let Some(row) = self.data_table_state.as_ref().and_then(|s| s.inspect_row()) else {
234 return;
235 };
236 let modal = &mut self.inspector_modal;
237 let here = modal
238 .pinned
239 .as_ref()
240 .is_some_and(|p| (p.frame, p.row) == (row.frame, row.row));
241 if here {
242 modal.pinned = None;
243 self.flash_note("Unpinned".to_string());
244 } else {
245 let n = row.display_row;
246 modal.pinned = Some(row);
247 modal.compare = true;
248 self.flash_note(format!(
249 "Pinned row {}; Compare shows it",
250 copy_modal::thousands(n)
251 ));
252 }
253 }
254
255 fn inspector_enter(&mut self) -> Option<AppEvent> {
258 let state = self.data_table_state.as_ref()?;
259 if state.can_drill_down() {
260 self.close_inspector();
261 self.drill_selected_row();
262 return None;
263 }
264 let row = state.inspect_row()?;
265 let field = self.inspector_modal.focused().cloned()?;
266 let shown = crate::widgets::inspector::shown(
267 &field,
268 &row,
269 self.inspector_modal.read.as_ref(),
270 state,
271 );
272 use crate::widgets::inspector::Shown;
273 match shown {
274 Shown::Unread | Shown::Failed(_) => self.read_focused_field(),
276 Shown::Value(ref v)
277 if crate::widgets::inspector::value_opens(v)
278 && !self
279 .inspector_modal
280 .known_not_json(row.frame, row.row, &field.name) =>
281 {
282 let column = if field.buffered() {
283 row.values.column(&field.name).ok()
284 } else {
285 self.inspector_modal
286 .read_values(row.frame, row.row)
287 .and_then(|values| values.column(&field.name).ok())
288 };
289 if let Some(column) = column {
290 let node =
291 inspector_drill::Node::Native(column.as_materialized_series().clone());
292 let path = inspector_drill::path_key([field.name.as_str()]);
293 self.inspector_open(row.frame, row.row, field.name.clone(), path, node);
294 }
295 }
296 _ => {}
297 }
298 None
299 }
300}