@@ -7,18 +7,21 @@ module Ormolu.Printer.Meat.Declaration.Value
77 , p_pat
88 , p_hsExpr
99 , p_hsSplice
10+ , p_stringLit
1011 )
1112where
1213
1314import Bag (bagToList)
1415import BasicTypes
1516import Control.Monad
17+import Ctype (is_space)
1618import Data.Bool (bool)
1719import Data.Char (isPunctuation, isSymbol)
1820import Data.Data hiding (Infix, Prefix)
1921import Data.List (intersperse, sortOn)
2022import Data.List.NonEmpty (NonEmpty ((:|)))
2123import Data.Text (Text)
24+import qualified Data.Text as Text
2225import GHC
2326import Ormolu.Printer.Combinators
2427import Ormolu.Printer.Meat.Common
@@ -489,7 +492,11 @@ p_hsExpr = \case
489492 txt "?"
490493 atom name
491494 HsOverLit NoExt v -> atom (ol_val v)
492− HsLit NoExt lit -> atom lit
495+ HsLit NoExt lit ->
496+ case lit of
497+ HsString (SourceText s) _ -> p_stringLit s
498+ HsStringPrim (SourceText s) _ -> p_stringLit s
499+ r -> atom r
493500 HsLam NoExt mgroup ->
494501 p_matchGroup Lambda mgroup
495502 HsLamCase NoExt mgroup -> do
@@ -938,6 +945,55 @@ p_hsBracket = \case
938945 breakpoint'
939946 txt "|]"
940947
948+-- Print the source text of a string literal while indenting
949+-- gaps correctly.
950+
951+p_stringLit :: String -> R ()
952+p_stringLit src =
953+ let s = splitGaps src
954+ singleLine =
955+ txt $ Text.pack (mconcat s)
956+ multiLine =
957+ sitcc $ sep breakpoint (txt . Text.pack) (backslashes s)
958+ in vlayout singleLine multiLine
959+ where
960+ -- Split a string on gaps (backslash delimited whitespaces)
961+ --
962+ -- > splitGaps "bar\\ \\fo\\&o" == ["bar", "fo\\&o"]
963+ splitGaps :: String -> [String]
964+ splitGaps "" = []
965+ splitGaps s =
966+ let -- A backslash and a whitespace starts a "gap"
967+ p (Just '\\', _, _) = True
968+ p (_, '\\', Just c) | ghcSpace c = False
969+ p _ = True
970+ in case span p (zipPrevNext s) of
971+ (l, r) ->
972+ let -- drop the initial '\', any amount of 'ghcSpace', and another '\'
973+ r' = drop 1 . dropWhile ghcSpace . drop 1 $ map orig r
974+ in map orig l : splitGaps r'
975+
976+ -- GHC's definition of whitespaces in strings
977+ -- See: https://gitlab.haskell.org/ghc/ghc/blob/86753475/compiler/parser/Lexer.x#L1653
978+ ghcSpace :: Char -> Bool
979+ ghcSpace c = c <= '\x7f' && is_space c
980+
981+ -- Add backslashes to the inner side of the strings
982+ --
983+ -- > backslashes ["a", "b", "c"] == ["a\\", "\\b\\", "\\c"]
984+ backslashes :: [String] -> [String]
985+ backslashes (x:y:xs) = (x ++ "\\") : backslashes (('\\' : y) : xs)
986+ backslashes xs = xs
987+
988+ -- Attaches previous and next items to each list element
989+ zipPrevNext :: [a] -> [(Maybe a, a, Maybe a)]
990+ zipPrevNext xs =
991+ let z = zip (zip (Nothing : map Just xs) xs)
992+ (map Just (tail xs) ++ repeat Nothing)
993+ in map (\((p, x), n) -> (p, x, n)) z
994+
995+ orig (_, x, _) = x
996+
941997----------------------------------------------------------------------------
942998-- Helpers
943999