@@ -818,6 +818,56 @@ fn edit_file_preview_lines(params: &Value, max_lines: usize) -> Option<Vec<Strin
818818 Some ( lines)
819819}
820820
821+ fn exact_edit_file_preview_lines ( params : & Value ) -> Option < Vec < String > > {
822+ let search = param_text ( params, & [ "search" ] ) ?;
823+ let replace = param_text ( params, & [ "replace" ] ) ?;
824+ let mut lines = vec ! [ "replace this" . to_string( ) ] ;
825+ lines. extend ( exact_preview_body_lines ( "- " , & search) ) ;
826+ lines. push ( "with this" . to_string ( ) ) ;
827+ lines. extend ( exact_preview_body_lines ( "+ " , & replace) ) ;
828+ Some ( lines)
829+ }
830+
831+ fn exact_preview_body_lines ( prefix : & str , content : & str ) -> Vec < String > {
832+ if content. is_empty ( ) {
833+ return vec ! [ format!( "{prefix}\" \" " ) ] ;
834+ }
835+
836+ content
837+ . split_inclusive ( '\n' )
838+ . map ( |chunk| {
839+ let ( body, ending) = if let Some ( body) = chunk. strip_suffix ( "\r \n " ) {
840+ ( body, "\\ r\\ n" )
841+ } else if let Some ( body) = chunk. strip_suffix ( '\n' ) {
842+ ( body, "\\ n" )
843+ } else {
844+ ( chunk, "" )
845+ } ;
846+ exact_preview_body_line ( prefix, body, ending)
847+ } )
848+ . collect ( )
849+ }
850+
851+ fn exact_preview_body_line ( prefix : & str , body : & str , ending : & str ) -> String {
852+ let mut line = String :: with_capacity ( prefix. len ( ) + body. len ( ) + ending. len ( ) + 2 ) ;
853+ line. push_str ( prefix) ;
854+ line. push ( '"' ) ;
855+ for ch in body. chars ( ) {
856+ match ch {
857+ '\\' => line. push_str ( "\\ \\ " ) ,
858+ '"' => line. push_str ( "\\ \" " ) ,
859+ ' ' => line. push_str ( "\\ x20" ) ,
860+ '\t' => line. push_str ( "\\ t" ) ,
861+ '\r' => line. push_str ( "\\ r" ) ,
862+ ch if ch. is_whitespace ( ) || ch. is_control ( ) => line. extend ( ch. escape_unicode ( ) ) ,
863+ ch => line. push ( ch) ,
864+ }
865+ }
866+ line. push_str ( ending) ;
867+ line. push ( '"' ) ;
868+ line
869+ }
870+
821871fn prefixed_preview_lines (
822872 header : & str ,
823873 prefix : & str ,
@@ -1359,7 +1409,7 @@ impl ApprovalView {
13591409 }
13601410 content. push ( '\n' ) ;
13611411 if canonical_action_alias ( & self . request . tool_name , & self . request . params ) == "edit_file"
1362- && let Some ( preview_lines) = edit_file_preview_lines ( & self . request . params , usize :: MAX )
1412+ && let Some ( preview_lines) = exact_edit_file_preview_lines ( & self . request . params )
13631413 {
13641414 content. push_str ( & localize_detail_label ( "Preview" , locale) ) ;
13651415 content. push_str ( ":\n " ) ;
@@ -3052,8 +3102,8 @@ diff --git a/src/b.rs b/src/b.rs
30523102 "Edit a file on disk" ,
30533103 & json ! ( {
30543104 "path" : "src/lib.rs" ,
3055- "search" : "old_1();\n old_2 ();\n old_3 ();\n old_4();\n old_5();" ,
3056- "replace" : "new_1 ();\n new_2 ();\n new_3();\n new_4();\n new_5();"
3105+ "search" : " old_1();\r \n \t old_2 ();\n old 3 ();\n old_4();\n old_5();\n " ,
3106+ "replace" : "\t new_1 ();\n new 2 ();\r \n new_3();\n new_4();\n new_5();"
30573107 } ) ,
30583108 "tool:edit_file" ,
30593109 ) ;
@@ -3064,23 +3114,33 @@ diff --git a/src/b.rs b/src/b.rs
30643114 panic ! ( "Alt+V should open the edit details pager" ) ;
30653115 } ;
30663116
3067- let expected_preview = "Preview:\n \
3068- replace this\n \
3069- - old_1();\n \
3070- - old_2();\n \
3071- - old_3();\n \
3072- - old_4();\n \
3073- - old_5();\n \
3074- with this\n \
3075- + new_1();\n \
3076- + new_2();\n \
3077- + new_3();\n \
3078- + new_4();\n \
3079- + new_5();";
3117+ let expected_preview = [
3118+ "Preview:" ,
3119+ "replace this" ,
3120+ "- \" \\ x20\\ x20old_1();\\ r\\ n\" " ,
3121+ "- \" \\ told_2();\\ n\" " ,
3122+ "- \" old\\ x20\\ x203();\\ n\" " ,
3123+ "- \" old_4();\\ n\" " ,
3124+ "- \" old_5();\\ n\" " ,
3125+ "with this" ,
3126+ "+ \" \\ tnew_1();\\ n\" " ,
3127+ "+ \" new\\ x20\\ x202();\\ r\\ n\" " ,
3128+ "+ \" new_3();\\ n\" " ,
3129+ "+ \" new_4();\\ n\" " ,
3130+ "+ \" new_5();\" " ,
3131+ ]
3132+ . join ( "\n " ) ;
30803133 assert ! (
3081- content. contains( expected_preview) ,
3134+ content. contains( & expected_preview) ,
30823135 "details pager omitted part of the edit preview:\n {content}"
30833136 ) ;
3137+
3138+ let pager = crate :: tui:: pager:: PagerView :: from_text ( "Tool Params" , & content, 200 ) ;
3139+ let displayed = pager. body_text ( ) ;
3140+ assert ! (
3141+ displayed. contains( & expected_preview) ,
3142+ "details pager display changed exact whitespace or line endings:\n {displayed}"
3143+ ) ;
30843144 }
30853145
30863146 #[ test]
0 commit comments