@@ -116,7 +116,7 @@ fn build_matcher(
116116 let mut exact_len = 0 ;
117117 for cap in placeholder_regex_for ( language) . captures_iter ( text) {
118118 let placeholder = cap. get ( 0 ) . unwrap ( ) ;
119- let text = regex :: escape ( & text[ last_end..placeholder. start ( ) ] ) ;
119+ let text = escape_ignore_newlines ( & text[ last_end..placeholder. start ( ) ] ) ;
120120 exact_len += text. len ( ) ;
121121 pattern. push_str ( text. as_str ( ) ) ;
122122 last_end = placeholder. end ( ) ;
@@ -128,7 +128,7 @@ fn build_matcher(
128128 ( None , None ) => FormatArgument :: Placeholder ,
129129 } ) ;
130130 }
131- let text = regex :: escape ( & text[ last_end..] ) ;
131+ let text = escape_ignore_newlines ( & text[ last_end..] ) ;
132132 exact_len += text. len ( ) ;
133133 if exact_len == 0 {
134134 None
@@ -139,6 +139,24 @@ fn build_matcher(
139139 }
140140}
141141
142+ /// Escape special chars except newlines and carriage returns in order to support multiline strings
143+ fn escape_ignore_newlines ( segment : & str ) -> String {
144+ let mut result = String :: with_capacity ( segment. len ( ) * 2 ) ;
145+ for c in segment. chars ( ) {
146+ match c {
147+ '\n' => result. push_str ( r"\n" ) , // Use actual newline in regex
148+ '\r' => result. push_str ( r"\r" ) , // Handle carriage returns too
149+ // Escape regex special chars
150+ '.' | '+' | '*' | '?' | '^' | '$' | '(' | ')' | '[' | ']' | '{' | '}' | '|' => {
151+ result. push ( '\\' ) ;
152+ result. push ( c) ;
153+ }
154+ _ => result. push ( c) ,
155+ }
156+ }
157+ result
158+ }
159+
142160#[ cfg( test) ]
143161mod tests {
144162 use super :: * ;
@@ -176,7 +194,10 @@ mod tests {
176194 #[ test]
177195 fn test_build_matcher_positional ( ) {
178196 let ( matcher, _pat, args) = build_matcher ( "second={2}" , SourceLanguage :: Rust ) . unwrap ( ) ;
179- assert_eq ! ( Regex :: new( r#"^second=(.+)$"# ) . unwrap( ) . as_str( ) , matcher. as_str( ) ) ;
197+ assert_eq ! (
198+ Regex :: new( r#"^second=(.+)$"# ) . unwrap( ) . as_str( ) ,
199+ matcher. as_str( )
200+ ) ;
180201 assert_eq ! ( args[ 0 ] , FormatArgument :: Positional ( 2 ) ) ;
181202 }
182203
@@ -193,8 +214,22 @@ mod tests {
193214
194215 #[ test]
195216 fn test_build_matcher_none ( ) {
196- let build_res =
197- build_matcher ( "%s" , SourceLanguage :: Cpp ) ;
217+ let build_res = build_matcher ( "%s" , SourceLanguage :: Cpp ) ;
198218 assert ! ( build_res. is_none( ) ) ;
199219 }
220+
221+ #[ test]
222+ fn test_build_matcher_multiline ( ) {
223+ let ( matcher, _pat, _args) = build_matcher (
224+ "you're only as funky\n as your last cut" ,
225+ SourceLanguage :: Rust ,
226+ )
227+ . unwrap ( ) ;
228+ assert_eq ! (
229+ Regex :: new( r#"^you're only as funky\n as your last cut$"# )
230+ . unwrap( )
231+ . as_str( ) ,
232+ matcher. as_str( )
233+ ) ;
234+ }
200235}
0 commit comments