packages feed

diagnose 1.7.2 → 1.8.0

raw patch · 9 files changed

+129/−104 lines, 9 filesdep +wcwidthdep ~aesondep ~bytestringdep ~data-defaultPVP ok

version bump matches the API change (PVP)

Dependencies added: wcwidth

Dependency ranges changed: aeson, bytestring, data-default, hashable, prettyprinter, prettyprinter-ansi-terminal, text

API changes (from Hackage documentation)

- Error.Diagnose.Diagnostic: printDiagnostic :: (MonadIO m, Pretty msg) => Handle -> Bool -> Bool -> Diagnostic msg -> m ()
+ Error.Diagnose.Diagnostic: printDiagnostic :: (MonadIO m, Pretty msg) => Handle -> Bool -> Bool -> Int -> Diagnostic msg -> m ()

Files

README.md view
@@ -25,6 +25,8 @@ - Diagnostics can be exported to JSON, if you don't quite like the rendering as it is, or if you need to transmit them to e.g. a website - Plug and play (mega)parsec integration and it magically works with your parsers! - Support for optional custom error codes, if you want to go the Rust way+- Variable width Unicode characters are handled in a crossplatform manner+- TAB characters have custom sizes specified when printing a diagnostic, so that *you* decide the width of a TAB, not your terminal emulator!  ## Usage @@ -97,17 +99,14 @@ let diagnostic' = addReport diagnostic beautifulExample  -- Print with unicode characters and colors-printDiagnostic stdout True True diagnostic'+printDiagnostic stdout True True 4 diagnostic' ```  More examples are given in the [`test/rendering`](./test/rendering) folder.  ## TODO list -- Handle variable-width characters such as tabs or some unicode characters.--  For tabs, we may just resort to having the user fix a given number of-  spaces in the `printDiagnostic` function.+<< empty, for now >>  ## License 
diagnose.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           diagnose-version:        1.7.2+version:        1.8.0 synopsis:       Beautiful error reporting done easily description:    This package provides a simple way of getting beautiful compiler/interpreter errors                 using a very simple interface for the programmer.@@ -65,17 +65,18 @@   ghc-options: -Wall -Wextra   build-depends:       base >=4.7 && <5-    , bytestring ==0.10.*-    , data-default ==0.7.*-    , hashable ==1.3.*-    , prettyprinter ==1.7.*-    , prettyprinter-ansi-terminal ==1.1.*-    , text ==1.2.*+    , data-default >=0.7 && <1+    , hashable >=1.3 && <2+    , prettyprinter >=1.7.0 && <2+    , prettyprinter-ansi-terminal >=1.1.0 && <2+    , text >=1.2 && <2     , unordered-containers ==0.2.*+    , wcwidth >=0.0.1 && <1   if flag(json)     cpp-options: -DUSE_AESON     build-depends:-        aeson ==1.5.*+        aeson >=1.5 && <2+      , bytestring >=0.9 && <1   if flag(megaparsec-compat)     build-depends:         containers ==0.6.*@@ -105,18 +106,19 @@   ghc-options: -Wall -Wextra -threaded -rtsopts -with-rtsopts=-N   build-depends:       base >=4.7 && <5-    , bytestring ==0.10.*-    , data-default ==0.7.*+    , data-default >=0.7 && <1     , diagnose-    , hashable ==1.3.*-    , prettyprinter ==1.7.*-    , prettyprinter-ansi-terminal ==1.1.*-    , text ==1.2.*+    , hashable >=1.3 && <2+    , prettyprinter >=1.7.0 && <2+    , prettyprinter-ansi-terminal >=1.1.0 && <2+    , text >=1.2 && <2     , unordered-containers ==0.2.*+    , wcwidth >=0.0.1 && <1   if flag(json)     cpp-options: -DUSE_AESON     build-depends:-        aeson ==1.5.*+        aeson >=1.5 && <2+      , bytestring >=0.9 && <1   if flag(megaparsec-compat)     build-depends:         containers ==0.6.*@@ -143,18 +145,19 @@   ghc-options: -Wall -Wextra -threaded -rtsopts -with-rtsopts=-N   build-depends:       base >=4.7 && <5-    , bytestring ==0.10.*-    , data-default ==0.7.*+    , data-default >=0.7 && <1     , diagnose-    , hashable ==1.3.*-    , prettyprinter ==1.7.*-    , prettyprinter-ansi-terminal ==1.1.*-    , text ==1.2.*+    , hashable >=1.3 && <2+    , prettyprinter >=1.7.0 && <2+    , prettyprinter-ansi-terminal >=1.1.0 && <2+    , text >=1.2 && <2     , unordered-containers ==0.2.*+    , wcwidth >=0.0.1 && <1   if flag(json)     cpp-options: -DUSE_AESON     build-depends:-        aeson ==1.5.*+        aeson >=1.5 && <2+      , bytestring >=0.9 && <1   if flag(megaparsec-compat)     build-depends:         containers ==0.6.*@@ -180,18 +183,19 @@   ghc-options: -Wall -Wextra -threaded -rtsopts -with-rtsopts=-N   build-depends:       base >=4.7 && <5-    , bytestring ==0.10.*-    , data-default ==0.7.*+    , data-default >=0.7 && <1     , diagnose-    , hashable ==1.3.*-    , prettyprinter ==1.7.*-    , prettyprinter-ansi-terminal ==1.1.*-    , text ==1.2.*+    , hashable >=1.3 && <2+    , prettyprinter >=1.7.0 && <2+    , prettyprinter-ansi-terminal >=1.1.0 && <2+    , text >=1.2 && <2     , unordered-containers ==0.2.*+    , wcwidth >=0.0.1 && <1   if flag(json)     cpp-options: -DUSE_AESON     build-depends:-        aeson ==1.5.*+        aeson >=1.5 && <2+      , bytestring >=0.9 && <1   if flag(megaparsec-compat)     build-depends:         containers ==0.6.*
src/Error/Diagnose.hs view
@@ -150,6 +150,8 @@ -- --   - A 'Bool' set to 'False' if you don't want colors in the end result. --+--   - A 'Int' describing the number of spaces with which to output a TAB character.+-- --   - And finally the 'Diagnostic' to output.  -- $diagnostic_json@@ -218,7 +220,7 @@ --   >            --   Creates a new diagnostic with no default hints from the bundle returned by megaparsec --   >         diag' = addFile diag filename content --   >            --   Add the file used when parsing with the same filename given to 'MP.runParser'---   >     in printDiagnostic stderr True True diag'+--   >     in printDiagnostic stderr True True 4 diag' --   >   Right res   -> print res -- --   This example will return the following error message (assuming default instances for @'Error.Diagnose.Compat.Megaparsec.HasHints' 'Data.Void.Void' msg@):@@ -259,7 +261,7 @@ --   >            --   Creates a new diagnostic with no default hints from the bundle returned by megaparsec --   >         diag' = addFile diag filename content --   >            --   Add the file used when parsing with the same filename given to 'MP.runParser'---   >     in printDiagnostic stderr True True diag'+--   >     in printDiagnostic stderr True True 4 diag' --   >   Right res  -> print res -- --   This will output the following errr on @stderr@:
src/Error/Diagnose/Diagnostic/Internal.hs view
@@ -69,11 +69,13 @@   Pretty msg =>   -- | Should we use unicode when printing paths?   Bool ->-  -- | The diagnostic to print+  -- | The number of spaces each TAB character will span.+  Int ->+  -- | The diagnostic to print.   Diagnostic msg ->   Doc AnsiStyle-prettyDiagnostic withUnicode (Diagnostic reports file) =-  fold . intersperse hardline $ prettyReport file withUnicode <$> reports+prettyDiagnostic withUnicode tabSize (Diagnostic reports file) =+  fold . intersperse hardline $ prettyReport file withUnicode tabSize <$> reports {-# INLINE prettyDiagnostic #-}  -- | Prints a 'Diagnostic' onto a specific 'Handle'.@@ -85,11 +87,13 @@   Bool ->   -- | 'False' to disable colors.   Bool ->+  -- | The number of spaces each TAB character will span.+  Int ->   -- | The diagnostic to output.   Diagnostic msg ->   m ()-printDiagnostic handle withUnicode withColors diag =-  liftIO $ hPutDoc handle (unlessId withColors unAnnotate $ prettyDiagnostic withUnicode diag)+printDiagnostic handle withUnicode withColors tabSize diag =+  liftIO $ hPutDoc handle (unlessId withColors unAnnotate $ prettyDiagnostic withUnicode tabSize diag)   where     unlessId cond app = if cond then id else app     {-# INLINE unlessId #-}
src/Error/Diagnose/Report/Internal.hs view
@@ -4,9 +4,8 @@ {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}- {-# OPTIONS -Wno-name-shadowing #-}+{-# LANGUAGE TypeApplications #-}  -- | -- Module      : Error.Diagnose.Report.Internal@@ -27,15 +26,16 @@ #endif import Control.Applicative ((<|>)) import Data.Bifunctor (bimap, first, second)+import Data.Char.WCWidth (wcwidth) import Data.Default (def) import Data.Foldable (fold) import Data.Function (on) import Data.Functor ((<&>)) import Data.HashMap.Lazy (HashMap) import qualified Data.HashMap.Lazy as HashMap+import qualified Data.HashMap.Lazy as IntMap import qualified Data.List as List import qualified Data.List.Safe as List-import Data.Ord (Down (..)) import Error.Diagnose.Position import Prettyprinter (Doc, Pretty (..), align, annotate, colon, hardline, lbracket, rbracket, space, width, (<+>)) import Prettyprinter.Internal (Doc (..))@@ -136,10 +136,12 @@   HashMap FilePath [String] ->   -- | Should we print paths in unicode?   Bool ->+  -- | The number of spaces each TAB character will span+  Int ->   -- | The whole report to output   Report msg ->   Doc AnsiStyle-prettyReport fileContent withUnicode (Report isError code message markers hints) =+prettyReport fileContent withUnicode tabSize (Report isError code message markers hints) =   let sortedMarkers = List.sortOn (fst . begin . fst) markers       -- sort the markers so that the first lines of the reports are the first lines of the file @@ -150,7 +152,8 @@       -- if there are no markers, then default to 3, else get the maximum between 3 and the length of the last marker        header =-        annotate (bold <> color if isError then Red else Yellow) $+        annotate+          (bold <> color if isError then Red else Yellow)           ( lbracket               <> ( if isError                      then "error"@@ -163,7 +166,7 @@    in {-               A report is of the form:               (1)    [error|warning]: <message>-              (2)           +-> <file>+              (2)           +--> <file>               (3)           :               (4)    <line> | <line of code>                             : <marker lines>@@ -174,7 +177,7 @@       -}        {- (1) -} header <> colon <+> align (pretty message)-        <> {- (2), (3), (4) -} fold (uncurry (prettySubReport fileContent withUnicode isError maxLineNumberLength) <$> groupedMarkers)+        <> {- (2), (3), (4) -} fold (uncurry (prettySubReport fileContent withUnicode isError tabSize maxLineNumberLength) <$> groupedMarkers)         <> {- (5) -} ( if                            | null hints && null markers -> mempty                            | null hints -> mempty@@ -280,6 +283,8 @@   Bool ->   -- | Is the current report an error report?   Bool ->+  -- | The number of spaces each TAB character will span+  Int ->   -- | The size of the biggest line number   Int ->   -- | Is this sub-report the first one in the list?@@ -287,7 +292,7 @@   -- | The list of line-ordered markers appearing in a single file   [(Position, Marker msg)] ->   Doc AnsiStyle-prettySubReport fileContent withUnicode isError maxLineNumberLength isFirst markers =+prettySubReport fileContent withUnicode isError tabSize maxLineNumberLength isFirst markers =   let (markersPerLine, multilineMarkers) = splitMarkersPerLine markers       -- split the list on whether markers are multiline or not @@ -312,7 +317,7 @@    in {- (2) -} hardline <> fileMarker         <> hardline           <+> {- (3) -} pipePrefix maxLineNumberLength withUnicode-        <> {- (4) -} prettyAllLines fileContent withUnicode isError maxLineNumberLength sortedMarkersPerLine multilineMarkers allLineNumbers+        <> {- (4) -} prettyAllLines fileContent withUnicode isError tabSize maxLineNumberLength sortedMarkersPerLine multilineMarkers allLineNumbers  isThisMarker :: Marker msg -> Bool isThisMarker (This _) = True@@ -333,13 +338,15 @@   HashMap FilePath [String] ->   Bool ->   Bool ->+  -- | The number of spaces each TAB character will span   Int ->+  Int ->   [(Int, [(Position, Marker msg)])] ->   [(Position, Marker msg)] ->   [Int] ->   Doc AnsiStyle-prettyAllLines _ _ _ _ _ [] [] = mempty-prettyAllLines _ withUnicode isError leftLen _ multiline [] =+prettyAllLines _ _ _ _ _ _ [] [] = mempty+prettyAllLines _ withUnicode isError _ leftLen _ multiline [] =   let colorOfLastMultilineMarker = maybe mempty (markerColor isError . snd) (List.safeLast multiline)       -- take the color of the last multiline marker in case we need to add additional bars @@ -358,7 +365,7 @@       showMultilineMarkerMessages [m] = [showMultilineMarkerMessage m True]       showMultilineMarkerMessages (m : ms) = showMultilineMarkerMessage m False : showMultilineMarkerMessages ms    in prefixWithBar colorOfLastMultilineMarker <> prefix <> fold (List.intersperse prefix $ showMultilineMarkerMessages $ reverse multiline)-prettyAllLines files withUnicode isError leftLen inline multiline (line : ls) =+prettyAllLines files withUnicode isError tabSize leftLen inline multiline (line : ls) =   {-       A line of code is composed of:       (1)     <line> | <source code>@@ -399,47 +406,50 @@                 <> space        allMarkersInLine = {- List.sortOn fst $ -} allInlineMarkersInLine <> allMultilineMarkersInLine++      (widths, renderedCode) = getLine_ files (allMarkersInLine <> allMultilineMarkersSpanningLine) line tabSize isError    in hardline         <> {- (1) -} linePrefix leftLen line withUnicode <+> additionalPrefix-        <> getLine_ files (allMarkersInLine <> allMultilineMarkersSpanningLine) line isError-        <> {- (2) -} showAllMarkersInLine (not $ null multiline) inSpanOfMultiline colorOfFirstMultilineMarker withUnicode isError leftLen allInlineMarkersInLine-        <> {- (3) -} prettyAllLines files withUnicode isError leftLen inline multiline ls+        <> renderedCode+        <> {- (2) -} showAllMarkersInLine (not $ null multiline) inSpanOfMultiline colorOfFirstMultilineMarker withUnicode isError leftLen widths allInlineMarkersInLine+        <> {- (3) -} prettyAllLines files withUnicode isError tabSize leftLen inline multiline ls  -- |-getLine_ :: HashMap FilePath [String] -> [(Position, Marker msg)] -> Int -> Bool -> Doc AnsiStyle-getLine_ files markers line isError = case List.safeIndex (line - 1) =<< (HashMap.!?) files . file . fst =<< List.safeHead markers of-  Nothing -> annotate (bold <> colorDull Magenta) "<no line>"+getLine_ :: HashMap FilePath [String] -> [(Position, Marker msg)] -> Int -> Int -> Bool -> (IntMap.HashMap Int Int, Doc AnsiStyle)+getLine_ files markers line tabSize isError = case List.safeIndex (line - 1) =<< (HashMap.!?) files . file . fst =<< List.safeHead markers of+  Nothing -> (mempty, annotate (bold <> colorDull Magenta) "<no line>")   Just code ->-    fold $-      indexed code <&> \(n, c) ->-        let colorizingMarkers = flip-              filter-              markers-              \(Position (bl, bc) (el, ec) _, _) ->-                if bl == el-                  then n >= bc && n < ec-                  else (bl == line && n >= bc) || (el == line && n < ec)-         in maybe id ((\m -> annotate (bold <> markerColor isError m)) . snd) (List.safeHead colorizingMarkers) (pretty c)+    let (tabs, code') = indexedWithTabsReplaced code+     in ( tabs,+          fold $+            code' <&> \(n, c) ->+              let colorizingMarkers = flip+                    filter+                    markers+                    \(Position (bl, bc) (el, ec) _, _) ->+                      if bl == el+                        then n >= bc && n < ec+                        else (bl == line && n >= bc) || (el == line && n < ec)+               in maybe id ((\m -> annotate (bold <> markerColor isError m)) . snd) (List.safeHead colorizingMarkers) (pretty c)+        )   where-    indexed :: [a] -> [(Int, a)]-    indexed = goIndexed 1+    indexedWithTabsReplaced :: String -> (IntMap.HashMap Int Int, [(Int, Char)])+    indexedWithTabsReplaced = goIndexed 1 -    goIndexed :: Int -> [a] -> [(Int, a)]-    goIndexed _ [] = []-    goIndexed n (x : xs) = (n, x) : goIndexed (n + 1) xs+    goIndexed :: Int -> String -> (IntMap.HashMap Int Int, [(Int, Char)])+    goIndexed _ [] = (mempty, [])+    goIndexed n ('\t' : xs) = bimap (IntMap.insert n tabSize) (replicate tabSize (n, ' ') <>) (goIndexed (n + 1) xs)+    goIndexed n (x : xs) = bimap (IntMap.insert n (wcwidth x)) ((n, x) :) (goIndexed (n + 1) xs)  -- |-showAllMarkersInLine :: Pretty msg => Bool -> Bool -> (Doc AnsiStyle -> Doc AnsiStyle) -> Bool -> Bool -> Int -> [(Position, Marker msg)] -> Doc AnsiStyle-showAllMarkersInLine _ _ _ _ _ _ [] = mempty-showAllMarkersInLine hasMultilines inSpanOfMultiline colorMultilinePrefix withUnicode isError leftLen ms =+showAllMarkersInLine :: Pretty msg => Bool -> Bool -> (Doc AnsiStyle -> Doc AnsiStyle) -> Bool -> Bool -> Int -> IntMap.HashMap Int Int -> [(Position, Marker msg)] -> Doc AnsiStyle+showAllMarkersInLine _ _ _ _ _ _ _ [] = mempty+showAllMarkersInLine hasMultilines inSpanOfMultiline colorMultilinePrefix withUnicode isError leftLen widths ms =   let maxMarkerColumn = snd $ end $ fst $ List.last $ List.sortOn (snd . end . fst) ms-      specialPrefix =-        if inSpanOfMultiline-          then colorMultilinePrefix (if withUnicode then "│ " else "| ") <> space-          else-            if hasMultilines-              then colorMultilinePrefix "  " <> space-              else mempty+      specialPrefix+        | inSpanOfMultiline = colorMultilinePrefix (if withUnicode then "│ " else "| ") <> space+        | hasMultilines = colorMultilinePrefix "  " <> space+        | otherwise = mempty    in -- get the maximum end column, so that we know when to stop looking for other markers on the same line       hardline <+> dotPrefix leftLen withUnicode <+> (if List.null ms then mempty else specialPrefix <> showMarkers 1 maxMarkerColumn <> showMessages specialPrefix ms maxMarkerColumn)   where@@ -449,12 +459,15 @@         let allMarkers = flip filter ms \(Position (_, bc) (_, ec) _, _) -> n >= bc && n < ec          in -- only consider markers which span onto the current column             case allMarkers of-              [] -> space <> showMarkers (n + 1) lineLen+              [] -> fold (replicate (IntMap.lookupDefault 0 n widths) space) <> showMarkers (n + 1) lineLen               (Position {..}, marker) : _ ->-                if snd begin == n-                  then annotate (markerColor isError marker) (if withUnicode then "┬" else "^") <> showMarkers (n + 1) lineLen-                  else annotate (markerColor isError marker) (if withUnicode then "─" else "-") <> showMarkers (n + 1) lineLen-    -- if the marker just started on this column, output a caret, else output a dash+                annotate+                  (markerColor isError marker)+                  ( if snd begin == n+                      then (if withUnicode then "┬" else "^") <> fold (replicate (IntMap.lookupDefault 0 n widths - 1) if withUnicode then "─" else "-")+                      else fold (replicate (IntMap.lookupDefault 0 n widths) if withUnicode then "─" else "-")+                  )+                  <> showMarkers (n + 1) lineLen      showMessages specialPrefix ms lineLen = case List.safeUncons ms of       Nothing -> mempty -- no more messages to show@@ -467,15 +480,16 @@             allColumns _ [] = (1, [])             allColumns n ms@((Position (_, bc) _ _, col) : ms')               | n == bc = bimap (+ 1) (col :) (allColumns (n + 1) ms')-              | n < bc = bimap (+ 1) (space :) (allColumns (n + 1) ms)-              | otherwise = bimap (+ 1) (space :) (allColumns (n + 1) ms')+              | n < bc = bimap (+ 1) (replicate (IntMap.lookupDefault 0 n widths) space <>) (allColumns (n + 1) ms)+              | otherwise = bimap (+ 1) (replicate (IntMap.lookupDefault 0 n widths) space <>) (allColumns (n + 1) ms')             -- transform the list of remaining markers into a single document line              hasSuccessor = length filteredPipes /= length pipes              lineStart pipes =               let (n, docs) = allColumns 1 $ List.sortOn (snd . begin . fst) pipes-               in dotPrefix leftLen withUnicode <+> specialPrefix <> fold docs <> pretty (replicate (bc - n) ' ')+                  numberOfSpaces = sum [IntMap.lookupDefault 0 x widths | x <- [n .. bc - 1]] -- bc - n+               in dotPrefix leftLen withUnicode <+> specialPrefix <> fold docs <> pretty (replicate numberOfSpaces ' ')             -- the start of the line contains the "dot"-prefix as well as all the pipes for all the still not rendered marker messages              prefix =@@ -485,11 +499,11 @@                   pipesBeforeRendered = pipesBefore <&> second \marker -> annotate (markerColor isError marker) (if withUnicode then "│" else "|")                   -- pre-render pipes which are before because they will be shown -                  lastBeginPosition = snd . begin . fst <$> List.safeLast (List.sortOn (Down . snd . begin . fst) pipesAfter)+                  lastBeginPosition = snd . begin . fst <$> List.safeHead (List.sortOn (snd . begin . fst) pipesAfter)                    lineLen = case lastBeginPosition of                     Nothing -> 0-                    Just col -> col - bc+                    Just col -> sum [IntMap.lookupDefault 0 x widths | x <- [bc .. col - 1]]                    currentPipe =                     if
test/megaparsec/Spec.hs view
@@ -30,8 +30,8 @@       res2 = first (errorDiagnosticFromBundle Nothing "Parse error on input" Nothing) $ MP.runParser @Void (MP.some MP.decimal <* MP.eof) filename content2    case res1 of-    Left diag -> printDiagnostic stdout True True (addFile diag filename (Text.unpack content1) :: Diagnostic String)+    Left diag -> printDiagnostic stdout True True 4 (addFile diag filename (Text.unpack content1) :: Diagnostic String)     Right res -> print res   case res2 of-    Left diag -> printDiagnostic stdout True True (addFile diag filename (Text.unpack content2) :: Diagnostic String)+    Left diag -> printDiagnostic stdout True True 4 (addFile diag filename (Text.unpack content2) :: Diagnostic String)     Right res -> print res
test/parsec/Repro2.hs view
@@ -29,8 +29,8 @@  main :: IO () main = do-  either (printDiagnostic stderr True True) print $ diagParse parser1 "issues/2.txt" "\\1"-  either (printDiagnostic stderr True True) print $ diagParse parser2 "issues/2.txt" "\\1"+  either (printDiagnostic stderr True True 4) print $ diagParse parser1 "issues/2.txt" "\\1"+  either (printDiagnostic stderr True True 4) print $ diagParse parser2 "issues/2.txt" "\\1"  -- smaller example op' :: String -> Parser String
test/parsec/Spec.hs view
@@ -30,10 +30,10 @@       res2 = first (errorDiagnosticFromParseError Nothing "Parse error on input" Nothing) $ P.parse (P.many1 P.digit <* P.eof) filename content2    case res1 of-    Left diag -> printDiagnostic stdout True True (addFile diag filename (Text.unpack content1) :: Diagnostic String)+    Left diag -> printDiagnostic stdout True True 4 (addFile diag filename (Text.unpack content1) :: Diagnostic String)     Right res -> print res   case res2 of-    Left diag -> printDiagnostic stdout True True (addFile diag filename (Text.unpack content2) :: Diagnostic String)+    Left diag -> printDiagnostic stdout True True 4 (addFile diag filename (Text.unpack content2) :: Diagnostic String)     Right res -> print res    -- all issue reproduction
test/rendering/Spec.hs view
@@ -31,7 +31,7 @@             ("somefile.zc", "let id<a>(x : a) : a := x\n  + 1"),             ("err.nst", "\n\n\n\n    = jmp g\n\n    g: forall(s: Ts, e: Tc).{ %r0: *s64 | s -> e }"),             ("unsized.nst", "main: forall(a: Ta, s: Ts, e: Tc).{ %r5: forall().{| s -> e } | s -> %r5 }\n    = salloc a\n    ; sfree\n"),-            ("unicode.txt", "±⅀\t★♲♥🎉⑳⓴ჳᏁℳ爪")+            ("unicode.txt", "±⅀\t★♲♥🎉汉⑳⓴ჳᏁℳ爪")           ]    let reports =@@ -70,9 +70,9 @@   let diag = HashMap.foldlWithKey' addFile (foldr (flip addReport) def reports) files    hPutStrLn stdout "\n\nWith unicode: ─────────────────────────\n"-  printDiagnostic stdout True True diag+  printDiagnostic stdout True True 4 diag   hPutStrLn stdout "\n\nWithout unicode: ----------------------\n"-  printDiagnostic stdout False True diag+  printDiagnostic stdout False True 4 diag #ifdef USE_AESON   hPutStrLn stdout "\n\nAs JSON: ------------------------------\n"   BS.hPutStr stdout (diagnosticToJson diag)@@ -354,5 +354,7 @@   err     (Just "❎")     "ⓈⓉⓇⒶⓃⒼⒺ ⓊⓃⒾⒸⓄⒹⒺ"-    [(Position (1, 1) (1, 7) "unicode.txt", This "should work fine 🎉")]+    [ (Position (1, 1) (1, 7) "unicode.txt", This "should work fine 🎉"),+      (Position (1, 7) (1, 9) "unicode.txt", Where "After TAB")+    ]     []