1+ use anyhow:: Result ;
12use bstr:: ByteSlice ;
3+ use dialoguer:: { Confirm , Select } ;
24use std:: io:: Read ;
35use std:: io:: Write ;
46
@@ -127,12 +129,85 @@ impl FileChecker for FixTypos {
127129 }
128130 }
129131 if !fixes. is_empty ( ) {
130- let file_name = file_name. to_owned ( ) . into_bytes ( ) ;
131- let new_name = fix_buffer ( file_name, fixes. into_iter ( ) ) ;
132- let new_name =
133- String :: from_utf8 ( new_name) . expect ( "corrections are valid utf-8" ) ;
134- let new_path = path. with_file_name ( new_name) ;
135- std:: fs:: rename ( path, new_path) ?;
132+ fix_file_name ( path, & file_name. to_owned ( ) , fixes) ?;
133+ }
134+ }
135+ }
136+
137+ Ok ( ( ) )
138+ }
139+ }
140+
141+ #[ derive( Debug , Clone , Copy ) ]
142+ pub struct AskFixTypos ;
143+
144+ impl FileChecker for AskFixTypos {
145+ fn check_file (
146+ & self ,
147+ path : & std:: path:: Path ,
148+ explicit : bool ,
149+ policy : & crate :: policy:: Policy < ' _ , ' _ , ' _ > ,
150+ reporter : & dyn report:: Report ,
151+ ) -> Result < ( ) , std:: io:: Error > {
152+ if policy. check_files {
153+ let ( buffer, content_type) = read_file ( path, reporter) ?;
154+ let bc = buffer. clone ( ) ;
155+ if !explicit && !policy. binary && content_type. is_binary ( ) {
156+ let msg = report:: BinaryFile { path } ;
157+ reporter. report ( msg. into ( ) ) ?;
158+ } else {
159+ let mut fixes = Vec :: new ( ) ;
160+
161+ let mut accum_line_num = AccumulateLineNum :: new ( ) ;
162+ for typo in check_bytes ( & bc, policy) {
163+ let line_num = accum_line_num. line_num ( & buffer, typo. byte_offset ) ;
164+ let ( line, line_offset) = extract_line ( & buffer, typo. byte_offset ) ;
165+ let msg = report:: Typo {
166+ context : Some ( report:: FileContext { path, line_num } . into ( ) ) ,
167+ buffer : std:: borrow:: Cow :: Borrowed ( line) ,
168+ byte_offset : line_offset,
169+ typo : typo. typo . as_ref ( ) ,
170+ corrections : typo. corrections . clone ( ) ,
171+ } ;
172+ reporter. report ( msg. into ( ) ) ?;
173+
174+ match select_fix ( & typo) {
175+ Some ( correction_index) => fixes. push ( ( typo, correction_index) ) ,
176+ None => ( ) ,
177+ }
178+
179+ println ! ( "\n " ) ;
180+ }
181+
182+ if !fixes. is_empty ( ) || path == std:: path:: Path :: new ( "-" ) {
183+ let buffer = fix_buffer_with_correction_index ( buffer, fixes. into_iter ( ) ) ;
184+ write_file ( path, content_type, buffer, reporter) ?;
185+ }
186+ }
187+ }
188+
189+ if policy. check_filenames {
190+ if let Some ( file_name) = path. file_name ( ) . and_then ( |s| s. to_str ( ) ) {
191+ let mut fixes = Vec :: new ( ) ;
192+
193+ for typo in check_str ( file_name, policy) {
194+ let msg = report:: Typo {
195+ context : Some ( report:: PathContext { path } . into ( ) ) ,
196+ buffer : std:: borrow:: Cow :: Borrowed ( file_name. as_bytes ( ) ) ,
197+ byte_offset : typo. byte_offset ,
198+ typo : typo. typo . as_ref ( ) ,
199+ corrections : typo. corrections . clone ( ) ,
200+ } ;
201+ reporter. report ( msg. into ( ) ) ?;
202+
203+ match select_fix ( & typo) {
204+ Some ( correction_index) => fixes. push ( ( typo, correction_index) ) ,
205+ None => ( ) ,
206+ }
207+ }
208+
209+ if !fixes. is_empty ( ) {
210+ fix_file_name_with_correction_index ( path, & file_name, fixes) ?;
136211 }
137212 }
138213 }
@@ -650,6 +725,40 @@ fn is_fixable(typo: &typos::Typo<'_>) -> bool {
650725 extract_fix ( typo) . is_some ( )
651726}
652727
728+ fn fix_buffer_with_correction_index < ' a > (
729+ mut buffer : Vec < u8 > ,
730+ typos : impl Iterator < Item = ( typos:: Typo < ' a > , usize ) > ,
731+ ) -> Vec < u8 > {
732+ let mut offset = 0isize ;
733+ for ( typo, correction_index) in typos {
734+ let fix = match & typo. corrections {
735+ typos:: Status :: Corrections ( c) => Some ( c[ correction_index] . as_ref ( ) ) ,
736+ _ => None ,
737+ }
738+ . expect ( "Caller provided invalid fix index" ) ;
739+ let start = ( ( typo. byte_offset as isize ) + offset) as usize ;
740+ let end = start + typo. typo . len ( ) ;
741+
742+ buffer. splice ( start..end, fix. as_bytes ( ) . iter ( ) . copied ( ) ) ;
743+
744+ offset += ( fix. len ( ) as isize ) - ( typo. typo . len ( ) as isize ) ;
745+ }
746+ buffer
747+ }
748+
749+ fn fix_file_name_with_correction_index < ' a > (
750+ path : & std:: path:: Path ,
751+ file_name : & ' a str ,
752+ fixes : Vec < ( typos:: Typo < ' a > , usize ) > ,
753+ ) -> Result < ( ) , std:: io:: Error > {
754+ let file_name = file_name. to_owned ( ) . into_bytes ( ) ;
755+ let new_name = fix_buffer_with_correction_index ( file_name, fixes. into_iter ( ) ) ;
756+ let new_name = String :: from_utf8 ( new_name) . expect ( "corrections are valid utf-8" ) ;
757+ let new_path = path. with_file_name ( new_name) ;
758+ std:: fs:: rename ( path, new_path) ?;
759+ Ok ( ( ) )
760+ }
761+
653762fn fix_buffer ( mut buffer : Vec < u8 > , typos : impl Iterator < Item = typos:: Typo < ' static > > ) -> Vec < u8 > {
654763 let mut offset = 0isize ;
655764 for typo in typos {
@@ -664,6 +773,56 @@ fn fix_buffer(mut buffer: Vec<u8>, typos: impl Iterator<Item = typos::Typo<'stat
664773 buffer
665774}
666775
776+ fn fix_file_name < ' a > (
777+ path : & std:: path:: Path ,
778+ file_name : & ' a str ,
779+ fixes : Vec < typos:: Typo < ' static > > ,
780+ ) -> Result < ( ) , std:: io:: Error > {
781+ let file_name = file_name. to_owned ( ) . into_bytes ( ) ;
782+ let new_name = fix_buffer ( file_name, fixes. into_iter ( ) ) ;
783+ let new_name = String :: from_utf8 ( new_name) . expect ( "corrections are valid utf-8" ) ;
784+ let new_path = path. with_file_name ( new_name) ;
785+ std:: fs:: rename ( path, new_path) ?;
786+ Ok ( ( ) )
787+ }
788+
789+ fn select_fix ( typo : & typos:: Typo < ' _ > ) -> Option < usize > {
790+ if is_fixable ( & typo) {
791+ let confirmation = Confirm :: new ( )
792+ . with_prompt ( "Do you want to apply the fix suggested above?" )
793+ . default ( true )
794+ . show_default ( true )
795+ . interact ( )
796+ . unwrap ( ) ;
797+
798+ if confirmation {
799+ return Some ( 0 ) ;
800+ }
801+ } else {
802+ let mut items = match & typo. corrections {
803+ typos:: Status :: Corrections ( c) => c,
804+ _ => return None ,
805+ }
806+ . clone ( ) ;
807+ items. insert ( 0 , std:: borrow:: Cow :: from ( "None (skip)" ) ) ;
808+
809+ let selection = Select :: new ( )
810+ . with_prompt ( "Please choose one of the following suggestions" )
811+ . items ( & items)
812+ . default ( 0 )
813+ . interact ( )
814+ . unwrap ( ) ;
815+
816+ if selection == 0 {
817+ return None ;
818+ }
819+
820+ return Some ( selection - 1 ) ;
821+ }
822+
823+ None
824+ }
825+
667826pub fn walk_path (
668827 walk : ignore:: Walk ,
669828 checks : & dyn FileChecker ,
0 commit comments