11use chrono:: { DateTime , Utc } ;
22use irc:: proto:: Command ;
33use serde:: { Deserialize , Serialize } ;
4+ use unicode_segmentation:: UnicodeSegmentation ;
45
56use crate :: isupport;
67use crate :: message:: { Encoded , Id } ;
@@ -28,6 +29,7 @@ impl Reaction {
2829 chantypes : & [ char ] ,
2930 statusmsg : & [ char ] ,
3031 casemapping : isupport:: CaseMap ,
32+ max_reaction_chars : u32 ,
3133 ) -> Option < Context > {
3234 let user = message. user ( casemapping) ?;
3335 let ( text, unreact) = match (
@@ -38,6 +40,7 @@ impl Reaction {
3840 ( None , Some ( s) ) => ( s. clone ( ) , true ) ,
3941 _ => return None ,
4042 } ;
43+ let text = truncate_text ( & text, max_reaction_chars as usize ) ;
4144 let in_reply_to = message. in_reply_to ( ) ?;
4245 let server_time = message. server_time ( ) ;
4346
@@ -60,6 +63,18 @@ impl Reaction {
6063 }
6164}
6265
66+ pub fn truncate_text ( text : & str , max_chars : usize ) -> String {
67+ if UnicodeSegmentation :: graphemes ( text, true ) . count ( ) <= max_chars {
68+ return text. to_string ( ) ;
69+ }
70+
71+ let mut truncated = UnicodeSegmentation :: graphemes ( text, true )
72+ . take ( max_chars)
73+ . collect :: < String > ( ) ;
74+ truncated. push_str ( "..." ) ;
75+ truncated
76+ }
77+
6378#[ derive( Debug ) ]
6479pub struct Pending {
6580 pub reactions : Vec < Reaction > ,
@@ -74,3 +89,38 @@ impl Pending {
7489 }
7590 }
7691}
92+
93+ #[ cfg( test) ]
94+ mod tests {
95+ use super :: truncate_text;
96+
97+ #[ test]
98+ fn keeps_short_reaction_text ( ) {
99+ assert_eq ! ( truncate_text( "hello" , 5 ) , "hello" ) ;
100+ }
101+
102+ #[ test]
103+ fn truncates_ascii_to_limit ( ) {
104+ assert_eq ! ( truncate_text( "hello world" , 5 ) , "hello..." ) ;
105+ }
106+
107+ #[ test]
108+ fn truncates_unicode_graphemes ( ) {
109+ assert_eq ! ( truncate_text( "cafe\u{301} " , 4 ) , "cafe\u{301} " ) ;
110+ }
111+
112+ #[ test]
113+ fn limit_one_keeps_first_grapheme_when_truncated ( ) {
114+ assert_eq ! ( truncate_text( "👍🏽👍🏽" , 1 ) , "👍🏽..." ) ;
115+ }
116+
117+ #[ test]
118+ fn does_not_split_zwj_emoji_clusters ( ) {
119+ assert_eq ! ( truncate_text( "👨👩👧👦x" , 1 ) , "👨👩👧👦..." ) ;
120+ }
121+
122+ #[ test]
123+ fn does_not_split_combining_mark_clusters ( ) {
124+ assert_eq ! ( truncate_text( "a\u{0301} b" , 1 ) , "a\u{0301} ..." ) ;
125+ }
126+ }
0 commit comments