@@ -19,13 +19,18 @@ pub enum ApplyAttributeError {
1919 InvalidBounds ,
2020 /// The range has `start > end`.
2121 InvalidRange ,
22+ /// Either `start` or `end` is not on a UTF-8 character boundary.
23+ NotOnCharBoundary ,
2224}
2325
2426impl core:: fmt:: Display for ApplyAttributeError {
2527 fn fmt ( & self , f : & mut core:: fmt:: Formatter < ' _ > ) -> core:: fmt:: Result {
2628 match self {
2729 Self :: InvalidBounds => f. write_str ( "attribute range is out of bounds" ) ,
2830 Self :: InvalidRange => f. write_str ( "attribute range start is greater than end" ) ,
31+ Self :: NotOnCharBoundary => {
32+ f. write_str ( "attribute range must align to UTF-8 char boundaries" )
33+ }
2934 }
3035 }
3136}
@@ -69,6 +74,9 @@ impl<T: Debug + TextStorage, Attr: Debug> AttributedText<T, Attr> {
6974 if range. start > text_len || range. end > text_len {
7075 return Err ( ApplyAttributeError :: InvalidBounds ) ;
7176 }
77+ if !self . text . is_char_boundary ( range. start ) || !self . text . is_char_boundary ( range. end ) {
78+ return Err ( ApplyAttributeError :: NotOnCharBoundary ) ;
79+ }
7280 self . attributes . push ( ( range, attribute) ) ;
7381 Ok ( ( ) )
7482 }
@@ -150,4 +158,17 @@ mod tests {
150158 Err ( ApplyAttributeError :: InvalidBounds )
151159 ) ;
152160 }
161+
162+ #[ test]
163+ fn not_on_char_boundary ( ) {
164+ // "é" is 2 bytes in UTF-8; index 1 is not a boundary
165+ let t = "éclair" ;
166+ let mut at = AttributedText :: new ( t) ;
167+ assert_eq ! (
168+ at. apply_attribute( 1 ..2 , TestAttribute :: Keep ) ,
169+ Err ( ApplyAttributeError :: NotOnCharBoundary )
170+ ) ;
171+ // Using proper boundaries is OK
172+ assert_eq ! ( at. apply_attribute( 0 ..2 , TestAttribute :: Keep ) , Ok ( ( ) ) ) ;
173+ }
153174}
0 commit comments