forked from 1b-agent/opencadstudio-upstream
Avoid slicing text inside multi-byte characters
Right-to-left line reordering walked each atom's byte range and sliced the line at every byte, panicking when a neutral multi-byte character such as a guillemet preceded the first strong character. Walk characters instead. FIND/FINDALL located the REPLACE keyword in a Unicode-uppercased copy and sliced the original with that offset; uppercasing can change byte length (dotless i, ligatures). Uppercase ASCII only, which keeps offsets aligned. FILLET and CHAMFER inline R/D shorthands now test the original text before slicing past the first character. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
1 parent
ef0c6bf403
commit
38aa2ee34d
3 files changed
+32
-6
No files matched your search
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -798,9 +798,11 @@ pub fn reorder_line_atoms(atoms: Vec<LayoutAtom>, is_rtl: bool) -> Vec<LayoutAto
|
||||
.iter()
|
||||
.map(|r| {
|
||||
let mut lvl = None;
|
||||
for i in r.clone() {
|
||||
// `bidi.levels` is indexed by byte, so walk characters rather than
|
||||
// bytes to avoid slicing inside a multi-byte character.
|
||||
for (offset, c) in line_text[r.clone()].char_indices() {
|
||||
let i = r.start + offset;
|
||||
if i < bidi.levels.len() {
|
||||
let c = line_text[i..].chars().next().unwrap_or(' ');
|
||||
match unicode_bidi::bidi_class(c) {
|
||||
unicode_bidi::BidiClass::L
|
||||
| unicode_bidi::BidiClass::R
|
||||
@@ -2966,6 +2968,12 @@ mod v_anchor_tests {
|
||||
reordered_b,
|
||||
vec!["2026 ", "is ", "here!", "שנה ", "שלום ", "مرحبا! ", "ہے۔ ", "اردو ", "یہ "]
|
||||
);
|
||||
|
||||
// Case 6: a word starting with multi-byte neutral punctuation must
|
||||
// not be sliced inside that character.
|
||||
let words6 = ["שלום", " ", "«x"];
|
||||
let reordered6 = reorder_atoms(&words6, false);
|
||||
assert_eq!(reordered6, vec!["שלום", " ", "«x"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1514,7 +1514,7 @@ impl CadCommand for FilletCommand {
|
||||
return Some(CmdResult::NeedPoint);
|
||||
}
|
||||
// "R 5.0" inline shorthand
|
||||
if upper.starts_with('R') {
|
||||
if t.starts_with(['r', 'R']) {
|
||||
let body = t[1..].trim();
|
||||
if let Some(v) = crate::entities::common::parse_typed_length(body) {
|
||||
if v >= 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<f64> = body
|
||||
.split_whitespace()
|
||||
|
||||
Reference in new issue
Block a user