@@ -8,21 +8,15 @@ use crate::extractor::{Extract, Extractor, ParserBackend};
88use std:: borrow:: Cow ;
99
1010/// Get the character at a given UTF-16 offset in a string.
11- /// Panics if the offset is out of bounds or doesn't align with a character boundary.
12- fn char_at_utf16_offset ( text : & str , utf16_offset : i32 ) -> char {
11+ fn char_at_utf16_offset ( text : & str , utf16_offset : i32 ) -> Option < char > {
1312 let mut current_utf16 = 0i32 ;
1413 for c in text. chars ( ) {
1514 if current_utf16 == utf16_offset {
16- return c ;
15+ return Some ( c ) ;
1716 }
1817 current_utf16 += c. len_utf16 ( ) as i32 ;
1918 }
20- panic ! (
21- "UTF-16 offset {} not found in text of length {} (UTF-16 length {})" ,
22- utf16_offset,
23- text. len( ) ,
24- current_utf16
25- ) ;
19+ None
2620}
2721
2822type Attributes = Vec < ( String , String ) > ;
@@ -270,7 +264,7 @@ impl<'a> Autolinker<'a> {
270264 }
271265
272266 fn link_to_hashtag ( & self , entity : & Entity , text : & str , buf : & mut String ) {
273- let hash_char = char_at_utf16_offset ( text, entity. get_start ( ) ) ;
267+ let hash_char = char_at_utf16_offset ( text, entity. get_start ( ) ) . unwrap_or ( '#' ) ;
274268 let hashtag = entity. get_value ( ) ;
275269 let mut attrs: Attributes = Vec :: new ( ) ;
276270 attrs. push ( ( HREF . to_string ( ) , self . hashtag_url_base . to_owned ( ) + hashtag) ) ;
@@ -297,7 +291,7 @@ impl<'a> Autolinker<'a> {
297291
298292 fn link_to_mention_and_list ( & self , entity : & Entity , text : & str , buf : & mut String ) {
299293 let mut mention = String :: from ( entity. get_value ( ) ) ;
300- let at_char = char_at_utf16_offset ( text, entity. get_start ( ) ) ;
294+ let at_char = char_at_utf16_offset ( text, entity. get_start ( ) ) . unwrap_or ( '@' ) ;
301295 let mut attrs: Attributes = Vec :: new ( ) ;
302296
303297 if entity. get_type ( ) == entity:: Type :: MENTION && !entity. get_list_slug ( ) . is_empty ( ) {
@@ -416,6 +410,9 @@ impl<'a> Autolinker<'a> {
416410
417411 let mut attrs: Attributes = Vec :: new ( ) ;
418412 attrs. push ( ( HREF . to_string ( ) , String :: from ( url) ) ) ;
413+ if !entity. get_expanded_url ( ) . is_empty ( ) {
414+ attrs. push ( ( TITLE . to_string ( ) , String :: from ( entity. get_expanded_url ( ) ) ) ) ;
415+ }
419416 if !self . url_class . is_empty ( ) {
420417 attrs. push ( ( CLASS . to_string ( ) , String :: from ( self . url_class ) ) ) ;
421418 }
@@ -830,22 +827,23 @@ mod tests {
830827 #[ test]
831828 fn test_char_at_utf16_offset ( ) {
832829 // ASCII text - UTF-16 offset equals char index
833- assert_eq ! ( char_at_utf16_offset( "hello" , 0 ) , 'h' ) ;
834- assert_eq ! ( char_at_utf16_offset( "hello" , 4 ) , 'o' ) ;
830+ assert_eq ! ( char_at_utf16_offset( "hello" , 0 ) , Some ( 'h' ) ) ;
831+ assert_eq ! ( char_at_utf16_offset( "hello" , 4 ) , Some ( 'o' ) ) ;
832+ assert_eq ! ( char_at_utf16_offset( "hello" , 99 ) , None ) ;
835833
836834 // Emoji at start (🔥 is U+1F525, takes 2 UTF-16 code units)
837835 let text = "🔥hello" ;
838- assert_eq ! ( char_at_utf16_offset( text, 0 ) , '🔥' ) ;
839- assert_eq ! ( char_at_utf16_offset( text, 2 ) , 'h' ) ; // After emoji (2 UTF-16 units)
840- assert_eq ! ( char_at_utf16_offset( text, 3 ) , 'e' ) ;
836+ assert_eq ! ( char_at_utf16_offset( text, 0 ) , Some ( '🔥' ) ) ;
837+ assert_eq ! ( char_at_utf16_offset( text, 2 ) , Some ( 'h' ) ) ; // After emoji (2 UTF-16 units)
838+ assert_eq ! ( char_at_utf16_offset( text, 3 ) , Some ( 'e' ) ) ;
841839
842840 // Multiple emoji
843841 let text = "🔥🔥🔥 @test" ;
844- assert_eq ! ( char_at_utf16_offset( text, 0 ) , '🔥' ) ;
845- assert_eq ! ( char_at_utf16_offset( text, 2 ) , '🔥' ) ;
846- assert_eq ! ( char_at_utf16_offset( text, 4 ) , '🔥' ) ;
847- assert_eq ! ( char_at_utf16_offset( text, 6 ) , ' ' ) ;
848- assert_eq ! ( char_at_utf16_offset( text, 7 ) , '@' ) ;
842+ assert_eq ! ( char_at_utf16_offset( text, 0 ) , Some ( '🔥' ) ) ;
843+ assert_eq ! ( char_at_utf16_offset( text, 2 ) , Some ( '🔥' ) ) ;
844+ assert_eq ! ( char_at_utf16_offset( text, 4 ) , Some ( '🔥' ) ) ;
845+ assert_eq ! ( char_at_utf16_offset( text, 6 ) , Some ( ' ' ) ) ;
846+ assert_eq ! ( char_at_utf16_offset( text, 7 ) , Some ( '@' ) ) ;
849847 }
850848
851849 #[ test]
0 commit comments