1use crate::{
6 Config,
7 buffer::{Buffer, Buffers, GapBuffer, Slice},
8 config_handle,
9 dot::TextObject,
10 editor::{Action, Actions, Editor},
11 key::{Arrow, Input},
12 system::System,
13};
14use ad_event::Source;
15use std::{
16 cmp::{self, min},
17 fmt,
18 path::Path,
19 sync::{Arc, RwLock},
20};
21use tracing::trace;
22
23const MINIBUFFER_ID: usize = usize::MAX - 1;
24
25#[derive(Debug, Default)]
26pub struct MiniBufferState<'a> {
27 pub(crate) cx: usize,
28 pub(crate) n_visible_lines: usize,
29 pub(crate) selected_line_idx: usize,
30 pub(crate) prompt: &'a str,
31 pub(crate) input: Slice<'a>,
32 pub(crate) b: Option<&'a Buffer>,
33 pub(crate) top: usize,
34 pub(crate) bottom: usize,
35}
36
37pub(crate) enum MiniBufferSelection {
38 Line { cy: usize, line: String },
39 UserInput { input: String },
40 Cancelled,
41}
42
43pub(crate) struct MiniBuffer<F>
48where
49 F: Fn(&GapBuffer) -> Option<Vec<String>>,
50{
51 on_change: F,
52 prompt: String,
53 n_prompt_chars: usize,
54 input: Buffer,
55 initial_lines: Vec<String>,
56 line_indices: Vec<usize>,
57 b: Buffer,
58 max_height: usize,
59 y: usize,
60 selected_line_idx: usize,
61 n_visible_lines: usize,
62 top: usize,
63 bottom: usize,
64 show_buffer_content: bool,
65}
66
67impl<F> fmt::Debug for MiniBuffer<F>
68where
69 F: Fn(&GapBuffer) -> Option<Vec<String>>,
70{
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 f.debug_struct("MiniBuffer")
73 .field("prompt", &self.prompt)
74 .field("input", &self.input)
75 .finish()
76 }
77}
78
79impl<F> MiniBuffer<F>
80where
81 F: Fn(&GapBuffer) -> Option<Vec<String>>,
82{
83 pub fn new(
84 prompt: String,
85 lines: Vec<String>,
86 max_height: usize,
87 on_change: F,
88 config: Arc<RwLock<Config>>,
89 ) -> Self {
90 let line_indices = Vec::with_capacity(lines.len());
91 let n_prompt_chars = prompt.chars().count();
92
93 Self {
94 on_change,
95 prompt,
96 n_prompt_chars,
97 input: Buffer::new_unnamed(MINIBUFFER_ID, "", config.clone()),
98 initial_lines: lines,
99 line_indices,
100 b: Buffer::new_minibuffer(config),
101 max_height,
102 y: 0,
103 selected_line_idx: 0,
104 n_visible_lines: 0,
105 top: 0,
106 bottom: 0,
107 show_buffer_content: true,
108 }
109 }
110
111 #[inline]
112 fn handle_on_change(&mut self) {
113 if let Some(lines) = (self.on_change)(&self.input.txt) {
114 self.b.txt = GapBuffer::from(lines.join("\n"));
115 self.b.dot.clamp_idx(self.b.txt.len_chars());
116 };
117 }
118
119 #[inline]
120 fn update_state(&mut self) {
121 self.b.txt.clear();
122 self.line_indices.clear();
123
124 let input_fragments: Vec<&str> = self.input.txt.as_str().split_whitespace().collect();
125 let mut visible_lines = vec![];
126
127 for (i, line) in self.initial_lines.iter().enumerate() {
128 let matching = input_fragments.iter().all(|f| {
129 if f.chars().all(|c| c.is_lowercase()) {
130 line.to_lowercase().contains(f)
131 } else {
132 line.contains(f)
133 }
134 });
135
136 if matching {
137 visible_lines.push(line.clone());
138 self.line_indices.push(i);
139 }
140 }
141
142 self.b.txt = GapBuffer::from(visible_lines.join("\n"));
143 self.b.dot.clamp_idx(self.b.txt.len_chars());
144
145 let n_visible_lines = min(visible_lines.len(), self.max_height);
146 let (y, _) = self.b.dot.active_cur().as_yx(&self.b);
147
148 let (selected_line_idx, top, bottom, show_buffer_content) = if n_visible_lines == 0 {
149 (0, 0, 0, false)
150 } else if y >= n_visible_lines {
151 let lower = y.saturating_sub(n_visible_lines) + 1;
152 (y, lower, y, true)
153 } else {
154 (y, 0, n_visible_lines - 1, true)
155 };
156
157 self.show_buffer_content = show_buffer_content;
158 self.selected_line_idx = selected_line_idx;
159 self.n_visible_lines = n_visible_lines;
160 self.y = y;
161 self.top = top;
162 self.bottom = bottom;
163 }
164
165 #[inline]
166 fn current_state(&self) -> MiniBufferState<'_> {
167 MiniBufferState {
168 cx: self.input.dot.active_cur().idx + self.n_prompt_chars,
169 n_visible_lines: self.n_visible_lines,
170 prompt: &self.prompt,
171 input: self.input.txt.as_slice(),
172 selected_line_idx: self.selected_line_idx,
173 b: if self.show_buffer_content {
174 Some(&self.b)
175 } else {
176 None
177 },
178 top: self.top,
179 bottom: self.bottom,
180 }
181 }
182
183 #[inline]
184 fn handle_input(&mut self, input: Input) -> Option<MiniBufferSelection> {
185 match input {
186 Input::Char(c) => {
187 self.input
188 .handle_action(Action::InsertChar { c }, Source::Keyboard);
189 self.handle_on_change();
190 }
191 Input::Ctrl('h') | Input::Backspace | Input::Del => {
192 self.input.handle_action(
193 Action::DotSet(TextObject::Arr(Arrow::Left), 1),
194 Source::Keyboard,
195 );
196 self.input.handle_action(Action::Delete, Source::Keyboard);
197 self.handle_on_change();
198 }
199
200 Input::Esc => return Some(MiniBufferSelection::Cancelled),
201 Input::Return => {
202 let selection = match self.b.line(self.y) {
203 Some(_) if self.line_indices.is_empty() => MiniBufferSelection::UserInput {
204 input: self.input.txt.to_string(),
205 },
206 Some(l) => MiniBufferSelection::Line {
207 cy: self.line_indices[self.y],
208 line: l.to_string(),
209 },
210 None => MiniBufferSelection::UserInput {
211 input: self.input.txt.to_string(),
212 },
213 };
214 return Some(selection);
215 }
216
217 Input::Alt('h') | Input::Arrow(Arrow::Left) => {
218 self.input.handle_action(
219 Action::DotSet(TextObject::Arr(Arrow::Left), 1),
220 Source::Keyboard,
221 );
222 }
223 Input::Alt('l') | Input::Arrow(Arrow::Right) => {
224 self.input.handle_action(
225 Action::DotSet(TextObject::Arr(Arrow::Right), 1),
226 Source::Keyboard,
227 );
228 }
229 Input::Alt('k') | Input::Arrow(Arrow::Up) => {
230 if self.selected_line_idx == 0 {
231 self.b.set_dot(TextObject::BufferEnd, 1);
232 } else {
233 self.b.set_dot(TextObject::Arr(Arrow::Up), 1);
234 }
235 }
236 Input::Alt('j') | Input::Arrow(Arrow::Down) => {
237 if self.selected_line_idx == self.b.len_lines() - 1 {
238 self.b.set_dot(TextObject::BufferStart, 1);
239 } else {
240 self.b.set_dot(TextObject::Arr(Arrow::Down), 1);
241 }
242 }
243
244 _ => (),
245 }
246
247 None
248 }
249}
250
251impl<S> Editor<S>
252where
253 S: System,
254{
255 fn prompt_w_callback<F: Fn(&GapBuffer) -> Option<Vec<String>>>(
256 &mut self,
257 prompt: &str,
258 initial_lines: Vec<String>,
259 initial_input: Option<String>,
260 on_change: F,
261 ) -> MiniBufferSelection {
262 let mut mb = MiniBuffer::new(
263 prompt.to_string(),
264 initial_lines,
265 config_handle!(self).minibuffer_lines,
266 on_change,
267 self.config.clone(),
268 );
269
270 if let Some(s) = initial_input {
271 mb.input
272 .handle_action(Action::InsertString { s }, Source::Fsys);
273 }
274
275 while self.running {
276 mb.update_state();
277 self.refresh_screen_w_minibuffer(Some(mb.current_state()));
278 let inputs = self.block_for_input();
279 for input in inputs.into_iter() {
280 if let Some(selection) = mb.handle_input(input) {
281 return selection;
282 }
283 }
284 }
285
286 MiniBufferSelection::Cancelled
287 }
288
289 pub(crate) fn minibuffer_prompt(&mut self, prompt: &str) -> Option<String> {
291 trace!(%prompt, "opening mini-buffer");
292 match self.prompt_w_callback(prompt, vec![], None, |_| None) {
293 MiniBufferSelection::UserInput { input } => Some(input),
294 _ => None,
295 }
296 }
297
298 pub(crate) fn minibuffer_confirm(&mut self, prompt: &str) -> bool {
301 let resp = self.minibuffer_prompt(&format!("{prompt}, continue? [y/n]: "));
302
303 matches!(resp.as_deref(), Some("y" | "Y" | "yes"))
304 }
305
306 pub(crate) fn minibuffer_select_from(
308 &mut self,
309 prompt: &str,
310 initial_lines: Vec<String>,
311 ) -> MiniBufferSelection {
312 self.prompt_w_callback(prompt, initial_lines, None, |_| None)
313 }
314
315 pub(crate) fn minibuffer_select_from_command_output(
317 &mut self,
318 prompt: &str,
319 cmd: &str,
320 dir: &Path,
321 ) -> MiniBufferSelection {
322 let initial_lines =
323 match self
324 .system
325 .run_command_blocking(cmd, dir, self.active_buffer_id())
326 {
327 Ok(s) => s.lines().map(String::from).collect(),
328 Err(e) => {
329 self.set_status_message(format!("unable to get minibuffer input: {e}"));
330 return MiniBufferSelection::Cancelled;
331 }
332 };
333
334 self.prompt_w_callback(prompt, initial_lines, None, |_| None)
335 }
336}
337
338pub(crate) trait MbSelect: Send + Sync {
341 fn clone_selector(&self) -> MbSelector;
342 fn prompt_and_options(&self, buffers: &Buffers) -> (String, Vec<String>);
343 fn selected_actions(&self, sel: MiniBufferSelection) -> Option<Actions>;
344
345 #[allow(unused_variables)]
346 fn initial_input(&self, buffers: &Buffers) -> Option<String> {
347 None
348 }
349
350 fn into_selector(self) -> MbSelector
351 where
352 Self: Sized + 'static,
353 {
354 MbSelector(Box::new(self))
355 }
356}
357
358pub struct MbSelector(Box<dyn MbSelect>);
359
360impl fmt::Debug for MbSelector {
361 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362 f.debug_struct("MbSelector").finish()
363 }
364}
365
366impl Clone for MbSelector {
367 fn clone(&self) -> Self {
368 self.0.clone_selector()
369 }
370}
371
372impl cmp::Eq for MbSelector {}
373impl cmp::PartialEq for MbSelector {
374 fn eq(&self, _: &Self) -> bool {
375 true
376 }
377}
378
379impl MbSelector {
380 pub(crate) fn run<S>(&self, ed: &mut Editor<S>)
381 where
382 S: System,
383 {
384 let (prompt, options) = self.0.prompt_and_options(ed.layout.buffers());
385 let initial_input = self.0.initial_input(ed.layout.buffers());
386 let selection = ed.prompt_w_callback(&prompt, options, initial_input, |_| None);
387 if let Some(actions) = self.0.selected_actions(selection) {
388 ed.handle_actions(actions, Source::Fsys);
389 }
390 }
391}