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