|
| 1 | +use anyhow::Result; |
1 | 2 | use bstr::ByteSlice; |
| 3 | +use dialoguer::{Confirm, Select}; |
2 | 4 | use std::io::Read; |
3 | 5 | use std::io::Write; |
4 | 6 |
|
@@ -137,6 +139,85 @@ impl FileChecker for FixTypos { |
137 | 139 | } |
138 | 140 | } |
139 | 141 |
|
| 142 | +#[derive(Debug, Clone, Copy)] |
| 143 | +pub struct Interactive; |
| 144 | + |
| 145 | +impl FileChecker for Interactive { |
| 146 | + fn check_file( |
| 147 | + &self, |
| 148 | + path: &std::path::Path, |
| 149 | + explicit: bool, |
| 150 | + policy: &crate::policy::Policy<'_, '_, '_>, |
| 151 | + reporter: &dyn report::Report, |
| 152 | + ) -> Result<(), std::io::Error> { |
| 153 | + if policy.check_files { |
| 154 | + let (buffer, content_type) = read_file(path, reporter)?; |
| 155 | + let bc = buffer.clone(); |
| 156 | + if !explicit && !policy.binary && content_type.is_binary() { |
| 157 | + let msg = report::BinaryFile { path }; |
| 158 | + reporter.report(msg.into())?; |
| 159 | + } else { |
| 160 | + let mut fixes = Vec::new(); |
| 161 | + let mut correction_indices = Vec::new(); |
| 162 | + |
| 163 | + let mut accum_line_num = AccumulateLineNum::new(); |
| 164 | + for typo in check_bytes(&bc, policy) { |
| 165 | + let line_num = accum_line_num.line_num(&buffer, typo.byte_offset); |
| 166 | + let (line, line_offset) = extract_line(&buffer, typo.byte_offset); |
| 167 | + let msg = report::Typo { |
| 168 | + context: Some(report::FileContext { path, line_num }.into()), |
| 169 | + buffer: std::borrow::Cow::Borrowed(line), |
| 170 | + byte_offset: line_offset, |
| 171 | + typo: typo.typo.as_ref(), |
| 172 | + corrections: typo.corrections.clone(), |
| 173 | + }; |
| 174 | + reporter.report(msg.into())?; |
| 175 | + |
| 176 | + if let Some(correction_index) = select_fix(&typo) { |
| 177 | + fixes.push(typo); |
| 178 | + correction_indices.push(correction_index); |
| 179 | + } |
| 180 | + } |
| 181 | + |
| 182 | + if !fixes.is_empty() || path == std::path::Path::new("-") { |
| 183 | + let buffer = fix_buffer(buffer, fixes, Some(correction_indices)); |
| 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 | + let mut correction_indices = Vec::new(); |
| 193 | + |
| 194 | + for typo in check_str(file_name, policy) { |
| 195 | + let msg = report::Typo { |
| 196 | + context: Some(report::PathContext { path }.into()), |
| 197 | + buffer: std::borrow::Cow::Borrowed(file_name.as_bytes()), |
| 198 | + byte_offset: typo.byte_offset, |
| 199 | + typo: typo.typo.as_ref(), |
| 200 | + corrections: typo.corrections.clone(), |
| 201 | + }; |
| 202 | + reporter.report(msg.into())?; |
| 203 | + |
| 204 | + if let Some(correction_index) = select_fix(&typo) { |
| 205 | + fixes.push(typo); |
| 206 | + correction_indices.push(correction_index); |
| 207 | + } |
| 208 | + } |
| 209 | + |
| 210 | + if !fixes.is_empty() { |
| 211 | + let new_path = fix_file_name(path, file_name, fixes, Some(correction_indices))?; |
| 212 | + std::fs::rename(path, new_path)?; |
| 213 | + } |
| 214 | + } |
| 215 | + } |
| 216 | + |
| 217 | + Ok(()) |
| 218 | + } |
| 219 | +} |
| 220 | + |
140 | 221 | #[derive(Debug, Clone, Copy)] |
141 | 222 | pub struct DiffTypos; |
142 | 223 |
|
@@ -683,6 +764,42 @@ fn fix_file_name<'a>( |
683 | 764 | Ok(new_path) |
684 | 765 | } |
685 | 766 |
|
| 767 | +fn select_fix(typo: &typos::Typo<'_>) -> Option<usize> { |
| 768 | + let corrections = match &typo.corrections { |
| 769 | + typos::Status::Corrections(c) => c, |
| 770 | + _ => return None, |
| 771 | + } |
| 772 | + .clone(); |
| 773 | + |
| 774 | + if corrections.len() == 1 { |
| 775 | + let confirmation = Confirm::new() |
| 776 | + .with_prompt("Do you want to apply the fix suggested above?") |
| 777 | + .default(true) |
| 778 | + .show_default(true) |
| 779 | + .interact() |
| 780 | + .unwrap(); |
| 781 | + |
| 782 | + if confirmation { |
| 783 | + return Some(0); |
| 784 | + } |
| 785 | + } else { |
| 786 | + let mut items = corrections.clone(); |
| 787 | + |
| 788 | + items.insert(0, std::borrow::Cow::from("None (skip)")); |
| 789 | + let selection = Select::new() |
| 790 | + .with_prompt("Please choose one of the following suggestions") |
| 791 | + .items(&items) |
| 792 | + .default(0) |
| 793 | + .interact() |
| 794 | + .unwrap(); |
| 795 | + if selection != 0 { |
| 796 | + return Some(selection - 1); |
| 797 | + } |
| 798 | + } |
| 799 | + |
| 800 | + None |
| 801 | +} |
| 802 | + |
686 | 803 | pub fn walk_path( |
687 | 804 | walk: ignore::Walk, |
688 | 805 | checks: &dyn FileChecker, |
|
0 commit comments