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, EditModeKey},
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 ctx.register_display(COMPONENT_ID, DisplayInfo::new(" MICROSCOPE ", " "));
538 ctx.register_component_mode_display(
539 COMPONENT_ID,
540 EditModeKey::Normal,
541 DisplayInfo::new(" MICROSCOPE ", " "),
542 );
543 ctx.register_component_mode_display(
544 COMPONENT_ID,
545 EditModeKey::Insert,
546 DisplayInfo::new(" MICROSCOPE | INSERT ", " "),
547 );
548
549 let _ = ctx.register_command(MicroscopeFindFiles);
551 let _ = ctx.register_command(MicroscopeFindBuffers);
552 let _ = ctx.register_command(MicroscopeLiveGrep);
553 let _ = ctx.register_command(MicroscopeFindRecent);
554 let _ = ctx.register_command(MicroscopeCommands);
555 let _ = ctx.register_command(MicroscopeHelp);
556 let _ = ctx.register_command(MicroscopeKeymaps);
557 let _ = ctx.register_command(MicroscopeThemes);
558 let _ = ctx.register_command(MicroscopeProfiles);
559
560 let _ = ctx.register_command(MicroscopeSelectNext);
562 let _ = ctx.register_command(MicroscopeSelectPrev);
563 let _ = ctx.register_command(MicroscopePageDown);
564 let _ = ctx.register_command(MicroscopePageUp);
565 let _ = ctx.register_command(MicroscopeGotoFirst);
566 let _ = ctx.register_command(MicroscopeGotoLast);
567
568 let _ = ctx.register_command(MicroscopeConfirm);
570 let _ = ctx.register_command(MicroscopeClose);
571 let _ = ctx.register_command(MicroscopeBackspace);
572
573 let _ = ctx.register_command(MicroscopeEnterInsert);
575 let _ = ctx.register_command(MicroscopeEnterNormal);
576
577 let _ = ctx.register_command(MicroscopeCursorLeft);
579 let _ = ctx.register_command(MicroscopeCursorRight);
580 let _ = ctx.register_command(MicroscopeCursorStart);
581 let _ = ctx.register_command(MicroscopeCursorEnd);
582 let _ = ctx.register_command(MicroscopeWordForward);
583 let _ = ctx.register_command(MicroscopeWordBackward);
584 let _ = ctx.register_command(MicroscopeClearQuery);
585 let _ = ctx.register_command(MicroscopeDeleteWord);
586
587 let editor_normal = KeymapScope::editor_normal();
589
590 ctx.keymap_mut()
592 .get_scope_mut(editor_normal.clone())
593 .insert(keys![Space 'f'], KeyMapInner::with_description("+find").with_category("find"));
594
595 ctx.bind_key_scoped(
596 editor_normal.clone(),
597 keys![Space 'f' 'f'],
598 CommandRef::Registered(command_id::MICROSCOPE_FIND_FILES),
599 );
600 ctx.bind_key_scoped(
601 editor_normal.clone(),
602 keys![Space 'f' 'b'],
603 CommandRef::Registered(command_id::MICROSCOPE_FIND_BUFFERS),
604 );
605 ctx.bind_key_scoped(
606 editor_normal.clone(),
607 keys![Space 'f' 'g'],
608 CommandRef::Registered(command_id::MICROSCOPE_LIVE_GREP),
609 );
610 ctx.bind_key_scoped(
611 editor_normal.clone(),
612 keys![Space 'f' 'r'],
613 CommandRef::Registered(command_id::MICROSCOPE_RECENT_FILES),
614 );
615 ctx.bind_key_scoped(
616 editor_normal.clone(),
617 keys![Space 'f' 'c'],
618 CommandRef::Registered(command_id::MICROSCOPE_COMMANDS),
619 );
620 ctx.bind_key_scoped(
621 editor_normal.clone(),
622 keys![Space 'f' 'h'],
623 CommandRef::Registered(command_id::MICROSCOPE_HELP_TAGS),
624 );
625 ctx.bind_key_scoped(
626 editor_normal,
627 keys![Space 'f' 'k'],
628 CommandRef::Registered(command_id::MICROSCOPE_KEYMAPS),
629 );
630
631 let microscope_normal = KeymapScope::Component {
633 id: COMPONENT_ID,
634 mode: EditModeKind::Normal,
635 };
636
637 ctx.bind_key_scoped(
639 microscope_normal.clone(),
640 keys!['j'],
641 CommandRef::Registered(command_id::MICROSCOPE_SELECT_NEXT),
642 );
643 ctx.bind_key_scoped(
644 microscope_normal.clone(),
645 keys!['k'],
646 CommandRef::Registered(command_id::MICROSCOPE_SELECT_PREV),
647 );
648 ctx.bind_key_scoped(
649 microscope_normal.clone(),
650 keys![(Ctrl 'n')],
651 CommandRef::Registered(command_id::MICROSCOPE_SELECT_NEXT),
652 );
653 ctx.bind_key_scoped(
654 microscope_normal.clone(),
655 keys![(Ctrl 'p')],
656 CommandRef::Registered(command_id::MICROSCOPE_SELECT_PREV),
657 );
658 ctx.bind_key_scoped(
659 microscope_normal.clone(),
660 keys![(Ctrl 'd')],
661 CommandRef::Registered(command_id::MICROSCOPE_PAGE_DOWN),
662 );
663 ctx.bind_key_scoped(
664 microscope_normal.clone(),
665 keys![(Ctrl 'u')],
666 CommandRef::Registered(command_id::MICROSCOPE_PAGE_UP),
667 );
668 ctx.bind_key_scoped(
669 microscope_normal.clone(),
670 keys!['g' 'g'],
671 CommandRef::Registered(command_id::MICROSCOPE_GOTO_FIRST),
672 );
673 ctx.bind_key_scoped(
674 microscope_normal.clone(),
675 keys!['G'],
676 CommandRef::Registered(command_id::MICROSCOPE_GOTO_LAST),
677 );
678
679 ctx.bind_key_scoped(
681 microscope_normal.clone(),
682 keys![Escape],
683 CommandRef::Registered(command_id::MICROSCOPE_CLOSE),
684 );
685 ctx.bind_key_scoped(
686 microscope_normal.clone(),
687 keys!['q'],
688 CommandRef::Registered(command_id::MICROSCOPE_CLOSE),
689 );
690 ctx.bind_key_scoped(
691 microscope_normal.clone(),
692 keys![Enter],
693 CommandRef::Registered(command_id::MICROSCOPE_CONFIRM),
694 );
695
696 ctx.bind_key_scoped(
698 microscope_normal.clone(),
699 keys!['i'],
700 CommandRef::Registered(command_id::MICROSCOPE_ENTER_INSERT),
701 );
702 ctx.bind_key_scoped(
703 microscope_normal.clone(),
704 keys!['a'],
705 CommandRef::Registered(command_id::MICROSCOPE_ENTER_INSERT),
706 );
707
708 ctx.bind_key_scoped(
710 microscope_normal.clone(),
711 keys!['h'],
712 CommandRef::Registered(command_id::MICROSCOPE_CURSOR_LEFT),
713 );
714 ctx.bind_key_scoped(
715 microscope_normal.clone(),
716 keys!['l'],
717 CommandRef::Registered(command_id::MICROSCOPE_CURSOR_RIGHT),
718 );
719 ctx.bind_key_scoped(
720 microscope_normal.clone(),
721 keys!['0'],
722 CommandRef::Registered(command_id::MICROSCOPE_CURSOR_START),
723 );
724 ctx.bind_key_scoped(
725 microscope_normal.clone(),
726 keys!['$'],
727 CommandRef::Registered(command_id::MICROSCOPE_CURSOR_END),
728 );
729 ctx.bind_key_scoped(
730 microscope_normal.clone(),
731 keys!['w'],
732 CommandRef::Registered(command_id::MICROSCOPE_WORD_FORWARD),
733 );
734 ctx.bind_key_scoped(
735 microscope_normal.clone(),
736 keys!['b'],
737 CommandRef::Registered(command_id::MICROSCOPE_WORD_BACKWARD),
738 );
739
740 ctx.keymap_mut()
742 .get_scope_mut(microscope_normal)
743 .insert(keys!['g'], KeyMapInner::new());
744
745 let microscope_insert = KeymapScope::Component {
747 id: COMPONENT_ID,
748 mode: EditModeKind::Insert,
749 };
750
751 ctx.bind_key_scoped(
753 microscope_insert.clone(),
754 keys![(Ctrl 'n')],
755 CommandRef::Registered(command_id::MICROSCOPE_SELECT_NEXT),
756 );
757 ctx.bind_key_scoped(
758 microscope_insert.clone(),
759 keys![(Ctrl 'p')],
760 CommandRef::Registered(command_id::MICROSCOPE_SELECT_PREV),
761 );
762
763 ctx.bind_key_scoped(
765 microscope_insert.clone(),
766 keys![Backspace],
767 CommandRef::Registered(command_id::MICROSCOPE_BACKSPACE),
768 );
769 ctx.bind_key_scoped(
770 microscope_insert.clone(),
771 keys![(Ctrl 'u')],
772 CommandRef::Registered(command_id::MICROSCOPE_CLEAR_QUERY),
773 );
774 ctx.bind_key_scoped(
775 microscope_insert.clone(),
776 keys![(Ctrl 'w')],
777 CommandRef::Registered(command_id::MICROSCOPE_DELETE_WORD),
778 );
779
780 ctx.bind_key_scoped(
782 microscope_insert.clone(),
783 keys![Escape],
784 CommandRef::Registered(command_id::MICROSCOPE_ENTER_NORMAL),
785 );
786 ctx.bind_key_scoped(
787 microscope_insert,
788 keys![Enter],
789 CommandRef::Registered(command_id::MICROSCOPE_CONFIRM),
790 );
791
792 let microscope_interactor = KeymapScope::SubMode(SubModeKind::Interactor(COMPONENT_ID));
795
796 ctx.bind_key_scoped(
798 microscope_interactor.clone(),
799 keys![(Ctrl 'n')],
800 CommandRef::Registered(command_id::MICROSCOPE_SELECT_NEXT),
801 );
802 ctx.bind_key_scoped(
803 microscope_interactor.clone(),
804 keys![(Ctrl 'p')],
805 CommandRef::Registered(command_id::MICROSCOPE_SELECT_PREV),
806 );
807
808 ctx.bind_key_scoped(
810 microscope_interactor.clone(),
811 keys![Backspace],
812 CommandRef::Registered(command_id::MICROSCOPE_BACKSPACE),
813 );
814 ctx.bind_key_scoped(
815 microscope_interactor.clone(),
816 keys![(Ctrl 'u')],
817 CommandRef::Registered(command_id::MICROSCOPE_CLEAR_QUERY),
818 );
819 ctx.bind_key_scoped(
820 microscope_interactor.clone(),
821 keys![(Ctrl 'w')],
822 CommandRef::Registered(command_id::MICROSCOPE_DELETE_WORD),
823 );
824
825 ctx.bind_key_scoped(
827 microscope_interactor.clone(),
828 keys![Escape],
829 CommandRef::Registered(command_id::MICROSCOPE_ENTER_NORMAL),
830 );
831 ctx.bind_key_scoped(
832 microscope_interactor,
833 keys![Enter],
834 CommandRef::Registered(command_id::MICROSCOPE_CONFIRM),
835 );
836
837 ctx.register_rpc_handler(Arc::new(MicroscopeStateHandler));
839 }
840
841 fn init_state(&self, registry: &PluginStateRegistry) {
842 registry.register(MicroscopeState::new());
844
845 registry.register(PickerRegistry::new());
847
848 registry.register_plugin_window(Arc::new(MicroscopePluginWindow));
850 }
851
852 fn subscribe(&self, bus: &reovim_core::event_bus::EventBus, state: Arc<PluginStateRegistry>) {
853 use reovim_core::{
854 event_bus::{
855 EventResult,
856 core_events::{
857 PluginBackspace, PluginTextInput, RequestFocusChange, RequestModeChange,
858 },
859 },
860 modd::{EditMode, ModeState, SubMode},
861 };
862
863 fn load_preview(state: &Arc<PluginStateRegistry>, picker_name: &str) {
865 let picker = state
866 .with::<PickerRegistry, _, _>(|r| r.get(picker_name))
867 .flatten();
868
869 if let Some(picker) = picker {
870 let selected_item = state
871 .with::<MicroscopeState, _, _>(|s| s.selected_item().cloned())
872 .flatten();
873
874 if let Some(item) = selected_item {
875 let preview = tokio::task::block_in_place(|| {
876 tokio::runtime::Handle::current().block_on(picker.preview(&item))
877 });
878 state.with_mut::<MicroscopeState, _, _>(|s| {
879 s.set_preview(preview);
880 });
881 }
882 }
883 }
884
885 let state_clone = Arc::clone(&state);
887 bus.subscribe::<commands::MicroscopeOpen, _>(100, move |event, ctx| {
888 let picker_name = event.picker.clone();
889
890 let picker = state_clone
892 .with::<PickerRegistry, _, _>(|registry| registry.get(&picker_name))
893 .flatten();
894
895 if let Some(picker) = picker {
896 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
898 s.open(&picker_name, picker.title(), picker.prompt());
899 });
900
901 let picker_ctx = PickerContext::default();
904 let items = tokio::task::block_in_place(|| {
905 tokio::runtime::Handle::current().block_on(picker.fetch(&picker_ctx))
906 });
907
908 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
910 s.update_items(items);
911 s.set_loading(microscope::LoadingState::Idle);
912 });
913
914 load_preview(&state_clone, &picker_name);
916
917 ctx.emit(RequestFocusChange {
919 target: COMPONENT_ID,
920 });
921
922 let mode = ModeState::with_interactor_id_and_mode(COMPONENT_ID, EditMode::Normal);
924 ctx.emit(RequestModeChange { mode });
925
926 ctx.request_render();
927 } else {
928 }
930
931 EventResult::Handled
932 });
933
934 let state_clone = Arc::clone(&state);
936 bus.subscribe::<PluginTextInput, _>(100, move |event, ctx| {
937 if event.target != COMPONENT_ID {
939 return EventResult::NotHandled;
940 }
941
942 let is_active = state_clone
943 .with::<MicroscopeState, _, _>(|s| s.active)
944 .unwrap_or(false);
945
946 if is_active {
947 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
948 s.insert_char(event.c);
949 });
950 ctx.request_render();
951 EventResult::Handled
952 } else {
953 EventResult::NotHandled
954 }
955 });
956
957 let state_clone = Arc::clone(&state);
959 bus.subscribe::<PluginBackspace, _>(100, move |event, ctx| {
960 if event.target != COMPONENT_ID {
962 return EventResult::NotHandled;
963 }
964
965 let is_active = state_clone
966 .with::<MicroscopeState, _, _>(|s| s.active)
967 .unwrap_or(false);
968
969 if is_active {
970 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
971 s.delete_char();
972 });
973 ctx.request_render();
974 EventResult::Handled
975 } else {
976 EventResult::NotHandled
977 }
978 });
979
980 let state_clone = Arc::clone(&state);
982 bus.subscribe::<commands::MicroscopeClose, _>(100, move |_event, ctx| {
983 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
984 s.close();
985 });
986
987 ctx.emit(RequestFocusChange {
989 target: reovim_core::modd::ComponentId("editor"),
990 });
991 let mode = ModeState::with_interactor_id_and_mode(
992 reovim_core::modd::ComponentId::EDITOR,
993 EditMode::Normal,
994 );
995 ctx.emit(RequestModeChange { mode });
996
997 ctx.request_render();
998 EventResult::Handled
999 });
1000
1001 let state_clone = Arc::clone(&state);
1003 bus.subscribe::<commands::MicroscopeSelectNext, _>(100, move |_event, ctx| {
1004 let picker_name = state_clone
1005 .with::<MicroscopeState, _, _>(|s| s.picker_name.clone())
1006 .unwrap_or_default();
1007
1008 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1009 s.select_next();
1010 });
1011
1012 load_preview(&state_clone, &picker_name);
1013 ctx.request_render();
1014 EventResult::Handled
1015 });
1016
1017 let state_clone = Arc::clone(&state);
1019 bus.subscribe::<commands::MicroscopeSelectPrev, _>(100, move |_event, ctx| {
1020 let picker_name = state_clone
1021 .with::<MicroscopeState, _, _>(|s| s.picker_name.clone())
1022 .unwrap_or_default();
1023
1024 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1025 s.select_prev();
1026 });
1027
1028 load_preview(&state_clone, &picker_name);
1029 ctx.request_render();
1030 EventResult::Handled
1031 });
1032
1033 let state_clone = Arc::clone(&state);
1035 bus.subscribe::<commands::MicroscopeBackspace, _>(100, move |_event, ctx| {
1036 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1037 s.delete_char();
1038 });
1039 ctx.request_render();
1040 EventResult::Handled
1041 });
1042
1043 let state_clone = Arc::clone(&state);
1045 bus.subscribe::<commands::MicroscopeConfirm, _>(100, move |_event, ctx| {
1046 let selected = state_clone
1047 .with::<MicroscopeState, _, _>(|s| s.selected_item().cloned())
1048 .flatten();
1049
1050 if let Some(item) = selected {
1051 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1053 s.close();
1054 });
1055
1056 ctx.emit(RequestFocusChange {
1058 target: reovim_core::modd::ComponentId::EDITOR,
1059 });
1060 let mode = ModeState::with_interactor_id_and_mode(
1061 reovim_core::modd::ComponentId::EDITOR,
1062 EditMode::Normal,
1063 );
1064 ctx.emit(RequestModeChange { mode });
1065
1066 ctx.emit(reovim_core::event_bus::core_events::RequestOpenFile {
1068 path: std::path::PathBuf::from(&item.id),
1069 });
1070
1071 ctx.request_render();
1072 }
1073 EventResult::Handled
1074 });
1075
1076 let state_clone = Arc::clone(&state);
1078 bus.subscribe::<commands::MicroscopeClearQuery, _>(100, move |_event, ctx| {
1079 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1080 s.clear_query();
1081 });
1082 ctx.request_render();
1083 EventResult::Handled
1084 });
1085
1086 let state_clone = Arc::clone(&state);
1088 bus.subscribe::<commands::MicroscopeDeleteWord, _>(100, move |_event, ctx| {
1089 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1090 s.delete_word();
1091 });
1092 ctx.request_render();
1093 EventResult::Handled
1094 });
1095
1096 let state_clone = Arc::clone(&state);
1098 bus.subscribe::<commands::MicroscopeCursorLeft, _>(100, move |_event, ctx| {
1099 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1100 s.cursor_left();
1101 });
1102 ctx.request_render();
1103 EventResult::Handled
1104 });
1105
1106 let state_clone = Arc::clone(&state);
1108 bus.subscribe::<commands::MicroscopeCursorRight, _>(100, move |_event, ctx| {
1109 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1110 s.cursor_right();
1111 });
1112 ctx.request_render();
1113 EventResult::Handled
1114 });
1115
1116 let state_clone = Arc::clone(&state);
1118 bus.subscribe::<commands::MicroscopeCursorStart, _>(100, move |_event, ctx| {
1119 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1120 s.cursor_home();
1121 });
1122 ctx.request_render();
1123 EventResult::Handled
1124 });
1125
1126 let state_clone = Arc::clone(&state);
1128 bus.subscribe::<commands::MicroscopeCursorEnd, _>(100, move |_event, ctx| {
1129 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1130 s.cursor_end();
1131 });
1132 ctx.request_render();
1133 EventResult::Handled
1134 });
1135
1136 let state_clone = Arc::clone(&state);
1138 bus.subscribe::<commands::MicroscopeWordForward, _>(100, move |_event, ctx| {
1139 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1140 s.word_forward();
1141 });
1142 ctx.request_render();
1143 EventResult::Handled
1144 });
1145
1146 let state_clone = Arc::clone(&state);
1148 bus.subscribe::<commands::MicroscopeWordBackward, _>(100, move |_event, ctx| {
1149 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1150 s.word_backward();
1151 });
1152 ctx.request_render();
1153 EventResult::Handled
1154 });
1155
1156 let state_clone = Arc::clone(&state);
1158 bus.subscribe::<commands::MicroscopePageDown, _>(100, move |_event, ctx| {
1159 let picker_name = state_clone
1160 .with::<MicroscopeState, _, _>(|s| s.picker_name.clone())
1161 .unwrap_or_default();
1162
1163 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1164 s.page_down();
1165 });
1166
1167 load_preview(&state_clone, &picker_name);
1168 ctx.request_render();
1169 EventResult::Handled
1170 });
1171
1172 let state_clone = Arc::clone(&state);
1174 bus.subscribe::<commands::MicroscopePageUp, _>(100, move |_event, ctx| {
1175 let picker_name = state_clone
1176 .with::<MicroscopeState, _, _>(|s| s.picker_name.clone())
1177 .unwrap_or_default();
1178
1179 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1180 s.page_up();
1181 });
1182
1183 load_preview(&state_clone, &picker_name);
1184 ctx.request_render();
1185 EventResult::Handled
1186 });
1187
1188 let state_clone = Arc::clone(&state);
1190 bus.subscribe::<commands::MicroscopeGotoFirst, _>(100, move |_event, ctx| {
1191 let picker_name = state_clone
1192 .with::<MicroscopeState, _, _>(|s| s.picker_name.clone())
1193 .unwrap_or_default();
1194
1195 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1196 s.move_to_first();
1197 });
1198
1199 load_preview(&state_clone, &picker_name);
1200 ctx.request_render();
1201 EventResult::Handled
1202 });
1203
1204 let state_clone = Arc::clone(&state);
1206 bus.subscribe::<commands::MicroscopeGotoLast, _>(100, move |_event, ctx| {
1207 let picker_name = state_clone
1208 .with::<MicroscopeState, _, _>(|s| s.picker_name.clone())
1209 .unwrap_or_default();
1210
1211 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1212 s.move_to_last();
1213 });
1214
1215 load_preview(&state_clone, &picker_name);
1216 ctx.request_render();
1217 EventResult::Handled
1218 });
1219
1220 let state_clone = Arc::clone(&state);
1222 bus.subscribe::<commands::MicroscopeEnterInsert, _>(100, move |_event, ctx| {
1223 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1224 s.enter_insert();
1225 });
1226
1227 let mode = ModeState::with_interactor_id_sub_mode(
1229 COMPONENT_ID,
1230 EditMode::Normal,
1231 SubMode::Interactor(COMPONENT_ID),
1232 );
1233 ctx.emit(RequestModeChange { mode });
1234
1235 ctx.request_render();
1236 EventResult::Handled
1237 });
1238
1239 let state_clone = Arc::clone(&state);
1241 bus.subscribe::<commands::MicroscopeEnterNormal, _>(100, move |_event, ctx| {
1242 state_clone.with_mut::<MicroscopeState, _, _>(|s| {
1243 s.enter_normal();
1244 });
1245
1246 let mode = ModeState::with_interactor_id_and_mode(COMPONENT_ID, EditMode::Normal);
1248 ctx.emit(RequestModeChange { mode });
1249
1250 ctx.request_render();
1251 EventResult::Handled
1252 });
1253 }
1254}