fix(command-line): prevent duplicate paste in command line and text fields

This commit is contained in:
yztink committed 2026-09-24 23:37:32 -07:00
1 parent 631c0c97aa
commit fa1b4db490
3 files changed
+91 -4

No files matched your search

+2
View File
@@ -2096,6 +2096,8 @@ pub enum Message {
/// Ctrl+V. Routed by `update` into an open text editor, the drawing-object
/// clipboard, or the system text clipboard.
PasteShortcut,
/// Result of probing whether a text input was focused when `PasteShortcut` arrived.
PasteShortcutResolved(bool),
/// Completion of a system text clipboard read requested by PASTECLIP.
SystemClipboardPaste(SystemClipboardText),
/// Ctrl/Cmd+A — select all layer rows when the Layer Manager is open, or all
+54 -4
View File
@@ -70,6 +70,48 @@ fn polyline_vertex_count(entity: &AcadEntityType) -> Option<usize> {
}
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Default)]
struct CheckTextInputFocused {
last_was_text_input: bool,
is_focused: bool,
}
#[cfg(not(target_arch = "wasm32"))]
impl iced::advanced::widget::Operation<bool> for CheckTextInputFocused {
fn text_input(
&mut self,
_id: Option<&iced::advanced::widget::Id>,
_bounds: iced::Rectangle,
_state: &mut dyn iced::advanced::widget::operation::TextInput,
) {
self.last_was_text_input = true;
}
fn focusable(
&mut self,
_id: Option<&iced::advanced::widget::Id>,
_bounds: iced::Rectangle,
state: &mut dyn iced::advanced::widget::operation::Focusable,
) {
let is_text = std::mem::replace(&mut self.last_was_text_input, false);
if is_text && state.is_focused() {
self.is_focused = true;
}
}
fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn iced::advanced::widget::Operation<bool>)) {
if self.is_focused {
return;
}
operate(self);
}
fn finish(&self) -> iced::advanced::widget::operation::Outcome<bool> {
iced::advanced::widget::operation::Outcome::Some(self.is_focused)
}
}
impl OpenCADStudio {
pub(super) fn begin_tab_close_queue(&mut self, tab_ids: Vec<u64>) -> Task<Message> {
self.pending_tab_closes.clear();
@@ -1955,10 +1997,18 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
#[cfg(not(target_arch = "wasm32"))]
return Task::none();
}
if self.clipboard.is_empty() {
self.read_system_clipboard_for_paste()
} else {
Task::done(Message::Command("PASTECLIP".to_string()))
#[cfg(not(target_arch = "wasm32"))]
{
iced::advanced::widget::operate(CheckTextInputFocused::default())
.map(Message::PasteShortcutResolved)
}
#[cfg(target_arch = "wasm32")]
{
if self.clipboard.is_empty() {
self.read_system_clipboard_for_paste()
} else {
Task::done(Message::Command("PASTECLIP".to_string()))
}
}
}
+35
View File
@@ -6104,6 +6104,18 @@ impl OpenCADStudio {
// take priority and system text is used when that clipboard is empty.
Message::PasteShortcut => self.on_paste_shortcut(),
Message::PasteShortcutResolved(focused) => {
if focused {
// A focused text_input widget already handled Ctrl+V natively.
// Doing anything here would duplicate the paste or leak text into the command line.
Task::none()
} else if self.clipboard.is_empty() {
self.read_system_clipboard_for_paste()
} else {
self.dispatch_command("PASTECLIP")
}
}
Message::SystemClipboardPaste(result) => {
use super::SystemClipboardText as Text;
@@ -10967,6 +10979,29 @@ mod free_text_entry_tests {
let _ = app.update(Message::CommandInput(">Plugin MixedCase".into()));
assert_eq!(app.command_line.input, ">Plugin MixedCase");
}
#[test]
fn paste_shortcut_resolved_focused_is_noop() {
let mut app = OpenCADStudio::new_for_test();
app.automation_op(r#"{"op":"new"}"#);
app.command_line.input = "EXISTING".into();
// When a text input is focused, PasteShortcutResolved(true) does not mutate command line or start a command.
let _ = app.update(Message::PasteShortcutResolved(true));
assert_eq!(app.command_line.input, "EXISTING");
assert!(app.tabs[0].active_cmd.is_none());
}
#[test]
fn paste_shortcut_resolved_unfocused_with_entities_starts_pasteclip() {
let mut app = OpenCADStudio::new_for_test();
app.automation_op(r#"{"op":"new"}"#);
app.clipboard = vec![acadrust::EntityType::Line(acadrust::entities::Line::default())];
// When unfocused and CAD entities are copied, dispatches PASTECLIP
let _ = app.update(Message::PasteShortcutResolved(false));
assert!(app.tabs[0].active_cmd.as_ref().is_some_and(|cmd| cmd.name() == "PASTECLIP"));
}
}
/// Set or clear one bit of `SELECTIONPREVIEW`.