diff --git a/Cargo.lock b/Cargo.lock index b08c507..a3d23cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1576,6 +1576,7 @@ dependencies = [ "typos-vars", "unicase", "unicode-segmentation", + "unicode-width", "varcon-core", "yansi", ] diff --git a/Cargo.toml b/Cargo.toml index 84ca8fb..090e72a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,6 +91,7 @@ encoding = "0.2" kstring = { version = "2.0.0", features = ["serde"] } typed-arena = "2.0.1" maplit = "1.0" +unicode-width = "0.1.9" [dev-dependencies] assert_fs = "1.0" diff --git a/src/bin/typos-cli/report.rs b/src/bin/typos-cli/report.rs index 1eb6318..87e7e75 100644 --- a/src/bin/typos-cli/report.rs +++ b/src/bin/typos-cli/report.rs @@ -3,6 +3,8 @@ use std::io::{self, Write}; use std::sync::atomic; +use unicode_width::UnicodeWidthStr; + use typos_cli::report::{Context, Message, Report, Typo}; #[derive(Copy, Clone, Debug)] @@ -218,8 +220,11 @@ fn print_long_correction(msg: &Typo, palette: Palette) -> Result<(), std::io::Er let line_num = context.line_num.to_string(); let line_indent: String = itertools::repeat_n(" ", line_num.len()).collect(); - let hl_indent: String = itertools::repeat_n(" ", column).collect(); - let hl: String = itertools::repeat_n("^", msg.typo.len()).collect(); + let visible_column = UnicodeWidthStr::width(start.as_ref()); + let visible_len = UnicodeWidthStr::width(msg.typo); + + let hl_indent: String = itertools::repeat_n(" ", visible_column).collect(); + let hl: String = itertools::repeat_n("^", visible_len).collect(); writeln!(handle, "{} |", line_indent)?; writeln!( diff --git a/tests/cmd/stdin-failure-multiwidth.stdin b/tests/cmd/stdin-failure-multiwidth.stdin new file mode 100644 index 0000000..1af6419 --- /dev/null +++ b/tests/cmd/stdin-failure-multiwidth.stdin @@ -0,0 +1,34 @@ +--- +Korean character (NFC) +Grapheme clusters: 1, codepoints: 1, UnicodeWidthStr::width() == 2 + +한 Apropriate world + ^^^^^^^^^^ highlight here + +--- +Korean character (NFD). +Grapheme clusters: 1, codepoints: 3, UnicodeWidthStr::width() == 2 + +한 Apropriate world + ^^^^^^^^^^ highlight here + +--- +Eye in Speech Bubble Emoji (U+1F441 U+FE0F U+200D U+1F5E8 U+FE0F, Recommended Emoji ZWJ Sequences, v2.0) +Grapheme clusters: 1, codepoints: 5, UnicodeWidthStr::width() == 2 (Read NOTE: https://github.com/unicode-rs/unicode-width) + +👁️‍🗨️ Apropriate world + ^^^^^^^^^^ highlight here + +--- +Face with spiral eyes (U+1F635 U+200D U+1F4AB, Recommended Emoji ZWJ Sequence, v13.1) +Grapheme clusters: 1, codepoints: 3, UnicodeWidthStr::width() == 4 (Read NOTE: https://github.com/unicode-rs/unicode-width) + +😵‍💫 Apropriate world + ^^^^^^^^^^ highlight here + +--- +Horizontal tab (\t, U+09) +Grapheme clusters: 1, codepoints: 1, UnicodeWidthStr::width() == 0 + + Apropriate world +^^^^^^^^^^ highlight here \ No newline at end of file diff --git a/tests/cmd/stdin-failure-multiwidth.stdout b/tests/cmd/stdin-failure-multiwidth.stdout new file mode 100644 index 0000000..e6cf44a --- /dev/null +++ b/tests/cmd/stdin-failure-multiwidth.stdout @@ -0,0 +1,30 @@ +error: `Apropriate` should be `Appropriate` + --> -:5:2 + | +5 | 한 Apropriate world + | ^^^^^^^^^^ + | +error: `Apropriate` should be `Appropriate` + --> -:12:2 + | +12 | 한 Apropriate world + | ^^^^^^^^^^ + | +error: `Apropriate` should be `Appropriate` + --> -:19:2 + | +19 | 👁️‍🗨️ Apropriate world + | ^^^^^^^^^^ + | +error: `Apropriate` should be `Appropriate` + --> -:26:2 + | +26 | 😵‍💫 Apropriate world + | ^^^^^^^^^^ + | +error: `Apropriate` should be `Appropriate` + --> -:33:1 + | +33 | Apropriate world + | ^^^^^^^^^^ + | diff --git a/tests/cmd/stdin-failure-multiwidth.toml b/tests/cmd/stdin-failure-multiwidth.toml new file mode 100644 index 0000000..18f517f --- /dev/null +++ b/tests/cmd/stdin-failure-multiwidth.toml @@ -0,0 +1,3 @@ +bin.name = "typos" +args = "-" +status.code = 2 \ No newline at end of file