reovim_plugin_pickers/
files.rs1use std::{future::Future, pin::Pin};
4
5use {
6 ignore::WalkBuilder,
7 reovim_plugin_microscope::{
8 MicroscopeAction, MicroscopeData, MicroscopeItem, Picker, PickerContext, PreviewContent,
9 },
10};
11
12pub struct FilesPicker {
14 ignore_patterns: Vec<String>,
16}
17
18impl FilesPicker {
19 #[must_use]
21 pub fn new() -> Self {
22 Self {
23 ignore_patterns: vec![
24 ".git".to_string(),
25 "node_modules".to_string(),
26 "target".to_string(),
27 ".cache".to_string(),
28 ],
29 }
30 }
31
32 pub fn add_ignore(&mut self, pattern: impl Into<String>) {
34 self.ignore_patterns.push(pattern.into());
35 }
36}
37
38impl Default for FilesPicker {
39 fn default() -> Self {
40 Self::new()
41 }
42}
43
44impl Picker for FilesPicker {
45 fn name(&self) -> &'static str {
46 "files"
47 }
48
49 fn title(&self) -> &'static str {
50 "Find Files"
51 }
52
53 fn prompt(&self) -> &'static str {
54 "Files> "
55 }
56
57 fn fetch(
58 &self,
59 ctx: &PickerContext,
60 ) -> Pin<Box<dyn Future<Output = Vec<MicroscopeItem>> + Send + '_>> {
61 let cwd = ctx.cwd.clone();
62 let max_items = ctx.max_items;
63 let ignore_patterns = self.ignore_patterns.clone();
64
65 Box::pin(async move {
66 let mut items = Vec::new();
67
68 let walker = WalkBuilder::new(&cwd)
69 .hidden(true)
70 .git_ignore(true)
71 .git_global(true)
72 .git_exclude(true)
73 .build();
74
75 for entry in walker.flatten() {
76 if items.len() >= max_items {
77 break;
78 }
79
80 let path = entry.path();
81
82 if path.is_dir() {
84 continue;
85 }
86
87 let path_str = path.to_string_lossy();
89 if ignore_patterns.iter().any(|p| path_str.contains(p)) {
90 continue;
91 }
92
93 let display = path
95 .strip_prefix(&cwd)
96 .unwrap_or(path)
97 .to_string_lossy()
98 .to_string();
99
100 let icon = match path.extension().and_then(|e| e.to_str()) {
102 Some("rs") => '\u{e7a8}', Some("js" | "ts") => '\u{e781}',
104 Some("py") => '\u{e73c}',
105 Some("md") => '\u{e73e}',
106 Some("json" | "toml" | "yaml" | "yml") => '\u{e60b}',
107 _ => '\u{f15b}', };
109
110 items.push(
111 MicroscopeItem::new(
112 &display,
113 &display,
114 MicroscopeData::FilePath(path.to_path_buf()),
115 "files",
116 )
117 .with_icon(icon),
118 );
119 }
120
121 items
122 })
123 }
124
125 fn on_select(&self, item: &MicroscopeItem) -> MicroscopeAction {
126 match &item.data {
127 MicroscopeData::FilePath(path) => MicroscopeAction::OpenFile(path.clone()),
128 _ => MicroscopeAction::Nothing,
129 }
130 }
131
132 fn preview(
133 &self,
134 item: &MicroscopeItem,
135 ctx: &PickerContext,
136 ) -> Pin<Box<dyn Future<Output = Option<PreviewContent>> + Send + '_>> {
137 let path = match &item.data {
138 MicroscopeData::FilePath(p) => p.clone(),
139 _ => return Box::pin(async { None }),
140 };
141
142 let file_name = path.file_name().and_then(|n| n.to_str()).map(String::from);
143 let syntax_factory = ctx.syntax_factory.clone();
144
145 Box::pin(async move {
146 tokio::fs::read_to_string(&path).await.ok().map(|content| {
148 let lines: Vec<String> = content.lines().map(String::from).collect();
149 let syntax_ext = path.extension().and_then(|e| e.to_str()).map(String::from);
150
151 let styled_lines = if let Some(factory) = syntax_factory {
153 let file_path = path.to_string_lossy().to_string();
154 crate::syntax_helper::compute_styled_lines(
155 factory.as_ref(),
156 &file_path,
157 &content,
158 &lines,
159 )
160 } else {
161 None
162 };
163
164 PreviewContent {
165 lines,
166 highlight_line: None,
167 syntax: syntax_ext,
168 title: file_name,
169 styled_lines,
170 }
171 })
172 })
173 }
174}