Fix error messages with wide character
Input may contain tabs and other characters whose byte widths do not correspond to their display widths. This causes error context underlining to be off when lines contain those characters Fixed by properly accounting for the display width of characters, as well as replacing tabs with spaces when printing error messages.
This commit is contained in:
parent
4d20ffeac4
commit
ac7634000e
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -10,6 +10,7 @@ dependencies = [
|
|||||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -15,3 +15,4 @@ itertools = "^0.5.5"
|
|||||||
lazy_static = "^0.2.1"
|
lazy_static = "^0.2.1"
|
||||||
regex = "^0.1.77"
|
regex = "^0.1.77"
|
||||||
tempdir = "^0.3.5"
|
tempdir = "^0.3.5"
|
||||||
|
unicode-width = "^0.1.3"
|
||||||
|
3
justfile
3
justfile
@ -50,6 +50,9 @@ nop:
|
|||||||
fail:
|
fail:
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
|
backtick-fail:
|
||||||
|
echo {{`exit 1`}}
|
||||||
|
|
||||||
# make a quine, compile it, and verify it
|
# make a quine, compile it, and verify it
|
||||||
quine: create
|
quine: create
|
||||||
cc tmp/gen0.c -o tmp/gen0
|
cc tmp/gen0.c -o tmp/gen0
|
||||||
|
@ -372,6 +372,96 @@ fn backtick_code_interpolation() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn backtick_code_interpolation_tab() {
|
||||||
|
integration_test(
|
||||||
|
&[],
|
||||||
|
"
|
||||||
|
backtick-fail:
|
||||||
|
\techo {{`exit 1`}}
|
||||||
|
",
|
||||||
|
1,
|
||||||
|
"",
|
||||||
|
"error: backtick failed with exit code 1
|
||||||
|
|
|
||||||
|
3 | echo {{`exit 1`}}
|
||||||
|
| ^^^^^^^^
|
||||||
|
",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn backtick_code_interpolation_tabs() {
|
||||||
|
integration_test(
|
||||||
|
&[],
|
||||||
|
"
|
||||||
|
backtick-fail:
|
||||||
|
\techo {{\t`exit 1`}}
|
||||||
|
",
|
||||||
|
1,
|
||||||
|
"",
|
||||||
|
"error: backtick failed with exit code 1
|
||||||
|
|
|
||||||
|
3 | echo {{ `exit 1`}}
|
||||||
|
| ^^^^^^^^
|
||||||
|
",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn backtick_code_interpolation_inner_tab() {
|
||||||
|
integration_test(
|
||||||
|
&[],
|
||||||
|
"
|
||||||
|
backtick-fail:
|
||||||
|
\techo {{\t`exit\t\t1`}}
|
||||||
|
",
|
||||||
|
1,
|
||||||
|
"",
|
||||||
|
"error: backtick failed with exit code 1
|
||||||
|
|
|
||||||
|
3 | echo {{ `exit 1`}}
|
||||||
|
| ^^^^^^^^^^^^^^^
|
||||||
|
",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn backtick_code_interpolation_leading_emoji() {
|
||||||
|
integration_test(
|
||||||
|
&[],
|
||||||
|
"
|
||||||
|
backtick-fail:
|
||||||
|
\techo 😬{{`exit 1`}}
|
||||||
|
",
|
||||||
|
1,
|
||||||
|
"",
|
||||||
|
"error: backtick failed with exit code 1
|
||||||
|
|
|
||||||
|
3 | echo 😬{{`exit 1`}}
|
||||||
|
| ^^^^^^^^
|
||||||
|
",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn backtick_code_interpolation_unicode_hell() {
|
||||||
|
integration_test(
|
||||||
|
&[],
|
||||||
|
"
|
||||||
|
backtick-fail:
|
||||||
|
\techo \t\t\t😬鎌鼬{{\t\t`exit 1 # \t\t\t😬鎌鼬`}}\t\t\t😬鎌鼬
|
||||||
|
",
|
||||||
|
1,
|
||||||
|
"",
|
||||||
|
"error: backtick failed with exit code 1
|
||||||
|
|
|
||||||
|
3 | echo 😬鎌鼬{{ `exit 1 # 😬鎌鼬`}} 😬鎌鼬
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn backtick_code_long() {
|
fn backtick_code_long() {
|
||||||
integration_test(
|
integration_test(
|
||||||
|
57
src/lib.rs
57
src/lib.rs
@ -14,6 +14,7 @@ extern crate regex;
|
|||||||
extern crate tempdir;
|
extern crate tempdir;
|
||||||
extern crate itertools;
|
extern crate itertools;
|
||||||
extern crate ansi_term;
|
extern crate ansi_term;
|
||||||
|
extern crate unicode_width;
|
||||||
|
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
|
||||||
@ -786,12 +787,41 @@ fn write_error_context(
|
|||||||
let red = maybe_red(f.alternate());
|
let red = maybe_red(f.alternate());
|
||||||
match text.lines().nth(line) {
|
match text.lines().nth(line) {
|
||||||
Some(line) => {
|
Some(line) => {
|
||||||
|
let mut i = 0;
|
||||||
|
let mut space_column = 0;
|
||||||
|
let mut space_line = String::new();
|
||||||
|
let mut space_width = 0;
|
||||||
|
for c in line.chars() {
|
||||||
|
if c == '\t' {
|
||||||
|
space_line.push_str(" ");
|
||||||
|
if i < column {
|
||||||
|
space_column += 4;
|
||||||
|
}
|
||||||
|
if i >= column && i < column + width.unwrap_or(1) {
|
||||||
|
space_width += 4;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if i < column {
|
||||||
|
space_column += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
if i >= column && i < column + width.unwrap_or(1) {
|
||||||
|
space_width += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
|
||||||
|
}
|
||||||
|
space_line.push(c);
|
||||||
|
}
|
||||||
|
i += c.len_utf8();
|
||||||
|
}
|
||||||
let line_number_width = line_number.to_string().len();
|
let line_number_width = line_number.to_string().len();
|
||||||
try!(write!(f, "{0:1$} |\n", "", line_number_width));
|
try!(write!(f, "{0:1$} |\n", "", line_number_width));
|
||||||
try!(write!(f, "{} | {}\n", line_number, line));
|
try!(write!(f, "{} | {}\n", line_number, space_line));
|
||||||
try!(write!(f, "{0:1$} |", "", line_number_width));
|
try!(write!(f, "{0:1$} |", "", line_number_width));
|
||||||
try!(write!(f, " {0:1$}{2}{3:^<4$}{5}", "", column,
|
if width == None {
|
||||||
red.prefix(), "", width.unwrap_or(1), red.suffix()));
|
try!(write!(f, " {0:1$}{2}^{3}", "", space_column, red.prefix(), red.suffix()));
|
||||||
|
} else {
|
||||||
|
try!(write!(f, " {0:1$}{2}{3:^<4$}{5}", "", space_column,
|
||||||
|
red.prefix(), "", space_width, red.suffix()));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
None => if index != text.len() {
|
None => if index != text.len() {
|
||||||
try!(write!(f, "internal error: Error has invalid line number: {}", line_number))
|
try!(write!(f, "internal error: Error has invalid line number: {}", line_number))
|
||||||
@ -1091,7 +1121,10 @@ enum RunError<'a> {
|
|||||||
impl<'a> Display for RunError<'a> {
|
impl<'a> Display for RunError<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
let red = maybe_red(f.alternate());
|
let red = maybe_red(f.alternate());
|
||||||
try!(write!(f, "{} ", red.paint("error:")));
|
let bold = maybe_bold(f.alternate());
|
||||||
|
try!(write!(f, "{} {}", red.paint("error:"), bold.prefix()));
|
||||||
|
|
||||||
|
let mut error_token = None;
|
||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
RunError::UnknownRecipes{ref recipes} => {
|
RunError::UnknownRecipes{ref recipes} => {
|
||||||
@ -1140,15 +1173,15 @@ impl<'a> Display for RunError<'a> {
|
|||||||
recipe, io_error)),
|
recipe, io_error)),
|
||||||
RunError::BacktickCode{code, ref token} => {
|
RunError::BacktickCode{code, ref token} => {
|
||||||
try!(write!(f, "backtick failed with exit code {}\n", code));
|
try!(write!(f, "backtick failed with exit code {}\n", code));
|
||||||
try!(write_token_error_context(f, token));
|
error_token = Some(token);
|
||||||
}
|
}
|
||||||
RunError::BacktickSignal{ref token, signal} => {
|
RunError::BacktickSignal{ref token, signal} => {
|
||||||
try!(write!(f, "backtick was terminated by signal {}", signal));
|
try!(write!(f, "backtick was terminated by signal {}", signal));
|
||||||
try!(write_token_error_context(f, token));
|
error_token = Some(token);
|
||||||
}
|
}
|
||||||
RunError::BacktickUnknownFailure{ref token} => {
|
RunError::BacktickUnknownFailure{ref token} => {
|
||||||
try!(write!(f, "backtick failed for an uknown reason"));
|
try!(write!(f, "backtick failed for an uknown reason"));
|
||||||
try!(write_token_error_context(f, token));
|
error_token = Some(token);
|
||||||
}
|
}
|
||||||
RunError::BacktickIoError{ref token, ref io_error} => {
|
RunError::BacktickIoError{ref token, ref io_error} => {
|
||||||
try!(match io_error.kind() {
|
try!(match io_error.kind() {
|
||||||
@ -1160,11 +1193,11 @@ impl<'a> Display for RunError<'a> {
|
|||||||
_ => write!(f, "backtick could not be run because of an IO \
|
_ => write!(f, "backtick could not be run because of an IO \
|
||||||
error while launching `sh`:\n{}", io_error),
|
error while launching `sh`:\n{}", io_error),
|
||||||
});
|
});
|
||||||
try!(write_token_error_context(f, token));
|
error_token = Some(token);
|
||||||
}
|
}
|
||||||
RunError::BacktickUtf8Error{ref token, ref utf8_error} => {
|
RunError::BacktickUtf8Error{ref token, ref utf8_error} => {
|
||||||
try!(write!(f, "backtick succeeded but stdout was not utf8: {}", utf8_error));
|
try!(write!(f, "backtick succeeded but stdout was not utf8: {}", utf8_error));
|
||||||
try!(write_token_error_context(f, token));
|
error_token = Some(token);
|
||||||
}
|
}
|
||||||
RunError::InternalError{ref message} => {
|
RunError::InternalError{ref message} => {
|
||||||
try!(write!(f, "internal error, this may indicate a bug in just: {}
|
try!(write!(f, "internal error, this may indicate a bug in just: {}
|
||||||
@ -1172,6 +1205,12 @@ consider filing an issue: https://github.com/casey/just/issues/new", message));
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try!(write!(f, "{}", bold.suffix()));
|
||||||
|
|
||||||
|
if let Some(token) = error_token {
|
||||||
|
try!(write_token_error_context(f, token));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user