module Language.Haskell.Formatter.Process.DetachComments (detachComments) where
import qualified Data.Foldable as Foldable
import qualified Data.List as List
import qualified Data.Map.Strict as Map
import qualified Data.Maybe as Maybe
import qualified Data.Monoid as Monoid
import qualified Data.Semigroup as Semigroup
import qualified Language.Haskell.Formatter.CommentCore as CommentCore
import qualified Language.Haskell.Formatter.ExactCode as ExactCode
import qualified Language.Haskell.Formatter.Location as Location
import qualified Language.Haskell.Formatter.Process.Code as Code
import qualified Language.Haskell.Formatter.Process.LineTool as LineTool
import qualified Language.Haskell.Formatter.Process.Note as Note
import qualified Language.Haskell.Formatter.Result as Result
import qualified Language.Haskell.Formatter.Source as Source
import qualified Language.Haskell.Formatter.Style as Style
import qualified Language.Haskell.Formatter.Toolkit.ListTool as ListTool
import qualified Language.Haskell.Formatter.Toolkit.StreamName as StreamName
newtype Reservation = Reservation (Map.Map Location.Line [Note.CommentBox])
deriving (Eq, Ord, Show)
instance Semigroup.Semigroup Reservation where
(Reservation left) <> (Reservation right) = Reservation merged
where merged = Map.unionWith merge left right
merge before after = concat [before, between, after]
where between
= [Note.EmptyLine |
hasActualComment (ListTool.maybeLast before) &&
hasActualComment (Maybe.listToMaybe after)]
hasActualComment maybeComment
= case maybeComment of
Nothing -> False
Just (Note.ActualComment _) -> True
Just Note.EmptyLine -> False
instance Monoid.Monoid Reservation where
mempty = Reservation Map.empty
detachComments ::
Style.Style ->
Code.LocatableCommentableCode ->
Result.Result ExactCode.ExactCode
detachComments _ locatableCommentable
= return $ ExactCode.create locatable' comments
where locatable' = LineTool.shiftCode shifter locatable
shifter = reservationShifter reservation
reservation = reserveForCode locatableCommentable
locatable = Code.dropComments locatableCommentable
comments = createComments stream reservation
stream = Location.streamName $ Location.getPortion locatable'
reservationShifter :: Reservation -> LineTool.Shifter
reservationShifter (Reservation reservation)
= LineTool.createShifter $ fmap commentsShift reservation
commentsShift :: [Note.CommentBox] -> LineTool.Shift
commentsShift = sum . fmap commentShift
commentShift :: Note.CommentBox -> LineTool.Shift
commentShift (Note.ActualComment comment)
= CommentCore.wrappedLineCount $ Note.commentCore comment
commentShift Note.EmptyLine = 1
reserveForCode :: Code.LocatableCommentableCode -> Reservation
reserveForCode = Foldable.foldMap reserveForNote
reserveForNote :: Note.LocationCommentNote -> Reservation
reserveForNote note = Monoid.mappend before after
where before = singleton lineBefore $ Note.commentsBefore commentNote
singleton line = Reservation . Map.singleton line
lineBefore = Location.getStartLine portion
portion = Location.getPortion note
commentNote = Note.commentNote note
after = singleton lineAfter $ Note.commentsAfter commentNote
lineAfter = succ $ Location.getEndLine portion
createComments :: StreamName.StreamName -> Reservation -> [Source.Comment]
createComments stream = accumulateReservation create
where create baseLine = snd . List.foldl' merge (baseLine, [])
merge (startLine, comments) box = (followingLine, comments')
where followingLine = Location.plus shift startLine
shift = commentShift box
comments' = Monoid.mappend comments commentsNow
commentsNow
= case box of
Note.ActualComment comment -> [createComment stream
startLine
comment]
Note.EmptyLine -> []
accumulateReservation ::
Monoid.Monoid m =>
(Location.Line -> [Note.CommentBox] -> m) ->
Reservation -> m
accumulateReservation create (Reservation reservation) = accumulation
where (_, accumulation) = Map.foldlWithKey' accumulate base reservation
accumulate (absoluteShift, structure) line comments
= (absoluteShift', structure')
where absoluteShift' = absoluteShift + relativeShift
relativeShift = commentsShift comments
structure' = Monoid.mappend structure part
part = create shiftedLine comments
shiftedLine = Location.plus absoluteShift line
base = (noShift, Monoid.mempty)
noShift = 0
createComment ::
StreamName.StreamName ->
Location.Line -> Note.IndentedComment -> Source.Comment
createComment stream startLine comment = Source.createComment core portion
where core = Note.commentCore comment
portion = Location.stringPortion startPosition wrappedComment
startPosition = Location.createPosition stream startLine startColumn
startColumn = Note.commentStartColumn comment
wrappedComment = show core