lineeditor/
engine.rs

1use std::io::stdout;
2use std::io::Result;
3
4use crossterm::cursor::position;
5use crossterm::cursor::SetCursorStyle;
6use crossterm::event;
7use crossterm::event::DisableBracketedPaste;
8use crossterm::event::DisableFocusChange;
9use crossterm::event::DisableMouseCapture;
10use crossterm::event::EnableBracketedPaste;
11use crossterm::event::EnableFocusChange;
12use crossterm::event::EnableMouseCapture;
13use crossterm::event::Event;
14use crossterm::event::KeyCode;
15use crossterm::event::KeyEventKind;
16use crossterm::event::KeyModifiers;
17use crossterm::event::KeyboardEnhancementFlags;
18use crossterm::event::PopKeyboardEnhancementFlags;
19use crossterm::event::PushKeyboardEnhancementFlags;
20use crossterm::execute;
21use crossterm::terminal;
22
23use crate::completion::Suggestion;
24use crate::editor::Editor;
25use crate::event::EditCommand;
26use crate::event::LineEditorEvent;
27use crate::event::MovementCommand;
28use crate::input_filter::filter_input;
29use crate::input_filter::InputFilter;
30use crate::keybindings::KeyCombination;
31use crate::keybindings::Keybindings;
32use crate::style::Style;
33use crate::styled_editor_view::StyledEditorView;
34use crate::AutoPair;
35use crate::Completer;
36use crate::DropDownListView;
37use crate::Highlighter;
38use crate::Hinter;
39use crate::ListView;
40use crate::Prompt;
41use crate::DEFAULT_PAIRS;
42
43/// A Result can return from`LineEditor::read_line()`
44#[derive(Debug)]
45pub enum LineEditorResult {
46    /// Entry succeeded with the provided content
47    Success(String),
48    /// Interrupt current editing
49    Interrupted,
50    /// End terminal session
51    EndTerminalSession,
52}
53
54/// An internal Status returned after applying event
55enum EventStatus {
56    #[allow(dead_code)]
57    /// General Event Handled
58    GeneralHandled,
59    /// Edit Event is handled
60    EditHandled,
61    /// Movement Event is handled
62    MovementHandled,
63    /// Selection Event is handled
64    SelectionHandled,
65    /// Auto Complete Event is handled
66    AutoCompleteHandled,
67    /// Event is in applicable to handle
68    Inapplicable,
69    /// Exit with Result or Error
70    Exits(LineEditorResult),
71}
72
73/// Line Editor Engine
74pub struct LineEditor {
75    prompt: Box<dyn Prompt>,
76    editor: Editor,
77    input_filter: InputFilter,
78    styled_editor_text: StyledEditorView,
79    keybindings: Keybindings,
80    auto_pair: Option<Box<dyn AutoPair>>,
81    highlighters: Vec<Box<dyn Highlighter>>,
82    hinters: Vec<Box<dyn Hinter>>,
83
84    completer: Option<Box<dyn Completer>>,
85    auto_complete_view: Box<dyn ListView<Suggestion>>,
86
87    cursor_style: Option<SetCursorStyle>,
88    selection_style: Option<Style>,
89    selected_start: u16,
90    selected_end: u16,
91    enable_surround_selection: bool,
92}
93
94impl LineEditor {
95    /// Create new instance of LineEditor with Prompt
96    #[must_use]
97    pub fn new(prompt: Box<dyn Prompt>) -> Self {
98        LineEditor {
99            prompt,
100            editor: Editor::default(),
101            input_filter: InputFilter::Text,
102            styled_editor_text: StyledEditorView::default(),
103            keybindings: Keybindings::default(),
104            auto_pair: None,
105            highlighters: vec![],
106            hinters: vec![],
107            completer: None,
108            auto_complete_view: Box::<DropDownListView>::default(),
109            cursor_style: None,
110
111            selection_style: None,
112            selected_start: 0,
113            selected_end: 0,
114            enable_surround_selection: false,
115        }
116    }
117
118    /// Wait for input and provide the user
119    ///
120    /// Returns a [`std::io::Result`] in which the `Err` type is [`std::io::Result`]
121    /// and the `Ok` variant wraps a [`LineEditorResult`] which handles user inputs.
122    pub fn read_line(&mut self) -> Result<LineEditorResult> {
123        if let Some(cursor_style) = self.cursor_style {
124            self.styled_editor_text.set_cursor_style(cursor_style)?;
125        }
126
127        terminal::enable_raw_mode()?;
128        execute!(
129            stdout(),
130            EnableBracketedPaste,
131            EnableFocusChange,
132            EnableMouseCapture,
133            PushKeyboardEnhancementFlags(
134                KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
135                    | KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES
136                    | KeyboardEnhancementFlags::REPORT_EVENT_TYPES
137            )
138        )?;
139
140        let result = self.read_line_helper();
141
142        terminal::disable_raw_mode()?;
143        execute!(
144            stdout(),
145            DisableBracketedPaste,
146            PopKeyboardEnhancementFlags,
147            DisableFocusChange,
148            DisableMouseCapture
149        )?;
150
151        let default_cursor_style = SetCursorStyle::DefaultUserShape;
152        self.styled_editor_text
153            .set_cursor_style(default_cursor_style)?;
154        result
155    }
156
157    /// Set style for visual selection or NONE to clear it
158    pub fn set_visual_selection_style(&mut self, style: Option<Style>) {
159        self.selection_style = style;
160    }
161
162    /// Get the current Editor
163    pub fn editor(&mut self) -> &mut Editor {
164        &mut self.editor
165    }
166
167    /// Get the current Keybindings
168    pub fn keybinding(&mut self) -> &mut Keybindings {
169        &mut self.keybindings
170    }
171
172    /// Set the current InputFilter type
173    pub fn set_input_filter(&mut self, input_filter: InputFilter) {
174        self.input_filter = input_filter;
175    }
176
177    /// Add Auto pair, or clear it by passing None
178    pub fn set_auto_pair(&mut self, auto_pair: Option<Box<dyn AutoPair>>) {
179        self.auto_pair = auto_pair
180    }
181
182    /// Set the current cursor style
183    /// Or `None` to reset
184    pub fn set_cursor_style(&mut self, style: Option<SetCursorStyle>) {
185        self.cursor_style = style;
186    }
187
188    /// Get the current list of highlighters
189    pub fn highlighters(&mut self) -> &mut Vec<Box<dyn Highlighter>> {
190        &mut self.highlighters
191    }
192
193    /// Add new Syntax highlighter
194    pub fn add_highlighter(&mut self, highlighter: Box<dyn Highlighter>) {
195        self.highlighters.push(highlighter);
196    }
197
198    /// Clear current syntax highlighter
199    pub fn clear_highlighters(&mut self) {
200        self.highlighters.clear();
201    }
202
203    /// Get current hinters
204    pub fn hinters(&mut self) -> &mut Vec<Box<dyn Hinter>> {
205        &mut self.hinters
206    }
207
208    /// Add new Hinter
209    pub fn add_hinter(&mut self, hinter: Box<dyn Hinter>) {
210        self.hinters.push(hinter);
211    }
212
213    /// Clear current hinters
214    pub fn clear_hinters(&mut self) {
215        self.hinters.clear();
216    }
217
218    /// Set the current Auto completer
219    pub fn set_completer(&mut self, completer: Box<dyn Completer>) {
220        self.completer = Some(completer);
221    }
222
223    /// Clear current auto completer
224    pub fn clear_completer(&mut self) {
225        self.completer = None
226    }
227
228    /// Set the current Auto Complete View
229    pub fn set_auto_complete_view(&mut self, auto_complete_view: Box<dyn ListView<Suggestion>>) {
230        self.auto_complete_view = auto_complete_view;
231    }
232
233    /// Enable or Disable surround selection feature
234    pub fn enable_surround_selection(&mut self, enable: bool) {
235        self.enable_surround_selection = enable;
236    }
237
238    /// Helper implementing the logic for [`LineEditor::read_line()`] to be wrapped
239    /// in a `raw_mode` context.
240    fn read_line_helper(&mut self) -> Result<LineEditorResult> {
241        let mut lineeditor_events: Vec<LineEditorEvent> = vec![];
242
243        let prompt_buffer = self.prompt.prompt();
244        let prompt_len = prompt_buffer.len() as u16;
245
246        let row_start = position().unwrap().1;
247        self.styled_editor_text
248            .set_start_position((prompt_len, row_start));
249        self.styled_editor_text
250            .render_prompt_buffer(&prompt_buffer)?;
251
252        'main: loop {
253            loop {
254                match event::read()? {
255                    Event::Key(key_event) => match key_event.code {
256                        KeyCode::Char(ch) => {
257                            if (key_event.modifiers == KeyModifiers::NONE
258                                || key_event.modifiers == KeyModifiers::SHIFT)
259                                && key_event.kind == KeyEventKind::Press
260                            {
261                                if filter_input(ch, &self.input_filter) {
262                                    let commands = vec![EditCommand::InsertChar(ch)];
263                                    let edit_command = LineEditorEvent::Edit(commands);
264                                    lineeditor_events.push(edit_command);
265                                }
266                                break;
267                            }
268
269                            let key_combination = KeyCombination::from(key_event);
270                            if let Some(command) = self.keybindings.find_binding(key_combination) {
271                                lineeditor_events.push(command);
272                                break;
273                            }
274                        }
275                        _ => {
276                            let key_combination = KeyCombination::from(key_event);
277                            if let Some(command) = self.keybindings.find_binding(key_combination) {
278                                lineeditor_events.push(command);
279                                break;
280                            }
281                        }
282                    },
283                    Event::Paste(string) => {
284                        lineeditor_events.push(LineEditorEvent::Edit(vec![
285                            EditCommand::InsertString(string),
286                        ]));
287                        break;
288                    }
289                    _ => {}
290                }
291            }
292
293            // Track the buffer size at the start
294            let buffer_len_before = self.editor.styled_buffer().len();
295
296            // Apply the list of events
297            for event in lineeditor_events.drain(..) {
298                match self.handle_editor_event(&event)? {
299                    EventStatus::AutoCompleteHandled => {
300                        continue 'main;
301                    }
302                    EventStatus::Inapplicable => {
303                        continue 'main;
304                    }
305                    EventStatus::Exits(result) => return Ok(result),
306                    _ => {}
307                }
308            }
309
310            // Run the auto pair complete if one char is inserted
311            if buffer_len_before < self.editor.styled_buffer().len() {
312                // Auto pair complete
313                if let Some(auto_pair) = &self.auto_pair {
314                    auto_pair.complete_pair(self.editor.styled_buffer());
315                }
316            }
317
318            // Reset styled buffer styles
319            self.editor.styled_buffer().reset_styles();
320
321            // Apply all registered syntax highlighter in insertion order
322            for highlighter in &self.highlighters {
323                highlighter.highlight(self.editor.styled_buffer());
324            }
325
326            // Apply visual selection
327            self.apply_visual_selection();
328
329            // Render the current buffer with style
330            self.styled_editor_text
331                .render_line_buffer(self.editor.styled_buffer())?;
332
333            // If cursor is at the end of the buffer, check if hint is available
334            if self.editor.styled_buffer().position() == self.editor.styled_buffer().len() {
335                for hinter in &self.hinters {
336                    if let Some(hint) = hinter.hint(self.editor.styled_buffer()) {
337                        self.styled_editor_text.render_hint(&hint)?;
338                        break;
339                    }
340                }
341            }
342        }
343    }
344
345    /// Apply LineEditorEvent and return handling status
346    fn handle_editor_event(&mut self, event: &LineEditorEvent) -> Result<EventStatus> {
347        match event {
348            LineEditorEvent::Edit(commands) => {
349                for command in commands {
350                    if self.enable_surround_selection && self.selected_start != self.selected_end {
351                        if let EditCommand::InsertChar(c) = &command {
352                            for (key, value) in DEFAULT_PAIRS {
353                                if key == c {
354                                    self.apply_surround_selection(*key, *value);
355                                    return Ok(EventStatus::EditHandled);
356                                }
357                            }
358                        }
359                    }
360                    self.editor.run_edit_commands(command);
361                }
362                self.reset_selection_range();
363                Ok(EventStatus::EditHandled)
364            }
365            LineEditorEvent::Movement(commands) => {
366                for command in commands {
367                    self.editor.run_movement_commands(command);
368                }
369                self.reset_selection_range();
370                Ok(EventStatus::MovementHandled)
371            }
372            LineEditorEvent::Enter => {
373                if self.auto_complete_view.is_visible() {
374                    if let Some(suggestion) = self.auto_complete_view.selected_element() {
375                        let literal = &suggestion.content.literal();
376                        let span = &suggestion.span;
377
378                        let delete_command = EditCommand::DeleteSpan(span.start, span.end);
379                        self.editor.run_edit_commands(&delete_command);
380
381                        let insert_command = EditCommand::InsertString(literal.to_string());
382                        self.editor.run_edit_commands(&insert_command);
383
384                        self.auto_complete_view.clear()?;
385                        self.auto_complete_view.set_visibility(false);
386                        return Ok(EventStatus::SelectionHandled);
387                    }
388                }
389
390                let buffer = self.editor.styled_buffer().buffer().iter().collect();
391                self.reset_selection_range();
392
393                self.editor.styled_buffer().clear();
394
395                Ok(EventStatus::Exits(LineEditorResult::Success(buffer)))
396            }
397            LineEditorEvent::Up => {
398                if self.auto_complete_view.is_visible() {
399                    self.auto_complete_view.focus_previous();
400                    self.auto_complete_view.render()?;
401                    return Ok(EventStatus::AutoCompleteHandled);
402                }
403                Ok(EventStatus::Inapplicable)
404            }
405            LineEditorEvent::Down => {
406                if self.auto_complete_view.is_visible() {
407                    self.auto_complete_view.focus_next();
408                    self.auto_complete_view.clear()?;
409                    self.auto_complete_view.render()?;
410                    return Ok(EventStatus::AutoCompleteHandled);
411                }
412                Ok(EventStatus::Inapplicable)
413            }
414            LineEditorEvent::Left => {
415                self.editor
416                    .run_movement_commands(&MovementCommand::MoveLeftChar);
417                self.reset_selection_range();
418                Ok(EventStatus::MovementHandled)
419            }
420            LineEditorEvent::Right => {
421                self.editor
422                    .run_movement_commands(&MovementCommand::MoveRightChar);
423                self.reset_selection_range();
424                Ok(EventStatus::MovementHandled)
425            }
426            LineEditorEvent::Delete => {
427                if self.selected_start != self.selected_end {
428                    self.delete_selected_text();
429                } else {
430                    self.editor.run_edit_commands(&EditCommand::DeleteRightChar)
431                }
432                Ok(EventStatus::EditHandled)
433            }
434            LineEditorEvent::Backspace => {
435                if self.selected_start != self.selected_end {
436                    self.delete_selected_text();
437                } else {
438                    self.editor.run_edit_commands(&EditCommand::DeleteLeftChar)
439                }
440                Ok(EventStatus::EditHandled)
441            }
442            LineEditorEvent::SelectLeft => {
443                if self.selected_end < 1 {
444                    Ok(EventStatus::Inapplicable)
445                } else {
446                    self.selected_end -= 1;
447                    Ok(EventStatus::SelectionHandled)
448                }
449            }
450            LineEditorEvent::SelectRight => {
451                if self.selected_end as usize > self.editor.styled_buffer().len() {
452                    Ok(EventStatus::Inapplicable)
453                } else {
454                    self.selected_end += 1;
455                    Ok(EventStatus::SelectionHandled)
456                }
457            }
458            LineEditorEvent::SelectAll => {
459                self.selected_start = 0;
460                self.selected_end = self.editor.styled_buffer().len() as u16;
461                Ok(EventStatus::SelectionHandled)
462            }
463            LineEditorEvent::CutSelected => {
464                // TODO: Fix Cut
465                /*
466                if self.selected_start != self.selected_end {
467                    let from = usize::min(self.selected_start.into(), self.selected_end.into());
468                    let to = usize::max(self.selected_start.into(), self.selected_end.into());
469                    let styled_buffer = self.editor.styled_buffer();
470                    if let Some(selected_text) = styled_buffer.sub_string(from, to) {
471                        let mut clipboard_context: ClipboardContext =
472                            ClipboardProvider::new().unwrap();
473                        let _ = clipboard_context.set_contents(selected_text);
474
475                        styled_buffer.delete_range(from, to);
476                        self.reset_selection_range();
477                        return Ok(EventStatus::GeneralHandled);
478                    }
479                }
480                */
481                Ok(EventStatus::Inapplicable)
482            }
483            LineEditorEvent::CopySelected => {
484                // TODO: Fix Cope
485                /*
486                if self.selected_start != self.selected_end {
487                    let from = usize::min(self.selected_start.into(), self.selected_end.into());
488                    let to = usize::max(self.selected_start.into(), self.selected_end.into());
489                    let styled_buffer = self.editor.styled_buffer();
490                    if let Some(selected_text) = styled_buffer.sub_string(from, to) {
491                        let mut clipboard_context: ClipboardContext =
492                            ClipboardProvider::new().unwrap();
493                        let _ = clipboard_context.set_contents(selected_text);
494                        return Ok(EventStatus::GeneralHandled);
495                    }
496                }
497                 */
498                Ok(EventStatus::Inapplicable)
499            }
500            LineEditorEvent::Paste => {
501                // TODO: Fix paste
502                /*
503                let mut clipboard_context: ClipboardContext = ClipboardProvider::new().unwrap();
504                let clipboard_contents = clipboard_context.get_contents();
505                if let Ok(content) = clipboard_contents {
506                    if self.selected_start != self.selected_end {
507                        self.delete_selected_text();
508                    }
509
510                    self.editor
511                        .run_edit_commands(&EditCommand::InsertString(content));
512                    return Ok(EventStatus::GeneralHandled);
513                }
514                */
515                Ok(EventStatus::Inapplicable)
516            }
517            LineEditorEvent::ToggleAutoComplete => {
518                if self.auto_complete_view.is_visible() {
519                    self.auto_complete_view.clear()?;
520                    self.auto_complete_view.set_visibility(false);
521                    return Ok(EventStatus::Inapplicable);
522                }
523
524                if let Some(completer) = &self.completer {
525                    let mut suggestions = completer.complete(self.editor.styled_buffer());
526                    if !suggestions.is_empty() {
527                        let prompt_width = self.prompt.prompt().len() as u16;
528                        let (_, row) = position()?;
529
530                        let mut style = Style::default();
531                        style.set_background_color(crossterm::style::Color::Blue);
532                        self.auto_complete_view.set_focus_style(style);
533
534                        self.auto_complete_view.reset();
535                        self.auto_complete_view.set_elements(&mut suggestions);
536                        self.auto_complete_view.clear()?;
537                        self.auto_complete_view.render()?;
538                        self.auto_complete_view.set_visibility(true);
539
540                        let auto_complete_height = self.auto_complete_view.len();
541                        let (_, max_row) = terminal::size()?;
542
543                        if row + auto_complete_height as u16 > max_row {
544                            let new_start_row = max_row - 2 - self.auto_complete_view.len() as u16;
545                            self.styled_editor_text
546                                .set_start_position((prompt_width, new_start_row));
547                        }
548
549                        return Ok(EventStatus::AutoCompleteHandled);
550                    }
551
552                    return Ok(EventStatus::Inapplicable);
553                }
554
555                Ok(EventStatus::Inapplicable)
556            }
557            _ => Ok(EventStatus::Inapplicable),
558        }
559    }
560
561    /// Apply visual selection on the current styled buffer
562    fn apply_visual_selection(&mut self) {
563        if self.selected_start == self.selected_end {
564            return;
565        }
566
567        // Apply visual selection style if it not None
568        if let Some(style) = &self.selection_style {
569            let styled_buffer = self.editor.styled_buffer();
570            // Handle From and To, so we allow select from any direction
571            let from = usize::min(self.selected_start.into(), self.selected_end.into());
572            let to = usize::max(self.selected_start.into(), self.selected_end.into());
573            styled_buffer.style_range(from, to, style.clone());
574        }
575    }
576
577    /// Apply surround selection on the current styled buffer
578    fn apply_surround_selection(&mut self, start: char, end: char) {
579        let from = usize::min(self.selected_start.into(), self.selected_end.into());
580        let to = usize::max(self.selected_start.into(), self.selected_end.into());
581
582        let editor = self.editor.styled_buffer();
583        editor.set_position(from);
584        editor.insert_char(start);
585        editor.set_position(to + 1);
586        editor.insert_char(end);
587        editor.set_position(from);
588    }
589
590    /// Delete the current selected text
591    fn delete_selected_text(&mut self) {
592        if self.selected_start == self.selected_end {
593            return;
594        }
595
596        let from = usize::min(self.selected_start.into(), self.selected_end.into());
597        let to = usize::max(self.selected_start.into(), self.selected_end.into());
598        let delete_selection = EditCommand::DeleteSpan(from, to);
599        self.editor.run_edit_commands(&delete_selection);
600        self.editor.styled_buffer().set_position(from);
601        self.reset_selection_range();
602    }
603
604    /// Reset selection start and end to be the current cursor position
605    fn reset_selection_range(&mut self) {
606        let position = self.editor.styled_buffer().position() as u16;
607        self.selected_start = position;
608        self.selected_end = position;
609    }
610}