@@ -0,0 +1,198 @@
1+-- | Helpers for formatting of comments.
3+{-# LANGUAGE LambdaCase #-}
4+{-# LANGUAGE OverloadedStrings #-}
6+module Ormolu.Printer.Comments
12+ -- * Functions for working with comments
19+import ApiAnnotation (AnnKeywordId (AnnModule))
22+import Data.List (isPrefixOf)
23+import Data.Maybe (mapMaybe)
24+import Language.Haskell.GHC.ExactPrint.Types
25+import Ormolu.Printer.Internal
27+import qualified Data.Text as T
29+-- | Placement instructions for comments.
32+ = Decoration Decorator Decorator
35+-- | Decorator in this context is a thing to put before\/after a comment.
38+ = NoDec -- ^ Output nothing
39+ | SpaceDec -- ^ Output single space
40+ | NewlineDec -- ^ Output single newline
43+-- | Position: before vs after.
50+-- | For which type of AST leaf we're preparing the comments.
54+ | Other -- ^ Other element
57+-- | Output a bunch of 'Comment's. 'DeltaPos'es are used to insert extra
58+-- space between the comments when necessary.
60+spitComments :: [(Comment, Decoration)] -> R ()
61+spitComments = mapM_ $ \(comment, (Decoration d0 d1)) -> do
62+ let spitDecorator = \case
65+ NewlineDec -> newline
70+-- | Output a 'Comment'.
72+spitComment :: Comment -> R ()
73+spitComment (Comment str _ _) =
76+ then handleOne (normalizePragma str)
77+ else forM_ (normalizeIndent str) handleOne
83+ isMultiline x = not ("--" `isPrefixOf` x)
84+ isPragma x = "{-#" `isPrefixOf` x
85+ normalizeIndent = fmap (dropWhile (== ' ')) . lines
86+ normalizePragma = unwords . words
88+-- | Partition annotations to get a collection of 'Comment's preceding a
89+-- definition and following it. Every 'Comment' has corresponding
90+-- 'Decoration' which is used to understand how to decorate it.
93+ :: CommentMode -- ^ For which type of element we prepare comments
94+ -> SrcSpan -- ^ Span of element the comments are attached to
95+ -> [(KeywordId, DeltaPos)] -- ^ Annotations
96+ -> ([(Comment, Decoration)], [(Comment, Decoration)])
97+partitionDPs cmode refSpan anns =
99+ Module -> partitionDPsModule refSpan anns
100+ Other -> partitionDPsOther refSpan anns
102+-- | Try to partition comments as if for a module.
105+ :: SrcSpan -- ^ Span of element the comments are attached to
106+ -> [(KeywordId, DeltaPos)] -- ^ Annotations
107+ -> ([(Comment, Decoration)], [(Comment, Decoration)])
108+partitionDPsModule refSpan
109+ = bimap (takeComments Before) (takeComments After)
110+ . break ((== G AnnModule) . fst)
112+ takeComments pos = mapMaybe $ \(keywordId, dpos) -> do
113+ c <- annComment keywordId
114+ return (c, getDecoration Module pos refSpan (c, dpos))
116+-- | Partition comments according to their spans (works for everything but
120+ :: SrcSpan -- ^ Span of element the comments are attached to
121+ -> [(KeywordId, DeltaPos)] -- ^ Annotations
122+ -> ([(Comment, Decoration)], [(Comment, Decoration)])
123+partitionDPsOther refSpan
124+ = bimap (fmap (addDecoration Other Before refSpan))
125+ (fixupLastDec . fmap (addDecoration Other After refSpan))
126+ . break (followedBySpan refSpan . commentIdentifier . fst)
127+ . mapMaybe annComment'
129+ annComment' :: (KeywordId, DeltaPos) -> Maybe (Comment, DeltaPos)
130+ annComment' (keywordId, dpos) = do
131+ c <- annComment keywordId
133+ followedBySpan :: SrcSpan -> SrcSpan -> Bool
134+ followedBySpan spn0 spn1 =
135+ if srcSpanEnd spn0 < srcSpanStart spn1
139+-- | Last following comment cannot be standalone because in that case we get
140+-- redundant newlines.
142+fixupLastDec :: [(Comment, Decoration)] -> [(Comment, Decoration)]
144+fixupLastDec [(c, Decoration d0 _)] = [(c, Decoration d0 NoDec)]
145+fixupLastDec (c:cs) = c : fixupLastDec cs
147+-- | If 'KeywordId' is a comment, extract it.
149+annComment :: KeywordId -> Maybe Comment
150+annComment (AnnComment x) = Just x
151+annComment _ = Nothing
153+-- | Replace 'DeltaPos' with 'Decoration'.
156+ :: CommentMode -- ^ For which type of element we prepare comments
157+ -> Position -- ^ Is this for comments before or after?
158+ -> SrcSpan -- ^ Span of element the comments are attached to
159+ -> (Comment, DeltaPos) -- ^ Thing to decorate
160+ -> (Comment, Decoration)
161+addDecoration cmode pos refSpan (comment, dpos) =
163+ , getDecoration cmode pos refSpan (comment, dpos)
166+-- | Calculate decoration for a comment.
169+ :: CommentMode -- ^ For which type of element we prepare comments
170+ -> Position -- ^ Is this for comment before or after?
171+ -> SrcSpan -- ^ Span of element the comments are attached to
172+ -> (Comment, DeltaPos) -- ^ Thing to decorate
174+getDecoration cmode pos refSpan (c, (DP (r, _))) =
175+ Decoration preceedingDec followingDec
195+ case (refSpan, commentIdentifier c) of
196+ (RealSrcSpan spn0, RealSrcSpan spn1) ->
197+ srcSpanStartLine spn0 == srcSpanStartLine spn1