Add Submit and SubmitOrNewline editor events (#490)

* `Submit` is used to return data unconditionally. This is useful for
  example if you want compiler to show you where the unclosed paren that
  doesn't pass validation actually is.
* `SubmitOrNewline` is similar to `Enter` but inserts a new line if the
  cursor is not at the end of the buffer. It's useful to insert newlines
  in the middle of the text when editing.

This also removes unconditional `KeyCode::Enter` to
`ReedlineEvent::Enter` event mapping and sets it in the keybindings
normally (so you can rebind to `Submit` or `SubmitOrNewline`)
This commit is contained in:
Paul Colomiets 2022-10-16 23:24:08 +03:00 committed by GitHub
parent 835d2e9f25
commit f949f560f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 18 deletions

View File

@ -22,6 +22,9 @@ pub fn default_emacs_keybindings() -> Keybindings {
add_common_navigation_bindings(&mut kb);
add_common_edit_bindings(&mut kb);
// This could be in common, but in Vi it also changes the mode
kb.add_binding(KM::NONE, KC::Enter, ReedlineEvent::Enter);
// *** CTRL ***
// Moves
kb.add_binding(
@ -141,7 +144,6 @@ impl EditMode for Emacs {
.unwrap_or(ReedlineEvent::None)
}
}
(KeyModifiers::NONE, KeyCode::Enter) => ReedlineEvent::Enter,
_ => self
.keybindings
.find_binding(modifiers, code)

View File

@ -629,7 +629,10 @@ impl Reedline {
self.painter.clear_scrollback()?;
Ok(EventStatus::Handled)
}
ReedlineEvent::Enter | ReedlineEvent::HistoryHintComplete => {
ReedlineEvent::Enter
| ReedlineEvent::HistoryHintComplete
| ReedlineEvent::Submit
| ReedlineEvent::SubmitOrNewline => {
if let Some(string) = self.history_cursor.string_at_cursor() {
self.editor
.set_buffer(string, UndoBehavior::CreateUndoPoint);
@ -848,7 +851,9 @@ impl Reedline {
self.painter.clear_scrollback()?;
Ok(EventStatus::Handled)
}
ReedlineEvent::Enter => {
ReedlineEvent::Enter | ReedlineEvent::Submit | ReedlineEvent::SubmitOrNewline
if self.menus.iter().any(|menu| menu.is_active()) =>
{
for menu in self.menus.iter_mut() {
if menu.is_active() {
menu.replace_in_buffer(&mut self.editor);
@ -857,7 +862,9 @@ impl Reedline {
return Ok(EventStatus::Handled);
}
}
unreachable!()
}
ReedlineEvent::Enter => {
#[cfg(feature = "bashisms")]
if let Some(event) = self.parse_bang_command() {
return self.handle_editor_event(prompt, event);
@ -865,22 +872,34 @@ impl Reedline {
let buffer = self.editor.get_buffer().to_string();
match self.validator.as_mut().map(|v| v.validate(&buffer)) {
None | Some(ValidationResult::Complete) => {
self.hide_hints = true;
// Additional repaint to show the content without hints etc.
self.repaint(prompt)?;
let buf = self.editor.get_buffer();
if !buf.is_empty() {
let mut entry = HistoryItem::from_command_line(buf);
entry.session_id = self.history_session_id;
let entry = self.history.save(entry).expect("todo: error handling");
self.history_last_run_id = entry.id;
}
self.run_edit_commands(&[EditCommand::Clear]);
self.editor.reset_undo_stack();
None | Some(ValidationResult::Complete) => Ok(self.submit_buffer(prompt)?),
Some(ValidationResult::Incomplete) => {
self.run_edit_commands(&[EditCommand::InsertNewline]);
Ok(EventStatus::Exits(Signal::Success(buffer)))
Ok(EventStatus::Handled)
}
}
}
ReedlineEvent::Submit => {
#[cfg(feature = "bashisms")]
if let Some(event) = self.parse_bang_command() {
return self.handle_editor_event(prompt, event);
}
Ok(self.submit_buffer(prompt)?)
}
ReedlineEvent::SubmitOrNewline => {
#[cfg(feature = "bashisms")]
if let Some(event) = self.parse_bang_command() {
return self.handle_editor_event(prompt, event);
}
let cursor_position_in_buffer = self.editor.insertion_point();
let buffer = self.editor.get_buffer().to_string();
if cursor_position_in_buffer < buffer.len() {
self.run_edit_commands(&[EditCommand::InsertNewline]);
return Ok(EventStatus::Handled);
}
match self.validator.as_mut().map(|v| v.validate(&buffer)) {
None | Some(ValidationResult::Complete) => Ok(self.submit_buffer(prompt)?),
Some(ValidationResult::Incomplete) => {
self.run_edit_commands(&[EditCommand::InsertNewline]);
@ -1449,6 +1468,23 @@ impl Reedline {
}
Ok(messages)
}
fn submit_buffer(&mut self, prompt: &dyn Prompt) -> io::Result<EventStatus> {
let buffer = self.editor.get_buffer().to_string();
self.hide_hints = true;
// Additional repaint to show the content without hints etc.
self.repaint(prompt)?;
if !buffer.is_empty() {
let mut entry = HistoryItem::from_command_line(&buffer);
entry.session_id = self.history_session_id;
let entry = self.history.save(entry).expect("todo: error handling");
self.history_last_run_id = entry.id;
}
self.run_edit_commands(&[EditCommand::Clear]);
self.editor.reset_undo_stack();
Ok(EventStatus::Exits(Signal::Success(buffer)))
}
}
#[test]

View File

@ -422,6 +422,12 @@ pub enum ReedlineEvent {
/// Handle enter event
Enter,
/// Handle unconditional submit event
Submit,
/// Submit at the end of the *complete* text, otherwise newline
SubmitOrNewline,
/// Esc event
Esc,
@ -510,6 +516,8 @@ impl Display for ReedlineEvent {
ReedlineEvent::ClearScreen => write!(f, "ClearScreen"),
ReedlineEvent::ClearScrollback => write!(f, "ClearScrollback"),
ReedlineEvent::Enter => write!(f, "Enter"),
ReedlineEvent::Submit => write!(f, "Submit"),
ReedlineEvent::SubmitOrNewline => write!(f, "SubmitOrNewline"),
ReedlineEvent::Esc => write!(f, "Esc"),
ReedlineEvent::Mouse => write!(f, "Mouse"),
ReedlineEvent::Resize(_, _) => write!(f, "Resize <int> <int>"),