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 85ec8ca commit b4cabe8
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 1 deletion.
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
118 changes: 118 additions & 0 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 @@ -137,6 +139,87 @@ impl FileChecker for FixTypos {
}
}

#[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 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(),
};
// HACK: we use the reporter to display the possible corrections to the user
// this will be looking very ugly with the format set to anything else than json
// technically we should only report typos when not correcting
reporter.report(msg.into())?;

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

if !fixes.is_empty() || path == std::path::Path::new("-") {
let buffer = fix_buffer(buffer, fixes.into_iter());
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();

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(),
};
// HACK: we use the reporter to display the possible corrections to the user
// this will be looking very ugly with the format set to anything else than json
// technically we should only report typos when not correcting
reporter.report(msg.into())?;

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

if !fixes.is_empty() {
let new_path = fix_file_name(path, file_name, fixes.into_iter())?;
std::fs::rename(path, new_path)?;
}
}
}

Ok(())
}
}

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

Expand Down Expand Up @@ -675,6 +758,41 @@ fn fix_file_name<'a>(
Ok(new_path)
}

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

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 b4cabe8

Please sign in to comment.