Skip to content

Commit 2f425ae

Browse files
author
kokobd
committed
add comments & fix dp calculation for inline case
1 parent 88f063f commit 2f425ae

File tree

4 files changed

+77
-20
lines changed

4 files changed

+77
-20
lines changed

plugins/hls-haddock-comments-plugin/src/Ide/Plugin/HaddockComments.hs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ codeActionProvider ideState _pId (CodeActionParams _ _ (TextDocumentIdentifier u
5050
genList :: [Maybe [LHsDecl GhcPs] -> Maybe Anns -> Range -> Maybe (T.Text, TextEdit)]
5151
genList =
5252
[ runGenCommentsSimple genForSig,
53-
-- runGenCommentsSimple genForRecord
5453
runGenComments genForDataDecl
5554
]
5655

plugins/hls-haddock-comments-plugin/src/Ide/Plugin/HaddockComments/Data.hs

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import Data.Data (Data)
1313
import Data.Foldable (for_)
1414
import Data.List (isPrefixOf)
1515
import qualified Data.Map.Strict as Map
16-
import Data.Maybe (fromMaybe)
16+
import Data.Maybe (fromMaybe, isJust)
1717
import Development.IDE (realSpan)
1818
import Development.IDE.GHC.Compat
1919
import Development.IDE.GHC.ExactPrint
@@ -35,53 +35,103 @@ updateDataAnns decl@(L declLoc (TyClD _ DataDecl {tcdDataDefn = HsDataDefn { dd_
3535
getAnnsT >>= (\anns -> unless (missingSomeHaddock anns cons) (lift Nothing))
3636

3737
-- visit each constructor and field
38-
addHaddockCommentsToList True declLoc cons
38+
addHaddockCommentsToList True declLoc (G AnnVbar) cons
3939
for_ cons $ \case
40-
L conLoc ConDeclH98 { con_args = RecCon (L _ fields) } -> addHaddockCommentsToList False conLoc fields
40+
L conLoc ConDeclH98 { con_args = RecCon (L _ fields) } -> addHaddockCommentsToList False conLoc (G AnnComma) fields
4141
_ -> pure ()
4242
modifyAnnsT $ Map.adjust (\ann -> ann {annPriorComments = []}) (mkAnnKey decl)
43-
updateDataAnns _ = pure ()
43+
updateDataAnns _ = lift Nothing
4444

45-
-- TODO Add explaination to this complex function.
46-
addHaddockCommentsToList :: (Data a, Monad m) => Bool -> SrcSpan -> [Located a] -> TransformT m ()
47-
addHaddockCommentsToList usePrevNodeAsAnchor outerLoc nodes =
45+
-- | Add haddock comments to a list of nodes
46+
addHaddockCommentsToList
47+
:: (Data a, Monad m)
48+
=> Bool -- ^ If true, for each node, use previous node in the list as the anchor. Otherwise, use the outer node
49+
-> SrcSpan -- ^ The outer node
50+
-> KeywordId -- ^ The seperator between adjacent nodes
51+
-> [Located a] -- ^ The list of nodes. Haddock comments will be added to each of them
52+
-> TransformT m ()
53+
addHaddockCommentsToList usePrevNodeAsAnchor outerLoc seperator nodes =
54+
-- If you want to understand this function, please first read this page carefully:
55+
-- https://hackage.haskell.org/package/ghc-exactprint-0.6.4/docs/Language-Haskell-GHC-ExactPrint-Delta.html
56+
-- The important part is that for DP(r,c), if r is zero, c is the offset start from the end of the previous node.
57+
-- However, if r is greater than zero, c is the offset start from the 'anchor'.
58+
-- Generally speaking, the 'anchor' is the node that "enclose"
4859
for_ (zip nodes (Nothing: fmap Just nodes)) $ \(node, prevNode) -> do
4960
addHaddockCommentToCurrentNode <- fmap (not . fromMaybe True . flip hasHaddock node) getAnnsT
61+
-- We don't add new haddock comments to nodes with existing ones.
5062
when addHaddockCommentToCurrentNode $ do
51-
let sameLineAsPrev = maybe False (\prevNode' -> notSeperatedByLineEnding prevNode' node) prevNode
52-
when sameLineAsPrev $ modifyAnnsT $ \anns ->
53-
let updateSepAnn :: Annotation -> Annotation
63+
-- 'sameLineAsPrev' is a flag to determine the inline case, for example:
64+
-- data T = A { a :: Int, b :: String } | B { b :: Double }
65+
-- Note that it's a 'Maybe (Located a)', containing the previous node if the current node
66+
-- and the previous node are on the same line.
67+
--
68+
-- For the multiline case (which is the most common), we keep the original indentation of each constructor
69+
-- and field.
70+
--
71+
-- For the inline case, we use the first construcotr/field as the base, and align all following items
72+
-- to them.
73+
let sameLineAsPrev = prevNode >>= (
74+
\prevNode' -> if notSeperatedByLineEnding prevNode' node
75+
then pure prevNode'
76+
else Nothing
77+
)
78+
-- For the inline case, we need to move the seperator to the next line.
79+
-- For constructors, it's vertical bar; for fields, it's comma.
80+
-- The seperator is passed in as function argument.
81+
when (isJust sameLineAsPrev) $ modifyAnnsT $ \anns ->
82+
let newSepCol :: Annotation -> Int
83+
newSepCol ann =
84+
if usePrevNodeAsAnchor then 0 else deltaColumn (annEntryDelta ann)
85+
updateSepAnn :: Annotation -> Annotation
5486
updateSepAnn ann = ann {annsDP =
55-
Map.toList . Map.adjust (const (DP (1,0))) (G AnnVbar) . Map.fromList $ annsDP ann}
87+
Map.toList . Map.adjust (const $ DP (1, newSepCol ann)) seperator . Map.fromList $ annsDP ann}
5688
in flip (maybe anns) prevNode $ \prevNode' -> Map.adjust updateSepAnn (mkAnnKey prevNode') anns
89+
-- Calculate the real column of the anchor
5790
let anchorCol = maybe 0 srcSpanStartCol . realSpan . maybe outerLoc getLoc $
5891
if usePrevNodeAsAnchor then prevNode else Nothing
92+
-- 'dpCol' is what we will use for the current node's entry delta's column
93+
dpCol <- flip fmap getAnnsT $ \anns ->
94+
case sameLineAsPrev of
95+
Just prevNode' ->
96+
-- If the previous node is the anchor, using 0 as column will make current code align with
97+
-- the previous one.
98+
-- Otherwise, use the column of entry delta of the previous node.
99+
-- The map lookup should not fail. '2' is used as a fallback value to make sure the syntax
100+
-- is correct after the changes.
101+
if usePrevNodeAsAnchor then 0 else maybe 2 (deltaColumn . annEntryDelta)
102+
$ anns Map.!? mkAnnKey prevNode'
103+
-- We subtract the real column to get dp column.
104+
Nothing -> (maybe 2 srcSpanStartCol . realSpan $ getLoc node) - anchorCol
105+
-- Modify the current node
59106
modifyAnnsT $
60107
let updateCurrent :: Annotation -> Annotation
61108
updateCurrent ann = ann {
62-
annPriorComments =
63-
case annPriorComments ann of
64-
(c, dp) : rem -> (emptyPriorHaddockComment, dp) : (c, DP (2,0)) : rem
65-
_ -> [(emptyPriorHaddockComment, annEntryDelta ann)],
109+
-- If there exists non-haddock comments, we simply inherit it's delta pos, and move existing
110+
-- comments two lines below (to seperate them from our newly added haddock comments)
111+
-- Otherwise, inherit the node's entry delta pos.
112+
annPriorComments = case annPriorComments ann of
113+
(c, dp) : rem -> (emptyPriorHaddockComment, dp) : (c, DP (2,0)) : rem
114+
_ -> [(emptyPriorHaddockComment, annEntryDelta ann)],
66115
annEntryDelta = DP (1, dpCol)
67116
}
68-
dpCol = if sameLineAsPrev then 0
69-
else (maybe 2 srcSpanStartCol . realSpan $ getLoc node) - anchorCol
70117
in Map.adjust updateCurrent (mkAnnKey node)
71118

119+
-- | Determine if a list of constructor declarations is missing some haddock comments.
72120
missingSomeHaddock :: Anns -> [LConDecl GhcPs] -> Bool
73121
missingSomeHaddock anns = any $ \lcon@(L _ conDecl) -> case conDecl of
74122
ConDeclH98 { con_args = RecCon (L _ fields) } ->
75123
elem (Just False) $ hasHaddock anns lcon : fmap (hasHaddock anns) fields
76124
_ -> False -- GADT is not supported yet
77125

126+
-- | Returns 'True' if the end of the first node and the start of the second node are on the same line.
78127
notSeperatedByLineEnding :: Located a
79128
-> Located a
80129
-> Bool
81130
notSeperatedByLineEnding (L (RealSrcSpan x _) _) (L (RealSrcSpan y _) _) =
82131
srcLocLine (realSrcSpanEnd x) == srcLocLine (realSrcSpanStart y)
83132
notSeperatedByLineEnding _ _ = False
84133

134+
-- | Empty haddock, suitable for being added to 'annPriorComments'
85135
emptyPriorHaddockComment :: Comment
86136
emptyPriorHaddockComment = mkComment "-- |"
87137
#if MIN_VERSION_ghc(9,0,0)
@@ -90,6 +140,7 @@ emptyPriorHaddockComment = mkComment "-- |"
90140
noSrcSpan
91141
#endif
92142

143+
-- | Determines the given node has haddock comments attached to it.
93144
hasHaddock :: Data a => Anns -> Located a -> Maybe Bool
94145
hasHaddock anns node = fmap annHasHaddock (anns Map.!? key)
95146
where
@@ -99,6 +150,7 @@ hasHaddock anns node = fmap annHasHaddock (anns Map.!? key)
99150
|| any (matchCommentPrefix followingCommentPrefix . fst) (annFollowingComments ann)
100151
|| any (keywordIdIsHaddockComment . fst) (annsDP ann)
101152

153+
-- | Checks if the given 'KeywordId' is a comment, and specifically, a haddock comment.
102154
keywordIdIsHaddockComment :: KeywordId -> Bool
103155
keywordIdIsHaddockComment (AnnComment comment) = any (`isPrefixOf` commentContents comment) (priorCommentPrefix ++ followingCommentPrefix)
104156
keywordIdIsHaddockComment _ = False

plugins/hls-haddock-comments-plugin/src/Ide/Plugin/HaddockComments/Prelude.hs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@ import Development.IDE.GHC.Compat
77
import Development.IDE.GHC.ExactPrint
88
import Language.Haskell.GHC.ExactPrint (AnnKey, Annotation)
99

10+
-- | A more generic comments generator
1011
data GenComments = GenComments
1112
{ title :: T.Text,
13+
-- | Use 'Maybe' monad to exit early. 'Nothing' means a code action for haddock comments
14+
-- in the given context is not possible.
1215
updateAnns :: LHsDecl GhcPs -> TransformT Maybe ()
1316
}
1417

1518
-- | Defines how to generate haddock comments by tweaking annotations of AST
19+
--
20+
-- This is left here for compatibility reason, so that we don't break the existing code.
1621
data GenCommentsSimple = forall a.
1722
GenCommentsSimple
1823
{ title :: T.Text,

plugins/hls-haddock-comments-plugin/test/testdata/InlineRecord.expected.hs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ module Record2 where
33
-- | A record
44
data Record = -- |
55
A { -- |
6-
a :: Int , -- |
7-
b :: String }
6+
a :: Int
7+
, -- |
8+
b :: String }
89
| -- |
910
B { -- |
1011
bb :: Double }

0 commit comments

Comments
 (0)