1pub mod commands;
19pub mod microscope;
20
21use std::{any::TypeId, sync::Arc};
22
23use reovim_core::{
24 bind::{CommandRef, EditModeKind, KeyMapInner, KeymapScope, SubModeKind},
25 display::DisplayInfo,
26 frame::FrameBuffer,
27 highlight::Theme,
28 keys,
29 modd::ComponentId,
30 plugin::{
31 EditorContext, Plugin, PluginContext, PluginId, PluginStateRegistry, PluginWindow, Rect,
32 WindowConfig,
33 },
34 rpc::{RpcHandler, RpcHandlerContext, RpcResult},
35};
36
37pub use commands::{
39 MicroscopeBackspace, MicroscopeClearQuery, MicroscopeClose, MicroscopeCommands,
40 MicroscopeConfirm, MicroscopeCursorEnd, MicroscopeCursorLeft, MicroscopeCursorRight,
41 MicroscopeCursorStart, MicroscopeDeleteWord, MicroscopeEnterInsert, MicroscopeEnterNormal,
42 MicroscopeFindBuffers, MicroscopeFindFiles, MicroscopeFindRecent, MicroscopeGotoFirst,
43 MicroscopeGotoLast, MicroscopeHelp, MicroscopeInsertChar, MicroscopeKeymaps,
44 MicroscopeLiveGrep, MicroscopeOpen, MicroscopePageDown, MicroscopePageUp, MicroscopeProfiles,
45 MicroscopeSelectNext, MicroscopeSelectPrev, MicroscopeThemes, MicroscopeWordBackward,
46 MicroscopeWordForward,
47};
48
49pub use microscope::{
51 BufferInfo, LayoutBounds, LayoutConfig, LoadingState, MatcherItem, MatcherStatus,
52 MicroscopeAction, MicroscopeData, MicroscopeItem, MicroscopeMatcher, MicroscopeState,
53 PanelBounds, Picker, PickerContext, PickerRegistry, PreviewContent, PromptMode, push_item,
54 push_items,
55};
56
57pub struct MicroscopePluginWindow;
59
60impl PluginWindow for MicroscopePluginWindow {
61 fn window_config(
62 &self,
63 state: &Arc<PluginStateRegistry>,
64 ctx: &EditorContext,
65 ) -> Option<WindowConfig> {
66 let is_active = state
68 .with::<MicroscopeState, _, _>(|s| s.active)
69 .unwrap_or(false);
70
71 if !is_active {
72 return None;
73 }
74
75 state.with_mut::<MicroscopeState, _, _>(|s| {
77 s.update_bounds(ctx.screen_width, ctx.screen_height);
78 });
79
80 state.with::<MicroscopeState, _, _>(|microscope| {
82 let total = µscope.bounds.total;
83 Some(WindowConfig {
84 bounds: Rect::new(total.x, total.y, total.width, total.height),
85 z_order: 300, visible: true,
87 })
88 })?
89 }
90
91 #[allow(clippy::cast_possible_truncation, clippy::too_many_lines)]
92 fn render(
93 &self,
94 state: &Arc<PluginStateRegistry>,
95 _ctx: &EditorContext,
96 buffer: &mut FrameBuffer,
97 _bounds: Rect,
98 theme: &Theme,
99 ) {
100 let Some(microscope) = state.with::<MicroscopeState, _, _>(Clone::clone) else {
101 return;
102 };
103
104 let border_style = &theme.popup.border;
105 let normal_style = &theme.popup.normal;
106 let selected_style = &theme.popup.selected;
107
108 let results = microscope.bounds.results;
109 let status = microscope.bounds.status;
110
111 Self::render_results_panel(
113 buffer,
114 µscope,
115 &results,
116 border_style,
117 normal_style,
118 selected_style,
119 );
120
121 if let Some(preview_bounds) = microscope.bounds.preview {
123 Self::render_preview_panel(
124 buffer,
125 µscope,
126 &preview_bounds,
127 border_style,
128 normal_style,
129 selected_style, );
131 }
132
133 Self::render_status_line(buffer, µscope, &status, border_style, normal_style);
135 }
136}
137
138impl MicroscopePluginWindow {
139 #[allow(clippy::cast_possible_truncation)]
141 fn render_results_panel(
142 buffer: &mut FrameBuffer,
143 microscope: &MicroscopeState,
144 bounds: &PanelBounds,
145 border_style: &reovim_core::highlight::Style,
146 normal_style: &reovim_core::highlight::Style,
147 selected_style: &reovim_core::highlight::Style,
148 ) {
149 let x = bounds.x;
150 let y = bounds.y;
151 let width = bounds.width;
152 let height = bounds.height;
153
154 buffer.put_char(x, y, '╭', border_style);
156 let title = format!(" {} ", microscope.title);
157 for (i, ch) in title.chars().enumerate() {
158 let cx = x + 1 + i as u16;
159 if cx < x + width - 1 {
160 buffer.put_char(cx, y, ch, border_style);
161 }
162 }
163 for cx in (x + 1 + title.len() as u16)..(x + width - 1) {
164 buffer.put_char(cx, y, '─', border_style);
165 }
166 buffer.put_char(x + width - 1, y, '╮', border_style);
167
168 let input_y = y + 1;
170 buffer.put_char(x, input_y, '│', border_style);
171 buffer.put_char(x + 1, input_y, '>', normal_style);
172 buffer.put_char(x + 2, input_y, ' ', normal_style);
173
174 for (i, ch) in microscope.query.chars().enumerate() {
176 let cx = x + 3 + i as u16;
177 if cx < x + width - 1 {
178 buffer.put_char(cx, input_y, ch, normal_style);
179 }
180 }
181 for cx in (x + 3 + microscope.query.len() as u16)..(x + width - 1) {
183 buffer.put_char(cx, input_y, ' ', normal_style);
184 }
185 buffer.put_char(x + width - 1, input_y, '│', border_style);
186
187 let sep_y = y + 2;
189 buffer.put_char(x, sep_y, '├', border_style);
190 for cx in (x + 1)..(x + width - 1) {
191 buffer.put_char(cx, sep_y, '─', border_style);
192 }
193 buffer.put_char(x + width - 1, sep_y, '┤', border_style);
194
195 let results_start = y + 3;
197 let visible_items = microscope.visible_items();
198 let max_results = height.saturating_sub(4) as usize;
199 let selected_in_view = microscope
200 .selected_index
201 .saturating_sub(microscope.scroll_offset);
202
203 for (idx, item) in visible_items.iter().take(max_results).enumerate() {
204 let ry = results_start + idx as u16;
205 let is_selected = idx == selected_in_view;
206 let style = if is_selected {
207 selected_style
208 } else {
209 normal_style
210 };
211
212 buffer.put_char(x, ry, '│', border_style);
213
214 let indicator = if is_selected { '>' } else { ' ' };
216 buffer.put_char(x + 1, ry, indicator, style);
217 buffer.put_char(x + 2, ry, ' ', style);
218
219 let mut text_start = x + 3;
221 if let Some(icon) = item.icon {
222 buffer.put_char(text_start, ry, icon, style);
223 buffer.put_char(text_start + 1, ry, ' ', style);
224 text_start += 2;
225 }
226
227 for (i, ch) in item.display.chars().enumerate() {
229 let cx = text_start + i as u16;
230 if cx < x + width - 1 {
231 buffer.put_char(cx, ry, ch, style);
232 }
233 }
234
235 let text_end = text_start + item.display.len() as u16;
237 for cx in text_end..(x + width - 1) {
238 buffer.put_char(cx, ry, ' ', style);
239 }
240
241 buffer.put_char(x + width - 1, ry, '│', border_style);
242 }
243
244 let items_shown = visible_items.len().min(max_results) as u16;
246 for ry in (results_start + items_shown)..(y + height - 1) {
247 buffer.put_char(x, ry, '│', border_style);
248 for cx in (x + 1)..(x + width - 1) {
249 buffer.put_char(cx, ry, ' ', normal_style);
250 }
251 buffer.put_char(x + width - 1, ry, '│', border_style);
252 }
253
254 let bottom_y = y + height - 1;
256 buffer.put_char(x, bottom_y, '╰', border_style);
257 for cx in (x + 1)..(x + width - 1) {
258 buffer.put_char(cx, bottom_y, '─', border_style);
259 }
260 buffer.put_char(x + width - 1, bottom_y, '┴', border_style);
261 }
262
263 #[allow(clippy::cast_possible_truncation)]
265 fn render_preview_panel(
266 buffer: &mut FrameBuffer,
267 microscope: &MicroscopeState,
268 bounds: &PanelBounds,
269 border_style: &reovim_core::highlight::Style,
270 normal_style: &reovim_core::highlight::Style,
271 highlight_style: &reovim_core::highlight::Style,
272 ) {
273 let x = bounds.x;
274 let y = bounds.y;
275 let width = bounds.width;
276 let height = bounds.height;
277
278 buffer.put_char(x, y, '┬', border_style);
280 let title = microscope
281 .preview
282 .as_ref()
283 .and_then(|p| p.title.as_deref())
284 .unwrap_or(" Preview ");
285 let title = format!(" {title} ");
286 for (i, ch) in title.chars().enumerate() {
287 let cx = x + 1 + i as u16;
288 if cx < x + width - 1 {
289 buffer.put_char(cx, y, ch, border_style);
290 }
291 }
292 for cx in (x + 1 + title.len() as u16)..(x + width - 1) {
293 buffer.put_char(cx, y, '─', border_style);
294 }
295 buffer.put_char(x + width - 1, y, '╮', border_style);
296
297 let content_start = y + 1;
299 let max_lines = height.saturating_sub(2) as usize;
300
301 if let Some(preview) = µscope.preview {
302 let styled_lines = preview.styled_lines.as_ref();
304
305 for (idx, line) in preview.lines.iter().take(max_lines).enumerate() {
306 let ry = content_start + idx as u16;
307 buffer.put_char(x, ry, '│', border_style);
308
309 let is_highlight_line = preview.highlight_line == Some(idx);
311 let base_style = if is_highlight_line {
312 highlight_style
313 } else {
314 normal_style
315 };
316
317 let line_spans = styled_lines.and_then(|lines| lines.get(idx));
319
320 let mut byte_offset = 0;
322 for (i, ch) in line.chars().enumerate() {
323 let cx = x + 1 + i as u16;
324 if cx < x + width - 1 {
325 let char_style = if let Some(spans) = line_spans {
327 spans
329 .iter()
330 .find(|span| byte_offset >= span.start && byte_offset < span.end)
331 .map(|span| &span.style)
332 .unwrap_or(base_style)
333 } else {
334 base_style
335 };
336 buffer.put_char(cx, ry, ch, char_style);
337 }
338 byte_offset += ch.len_utf8();
339 }
340
341 let line_end = x + 1 + line.chars().count().min((width - 2) as usize) as u16;
343 for cx in line_end..(x + width - 1) {
344 buffer.put_char(cx, ry, ' ', base_style);
345 }
346
347 buffer.put_char(x + width - 1, ry, '│', border_style);
348 }
349
350 let lines_shown = preview.lines.len().min(max_lines) as u16;
352 for ry in (content_start + lines_shown)..(y + height - 1) {
353 buffer.put_char(x, ry, '│', border_style);
354 for cx in (x + 1)..(x + width - 1) {
355 buffer.put_char(cx, ry, ' ', normal_style);
356 }
357 buffer.put_char(x + width - 1, ry, '│', border_style);
358 }
359 } else {
360 for ry in content_start..(y + height - 1) {
362 buffer.put_char(x, ry, '│', border_style);
363 for cx in (x + 1)..(x + width - 1) {
364 buffer.put_char(cx, ry, ' ', normal_style);
365 }
366 buffer.put_char(x + width - 1, ry, '│', border_style);
367 }
368 }
369
370 let bottom_y = y + height - 1;
372 buffer.put_char(x, bottom_y, '┴', border_style);
373 for cx in (x + 1)..(x + width - 1) {
374 buffer.put_char(cx, bottom_y, '─', border_style);
375 }
376 buffer.put_char(x + width - 1, bottom_y, '╯', border_style);
377 }
378
379 #[allow(clippy::cast_possible_truncation)]
381 fn render_status_line(
382 buffer: &mut FrameBuffer,
383 microscope: &MicroscopeState,
384 bounds: &PanelBounds,
385 _border_style: &reovim_core::highlight::Style,
386 normal_style: &reovim_core::highlight::Style,
387 ) {
388 let x = bounds.x;
389 let y = bounds.y;
390 let width = bounds.width;
391
392 let status = microscope.status_text();
394 for (i, ch) in status.chars().enumerate() {
395 let cx = x + i as u16;
396 if cx < x + width {
397 buffer.put_char(cx, y, ch, normal_style);
398 }
399 }
400
401 let help = "<CR> Confirm | <Esc> Close";
403 let help_start = (x + width).saturating_sub(help.len() as u16);
404 for (i, ch) in help.chars().enumerate() {
405 let cx = help_start + i as u16;
406 if cx >= x + status.len() as u16 && cx < x + width {
407 buffer.put_char(cx, y, ch, normal_style);
408 }
409 }
410
411 for cx in (x + status.len() as u16)..help_start {
413 buffer.put_char(cx, y, ' ', normal_style);
414 }
415 }
416}
417
418pub const COMPONENT_ID: ComponentId = ComponentId("microscope");
420
421pub mod command_id {
423 use reovim_core::command::CommandId;
424
425 pub const MICROSCOPE_FIND_FILES: CommandId = CommandId::new("microscope_find_files");
427 pub const MICROSCOPE_FIND_BUFFERS: CommandId = CommandId::new("microscope_find_buffers");
428 pub const MICROSCOPE_LIVE_GREP: CommandId = CommandId::new("microscope_live_grep");
429 pub const MICROSCOPE_RECENT_FILES: CommandId = CommandId::new("microscope_recent_files");
430 pub const MICROSCOPE_COMMANDS: CommandId = CommandId::new("microscope_commands");
431 pub const MICROSCOPE_HELP_TAGS: CommandId = CommandId::new("microscope_help_tags");
432 pub const MICROSCOPE_KEYMAPS: CommandId = CommandId::new("microscope_keymaps");
433 pub const MICROSCOPE_THEMES: CommandId = CommandId::new("microscope_themes");
434
435 pub const MICROSCOPE_SELECT_NEXT: CommandId = CommandId::new("microscope_select_next");
437 pub const MICROSCOPE_SELECT_PREV: CommandId = CommandId::new("microscope_select_prev");
438 pub const MICROSCOPE_PAGE_DOWN: CommandId = CommandId::new("microscope_page_down");
439 pub const MICROSCOPE_PAGE_UP: CommandId = CommandId::new("microscope_page_up");
440 pub const MICROSCOPE_GOTO_FIRST: CommandId = CommandId::new("microscope_goto_first");
441 pub const MICROSCOPE_GOTO_LAST: CommandId = CommandId::new("microscope_goto_last");
442
443 pub const MICROSCOPE_CONFIRM: CommandId = CommandId::new("microscope_confirm");
445 pub const MICROSCOPE_CLOSE: CommandId = CommandId::new("microscope_close");
446 pub const MICROSCOPE_BACKSPACE: CommandId = CommandId::new("microscope_backspace");
447
448 pub const MICROSCOPE_ENTER_INSERT: CommandId = CommandId::new("microscope_enter_insert");
450 pub const MICROSCOPE_ENTER_NORMAL: CommandId = CommandId::new("microscope_enter_normal");
451
452 pub const MICROSCOPE_CURSOR_LEFT: CommandId = CommandId::new("microscope_cursor_left");
454 pub const MICROSCOPE_CURSOR_RIGHT: CommandId = CommandId::new("microscope_cursor_right");
455 pub const MICROSCOPE_CURSOR_START: CommandId = CommandId::new("microscope_cursor_start");
456 pub const MICROSCOPE_CURSOR_END: CommandId = CommandId::new("microscope_cursor_end");
457 pub const MICROSCOPE_WORD_FORWARD: CommandId = CommandId::new("microscope_word_forward");
458 pub const MICROSCOPE_WORD_BACKWARD: CommandId = CommandId::new("microscope_word_backward");
459 pub const MICROSCOPE_CLEAR_QUERY: CommandId = CommandId::new("microscope_clear_query");
460 pub const MICROSCOPE_DELETE_WORD: CommandId = CommandId::new("microscope_delete_word");
461}
462
463pub struct MicroscopePlugin;
472
473struct MicroscopeStateHandler;
475
476impl RpcHandler for MicroscopeStateHandler {
477 fn method(&self) -> &'static str {
478 "state/microscope"
479 }
480
481 fn handle(&self, _params: &serde_json::Value, ctx: &RpcHandlerContext) -> RpcResult {
482 let state = ctx
483 .with_state::<MicroscopeState, _, _>(|s| {
484 serde_json::json!({
485 "active": s.active,
486 "query": s.query,
487 "selected_index": s.selected_index,
488 "item_count": s.items.len(),
489 "picker_name": s.picker_name,
490 "title": s.title,
491 "selected_item": s.selected_item().map(|i| i.display.clone()),
492 "prompt_mode": match s.prompt_mode {
493 PromptMode::Insert => "Insert",
494 PromptMode::Normal => "Normal",
495 },
496 })
497 })
498 .unwrap_or_else(|| {
499 serde_json::json!({
500 "active": false,
501 "query": "",
502 "selected_index": 0,
503 "item_count": 0,
504 "picker_name": "",
505 "title": "",
506 "selected_item": null,
507 "prompt_mode": "Insert",
508 })
509 });
510 RpcResult::Success(state)
511 }
512
513 fn description(&self) -> &'static str {
514 "Get microscope fuzzy finder state"
515 }
516}
517
518impl Plugin for MicroscopePlugin {
519 fn id(&self) -> PluginId {
520 PluginId::new("reovim:microscope")
521 }
522
523 fn name(&self) -> &'static str {
524 "Microscope"
525 }
526
527 fn description(&self) -> &'static str {
528 "Fuzzy finder: files, buffers, grep, commands"
529 }
530
531 fn dependencies(&self) -> Vec<TypeId> {
532 vec![]
533 }
534
535 fn build(&self, ctx: &mut PluginContext) {
536 use reovim_core::highlight::{Color, Style};
538
539 let blue = Color::Rgb {
541 r: 97,
542 g: 175,
543 b: 239,
544 };
545 let fg = Color::Rgb {
546 r: 33,
547 g: 37,
548 b: 43,
549 };
550 let style = Style::new().fg(fg).bg(blue).bold();
551
552 ctx.register_display(COMPONENT_ID, DisplayInfo::new(" MICROSCOPE ", " ", style));
553
554 let _ = ctx.register_command(MicroscopeFindFiles);
556 let _ = ctx.register_command(MicroscopeFindBuffers);
557 let _ = ctx.register_command(MicroscopeLiveGrep);
558 let _ = ctx.register_command(MicroscopeFindRecent);
559 let _ = ctx.register_command(MicroscopeCommands);
560 let _ = ctx.register_command(MicroscopeHelp);
561 let _ = ctx.register_command(MicroscopeKeymaps);
562 let _ = ctx.register_command(MicroscopeThemes);
563 let _ = ctx.register_command(MicroscopeProfiles);
564
565 let _ = ctx.register_command(MicroscopeSelectNext);
567 let _ = ctx.register_command(MicroscopeSelectPrev);
568 let _ = ctx.register_command(MicroscopePageDown);
569 let _ = ctx.register_command(MicroscopePageUp);
570 let _ = ctx.register_command(MicroscopeGotoFirst);
571 let _ = ctx.register_command(MicroscopeGotoLast);
572
573 let _ = ctx.register_command(MicroscopeConfirm);
575 let _ = ctx.register_command(MicroscopeClose);
576 let _ = ctx.register_command(MicroscopeBackspace);
577
578 let _ = ctx.register_command(MicroscopeEnterInsert);
580 let _ = ctx.register_command(MicroscopeEnterNormal);
581
582 let _ = ctx.register_command(MicroscopeCursorLeft);
584 let _ = ctx.register_command(MicroscopeCursorRight);
585 let _ = ctx.register_command(MicroscopeCursorStart);
586 let _ = ctx.register_command(MicroscopeCursorEnd);
587 let _ = ctx.register_command(MicroscopeWordForward);
588 let _ = ctx.register_command(MicroscopeWordBackward);
589 let _ = ctx.register_command(MicroscopeClearQuery);
590 let _ = ctx.register_command(MicroscopeDeleteWord);
591
592 let editor_normal = KeymapScope::editor_normal();
594
595 ctx.keymap_mut()
597 .get_scope_mut(editor_normal.clone())
598 .insert(keys![Space 'f'], KeyMapInner::with_description("+find").with_category("find"));
599
600 ctx.bind_key_scoped(
601 editor_normal.clone(),
602 keys![Space 'f' 'f'],
603 CommandRef::Registered(command_id::MICROSCOPE_FIND_FILES),
604 );
605 ctx.bind_key_scoped(
606 editor_normal.clone(),
607 keys![Space 'f' 'b'],
608 CommandRef::Registered(command_id::MICROSCOPE_FIND_BUFFERS),
609 );
610 ctx.bind_key_scoped(
611 editor_normal.clone(),
612 keys![Space 'f' 'g'],
613 CommandRef::Registered(command_id::MICROSCOPE_LIVE_GREP),
614 );
615 ctx.bind_key_scoped(
616 editor_normal.clone(),
617 keys![Space 'f' 'r'],
618 CommandRef::Registered(command_id::MICROSCOPE_RECENT_FILES),
619 );
620 ctx.bind_key_scoped(
621 editor_normal.clone(),
622 keys![Space 'f' 'c'],
623 CommandRef::Registered(command_id::MICROSCOPE_COMMANDS),
624 );
625 ctx.bind_key_scoped(
626 editor_normal.clone(),
627 keys![Space 'f' 'h'],
628 CommandRef::Registered(command_id::MICROSCOPE_HELP_TAGS),
629 );
630 ctx.bind_key_scoped(
631 editor_normal,
632 keys![Space 'f' 'k'],
633 CommandRef::Registered(command_id::MICROSCOPE_KEYMAPS),
634 );
635
636 let microscope_normal = KeymapScope::Component {
638 id: COMPONENT_ID,
639 mode: EditModeKind::Normal,
640 };
641
642 ctx.bind_key_scoped(
644 microscope_normal.clone(),
645 keys!['j'],
646 CommandRef::Registered(command_id::MICROSCOPE_SELECT_NEXT),
647 );
648 ctx.bind_key_scoped(
649 microscope_normal.clone(),
650 keys!['k'],
651 CommandRef::Registered(command_id::MICROSCOPE_SELECT_PREV),
652 );
653 ctx.bind_key_scoped(
654 microscope_normal.clone(),
655 keys![(Ctrl 'n')],
656 CommandRef::Registered(command_id::MICROSCOPE_SELECT_NEXT),
657 );
658 ctx.bind_key_scoped(
659 microscope_normal.clone(),
660 keys![(Ctrl 'p')],
661 CommandRef::Registered(command_id::MICROSCOPE_SELECT_PREV),
662 );
663 ctx.bind_key_scoped(
664 microscope_normal.clone(),
665 keys![(Ctrl 'd')],
666 CommandRef::Registered(command_id::MICROSCOPE_PAGE_DOWN),
667 );
668 ctx.bind_key_scoped(
669 microscope_normal.clone(),
670 keys![(Ctrl 'u')],
671 CommandRef::Registered(command_id::MICROSCOPE_PAGE_UP),
672 );
673 ctx.bind_key_scoped(
674 microscope_normal.clone(),
675 keys!['g' 'g'],
676 CommandRef::Registered(command_id::MICROSCOPE_GOTO_FIRST),
677 );
678 ctx.bind_key_scoped(
679 microscope_normal.clone(),
680 keys!['G'],
681 CommandRef::Registered(command_id::MICROSCOPE_GOTO_LAST),
682 );
683
684 ctx.bind_key_scoped(
686 microscope_normal.clone(),
687 keys![Escape],
688 CommandRef::Registered(command_id::MICROSCOPE_CLOSE),
689 );
690 ctx.bind_key_scoped(
691 microscope_normal.clone(),
692 keys!['q'],
693 CommandRef::Registered(command_id::MICROSCOPE_CLOSE),
694 );
695 ctx.bind_key_scoped(
696 microscope_normal.clone(),
697 keys![Enter],
698 CommandRef::Registered(command_id::MICROSCOPE_CONFIRM),
699 );
700
701 ctx.bind_key_scoped(
703 microscope_normal.clone(),
704 keys!['i'],
705 CommandRef::Registered(command_id::MICROSCOPE_ENTER_INSERT),
706 );
707 ctx.bind_key_scoped(
708 microscope_normal.clone(),
709 keys!['a'],
710 CommandRef::Registered(command_id::MICROSCOPE_ENTER_INSERT),
711 );
712
713 ctx.bind_key_scoped(
715 microscope_normal.clone(),
716 keys!['h'],
717 CommandRef::Registered(command_id::MICROSCOPE_CURSOR_LEFT),
718 );
719 ctx.bind_key_scoped(
720 microscope_normal.clone(),
721 keys!['l'],
722 CommandRef::Registered(command_id::MICROSCOPE_CURSOR_RIGHT),
723 );
724 ctx.bind_key_scoped(
725 microscope_normal.clone(),
726 keys!['0'],
727 CommandRef::Registered(command_id::MICROSCOPE_CURSOR_START),
728 );
729 ctx.bind_key_scoped(
730 microscope_normal.clone(),
731 keys!['$'],
732 CommandRef::Registered(command_id::MICROSCOPE_CURSOR_END),
733 );
734 ctx.bind_key_scoped(
735 microscope_normal.clone(),
736 keys!['w'],
737 CommandRef::Registered(command_id::MICROSCOPE_WORD_FORWARD),
738 );
739 ctx.bind_key_scoped(
740 microscope_normal.clone(),
741 keys!['b'],
742 CommandRef::Registered(command_id::MICROSCOPE_WORD_BACKWARD),
743 );
744
745 ctx.keymap_mut()
747 .get_scope_mut(microscope_normal)
748 .insert(keys!['g'], KeyMapInner::new());
749
750 let microscope_insert = KeymapScope::Component {
752 id: COMPONENT_ID,
753 mode: EditModeKind::Insert,
754 };
755
756 ctx.bind_key_scoped(
758 microscope_insert.clone(),
759 keys![(Ctrl 'n')],
760 CommandRef::Registered(command_id::MICROSCOPE_SELECT_NEXT),
761 );
762 ctx.bind_key_scoped(
763 microscope_insert.clone(),
764 keys![(Ctrl 'p')],
765 CommandRef::Registered(command_id::MICROSCOPE_SELECT_PREV),
766 );
767
768 ctx.bind_key_scoped(
770 microscope_insert.clone(),
771 keys![Backspace],
772 CommandRef::Registered(command_id::MICROSCOPE_BACKSPACE),
773 );
774 ctx.bind_key_scoped(
775 microscope_insert.clone(),
776 keys![(Ctrl 'u')],
777 CommandRef::Registered(command_id::MICROSCOPE_CLEAR_QUERY),
778 );
779 ctx.bind_key_scoped(
780 microscope_insert.clone(),
781 keys![(Ctrl 'w')],
782 CommandRef::Registered(command_id::MICROSCOPE_DELETE_WORD),
783 );
784
785 ctx.bind_key_scoped(
787 microscope_insert.clone(),
788 keys![Escape],
789 CommandRef::Registered(command_id::MICROSCOPE_ENTER_NORMAL),
790 );
791 ctx.bind_key_scoped(
792 microscope_insert,
793 keys![Enter],
794 CommandRef::Registered(command_id::MICROSCOPE_CONFIRM),
795 );
796
797 let microscope_interactor = KeymapScope::SubMode(SubModeKind::Interactor(COMPONENT_ID));
800
801 ctx.bind_key_scoped(
803 microscope_interactor.clone(),
804 keys![(Ctrl 'n')],
805 CommandRef::Registered(command_id::MICROSCOPE_SELECT_NEXT),
806 );
807 ctx.bind_key_scoped(
808 microscope_interactor.clone(),
809 keys![(Ctrl 'p')],
810 CommandRef::Registered(command_id::MICROSCOPE_SELECT_PREV),
811 );
812
813 ctx.bind_key_scoped(
815 microscope_interactor.clone(),
816 keys![Backspace],
817 CommandRef::Registered(command_id::MICROSCOPE_BACKSPACE),
818 );
819 ctx.bind_key_scoped(
820 microscope_interactor.clone(),
821 keys![(Ctrl 'u')],
822 CommandRef::Registered(command_id::MICROSCOPE_CLEAR_QUERY),
823 );
824 ctx.bind_key_scoped(
825 microscope_interactor.clone(),
826 keys![(Ctrl 'w')],
827 CommandRef::Registered(command_id::MICROSCOPE_DELETE_WORD),
828 );
829
830 ctx.bind_key_scoped(
832 microscope_interactor.clone(),
833 keys![Escape],
834 CommandRef::Registered(command_id::MICROSCOPE_ENTER_NORMAL),
835 );
836 ctx.bind_key_scoped(
837 microscope_interactor,
838 keys![Enter],
839 CommandRef::Registered(command_id::MICROSCOPE_CONFIRM),
840 );
841
842 ctx.register_rpc_handler(Arc::new(MicroscopeStateHandler));
844 }
845
846 fn init_state(&self, registry: &PluginStateRegistry) {
847 registry.register(MicroscopeState::new());
849
850 registry.register(PickerRegistry::new());
852
853 registry.register_plugin_window(Arc::new(MicroscopePluginWindow));
855 }
856
857 fn subscribe(&self, bus: &reovim_core::event_bus::EventBus, state: Arc<PluginStateRegistry>) {
858 use reovim_core::{
859 event_bus::{
860 EventResult,
861 core_events::{
862 PluginBackspace, PluginTextInput, RequestFocusChange, RequestModeChange,
863 },
864 },
865 modd::{EditMode, ModeState},
866 };
867
868 fn load_preview(state: &Arc<PluginStateRegistry>, picker_name: &str) {
870 let picker = state
871 .with::<PickerRegistry, _, _>(|r| r.get(picker_name))
872 .flatten();
873
874 if let Some(picker) = picker {
875 let selected_item = state
876 .with::<MicroscopeState, _, _>(|s| s.selected_item().cloned())
877 .flatten();
878
879 if let Some(item) = selected_item {
880 let picker_ctx = PickerContext {
882 syntax_factory: state.syntax_factory(),
883 decoration_factory: state.decoration_factory(),
884 ..PickerContext::default()
885 };
886
887 let preview = tokio::task::block_in_place(|| {
888 tokio::runtime::Handle::current()
889 .block_on(picker.preview(&item, &picker_ctx))
890 });
891 state.with_mut::<MicroscopeState, _, _>(|s| {
892 s.set_preview(preview);
893 });
894 }
895 }
896 }
897
898 let state_clone = Arc::clone(&state);
900 bus.subscribe::<commands::MicroscopeOpen, _>(100, move |event, ctx| {
901 let picker_name = event.picker.clone();
902
903 let picker = state_clone
905 .with::<PickerRegistry, _, _>(|registry| registry.get(&picker_name))
906 .flatten();
907
908 if let Some(picker) = picker {
909 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
911 s.open(&picker_name, picker.title(), picker.prompt());
912 });
913
914 let picker_ctx = PickerContext::default();
917 let items = tokio::task::block_in_place(|| {
918 tokio::runtime::Handle::current().block_on(picker.fetch(&picker_ctx))
919 });
920
921 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
923 s.update_items(items);
924 s.set_loading(microscope::LoadingState::Idle);
925 });
926
927 load_preview(&state_clone, &picker_name);
929
930 ctx.emit(RequestFocusChange {
932 target: COMPONENT_ID,
933 });
934
935 let mode = ModeState::with_interactor_id_and_mode(COMPONENT_ID, EditMode::Normal);
937 ctx.emit(RequestModeChange { mode });
938
939 ctx.request_render();
940 } else {
941 }
943
944 EventResult::Handled
945 });
946
947 let state_clone = Arc::clone(&state);
949 bus.subscribe_targeted::<PluginTextInput, _>(COMPONENT_ID, 100, move |event, ctx| {
950 let is_active = state_clone
951 .with::<MicroscopeState, _, _>(|s| s.active)
952 .unwrap_or(false);
953
954 if is_active {
955 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
956 s.insert_char(event.c);
957 });
958 ctx.request_render();
959 EventResult::Handled
960 } else {
961 EventResult::NotHandled
962 }
963 });
964
965 let state_clone = Arc::clone(&state);
967 bus.subscribe_targeted::<PluginBackspace, _>(COMPONENT_ID, 100, move |_event, ctx| {
968 let is_active = state_clone
969 .with::<MicroscopeState, _, _>(|s| s.active)
970 .unwrap_or(false);
971
972 if is_active {
973 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
974 s.delete_char();
975 });
976 ctx.request_render();
977 EventResult::Handled
978 } else {
979 EventResult::NotHandled
980 }
981 });
982
983 let state_clone = Arc::clone(&state);
985 bus.subscribe::<commands::MicroscopeClose, _>(100, move |_event, ctx| {
986 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
987 s.close();
988 });
989
990 ctx.emit(RequestFocusChange {
992 target: reovim_core::modd::ComponentId("editor"),
993 });
994 let mode = ModeState::with_interactor_id_and_mode(
995 reovim_core::modd::ComponentId::EDITOR,
996 EditMode::Normal,
997 );
998 ctx.emit(RequestModeChange { mode });
999
1000 ctx.request_render();
1001 EventResult::Handled
1002 });
1003
1004 let state_clone = Arc::clone(&state);
1006 bus.subscribe::<commands::MicroscopeSelectNext, _>(100, move |_event, ctx| {
1007 let picker_name = state_clone
1008 .with::<MicroscopeState, _, _>(|s| s.picker_name.clone())
1009 .unwrap_or_default();
1010
1011 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1012 s.select_next();
1013 });
1014
1015 load_preview(&state_clone, &picker_name);
1016 ctx.request_render();
1017 EventResult::Handled
1018 });
1019
1020 let state_clone = Arc::clone(&state);
1022 bus.subscribe::<commands::MicroscopeSelectPrev, _>(100, move |_event, ctx| {
1023 let picker_name = state_clone
1024 .with::<MicroscopeState, _, _>(|s| s.picker_name.clone())
1025 .unwrap_or_default();
1026
1027 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1028 s.select_prev();
1029 });
1030
1031 load_preview(&state_clone, &picker_name);
1032 ctx.request_render();
1033 EventResult::Handled
1034 });
1035
1036 let state_clone = Arc::clone(&state);
1038 bus.subscribe::<commands::MicroscopeBackspace, _>(100, move |_event, ctx| {
1039 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1040 s.delete_char();
1041 });
1042 ctx.request_render();
1043 EventResult::Handled
1044 });
1045
1046 let state_clone = Arc::clone(&state);
1048 bus.subscribe::<commands::MicroscopeConfirm, _>(100, move |_event, ctx| {
1049 let selected = state_clone
1050 .with::<MicroscopeState, _, _>(|s| s.selected_item().cloned())
1051 .flatten();
1052
1053 if let Some(item) = selected {
1054 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1056 s.close();
1057 });
1058
1059 ctx.emit(RequestFocusChange {
1061 target: reovim_core::modd::ComponentId::EDITOR,
1062 });
1063 let mode = ModeState::with_interactor_id_and_mode(
1064 reovim_core::modd::ComponentId::EDITOR,
1065 EditMode::Normal,
1066 );
1067 ctx.emit(RequestModeChange { mode });
1068
1069 ctx.emit(reovim_core::event_bus::core_events::RequestOpenFile {
1071 path: std::path::PathBuf::from(&item.id),
1072 });
1073
1074 ctx.request_render();
1075 }
1076 EventResult::Handled
1077 });
1078
1079 let state_clone = Arc::clone(&state);
1081 bus.subscribe::<commands::MicroscopeClearQuery, _>(100, move |_event, ctx| {
1082 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1083 s.clear_query();
1084 });
1085 ctx.request_render();
1086 EventResult::Handled
1087 });
1088
1089 let state_clone = Arc::clone(&state);
1091 bus.subscribe::<commands::MicroscopeDeleteWord, _>(100, move |_event, ctx| {
1092 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1093 s.delete_word();
1094 });
1095 ctx.request_render();
1096 EventResult::Handled
1097 });
1098
1099 let state_clone = Arc::clone(&state);
1101 bus.subscribe::<commands::MicroscopeCursorLeft, _>(100, move |_event, ctx| {
1102 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1103 s.cursor_left();
1104 });
1105 ctx.request_render();
1106 EventResult::Handled
1107 });
1108
1109 let state_clone = Arc::clone(&state);
1111 bus.subscribe::<commands::MicroscopeCursorRight, _>(100, move |_event, ctx| {
1112 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1113 s.cursor_right();
1114 });
1115 ctx.request_render();
1116 EventResult::Handled
1117 });
1118
1119 let state_clone = Arc::clone(&state);
1121 bus.subscribe::<commands::MicroscopeCursorStart, _>(100, move |_event, ctx| {
1122 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1123 s.cursor_home();
1124 });
1125 ctx.request_render();
1126 EventResult::Handled
1127 });
1128
1129 let state_clone = Arc::clone(&state);
1131 bus.subscribe::<commands::MicroscopeCursorEnd, _>(100, move |_event, ctx| {
1132 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1133 s.cursor_end();
1134 });
1135 ctx.request_render();
1136 EventResult::Handled
1137 });
1138
1139 let state_clone = Arc::clone(&state);
1141 bus.subscribe::<commands::MicroscopeWordForward, _>(100, move |_event, ctx| {
1142 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1143 s.word_forward();
1144 });
1145 ctx.request_render();
1146 EventResult::Handled
1147 });
1148
1149 let state_clone = Arc::clone(&state);
1151 bus.subscribe::<commands::MicroscopeWordBackward, _>(100, move |_event, ctx| {
1152 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1153 s.word_backward();
1154 });
1155 ctx.request_render();
1156 EventResult::Handled
1157 });
1158
1159 let state_clone = Arc::clone(&state);
1161 bus.subscribe::<commands::MicroscopePageDown, _>(100, move |_event, ctx| {
1162 let picker_name = state_clone
1163 .with::<MicroscopeState, _, _>(|s| s.picker_name.clone())
1164 .unwrap_or_default();
1165
1166 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1167 s.page_down();
1168 });
1169
1170 load_preview(&state_clone, &picker_name);
1171 ctx.request_render();
1172 EventResult::Handled
1173 });
1174
1175 let state_clone = Arc::clone(&state);
1177 bus.subscribe::<commands::MicroscopePageUp, _>(100, move |_event, ctx| {
1178 let picker_name = state_clone
1179 .with::<MicroscopeState, _, _>(|s| s.picker_name.clone())
1180 .unwrap_or_default();
1181
1182 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1183 s.page_up();
1184 });
1185
1186 load_preview(&state_clone, &picker_name);
1187 ctx.request_render();
1188 EventResult::Handled
1189 });
1190
1191 let state_clone = Arc::clone(&state);
1193 bus.subscribe::<commands::MicroscopeGotoFirst, _>(100, move |_event, ctx| {
1194 let picker_name = state_clone
1195 .with::<MicroscopeState, _, _>(|s| s.picker_name.clone())
1196 .unwrap_or_default();
1197
1198 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1199 s.move_to_first();
1200 });
1201
1202 load_preview(&state_clone, &picker_name);
1203 ctx.request_render();
1204 EventResult::Handled
1205 });
1206
1207 let state_clone = Arc::clone(&state);
1209 bus.subscribe::<commands::MicroscopeGotoLast, _>(100, move |_event, ctx| {
1210 let picker_name = state_clone
1211 .with::<MicroscopeState, _, _>(|s| s.picker_name.clone())
1212 .unwrap_or_default();
1213
1214 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1215 s.move_to_last();
1216 });
1217
1218 load_preview(&state_clone, &picker_name);
1219 ctx.request_render();
1220 EventResult::Handled
1221 });
1222
1223 let state_clone = Arc::clone(&state);
1225 bus.subscribe::<commands::MicroscopeEnterInsert, _>(100, move |_event, ctx| {
1226 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1227 s.enter_insert();
1228 });
1229
1230 ctx.enter_interactor_mode(COMPONENT_ID);
1232
1233 ctx.request_render();
1234 EventResult::Handled
1235 });
1236
1237 let state_clone = Arc::clone(&state);
1239 bus.subscribe::<commands::MicroscopeEnterNormal, _>(100, move |_event, ctx| {
1240 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1241 s.enter_normal();
1242 });
1243
1244 let mode = ModeState::with_interactor_id_and_mode(COMPONENT_ID, EditMode::Normal);
1246 ctx.emit(RequestModeChange { mode });
1247
1248 ctx.request_render();
1249 EventResult::Handled
1250 });
1251 }
1252}