11use std:: borrow:: Cow ;
22use std:: io:: Write ;
3+ use std:: path:: Path ;
34
45use crossterm:: queue;
56use crossterm:: style:: {
@@ -25,7 +26,6 @@ use super::{
2526 stylize_output_if_able,
2627 terminal_width_required_for_line_count,
2728} ;
28- use crate :: cli:: chat:: tools:: supports_truecolor;
2929
3030#[ derive( Debug , Clone , Deserialize ) ]
3131#[ serde( tag = "command" ) ]
@@ -55,19 +55,6 @@ pub enum FsWrite {
5555}
5656
5757impl FsWrite {
58- /// Helper function to clean file content:
59- /// 1. Ensures the file ends with a newline
60- /// 2. Removes trailing whitespace from each line
61- fn clean_file_content ( content : String ) -> String {
62- let mut cleaned = content
63- . lines ( )
64- . map ( |line| line. trim_end ( ) )
65- . collect :: < Vec < _ > > ( )
66- . join ( "\n " ) ;
67- cleaned. push ( '\n' ) ;
68- cleaned
69- }
70-
7158 pub async fn invoke ( & self , ctx : & Context , updates : & mut impl Write ) -> Result < InvokeOutput > {
7259 let fs = ctx. fs ( ) ;
7360 let cwd = ctx. env ( ) . current_dir ( ) ?;
@@ -89,8 +76,7 @@ impl FsWrite {
8976 style:: Print ( "\n " ) ,
9077 ) ?;
9178
92- let cleaned_text = Self :: clean_file_content ( file_text) ;
93- fs. write ( & path, cleaned_text. as_bytes ( ) ) . await ?;
79+ write_to_file ( ctx, path, file_text) . await ?;
9480 Ok ( Default :: default ( ) )
9581 } ,
9682 FsWrite :: StrReplace { path, old_str, new_str } => {
@@ -109,8 +95,7 @@ impl FsWrite {
10995 0 => Err ( eyre ! ( "no occurrences of \" {old_str}\" were found" ) ) ,
11096 1 => {
11197 let file = file. replacen ( old_str, new_str, 1 ) ;
112- let cleaned_file = Self :: clean_file_content ( file) ;
113- fs. write ( path, cleaned_file) . await ?;
98+ fs. write ( path, file) . await ?;
11499 Ok ( Default :: default ( ) )
115100 } ,
116101 x => Err ( eyre ! ( "{x} occurrences of old_str were found when only 1 is expected" ) ) ,
@@ -141,18 +126,12 @@ impl FsWrite {
141126 i += line_len;
142127 }
143128 file. insert_str ( i, new_str) ;
144- let cleaned_file = Self :: clean_file_content ( file) ;
145- fs. write ( & path, & cleaned_file) . await ?;
129+ write_to_file ( ctx, & path, file) . await ?;
146130 Ok ( Default :: default ( ) )
147131 } ,
148132 FsWrite :: Append { path, new_str } => {
149133 let path = sanitize_path_tool_arg ( ctx, path) ;
150134
151- // Return an error if the file doesn't exist
152- if !fs. exists ( & path) {
153- bail ! ( "The file does not exist: {}" , path. display( ) ) ;
154- }
155-
156135 queue ! (
157136 updates,
158137 style:: Print ( "Appending to: " ) ,
@@ -162,15 +141,12 @@ impl FsWrite {
162141 style:: Print ( "\n " ) ,
163142 ) ?;
164143
165- let mut file_content = fs. read_to_string ( & path) . await . unwrap_or_default ( ) ;
166- if !file_content. ends_with_newline ( ) {
167- file_content. push ( '\n' ) ;
168- }
169- file_content. push_str ( new_str) ;
170- if !file_content. ends_with_newline ( ) {
171- file_content. push ( '\n' ) ;
144+ let mut file = fs. read_to_string ( & path) . await ?;
145+ if !file. ends_with_newline ( ) {
146+ file. push ( '\n' ) ;
172147 }
173- fs. write ( & path, file_content) . await ?;
148+ file. push_str ( new_str) ;
149+ write_to_file ( ctx, path, file) . await ?;
174150 Ok ( Default :: default ( ) )
175151 } ,
176152 }
@@ -190,7 +166,7 @@ impl FsWrite {
190166 Default :: default ( )
191167 } ;
192168 let new = stylize_output_if_able ( ctx, & relative_path, & file_text) ;
193- print_diff ( ctx , updates, & prev, & new, 1 ) ?;
169+ print_diff ( updates, & prev, & new, 1 ) ?;
194170 Ok ( ( ) )
195171 } ,
196172 FsWrite :: Insert {
@@ -213,7 +189,7 @@ impl FsWrite {
213189
214190 let old = stylize_output_if_able ( ctx, & relative_path, & old) ;
215191 let new = stylize_output_if_able ( ctx, & relative_path, & new) ;
216- print_diff ( ctx , updates, & old, & new, start_line) ?;
192+ print_diff ( updates, & old, & new, start_line) ?;
217193 Ok ( ( ) )
218194 } ,
219195 FsWrite :: StrReplace { path, old_str, new_str } => {
@@ -225,15 +201,15 @@ impl FsWrite {
225201 } ;
226202 let old_str = stylize_output_if_able ( ctx, & relative_path, old_str) ;
227203 let new_str = stylize_output_if_able ( ctx, & relative_path, new_str) ;
228- print_diff ( ctx , updates, & old_str, & new_str, start_line) ?;
204+ print_diff ( updates, & old_str, & new_str, start_line) ?;
229205
230206 Ok ( ( ) )
231207 } ,
232208 FsWrite :: Append { path, new_str } => {
233209 let relative_path = format_path ( cwd, path) ;
234210 let start_line = ctx. fs ( ) . read_to_string_sync ( & relative_path) ?. lines ( ) . count ( ) + 1 ;
235211 let file = stylize_output_if_able ( ctx, & relative_path, new_str) ;
236- print_diff ( ctx , updates, & Default :: default ( ) , & file, start_line) ?;
212+ print_diff ( updates, & Default :: default ( ) , & file, start_line) ?;
237213 Ok ( ( ) )
238214 } ,
239215 }
@@ -305,6 +281,15 @@ impl FsWrite {
305281 }
306282}
307283
284+ /// Writes `content` to `path`, adding a newline if necessary.
285+ async fn write_to_file ( ctx : & Context , path : impl AsRef < Path > , mut content : String ) -> Result < ( ) > {
286+ if !content. ends_with_newline ( ) {
287+ content. push ( '\n' ) ;
288+ }
289+ ctx. fs ( ) . write ( path. as_ref ( ) , content) . await ?;
290+ Ok ( ( ) )
291+ }
292+
308293/// Returns a prefix/suffix pair before and after the content dictated by `[start_line, end_line]`
309294/// within `content`. The updated start and end lines containing the original context along with
310295/// the suffix and prefix are returned.
@@ -365,7 +350,6 @@ fn get_lines_with_context(
365350/// Prints a git-diff style comparison between `old_str` and `new_str`.
366351/// - `start_line` - 1-indexed line number that `old_str` and `new_str` start at.
367352fn print_diff (
368- ctx : & Context ,
369353 updates : & mut impl Write ,
370354 old_str : & StylizedFile ,
371355 new_str : & StylizedFile ,
@@ -387,15 +371,15 @@ fn print_diff(
387371 let new_line_num_width = terminal_width_required_for_line_count ( max_new_i) ;
388372
389373 // Now, print
390- fn fmt_i ( i : Option < usize > , start_line : usize ) -> String {
374+ fn fmt_index ( i : Option < usize > , start_line : usize ) -> String {
391375 match i {
392376 Some ( i) => ( i + start_line) . to_string ( ) ,
393377 _ => " " . to_string ( ) ,
394378 }
395379 }
396380 for change in diff. iter_all_changes ( ) {
397- // Colors
398- let ( text_color, gutter_bg_color, line_bg_color) = match ( change. tag ( ) , supports_truecolor ( ctx ) ) {
381+ // Define the colors per line.
382+ let ( text_color, gutter_bg_color, line_bg_color) = match ( change. tag ( ) , new_str . truecolor ) {
399383 ( similar:: ChangeTag :: Equal , true ) => ( style:: Color :: Reset , new_str. gutter_bg , new_str. line_bg ) ,
400384 ( similar:: ChangeTag :: Delete , true ) => (
401385 style:: Color :: Reset ,
@@ -407,19 +391,21 @@ fn print_diff(
407391 style:: Color :: Rgb { r : 40 , g : 67 , b : 43 } ,
408392 style:: Color :: Rgb { r : 24 , g : 38 , b : 30 } ,
409393 ) ,
410- ( similar:: ChangeTag :: Equal , false ) => ( style:: Color :: Reset , style :: Color :: Reset , style :: Color :: Reset ) ,
411- ( similar:: ChangeTag :: Delete , false ) => ( style:: Color :: Red , style :: Color :: Reset , style :: Color :: Reset ) ,
412- ( similar:: ChangeTag :: Insert , false ) => ( style:: Color :: Green , style :: Color :: Reset , style :: Color :: Reset ) ,
394+ ( similar:: ChangeTag :: Equal , false ) => ( style:: Color :: Reset , new_str . gutter_bg , new_str . line_bg ) ,
395+ ( similar:: ChangeTag :: Delete , false ) => ( style:: Color :: Red , new_str . gutter_bg , new_str . line_bg ) ,
396+ ( similar:: ChangeTag :: Insert , false ) => ( style:: Color :: Green , new_str . gutter_bg , new_str . line_bg ) ,
413397 } ;
414- // Change tag character
398+ // Define the change tag character to print, if any.
415399 let sign = match change. tag ( ) {
416400 similar:: ChangeTag :: Equal => " " ,
417401 similar:: ChangeTag :: Delete => "-" ,
418402 similar:: ChangeTag :: Insert => "+" ,
419403 } ;
420404
421- let old_i_str = fmt_i ( change. old_index ( ) , start_line) ;
422- let new_i_str = fmt_i ( change. new_index ( ) , start_line) ;
405+ let old_i_str = fmt_index ( change. old_index ( ) , start_line) ;
406+ let new_i_str = fmt_index ( change. new_index ( ) , start_line) ;
407+
408+ // Print the gutter and line numbers.
423409 queue ! ( updates, style:: SetBackgroundColor ( gutter_bg_color) ) ?;
424410 queue ! (
425411 updates,
@@ -448,6 +434,7 @@ fn print_diff(
448434 new_line_num_width = new_line_num_width
449435 ) )
450436 ) ?;
437+ // Print the line.
451438 queue ! (
452439 updates,
453440 style:: SetForegroundColor ( style:: Color :: Reset ) ,
@@ -502,8 +489,6 @@ fn line_number_at(file: impl AsRef<str>, needle: impl AsRef<str>) -> Option<(usi
502489mod tests {
503490 use std:: sync:: Arc ;
504491
505- use similar:: DiffableStr ;
506-
507492 use super :: * ;
508493
509494 const TEST_FILE_CONTENTS : & str = "\
@@ -856,29 +841,6 @@ mod tests {
856841 assert_eq ! ( truncate_str( s, 0 ) , "<...Truncated>" ) ;
857842 }
858843
859- #[ test]
860- fn test_clean_file_content ( ) {
861- // Test removing trailing whitespace
862- let content = "Hello world! \n This is a test \n With trailing spaces " ;
863- let expected = "Hello world!\n This is a test\n With trailing spaces\n " ;
864- assert_eq ! ( FsWrite :: clean_file_content( content. to_string( ) ) , expected) ;
865-
866- // Test ensuring ending newline
867- let content = "Hello world!\n No ending newline" ;
868- let expected = "Hello world!\n No ending newline\n " ;
869- assert_eq ! ( FsWrite :: clean_file_content( content. to_string( ) ) , expected) ;
870-
871- // Test with content already having ending newline
872- let content = "Hello world!\n With ending newline\n " ;
873- let expected = "Hello world!\n With ending newline\n " ;
874- assert_eq ! ( FsWrite :: clean_file_content( content. to_string( ) ) , expected) ;
875-
876- // Test with empty string
877- let content = "" ;
878- let expected = "\n " ;
879- assert_eq ! ( FsWrite :: clean_file_content( content. to_string( ) ) , expected) ;
880- }
881-
882844 #[ test]
883845 fn test_lines_with_context ( ) {
884846 let content = "Hello\n World!\n how\n are\n you\n today?" ;
0 commit comments