cabal-gild-1.7.0.0: source/library/CabalGild/Unstable/Action/AttachComments.hs
module CabalGild.Unstable.Action.AttachComments where
import qualified CabalGild.Unstable.Extra.Field as Field
import qualified CabalGild.Unstable.Extra.Name as Name
import qualified CabalGild.Unstable.Type.Comment as Comment
import qualified CabalGild.Unstable.Type.Comments as Comments
import qualified Control.Monad.Trans.State as StateT
import qualified Data.Maybe as Maybe
import qualified Distribution.Compat.Lens as Lens
import qualified Distribution.Fields as Fields
import qualified Distribution.Parsec.Position as Position
-- | High level wrapper around 'fields' that makes this action easier to compose
-- with other actions.
run ::
(Applicative m) =>
([Fields.Field Position.Position], [Comment.Comment Position.Position]) ->
m ([Fields.Field (Position.Position, Comments.Comments Position.Position)], [Comment.Comment Position.Position])
run (fs, cs) = pure $ StateT.runState (fields Nothing fs) cs
-- | Attaches comments to a list of fields, with awareness of sibling positions.
-- The first argument is the boundary position (e.g., from a parent's next sibling).
fields ::
Maybe Position.Position ->
[Fields.Field Position.Position] ->
StateT.State [Comment.Comment Position.Position] [Fields.Field (Position.Position, Comments.Comments Position.Position)]
fields p fs = case fs of
[] -> pure []
f : gs -> do
let q = maybe p (Just . Name.annotation . Field.name) $ Maybe.listToMaybe gs
(:)
<$> field q f
<*> fields p gs
-- | Attaches comments to a single field. It is assumed that both the fields
-- and comments are already sorted by their position. This precondition is
-- not checked. Note that comments actually end up attached to the field's
-- name. That's because the 'Field.Field' type doesn't have any annotations
-- directly on it.
--
-- Comments that are indented beyond the field's starting column are attached
-- as trailing comments (in 'Comments.after'), but only if they appear before
-- the next sibling's position.
field ::
Maybe Position.Position ->
Fields.Field Position.Position ->
StateT.State [Comment.Comment Position.Position] (Fields.Field (Position.Position, Comments.Comments Position.Position))
field p f = case f of
Fields.Field n1 fls1 -> do
let col = Position.positionCol $ Name.annotation n1
n2 <- name n1
fls2 <- traverse fieldLine fls1
cs <- attachTrailing col p
pure $ Fields.Field (addAfterComments n2 cs) fls2
Fields.Section n1 sas1 fs1 -> do
let col = Position.positionCol $ Name.annotation n1
n2 <- name n1
sas2 <- traverse sectionArg sas1
fs2 <- fields p fs1
cs <- attachTrailing col p
pure $ Fields.Section (addAfterComments n2 cs) sas2 fs2
-- | Attaches comments to a name. Note that this could be a field name or a
-- section name.
name ::
Fields.Name Position.Position ->
StateT.State [Comment.Comment Position.Position] (Fields.Name (Position.Position, Comments.Comments Position.Position))
name (Fields.Name p fn) =
Fields.Name
<$> toPosition p
<*> pure fn
-- | Attach comments to a field line.
fieldLine ::
Fields.FieldLine Position.Position ->
StateT.State [Comment.Comment Position.Position] (Fields.FieldLine (Position.Position, Comments.Comments Position.Position))
fieldLine (Fields.FieldLine p bs) =
Fields.FieldLine
<$> toPosition p
<*> pure bs
-- | Attaches comments to a section argument. Note that section arguments
-- cannot actually have comments attached. That's because section arguments
-- must be on the same line as the section name, so all comments will end up
-- attached to the name.
sectionArg ::
Fields.SectionArg Position.Position ->
StateT.State [Comment.Comment Position.Position] (Fields.SectionArg (Position.Position, Comments.Comments Position.Position))
sectionArg sa = case sa of
Fields.SecArgName p bs ->
Fields.SecArgName
<$> toPosition p
<*> pure bs
Fields.SecArgStr p bs ->
Fields.SecArgStr
<$> toPosition p
<*> pure bs
Fields.SecArgOther p bs ->
Fields.SecArgOther
<$> toPosition p
<*> pure bs
-- | Attaches comments to a position. This is the workhorse of the module.
-- Comments are attached when their position is less than or equal to the given
-- position. The comments are removed from the state as they are attached.
toPosition ::
Position.Position ->
StateT.State [Comment.Comment Position.Position] (Position.Position, Comments.Comments Position.Position)
toPosition p = do
cs <- StateT.get
let (xs, ys) = span ((<= p) . Comment.annotation) cs
StateT.put ys
pure (p, Comments.MkComments {Comments.before = xs, Comments.after = []})
-- | Attaches trailing comments based on column indentation. Comments that are
-- indented beyond the given column are considered trailing, but only up to
-- the boundary position (if specified). Comments are removed from the state.
attachTrailing ::
Int ->
Maybe Position.Position ->
StateT.State [Comment.Comment Position.Position] [Comment.Comment Position.Position]
attachTrailing col boundary = do
cs <- StateT.get
let isTrailing c =
let p = Comment.annotation c
in Position.positionCol p > col && maybe True (p <) boundary
(xs, ys) = span isTrailing cs
StateT.put ys
pure xs
-- | Adds trailing comments to the 'after' field of a Name's Comments.
addAfterComments ::
Fields.Name (Position.Position, Comments.Comments Position.Position) ->
[Comment.Comment Position.Position] ->
Fields.Name (Position.Position, Comments.Comments Position.Position)
addAfterComments n cs = Lens.over (Name.annotationLens . Lens._2 . Comments.afterLens) (<> cs) n