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