Repository: pest-parser/pest (language: rust)

Issue:
Error messages include ␊ on all lines
https://github.com/pest-parser/pest/commit/95881fd2e3a4fc278f73326b5a86b079eda41d24 changed `Error` so that both `new_from_pos` and `new_from_span` variants have `visualize_whitespace` called.  Prior to this, only `new_from_pos` variants had their whitespace visualized.

This has the side-effect of making *all* error messages include replacement symbols for their terminating newline.  You can see this on the pest editor here: https://pest-parser.github.io/?bin=18hayc#editor

```
 --> 1:1
  |
1 | goodbye␊
  | ^---
  |
  = expected hello
```

The extra LF symbol (␊) is confusing for end users, and is noise in cases like this where whitespace is irrelevant.  I would expect CR and NL to only be visualized when they are important to the error.


Discussion / hints:
Thanks for reporting this. I completely agree. If anyone won't grab this in the next few days, I'll fix this myself. If you need this urgently, I'll release a fixed version right away.
Not urgent.  For now we will live with the extra LFs in the error messages.  Thanks for looking into it!
If I understand this correctly, the fix involves just removing the calls to `visualize_whitespace` from `new_from_span`? If so, I can open a pull request.
I believe one would need to remove it from `new_from_pos`? The whitespace should only appear where it makes sense.
Hello, I'm new to using Pest and indeed found this to be confusing and assumed it was a bug. Did you have a fix @dragostis ?

@PvdBerg1998, I haven't had so much time to fix stuff lately. Feel free to open a PR for this.

Fix patch:
diff --git a/pest/src/error.rs b/pest/src/error.rs
index deb43745..312768b4 100644
--- a/pest/src/error.rs
+++ b/pest/src/error.rs
@@ -101,11 +101,18 @@ impl<R: RuleType> Error<R> {
     /// ```
     #[allow(clippy::needless_pass_by_value)]
     pub fn new_from_pos(variant: ErrorVariant<R>, pos: Position) -> Error<R> {
+        let visualize_ws = pos.match_char('\n') || pos.match_char('\r');
+        let line_of = pos.line_of();
+        let line = if visualize_ws {
+            visualize_whitespace(line_of)
+        } else {
+            line_of.replace(&['\r', '\n'][..], "")
+        };
         Error {
             variant,
             location: InputLocation::Pos(pos.pos()),
             path: None,
-            line: visualize_whitespace(pos.line_of()),
+            line,
             continued_line: None,
             line_col: LineColLocation::Pos(pos.line_col()),
         }
@@ -142,19 +149,31 @@ impl<R: RuleType> Error<R> {
     #[allow(clippy::needless_pass_by_value)]
     pub fn new_from_span(variant: ErrorVariant<R>, span: Span) -> Error<R> {
         let end = span.end_pos();
-
         let mut end_line_col = end.line_col();
         // end position is after a \n, so we want to point to the visual lf symbol
         if end_line_col.1 == 1 {
-            let mut visual_end = end.clone();
+            let mut visual_end = end;
             visual_end.skip_back(1);
             let lc = visual_end.line_col();
             end_line_col = (lc.0, lc.1 + 1);
         };
 
         let mut line_iter = span.lines();
-        let start_line = visualize_whitespace(line_iter.next().unwrap_or(""));
-        let continued_line = line_iter.last().map(visualize_whitespace);
+        let sl = line_iter.next().unwrap_or("");
+        let mut chars = span.as_str().chars();
+        let visualize_ws = matches!(chars.next(), Some('\n') | Some('\r'))
+            || matches!(chars.last(), Some('\n') | Some('\r'));
+        let start_line = if visualize_ws {
+            visualize_whitespace(sl)
+        } else {
+            sl.to_owned().replace(&['\r', '\n'][..], "")
+        };
+        let ll = line_iter.last();
+        let continued_line = if visualize_ws {
+            ll.map(&str::to_owned)
+        } else {
+            ll.map(visualize_whitespace)
+        };
 
         Error {
             variant,
@@ -538,7 +557,7 @@ mod tests {
             vec![
                 " --> 2:2",
                 "  |",
-                "2 | cd␊",
+                "2 | cd",
                 "  |  ^---",
                 "  |",
                 "  = unexpected 4, 5, or 6; expected 1, 2, or 3",
@@ -564,7 +583,7 @@ mod tests {
             vec![
                 " --> 2:2",
                 "  |",
-                "2 | cd␊",
+                "2 | cd",
                 "  |  ^---",
                 "  |",
                 "  = expected 1 or 2",
@@ -590,7 +609,7 @@ mod tests {
             vec![
                 " --> 2:2",
                 "  |",
-                "2 | cd␊",
+                "2 | cd",
                 "  |  ^---",
                 "  |",
                 "  = unexpected 4, 5, or 6",
@@ -616,7 +635,7 @@ mod tests {
             vec![
                 " --> 2:2",
                 "  |",
-                "2 | cd␊",
+                "2 | cd",
                 "  |  ^---",
                 "  |",
                 "  = unknown parsing error",
@@ -641,7 +660,7 @@ mod tests {
             vec![
                 " --> 2:2",
                 "  |",
-                "2 | cd␊",
+                "2 | cd",
                 "  |  ^---",
                 "  |",
                 "  = error: big one",
@@ -667,7 +686,7 @@ mod tests {
             vec![
                 " --> 2:2",
                 "  |",
-                "2 | cd␊",
+                "2 | cd",
                 "3 | efgh",
                 "  |  ^^",
                 "  |",
@@ -694,7 +713,7 @@ mod tests {
             vec![
                 " --> 1:2",
                 "  |",
-                "1 | ab␊",
+                "1 | ab",
                 "  | ...",
                 "3 | efgh",
                 "  |  ^^",
@@ -722,7 +741,7 @@ mod tests {
             vec![
                 " --> 1:6",
                 "  |",
-                "1 | abcdef␊",
+                "1 | abcdef",
                 "2 | gh",
                 "  | ^----^",
                 "  |",
@@ -808,7 +827,7 @@ mod tests {
             vec![
                 " --> 2:2",
                 "  |",
-                "2 | cd␊",
+                "2 | cd",
                 "  |  ^---",
                 "  |",
                 "  = unexpected 5, 6, or 7; expected 2, 3, or 4",
@@ -835,7 +854,7 @@ mod tests {
             vec![
                 " --> file.rs:2:2",
                 "  |",
-                "2 | cd␊",
+                "2 | cd",
                 "  |  ^---",
                 "  |",
                 "  = unexpected 4, 5, or 6; expected 1, 2, or 3",
diff --git a/pest/src/position.rs b/pest/src/position.rs
index 0a445bf7..e634da5c 100644
--- a/pest/src/position.rs
+++ b/pest/src/position.rs
@@ -318,6 +318,14 @@ impl<'i> Position<'i> {
         false
     }
 
+    /// Matches the char at the `Position` against a specified character and returns `true` if a match
+    /// was made. If no match was made, returns `false`.
+    /// `pos` will not be updated in either case.
+    #[inline]
+    pub(crate) fn match_char(&self, c: char) -> bool {
+        matches!(self.input[self.pos..].chars().next(), Some(cc) if c == cc)
+    }
+
     /// Matches the char at the `Position` against a filter function and returns `true` if a match
     /// was made. If no match was made, returns `false` and `pos` will not be updated.
     #[inline]


Test patch: