use std::io::stdout;
use std::io::Result;
use crossterm::cursor::position;
use crossterm::cursor::SetCursorStyle;
use crossterm::event;
use crossterm::event::DisableBracketedPaste;
use crossterm::event::DisableFocusChange;
use crossterm::event::DisableMouseCapture;
use crossterm::event::EnableBracketedPaste;
use crossterm::event::EnableFocusChange;
use crossterm::event::EnableMouseCapture;
use crossterm::event::Event;
use crossterm::event::KeyCode;
use crossterm::event::KeyEventKind;
use crossterm::event::KeyModifiers;
use crossterm::event::KeyboardEnhancementFlags;
use crossterm::event::PopKeyboardEnhancementFlags;
use crossterm::event::PushKeyboardEnhancementFlags;
use crossterm::execute;
use crossterm::terminal;
use crate::completion::Suggestion;
use crate::editor::Editor;
use crate::event::EditCommand;
use crate::event::LineEditorEvent;
use crate::event::MovementCommand;
use crate::input_filter::filter_input;
use crate::input_filter::InputFilter;
use crate::keybindings::KeyCombination;
use crate::keybindings::Keybindings;
use crate::style::Style;
use crate::styled_editor_view::StyledEditorView;
use crate::AutoPair;
use crate::Completer;
use crate::DropDownListView;
use crate::Highlighter;
use crate::Hinter;
use crate::ListView;
use crate::Prompt;
use crate::DEFAULT_PAIRS;
#[derive(Debug)]
pub enum LineEditorResult {
Success(String),
Interrupted,
EndTerminalSession,
}
enum EventStatus {
#[allow(dead_code)]
GeneralHandled,
EditHandled,
MovementHandled,
SelectionHandled,
AutoCompleteHandled,
Inapplicable,
Exits(LineEditorResult),
}
pub struct LineEditor {
prompt: Box<dyn Prompt>,
editor: Editor,
input_filter: InputFilter,
styled_editor_text: StyledEditorView,
keybindings: Keybindings,
auto_pair: Option<Box<dyn AutoPair>>,
highlighters: Vec<Box<dyn Highlighter>>,
hinters: Vec<Box<dyn Hinter>>,
completer: Option<Box<dyn Completer>>,
auto_complete_view: Box<dyn ListView<Suggestion>>,
cursor_style: Option<SetCursorStyle>,
selection_style: Option<Style>,
selected_start: u16,
selected_end: u16,
enable_surround_selection: bool,
}
impl LineEditor {
#[must_use]
pub fn new(prompt: Box<dyn Prompt>) -> Self {
LineEditor {
prompt,
editor: Editor::default(),
input_filter: InputFilter::Text,
styled_editor_text: StyledEditorView::default(),
keybindings: Keybindings::default(),
auto_pair: None,
highlighters: vec![],
hinters: vec![],
completer: None,
auto_complete_view: Box::<DropDownListView>::default(),
cursor_style: None,
selection_style: None,
selected_start: 0,
selected_end: 0,
enable_surround_selection: false,
}
}
pub fn read_line(&mut self) -> Result<LineEditorResult> {
if let Some(cursor_style) = self.cursor_style {
self.styled_editor_text.set_cursor_style(cursor_style)?;
}
terminal::enable_raw_mode()?;
execute!(
stdout(),
EnableBracketedPaste,
EnableFocusChange,
EnableMouseCapture,
PushKeyboardEnhancementFlags(
KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
| KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES
| KeyboardEnhancementFlags::REPORT_EVENT_TYPES
)
)?;
let result = self.read_line_helper();
terminal::disable_raw_mode()?;
execute!(
stdout(),
DisableBracketedPaste,
PopKeyboardEnhancementFlags,
DisableFocusChange,
DisableMouseCapture
)?;
let default_cursor_style = SetCursorStyle::DefaultUserShape;
self.styled_editor_text
.set_cursor_style(default_cursor_style)?;
result
}
pub fn set_visual_selection_style(&mut self, style: Option<Style>) {
self.selection_style = style;
}
pub fn editor(&mut self) -> &mut Editor {
&mut self.editor
}
pub fn keybinding(&mut self) -> &mut Keybindings {
&mut self.keybindings
}
pub fn set_input_filter(&mut self, input_filter: InputFilter) {
self.input_filter = input_filter;
}
pub fn set_auto_pair(&mut self, auto_pair: Option<Box<dyn AutoPair>>) {
self.auto_pair = auto_pair
}
pub fn set_cursor_style(&mut self, style: Option<SetCursorStyle>) {
self.cursor_style = style;
}
pub fn highlighters(&mut self) -> &mut Vec<Box<dyn Highlighter>> {
&mut self.highlighters
}
pub fn add_highlighter(&mut self, highlighter: Box<dyn Highlighter>) {
self.highlighters.push(highlighter);
}
pub fn clear_highlighters(&mut self) {
self.highlighters.clear();
}
pub fn hinters(&mut self) -> &mut Vec<Box<dyn Hinter>> {
&mut self.hinters
}
pub fn add_hinter(&mut self, hinter: Box<dyn Hinter>) {
self.hinters.push(hinter);
}
pub fn clear_hinters(&mut self) {
self.hinters.clear();
}
pub fn set_completer(&mut self, completer: Box<dyn Completer>) {
self.completer = Some(completer);
}
pub fn clear_completer(&mut self) {
self.completer = None
}
pub fn set_auto_complete_view(&mut self, auto_complete_view: Box<dyn ListView<Suggestion>>) {
self.auto_complete_view = auto_complete_view;
}
pub fn enable_surround_selection(&mut self, enable: bool) {
self.enable_surround_selection = enable;
}
fn read_line_helper(&mut self) -> Result<LineEditorResult> {
let mut lineeditor_events: Vec<LineEditorEvent> = vec![];
let prompt_buffer = self.prompt.prompt();
let prompt_len = prompt_buffer.len() as u16;
let row_start = position().unwrap().1;
self.styled_editor_text
.set_start_position((prompt_len, row_start));
self.styled_editor_text
.render_prompt_buffer(&prompt_buffer)?;
'main: loop {
loop {
match event::read()? {
Event::Key(key_event) => match key_event.code {
KeyCode::Char(ch) => {
if (key_event.modifiers == KeyModifiers::NONE
|| key_event.modifiers == KeyModifiers::SHIFT)
&& key_event.kind == KeyEventKind::Press
{
if filter_input(ch, &self.input_filter) {
let commands = vec![EditCommand::InsertChar(ch)];
let edit_command = LineEditorEvent::Edit(commands);
lineeditor_events.push(edit_command);
}
break;
}
let key_combination = KeyCombination::from(key_event);
if let Some(command) = self.keybindings.find_binding(key_combination) {
lineeditor_events.push(command);
break;
}
}
_ => {
let key_combination = KeyCombination::from(key_event);
if let Some(command) = self.keybindings.find_binding(key_combination) {
lineeditor_events.push(command);
break;
}
}
},
Event::Paste(string) => {
lineeditor_events.push(LineEditorEvent::Edit(vec![
EditCommand::InsertString(string),
]));
break;
}
_ => {}
}
}
let buffer_len_before = self.editor.styled_buffer().len();
for event in lineeditor_events.drain(..) {
match self.handle_editor_event(&event)? {
EventStatus::AutoCompleteHandled => {
continue 'main;
}
EventStatus::Inapplicable => {
continue 'main;
}
EventStatus::Exits(result) => return Ok(result),
_ => {}
}
}
if buffer_len_before < self.editor.styled_buffer().len() {
if let Some(auto_pair) = &self.auto_pair {
auto_pair.complete_pair(self.editor.styled_buffer());
}
}
self.editor.styled_buffer().reset_styles();
for highlighter in &self.highlighters {
highlighter.highlight(self.editor.styled_buffer());
}
self.apply_visual_selection();
self.styled_editor_text
.render_line_buffer(self.editor.styled_buffer())?;
if self.editor.styled_buffer().position() == self.editor.styled_buffer().len() {
for hinter in &self.hinters {
if let Some(hint) = hinter.hint(self.editor.styled_buffer()) {
self.styled_editor_text.render_hint(&hint)?;
break;
}
}
}
}
}
fn handle_editor_event(&mut self, event: &LineEditorEvent) -> Result<EventStatus> {
match event {
LineEditorEvent::Edit(commands) => {
for command in commands {
if self.enable_surround_selection && self.selected_start != self.selected_end {
if let EditCommand::InsertChar(c) = &command {
for (key, value) in DEFAULT_PAIRS {
if key == c {
self.apply_surround_selection(*key, *value);
return Ok(EventStatus::EditHandled);
}
}
}
}
self.editor.run_edit_commands(command);
}
self.reset_selection_range();
Ok(EventStatus::EditHandled)
}
LineEditorEvent::Movement(commands) => {
for command in commands {
self.editor.run_movement_commands(command);
}
self.reset_selection_range();
Ok(EventStatus::MovementHandled)
}
LineEditorEvent::Enter => {
if self.auto_complete_view.is_visible() {
if let Some(suggestion) = self.auto_complete_view.selected_element() {
let literal = &suggestion.content.literal();
let span = &suggestion.span;
let delete_command = EditCommand::DeleteSpan(span.start, span.end);
self.editor.run_edit_commands(&delete_command);
let insert_command = EditCommand::InsertString(literal.to_string());
self.editor.run_edit_commands(&insert_command);
self.auto_complete_view.clear()?;
self.auto_complete_view.set_visibility(false);
return Ok(EventStatus::SelectionHandled);
}
}
let buffer = self.editor.styled_buffer().buffer().iter().collect();
self.reset_selection_range();
self.editor.styled_buffer().clear();
Ok(EventStatus::Exits(LineEditorResult::Success(buffer)))
}
LineEditorEvent::Up => {
if self.auto_complete_view.is_visible() {
self.auto_complete_view.focus_previous();
self.auto_complete_view.render()?;
return Ok(EventStatus::AutoCompleteHandled);
}
Ok(EventStatus::Inapplicable)
}
LineEditorEvent::Down => {
if self.auto_complete_view.is_visible() {
self.auto_complete_view.focus_next();
self.auto_complete_view.clear()?;
self.auto_complete_view.render()?;
return Ok(EventStatus::AutoCompleteHandled);
}
Ok(EventStatus::Inapplicable)
}
LineEditorEvent::Left => {
self.editor
.run_movement_commands(&MovementCommand::MoveLeftChar);
self.reset_selection_range();
Ok(EventStatus::MovementHandled)
}
LineEditorEvent::Right => {
self.editor
.run_movement_commands(&MovementCommand::MoveRightChar);
self.reset_selection_range();
Ok(EventStatus::MovementHandled)
}
LineEditorEvent::Delete => {
if self.selected_start != self.selected_end {
self.delete_selected_text();
} else {
self.editor.run_edit_commands(&EditCommand::DeleteRightChar)
}
Ok(EventStatus::EditHandled)
}
LineEditorEvent::Backspace => {
if self.selected_start != self.selected_end {
self.delete_selected_text();
} else {
self.editor.run_edit_commands(&EditCommand::DeleteLeftChar)
}
Ok(EventStatus::EditHandled)
}
LineEditorEvent::SelectLeft => {
if self.selected_end < 1 {
Ok(EventStatus::Inapplicable)
} else {
self.selected_end -= 1;
Ok(EventStatus::SelectionHandled)
}
}
LineEditorEvent::SelectRight => {
if self.selected_end as usize > self.editor.styled_buffer().len() {
Ok(EventStatus::Inapplicable)
} else {
self.selected_end += 1;
Ok(EventStatus::SelectionHandled)
}
}
LineEditorEvent::SelectAll => {
self.selected_start = 0;
self.selected_end = self.editor.styled_buffer().len() as u16;
Ok(EventStatus::SelectionHandled)
}
LineEditorEvent::CutSelected => {
Ok(EventStatus::Inapplicable)
}
LineEditorEvent::CopySelected => {
Ok(EventStatus::Inapplicable)
}
LineEditorEvent::Paste => {
Ok(EventStatus::Inapplicable)
}
LineEditorEvent::ToggleAutoComplete => {
if self.auto_complete_view.is_visible() {
self.auto_complete_view.clear()?;
self.auto_complete_view.set_visibility(false);
return Ok(EventStatus::Inapplicable);
}
if let Some(completer) = &self.completer {
let mut suggestions = completer.complete(self.editor.styled_buffer());
if !suggestions.is_empty() {
let prompt_width = self.prompt.prompt().len() as u16;
let (_, row) = position()?;
let mut style = Style::default();
style.set_background_color(crossterm::style::Color::Blue);
self.auto_complete_view.set_focus_style(style);
self.auto_complete_view.reset();
self.auto_complete_view.set_elements(&mut suggestions);
self.auto_complete_view.clear()?;
self.auto_complete_view.render()?;
self.auto_complete_view.set_visibility(true);
let auto_complete_height = self.auto_complete_view.len();
let (_, max_row) = terminal::size()?;
if row + auto_complete_height as u16 > max_row {
let new_start_row = max_row - 2 - self.auto_complete_view.len() as u16;
self.styled_editor_text
.set_start_position((prompt_width, new_start_row));
}
return Ok(EventStatus::AutoCompleteHandled);
}
return Ok(EventStatus::Inapplicable);
}
Ok(EventStatus::Inapplicable)
}
_ => Ok(EventStatus::Inapplicable),
}
}
fn apply_visual_selection(&mut self) {
if self.selected_start == self.selected_end {
return;
}
if let Some(style) = &self.selection_style {
let styled_buffer = self.editor.styled_buffer();
let from = usize::min(self.selected_start.into(), self.selected_end.into());
let to = usize::max(self.selected_start.into(), self.selected_end.into());
styled_buffer.style_range(from, to, style.clone());
}
}
fn apply_surround_selection(&mut self, start: char, end: char) {
let from = usize::min(self.selected_start.into(), self.selected_end.into());
let to = usize::max(self.selected_start.into(), self.selected_end.into());
let editor = self.editor.styled_buffer();
editor.set_position(from);
editor.insert_char(start);
editor.set_position(to + 1);
editor.insert_char(end);
editor.set_position(from);
}
fn delete_selected_text(&mut self) {
if self.selected_start == self.selected_end {
return;
}
let from = usize::min(self.selected_start.into(), self.selected_end.into());
let to = usize::max(self.selected_start.into(), self.selected_end.into());
let delete_selection = EditCommand::DeleteSpan(from, to);
self.editor.run_edit_commands(&delete_selection);
self.editor.styled_buffer().set_position(from);
self.reset_selection_range();
}
fn reset_selection_range(&mut self) {
let position = self.editor.styled_buffer().position() as u16;
self.selected_start = position;
self.selected_end = position;
}
}