|
1 | 1 | use bstr::ByteSlice;
|
| 2 | +use dialoguer::{Confirm, Select}; |
2 | 3 | use std::io::Read;
|
3 | 4 | use std::io::Write;
|
4 | 5 |
|
@@ -137,6 +138,87 @@ impl FileChecker for FixTypos {
|
137 | 138 | }
|
138 | 139 | }
|
139 | 140 |
|
| 141 | +#[derive(Debug, Clone, Copy)] |
| 142 | +pub struct Interactive; |
| 143 | + |
| 144 | +impl FileChecker for Interactive { |
| 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 | + // HACK: we use the reporter to display the possible corrections to the user |
| 173 | + // this will be looking very ugly with the format set to anything else than json |
| 174 | + // technically we should only report typos when not correcting |
| 175 | + reporter.report(msg.into())?; |
| 176 | + |
| 177 | + if let Some(correction_index) = select_fix(&typo) { |
| 178 | + fixes.push((typo, correction_index)); |
| 179 | + } |
| 180 | + } |
| 181 | + |
| 182 | + if !fixes.is_empty() || path == std::path::Path::new("-") { |
| 183 | + let buffer = fix_buffer(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 | + // HACK: we use the reporter to display the possible corrections to the user |
| 202 | + // this will be looking very ugly with the format set to anything else than json |
| 203 | + // technically we should only report typos when not correcting |
| 204 | + reporter.report(msg.into())?; |
| 205 | + |
| 206 | + if let Some(correction_index) = select_fix(&typo) { |
| 207 | + fixes.push((typo, correction_index)); |
| 208 | + } |
| 209 | + } |
| 210 | + |
| 211 | + if !fixes.is_empty() { |
| 212 | + let new_path = fix_file_name(path, file_name, fixes.into_iter())?; |
| 213 | + std::fs::rename(path, new_path)?; |
| 214 | + } |
| 215 | + } |
| 216 | + } |
| 217 | + |
| 218 | + Ok(()) |
| 219 | + } |
| 220 | +} |
| 221 | + |
140 | 222 | #[derive(Debug, Clone, Copy)]
|
141 | 223 | pub struct DiffTypos;
|
142 | 224 |
|
@@ -675,6 +757,35 @@ fn fix_file_name<'a>(
|
675 | 757 | Ok(new_path)
|
676 | 758 | }
|
677 | 759 |
|
| 760 | +fn select_fix(typo: &typos::Typo<'_>) -> Option<usize> { |
| 761 | + let corrections = match &typo.corrections { |
| 762 | + typos::Status::Corrections(c) => c, |
| 763 | + _ => return None, |
| 764 | + }; |
| 765 | + |
| 766 | + if corrections.len() == 1 { |
| 767 | + Confirm::new() |
| 768 | + .with_prompt("Do you want to apply the fix suggested above?") |
| 769 | + .default(true) |
| 770 | + .show_default(true) |
| 771 | + .interact() |
| 772 | + .ok()?; |
| 773 | + |
| 774 | + Some(0) |
| 775 | + } else { |
| 776 | + let mut items = corrections.clone(); |
| 777 | + |
| 778 | + items.insert(0, std::borrow::Cow::from("None (skip)")); |
| 779 | + let selection = Select::new() |
| 780 | + .with_prompt("Please choose one of the following suggestions") |
| 781 | + .items(&items) |
| 782 | + .default(0) |
| 783 | + .interact() |
| 784 | + .ok()?; |
| 785 | + selection.checked_sub(1) |
| 786 | + } |
| 787 | +} |
| 788 | + |
678 | 789 | pub fn walk_path(
|
679 | 790 | walk: ignore::Walk,
|
680 | 791 | checks: &dyn FileChecker,
|
|
0 commit comments