Skip to main content

shannon_reedline/menu/
mod.rs

1mod columnar_menu;
2mod description_menu;
3mod ide_menu;
4mod list_menu;
5pub mod menu_functions;
6
7use crate::core_editor::Editor;
8use crate::History;
9use crate::{completion::history::HistoryCompleter, painting::Painter, Completer, Suggestion};
10pub use columnar_menu::ColumnarMenu;
11pub use columnar_menu::TraversalDirection;
12pub use description_menu::DescriptionMenu;
13pub use ide_menu::DescriptionMode;
14pub use ide_menu::IdeMenu;
15pub use list_menu::ListMenu;
16use nu_ansi_term::{Color, Style};
17
18/// Struct to store the menu style
19pub struct MenuTextStyle {
20    /// Text style for selected text in a menu
21    pub selected_text_style: Style,
22    /// Text style for not selected text in the menu
23    pub text_style: Style,
24    /// Text style for the item description
25    pub description_style: Style,
26    /// Text style of the parts of the suggestions that match the
27    /// typed text when the suggestion is selected
28    pub selected_match_style: Style,
29    /// Text style of the parts of the suggestions that match the
30    /// typed text
31    pub match_style: Style,
32}
33
34impl Default for MenuTextStyle {
35    fn default() -> Self {
36        Self {
37            selected_text_style: Color::Green.bold().reverse(),
38            text_style: Color::DarkGray.normal(),
39            description_style: Color::Yellow.normal(),
40            selected_match_style: Color::Green.bold().reverse().underline(),
41            match_style: Style::default().underline(),
42        }
43    }
44}
45
46/// Defines all possible events that could happen with a menu.
47#[derive(Clone)]
48pub enum MenuEvent {
49    /// Activation event for the menu. When the bool is true it means that the values
50    /// have already being updated. This is true when the option `quick_completions` is true
51    Activate(bool),
52    /// Deactivation event
53    Deactivate,
54    /// Line buffer edit event. When the bool is true it means that the values
55    /// have already being updated. This is true when the option `quick_completions` is true
56    Edit(bool),
57    /// Selecting next element in the menu
58    NextElement,
59    /// Selecting previous element in the menu
60    PreviousElement,
61    /// Moving up in the menu
62    MoveUp,
63    /// Moving down in the menu
64    MoveDown,
65    /// Moving left in the menu
66    MoveLeft,
67    /// Moving right in the menu
68    MoveRight,
69    /// Move to next page
70    NextPage,
71    /// Move to previous page
72    PreviousPage,
73}
74
75/// Trait that defines how a menu will be printed by the painter
76pub trait Menu: Send {
77    /// Get MenuSettings
78    fn settings(&self) -> &MenuSettings {
79        // We panic here, so this function has base implementation
80        // so existing menus will not break.
81        // if a breaking change is ok, this can be removed
82        panic!("`settings` requires a manual implementation per menu. It has a base implementation to not break existing menus")
83    }
84
85    /// Menu name
86    fn name(&self) -> &str {
87        &self.settings().name
88    }
89
90    /// Menu indicator
91    fn indicator(&self) -> &str {
92        &self.settings().marker
93    }
94
95    /// Checks if the menu is active
96    fn is_active(&self) -> bool;
97
98    /// Selects what type of event happened with the menu
99    fn menu_event(&mut self, event: MenuEvent);
100
101    /// A menu may not be allowed to quick complete because it needs to stay
102    /// active even with one element
103    fn can_quick_complete(&self) -> bool;
104
105    /// The completion menu can try to find the common string and replace it
106    /// in the given line buffer
107    fn can_partially_complete(
108        &mut self,
109        values_updated: bool,
110        editor: &mut Editor,
111        completer: &mut dyn Completer,
112    ) -> bool;
113
114    /// Updates the values presented in the menu
115    /// This function needs to be defined in the trait because when the menu is
116    /// activated or the `quick_completion` option is true, the len of the values
117    /// is calculated to know if there is only one value so it can be selected
118    /// immediately
119    fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer);
120
121    /// The working details of a menu are values that could change based on
122    /// the menu conditions before it being printed, such as the number or size
123    /// of columns, etc.
124    /// In this function should be defined how the menu event is treated since
125    /// it is called just before painting the menu
126    fn update_working_details(
127        &mut self,
128        editor: &mut Editor,
129        completer: &mut dyn Completer,
130        painter: &Painter,
131    );
132
133    /// Indicates how to replace in the line buffer the selected value from the menu
134    fn replace_in_buffer(&self, editor: &mut Editor);
135
136    /// Calculates the real required lines for the menu considering how many lines
137    /// wrap the terminal or if entries have multiple lines
138    fn menu_required_lines(&self, terminal_columns: u16) -> u16;
139
140    /// Creates the menu representation as a string which will be painted by the painter
141    fn menu_string(&self, available_lines: u16, use_ansi_coloring: bool) -> String;
142
143    /// Minimum rows that should be displayed by the menu
144    fn min_rows(&self) -> u16;
145
146    /// Gets cached values from menu that will be displayed
147    fn get_values(&self) -> &[Suggestion];
148    /// Sets the position of the cursor (currently only required by the IDE menu)
149    fn set_cursor_pos(&mut self, _pos: (u16, u16)) {
150        // empty implementation to make it optional
151    }
152}
153
154/// Struct to store configuration for a menu.
155pub struct MenuSettings {
156    /// Menu name
157    name: String,
158    /// Menu coloring
159    color: MenuTextStyle,
160    /// Menu marker when active
161    marker: String,
162    /// Calls the completer using only the line buffer difference difference
163    /// after the menu was activated
164    only_buffer_difference: bool,
165}
166
167impl Default for MenuSettings {
168    fn default() -> Self {
169        Self {
170            name: "menu".to_string(),
171            color: MenuTextStyle::default(),
172            marker: "| ".to_string(),
173            only_buffer_difference: false,
174        }
175    }
176}
177
178impl MenuSettings {
179    /// MenuSettings builder with name
180    #[must_use]
181    pub fn with_name(mut self, name: &str) -> Self {
182        self.name = name.to_string();
183        self
184    }
185
186    /// MenuSettings builder with color
187    #[must_use]
188    pub fn with_color(mut self, color: MenuTextStyle) -> Self {
189        self.color = color;
190        self
191    }
192
193    /// MenuSettings builder with marker
194    #[must_use]
195    pub fn with_marker(mut self, marker: &str) -> Self {
196        self.marker = marker.to_string();
197        self
198    }
199
200    /// MenuSettings builder with only_buffer_difference
201    #[must_use]
202    pub fn with_only_buffer_difference(mut self, only_buffer_difference: bool) -> Self {
203        self.only_buffer_difference = only_buffer_difference;
204        self
205    }
206}
207
208/// Common builder for all menus
209pub trait MenuBuilder: Menu + Sized {
210    /// Get mutable MenuSettings
211    /// required for the builder functions
212    fn settings_mut(&mut self) -> &mut MenuSettings;
213
214    /// Menu builder with new name
215    #[must_use]
216    fn with_name(mut self, name: &str) -> Self {
217        self.settings_mut().name = name.to_string();
218        self
219    }
220
221    /// Menu builder with new value for text style
222    #[must_use]
223    fn with_text_style(mut self, color: Style) -> Self {
224        self.settings_mut().color.text_style = color;
225        self
226    }
227
228    /// Menu builder with new value for selected text style
229    #[must_use]
230    fn with_selected_text_style(mut self, color: Style) -> Self {
231        self.settings_mut().color.selected_text_style = color;
232        self
233    }
234
235    /// Menu builder with new value for description style
236    #[must_use]
237    fn with_description_text_style(mut self, color: Style) -> Self {
238        self.settings_mut().color.description_style = color;
239        self
240    }
241
242    /// Menu builder with new value for match style
243    /// This is the style of the part of the input text, the suggestions
244    /// are based on
245    #[must_use]
246    fn with_match_text_style(mut self, color: Style) -> Self {
247        self.settings_mut().color.match_style = color;
248        self
249    }
250
251    /// Menu builder with new value for selected match style
252    /// This is the style of the part of the input text, the suggestions
253    /// are based on
254    #[must_use]
255    fn with_selected_match_text_style(mut self, color: Style) -> Self {
256        self.settings_mut().color.selected_match_style = color;
257        self
258    }
259
260    /// Menu builder with new value for marker
261    #[must_use]
262    fn with_marker(mut self, marker: &str) -> Self {
263        self.settings_mut().marker = marker.to_string();
264        self
265    }
266
267    /// Menu builder with new value for only_buffer_difference
268    #[must_use]
269    fn with_only_buffer_difference(mut self, only_buffer_difference: bool) -> Self {
270        self.settings_mut().only_buffer_difference = only_buffer_difference;
271        self
272    }
273}
274
275/// Allowed menus in Reedline
276pub enum ReedlineMenu {
277    /// Menu that uses Reedline's completer to update its values
278    EngineCompleter(Box<dyn Menu>),
279    /// Menu that uses the history as its completer
280    HistoryMenu(Box<dyn Menu>),
281    /// Menu that has its own Completer
282    WithCompleter {
283        /// Base menu
284        menu: Box<dyn Menu>,
285        /// External completer defined outside Reedline
286        completer: Box<dyn Completer>,
287    },
288}
289
290impl ReedlineMenu {
291    fn as_ref(&self) -> &dyn Menu {
292        match self {
293            Self::EngineCompleter(menu)
294            | Self::HistoryMenu(menu)
295            | Self::WithCompleter { menu, .. } => menu.as_ref(),
296        }
297    }
298
299    fn as_mut(&mut self) -> &mut dyn Menu {
300        match self {
301            Self::EngineCompleter(menu)
302            | Self::HistoryMenu(menu)
303            | Self::WithCompleter { menu, .. } => menu.as_mut(),
304        }
305    }
306
307    pub(crate) fn can_partially_complete(
308        &mut self,
309        values_updated: bool,
310        editor: &mut Editor,
311        completer: &mut dyn Completer,
312        history: &dyn History,
313    ) -> bool {
314        match self {
315            Self::EngineCompleter(menu) => {
316                menu.can_partially_complete(values_updated, editor, completer)
317            }
318            Self::HistoryMenu(menu) => {
319                let mut history_completer = HistoryCompleter::new(history);
320                menu.can_partially_complete(values_updated, editor, &mut history_completer)
321            }
322            Self::WithCompleter {
323                menu,
324                completer: own_completer,
325            } => menu.can_partially_complete(values_updated, editor, own_completer.as_mut()),
326        }
327    }
328
329    pub(crate) fn update_values(
330        &mut self,
331        editor: &mut Editor,
332        completer: &mut dyn Completer,
333        history: &dyn History,
334    ) {
335        match self {
336            Self::EngineCompleter(menu) => menu.update_values(editor, completer),
337            Self::HistoryMenu(menu) => {
338                let mut history_completer = HistoryCompleter::new(history);
339                menu.update_values(editor, &mut history_completer);
340            }
341            Self::WithCompleter {
342                menu,
343                completer: own_completer,
344            } => {
345                menu.update_values(editor, own_completer.as_mut());
346            }
347        }
348    }
349
350    pub(crate) fn update_working_details(
351        &mut self,
352        editor: &mut Editor,
353        completer: &mut dyn Completer,
354        history: &dyn History,
355        painter: &Painter,
356    ) {
357        match self {
358            Self::EngineCompleter(menu) => {
359                menu.update_working_details(editor, completer, painter);
360            }
361            Self::HistoryMenu(menu) => {
362                let mut history_completer = HistoryCompleter::new(history);
363                menu.update_working_details(editor, &mut history_completer, painter);
364            }
365            Self::WithCompleter {
366                menu,
367                completer: own_completer,
368            } => {
369                menu.update_working_details(editor, own_completer.as_mut(), painter);
370            }
371        }
372    }
373}
374
375impl Menu for ReedlineMenu {
376    fn settings(&self) -> &MenuSettings {
377        self.as_ref().settings()
378    }
379
380    fn name(&self) -> &str {
381        self.as_ref().name()
382    }
383
384    fn indicator(&self) -> &str {
385        self.as_ref().indicator()
386    }
387
388    fn is_active(&self) -> bool {
389        self.as_ref().is_active()
390    }
391
392    fn menu_event(&mut self, event: MenuEvent) {
393        self.as_mut().menu_event(event);
394    }
395
396    fn can_quick_complete(&self) -> bool {
397        self.as_ref().can_quick_complete()
398    }
399
400    fn can_partially_complete(
401        &mut self,
402        values_updated: bool,
403        editor: &mut Editor,
404        completer: &mut dyn Completer,
405    ) -> bool {
406        match self {
407            Self::EngineCompleter(menu) | Self::HistoryMenu(menu) => {
408                menu.can_partially_complete(values_updated, editor, completer)
409            }
410            Self::WithCompleter {
411                menu,
412                completer: own_completer,
413            } => menu.can_partially_complete(values_updated, editor, own_completer.as_mut()),
414        }
415    }
416
417    fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer) {
418        match self {
419            Self::EngineCompleter(menu) | Self::HistoryMenu(menu) => {
420                menu.update_values(editor, completer);
421            }
422            Self::WithCompleter {
423                menu,
424                completer: own_completer,
425            } => {
426                menu.update_values(editor, own_completer.as_mut());
427            }
428        }
429    }
430
431    fn update_working_details(
432        &mut self,
433        editor: &mut Editor,
434        completer: &mut dyn Completer,
435        painter: &Painter,
436    ) {
437        match self {
438            Self::EngineCompleter(menu) | Self::HistoryMenu(menu) => {
439                menu.update_working_details(editor, completer, painter);
440            }
441            Self::WithCompleter {
442                menu,
443                completer: own_completer,
444            } => {
445                menu.update_working_details(editor, own_completer.as_mut(), painter);
446            }
447        }
448    }
449
450    fn replace_in_buffer(&self, editor: &mut Editor) {
451        self.as_ref().replace_in_buffer(editor);
452    }
453
454    fn menu_required_lines(&self, terminal_columns: u16) -> u16 {
455        self.as_ref().menu_required_lines(terminal_columns)
456    }
457
458    fn menu_string(&self, available_lines: u16, use_ansi_coloring: bool) -> String {
459        self.as_ref()
460            .menu_string(available_lines, use_ansi_coloring)
461    }
462
463    fn min_rows(&self) -> u16 {
464        self.as_ref().min_rows()
465    }
466
467    fn get_values(&self) -> &[Suggestion] {
468        self.as_ref().get_values()
469    }
470
471    fn set_cursor_pos(&mut self, pos: (u16, u16)) {
472        self.as_mut().set_cursor_pos(pos);
473    }
474}