diff --git a/crates/engine/src/lisp.rs b/crates/engine/src/lisp.rs index 977d4f551..b1ab4c384 100644 --- a/crates/engine/src/lisp.rs +++ b/crates/engine/src/lisp.rs @@ -415,9 +415,12 @@ impl Session { /// Move printed text to the command history: complete lines, and with `all` the rest too. fn lisp_flush(&mut self, env: &mut Env, all: bool) { - while let Some(i) = env.out.find('\n') { - let line: String = env.out.drain(..=i).collect(); - let line = line.trim_end_matches(['\n', '\r']).to_string(); + // One pass over the buffer, dropping the printed lines once at the end: draining each line + // from the front shifted the rest every time, quadratic in newline-dense output. + let mut done = 0; + while let Some(i) = env.out.get(done..).and_then(|rest| rest.find('\n')) { + let line = env.out.get(done..done + i).unwrap_or_default().trim_end_matches('\r').to_string(); + done += i + 1; // `(princ "\nText")` after a prompt or a command's echo ends that line instead of // leaving an empty one; after LISP's own line it does print an empty line. if line.is_empty() && env.printed_at != Some(self.log.len()) { @@ -427,6 +430,7 @@ impl Session { self.echo(line); env.printed_at = Some(self.log.len()); } + env.out.drain(..done); if all && !env.out.is_empty() { let line = std::mem::take(&mut env.out); self.echo(line); diff --git a/crates/engine/src/lisp/tests.rs b/crates/engine/src/lisp/tests.rs index 0ed4b22fd..0feba7534 100644 --- a/crates/engine/src/lisp/tests.rs +++ b/crates/engine/src/lisp/tests.rs @@ -307,6 +307,16 @@ fn printing() { assert_eq!(ok(&mut s, "(rtos 12.5 2 2)"), "\"12.50\""); } +/// Output with many lines moves to the history in one pass, not one buffer shift per line. +#[test] +fn printing_many_lines_is_linear() { + let mut s = session(); + let started = std::time::Instant::now(); + ok(&mut s, "(setq x \"a\\r\\n\") (repeat 17 (setq x (strcat x x))) (princ x) (princ)"); + assert!(started.elapsed() < std::time::Duration::from_secs(10), "took {:?}", started.elapsed()); + assert_eq!(s.log.last().map(String::as_str), Some("a")); +} + #[test] fn command_draws_and_one_routine_is_one_undo_step() { let mut s = session();