diff --git a/src/app/commands/inquiry.rs b/src/app/commands/inquiry.rs index 161a6c28c..eb33b4ca0 100644 --- a/src/app/commands/inquiry.rs +++ b/src/app/commands/inquiry.rs @@ -1302,8 +1302,9 @@ impl OpenCADStudio { let all_mode = cmd.starts_with("FINDALL"); let rest = cmd.split_once(' ').map(|(_, r)| r.trim()).unwrap_or(""); - // Split at " REPLACE " keyword (case-insensitive) - let (search, replacement) = if let Some(pos) = rest.to_uppercase().find(" REPLACE ") + // Split at " REPLACE " keyword (case-insensitive). ASCII case + // mapping keeps byte offsets valid for slicing `rest`. + let (search, replacement) = if let Some(pos) = rest.to_ascii_uppercase().find(" REPLACE ") { (&rest[..pos], Some(rest[pos + 9..].trim())) } else { @@ -2386,3 +2387,20 @@ mod align_selected_bounds_tests { assert_eq!(line_start_x(&app, b), 2.0, "b must keep its offset from a, not collapse onto it"); } } + +#[cfg(test)] +mod find_replace_command_tests { + use super::*; + + /// Unicode case mapping can change a string's byte length (`ı` uppercases + /// to `I`), so the REPLACE keyword split must not slice with offsets + /// taken from a case-mapped copy. + #[test] + fn find_replace_accepts_search_text_whose_case_mapping_changes_length() { + for cmd in ["FIND ı REPLACE x", "FINDALL fi REPLACE fi", "FIND ʼn replace n"] { + let mut app = OpenCADStudio::new_for_test(); + let i = app.active_tab; + let _ = app.dispatch_inquiry(cmd, i); + } + } +} diff --git a/src/entities/text_support.rs b/src/entities/text_support.rs index f26f7d52a..c115c4e18 100644 --- a/src/entities/text_support.rs +++ b/src/entities/text_support.rs @@ -798,9 +798,11 @@ pub fn reorder_line_atoms(atoms: Vec, is_rtl: bool) -> Vec= 0.0 { @@ -2109,7 +2109,7 @@ impl CadCommand for ChamferCommand { return Some(CmdResult::NeedPoint); } // "D 5.0" or "D 5.0 3.0" inline shorthand - if upper.starts_with('D') { + if t.starts_with(['d', 'D']) { let body = t[1..].trim(); let parts: Vec = body .split_whitespace()