reovim_plugin_pair/
lib.rs1pub mod rainbow;
40pub mod stage;
41pub mod state;
42
43use std::{
44 any::TypeId,
45 sync::{
46 Arc,
47 atomic::{AtomicU8, Ordering},
48 },
49};
50
51use reovim_core::{
52 event_bus::{
53 BufferClosed, BufferModification, BufferModified, CursorMoved, EventBus, EventResult,
54 RequestInsertText,
55 },
56 plugin::{Plugin, PluginContext, PluginId, PluginStateRegistry},
57};
58
59use {stage::PairRenderStage, state::SharedPairState};
60
61static LAST_AUTO_INSERT: AtomicU8 = AtomicU8::new(0);
64
65fn char_to_code(c: &str) -> u8 {
66 match c {
67 "`" => 1,
68 "'" => 2,
69 "\"" => 3,
70 _ => 0,
71 }
72}
73
74fn is_last_auto_insert(c: &str) -> bool {
75 let code = char_to_code(c);
76 code != 0 && LAST_AUTO_INSERT.swap(0, Ordering::SeqCst) == code
77}
78
79pub struct PairPlugin {
81 state: Arc<SharedPairState>,
82}
83
84impl Default for PairPlugin {
85 fn default() -> Self {
86 Self::new()
87 }
88}
89
90impl PairPlugin {
91 #[must_use]
93 pub fn new() -> Self {
94 Self {
95 state: Arc::new(SharedPairState::new()),
96 }
97 }
98}
99
100impl Plugin for PairPlugin {
101 fn id(&self) -> PluginId {
102 PluginId::new("reovim:pair")
103 }
104
105 fn name(&self) -> &'static str {
106 "Pair"
107 }
108
109 fn description(&self) -> &'static str {
110 "Rainbow brackets, matched pair highlighting, and auto-pair insertion"
111 }
112
113 fn dependencies(&self) -> Vec<TypeId> {
114 vec![]
115 }
116
117 fn build(&self, ctx: &mut PluginContext) {
118 let stage = Arc::new(PairRenderStage::new(Arc::clone(&self.state)));
120 ctx.register_render_stage(stage);
121
122 tracing::debug!("PairPlugin: registered render stage");
123 }
124
125 fn init_state(&self, registry: &PluginStateRegistry) {
126 registry.register(Arc::clone(&self.state));
128
129 tracing::debug!("PairPlugin: initialized SharedPairState");
130 }
131
132 fn subscribe(&self, bus: &EventBus, state: Arc<PluginStateRegistry>) {
133 let state_clone = Arc::clone(&state);
135 bus.subscribe::<CursorMoved, _>(100, move |event, ctx| {
136 state_clone.with::<Arc<SharedPairState>, _, _>(|pair_state| {
137 pair_state.update_cursor(event.buffer_id, event.to);
138 });
139 ctx.request_render();
141 EventResult::Handled
142 });
143
144 let state_clone = Arc::clone(&state);
146 bus.subscribe::<BufferModified, _>(100, move |event, _ctx| {
147 state_clone.with::<Arc<SharedPairState>, _, _>(|pair_state| {
148 pair_state.invalidate_buffer(event.buffer_id);
149 });
150 tracing::trace!(buffer_id = event.buffer_id, "PairPlugin: invalidated bracket cache");
151 EventResult::Handled
152 });
153
154 bus.subscribe::<BufferModified, _>(90, move |event, ctx| {
156 if let BufferModification::Insert { text, .. } = &event.modification {
157 if is_last_auto_insert(text) {
160 tracing::trace!(
161 buffer_id = event.buffer_id,
162 text = text,
163 "PairPlugin: skipping auto-insert for our own symmetric pair"
164 );
165 return EventResult::Handled;
166 }
167
168 let (close, is_symmetric) = match text.as_str() {
170 "(" => (Some(")"), false),
171 "[" => (Some("]"), false),
172 "{" => (Some("}"), false),
173 "`" => (Some("`"), true),
174 "'" => (Some("'"), true),
175 "\"" => (Some("\""), true),
176 _ => (None, false),
178 };
179
180 if let Some(closing) = close {
181 tracing::trace!(
182 buffer_id = event.buffer_id,
183 open = text,
184 close = closing,
185 is_symmetric = is_symmetric,
186 "PairPlugin: auto-inserting closing bracket"
187 );
188
189 if is_symmetric {
191 LAST_AUTO_INSERT.store(char_to_code(closing), Ordering::SeqCst);
192 }
193
194 ctx.emit(RequestInsertText {
196 text: closing.to_string(),
197 move_cursor_left: true,
198 delete_prefix_len: 0,
199 });
200 }
201 }
202 EventResult::Handled
203 });
204
205 let state_clone = Arc::clone(&state);
207 bus.subscribe::<BufferClosed, _>(100, move |event, _ctx| {
208 state_clone.with::<Arc<SharedPairState>, _, _>(|pair_state| {
209 pair_state.remove_buffer(event.buffer_id);
210 });
211 tracing::trace!(
212 buffer_id = event.buffer_id,
213 "PairPlugin: cleaned up pair state for closed buffer"
214 );
215 EventResult::Handled
216 });
217
218 tracing::debug!("PairPlugin: subscribed to cursor and buffer events");
219 }
220}
221
222pub use state::{BracketInfo, PairState};