diff --git a/crates/engine/src/lisp/input.rs b/crates/engine/src/lisp/input.rs index fca32e5b8..d12dcbbce 100644 --- a/crates/engine/src/lisp/input.rs +++ b/crates/engine/src/lisp/input.rs @@ -67,6 +67,12 @@ impl GetRequest { let mut p = Prompt::new(message, accept).base_opt(base); p.keywords = self.initget.keywords.clone(); p.raw = true; + // `entsel`/`nentsel` pick one object. The routine writes the message, so the prompt says + // so itself rather than being read for the word "Select" -- with a message of its own + // ("\nSelect the second line: ", or no mention of selecting at all) the pick box went + // missing, while the same call without one got it from the default "Select object: " + // (#1598). + p.picks_one = matches!(self.kind, GetKind::EntSel); p } diff --git a/crates/engine/src/lisp/tests.rs b/crates/engine/src/lisp/tests.rs index e8eb135b9..04d78f893 100644 --- a/crates/engine/src/lisp/tests.rs +++ b/crates/engine/src/lisp/tests.rs @@ -485,6 +485,37 @@ fn hostile_lisp_never_panics() { assert!(!s.lisp_running()); } +/// `entsel` and `nentsel` pick one object, so the cursor shows the pick box at their prompt. It +/// was read off the message's first word, and a routine's own message begins with a newline -- so +/// the same call showed the pick box without a message and lost it with one (#1598). +#[test] +fn an_entsel_prompt_picks_objects_whatever_its_message_says() { + let mut s = session(); + // No message: the default "Select object: ". + s.cmdline("(nentsel)").unwrap(); + assert!(s.current_prompt().unwrap().picks_objects(), "{}", s.prompt_text()); + s.cancel(); + + // The reported case: the routine's own message, starting with a newline. + s.cmdline("(nentsel \"\nSelect second line or polyline: \")").unwrap(); + assert!(s.current_prompt().unwrap().picks_objects(), "{}", s.prompt_text()); + s.cancel(); + + // Wording that never says "select" picks an object just the same. + s.cmdline("(entsel \"\nPick a line: \")").unwrap(); + assert!(s.current_prompt().unwrap().picks_objects(), "{}", s.prompt_text()); + s.cancel(); + + // A point prompt is not a pick, and keeps its point modifiers -- including one whose own + // message happens to begin with the word the built-in commands are read by. `picks_one` says + // which calls pick, so the wording is only ever read where the engine wrote it (#1635 review). + for point in ["(getpoint \"\nSpecify a point: \")", "(getpoint \"\nSelect a point: \")", "(getpoint \"Select a point: \")"] { + s.cmdline(point).unwrap(); + assert!(!s.current_prompt().unwrap().picks_objects(), "{point}: {}", s.prompt_text()); + s.cancel(); + } +} + #[test] fn a_waiting_routine_is_a_prompt_with_a_rubber_band() { let mut s = session(); diff --git a/crates/engine/src/prompt.rs b/crates/engine/src/prompt.rs index a41bcb705..b9c45eae7 100644 --- a/crates/engine/src/prompt.rs +++ b/crates/engine/src/prompt.rs @@ -62,6 +62,10 @@ pub struct Prompt { /// pointer boxes. #[serde(skip_serializing_if = "std::ops::Not::not")] pub distance: bool, + /// The prompt picks one object whatever it says: an AutoLISP `entsel`/`nentsel`, whose message + /// the routine writes, so it cannot be read for the word "Select" (#1598). + #[serde(skip_serializing_if = "std::ops::Not::not")] + pub picks_one: bool, } /// Parse `@distance bool { - self.accept.select || (self.accept.point && self.message.starts_with("Select")) + self.picks_one || self.accept.select || (self.accept.point && !self.raw && self.message.starts_with("Select")) } /// "Specify next point or [Undo/Close] :". pub fn display(&self) -> String {