Skip to content

Commit

Permalink
Rework read_line_helper()
Browse files Browse the repository at this point in the history
  • Loading branch information
YizhePKU committed Oct 17, 2024
1 parent 5dd7e0e commit faddde8
Showing 1 changed file with 62 additions and 72 deletions.
134 changes: 62 additions & 72 deletions src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,10 @@ use {
// a POLL_WAIT of zero means that every single event is treated as soon as it
// arrives. This doesn't allow for the possibility of more than 1 event
// happening at the same time.
const POLL_WAIT: u64 = 10;
// Since a paste event is multiple Event::Key events happening at the same time, we specify
// how many events should be in the crossterm_events vector before it is considered
// a paste. 10 events in 10 milliseconds is conservative enough (unlikely somebody
// will type more than 10 characters in 10 milliseconds)
const POLL_WAIT: Duration = Duration::from_millis(100);
// Since a paste event is multiple `Event::Key` events happening at the same
// time, we specify how many events should be in the `crossterm_events` vector
// before it is considered a paste. 10 events is conservative enough.
const EVENTS_THRESHOLD: usize = 10;

/// Determines if inputs should be used to extend the regular line buffer,
Expand Down Expand Up @@ -695,12 +694,7 @@ impl Reedline {

self.repaint(prompt)?;

let mut crossterm_events: Vec<ReedlineRawEvent> = vec![];
let mut reedline_events: Vec<ReedlineEvent> = vec![];

loop {
let mut paste_enter_state = false;

#[cfg(feature = "external_printer")]
if let Some(ref external_printer) = self.external_printer {
// get messages from printer as crlf separated "lines"
Expand All @@ -716,76 +710,74 @@ impl Reedline {
}
}

let mut latest_resize = None;
loop {
// There could be multiple events queued up!
// pasting text, resizes, blocking this thread (e.g. during debugging)
// We should be able to handle all of them as quickly as possible without causing unnecessary output steps.
if !event::poll(Duration::from_millis(POLL_WAIT))? {
break;
}

match event::read()? {
Event::Resize(x, y) => {
latest_resize = Some((x, y));
}
enter @ Event::Key(KeyEvent {
code: KeyCode::Enter,
modifiers: KeyModifiers::NONE,
..
}) => {
let enter = ReedlineRawEvent::convert_from(enter);
if let Some(enter) = enter {
crossterm_events.push(enter);
// Break early to check if the input is complete and
// can be send to the hosting application. If
// multiple complete entries are submitted, events
// are still in the crossterm queue for us to
// process.
paste_enter_state = crossterm_events.len() > EVENTS_THRESHOLD;
break;
}
}
x => {
let raw_event = ReedlineRawEvent::convert_from(x);
if let Some(evt) = raw_event {
crossterm_events.push(evt);
}
}
// Helper function that returns true if the input is complete and
// can be sent to the hosting application.
fn completed(events: &[Event]) -> bool {
if let Some(event) = events.last() {
matches!(
event,
Event::Key(KeyEvent {
code: KeyCode::Enter,
modifiers: KeyModifiers::NONE,
..
})
)
} else {
false
}
}

if let Some((x, y)) = latest_resize {
reedline_events.push(ReedlineEvent::Resize(x, y));
let mut events: Vec<Event> = vec![];

// Block until we receive an event.
events.push(crossterm::event::read()?);

// Receive all events in the queue without blocking. Will stop when
// a line of input is completed.
while !completed(&events) && event::poll(Duration::from_millis(0))? {
events.push(crossterm::event::read()?);
}

// Accelerate pasted text by fusing `EditCommand`s
//
// (Text should only be `EditCommand::InsertChar`s)
let mut last_edit_commands = None;
for event in crossterm_events.drain(..) {
match (&mut last_edit_commands, self.edit_mode.parse_event(event)) {
(None, ReedlineEvent::Edit(ec)) => {
last_edit_commands = Some(ec);
}
(None, other_event) => {
reedline_events.push(other_event);
}
(Some(ref mut last_ecs), ReedlineEvent::Edit(ec)) => {
last_ecs.extend(ec);
}
(ref mut a @ Some(_), other_event) => {
reedline_events.push(ReedlineEvent::Edit(a.take().unwrap()));
// If we believe there's text pasting or resizing going on, batch
// more events at the cost of a slight delay.
if events.len() > EVENTS_THRESHOLD
|| events.iter().any(|e| matches!(e, Event::Resize(_, _)))
{
while !completed(&events) && event::poll(POLL_WAIT)? {
events.push(crossterm::event::read()?);
}
}

reedline_events.push(other_event);
// Convert `Event` into `ReedlineEvent`. Also, fuse consecutive
// `ReedlineEvent::EditCommand` into one. Also, if there're multiple
// `ReedlineEvent::Resize`, only keep the last one.
let mut reedline_events: Vec<ReedlineEvent> = vec![];
let mut edits = vec![];
let mut resize = None;
for event in events {
if let Some(event) = ReedlineRawEvent::convert_from(event) {
match self.edit_mode.parse_event(event) {
ReedlineEvent::Edit(edit) => edits.extend(edit),
ReedlineEvent::Resize(x, y) => resize = Some((x, y)),
event => {
if !edits.is_empty() {
reedline_events
.push(ReedlineEvent::Edit(std::mem::take(&mut edits)));
}
reedline_events.push(event);
}
}
}
}
if let Some(ec) = last_edit_commands {
reedline_events.push(ReedlineEvent::Edit(ec));
if !edits.is_empty() {
reedline_events.push(ReedlineEvent::Edit(edits));
}
if let Some((x, y)) = resize {
reedline_events.push(ReedlineEvent::Resize(x, y));
}

for event in reedline_events.drain(..) {
// Handle reedline events.
for event in reedline_events {
match self.handle_event(prompt, event)? {
EventStatus::Exits(signal) => {
// Check if we are merely suspended (to process an ExecuteHostCommand event)
Expand All @@ -798,9 +790,7 @@ impl Reedline {
return Ok(signal);
}
EventStatus::Handled => {
if !paste_enter_state {
self.repaint(prompt)?;
}
self.repaint(prompt)?;
}
EventStatus::Inapplicable => {
// Nothing changed, no need to repaint
Expand Down

0 comments on commit faddde8

Please sign in to comment.