Skip to content

Commit 1eebc32

Browse files
committed
Fix sticky text property insertion boundaries
1 parent aedb3be commit 1eebc32

3 files changed

Lines changed: 50 additions & 35 deletions

File tree

neovm-core/src/emacs_core/builtins/buffers.rs

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2931,22 +2931,12 @@ pub(crate) fn apply_inherited_text_properties(
29312931
return;
29322932
}
29332933

2934-
let (props, existing_keys) = buffers
2934+
let props = buffers
29352935
.get(current_id)
29362936
.map(|buf| {
2937-
let merged = super::misc_eval::inherited_text_properties_for_inserted_range_in_state(
2937+
super::misc_eval::inherited_text_properties_for_inserted_range_in_state(
29382938
obarray, dynamic, buf, old_pt, text_len,
2939-
);
2940-
// Properties already present on the just-inserted region (from the
2941-
// source string's own propertize plist). Source properties win
2942-
// over inherited ones, matching GNU graft_intervals_into_buffer.
2943-
let existing: std::collections::HashSet<Value> = buf
2944-
.text
2945-
.text_props_get_properties_ordered(old_pt)
2946-
.into_iter()
2947-
.map(|(name, _)| name)
2948-
.collect();
2949-
(merged, existing)
2939+
)
29502940
})
29512941
.unwrap_or_default();
29522942
if props.is_empty() {
@@ -2956,9 +2946,6 @@ pub(crate) fn apply_inherited_text_properties(
29562946
// put_property prepends new properties to interval order, so apply the
29572947
// merged GNU plist in reverse to preserve the final plist shape.
29582948
for (name, value) in props.iter().rev() {
2959-
if existing_keys.contains(name) {
2960-
continue;
2961-
}
29622949
let _ =
29632950
buffers.put_buffer_text_property(current_id, old_pt, old_pt + text_len, *name, *value);
29642951
}
@@ -3062,20 +3049,19 @@ fn insert_pieces_in_state(
30623049
if let Some(str_table) = piece.text_props {
30633050
let _ = buffers.append_buffer_text_properties(current_id, &str_table, insert_pos);
30643051
}
3065-
// Match GNU adjust_intervals_for_insertion (intervals.c:802): every
3066-
// insert applies sticky-aware inheritance from the surrounding
3067-
// intervals, regardless of whether `insert-and-inherit` was used.
3068-
// Source-string properties already applied above take precedence
3069-
// because apply_inherited_text_properties skips keys already present.
3070-
let _ = inherit;
3071-
apply_inherited_text_properties(
3072-
obarray,
3073-
dynamic,
3074-
buffers,
3075-
current_id,
3076-
insert_pos,
3077-
piece.text.sbytes(),
3078-
);
3052+
if inherit {
3053+
// GNU clears surrounding inherited properties for plain `insert`
3054+
// after interval adjustment; only the inheriting insert variants
3055+
// keep sticky properties from adjoining text.
3056+
apply_inherited_text_properties(
3057+
obarray,
3058+
dynamic,
3059+
buffers,
3060+
current_id,
3061+
insert_pos,
3062+
piece.text.sbytes(),
3063+
);
3064+
}
30793065
}
30803066
Ok(Value::NIL)
30813067
}

neovm-core/src/emacs_core/builtins/misc_eval.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -716,15 +716,20 @@ pub(crate) fn inherited_text_properties_for_inserted_range_in_state(
716716
insert_start: usize,
717717
insert_len: usize,
718718
) -> Vec<(Value, Value)> {
719-
let left_props = if insert_start > buf.point_min_byte() {
720-
buf.text
721-
.text_props_get_properties_ordered(insert_start.saturating_sub(1))
719+
let insert_start_char = buf.text.emacs_byte_to_char(insert_start);
720+
let left_props = if insert_start_char > buf.point_min_char() {
721+
let left_byte = buf
722+
.text
723+
.char_to_emacs_byte(insert_start_char.saturating_sub(1));
724+
buf.text.text_props_get_properties_ordered(left_byte)
722725
} else {
723726
Vec::new()
724727
};
725728
let right_pos = insert_start.saturating_add(insert_len);
726-
let right_props = if right_pos < buf.point_max_byte() {
727-
buf.text.text_props_get_properties_ordered(right_pos)
729+
let right_char = buf.text.emacs_byte_to_char(right_pos);
730+
let right_props = if right_char < buf.point_max_char() {
731+
let right_byte = buf.text.char_to_emacs_byte(right_char);
732+
buf.text.text_props_get_properties_ordered(right_byte)
728733
} else {
729734
Vec::new()
730735
};

neovm-core/src/emacs_core/builtins/tests.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2244,6 +2244,30 @@ fn plain_insert_does_not_inherit_spanning_text_properties() {
22442244
);
22452245
}
22462246

2247+
#[test]
2248+
fn insert_inherits_left_multibyte_text_property_at_char_boundary() {
2249+
crate::test_utils::init_test_tracing();
2250+
let mut eval = super::super::eval::Context::new();
2251+
{
2252+
let buf = eval.buffers.current_buffer_mut().expect("current buffer");
2253+
buf.insert("é");
2254+
buf.text
2255+
.text_props_put_property(0, 2, Value::symbol("face"), Value::symbol("bold"));
2256+
}
2257+
2258+
assert_eq!(
2259+
builtin_insert_and_inherit(&mut eval, vec![Value::string("X")]).unwrap(),
2260+
Value::NIL
2261+
);
2262+
2263+
let buf = eval.buffers.current_buffer().expect("current buffer");
2264+
assert_eq!(buf.buffer_string(), "éX");
2265+
assert_eq!(
2266+
buf.text.text_props_get_property(2, Value::symbol("face")),
2267+
Some(Value::symbol("bold"))
2268+
);
2269+
}
2270+
22472271
#[test]
22482272
fn insert_char_nil_count_defaults_to_one_and_can_inherit_text_properties() {
22492273
crate::test_utils::init_test_tracing();

0 commit comments

Comments
 (0)