1use std::{any::TypeId, sync::Arc};
18
19use reovim_core::{
20 bind::{CommandRef, EditModeKind, KeymapScope},
21 event_bus::{EventBus, EventResult},
22 keys,
23 modd::ComponentId,
24 plugin::{Plugin, PluginContext, PluginId, PluginStateRegistry},
25 subscribe_state, subscribe_state_mode,
26};
27
28mod command;
29mod file_colors;
30mod node;
31mod provider;
32mod render;
33mod state;
34mod tree;
35mod tree_render;
36mod window;
37
38#[cfg(test)]
39mod tests;
40
41pub use {provider::ExplorerBufferProvider, state::ExplorerState};
43
44pub mod command_id {
46 use reovim_core::command::id::CommandId;
47
48 pub const TOGGLE_EXPLORER: CommandId = CommandId::new("explorer_toggle");
49
50 pub const CURSOR_UP: CommandId = CommandId::new("explorer_cursor_up");
52 pub const CURSOR_DOWN: CommandId = CommandId::new("explorer_cursor_down");
53 pub const PAGE_UP: CommandId = CommandId::new("explorer_page_up");
54 pub const PAGE_DOWN: CommandId = CommandId::new("explorer_page_down");
55 pub const GOTO_FIRST: CommandId = CommandId::new("explorer_goto_first");
56 pub const GOTO_LAST: CommandId = CommandId::new("explorer_goto_last");
57 pub const GO_TO_PARENT: CommandId = CommandId::new("explorer_goto_parent");
58 pub const CHANGE_ROOT: CommandId = CommandId::new("explorer_change_root");
59
60 pub const TOGGLE_NODE: CommandId = CommandId::new("explorer_toggle_node");
62 pub const OPEN_NODE: CommandId = CommandId::new("explorer_open_node");
63 pub const CLOSE_PARENT: CommandId = CommandId::new("explorer_close_parent");
64 pub const REFRESH: CommandId = CommandId::new("explorer_refresh");
65 pub const TOGGLE_HIDDEN: CommandId = CommandId::new("explorer_toggle_hidden");
66 pub const TOGGLE_SIZES: CommandId = CommandId::new("explorer_toggle_sizes");
67 pub const CLOSE: CommandId = CommandId::new("explorer_close");
68 pub const FOCUS_EDITOR: CommandId = CommandId::new("explorer_focus_editor");
69
70 pub const CREATE_FILE: CommandId = CommandId::new("explorer_create_file");
72 pub const CREATE_DIR: CommandId = CommandId::new("explorer_create_dir");
73 pub const RENAME: CommandId = CommandId::new("explorer_rename");
74 pub const DELETE: CommandId = CommandId::new("explorer_delete");
75 pub const FILTER: CommandId = CommandId::new("explorer_filter");
76 pub const CLEAR_FILTER: CommandId = CommandId::new("explorer_clear_filter");
77
78 pub const YANK: CommandId = CommandId::new("explorer_yank");
80 pub const CUT: CommandId = CommandId::new("explorer_cut");
81 pub const PASTE: CommandId = CommandId::new("explorer_paste");
82
83 pub const VISUAL_MODE: CommandId = CommandId::new("explorer_visual_mode");
85 pub const TOGGLE_SELECT: CommandId = CommandId::new("explorer_toggle_select");
86 pub const SELECT_ALL: CommandId = CommandId::new("explorer_select_all");
87 pub const EXIT_VISUAL: CommandId = CommandId::new("explorer_exit_visual");
88
89 pub const CONFIRM_INPUT: CommandId = CommandId::new("explorer_confirm_input");
91 pub const CANCEL_INPUT: CommandId = CommandId::new("explorer_cancel_input");
92 pub const INPUT_BACKSPACE: CommandId = CommandId::new("explorer_input_backspace");
93
94 pub const SHOW_INFO: CommandId = CommandId::new("explorer_show_info");
96 pub const CLOSE_POPUP: CommandId = CommandId::new("explorer_close_popup");
97 pub const COPY_PATH: CommandId = CommandId::new("explorer_copy_path");
98}
99
100pub use command::{
102 ExplorerCancelInput, ExplorerChangeRoot, ExplorerClearFilter, ExplorerClose,
103 ExplorerCloseParent, ExplorerClosePopup, ExplorerConfirmInput, ExplorerCopyPath,
104 ExplorerCreateDir, ExplorerCreateFile, ExplorerCursorDown, ExplorerCursorUp, ExplorerCut,
105 ExplorerDelete, ExplorerExitVisual, ExplorerFocusEditor, ExplorerGoToParent, ExplorerGotoFirst,
106 ExplorerGotoLast, ExplorerInputBackspace, ExplorerInputChar, ExplorerOpenNode,
107 ExplorerPageDown, ExplorerPageUp, ExplorerPaste, ExplorerRefresh, ExplorerRename,
108 ExplorerSelectAll, ExplorerShowInfo, ExplorerStartFilter, ExplorerToggle, ExplorerToggleHidden,
109 ExplorerToggleNode, ExplorerToggleSelect, ExplorerToggleSizes, ExplorerVisualMode,
110 ExplorerYank,
111};
112
113pub struct ExplorerPlugin;
121
122impl Plugin for ExplorerPlugin {
123 fn id(&self) -> PluginId {
124 PluginId::new("reovim:explorer")
125 }
126
127 fn name(&self) -> &'static str {
128 "Explorer"
129 }
130
131 fn description(&self) -> &'static str {
132 "File browser sidebar with tree navigation"
133 }
134
135 fn dependencies(&self) -> Vec<TypeId> {
136 vec![]
138 }
139
140 fn build(&self, ctx: &mut PluginContext) {
141 self.register_display_info(ctx);
142 self.register_navigation_commands(ctx);
143 self.register_tree_commands(ctx);
144 self.register_file_commands(ctx);
145 self.register_clipboard_commands(ctx);
146 self.register_visual_commands(ctx);
147 self.register_input_commands(ctx);
148 self.register_keybindings(ctx);
149 }
151
152 fn init_state(&self, registry: &PluginStateRegistry) {
153 if let Ok(cwd) = std::env::current_dir()
156 && let Ok(state) = ExplorerState::new(cwd)
157 {
158 registry.register(state);
159 tracing::info!("ExplorerPlugin: registered state");
160 } else {
161 tracing::error!("ExplorerPlugin: failed to create ExplorerState");
162 }
163
164 registry.register_plugin_window(Arc::new(window::ExplorerPluginWindow));
166 registry.register_plugin_window(Arc::new(window::FileDetailsPluginWindow));
167 tracing::info!("ExplorerPlugin: registered plugin windows");
168 }
169
170 fn subscribe(&self, bus: &EventBus, state: Arc<PluginStateRegistry>) {
171 self.subscribe_raw_input(bus, &state);
172 self.subscribe_navigation(bus, &state);
173 self.subscribe_tree_operations(bus, &state);
174 self.subscribe_clipboard(bus, &state);
175 self.subscribe_file_operations(bus, &state);
176 self.subscribe_input_handling(bus, &state);
177 self.subscribe_visual_mode(bus, &state);
178 self.subscribe_focus_visibility(bus, &state);
179 self.subscribe_popup(bus, &state);
180 self.subscribe_settings(bus, &state);
181 }
182}
183
184impl ExplorerPlugin {
185 fn subscribe_settings(&self, bus: &EventBus, state: &Arc<PluginStateRegistry>) {
187 use reovim_core::option::{
188 OptionChanged, OptionSpec, OptionValue, RegisterOption, RegisterSettingSection,
189 };
190
191 bus.emit(
193 RegisterSettingSection::new("explorer", "File Explorer")
194 .with_description("File tree browser settings")
195 .with_order(110),
196 ); bus.emit(RegisterOption::new(
200 OptionSpec::new(
201 "explorer.enable_colors",
202 "Enable file type coloring",
203 OptionValue::Bool(true),
204 )
205 .with_section("File Explorer")
206 .with_display_order(10),
207 ));
208
209 bus.emit(RegisterOption::new(
210 OptionSpec::new(
211 "explorer.tree_style",
212 "Tree drawing style",
213 OptionValue::String("box_drawing".to_string()),
214 )
215 .with_section("File Explorer")
216 .with_display_order(20),
217 ));
218
219 bus.emit(RegisterOption::new(
220 OptionSpec::new("explorer.show_hidden", "Show hidden files", OptionValue::Bool(false))
221 .with_section("File Explorer")
222 .with_display_order(30),
223 ));
224
225 bus.emit(RegisterOption::new(
226 OptionSpec::new("explorer.show_sizes", "Show file sizes", OptionValue::Bool(false))
227 .with_section("File Explorer")
228 .with_display_order(40),
229 ));
230
231 let state_clone = Arc::clone(state);
233 bus.subscribe::<OptionChanged, _>(100, move |event, ctx| {
234 match event.name.as_str() {
235 "explorer.enable_colors" => {
236 if let Some(enabled) = event.new_value.as_bool() {
237 state_clone.with_mut::<ExplorerState, _, _>(|explorer| {
238 explorer.enable_colors = enabled;
239 });
240 ctx.request_render();
241 }
242 }
243 "explorer.tree_style" => {
244 if let Some(style_str) = event.new_value.as_str() {
245 let tree_style = match style_str {
246 "none" => crate::state::TreeStyle::None,
247 "simple" => crate::state::TreeStyle::Simple,
248 "box_drawing" => crate::state::TreeStyle::BoxDrawing,
249 _ => crate::state::TreeStyle::BoxDrawing,
250 };
251 state_clone.with_mut::<ExplorerState, _, _>(|explorer| {
252 explorer.tree_style = tree_style;
253 });
254 ctx.request_render();
255 }
256 }
257 "explorer.show_hidden" => {
258 if let Some(show) = event.new_value.as_bool() {
259 state_clone.with_mut::<ExplorerState, _, _>(|explorer| {
260 explorer.show_hidden = show;
261 });
262 ctx.request_render();
263 }
264 }
265 "explorer.show_sizes" => {
266 if let Some(show) = event.new_value.as_bool() {
267 state_clone.with_mut::<ExplorerState, _, _>(|explorer| {
268 explorer.show_sizes = show;
269 });
270 ctx.request_render();
271 }
272 }
273 _ => {}
274 }
275 EventResult::Handled
276 });
277 }
278}
279
280impl ExplorerPlugin {
282 fn subscribe_raw_input(&self, bus: &EventBus, state: &Arc<PluginStateRegistry>) {
284 use reovim_core::event_bus::core_events::{PluginBackspace, PluginTextInput};
285
286 let state_clone = Arc::clone(state);
288 bus.subscribe_targeted::<PluginTextInput, _>(COMPONENT_ID, 100, move |event, ctx| {
289 state_clone.with_mut::<ExplorerState, _, _>(|s| {
290 s.input_char(event.c);
291 });
292 ctx.request_render();
293 EventResult::Handled
294 });
295
296 let state_clone = Arc::clone(state);
300 bus.subscribe_targeted::<PluginBackspace, _>(COMPONENT_ID, 100, move |_event, ctx| {
301 let is_input_mode = state_clone
302 .with::<ExplorerState, _, _>(|s| s.is_input_mode())
303 .unwrap_or(false);
304
305 if is_input_mode {
306 state_clone.with_mut::<ExplorerState, _, _>(|s| {
308 s.input_backspace();
309 });
310 } else {
311 state_clone.with_mut::<ExplorerState, _, _>(|s| {
313 s.go_to_parent();
314 s.update_scroll();
315 s.sync_popup();
316 });
317 }
318 ctx.request_render();
319 EventResult::Handled
320 });
321 }
322
323 fn subscribe_navigation(&self, bus: &EventBus, state: &Arc<PluginStateRegistry>) {
325 subscribe_state!(bus, state, ExplorerCursorUp, ExplorerState, |s, e| {
326 s.move_cursor(-(e.count as isize));
327 s.update_scroll();
328 s.sync_popup();
329 });
330
331 subscribe_state!(bus, state, ExplorerCursorDown, ExplorerState, |s, e| {
332 s.move_cursor(e.count as isize);
333 s.update_scroll();
334 s.sync_popup();
335 });
336
337 subscribe_state!(bus, state, ExplorerPageUp, ExplorerState, |s| {
338 s.move_page(s.visible_height, false);
339 s.update_scroll();
340 s.sync_popup();
341 });
342
343 subscribe_state!(bus, state, ExplorerPageDown, ExplorerState, |s| {
344 s.move_page(s.visible_height, true);
345 s.update_scroll();
346 s.sync_popup();
347 });
348
349 subscribe_state!(bus, state, ExplorerGotoFirst, ExplorerState, |s| {
350 s.move_to_first();
351 s.update_scroll();
352 s.sync_popup();
353 });
354
355 subscribe_state!(bus, state, ExplorerGotoLast, ExplorerState, |s| {
356 s.move_to_last();
357 s.update_scroll();
358 s.sync_popup();
359 });
360
361 subscribe_state!(bus, state, ExplorerGoToParent, ExplorerState, |s| {
362 s.go_to_parent();
363 s.update_scroll();
364 s.sync_popup();
365 });
366
367 subscribe_state!(bus, state, ExplorerChangeRoot, ExplorerState, |s| {
368 s.change_root_to_current();
369 s.update_scroll();
370 s.sync_popup();
371 });
372 }
373
374 fn subscribe_tree_operations(&self, bus: &EventBus, state: &Arc<PluginStateRegistry>) {
376 use reovim_core::event_bus::core_events::{RequestFocusChange, RequestOpenFile};
377
378 let state_clone = Arc::clone(state);
380 bus.subscribe::<ExplorerOpenNode, _>(100, move |_event, ctx| {
381 tracing::info!("ExplorerPlugin: ExplorerOpenNode received");
382
383 let result = state_clone.with::<ExplorerState, _, _>(|explorer| {
384 if let Some(node) = explorer.current_node() {
385 if node.is_file() {
386 Some((node.path.clone(), true))
388 } else if node.is_dir() {
389 Some((node.path.clone(), false))
391 } else {
392 None
393 }
394 } else {
395 None
396 }
397 });
398
399 if let Some(Some((path, is_file))) = result {
400 if is_file {
401 tracing::info!("ExplorerPlugin: Requesting to open file: {:?}", path);
402 ctx.emit(RequestOpenFile { path });
403 ctx.emit(RequestFocusChange {
405 target: ComponentId::EDITOR,
406 });
407 } else {
408 tracing::info!("ExplorerPlugin: Toggling directory: {:?}", path);
409 state_clone.with_mut::<ExplorerState, _, _>(|explorer| {
410 let _ = explorer.toggle_current();
411 });
412 }
413 ctx.request_render();
414 }
415
416 EventResult::Handled
417 });
418
419 subscribe_state!(bus, state, ExplorerToggleNode, ExplorerState, |s| {
421 let _ = s.toggle_current();
422 });
423
424 subscribe_state!(bus, state, ExplorerCloseParent, ExplorerState, |s| {
425 s.collapse_current();
426 });
427
428 subscribe_state!(bus, state, ExplorerRefresh, ExplorerState, |s| {
429 let _ = s.refresh();
430 });
431
432 subscribe_state!(bus, state, ExplorerToggleHidden, ExplorerState, |s| {
433 s.toggle_hidden();
434 });
435
436 subscribe_state!(bus, state, ExplorerToggleSizes, ExplorerState, |s| {
437 s.toggle_sizes();
438 });
439 }
440
441 fn subscribe_clipboard(&self, bus: &EventBus, state: &Arc<PluginStateRegistry>) {
443 subscribe_state!(bus, state, ExplorerYank, ExplorerState, |s| {
444 s.yank_current();
445 });
446
447 subscribe_state!(bus, state, ExplorerCut, ExplorerState, |s| {
448 s.cut_current();
449 });
450
451 subscribe_state!(bus, state, ExplorerPaste, ExplorerState, |s| {
452 let _ = s.paste();
453 });
454
455 let state_clone = Arc::clone(state);
457 bus.subscribe::<ExplorerCopyPath, _>(100, move |_event, ctx| {
458 use reovim_core::event_bus::core_events::RequestSetRegister;
459
460 let path = state_clone
461 .with::<ExplorerState, _, _>(|s| {
462 s.current_node()
463 .map(|n| n.path.to_string_lossy().to_string())
464 })
465 .flatten();
466
467 if let Some(path) = path {
468 ctx.emit(RequestSetRegister {
470 register: None, text: path.clone(),
472 });
473 ctx.emit(RequestSetRegister {
474 register: Some('+'), text: path,
476 });
477
478 state_clone.with_mut::<ExplorerState, _, _>(|s| {
479 s.close_popup();
480 s.message = Some("Path copied to clipboard".to_string());
481 });
482 }
483
484 ctx.request_render();
485 EventResult::Handled
486 });
487 }
488
489 fn subscribe_file_operations(&self, bus: &EventBus, state: &Arc<PluginStateRegistry>) {
491 use reovim_core::modd::{EditMode, ModeState, SubMode};
492
493 subscribe_state_mode!(
494 bus,
495 state,
496 ExplorerCreateFile,
497 ExplorerState,
498 |s| {
499 s.start_create_file();
500 },
501 ModeState::with_interactor_id_sub_mode(
502 COMPONENT_ID,
503 EditMode::Normal,
504 SubMode::Interactor(COMPONENT_ID)
505 )
506 );
507
508 subscribe_state_mode!(
509 bus,
510 state,
511 ExplorerCreateDir,
512 ExplorerState,
513 |s| {
514 s.start_create_dir();
515 },
516 ModeState::with_interactor_id_sub_mode(
517 COMPONENT_ID,
518 EditMode::Normal,
519 SubMode::Interactor(COMPONENT_ID)
520 )
521 );
522
523 subscribe_state_mode!(
524 bus,
525 state,
526 ExplorerRename,
527 ExplorerState,
528 |s| {
529 s.start_rename();
530 },
531 ModeState::with_interactor_id_sub_mode(
532 COMPONENT_ID,
533 EditMode::Normal,
534 SubMode::Interactor(COMPONENT_ID)
535 )
536 );
537
538 subscribe_state_mode!(
539 bus,
540 state,
541 ExplorerDelete,
542 ExplorerState,
543 |s| {
544 s.start_delete();
545 },
546 ModeState::with_interactor_id_sub_mode(
547 COMPONENT_ID,
548 EditMode::Normal,
549 SubMode::Interactor(COMPONENT_ID)
550 )
551 );
552
553 subscribe_state_mode!(
554 bus,
555 state,
556 ExplorerStartFilter,
557 ExplorerState,
558 |s| {
559 s.start_filter();
560 },
561 ModeState::with_interactor_id_sub_mode(
562 COMPONENT_ID,
563 EditMode::Normal,
564 SubMode::Interactor(COMPONENT_ID)
565 )
566 );
567
568 subscribe_state_mode!(
570 bus,
571 state,
572 ExplorerClearFilter,
573 ExplorerState,
574 |s| {
575 s.clear_filter();
576 },
577 ModeState::with_interactor_id_and_mode(COMPONENT_ID, EditMode::Normal)
578 );
579 }
580
581 fn subscribe_input_handling(&self, bus: &EventBus, state: &Arc<PluginStateRegistry>) {
583 use reovim_core::{
584 event_bus::core_events::RequestModeChange,
585 modd::{EditMode, ModeState},
586 };
587
588 let state_clone = Arc::clone(state);
589 bus.subscribe::<ExplorerConfirmInput, _>(100, move |_event, ctx| {
590 let popup_visible = state_clone
592 .with::<ExplorerState, _, _>(|s| s.is_popup_visible())
593 .unwrap_or(false);
594
595 if popup_visible {
596 state_clone.with_mut::<ExplorerState, _, _>(|s| {
598 s.close_popup();
599 });
600 ctx.request_render();
601 return EventResult::Handled;
602 }
603
604 let in_input_mode = state_clone
606 .with::<ExplorerState, _, _>(|s| {
607 !matches!(s.input_mode, crate::state::ExplorerInputMode::None)
608 })
609 .unwrap_or(false);
610
611 if in_input_mode {
612 state_clone.with_mut::<ExplorerState, _, _>(|s| {
614 let _ = s.confirm_input();
615 });
616
617 let mode = ModeState::with_interactor_id_and_mode(COMPONENT_ID, EditMode::Normal);
619 ctx.emit(RequestModeChange { mode });
620 } else {
621 ctx.emit(ExplorerOpenNode);
623 }
624
625 ctx.request_render();
626 EventResult::Handled
627 });
628
629 let state_clone = Arc::clone(state);
630 bus.subscribe::<ExplorerCancelInput, _>(100, move |_event, ctx| {
631 let popup_visible = state_clone
633 .with::<ExplorerState, _, _>(|s| s.is_popup_visible())
634 .unwrap_or(false);
635
636 if popup_visible {
637 state_clone.with_mut::<ExplorerState, _, _>(|s| {
639 s.close_popup();
640 });
641 ctx.request_render();
642 return EventResult::Handled;
643 }
644
645 let in_input_mode = state_clone
647 .with::<ExplorerState, _, _>(|s| {
648 !matches!(s.input_mode, crate::state::ExplorerInputMode::None)
649 })
650 .unwrap_or(false);
651
652 if in_input_mode {
653 state_clone.with_mut::<ExplorerState, _, _>(|s| {
655 s.cancel_input();
656 });
657
658 let mode = ModeState::with_interactor_id_and_mode(COMPONENT_ID, EditMode::Normal);
660 ctx.emit(RequestModeChange { mode });
661 } else {
662 ctx.emit(ExplorerFocusEditor);
664 }
665
666 ctx.request_render();
667 EventResult::Handled
668 });
669
670 subscribe_state!(bus, state, ExplorerInputChar, ExplorerState, |s, e| {
671 s.input_char(e.c);
672 });
673
674 subscribe_state!(bus, state, ExplorerInputBackspace, ExplorerState, |s| {
675 s.input_backspace();
676 });
677 }
678
679 fn subscribe_visual_mode(&self, bus: &EventBus, state: &Arc<PluginStateRegistry>) {
681 subscribe_state!(bus, state, ExplorerVisualMode, ExplorerState, |s| {
682 s.enter_visual_mode();
683 });
684
685 subscribe_state!(bus, state, ExplorerToggleSelect, ExplorerState, |s| {
686 s.toggle_select_current();
687 });
688
689 subscribe_state!(bus, state, ExplorerSelectAll, ExplorerState, |s| {
690 s.select_all();
691 });
692
693 subscribe_state!(bus, state, ExplorerExitVisual, ExplorerState, |s| {
694 s.exit_visual_mode();
695 });
696 }
697
698 fn subscribe_focus_visibility(&self, bus: &EventBus, state: &Arc<PluginStateRegistry>) {
700 use reovim_core::event_bus::core_events::RequestFocusChange;
701
702 let state_clone = Arc::clone(state);
704 bus.subscribe::<ExplorerToggle, _>(100, move |_event, ctx| {
705 tracing::info!("ExplorerPlugin: ExplorerToggle received");
706
707 let old_visible = state_clone
708 .with::<ExplorerState, _, _>(|e| e.visible)
709 .unwrap_or(false);
710
711 state_clone.with_mut::<ExplorerState, _, _>(|explorer| {
712 explorer.toggle_visibility();
713 });
714
715 let (new_visible, width) = state_clone
716 .with::<ExplorerState, _, _>(|e| (e.visible, e.width))
717 .unwrap_or((false, 0));
718
719 tracing::info!("ExplorerPlugin: Explorer toggled: {} -> {}", old_visible, new_visible);
720
721 if new_visible {
723 state_clone.set_left_panel_width(width);
724 } else {
725 state_clone.set_left_panel_width(0);
726 }
727
728 if new_visible {
730 ctx.emit(RequestFocusChange {
732 target: COMPONENT_ID,
733 });
734 tracing::info!("ExplorerPlugin: Requesting focus change to explorer");
735 } else {
736 ctx.emit(RequestFocusChange {
738 target: ComponentId::EDITOR,
739 });
740 tracing::info!("ExplorerPlugin: Requesting focus change to editor");
741 }
742
743 ctx.request_render();
744 tracing::info!("ExplorerPlugin: Render requested");
745 EventResult::Handled
746 });
747
748 let state_clone = Arc::clone(state);
750 bus.subscribe::<ExplorerClose, _>(100, move |_event, ctx| {
751 tracing::info!("ExplorerPlugin: ExplorerClose received");
752
753 state_clone.with_mut::<ExplorerState, _, _>(|explorer| {
754 explorer.visible = false;
755 });
756
757 state_clone.set_left_panel_width(0);
759
760 ctx.emit(RequestFocusChange {
761 target: ComponentId::EDITOR,
762 });
763 tracing::info!("ExplorerPlugin: Requesting focus change to editor");
764
765 ctx.request_render();
766 EventResult::Handled
767 });
768
769 bus.subscribe::<ExplorerFocusEditor, _>(100, move |_event, ctx| {
771 tracing::info!("ExplorerPlugin: ExplorerFocusEditor received");
772
773 ctx.emit(RequestFocusChange {
774 target: ComponentId::EDITOR,
775 });
776 tracing::info!("ExplorerPlugin: Requesting focus change to editor");
777
778 ctx.request_render();
779 EventResult::Handled
780 });
781 }
782
783 fn subscribe_popup(&self, bus: &EventBus, state: &Arc<PluginStateRegistry>) {
785 subscribe_state!(bus, state, ExplorerShowInfo, ExplorerState, |s| {
786 s.show_file_details();
787 });
788
789 subscribe_state!(bus, state, ExplorerClosePopup, ExplorerState, |s| {
790 s.close_popup();
791 });
792 }
793}
794
795pub const COMPONENT_ID: ComponentId = ComponentId("explorer");
797
798#[allow(clippy::unused_self)]
799impl ExplorerPlugin {
800 fn register_display_info(&self, ctx: &mut PluginContext) {
801 use reovim_core::highlight::{Color, Style};
802
803 let orange = Color::Rgb {
805 r: 255,
806 g: 153,
807 b: 51,
808 };
809 let fg = Color::Rgb {
810 r: 33,
811 g: 37,
812 b: 43,
813 };
814 let style = Style::new().fg(fg).bg(orange).bold();
815
816 ctx.display_info(COMPONENT_ID)
817 .default(" EXPLORER ", " ", style)
818 .register();
819 }
820
821 fn register_navigation_commands(&self, ctx: &PluginContext) {
822 let _ = ctx.register_command(ExplorerCursorUp::new(1));
823 let _ = ctx.register_command(ExplorerCursorDown::new(1));
824 let _ = ctx.register_command(ExplorerPageUp);
825 let _ = ctx.register_command(ExplorerPageDown);
826 let _ = ctx.register_command(ExplorerGotoFirst);
827 let _ = ctx.register_command(ExplorerGotoLast);
828 let _ = ctx.register_command(ExplorerGoToParent);
829 let _ = ctx.register_command(ExplorerChangeRoot);
830 }
831
832 fn register_tree_commands(&self, ctx: &PluginContext) {
833 let _ = ctx.register_command(ExplorerToggle);
834 let _ = ctx.register_command(ExplorerToggleNode);
835 let _ = ctx.register_command(ExplorerOpenNode);
836 let _ = ctx.register_command(ExplorerCloseParent);
837 let _ = ctx.register_command(ExplorerRefresh);
838 let _ = ctx.register_command(ExplorerToggleHidden);
839 let _ = ctx.register_command(ExplorerToggleSizes);
840 let _ = ctx.register_command(ExplorerClose);
841 let _ = ctx.register_command(ExplorerFocusEditor);
842 let _ = ctx.register_command(ExplorerShowInfo);
843 let _ = ctx.register_command(ExplorerClosePopup);
844 let _ = ctx.register_command(ExplorerCopyPath);
845 }
846
847 fn register_file_commands(&self, ctx: &PluginContext) {
848 let _ = ctx.register_command(ExplorerCreateFile);
849 let _ = ctx.register_command(ExplorerCreateDir);
850 let _ = ctx.register_command(ExplorerRename);
851 let _ = ctx.register_command(ExplorerDelete);
852 let _ = ctx.register_command(ExplorerStartFilter);
853 let _ = ctx.register_command(ExplorerClearFilter);
854 }
855
856 fn register_clipboard_commands(&self, ctx: &PluginContext) {
857 let _ = ctx.register_command(ExplorerYank);
858 let _ = ctx.register_command(ExplorerCut);
859 let _ = ctx.register_command(ExplorerPaste);
860 }
861
862 fn register_visual_commands(&self, ctx: &PluginContext) {
863 let _ = ctx.register_command(ExplorerVisualMode);
864 let _ = ctx.register_command(ExplorerToggleSelect);
865 let _ = ctx.register_command(ExplorerSelectAll);
866 let _ = ctx.register_command(ExplorerExitVisual);
867 }
868
869 fn register_input_commands(&self, ctx: &PluginContext) {
870 let _ = ctx.register_command(ExplorerConfirmInput);
871 let _ = ctx.register_command(ExplorerCancelInput);
872 let _ = ctx.register_command(ExplorerInputBackspace);
873 }
874
875 fn register_keybindings(&self, ctx: &mut PluginContext) {
876 use reovim_core::bind::SubModeKind;
877
878 let editor_normal = KeymapScope::editor_normal();
879 let explorer_normal = KeymapScope::Component {
880 id: COMPONENT_ID,
881 mode: EditModeKind::Normal,
882 };
883 let explorer_interactor = KeymapScope::SubMode(SubModeKind::Interactor(COMPONENT_ID));
884
885 ctx.bind_key_scoped(
887 editor_normal,
888 keys![Space 'e'],
889 CommandRef::Registered(command_id::TOGGLE_EXPLORER),
890 );
891
892 ctx.bind_key_scoped(
896 explorer_normal.clone(),
897 keys!['j'],
898 CommandRef::Registered(command_id::CURSOR_DOWN),
899 );
900 ctx.bind_key_scoped(
901 explorer_normal.clone(),
902 keys!['k'],
903 CommandRef::Registered(command_id::CURSOR_UP),
904 );
905 ctx.bind_key_scoped(
906 explorer_normal.clone(),
907 keys![(Ctrl 'd')],
908 CommandRef::Registered(command_id::PAGE_DOWN),
909 );
910 ctx.bind_key_scoped(
911 explorer_normal.clone(),
912 keys![(Ctrl 'u')],
913 CommandRef::Registered(command_id::PAGE_UP),
914 );
915 ctx.bind_key_scoped(
916 explorer_normal.clone(),
917 keys!['g' 'g'],
918 CommandRef::Registered(command_id::GOTO_FIRST),
919 );
920 ctx.bind_key_scoped(
921 explorer_normal.clone(),
922 keys!['G'],
923 CommandRef::Registered(command_id::GOTO_LAST),
924 );
925 ctx.bind_key_scoped(
926 explorer_normal.clone(),
927 keys!['-'],
928 CommandRef::Registered(command_id::GO_TO_PARENT),
929 );
930 ctx.bind_key_scoped(
931 explorer_normal.clone(),
932 keys![Backspace],
933 CommandRef::Registered(command_id::GO_TO_PARENT),
934 );
935 ctx.bind_key_scoped(
936 explorer_normal.clone(),
937 keys!['.'],
938 CommandRef::Registered(command_id::CHANGE_ROOT),
939 );
940
941 ctx.bind_key_scoped(
944 explorer_normal.clone(),
945 keys![Enter],
946 CommandRef::Registered(command_id::CONFIRM_INPUT),
947 );
948 ctx.bind_key_scoped(
949 explorer_normal.clone(),
950 keys!['l'],
951 CommandRef::Registered(command_id::OPEN_NODE),
952 );
953 ctx.bind_key_scoped(
954 explorer_normal.clone(),
955 keys!['h'],
956 CommandRef::Registered(command_id::CLOSE_PARENT),
957 );
958 ctx.bind_key_scoped(
959 explorer_normal.clone(),
960 keys![Space],
961 CommandRef::Registered(command_id::TOGGLE_NODE),
962 );
963 ctx.bind_key_scoped(
964 explorer_normal.clone(),
965 keys!['R'],
966 CommandRef::Registered(command_id::REFRESH),
967 );
968 ctx.bind_key_scoped(
969 explorer_normal.clone(),
970 keys!['H'],
971 CommandRef::Registered(command_id::TOGGLE_HIDDEN),
972 );
973 ctx.bind_key_scoped(
974 explorer_normal.clone(),
975 keys!['s'],
976 CommandRef::Registered(command_id::SHOW_INFO),
977 );
978 ctx.bind_key_scoped(
979 explorer_normal.clone(),
980 keys!['t'],
981 CommandRef::Registered(command_id::COPY_PATH),
982 );
983 ctx.bind_key_scoped(
984 explorer_normal.clone(),
985 keys!['q'],
986 CommandRef::Registered(command_id::CLOSE),
987 );
988 ctx.bind_key_scoped(
990 explorer_normal.clone(),
991 keys![Escape],
992 CommandRef::Registered(command_id::CANCEL_INPUT),
993 );
994
995 ctx.bind_key_scoped(
997 explorer_normal.clone(),
998 keys!['a'],
999 CommandRef::Registered(command_id::CREATE_FILE),
1000 );
1001 ctx.bind_key_scoped(
1002 explorer_normal.clone(),
1003 keys!['A'],
1004 CommandRef::Registered(command_id::CREATE_DIR),
1005 );
1006 ctx.bind_key_scoped(
1007 explorer_normal.clone(),
1008 keys!['r'],
1009 CommandRef::Registered(command_id::RENAME),
1010 );
1011 ctx.bind_key_scoped(
1012 explorer_normal.clone(),
1013 keys!['d'],
1014 CommandRef::Registered(command_id::DELETE),
1015 );
1016 ctx.bind_key_scoped(
1017 explorer_normal.clone(),
1018 keys!['/'],
1019 CommandRef::Registered(command_id::FILTER),
1020 );
1021 ctx.bind_key_scoped(
1022 explorer_normal.clone(),
1023 keys!['c'],
1024 CommandRef::Registered(command_id::CLEAR_FILTER),
1025 );
1026
1027 ctx.bind_key_scoped(
1029 explorer_normal.clone(),
1030 keys!['y'],
1031 CommandRef::Registered(command_id::YANK),
1032 );
1033 ctx.bind_key_scoped(
1034 explorer_normal.clone(),
1035 keys!['x'],
1036 CommandRef::Registered(command_id::CUT),
1037 );
1038 ctx.bind_key_scoped(
1039 explorer_normal.clone(),
1040 keys!['p'],
1041 CommandRef::Registered(command_id::PASTE),
1042 );
1043
1044 ctx.bind_key_scoped(
1046 explorer_normal.clone(),
1047 keys!['v'],
1048 CommandRef::Registered(command_id::VISUAL_MODE),
1049 );
1050
1051 ctx.bind_key_scoped(
1054 explorer_interactor.clone(),
1055 keys![Enter],
1056 CommandRef::Registered(command_id::CONFIRM_INPUT),
1057 );
1058 ctx.bind_key_scoped(
1060 explorer_interactor.clone(),
1061 keys![Escape],
1062 CommandRef::Registered(command_id::CANCEL_INPUT),
1063 );
1064 ctx.bind_key_scoped(
1066 explorer_interactor,
1067 keys![Backspace],
1068 CommandRef::Registered(command_id::INPUT_BACKSPACE),
1069 );
1070
1071 tracing::info!(
1072 "ExplorerPlugin: registered keybinding Space+e, explorer navigation, and interactor input"
1073 );
1074 }
1075}