@@ -33,6 +33,103 @@ struct FortressEntry { |
| 33 | 33 | is_dir: bool, |
| 34 | 34 | } |
| 35 | 35 | |
| 36 | +/// A command in the command palette |
| 37 | +#[derive(Debug, Clone, PartialEq)] |
| 38 | +struct PaletteCommand { |
| 39 | + /// Display name (e.g., "Save File") |
| 40 | + name: &'static str, |
| 41 | + /// Keyboard shortcut (e.g., "Ctrl+S") |
| 42 | + shortcut: &'static str, |
| 43 | + /// Category for grouping (e.g., "File", "Edit") |
| 44 | + category: &'static str, |
| 45 | + /// Unique command identifier |
| 46 | + id: &'static str, |
| 47 | + /// Fuzzy match score (computed during filtering) |
| 48 | + score: i32, |
| 49 | +} |
| 50 | + |
| 51 | +impl PaletteCommand { |
| 52 | + const fn new(name: &'static str, shortcut: &'static str, category: &'static str, id: &'static str) -> Self { |
| 53 | + Self { name, shortcut, category, id, score: 0 } |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +/// All available commands for the command palette |
| 58 | +const ALL_COMMANDS: &[PaletteCommand] = &[ |
| 59 | + // File operations |
| 60 | + PaletteCommand::new("Save File", "Ctrl+S", "File", "save"), |
| 61 | + PaletteCommand::new("Save All", "Ctrl+Shift+S", "File", "save-all"), |
| 62 | + PaletteCommand::new("Open File Browser", "Ctrl+O", "File", "open"), |
| 63 | + PaletteCommand::new("New Tab", "Ctrl+T", "File", "new-tab"), |
| 64 | + PaletteCommand::new("Close Tab", "Ctrl+W", "File", "close-tab"), |
| 65 | + PaletteCommand::new("Next Tab", "Ctrl+Tab", "File", "next-tab"), |
| 66 | + PaletteCommand::new("Previous Tab", "Ctrl+Shift+Tab", "File", "prev-tab"), |
| 67 | + PaletteCommand::new("Quit", "Ctrl+Q", "File", "quit"), |
| 68 | + |
| 69 | + // Edit operations |
| 70 | + PaletteCommand::new("Undo", "Ctrl+Z", "Edit", "undo"), |
| 71 | + PaletteCommand::new("Redo", "Ctrl+Shift+Z", "Edit", "redo"), |
| 72 | + PaletteCommand::new("Cut", "Ctrl+X", "Edit", "cut"), |
| 73 | + PaletteCommand::new("Copy", "Ctrl+C", "Edit", "copy"), |
| 74 | + PaletteCommand::new("Paste", "Ctrl+V", "Edit", "paste"), |
| 75 | + PaletteCommand::new("Select All", "Ctrl+A", "Edit", "select-all"), |
| 76 | + PaletteCommand::new("Select Line", "Ctrl+L", "Edit", "select-line"), |
| 77 | + PaletteCommand::new("Select Word", "Ctrl+D", "Edit", "select-word"), |
| 78 | + PaletteCommand::new("Toggle Line Comment", "Ctrl+/", "Edit", "toggle-comment"), |
| 79 | + PaletteCommand::new("Join Lines", "Ctrl+J", "Edit", "join-lines"), |
| 80 | + PaletteCommand::new("Duplicate Line", "Alt+Shift+Down", "Edit", "duplicate-line"), |
| 81 | + PaletteCommand::new("Move Line Up", "Alt+Up", "Edit", "move-line-up"), |
| 82 | + PaletteCommand::new("Move Line Down", "Alt+Down", "Edit", "move-line-down"), |
| 83 | + PaletteCommand::new("Delete Line", "Ctrl+Shift+K", "Edit", "delete-line"), |
| 84 | + PaletteCommand::new("Indent", "Tab", "Edit", "indent"), |
| 85 | + PaletteCommand::new("Outdent", "Shift+Tab", "Edit", "outdent"), |
| 86 | + PaletteCommand::new("Transpose Characters", "Ctrl+T", "Edit", "transpose"), |
| 87 | + |
| 88 | + // Search operations |
| 89 | + PaletteCommand::new("Find", "Ctrl+F", "Search", "find"), |
| 90 | + PaletteCommand::new("Find and Replace", "Ctrl+R", "Search", "replace"), |
| 91 | + PaletteCommand::new("Find Next", "F3", "Search", "find-next"), |
| 92 | + PaletteCommand::new("Find Previous", "Shift+F3", "Search", "find-prev"), |
| 93 | + PaletteCommand::new("Search in Files", "F4", "Search", "search-files"), |
| 94 | + |
| 95 | + // Navigation |
| 96 | + PaletteCommand::new("Go to Line", "Ctrl+G", "Navigation", "goto-line"), |
| 97 | + PaletteCommand::new("Go to Beginning of File", "Ctrl+Home", "Navigation", "goto-start"), |
| 98 | + PaletteCommand::new("Go to End of File", "Ctrl+End", "Navigation", "goto-end"), |
| 99 | + PaletteCommand::new("Go to Matching Bracket", "Ctrl+M", "Navigation", "goto-bracket"), |
| 100 | + PaletteCommand::new("Page Up", "PageUp", "Navigation", "page-up"), |
| 101 | + PaletteCommand::new("Page Down", "PageDown", "Navigation", "page-down"), |
| 102 | + |
| 103 | + // Selection |
| 104 | + PaletteCommand::new("Expand Selection to Brackets", "Ctrl+Shift+M", "Selection", "select-brackets"), |
| 105 | + PaletteCommand::new("Add Cursor Above", "Ctrl+Alt+Up", "Selection", "cursor-above"), |
| 106 | + PaletteCommand::new("Add Cursor Below", "Ctrl+Alt+Down", "Selection", "cursor-below"), |
| 107 | + |
| 108 | + // View / Panes |
| 109 | + PaletteCommand::new("Split Pane Vertical", "Ctrl+\\", "View", "split-vertical"), |
| 110 | + PaletteCommand::new("Split Pane Horizontal", "Ctrl+Shift+\\", "View", "split-horizontal"), |
| 111 | + PaletteCommand::new("Close Pane", "Ctrl+Shift+W", "View", "close-pane"), |
| 112 | + PaletteCommand::new("Focus Next Pane", "Ctrl+Alt+Right", "View", "next-pane"), |
| 113 | + PaletteCommand::new("Focus Previous Pane", "Ctrl+Alt+Left", "View", "prev-pane"), |
| 114 | + PaletteCommand::new("Toggle File Explorer", "Ctrl+B", "View", "toggle-explorer"), |
| 115 | + |
| 116 | + // LSP / Code Intelligence |
| 117 | + PaletteCommand::new("Go to Definition", "F12", "LSP", "goto-definition"), |
| 118 | + PaletteCommand::new("Find References", "Shift+F12", "LSP", "find-references"), |
| 119 | + PaletteCommand::new("Rename Symbol", "F2", "LSP", "rename"), |
| 120 | + PaletteCommand::new("Show Hover Info", "Ctrl+K Ctrl+I", "LSP", "hover"), |
| 121 | + PaletteCommand::new("Trigger Completion", "Ctrl+Space", "LSP", "completion"), |
| 122 | + PaletteCommand::new("LSP Server Manager", "Alt+M", "LSP", "server-manager"), |
| 123 | + |
| 124 | + // Bracket/Quote operations |
| 125 | + PaletteCommand::new("Jump to Bracket", "Alt+]", "Brackets", "jump-bracket"), |
| 126 | + PaletteCommand::new("Cycle Bracket Type", "Alt+[", "Brackets", "cycle-brackets"), |
| 127 | + PaletteCommand::new("Remove Surrounding", "Alt+Backspace", "Brackets", "remove-surrounding"), |
| 128 | + |
| 129 | + // Help |
| 130 | + PaletteCommand::new("Command Palette", "Ctrl+P", "Help", "command-palette"), |
| 131 | +]; |
| 132 | + |
| 36 | 133 | /// Prompt state for quit confirmation |
| 37 | 134 | #[derive(Debug, Clone, PartialEq)] |
| 38 | 135 | enum PromptState { |
@@ -98,6 +195,17 @@ enum PromptState { |
| 98 | 195 | /// Whether search is in progress |
| 99 | 196 | searching: bool, |
| 100 | 197 | }, |
| 198 | + /// Command palette (Ctrl+P) |
| 199 | + CommandPalette { |
| 200 | + /// Search/filter query (with > prefix) |
| 201 | + query: String, |
| 202 | + /// Filtered commands matching query |
| 203 | + filtered: Vec<PaletteCommand>, |
| 204 | + /// Currently selected index |
| 205 | + selected_index: usize, |
| 206 | + /// Scroll offset for long lists |
| 207 | + scroll_offset: usize, |
| 208 | + }, |
| 101 | 209 | } |
| 102 | 210 | |
| 103 | 211 | /// A single result from multi-file search |
@@ -1459,6 +1567,27 @@ impl Editor { |
| 1459 | 1567 | return Ok(()); // Modal handles cursor |
| 1460 | 1568 | } |
| 1461 | 1569 | |
| 1570 | + // Render command palette if active |
| 1571 | + if let PromptState::CommandPalette { |
| 1572 | + ref query, |
| 1573 | + ref filtered, |
| 1574 | + selected_index, |
| 1575 | + scroll_offset, |
| 1576 | + } = self.prompt { |
| 1577 | + // Convert commands to tuple format for render function |
| 1578 | + let commands_tuples: Vec<(String, String, String, String)> = filtered |
| 1579 | + .iter() |
| 1580 | + .map(|c| (c.name.to_string(), c.shortcut.to_string(), c.category.to_string(), c.id.to_string())) |
| 1581 | + .collect(); |
| 1582 | + self.screen.render_command_palette( |
| 1583 | + query, |
| 1584 | + &commands_tuples, |
| 1585 | + selected_index, |
| 1586 | + scroll_offset, |
| 1587 | + )?; |
| 1588 | + return Ok(()); // Modal handles cursor |
| 1589 | + } |
| 1590 | + |
| 1462 | 1591 | // Render find/replace bar if active (replaces status bar) |
| 1463 | 1592 | if let PromptState::FindReplace { |
| 1464 | 1593 | ref find_query, |
@@ -1741,6 +1870,8 @@ impl Editor { |
| 1741 | 1870 | (Key::F(5), _) => self.open_goto_line(), |
| 1742 | 1871 | // Multi-file search: F4 |
| 1743 | 1872 | (Key::F(4), _) => self.open_file_search(), |
| 1873 | + // Command palette: Ctrl+P |
| 1874 | + (Key::Char('p'), Modifiers { ctrl: true, .. }) => self.open_command_palette(), |
| 1744 | 1875 | |
| 1745 | 1876 | // === Editing === |
| 1746 | 1877 | (Key::Char(c), Modifiers { ctrl: false, alt: false, .. }) => { |
@@ -4416,6 +4547,78 @@ impl Editor { |
| 4416 | 4547 | _ => {} |
| 4417 | 4548 | } |
| 4418 | 4549 | } |
| 4550 | + PromptState::CommandPalette { |
| 4551 | + ref mut query, |
| 4552 | + ref mut filtered, |
| 4553 | + ref mut selected_index, |
| 4554 | + ref mut scroll_offset, |
| 4555 | + } => { |
| 4556 | + match key { |
| 4557 | + Key::Escape => { |
| 4558 | + self.prompt = PromptState::None; |
| 4559 | + } |
| 4560 | + Key::Enter => { |
| 4561 | + // Execute selected command |
| 4562 | + if let Some(cmd) = filtered.get(*selected_index) { |
| 4563 | + let cmd_id = cmd.id.to_string(); |
| 4564 | + self.prompt = PromptState::None; |
| 4565 | + self.execute_command(&cmd_id); |
| 4566 | + self.scroll_to_cursor(); // Ensure viewport follows cursor after command |
| 4567 | + } else { |
| 4568 | + self.prompt = PromptState::None; |
| 4569 | + } |
| 4570 | + } |
| 4571 | + Key::Up => { |
| 4572 | + if *selected_index > 0 { |
| 4573 | + *selected_index -= 1; |
| 4574 | + if *selected_index < *scroll_offset { |
| 4575 | + *scroll_offset = *selected_index; |
| 4576 | + } |
| 4577 | + } |
| 4578 | + } |
| 4579 | + Key::Down => { |
| 4580 | + if *selected_index + 1 < filtered.len() { |
| 4581 | + *selected_index += 1; |
| 4582 | + // Keep selected item visible (assume ~15 visible rows) |
| 4583 | + let visible_rows = 15; |
| 4584 | + if *selected_index >= *scroll_offset + visible_rows { |
| 4585 | + *scroll_offset = selected_index.saturating_sub(visible_rows - 1); |
| 4586 | + } |
| 4587 | + } |
| 4588 | + } |
| 4589 | + Key::PageUp => { |
| 4590 | + *selected_index = selected_index.saturating_sub(10); |
| 4591 | + if *selected_index < *scroll_offset { |
| 4592 | + *scroll_offset = *selected_index; |
| 4593 | + } |
| 4594 | + } |
| 4595 | + Key::PageDown => { |
| 4596 | + *selected_index = (*selected_index + 10).min(filtered.len().saturating_sub(1)); |
| 4597 | + let visible_rows = 15; |
| 4598 | + if *selected_index >= *scroll_offset + visible_rows { |
| 4599 | + *scroll_offset = selected_index.saturating_sub(visible_rows - 1); |
| 4600 | + } |
| 4601 | + } |
| 4602 | + Key::Backspace => { |
| 4603 | + if !query.is_empty() { |
| 4604 | + query.pop(); |
| 4605 | + *filtered = filter_commands(query); |
| 4606 | + *selected_index = 0; |
| 4607 | + *scroll_offset = 0; |
| 4608 | + } |
| 4609 | + } |
| 4610 | + Key::Char(c) => { |
| 4611 | + // Only accept printable characters |
| 4612 | + if !c.is_control() { |
| 4613 | + query.push(c); |
| 4614 | + *filtered = filter_commands(query); |
| 4615 | + *selected_index = 0; |
| 4616 | + *scroll_offset = 0; |
| 4617 | + } |
| 4618 | + } |
| 4619 | + _ => {} |
| 4620 | + } |
| 4621 | + } |
| 4419 | 4622 | PromptState::None => {} |
| 4420 | 4623 | } |
| 4421 | 4624 | Ok(()) |
@@ -5141,6 +5344,221 @@ impl Editor { |
| 5141 | 5344 | let viewport_height = self.screen.rows.saturating_sub(2) as usize; |
| 5142 | 5345 | pane.viewport_line = target_line.saturating_sub(viewport_height / 2); |
| 5143 | 5346 | } |
| 5347 | + |
| 5348 | + // === Command Palette === |
| 5349 | + |
| 5350 | + /// Open the command palette |
| 5351 | + fn open_command_palette(&mut self) { |
| 5352 | + let filtered = filter_commands(""); |
| 5353 | + self.prompt = PromptState::CommandPalette { |
| 5354 | + query: String::new(), |
| 5355 | + filtered, |
| 5356 | + selected_index: 0, |
| 5357 | + scroll_offset: 0, |
| 5358 | + }; |
| 5359 | + } |
| 5360 | + |
| 5361 | + /// Execute a command by its ID |
| 5362 | + fn execute_command(&mut self, command_id: &str) { |
| 5363 | + match command_id { |
| 5364 | + // File operations |
| 5365 | + "save" => { let _ = self.save(); } |
| 5366 | + "save-all" => { let _ = self.workspace.save_all(); } |
| 5367 | + "open" => self.open_fortress(), |
| 5368 | + "new-tab" => self.workspace.new_tab(), |
| 5369 | + "close-tab" => self.close_pane(), // Close current pane/tab |
| 5370 | + "next-tab" => self.workspace.next_tab(), |
| 5371 | + "prev-tab" => self.workspace.prev_tab(), |
| 5372 | + "quit" => self.try_quit(), |
| 5373 | + |
| 5374 | + // Edit operations |
| 5375 | + "undo" => self.undo(), |
| 5376 | + "redo" => self.redo(), |
| 5377 | + "cut" => self.cut(), |
| 5378 | + "copy" => self.copy(), |
| 5379 | + "paste" => self.paste(), |
| 5380 | + "select-all" => { |
| 5381 | + // Select all text in current buffer |
| 5382 | + let line_count = self.buffer().line_count(); |
| 5383 | + let last_line = line_count.saturating_sub(1); |
| 5384 | + let last_col = self.buffer().line_len(last_line); |
| 5385 | + self.cursor_mut().anchor_line = 0; |
| 5386 | + self.cursor_mut().anchor_col = 0; |
| 5387 | + self.cursor_mut().line = last_line; |
| 5388 | + self.cursor_mut().col = last_col; |
| 5389 | + self.cursor_mut().selecting = true; |
| 5390 | + } |
| 5391 | + "select-line" => self.select_line(), |
| 5392 | + "select-word" => self.select_word(), |
| 5393 | + "toggle-comment" => self.toggle_line_comment(), |
| 5394 | + "join-lines" => self.join_lines(), |
| 5395 | + "duplicate-line" => self.duplicate_line_down(), |
| 5396 | + "move-line-up" => self.move_line_up(), |
| 5397 | + "move-line-down" => self.move_line_down(), |
| 5398 | + "delete-line" => { |
| 5399 | + // Delete the current line |
| 5400 | + let line = self.cursor().line; |
| 5401 | + let line_count = self.buffer().line_count(); |
| 5402 | + let line_start = self.buffer().line_col_to_char(line, 0); |
| 5403 | + let line_end = if line + 1 < line_count { |
| 5404 | + self.buffer().line_col_to_char(line + 1, 0) |
| 5405 | + } else { |
| 5406 | + self.buffer().len_chars() |
| 5407 | + }; |
| 5408 | + if line_start < line_end { |
| 5409 | + self.buffer_mut().delete(line_start, line_end); |
| 5410 | + self.cursor_mut().col = 0; |
| 5411 | + self.cursor_mut().desired_col = 0; |
| 5412 | + // Clamp line if we deleted the last line |
| 5413 | + let new_line_count = self.buffer().line_count(); |
| 5414 | + if self.cursor().line >= new_line_count { |
| 5415 | + self.cursor_mut().line = new_line_count.saturating_sub(1); |
| 5416 | + } |
| 5417 | + } |
| 5418 | + } |
| 5419 | + "indent" => self.insert_tab(), |
| 5420 | + "outdent" => self.dedent(), |
| 5421 | + "transpose" => self.transpose_chars(), |
| 5422 | + |
| 5423 | + // Search operations |
| 5424 | + "find" => self.open_find(), |
| 5425 | + "replace" => self.open_replace(), |
| 5426 | + "find-next" => self.find_next(), |
| 5427 | + "find-prev" => self.find_prev(), |
| 5428 | + "search-files" => self.open_file_search(), |
| 5429 | + |
| 5430 | + // Navigation |
| 5431 | + "goto-line" => self.open_goto_line(), |
| 5432 | + "goto-start" => { |
| 5433 | + self.cursor_mut().line = 0; |
| 5434 | + self.cursor_mut().col = 0; |
| 5435 | + self.cursor_mut().desired_col = 0; |
| 5436 | + self.cursor_mut().clear_selection(); |
| 5437 | + } |
| 5438 | + "goto-end" => { |
| 5439 | + let last_line = self.buffer().line_count().saturating_sub(1); |
| 5440 | + let last_col = self.buffer().line_len(last_line); |
| 5441 | + self.cursor_mut().line = last_line; |
| 5442 | + self.cursor_mut().col = last_col; |
| 5443 | + self.cursor_mut().desired_col = last_col; |
| 5444 | + self.cursor_mut().clear_selection(); |
| 5445 | + } |
| 5446 | + "goto-bracket" => self.jump_to_matching_bracket(), |
| 5447 | + "page-up" => self.page_up(false), |
| 5448 | + "page-down" => self.page_down(false), |
| 5449 | + |
| 5450 | + // Selection |
| 5451 | + "select-brackets" => self.jump_to_matching_bracket(), // TODO: implement select inside brackets |
| 5452 | + "cursor-above" => self.add_cursor_above(), |
| 5453 | + "cursor-below" => self.add_cursor_below(), |
| 5454 | + |
| 5455 | + // View / Panes |
| 5456 | + "split-vertical" => self.split_vertical(), |
| 5457 | + "split-horizontal" => self.split_horizontal(), |
| 5458 | + "close-pane" => self.close_pane(), |
| 5459 | + "next-pane" => self.tab_mut().navigate_pane(PaneDirection::Right), |
| 5460 | + "prev-pane" => self.tab_mut().navigate_pane(PaneDirection::Left), |
| 5461 | + "toggle-explorer" => self.workspace.fuss.toggle(), |
| 5462 | + |
| 5463 | + // LSP operations |
| 5464 | + "goto-definition" => self.lsp_goto_definition(), |
| 5465 | + "find-references" => self.lsp_find_references(), |
| 5466 | + "rename" => self.lsp_rename(), |
| 5467 | + "hover" => self.lsp_hover(), |
| 5468 | + "completion" => self.filter_completions(), |
| 5469 | + "server-manager" => self.toggle_server_manager(), |
| 5470 | + |
| 5471 | + // Bracket/Quote operations |
| 5472 | + "jump-bracket" => self.jump_to_matching_bracket(), |
| 5473 | + "cycle-brackets" => self.cycle_brackets(), |
| 5474 | + "remove-surrounding" => self.remove_surrounding(), |
| 5475 | + |
| 5476 | + // Help |
| 5477 | + "command-palette" => {} // Already open |
| 5478 | + |
| 5479 | + _ => { |
| 5480 | + self.message = Some(format!("Unknown command: {}", command_id)); |
| 5481 | + } |
| 5482 | + } |
| 5483 | + } |
| 5484 | +} |
| 5485 | + |
| 5486 | +/// Fuzzy match scoring for command palette |
| 5487 | +fn fuzzy_match_score(text: &str, pattern: &str) -> i32 { |
| 5488 | + if pattern.is_empty() { |
| 5489 | + return 100; // Empty pattern matches everything with base score |
| 5490 | + } |
| 5491 | + |
| 5492 | + let text_lower = text.to_lowercase(); |
| 5493 | + let pattern_lower = pattern.to_lowercase(); |
| 5494 | + |
| 5495 | + let mut score = 0i32; |
| 5496 | + let mut pattern_idx = 0; |
| 5497 | + let mut consecutive = 0; |
| 5498 | + let pattern_chars: Vec<char> = pattern_lower.chars().collect(); |
| 5499 | + let text_chars: Vec<char> = text_lower.chars().collect(); |
| 5500 | + |
| 5501 | + if pattern_chars.is_empty() { |
| 5502 | + return 100; |
| 5503 | + } |
| 5504 | + |
| 5505 | + for (i, &tc) in text_chars.iter().enumerate() { |
| 5506 | + if pattern_idx >= pattern_chars.len() { |
| 5507 | + break; |
| 5508 | + } |
| 5509 | + |
| 5510 | + if tc == pattern_chars[pattern_idx] { |
| 5511 | + score += 10; |
| 5512 | + consecutive += 1; |
| 5513 | + |
| 5514 | + // Bonus for consecutive matches |
| 5515 | + if consecutive > 1 { |
| 5516 | + score += 5; |
| 5517 | + } |
| 5518 | + |
| 5519 | + // Bonus for matching at start or after space/separator |
| 5520 | + if i == 0 || matches!(text_chars.get(i.wrapping_sub(1)), Some(' ' | ':' | '-' | '_' | '/')) { |
| 5521 | + score += 15; |
| 5522 | + } |
| 5523 | + |
| 5524 | + pattern_idx += 1; |
| 5525 | + } else { |
| 5526 | + consecutive = 0; |
| 5527 | + } |
| 5528 | + } |
| 5529 | + |
| 5530 | + // Only return positive score if all pattern characters matched |
| 5531 | + if pattern_idx == pattern_chars.len() { |
| 5532 | + score |
| 5533 | + } else { |
| 5534 | + 0 |
| 5535 | + } |
| 5536 | +} |
| 5537 | + |
| 5538 | +/// Filter and sort commands by fuzzy match score |
| 5539 | +fn filter_commands(query: &str) -> Vec<PaletteCommand> { |
| 5540 | + let mut filtered: Vec<PaletteCommand> = ALL_COMMANDS |
| 5541 | + .iter() |
| 5542 | + .filter_map(|cmd| { |
| 5543 | + // Match against name, category, or command ID |
| 5544 | + let name_score = fuzzy_match_score(cmd.name, query); |
| 5545 | + let category_score = fuzzy_match_score(cmd.category, query) / 2; // Category match worth less |
| 5546 | + let id_score = fuzzy_match_score(cmd.id, query) / 2; |
| 5547 | + |
| 5548 | + let score = name_score.max(category_score).max(id_score); |
| 5549 | + if score > 0 { |
| 5550 | + let mut cmd = cmd.clone(); |
| 5551 | + cmd.score = score; |
| 5552 | + Some(cmd) |
| 5553 | + } else { |
| 5554 | + None |
| 5555 | + } |
| 5556 | + }) |
| 5557 | + .collect(); |
| 5558 | + |
| 5559 | + // Sort by score descending |
| 5560 | + filtered.sort_by(|a, b| b.score.cmp(&a.score)); |
| 5561 | + filtered |
| 5144 | 5562 | } |
| 5145 | 5563 | |
| 5146 | 5564 | impl Drop for Editor { |