Skip to main content

gitlab_tracker_core/
shortcuts.rs

1/// A single keyboard shortcut entry displayed in the help popup.
2pub struct ShortcutEntry {
3    /// The key combination as shown to the user (e.g. `"?"`, `"j / k"`, `"Tab"`).
4    pub key: &'static str,
5    /// Short description of what the shortcut does.
6    pub description: &'static str,
7}
8
9/// A named group of shortcut entries, one per lib (Core, Redmine, …).
10///
11/// Each lib that registers shortcuts produces exactly one `ShortcutBlock`.
12/// The orchestrator collects all blocks and renders them section by section
13/// in the help popup, sorted by `priority` (lowest first).
14pub struct ShortcutBlock {
15    /// Section header rendered above the entries (e.g. `"Core"`, `"Redmine"`).
16    pub section: &'static str,
17    /// Ordered list of shortcut entries belonging to this section.
18    pub entries: &'static [ShortcutEntry],
19    /// Display priority — lower values appear first in the help popup.
20    ///
21    /// Convention:
22    ///   - `0`   → Core (always first)
23    ///   - `100` → first-party tracker plugins (Redmine, Jira, …)
24    ///   - `200` → third-party / community plugins
25    pub priority: u8,
26}
27
28/// A registered shortcut factory: a plain function pointer that produces a [`ShortcutBlock`].
29///
30/// # Why a function pointer and not `Box<dyn Trait>`?
31///
32/// `inventory` requires collected types to be `'static`. A `fn() -> ShortcutBlock` is
33/// trivially `'static` and requires no heap allocation at registration time, making it
34/// ideal for link-time auto-registration.
35///
36/// # How to register a new provider (e.g. Jira)
37///
38/// In `gitlab-tracker-jira/src/shortcuts.rs`, add:
39/// ```rust
40/// fn jira_shortcuts() -> ShortcutBlock { /* … */ }
41/// inventory::submit!(ShortcutFactory(jira_shortcuts));
42/// ```
43/// That's it — no change to `main.rs` or any other existing file.
44pub struct ShortcutFactory(pub fn() -> ShortcutBlock);
45
46// Declare the global registry. Every `inventory::submit!(ShortcutFactory(…))` call
47// anywhere in the dependency graph (including optional/feature-gated crates that are
48// actually linked) will be collected here at startup.
49inventory::collect!(ShortcutFactory);
50
51/// Collects all registered [`ShortcutBlock`]s from every linked crate,
52/// sorted by `priority` (ascending) so Core always appears before plugins
53/// regardless of link order.
54///
55/// Call this once at startup to populate `App::shortcut_providers`.
56pub fn collect_all_blocks() -> Vec<ShortcutBlock> {
57    let mut blocks: Vec<ShortcutBlock> = inventory::iter::<ShortcutFactory>
58        .into_iter()
59        .map(|factory| (factory.0)())
60        .collect();
61    // Stable sort preserves relative order of blocks with equal priority.
62    blocks.sort_by_key(|b| b.priority);
63    blocks
64}