reovim_plugin_sticky_context/
lib.rs

1//! Sticky context headers plugin
2//!
3//! Displays 1-3 enclosing scope headers pinned at the top of the viewport
4//! as the user scrolls through large files.
5//!
6//! This plugin subscribes to `ViewportContextUpdated` events from the context
7//! plugin and caches the context for rendering, avoiding redundant computation.
8
9use std::sync::Arc;
10
11use {
12    reovim_core::{
13        event_bus::{EventBus, EventResult},
14        plugin::{Plugin, PluginContext, PluginId, PluginStateRegistry},
15    },
16    reovim_plugin_context::ViewportContextUpdated,
17};
18
19mod state;
20mod window;
21
22use {
23    state::{CachedViewportContext, StickyContextState},
24    window::StickyContextWindow,
25};
26
27/// Sticky context headers plugin
28pub struct StickyContextPlugin {
29    state: Arc<StickyContextState>,
30}
31
32impl StickyContextPlugin {
33    /// Create a new sticky context plugin
34    #[must_use]
35    pub fn new() -> Self {
36        Self {
37            state: Arc::new(StickyContextState::new()),
38        }
39    }
40}
41
42impl Default for StickyContextPlugin {
43    fn default() -> Self {
44        Self::new()
45    }
46}
47
48impl Plugin for StickyContextPlugin {
49    fn id(&self) -> PluginId {
50        PluginId::new("reovim:sticky-context")
51    }
52
53    fn name(&self) -> &'static str {
54        "sticky-context"
55    }
56
57    fn init_state(&self, registry: &PluginStateRegistry) {
58        // Register plugin state
59        registry.register(self.state.clone());
60
61        // Register plugin window for rendering
62        registry.register_plugin_window(Arc::new(StickyContextWindow::new(self.state.clone())));
63
64        tracing::debug!("StickyContextPlugin: initialized");
65    }
66
67    fn build(&self, _ctx: &mut PluginContext) {
68        tracing::debug!("StickyContextPlugin::build");
69    }
70
71    fn subscribe(&self, bus: &EventBus, _state: Arc<PluginStateRegistry>) {
72        // Register settings
73        Self::register_settings(bus);
74
75        // Subscribe to viewport context updates from context plugin
76        let plugin_state = Arc::clone(&self.state);
77        bus.subscribe::<ViewportContextUpdated, _>(100, move |event, _ctx| {
78            // Cache the context for rendering
79            plugin_state.set_cached_context(CachedViewportContext {
80                buffer_id: event.buffer_id,
81                context: event.context.clone(),
82            });
83
84            tracing::trace!(
85                window_id = event.window_id,
86                buffer_id = event.buffer_id,
87                top_line = event.top_line,
88                has_context = event.context.is_some(),
89                "StickyContextPlugin: received ViewportContextUpdated"
90            );
91
92            EventResult::Handled
93        });
94
95        tracing::debug!("StickyContextPlugin::subscribe - subscribed to ViewportContextUpdated");
96    }
97}
98
99impl StickyContextPlugin {
100    /// Register plugin settings
101    fn register_settings(bus: &EventBus) {
102        use reovim_core::option::{OptionCategory, OptionSpec, OptionValue, RegisterOption};
103
104        // Enable/disable sticky headers
105        bus.emit(RegisterOption::new(
106            OptionSpec::new(
107                "sticky_headers_enabled",
108                "Enable sticky context headers",
109                OptionValue::Bool(true),
110            )
111            .with_category(OptionCategory::Display)
112            .with_section("Editor")
113            .with_display_order(55),
114        ));
115
116        // Max number of headers to show
117        bus.emit(RegisterOption::new(
118            OptionSpec::new(
119                "sticky_headers_max_count",
120                "Maximum sticky headers to display",
121                OptionValue::Integer(3),
122            )
123            .with_category(OptionCategory::Display)
124            .with_section("Editor")
125            .with_display_order(56),
126        ));
127
128        // Show separator line
129        bus.emit(RegisterOption::new(
130            OptionSpec::new(
131                "sticky_headers_show_separator",
132                "Show separator line below sticky headers",
133                OptionValue::Bool(true),
134            )
135            .with_category(OptionCategory::Display)
136            .with_section("Editor")
137            .with_display_order(57),
138        ));
139    }
140}