Skip to content

Commit

Permalink
feat: add --interactive option to prompt for each change
Browse files Browse the repository at this point in the history
  • Loading branch information
Bnyro committed Oct 11, 2024
1 parent 3cb7080 commit b0bdd7e
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 7 deletions.
68 changes: 67 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/typos-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ colorchoice-clap = "1.0.3"
serde_regex = "1.1.0"
regex = "1.10.4"
encoding_rs = "0.8.34"
dialoguer = "0.11.0"

[dev-dependencies]
assert_fs = "1.1"
Expand Down
4 changes: 4 additions & 0 deletions crates/typos-cli/src/bin/typos-cli/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ pub(crate) struct Args {
#[arg(long, short = 'w', group = "mode", help_heading = "Mode")]
pub(crate) write_changes: bool,

/// Prompt for each suggested correction whether to write the fix
#[arg(long, short = 'i', group = "mode", help_heading = "Mode")]
pub(crate) interactive: bool,

/// Debug: Print each file that would be spellchecked.
#[arg(long, group = "mode", help_heading = "Mode")]
pub(crate) files: bool,
Expand Down
2 changes: 2 additions & 0 deletions crates/typos-cli/src/bin/typos-cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,8 @@ fn run_checks(args: &args::Args) -> proc_exit::ExitResult {
&typos_cli::file::Identifiers
} else if args.words {
&typos_cli::file::Words
} else if args.interactive {
&typos_cli::file::Interactive
} else if args.write_changes {
&typos_cli::file::FixTypos
} else if args.diff {
Expand Down
137 changes: 131 additions & 6 deletions crates/typos-cli/src/file.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use anyhow::Result;
use bstr::ByteSlice;
use dialoguer::{Confirm, Select};
use std::io::Read;
use std::io::Write;

Expand Down Expand Up @@ -127,12 +129,85 @@ impl FileChecker for FixTypos {
}
}
if !fixes.is_empty() {
let file_name = file_name.to_owned().into_bytes();
let new_name = fix_buffer(file_name, fixes, None);
let new_name =
String::from_utf8(new_name).expect("corrections are valid utf-8");
let new_path = path.with_file_name(new_name);
std::fs::rename(path, new_path)?;
fix_file_name(path, file_name, fixes, None)?;
}
}
}

Ok(())
}
}

#[derive(Debug, Clone, Copy)]
pub struct Interactive;

impl FileChecker for Interactive {
fn check_file(
&self,
path: &std::path::Path,
explicit: bool,
policy: &crate::policy::Policy<'_, '_, '_>,
reporter: &dyn report::Report,
) -> Result<(), std::io::Error> {
if policy.check_files {
let (buffer, content_type) = read_file(path, reporter)?;
let bc = buffer.clone();
if !explicit && !policy.binary && content_type.is_binary() {
let msg = report::BinaryFile { path };
reporter.report(msg.into())?;
} else {
let mut fixes = Vec::new();
let mut correction_indices = Vec::new();

let mut accum_line_num = AccumulateLineNum::new();
for typo in check_bytes(&bc, policy) {
let line_num = accum_line_num.line_num(&buffer, typo.byte_offset);
let (line, line_offset) = extract_line(&buffer, typo.byte_offset);
let msg = report::Typo {
context: Some(report::FileContext { path, line_num }.into()),
buffer: std::borrow::Cow::Borrowed(line),
byte_offset: line_offset,
typo: typo.typo.as_ref(),
corrections: typo.corrections.clone(),
};
reporter.report(msg.into())?;

if let Some(correction_index) = select_fix(&typo) {
fixes.push(typo);
correction_indices.push(correction_index);
}
}

if !fixes.is_empty() || path == std::path::Path::new("-") {
let buffer = fix_buffer(buffer, fixes, Some(correction_indices));
write_file(path, content_type, buffer, reporter)?;
}
}
}

if policy.check_filenames {
if let Some(file_name) = path.file_name().and_then(|s| s.to_str()) {
let mut fixes = Vec::new();
let mut correction_indices = Vec::new();

for typo in check_str(file_name, policy) {
let msg = report::Typo {
context: Some(report::PathContext { path }.into()),
buffer: std::borrow::Cow::Borrowed(file_name.as_bytes()),
byte_offset: typo.byte_offset,
typo: typo.typo.as_ref(),
corrections: typo.corrections.clone(),
};
reporter.report(msg.into())?;

if let Some(correction_index) = select_fix(&typo) {
fixes.push(typo);
correction_indices.push(correction_index);
}
}

if !fixes.is_empty() {
fix_file_name(path, file_name, fixes, Some(correction_indices))?;
}
}
}
Expand Down Expand Up @@ -678,6 +753,56 @@ fn fix_buffer(
buffer
}

fn fix_file_name<'a>(
path: &std::path::Path,
file_name: &'a str,
fixes: Vec<typos::Typo<'a>>,
correction_indices: Option<Vec<usize>>,
) -> Result<(), std::io::Error> {
let file_name = file_name.to_owned().into_bytes();
let new_name = fix_buffer(file_name, fixes, correction_indices);
let new_name = String::from_utf8(new_name).expect("corrections are valid utf-8");
let new_path = path.with_file_name(new_name);
std::fs::rename(path, new_path)?;
Ok(())
}

fn select_fix(typo: &typos::Typo<'_>) -> Option<usize> {
let corrections = match &typo.corrections {
typos::Status::Corrections(c) => c,
_ => return None,
}
.clone();

if corrections.len() == 1 {
let confirmation = Confirm::new()
.with_prompt("Do you want to apply the fix suggested above?")
.default(true)
.show_default(true)
.interact()
.unwrap();

if confirmation {
return Some(0);
}
} else {
let mut items = corrections.clone();

items.insert(0, std::borrow::Cow::from("None (skip)"));
let selection = Select::new()
.with_prompt("Please choose one of the following suggestions")
.items(&items)
.default(0)
.interact()
.unwrap();
if selection != 0 {
return Some(selection - 1);
}
}

None
}

pub fn walk_path(
walk: ignore::Walk,
checks: &dyn FileChecker,
Expand Down

0 comments on commit b0bdd7e

Please sign in to comment.