{-# LANGUAGE CPP #-}
#include "ghc-api-version.h"
module Development.IDE.Spans.Documentation (
getDocumentation
, getDocumentationTryGhc
) where
import Control.Monad
import Data.List.Extra
import qualified Data.Map as M
import Data.Maybe
import qualified Data.Text as T
import Development.IDE.GHC.Error
import Development.IDE.Spans.Calculate
import FastString
import GHC
import SrcLoc
#if MIN_GHC_API_VERSION(8,6,0)
import Data.Char (isSpace)
import Development.IDE.GHC.Util
import qualified Documentation.Haddock.Parser as H
import qualified Documentation.Haddock.Types as H
#endif
getDocumentationTryGhc
:: HscEnv
-> [TypecheckedModule]
-> Name
-> IO [T.Text]
#if MIN_GHC_API_VERSION(8,6,0)
getDocumentationTryGhc packageState tcs name = do
res <- runGhcEnv packageState $ catchSrcErrors "docs" $ getDocs name
case res of
Right (Right (Just docs, _)) -> return [T.pack $ haddockToMarkdown $ H.toRegular $ H._doc $ H.parseParas Nothing $ unpackHDS docs]
_ -> return $ getDocumentation tcs name
#else
getDocumentationTryGhc _packageState tcs name = do
return $ getDocumentation tcs name
#endif
getDocumentation
:: [TypecheckedModule]
-> Name
-> [T.Text]
getDocumentation tcs targetName = fromMaybe [] $ do
targetNameSpan <- realSpan $ nameSrcSpan targetName
tc <-
find ((==) (Just $ srcSpanFile targetNameSpan) . annotationFileName)
$ reverse tcs
let bs = mapMaybe name_of_bind
(listifyAllSpans (tm_typechecked_source tc) :: [LHsBind GhcTc])
let sortedSpans = sortedNameSpans bs
let docs = ann tc
nameInd <- elemIndex targetNameSpan sortedSpans
let prevNameSpan =
if nameInd >= 1
then sortedSpans !! (nameInd - 1)
else zeroSpan $ srcSpanFile targetNameSpan
pure
$ docHeaders
$ filter (\(L target _) -> isBetween target prevNameSpan targetNameSpan)
$ mapMaybe (\(L l v) -> L <$> realSpan l <*> pure v)
$ join
$ M.elems
docs
where
name_of_bind :: LHsBind GhcTc -> Maybe Name
name_of_bind (L _ FunBind {fun_id}) = Just (getName (unLoc fun_id))
name_of_bind _ = Nothing
sortedNameSpans :: [Name] -> [RealSrcSpan]
sortedNameSpans ls = nubSort (mapMaybe (realSpan . nameSrcSpan) ls)
isBetween target before after = before <= target && target <= after
ann = snd . pm_annotations . tm_parsed_module
annotationFileName :: TypecheckedModule -> Maybe FastString
annotationFileName = fmap srcSpanFile . listToMaybe . realSpans . ann
realSpans :: M.Map SrcSpan [Located a] -> [RealSrcSpan]
realSpans =
mapMaybe (realSpan . getLoc)
. join
. M.elems
docHeaders :: [RealLocated AnnotationComment]
-> [T.Text]
docHeaders = mapMaybe (\(L _ x) -> wrk x)
where
wrk = \case
AnnDocCommentNext s -> Just $ T.pack s
AnnLineComment s -> if "-- |" `isPrefixOf` s
then Just $ T.pack s
else Nothing
_ -> Nothing
#if MIN_GHC_API_VERSION(8,6,0)
haddockToMarkdown
:: H.DocH String String -> String
haddockToMarkdown H.DocEmpty
= ""
haddockToMarkdown (H.DocAppend d1 d2)
= haddockToMarkdown d1 <> haddockToMarkdown d2
haddockToMarkdown (H.DocString s)
= s
haddockToMarkdown (H.DocParagraph p)
= "\n\n" ++ haddockToMarkdown p
haddockToMarkdown (H.DocIdentifier i)
= "`" ++ i ++ "`"
haddockToMarkdown (H.DocIdentifierUnchecked i)
= "`" ++ i ++ "`"
haddockToMarkdown (H.DocModule i)
= "`" ++ i ++ "`"
haddockToMarkdown (H.DocWarning w)
= haddockToMarkdown w
haddockToMarkdown (H.DocEmphasis d)
= "*" ++ haddockToMarkdown d ++ "*"
haddockToMarkdown (H.DocBold d)
= "**" ++ haddockToMarkdown d ++ "**"
haddockToMarkdown (H.DocMonospaced d)
= "`" ++ escapeBackticks (haddockToMarkdown d) ++ "`"
where
escapeBackticks "" = ""
escapeBackticks ('`':ss) = '\\':'`':escapeBackticks ss
escapeBackticks (s :ss) = s:escapeBackticks ss
haddockToMarkdown (H.DocCodeBlock d)
= "\n```haskell\n" ++ haddockToMarkdown d ++ "\n```\n"
haddockToMarkdown (H.DocExamples es)
= "\n```haskell\n" ++ unlines (map exampleToMarkdown es) ++ "\n```\n"
where
exampleToMarkdown (H.Example expr result)
= ">>> " ++ expr ++ "\n" ++ unlines result
haddockToMarkdown (H.DocHyperlink (H.Hyperlink url Nothing))
= "<" ++ url ++ ">"
#if MIN_VERSION_haddock_library(1,8,0)
haddockToMarkdown (H.DocHyperlink (H.Hyperlink url (Just label)))
= "[" ++ haddockToMarkdown label ++ "](" ++ url ++ ")"
#else
haddockToMarkdown (H.DocHyperlink (H.Hyperlink url (Just label)))
= "[" ++ label ++ "](" ++ url ++ ")"
#endif
haddockToMarkdown (H.DocPic (H.Picture url Nothing))
= ""
haddockToMarkdown (H.DocPic (H.Picture url (Just label)))
= ""
haddockToMarkdown (H.DocAName aname)
= "[" ++ aname ++ "]:"
haddockToMarkdown (H.DocHeader (H.Header level title))
= replicate level '#' ++ " " ++ haddockToMarkdown title
haddockToMarkdown (H.DocUnorderedList things)
= '\n' : (unlines $ map (\thing -> "+ " ++ dropWhile isSpace (haddockToMarkdown thing)) things)
haddockToMarkdown (H.DocOrderedList things)
= '\n' : (unlines $ map (\thing -> "1. " ++ dropWhile isSpace (haddockToMarkdown thing)) things)
haddockToMarkdown (H.DocDefList things)
= '\n' : (unlines $ map (\(term, defn) -> "+ **" ++ haddockToMarkdown term ++ "**: " ++ haddockToMarkdown defn) things)
haddockToMarkdown (H.DocMathInline _)
= "*cannot render inline math formula*"
haddockToMarkdown (H.DocMathDisplay _)
= "\n\n*cannot render display math formula*\n\n"
haddockToMarkdown (H.DocTable _t)
= "\n\n*tables are not yet supported*\n\n"
haddockToMarkdown (H.DocProperty _)
= ""
#endif