1use crate::app::keybinding_editor::{
6 BindingSource, ContextFilter, DeleteResult, EditMode, KeybindingEditor, SearchMode,
7 SourceFilter,
8};
9use crate::input::keybindings::{format_keybinding, KeybindingResolver};
10use crate::view::theme::Theme;
11use crate::view::ui::scrollbar::{render_scrollbar, ScrollbarColors};
12use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
13use ratatui::{
14 layout::{Constraint, Layout, Rect},
15 style::{Modifier, Style},
16 text::{Line, Span},
17 widgets::{Block, Borders, Clear, Paragraph},
18 Frame,
19};
20use rust_i18n::t;
21
22pub fn render_keybinding_editor(
24 frame: &mut Frame,
25 area: Rect,
26 editor: &mut KeybindingEditor,
27 theme: &Theme,
28) {
29 let modal_width = (area.width as f32 * 0.90).min(120.0) as u16;
31 let modal_height = (area.height as f32 * 0.90) as u16;
32 let modal_width = modal_width.max(60).min(area.width.saturating_sub(2));
33 let modal_height = modal_height.max(20).min(area.height.saturating_sub(2));
34
35 let x = (area.width.saturating_sub(modal_width)) / 2;
36 let y = (area.height.saturating_sub(modal_height)) / 2;
37
38 let modal_area = Rect {
39 x,
40 y,
41 width: modal_width,
42 height: modal_height,
43 };
44
45 frame.render_widget(Clear, modal_area);
47
48 let title = format!(
50 " {} \u{2500} [{}] ",
51 t!("keybinding_editor.title"),
52 editor.active_keymap
53 );
54 let block = Block::default()
55 .title(title)
56 .borders(Borders::ALL)
57 .border_style(Style::default().fg(theme.popup_border_fg))
58 .style(Style::default().bg(theme.popup_bg).fg(theme.popup_text_fg));
59
60 let inner = block.inner(modal_area);
61 frame.render_widget(block, modal_area);
62
63 let chunks = Layout::vertical([
65 Constraint::Length(3), Constraint::Min(5), Constraint::Length(1), ])
69 .split(inner);
70
71 editor.layout.modal_area = modal_area;
73 editor.layout.table_area = chunks[1];
74 editor.layout.table_first_row_y = chunks[1].y + 2; editor.layout.search_bar = Some(Rect {
76 x: inner.x,
77 y: inner.y + 1, width: inner.width,
79 height: 1,
80 });
81 editor.layout.dialog_buttons = None;
83 editor.layout.dialog_key_field = None;
84 editor.layout.dialog_action_field = None;
85 editor.layout.dialog_context_field = None;
86 editor.layout.confirm_buttons = None;
87
88 render_header(frame, chunks[0], editor, theme);
89 render_table(frame, chunks[1], editor, theme);
90 render_footer(frame, chunks[2], editor, theme);
91
92 if editor.showing_help {
94 render_help_overlay(frame, inner, theme);
95 }
96
97 if let Some(dialog) = editor.edit_dialog.take() {
99 render_edit_dialog(frame, inner, &dialog, editor, theme);
100 editor.edit_dialog = Some(dialog);
101 }
102
103 if editor.showing_confirm_dialog {
104 render_confirm_dialog(frame, inner, editor, theme);
105 }
106}
107
108fn render_header(frame: &mut Frame, area: Rect, editor: &KeybindingEditor, theme: &Theme) {
110 let chunks = Layout::vertical([
111 Constraint::Length(1), Constraint::Length(1), Constraint::Length(1), ])
115 .split(area);
116
117 let mut path_spans = vec![
119 Span::styled(
120 format!(" {} ", t!("keybinding_editor.label_config")),
121 Style::default().fg(theme.popup_text_fg),
122 ),
123 Span::styled(
124 &editor.config_file_path,
125 Style::default().fg(theme.diagnostic_info_fg),
126 ),
127 ];
128 if !editor.keymap_names.is_empty() {
129 path_spans.push(Span::styled(
130 format!(" {} ", t!("keybinding_editor.label_maps")),
131 Style::default().fg(theme.popup_text_fg),
132 ));
133 path_spans.push(Span::styled(
134 editor.keymap_names.join(", "),
135 Style::default().fg(theme.popup_text_fg),
136 ));
137 }
138 frame.render_widget(Paragraph::new(Line::from(path_spans)), chunks[0]);
139
140 if editor.search_active {
142 let search_spans = match editor.search_mode {
143 SearchMode::Text => {
144 let mut spans = vec![
145 Span::styled(
146 format!(" {} ", t!("keybinding_editor.label_search")),
147 Style::default()
148 .fg(theme.help_key_fg)
149 .add_modifier(Modifier::BOLD),
150 ),
151 Span::styled(
152 &editor.search_query,
153 Style::default().fg(theme.popup_text_fg),
154 ),
155 ];
156 if editor.search_focused {
157 spans.push(Span::styled("_", Style::default().fg(theme.cursor)));
158 spans.push(Span::styled(
159 format!(" {}", t!("keybinding_editor.search_text_hint")),
160 Style::default().fg(theme.popup_text_fg),
161 ));
162 }
163 spans
164 }
165 SearchMode::RecordKey => {
166 let key_text = if editor.search_key_display.is_empty() {
167 t!("keybinding_editor.press_a_key").to_string()
168 } else {
169 editor.search_key_display.clone()
170 };
171 vec![
172 Span::styled(
173 format!(" {} ", t!("keybinding_editor.label_record_key")),
174 Style::default()
175 .fg(theme.diagnostic_warning_fg)
176 .add_modifier(Modifier::BOLD),
177 ),
178 Span::styled(key_text, Style::default().fg(theme.popup_text_fg)),
179 Span::styled(
180 format!(" {}", t!("keybinding_editor.search_record_hint")),
181 Style::default().fg(theme.popup_text_fg),
182 ),
183 ]
184 }
185 };
186 frame.render_widget(Paragraph::new(Line::from(search_spans)), chunks[1]);
187 } else {
188 let hint = Line::from(vec![
189 Span::styled(" ", Style::default()),
190 Span::styled(
191 t!("keybinding_editor.search_hint").to_string(),
192 Style::default().fg(theme.popup_text_fg),
193 ),
194 ]);
195 frame.render_widget(Paragraph::new(hint), chunks[1]);
196 }
197
198 let total = editor.bindings.len();
200 let filtered = editor.filtered_indices.len();
201 let count_str = if filtered == total {
202 t!("keybinding_editor.bindings_count", count = total).to_string()
203 } else {
204 t!(
205 "keybinding_editor.bindings_filtered",
206 filtered = filtered,
207 total = total
208 )
209 .to_string()
210 };
211
212 let filter_spans = vec![
213 Span::styled(
214 format!(" {} ", t!("keybinding_editor.label_context")),
215 Style::default().fg(theme.popup_text_fg),
216 ),
217 Span::styled(
218 format!("[{}]", editor.context_filter_display()),
219 Style::default().fg(if editor.context_filter == ContextFilter::All {
220 theme.popup_text_fg
221 } else {
222 theme.diagnostic_info_fg
223 }),
224 ),
225 Span::styled(
226 format!(" {} ", t!("keybinding_editor.label_source")),
227 Style::default().fg(theme.popup_text_fg),
228 ),
229 Span::styled(
230 format!("[{}]", editor.source_filter_display()),
231 Style::default().fg(if editor.source_filter == SourceFilter::All {
232 theme.popup_text_fg
233 } else {
234 theme.diagnostic_info_fg
235 }),
236 ),
237 Span::styled(
238 format!(" {}", count_str),
239 Style::default().fg(theme.popup_text_fg),
240 ),
241 Span::styled(
242 if editor.has_changes {
243 format!(" {}", t!("keybinding_editor.modified"))
244 } else {
245 String::new()
246 },
247 Style::default().fg(theme.diagnostic_warning_fg),
248 ),
249 ];
250 frame.render_widget(Paragraph::new(Line::from(filter_spans)), chunks[2]);
251}
252
253fn render_table(frame: &mut Frame, area: Rect, editor: &mut KeybindingEditor, theme: &Theme) {
255 if area.height < 2 {
256 return;
257 }
258
259 let inner_width = area.width.saturating_sub(2); let key_col_width = (inner_width as f32 * 0.16).min(20.0) as u16;
263 let action_name_col_width = (inner_width as f32 * 0.22).min(28.0) as u16;
264 let context_col_width = 14u16;
265 let source_col_width = 8u16;
266 let fixed_cols =
267 key_col_width + action_name_col_width + context_col_width + source_col_width + 5; let description_col_width = inner_width.saturating_sub(fixed_cols);
269
270 let header = Line::from(vec![
272 Span::styled(" ", Style::default()),
273 Span::styled(
274 pad_right(&t!("keybinding_editor.header_key"), key_col_width as usize),
275 Style::default()
276 .fg(theme.help_key_fg)
277 .add_modifier(Modifier::BOLD),
278 ),
279 Span::styled(" ", Style::default()),
280 Span::styled(
281 pad_right(
282 &t!("keybinding_editor.header_action"),
283 action_name_col_width as usize,
284 ),
285 Style::default()
286 .fg(theme.help_key_fg)
287 .add_modifier(Modifier::BOLD),
288 ),
289 Span::styled(" ", Style::default()),
290 Span::styled(
291 pad_right(
292 &t!("keybinding_editor.header_description"),
293 description_col_width as usize,
294 ),
295 Style::default()
296 .fg(theme.help_key_fg)
297 .add_modifier(Modifier::BOLD),
298 ),
299 Span::styled(" ", Style::default()),
300 Span::styled(
301 pad_right(
302 &t!("keybinding_editor.header_context"),
303 context_col_width as usize,
304 ),
305 Style::default()
306 .fg(theme.help_key_fg)
307 .add_modifier(Modifier::BOLD),
308 ),
309 Span::styled(" ", Style::default()),
310 Span::styled(
311 pad_right(
312 &t!("keybinding_editor.header_source"),
313 source_col_width as usize,
314 ),
315 Style::default()
316 .fg(theme.help_key_fg)
317 .add_modifier(Modifier::BOLD),
318 ),
319 ]);
320 frame.render_widget(Paragraph::new(header), Rect { height: 1, ..area });
321
322 if area.height > 1 {
324 let sep = "\u{2500}".repeat(inner_width as usize);
325 frame.render_widget(
326 Paragraph::new(Line::from(Span::styled(
327 format!(" {}", sep),
328 Style::default().fg(theme.popup_text_fg),
329 ))),
330 Rect {
331 y: area.y + 1,
332 height: 1,
333 ..area
334 },
335 );
336 }
337
338 let table_area = Rect {
340 y: area.y + 2,
341 height: area.height.saturating_sub(2),
342 ..area
343 };
344
345 editor.scroll.set_viewport(table_area.height);
347 editor
348 .scroll
349 .set_content_height(editor.filtered_indices.len() as u16);
350
351 let visible_rows = table_area.height as usize;
352 let scroll_offset = editor.scroll.offset as usize;
353
354 for (display_idx, &binding_idx) in editor
355 .filtered_indices
356 .iter()
357 .skip(scroll_offset)
358 .take(visible_rows)
359 .enumerate()
360 {
361 let row_y = table_area.y + display_idx as u16;
362 if row_y >= table_area.y + table_area.height {
363 break;
364 }
365
366 let binding = &editor.bindings[binding_idx];
367 let is_selected = scroll_offset + display_idx == editor.selected;
368
369 let (row_bg, row_fg) = if is_selected {
370 (theme.popup_selection_bg, theme.popup_text_fg)
371 } else {
372 (theme.popup_bg, theme.popup_text_fg)
373 };
374
375 let key_style = Style::default()
376 .fg(if is_selected {
377 theme.popup_text_fg
378 } else {
379 theme.help_key_fg
380 })
381 .bg(row_bg);
382 let action_name_style = Style::default()
383 .fg(if is_selected {
384 theme.popup_text_fg
385 } else {
386 theme.diagnostic_info_fg
387 })
388 .bg(row_bg);
389 let action_style = Style::default().fg(row_fg).bg(row_bg);
390 let context_style = Style::default()
391 .fg(if is_selected {
392 row_fg
393 } else {
394 theme.popup_text_fg
395 })
396 .bg(row_bg);
397 let source_style = Style::default()
398 .fg(if binding.source == BindingSource::Custom {
399 if is_selected {
400 theme.popup_text_fg
401 } else {
402 theme.diagnostic_info_fg
403 }
404 } else {
405 context_style.fg.unwrap_or(theme.popup_text_fg)
406 })
407 .bg(row_bg);
408
409 let indicator = if is_selected { ">" } else { " " };
410
411 let row = Line::from(vec![
412 Span::styled(indicator, Style::default().fg(theme.help_key_fg).bg(row_bg)),
413 Span::styled(
414 pad_right(&binding.key_display, key_col_width as usize),
415 key_style,
416 ),
417 Span::styled(" ", action_name_style),
418 Span::styled(
419 pad_right(&binding.action, action_name_col_width as usize),
420 action_name_style,
421 ),
422 Span::styled(" ", action_style),
423 Span::styled(
424 pad_right(&binding.action_display, description_col_width as usize),
425 action_style,
426 ),
427 Span::styled(" ", context_style),
428 Span::styled(
429 pad_right(&binding.context, context_col_width as usize),
430 context_style,
431 ),
432 Span::styled(" ", source_style),
433 Span::styled(
434 pad_right(
435 &match binding.source {
436 BindingSource::Custom => t!("keybinding_editor.source_custom").to_string(),
437 BindingSource::Keymap => t!("keybinding_editor.source_keymap").to_string(),
438 BindingSource::Unbound => String::new(),
439 },
440 source_col_width as usize,
441 ),
442 source_style,
443 ),
444 ]);
445
446 let row_area = Rect {
447 y: row_y,
448 height: 1,
449 ..table_area
450 };
451 frame.render_widget(
453 Paragraph::new("").style(Style::default().bg(row_bg)),
454 row_area,
455 );
456 frame.render_widget(Paragraph::new(row), row_area);
457 }
458
459 if editor.scroll.needs_scrollbar() {
461 let sb_area = Rect::new(
462 table_area.x + table_area.width.saturating_sub(1),
463 table_area.y,
464 1,
465 table_area.height,
466 );
467 let sb_state = editor.scroll.to_scrollbar_state();
468 let sb_colors = ScrollbarColors::from_theme(theme);
469 render_scrollbar(frame, sb_area, &sb_state, &sb_colors);
470 }
471}
472
473fn render_footer(frame: &mut Frame, area: Rect, editor: &KeybindingEditor, theme: &Theme) {
475 let hints = if editor.search_active && editor.search_focused {
476 vec![
477 Span::styled(" Esc", Style::default().fg(theme.help_key_fg)),
478 Span::styled(
479 format!(":{} ", t!("keybinding_editor.footer_cancel")),
480 Style::default().fg(theme.popup_text_fg),
481 ),
482 Span::styled("Tab", Style::default().fg(theme.help_key_fg)),
483 Span::styled(
484 format!(":{} ", t!("keybinding_editor.footer_toggle_mode")),
485 Style::default().fg(theme.popup_text_fg),
486 ),
487 Span::styled("Enter", Style::default().fg(theme.help_key_fg)),
488 Span::styled(
489 format!(":{}", t!("keybinding_editor.footer_confirm")),
490 Style::default().fg(theme.popup_text_fg),
491 ),
492 ]
493 } else {
494 vec![
495 Span::styled(" Enter", Style::default().fg(theme.help_key_fg)),
496 Span::styled(
497 format!(":{} ", t!("keybinding_editor.footer_edit")),
498 Style::default().fg(theme.popup_text_fg),
499 ),
500 Span::styled("a", Style::default().fg(theme.help_key_fg)),
501 Span::styled(
502 format!(":{} ", t!("keybinding_editor.footer_add")),
503 Style::default().fg(theme.popup_text_fg),
504 ),
505 Span::styled("d", Style::default().fg(theme.help_key_fg)),
506 Span::styled(
507 format!(":{} ", t!("keybinding_editor.footer_delete")),
508 Style::default().fg(theme.popup_text_fg),
509 ),
510 Span::styled("/", Style::default().fg(theme.help_key_fg)),
511 Span::styled(
512 format!(":{} ", t!("keybinding_editor.footer_search")),
513 Style::default().fg(theme.popup_text_fg),
514 ),
515 Span::styled("r", Style::default().fg(theme.help_key_fg)),
516 Span::styled(
517 format!(":{} ", t!("keybinding_editor.footer_record_key")),
518 Style::default().fg(theme.popup_text_fg),
519 ),
520 Span::styled("c", Style::default().fg(theme.help_key_fg)),
521 Span::styled(
522 format!(":{} ", t!("keybinding_editor.footer_context")),
523 Style::default().fg(theme.popup_text_fg),
524 ),
525 Span::styled("s", Style::default().fg(theme.help_key_fg)),
526 Span::styled(
527 format!(":{} ", t!("keybinding_editor.footer_source")),
528 Style::default().fg(theme.popup_text_fg),
529 ),
530 Span::styled("?", Style::default().fg(theme.help_key_fg)),
531 Span::styled(
532 format!(":{} ", t!("keybinding_editor.footer_help")),
533 Style::default().fg(theme.popup_text_fg),
534 ),
535 Span::styled("Ctrl+S", Style::default().fg(theme.help_key_fg)),
536 Span::styled(
537 format!(":{} ", t!("keybinding_editor.footer_save")),
538 Style::default().fg(theme.popup_text_fg),
539 ),
540 Span::styled("Esc", Style::default().fg(theme.help_key_fg)),
541 Span::styled(
542 format!(":{}", t!("keybinding_editor.footer_close")),
543 Style::default().fg(theme.popup_text_fg),
544 ),
545 ]
546 };
547
548 frame.render_widget(Paragraph::new(Line::from(hints)), area);
549}
550
551fn render_help_overlay(frame: &mut Frame, area: Rect, theme: &Theme) {
553 let width = 52u16.min(area.width.saturating_sub(4));
554 let height = 22u16.min(area.height.saturating_sub(4));
555 let x = area.x + (area.width.saturating_sub(width)) / 2;
556 let y = area.y + (area.height.saturating_sub(height)) / 2;
557
558 let dialog_area = Rect {
559 x,
560 y,
561 width,
562 height,
563 };
564 frame.render_widget(Clear, dialog_area);
565
566 let block = Block::default()
567 .title(format!(" {} ", t!("keybinding_editor.help_title")))
568 .borders(Borders::ALL)
569 .border_style(Style::default().fg(theme.popup_border_fg))
570 .style(Style::default().bg(theme.popup_bg).fg(theme.popup_text_fg));
571 let inner = block.inner(dialog_area);
572 frame.render_widget(block, dialog_area);
573
574 let h_nav = t!("keybinding_editor.help_navigation").to_string();
575 let h_move = t!("keybinding_editor.help_move_up_down").to_string();
576 let h_page = t!("keybinding_editor.help_page_up_down").to_string();
577 let h_first = t!("keybinding_editor.help_first_last").to_string();
578 let h_search = t!("keybinding_editor.help_search").to_string();
579 let h_by_name = t!("keybinding_editor.help_search_by_name").to_string();
580 let h_by_key = t!("keybinding_editor.help_search_by_key").to_string();
581 let h_toggle = t!("keybinding_editor.help_toggle_search").to_string();
582 let h_cancel = t!("keybinding_editor.help_cancel_search").to_string();
583 let h_editing = t!("keybinding_editor.help_editing").to_string();
584 let h_edit = t!("keybinding_editor.help_edit_binding").to_string();
585 let h_add = t!("keybinding_editor.help_add_binding").to_string();
586 let h_del = t!("keybinding_editor.help_delete_binding").to_string();
587 let h_filters = t!("keybinding_editor.help_filters").to_string();
588 let h_ctx = t!("keybinding_editor.help_cycle_context").to_string();
589 let h_src = t!("keybinding_editor.help_cycle_source").to_string();
590 let h_save = t!("keybinding_editor.help_save_changes").to_string();
591 let h_close = t!("keybinding_editor.help_close_help").to_string();
592
593 let help_lines = vec![
594 help_line(&h_nav, "", theme, true),
595 help_line(" \u{2191} / \u{2193}", &h_move, theme, false),
596 help_line(" PgUp / PgDn", &h_page, theme, false),
597 help_line(" Home / End", &h_first, theme, false),
598 help_line("", "", theme, false),
599 help_line(&h_search, "", theme, true),
600 help_line(" /", &h_by_name, theme, false),
601 help_line(" r", &h_by_key, theme, false),
602 help_line(" Tab", &h_toggle, theme, false),
603 help_line(" Esc", &h_cancel, theme, false),
604 help_line("", "", theme, false),
605 help_line(&h_editing, "", theme, true),
606 help_line(" Enter", &h_edit, theme, false),
607 help_line(" a", &h_add, theme, false),
608 help_line(" d / Delete", &h_del, theme, false),
609 help_line("", "", theme, false),
610 help_line(&h_filters, "", theme, true),
611 help_line(" c", &h_ctx, theme, false),
612 help_line(" s", &h_src, theme, false),
613 help_line("", "", theme, false),
614 help_line(" Ctrl+S", &h_save, theme, false),
615 help_line(" Esc / ?", &h_close, theme, false),
616 ];
617
618 let para = Paragraph::new(help_lines);
619 frame.render_widget(para, inner);
620}
621
622fn help_line<'a>(key: &'a str, desc: &'a str, theme: &Theme, is_header: bool) -> Line<'a> {
623 if is_header {
624 Line::from(vec![Span::styled(
625 key,
626 Style::default()
627 .fg(theme.popup_text_fg)
628 .add_modifier(Modifier::BOLD),
629 )])
630 } else {
631 Line::from(vec![
632 Span::styled(
633 format!("{:16}", key),
634 Style::default()
635 .fg(theme.help_key_fg)
636 .add_modifier(Modifier::BOLD),
637 ),
638 Span::styled(desc, Style::default().fg(theme.popup_text_fg)),
639 ])
640 }
641}
642
643const MAX_AUTOCOMPLETE_VISIBLE: usize = 8;
645
646fn render_edit_dialog(
648 frame: &mut Frame,
649 area: Rect,
650 dialog: &crate::app::keybinding_editor::EditBindingState,
651 editor: &mut KeybindingEditor,
652 theme: &Theme,
653) {
654 let width = 56u16.min(area.width.saturating_sub(4));
655 let height = 18u16.min(area.height.saturating_sub(4));
656 let x = area.x + (area.width.saturating_sub(width)) / 2;
657 let y = area.y + (area.height.saturating_sub(height)) / 2;
658
659 let dialog_area = Rect {
660 x,
661 y,
662 width,
663 height,
664 };
665 frame.render_widget(Clear, dialog_area);
666
667 let title = if dialog.editing_index.is_some() {
668 format!(" {} ", t!("keybinding_editor.dialog_edit_title"))
669 } else {
670 format!(" {} ", t!("keybinding_editor.dialog_add_title"))
671 };
672
673 let block = Block::default()
674 .title(title)
675 .borders(Borders::ALL)
676 .border_style(Style::default().fg(theme.popup_border_fg))
677 .style(Style::default().bg(theme.popup_bg).fg(theme.popup_text_fg));
678 let inner = block.inner(dialog_area);
679 frame.render_widget(block, dialog_area);
680
681 let chunks = Layout::vertical([
682 Constraint::Length(1), Constraint::Length(1), Constraint::Length(1), Constraint::Length(1), Constraint::Length(1), Constraint::Length(1), Constraint::Length(1), Constraint::Min(3), Constraint::Length(1), ])
692 .split(inner);
693
694 let instr = match dialog.mode {
696 EditMode::RecordingKey => t!("keybinding_editor.instr_recording_key").to_string(),
697 EditMode::EditingAction => t!("keybinding_editor.instr_editing_action").to_string(),
698 EditMode::EditingContext => t!("keybinding_editor.instr_editing_context").to_string(),
699 };
700 frame.render_widget(
701 Paragraph::new(Line::from(Span::styled(
702 format!(" {}", instr),
703 Style::default().fg(theme.popup_text_fg),
704 ))),
705 chunks[0],
706 );
707
708 let key_focused = dialog.focus_area == 0;
710 let key_none_text;
711 let key_recording_text;
712 let key_text = if dialog.key_display.is_empty() {
713 if dialog.mode == EditMode::RecordingKey {
714 key_recording_text = t!("keybinding_editor.key_recording").to_string();
715 &key_recording_text
716 } else {
717 key_none_text = t!("keybinding_editor.key_none").to_string();
718 &key_none_text
719 }
720 } else {
721 &dialog.key_display
722 };
723 let field_bg = if key_focused {
724 theme.popup_selection_bg
725 } else {
726 theme.popup_bg
727 };
728 let key_label_style = if key_focused {
729 Style::default()
730 .fg(theme.help_key_fg)
731 .bg(field_bg)
732 .add_modifier(Modifier::BOLD)
733 } else {
734 Style::default().fg(theme.popup_text_fg).bg(field_bg)
735 };
736 let key_value_style = Style::default().fg(theme.popup_text_fg).bg(field_bg);
737 if key_focused {
738 frame.render_widget(
739 Paragraph::new("").style(Style::default().bg(field_bg)),
740 chunks[2],
741 );
742 }
743 frame.render_widget(
744 Paragraph::new(Line::from(vec![
745 Span::styled(
746 format!(" {:9}", t!("keybinding_editor.label_key")),
747 key_label_style,
748 ),
749 Span::styled(key_text, key_value_style),
750 ])),
751 chunks[2],
752 );
753
754 let action_focused = dialog.focus_area == 1;
756 let field_bg = if action_focused {
757 theme.popup_selection_bg
758 } else {
759 theme.popup_bg
760 };
761 let action_label_style = if action_focused {
762 Style::default()
763 .fg(theme.help_key_fg)
764 .bg(field_bg)
765 .add_modifier(Modifier::BOLD)
766 } else {
767 Style::default().fg(theme.popup_text_fg).bg(field_bg)
768 };
769 let has_error = dialog.action_error.is_some();
770 let action_value_style = if has_error {
771 Style::default().fg(theme.diagnostic_error_fg).bg(field_bg)
772 } else {
773 Style::default().fg(theme.popup_text_fg).bg(field_bg)
774 };
775 let action_placeholder;
776 let action_display = if dialog.action_text.is_empty() && dialog.mode != EditMode::EditingAction
777 {
778 action_placeholder = t!("keybinding_editor.action_placeholder").to_string();
779 &action_placeholder
780 } else {
781 &dialog.action_text
782 };
783 if action_focused {
784 frame.render_widget(
785 Paragraph::new("").style(Style::default().bg(field_bg)),
786 chunks[3],
787 );
788 }
789 let mut action_spans = vec![
790 Span::styled(
791 format!(" {:9}", t!("keybinding_editor.label_action")),
792 action_label_style,
793 ),
794 Span::styled(action_display, action_value_style),
795 ];
796 if action_focused && dialog.mode == EditMode::EditingAction {
797 action_spans.push(Span::styled(
798 "_",
799 Style::default().fg(theme.cursor).bg(field_bg),
800 ));
801 }
802 frame.render_widget(Paragraph::new(Line::from(action_spans)), chunks[3]);
803
804 if !dialog.action_text.is_empty() {
806 let description = KeybindingResolver::format_action_from_str(&dialog.action_text);
807 if description.to_lowercase() != dialog.action_text.replace('_', " ").to_lowercase() {
809 frame.render_widget(
810 Paragraph::new(Line::from(vec![
811 Span::styled(" ", Style::default().fg(theme.popup_text_fg)),
812 Span::styled(
813 format!("\u{2192} {}", description),
814 Style::default()
815 .fg(theme.popup_text_fg)
816 .add_modifier(Modifier::ITALIC),
817 ),
818 ])),
819 chunks[4],
820 );
821 }
822 }
823
824 let ctx_focused = dialog.focus_area == 2;
826 let field_bg = if ctx_focused {
827 theme.popup_selection_bg
828 } else {
829 theme.popup_bg
830 };
831 let ctx_label_style = if ctx_focused {
832 Style::default()
833 .fg(theme.help_key_fg)
834 .bg(field_bg)
835 .add_modifier(Modifier::BOLD)
836 } else {
837 Style::default().fg(theme.popup_text_fg).bg(field_bg)
838 };
839 if ctx_focused {
840 frame.render_widget(
841 Paragraph::new("").style(Style::default().bg(field_bg)),
842 chunks[5],
843 );
844 }
845 frame.render_widget(
846 Paragraph::new(Line::from(vec![
847 Span::styled(
848 format!(" {:9}", t!("keybinding_editor.label_context")),
849 ctx_label_style,
850 ),
851 Span::styled(
852 format!("[{}]", dialog.context),
853 Style::default().fg(theme.popup_text_fg).bg(field_bg),
854 ),
855 if ctx_focused {
856 Span::styled(
857 format!(" {}", t!("keybinding_editor.context_change_hint")),
858 Style::default().fg(theme.popup_text_fg).bg(field_bg),
859 )
860 } else {
861 Span::raw("")
862 },
863 ])),
864 chunks[5],
865 );
866
867 let mut info_lines: Vec<Line> = Vec::new();
869 if let Some(ref err) = dialog.action_error {
870 info_lines.push(Line::from(Span::styled(
871 format!(" \u{2717} {}", err),
872 Style::default()
873 .fg(theme.diagnostic_error_fg)
874 .add_modifier(Modifier::BOLD),
875 )));
876 }
877 if !dialog.conflicts.is_empty() {
878 info_lines.push(Line::from(Span::styled(
879 format!(" {}", t!("keybinding_editor.conflicts_label")),
880 Style::default()
881 .fg(theme.diagnostic_warning_fg)
882 .add_modifier(Modifier::BOLD),
883 )));
884 for conflict in &dialog.conflicts {
885 info_lines.push(Line::from(Span::styled(
886 format!(" {}", conflict),
887 Style::default().fg(theme.diagnostic_warning_fg),
888 )));
889 }
890 }
891 if !info_lines.is_empty() {
892 frame.render_widget(Paragraph::new(info_lines), chunks[7]);
893 }
894
895 let btn_focused = dialog.focus_area == 3;
897 let save_style = if btn_focused && dialog.selected_button == 0 {
898 Style::default()
899 .fg(theme.popup_bg)
900 .bg(theme.help_key_fg)
901 .add_modifier(Modifier::BOLD)
902 } else {
903 Style::default().fg(theme.popup_text_fg)
904 };
905 let cancel_style = if btn_focused && dialog.selected_button == 1 {
906 Style::default()
907 .fg(theme.popup_bg)
908 .bg(theme.help_key_fg)
909 .add_modifier(Modifier::BOLD)
910 } else {
911 Style::default().fg(theme.popup_text_fg)
912 };
913 editor.layout.dialog_key_field = Some(chunks[2]);
915 editor.layout.dialog_action_field = Some(chunks[3]);
916 editor.layout.dialog_context_field = Some(chunks[5]);
917
918 let save_text = format!(" {} ", t!("keybinding_editor.btn_save"));
919 let cancel_text = format!(" {} ", t!("keybinding_editor.btn_cancel"));
920 let save_x = chunks[8].x + 3;
921 let cancel_x = save_x + save_text.len() as u16 + 2;
922 editor.layout.dialog_buttons = Some((
923 Rect {
924 x: save_x,
925 y: chunks[8].y,
926 width: save_text.len() as u16,
927 height: 1,
928 },
929 Rect {
930 x: cancel_x,
931 y: chunks[8].y,
932 width: cancel_text.len() as u16,
933 height: 1,
934 },
935 ));
936
937 frame.render_widget(
938 Paragraph::new(Line::from(vec![
939 Span::raw(" "),
940 Span::styled(save_text, save_style),
941 Span::raw(" "),
942 Span::styled(cancel_text, cancel_style),
943 ])),
944 chunks[8],
945 );
946
947 if dialog.autocomplete_visible && !dialog.autocomplete_suggestions.is_empty() {
949 render_autocomplete_popup(frame, chunks[3], dialog, theme);
950 }
951}
952
953fn render_autocomplete_popup(
955 frame: &mut Frame,
956 action_field_area: Rect,
957 dialog: &crate::app::keybinding_editor::EditBindingState,
958 theme: &Theme,
959) {
960 let suggestion_count = dialog
961 .autocomplete_suggestions
962 .len()
963 .min(MAX_AUTOCOMPLETE_VISIBLE);
964 if suggestion_count == 0 {
965 return;
966 }
967
968 let popup_x = action_field_area.x + 12; let popup_y = action_field_area.y + 1;
971 let popup_width = 36u16.min(action_field_area.width.saturating_sub(12));
972 let popup_height = (suggestion_count as u16) + 2; let popup_area = Rect {
975 x: popup_x,
976 y: popup_y,
977 width: popup_width,
978 height: popup_height,
979 };
980
981 frame.render_widget(Clear, popup_area);
982
983 let block = Block::default()
984 .borders(Borders::ALL)
985 .border_style(Style::default().fg(theme.popup_border_fg))
986 .style(Style::default().bg(theme.popup_bg).fg(theme.popup_text_fg));
987 let inner = block.inner(popup_area);
988 frame.render_widget(block, popup_area);
989
990 let selected = dialog.autocomplete_selected.unwrap_or(0);
992 let scroll_offset = if selected >= MAX_AUTOCOMPLETE_VISIBLE {
993 selected - MAX_AUTOCOMPLETE_VISIBLE + 1
994 } else {
995 0
996 };
997
998 let mut lines: Vec<Line> = Vec::new();
999 for (i, suggestion) in dialog
1000 .autocomplete_suggestions
1001 .iter()
1002 .skip(scroll_offset)
1003 .take(MAX_AUTOCOMPLETE_VISIBLE)
1004 .enumerate()
1005 {
1006 let actual_idx = i + scroll_offset;
1007 let is_selected = Some(actual_idx) == dialog.autocomplete_selected;
1008
1009 let style = if is_selected {
1010 Style::default()
1011 .fg(theme.popup_bg)
1012 .bg(theme.help_key_fg)
1013 .add_modifier(Modifier::BOLD)
1014 } else {
1015 Style::default().fg(theme.popup_text_fg).bg(theme.popup_bg)
1016 };
1017
1018 let display = pad_right(suggestion, inner.width as usize);
1020 lines.push(Line::from(Span::styled(display, style)));
1021 }
1022
1023 frame.render_widget(Paragraph::new(lines), inner);
1024}
1025
1026fn render_confirm_dialog(
1028 frame: &mut Frame,
1029 area: Rect,
1030 editor: &mut KeybindingEditor,
1031 theme: &Theme,
1032) {
1033 let width = 44u16.min(area.width.saturating_sub(4));
1034 let height = 7u16.min(area.height.saturating_sub(4));
1035 let x = area.x + (area.width.saturating_sub(width)) / 2;
1036 let y = area.y + (area.height.saturating_sub(height)) / 2;
1037
1038 let dialog_area = Rect {
1039 x,
1040 y,
1041 width,
1042 height,
1043 };
1044 frame.render_widget(Clear, dialog_area);
1045
1046 let block = Block::default()
1047 .title(format!(" {} ", t!("keybinding_editor.confirm_title")))
1048 .borders(Borders::ALL)
1049 .border_style(Style::default().fg(theme.diagnostic_warning_fg))
1050 .style(Style::default().bg(theme.popup_bg).fg(theme.popup_text_fg));
1051 let inner = block.inner(dialog_area);
1052 frame.render_widget(block, dialog_area);
1053
1054 let chunks = Layout::vertical([
1055 Constraint::Length(2), Constraint::Length(1), Constraint::Length(1), ])
1059 .split(inner);
1060
1061 frame.render_widget(
1062 Paragraph::new(Line::from(Span::styled(
1063 format!(" {}", t!("keybinding_editor.confirm_message")),
1064 Style::default().fg(theme.popup_text_fg),
1065 ))),
1066 chunks[0],
1067 );
1068
1069 let options = [
1070 t!("keybinding_editor.btn_save").to_string(),
1071 t!("keybinding_editor.btn_discard").to_string(),
1072 t!("keybinding_editor.btn_cancel").to_string(),
1073 ];
1074 let mut x_offset = chunks[2].x + 1;
1076 let mut btn_rects = Vec::new();
1077 let mut spans = vec![Span::raw(" ")];
1078 for (i, opt) in options.iter().enumerate() {
1079 let style = if i == editor.confirm_selection {
1080 Style::default()
1081 .fg(theme.popup_bg)
1082 .bg(theme.help_key_fg)
1083 .add_modifier(Modifier::BOLD)
1084 } else {
1085 Style::default().fg(theme.popup_text_fg)
1086 };
1087 let text = format!(" {} ", opt);
1088 let text_len = text.len() as u16;
1089 btn_rects.push(Rect {
1090 x: x_offset,
1091 y: chunks[2].y,
1092 width: text_len,
1093 height: 1,
1094 });
1095 x_offset += text_len + 2; spans.push(Span::styled(text, style));
1097 spans.push(Span::raw(" "));
1098 }
1099 if btn_rects.len() == 3 {
1100 editor.layout.confirm_buttons = Some((btn_rects[0], btn_rects[1], btn_rects[2]));
1101 }
1102
1103 frame.render_widget(Paragraph::new(Line::from(spans)), chunks[2]);
1104}
1105
1106fn pad_right(s: &str, width: usize) -> String {
1108 let char_count = s.chars().count();
1109 if char_count >= width {
1110 s.chars().take(width).collect()
1111 } else {
1112 let padding = width - char_count;
1113 format!("{}{}", s, " ".repeat(padding))
1114 }
1115}
1116
1117pub fn handle_keybinding_editor_input(
1121 editor: &mut KeybindingEditor,
1122 event: &KeyEvent,
1123) -> KeybindingEditorAction {
1124 if editor.showing_help {
1126 match event.code {
1127 KeyCode::Esc | KeyCode::Char('?') | KeyCode::Enter => {
1128 editor.showing_help = false;
1129 }
1130 _ => {}
1131 }
1132 return KeybindingEditorAction::Consumed;
1133 }
1134
1135 if editor.showing_confirm_dialog {
1137 return handle_confirm_input(editor, event);
1138 }
1139
1140 if editor.edit_dialog.is_some() {
1142 return handle_edit_dialog_input(editor, event);
1143 }
1144
1145 if editor.search_active && editor.search_focused {
1147 return handle_search_input(editor, event);
1148 }
1149
1150 handle_main_input(editor, event)
1152}
1153
1154pub enum KeybindingEditorAction {
1156 Consumed,
1158 Close,
1160 SaveAndClose,
1162 StatusMessage(String),
1164}
1165
1166fn handle_main_input(editor: &mut KeybindingEditor, event: &KeyEvent) -> KeybindingEditorAction {
1167 match (event.code, event.modifiers) {
1168 (KeyCode::Esc, KeyModifiers::NONE) => {
1170 if editor.search_active {
1171 editor.cancel_search();
1173 KeybindingEditorAction::Consumed
1174 } else if editor.has_changes {
1175 editor.showing_confirm_dialog = true;
1176 editor.confirm_selection = 0;
1177 KeybindingEditorAction::Consumed
1178 } else {
1179 KeybindingEditorAction::Close
1180 }
1181 }
1182
1183 (KeyCode::Char('s'), m) if m.contains(KeyModifiers::CONTROL) => {
1185 KeybindingEditorAction::SaveAndClose
1186 }
1187
1188 (KeyCode::Up, KeyModifiers::NONE) | (KeyCode::Char('k'), KeyModifiers::NONE) => {
1190 editor.select_prev();
1191 KeybindingEditorAction::Consumed
1192 }
1193 (KeyCode::Down, KeyModifiers::NONE) | (KeyCode::Char('j'), KeyModifiers::NONE) => {
1194 editor.select_next();
1195 KeybindingEditorAction::Consumed
1196 }
1197 (KeyCode::PageUp, _) => {
1198 editor.page_up();
1199 KeybindingEditorAction::Consumed
1200 }
1201 (KeyCode::PageDown, _) => {
1202 editor.page_down();
1203 KeybindingEditorAction::Consumed
1204 }
1205 (KeyCode::Home, _) => {
1206 editor.selected = 0;
1207 editor.scroll.offset = 0;
1208 KeybindingEditorAction::Consumed
1209 }
1210 (KeyCode::End, _) => {
1211 editor.selected = editor.filtered_indices.len().saturating_sub(1);
1212 editor.ensure_visible_public();
1213 KeybindingEditorAction::Consumed
1214 }
1215
1216 (KeyCode::Char('/'), KeyModifiers::NONE) => {
1218 editor.start_search();
1219 KeybindingEditorAction::Consumed
1220 }
1221
1222 (KeyCode::Char('r'), KeyModifiers::NONE) => {
1224 editor.start_record_key_search();
1225 KeybindingEditorAction::Consumed
1226 }
1227
1228 (KeyCode::Char('?'), _) => {
1230 editor.showing_help = true;
1231 KeybindingEditorAction::Consumed
1232 }
1233
1234 (KeyCode::Char('a'), KeyModifiers::NONE) => {
1236 editor.open_add_dialog();
1237 KeybindingEditorAction::Consumed
1238 }
1239
1240 (KeyCode::Enter, KeyModifiers::NONE) => {
1242 editor.open_edit_dialog();
1243 KeybindingEditorAction::Consumed
1244 }
1245
1246 (KeyCode::Char('d'), KeyModifiers::NONE) | (KeyCode::Delete, _) => {
1248 match editor.delete_selected() {
1249 DeleteResult::CustomRemoved => KeybindingEditorAction::StatusMessage(
1250 t!("keybinding_editor.status_binding_removed").to_string(),
1251 ),
1252 DeleteResult::KeymapOverridden => KeybindingEditorAction::StatusMessage(
1253 t!("keybinding_editor.status_keymap_overridden").to_string(),
1254 ),
1255 DeleteResult::CannotDelete | DeleteResult::NothingSelected => {
1256 KeybindingEditorAction::StatusMessage(
1257 t!("keybinding_editor.status_cannot_delete").to_string(),
1258 )
1259 }
1260 }
1261 }
1262
1263 (KeyCode::Char('c'), KeyModifiers::NONE) => {
1265 editor.cycle_context_filter();
1266 KeybindingEditorAction::Consumed
1267 }
1268
1269 (KeyCode::Char('s'), KeyModifiers::NONE) => {
1271 editor.cycle_source_filter();
1272 KeybindingEditorAction::Consumed
1273 }
1274
1275 _ => KeybindingEditorAction::Consumed,
1276 }
1277}
1278
1279fn handle_search_input(editor: &mut KeybindingEditor, event: &KeyEvent) -> KeybindingEditorAction {
1280 match editor.search_mode {
1281 SearchMode::Text => match (event.code, event.modifiers) {
1282 (KeyCode::Esc, _) => {
1283 editor.cancel_search();
1284 KeybindingEditorAction::Consumed
1285 }
1286 (KeyCode::Enter, _) | (KeyCode::Down, _) => {
1287 editor.search_focused = false;
1289 KeybindingEditorAction::Consumed
1290 }
1291 (KeyCode::Up, _) => {
1292 editor.search_focused = false;
1294 editor.selected = editor.filtered_indices.len().saturating_sub(1);
1295 editor.ensure_visible_public();
1296 KeybindingEditorAction::Consumed
1297 }
1298 (KeyCode::Tab, _) => {
1299 editor.search_mode = SearchMode::RecordKey;
1301 editor.search_key_display.clear();
1302 editor.search_key_code = None;
1303 KeybindingEditorAction::Consumed
1304 }
1305 (KeyCode::Backspace, _) => {
1306 editor.search_query.pop();
1307 editor.apply_filters();
1308 KeybindingEditorAction::Consumed
1309 }
1310 (KeyCode::Char(c), m) if !m.contains(KeyModifiers::CONTROL) => {
1311 editor.search_query.push(c);
1312 editor.apply_filters();
1313 KeybindingEditorAction::Consumed
1314 }
1315 _ => KeybindingEditorAction::Consumed,
1316 },
1317 SearchMode::RecordKey => match (event.code, event.modifiers) {
1318 (KeyCode::Esc, KeyModifiers::NONE) => {
1319 editor.cancel_search();
1320 KeybindingEditorAction::Consumed
1321 }
1322 (KeyCode::Tab, KeyModifiers::NONE) => {
1323 editor.search_mode = SearchMode::Text;
1325 editor.apply_filters();
1326 KeybindingEditorAction::Consumed
1327 }
1328 (KeyCode::Enter, KeyModifiers::NONE) => {
1329 editor.search_focused = false;
1331 KeybindingEditorAction::Consumed
1332 }
1333 _ => {
1334 editor.record_search_key(event);
1336 KeybindingEditorAction::Consumed
1337 }
1338 },
1339 }
1340}
1341
1342fn handle_edit_dialog_input(
1343 editor: &mut KeybindingEditor,
1344 event: &KeyEvent,
1345) -> KeybindingEditorAction {
1346 let mut dialog = match editor.edit_dialog.take() {
1348 Some(d) => d,
1349 None => return KeybindingEditorAction::Consumed,
1350 };
1351
1352 if event.code == KeyCode::Esc && event.modifiers == KeyModifiers::NONE {
1354 return KeybindingEditorAction::Consumed;
1356 }
1357
1358 match dialog.focus_area {
1359 0 => {
1360 match (event.code, event.modifiers) {
1362 (KeyCode::Tab, KeyModifiers::NONE) => {
1363 dialog.focus_area = 1;
1364 dialog.mode = EditMode::EditingAction;
1365 }
1366 (KeyCode::Enter, KeyModifiers::NONE) if dialog.key_code.is_some() => {
1367 dialog.focus_area = 1;
1368 dialog.mode = EditMode::EditingAction;
1369 }
1370 _ => {
1371 match event.code {
1373 KeyCode::Modifier(_) => {}
1374 _ => {
1375 dialog.key_code = Some(event.code);
1376 dialog.modifiers = event.modifiers;
1377 dialog.key_display = format_keybinding(&event.code, &event.modifiers);
1378 dialog.conflicts =
1380 editor.find_conflicts(event.code, event.modifiers, &dialog.context);
1381 }
1382 }
1383 }
1384 }
1385 }
1386 1 => {
1387 match (event.code, event.modifiers) {
1389 (KeyCode::Tab, KeyModifiers::NONE) => {
1390 if dialog.autocomplete_visible {
1392 if let Some(sel) = dialog.autocomplete_selected {
1393 if sel < dialog.autocomplete_suggestions.len() {
1394 let suggestion = dialog.autocomplete_suggestions[sel].clone();
1395 dialog.action_text = suggestion;
1396 dialog.action_cursor = dialog.action_text.len();
1397 dialog.autocomplete_visible = false;
1398 dialog.autocomplete_selected = None;
1399 dialog.action_error = None;
1400 }
1401 }
1402 } else {
1403 dialog.focus_area = 2;
1404 dialog.mode = EditMode::EditingContext;
1405 }
1406 }
1407 (KeyCode::BackTab, _) => {
1408 dialog.autocomplete_visible = false;
1409 dialog.focus_area = 0;
1410 dialog.mode = EditMode::RecordingKey;
1411 }
1412 (KeyCode::Enter, KeyModifiers::NONE) => {
1413 if dialog.autocomplete_visible {
1415 if let Some(sel) = dialog.autocomplete_selected {
1416 if sel < dialog.autocomplete_suggestions.len() {
1417 let suggestion = dialog.autocomplete_suggestions[sel].clone();
1418 dialog.action_text = suggestion;
1419 dialog.action_cursor = dialog.action_text.len();
1420 dialog.autocomplete_visible = false;
1421 dialog.autocomplete_selected = None;
1422 dialog.action_error = None;
1423 }
1424 }
1425 } else {
1426 dialog.focus_area = 3;
1427 dialog.selected_button = 0;
1428 dialog.mode = EditMode::EditingContext;
1429 }
1430 }
1431 (KeyCode::Up, _) if dialog.autocomplete_visible => {
1432 if let Some(sel) = dialog.autocomplete_selected {
1434 if sel > 0 {
1435 dialog.autocomplete_selected = Some(sel - 1);
1436 }
1437 }
1438 }
1439 (KeyCode::Down, _) if dialog.autocomplete_visible => {
1440 if let Some(sel) = dialog.autocomplete_selected {
1442 let max = dialog.autocomplete_suggestions.len().saturating_sub(1);
1443 if sel < max {
1444 dialog.autocomplete_selected = Some(sel + 1);
1445 }
1446 }
1447 }
1448 (KeyCode::Esc, _) if dialog.autocomplete_visible => {
1449 dialog.autocomplete_visible = false;
1451 dialog.autocomplete_selected = None;
1452 editor.edit_dialog = Some(dialog);
1454 return KeybindingEditorAction::Consumed;
1455 }
1456 (KeyCode::Backspace, _) => {
1457 if dialog.action_cursor > 0 {
1458 dialog.action_cursor -= 1;
1459 dialog.action_text.remove(dialog.action_cursor);
1460 dialog.action_error = None;
1461 }
1462 editor.edit_dialog = Some(dialog);
1464 editor.update_autocomplete();
1465 return KeybindingEditorAction::Consumed;
1466 }
1467 (KeyCode::Char(c), m) if !m.contains(KeyModifiers::CONTROL) => {
1468 dialog.action_text.insert(dialog.action_cursor, c);
1469 dialog.action_cursor += 1;
1470 dialog.action_error = None;
1471 editor.edit_dialog = Some(dialog);
1473 editor.update_autocomplete();
1474 return KeybindingEditorAction::Consumed;
1475 }
1476 _ => {}
1477 }
1478 }
1479 2 => {
1480 match (event.code, event.modifiers) {
1482 (KeyCode::Tab, KeyModifiers::NONE) => {
1483 dialog.focus_area = 3;
1484 dialog.selected_button = 0;
1485 }
1486 (KeyCode::BackTab, _) => {
1487 dialog.focus_area = 1;
1488 dialog.mode = EditMode::EditingAction;
1489 }
1490 (KeyCode::Left, _) => {
1491 if dialog.context_option_index > 0 {
1492 dialog.context_option_index -= 1;
1493 dialog.context =
1494 dialog.context_options[dialog.context_option_index].clone();
1495 if let Some(key_code) = dialog.key_code {
1497 dialog.conflicts =
1498 editor.find_conflicts(key_code, dialog.modifiers, &dialog.context);
1499 }
1500 }
1501 }
1502 (KeyCode::Right, _) => {
1503 if dialog.context_option_index + 1 < dialog.context_options.len() {
1504 dialog.context_option_index += 1;
1505 dialog.context =
1506 dialog.context_options[dialog.context_option_index].clone();
1507 if let Some(key_code) = dialog.key_code {
1508 dialog.conflicts =
1509 editor.find_conflicts(key_code, dialog.modifiers, &dialog.context);
1510 }
1511 }
1512 }
1513 (KeyCode::Enter, _) => {
1514 dialog.focus_area = 3;
1515 dialog.selected_button = 0;
1516 }
1517 _ => {}
1518 }
1519 }
1520 3 => {
1521 match (event.code, event.modifiers) {
1523 (KeyCode::Tab, KeyModifiers::NONE) => {
1524 if dialog.selected_button < 1 {
1525 dialog.selected_button = 1;
1527 } else {
1528 dialog.focus_area = 0;
1530 dialog.mode = EditMode::RecordingKey;
1531 }
1532 }
1533 (KeyCode::BackTab, _) => {
1534 if dialog.selected_button > 0 {
1535 dialog.selected_button = 0;
1537 } else {
1538 dialog.focus_area = 2;
1540 dialog.mode = EditMode::EditingContext;
1541 }
1542 }
1543 (KeyCode::Left, _) => {
1544 if dialog.selected_button > 0 {
1545 dialog.selected_button -= 1;
1546 }
1547 }
1548 (KeyCode::Right, _) => {
1549 if dialog.selected_button < 1 {
1550 dialog.selected_button += 1;
1551 }
1552 }
1553 (KeyCode::Enter, _) => {
1554 if dialog.selected_button == 0 {
1555 editor.edit_dialog = Some(dialog);
1557 if let Some(err) = editor.apply_edit_dialog() {
1558 return KeybindingEditorAction::StatusMessage(err);
1560 }
1561 return KeybindingEditorAction::Consumed;
1562 } else {
1563 return KeybindingEditorAction::Consumed;
1565 }
1566 }
1567 _ => {}
1568 }
1569 }
1570 _ => {}
1571 }
1572
1573 editor.edit_dialog = Some(dialog);
1575 KeybindingEditorAction::Consumed
1576}
1577
1578fn handle_confirm_input(editor: &mut KeybindingEditor, event: &KeyEvent) -> KeybindingEditorAction {
1579 match (event.code, event.modifiers) {
1580 (KeyCode::Left, _) => {
1581 if editor.confirm_selection > 0 {
1582 editor.confirm_selection -= 1;
1583 }
1584 KeybindingEditorAction::Consumed
1585 }
1586 (KeyCode::Right, _) => {
1587 if editor.confirm_selection < 2 {
1588 editor.confirm_selection += 1;
1589 }
1590 KeybindingEditorAction::Consumed
1591 }
1592 (KeyCode::Enter, _) => match editor.confirm_selection {
1593 0 => KeybindingEditorAction::SaveAndClose,
1594 1 => KeybindingEditorAction::Close, _ => {
1596 editor.showing_confirm_dialog = false;
1597 KeybindingEditorAction::Consumed
1598 }
1599 },
1600 (KeyCode::Esc, _) => {
1601 editor.showing_confirm_dialog = false;
1602 KeybindingEditorAction::Consumed
1603 }
1604 _ => KeybindingEditorAction::Consumed,
1605 }
1606}