lattice_mode/modes/hover.rs
1//! `hover-mode` -- minor mode activated on the hover popup buffer
2//! (M.4 hover-popup unification). Marker mode for v1; the only
3//! behaviour gated on it today is the State-A auto-dismiss-on-
4//! doc-cursor-motion check in the App's dispatch loop. Future
5//! hover-only contributions (auto-close timer, bound-`<Esc>`-to-
6//! dismiss, signature-help fan-in) layer on without touching the
7//! popup-overlay code.
8//!
9//! Hover content is markdown; the major mode the App activates
10//! alongside `hover-mode` is `markdown-mode`, so the renderer's
11//! syntax + link extraction treats hover content as any other
12//! markdown buffer. Help-mode is intentionally NOT activated on
13//! hover popups -- hover content's links are typically external
14//! URLs we don't follow internally.
15
16use lattice_config::OptionOverrideSet;
17
18use crate::{CapabilitySet, LifecycleFuture, Mode, ModeContext, ModeId, ModeKind};
19
20/// `hover-mode` — marker minor on the hover popup buffer (activated
21/// alongside `markdown-mode`). Wraps long lines and borrows help-mode's
22/// read-only invocation runner; see the module docs.
23pub struct HoverMode;
24
25impl HoverMode {
26 /// The canonical id, `"hover-mode"` — what [`Mode::id`](crate::Mode::id)
27 /// returns. Use it to name this mode without an instance (activation,
28 /// `implies`, keymap layers, tests).
29 pub fn mode_id() -> ModeId {
30 ModeId::new("hover-mode")
31 }
32}
33
34impl Mode for HoverMode {
35 type Guard = ();
36 fn id(&self) -> ModeId {
37 Self::mode_id()
38 }
39 fn kind(&self) -> ModeKind {
40 ModeKind::Minor
41 }
42 fn options(&self) -> OptionOverrideSet {
43 // Bug 4: hover popups are width-constrained (cursor-
44 // anchored, capped at ~80 cells); long markdown bodies
45 // need to wrap or they overflow horizontally with
46 // no horizontal scroll path.
47 lattice_config::overrides! {
48 lattice_config::Wrap = true,
49 }
50 }
51 fn required_capabilities(&self) -> CapabilitySet {
52 CapabilitySet::empty()
53 }
54 /// 2026-05-26: hover popups want the same read-only motion
55 /// dispatch help buffers use. Point at help-mode's runner id
56 /// so the host's invocation_runners map (where boot registers
57 /// `Editor::run_help_invocation` under `help-mode`) routes
58 /// `j` / `k` / `gg` / `G` through the help runner without
59 /// needing a separate `run_hover_invocation`.
60 fn invocation_runner(&self) -> Option<ModeId> {
61 Some(crate::HelpMode::mode_id())
62 }
63 fn on_activate(&self, _ctx: ModeContext) -> LifecycleFuture<'_, ()> {
64 Box::pin(async { Ok(()) })
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn id_and_kind() {
74 assert_eq!(HoverMode.id(), HoverMode::mode_id());
75 assert_eq!(HoverMode::mode_id().as_str(), "hover-mode");
76 assert_eq!(HoverMode.kind(), ModeKind::Minor);
77 }
78
79 #[test]
80 fn contributes_wrap() {
81 // Bug 4: hover popups are width-constrained; long
82 // markdown bodies need to wrap.
83 let opts = HoverMode.options();
84 assert_eq!(opts.iter().count(), 1, "expected Wrap");
85 }
86}