reovim_plugin_explorer/
provider.rs

1//! Explorer buffer provider for virtual buffer
2
3use reovim_core::{
4    content::{BufferContext, InputResult, PluginBufferProvider},
5    event::KeyEvent,
6    screen::Position,
7};
8
9use reovim_sys::event::KeyCode;
10
11use crate::state::ExplorerState;
12
13/// Explorer buffer provider - generates virtual buffer content for file tree
14pub struct ExplorerBufferProvider;
15
16impl PluginBufferProvider for ExplorerBufferProvider {
17    fn get_lines(&self, ctx: &BufferContext) -> Vec<String> {
18        // Get explorer state from plugin state registry
19        ctx.state
20            .with::<ExplorerState, _, _>(|explorer| {
21                if !explorer.visible {
22                    return Vec::new();
23                }
24
25                // Get visible nodes from tree
26                let nodes = explorer.visible_nodes();
27                let mut lines = Vec::with_capacity(ctx.height as usize);
28
29                // Reserve last line for message/prompt if present
30                let available_height = if explorer.message.is_some() {
31                    ctx.height.saturating_sub(1) as usize
32                } else {
33                    ctx.height as usize
34                };
35
36                // Calculate visible range based on scroll offset
37                let start = explorer.scroll_offset;
38                let end = (start + available_height).min(nodes.len());
39
40                // Generate plain text lines for each visible node
41                for (i, node) in nodes.iter().enumerate().skip(start).take(end - start) {
42                    let is_cursor = i == explorer.cursor_index;
43                    let is_marked = explorer.selection.selected.contains(&node.path);
44
45                    // Build line with indent and icon
46                    let indent = "  ".repeat(node.depth);
47                    let icon = if node.is_dir() {
48                        if node.is_expanded() { "▾ " } else { "▸ " }
49                    } else {
50                        "  "
51                    };
52
53                    let marker = if is_marked {
54                        "* "
55                    } else if is_cursor {
56                        "> "
57                    } else {
58                        "  "
59                    };
60
61                    let size_display = if explorer.show_sizes && !node.is_dir() {
62                        if let Some(size) = node.size() {
63                            format!(" {:>5} ", super::node::format_size(size))
64                        } else {
65                            String::new()
66                        }
67                    } else {
68                        String::new()
69                    };
70
71                    let line = format!("{marker}{indent}{icon}{}{}", node.name, size_display);
72                    lines.push(line);
73                }
74
75                // Add message/input prompt at the bottom if present
76                tracing::debug!(
77                    "ExplorerBufferProvider: message={:?}, input_buffer={:?}",
78                    explorer.message,
79                    explorer.input_buffer
80                );
81                if let Some(ref message) = explorer.message {
82                    let input_display = if !explorer.input_buffer.is_empty() {
83                        explorer.input_buffer.as_str()
84                    } else {
85                        "_" // Show cursor placeholder
86                    };
87                    let prompt_line = format!("{}{}", message, input_display);
88                    tracing::info!("ExplorerBufferProvider: Adding prompt line: {}", prompt_line);
89                    lines.push(prompt_line);
90                }
91
92                // Pad with empty lines if needed to fill remaining height
93                while lines.len() < ctx.height as usize {
94                    lines.push(String::new());
95                }
96
97                lines
98            })
99            .unwrap_or_default()
100    }
101
102    fn on_cursor_move(&mut self, position: Position, ctx: &mut BufferContext) {
103        // Update explorer cursor index when cursor moves
104        let _ = ctx.state.with_mut::<ExplorerState, _, _>(|explorer| {
105            let new_index = (explorer.scroll_offset + position.y as usize)
106                .min(explorer.visible_nodes().len().saturating_sub(1));
107            explorer.cursor_index = new_index;
108        });
109    }
110
111    fn on_input(&mut self, key: KeyEvent, ctx: &mut BufferContext) -> InputResult {
112        use crate::state::ExplorerInputMode;
113
114        tracing::info!("ExplorerBufferProvider::on_input called with key: {:?}", key);
115
116        // Check if explorer is in input mode (create file, rename, etc.)
117        let in_input_mode = ctx
118            .state
119            .with::<ExplorerState, _, _>(|explorer| {
120                !matches!(explorer.input_mode, ExplorerInputMode::None)
121            })
122            .unwrap_or(false);
123
124        tracing::info!("ExplorerBufferProvider::on_input: in_input_mode={}", in_input_mode);
125
126        if !in_input_mode {
127            return InputResult::Unhandled;
128        }
129
130        // Handle character input
131        if let KeyCode::Char(c) = key.code {
132            tracing::info!("ExplorerBufferProvider::on_input: Adding char '{}' to input buffer", c);
133            ctx.state.with_mut::<ExplorerState, _, _>(|explorer| {
134                explorer.input_buffer.push(c);
135            });
136            return InputResult::Handled;
137        }
138
139        // Let other keys (Enter, Escape, Backspace) be handled by commands
140        InputResult::Unhandled
141    }
142
143    fn is_editable(&self) -> bool {
144        // Explorer is not directly editable
145        false
146    }
147}