diff --git a/.hlint.yaml b/.hlint.yaml index 4d13003df..e4fd843d4 100644 --- a/.hlint.yaml +++ b/.hlint.yaml @@ -85,6 +85,7 @@ - Development.IDE.LSP.CodeAction - Development.IDE.Spans.Calculate - Development.IDE.Spans.Documentation + - Development.IDE.Spans.Common - Main - flags: diff --git a/ghcide.cabal b/ghcide.cabal index 014251aa7..6dad98291 100644 --- a/ghcide.cabal +++ b/ghcide.cabal @@ -134,6 +134,7 @@ library Development.IDE.LSP.Outline Development.IDE.Spans.AtPoint Development.IDE.Spans.Calculate + Development.IDE.Spans.Common Development.IDE.Spans.Documentation Development.IDE.Spans.Type ghc-options: -Wall -Wno-name-shadowing diff --git a/src/Development/IDE/Core/Completions.hs b/src/Development/IDE/Core/Completions.hs index db2bf993f..6d9a384be 100644 --- a/src/Development/IDE/Core/Completions.hs +++ b/src/Development/IDE/Core/Completions.hs @@ -25,8 +25,6 @@ import Type import Var import Packages import DynFlags -import ConLike -import DataCon import Language.Haskell.LSP.Types import Language.Haskell.LSP.Types.Capabilities @@ -35,25 +33,9 @@ import Development.IDE.Core.CompletionsTypes import Development.IDE.Spans.Documentation import Development.IDE.GHC.Error import Development.IDE.Types.Options - -#ifndef GHC_LIB +import Development.IDE.Spans.Common import Development.IDE.GHC.Util - -safeTyThingType :: TyThing -> Maybe Type -safeTyThingType thing - | Just i <- safeTyThingId thing = Just (varType i) -safeTyThingType (ATyCon tycon) = Just (tyConKind tycon) -safeTyThingType _ = Nothing -#endif - --- From haskell-ide-engine/src/Haskell/Ide/Engine/Support/HieExtras.hs - -safeTyThingId :: TyThing -> Maybe Id -safeTyThingId (AnId i) = Just i -safeTyThingId (AConLike (RealDataCon dc)) = Just $ dataConWrapId dc -safeTyThingId _ = Nothing - -- From haskell-ide-engine/hie-plugin-api/Haskell/Ide/Engine/Context.hs -- | A context of a declaration in the program @@ -158,7 +140,7 @@ mkCompl IdeOptions{..} CI{origName,importedFrom,thingType,label,isInfix,docs} = typeText | Just t <- thingType = Just . stripForall $ T.pack (showGhc t) | otherwise = Nothing - docs' = ("*Defined in '" <> importedFrom <> "'*\n") : docs + docs' = ("*Defined in '" <> importedFrom <> "'*\n") : spanDocToMarkdown docs colon = if optNewColonConvention then ": " else ":: " stripForall :: T.Text -> T.Text @@ -275,12 +257,12 @@ cacheDataProducer packageState dflags tm tcs = do let typ = Just $ varType var name = Var.varName var label = T.pack $ showGhc name - docs <- getDocumentationTryGhc packageState (tm:tcs) name + docs <- runGhcEnv packageState $ getDocumentationTryGhc (tm:tcs) name return $ CI name (showModName curMod) typ label Nothing docs toCompItem :: ModuleName -> Name -> IO CompItem toCompItem mn n = do - docs <- getDocumentationTryGhc packageState (tm:tcs) n + docs <- runGhcEnv packageState $ getDocumentationTryGhc (tm:tcs) n -- lookupName uses runInteractiveHsc, i.e., GHCi stuff which does not work with GHCi -- and leads to fun errors like "Cannot continue after interface file error". #ifdef GHC_LIB diff --git a/src/Development/IDE/Core/CompletionsTypes.hs b/src/Development/IDE/Core/CompletionsTypes.hs index c7f5b33c3..cce485750 100644 --- a/src/Development/IDE/Core/CompletionsTypes.hs +++ b/src/Development/IDE/Core/CompletionsTypes.hs @@ -5,15 +5,11 @@ module Development.IDE.Core.CompletionsTypes ( import Control.DeepSeq import qualified Data.Map as Map import qualified Data.Text as T - import GHC -import Outputable -import DynFlags --- From haskell-ide-engine/src/Haskell/Ide/Engine/LSP/Completions.hs +import Development.IDE.Spans.Common -showGhc :: Outputable a => a -> String -showGhc = showPpr unsafeGlobalDynFlags +-- From haskell-ide-engine/src/Haskell/Ide/Engine/LSP/Completions.hs data Backtick = Surrounded | LeftSide deriving Show data CompItem = CI @@ -23,7 +19,7 @@ data CompItem = CI , label :: T.Text -- ^ Label to display to the user. , isInfix :: Maybe Backtick -- ^ Did the completion happen -- in the context of an infix notation. - , docs :: [T.Text] -- ^ Available documentation. + , docs :: SpanDoc -- ^ Available documentation. } instance Show CompItem where show CI { .. } = "CompItem { origName = \"" ++ showGhc origName ++ "\"" diff --git a/src/Development/IDE/Core/Rules.hs b/src/Development/IDE/Core/Rules.hs index b0b5c5c12..d0d14a3ce 100644 --- a/src/Development/IDE/Core/Rules.hs +++ b/src/Development/IDE/Core/Rules.hs @@ -104,9 +104,7 @@ getAtPoint :: NormalizedFilePath -> Position -> Action (Maybe (Maybe Range, [T.T getAtPoint file pos = fmap join $ runMaybeT $ do opts <- lift getIdeOptions spans <- useE GetSpanInfo file - files <- transitiveModuleDeps <$> useE GetDependencies file - tms <- usesE TypeCheck (file : files) - return $ AtPoint.atPoint opts (map tmrModule tms) spans pos + return $ AtPoint.atPoint opts spans pos -- | Goto Definition. getDefinition :: NormalizedFilePath -> Position -> Action (Maybe Location) @@ -262,9 +260,11 @@ getSpanInfoRule :: Rules () getSpanInfoRule = define $ \GetSpanInfo file -> do tc <- use_ TypeCheck file + deps <- maybe (TransitiveDependencies [] []) fst <$> useWithStale GetDependencies file + tms <- mapMaybe (fmap fst) <$> usesWithStale TypeCheck (transitiveModuleDeps deps) (fileImports, _) <- use_ GetLocatedImports file packageState <- hscEnv <$> use_ GhcSession file - x <- liftIO $ getSrcSpanInfos packageState fileImports tc + x <- liftIO $ getSrcSpanInfos packageState fileImports tc tms return ([], Just x) -- Typechecks a module. diff --git a/src/Development/IDE/Spans/AtPoint.hs b/src/Development/IDE/Spans/AtPoint.hs index ea5600635..e8faadb95 100644 --- a/src/Development/IDE/Spans/AtPoint.hs +++ b/src/Development/IDE/Spans/AtPoint.hs @@ -8,7 +8,6 @@ module Development.IDE.Spans.AtPoint ( , gotoDefinition ) where -import Development.IDE.Spans.Documentation import Development.IDE.GHC.Error import Development.IDE.GHC.Orphans() import Development.IDE.Types.Location @@ -18,7 +17,8 @@ import Development.Shake import Development.IDE.GHC.Util import Development.IDE.GHC.Compat import Development.IDE.Types.Options -import Development.IDE.Spans.Type as SpanInfo +import Development.IDE.Spans.Type as SpanInfo +import Development.IDE.Spans.Common (spanDocToMarkdown) -- GHC API imports import Avail @@ -50,40 +50,42 @@ gotoDefinition getHieFile ideOpts pkgState srcSpans pos = -- | Synopsis for the name at a given position. atPoint :: IdeOptions - -> [TypecheckedModule] -> [SpanInfo] -> Position -> Maybe (Maybe Range, [T.Text]) -atPoint IdeOptions{..} tcs srcSpans pos = do +atPoint IdeOptions{..} srcSpans pos = do firstSpan <- listToMaybe $ deEmpasizeGeneratedEqShow $ spansAtPoint pos srcSpans return (Just (range firstSpan), hoverInfo firstSpan) where -- Hover info for types, classes, type variables - hoverInfo SpanInfo{spaninfoType = Nothing , ..} = - documentation <> (wrapLanguageSyntax <$> name <> kind) <> location + hoverInfo SpanInfo{spaninfoType = Nothing , spaninfoDocs = docs , ..} = + (wrapLanguageSyntax <$> name) <> location <> spanDocToMarkdown docs where - documentation = findDocumentation mbName name = [maybe shouldNotHappen showName mbName] location = [maybe shouldNotHappen definedAt mbName] - kind = [] -- TODO shouldNotHappen = "ghcide: did not expect a type level component without a name" mbName = getNameM spaninfoSource -- Hover info for values/data - hoverInfo SpanInfo{spaninfoType = (Just typ), ..} = - documentation <> (wrapLanguageSyntax <$> nameOrSource <> typeAnnotation) <> location + hoverInfo SpanInfo{spaninfoType = (Just typ), spaninfoDocs = docs , ..} = + (wrapLanguageSyntax <$> nameOrSource) <> location <> spanDocToMarkdown docs where mbName = getNameM spaninfoSource - documentation = findDocumentation mbName - typeAnnotation = [colon <> showName typ] - nameOrSource = [maybe literalSource qualifyNameIfPossible mbName] - literalSource = "" -- TODO: literals: display (length-limited) source + typeAnnotation = colon <> showName typ + expr = case spaninfoSource of + Named n -> qualifyNameIfPossible n + Lit l -> crop $ T.pack l + _ -> "" + nameOrSource = [expr <> "\n" <> typeAnnotation] qualifyNameIfPossible name' = modulePrefix <> showName name' where modulePrefix = maybe "" (<> ".") (getModuleNameAsText name') location = [maybe "" definedAt mbName] - findDocumentation = maybe [] (getDocumentation tcs) - definedAt name = "**Defined " <> T.pack (showSDocUnsafe $ pprNameDefnLoc name) <> "**\n" + definedAt name = "*Defined " <> T.pack (showSDocUnsafe $ pprNameDefnLoc name) <> "*\n" + + crop txt + | T.length txt > 50 = T.take 46 txt <> " ..." + | otherwise = txt range SpanInfo{..} = Range (Position spaninfoStartLine spaninfoStartCol) @@ -112,6 +114,7 @@ locationsAtPoint getHieFile IdeOptions{..} pkgState pos = where getSpan :: SpanSource -> m (Maybe SrcSpan) getSpan NoSource = pure Nothing getSpan (SpanS sp) = pure $ Just sp + getSpan (Lit _) = pure Nothing getSpan (Named name) = case nameSrcSpan name of sp@(RealSrcSpan _) -> pure $ Just sp sp@(UnhelpfulSpan _) -> runMaybeT $ do diff --git a/src/Development/IDE/Spans/Calculate.hs b/src/Development/IDE/Spans/Calculate.hs index b6016ff33..ed83923e5 100644 --- a/src/Development/IDE/Spans/Calculate.hs +++ b/src/Development/IDE/Spans/Calculate.hs @@ -9,13 +9,11 @@ -- | Get information on modules, identifiers, etc. -module Development.IDE.Spans.Calculate(getSrcSpanInfos,listifyAllSpans) where +module Development.IDE.Spans.Calculate(getSrcSpanInfos) where import ConLike import Control.Monad import qualified CoreUtils -import Data.Data -import qualified Data.Generics import Data.List import Data.Maybe import DataCon @@ -26,14 +24,19 @@ import FastString (mkFastString) import OccName import Development.IDE.Types.Location import Development.IDE.Spans.Type +#ifdef GHC_LIB import Development.IDE.GHC.Error (zeroSpan) +#else +import Development.IDE.GHC.Error (zeroSpan, catchSrcErrors) +#endif import Prelude hiding (mod) import TcHsSyn import Var import Development.IDE.Core.Compile import qualified Development.IDE.GHC.Compat as Compat import Development.IDE.GHC.Util - +import Development.IDE.Spans.Common +import Development.IDE.Spans.Documentation -- A lot of things gained an extra X argument in GHC 8.6, which we mostly ignore -- this U ignores that arg in 8.6, but is hidden in 8.4 @@ -48,37 +51,41 @@ getSrcSpanInfos :: HscEnv -> [(Located ModuleName, Maybe NormalizedFilePath)] -> TcModuleResult + -> [TcModuleResult] -> IO [SpanInfo] -getSrcSpanInfos env imports tc = - runGhcEnv env - . getSpanInfo imports - $ tmrModule tc +getSrcSpanInfos env imports tc tms = + runGhcEnv env $ + getSpanInfo imports (tmrModule tc) (map tmrModule tms) -- | Get ALL source spans in the module. getSpanInfo :: GhcMonad m => [(Located ModuleName, Maybe NormalizedFilePath)] -- ^ imports -> TypecheckedModule + -> [TypecheckedModule] -> m [SpanInfo] -getSpanInfo mods tcm = +getSpanInfo mods tcm tcms = do let tcs = tm_typechecked_source tcm bs = listifyAllSpans tcs :: [LHsBind GhcTc] es = listifyAllSpans tcs :: [LHsExpr GhcTc] ps = listifyAllSpans' tcs :: [Pat GhcTc] ts = listifyAllSpans $ tm_renamed_source tcm :: [LHsType GhcRn] - let funBinds = funBindMap $ tm_parsed_module tcm - bts <- mapM (getTypeLHsBind funBinds) bs -- binds - ets <- mapM (getTypeLHsExpr tcm) es -- expressions - pts <- mapM (getTypeLPat tcm) ps -- patterns - tts <- mapM (getLHsType tcm) ts -- types + allModules = tcm:tcms + funBinds = funBindMap $ tm_parsed_module tcm + bts <- mapM (getTypeLHsBind allModules funBinds) bs -- binds + ets <- mapM (getTypeLHsExpr allModules) es -- expressions + pts <- mapM (getTypeLPat allModules) ps -- patterns + tts <- mapM (getLHsType allModules) ts -- types let imports = importInfo mods let exports = getExports tcm - let exprs = exports ++ imports ++ concat bts ++ concat tts ++ catMaybes (ets ++ pts) + let exprs = addEmptyInfo exports ++ addEmptyInfo imports ++ concat bts ++ concat tts ++ catMaybes (ets ++ pts) return (mapMaybe toSpanInfo (sortBy cmp exprs)) - where cmp (_,a,_) (_,b,_) + where cmp (_,a,_,_) (_,b,_,_) | a `isSubspanOf` b = LT | b `isSubspanOf` a = GT | otherwise = compare (srcSpanStart a) (srcSpanStart b) + addEmptyInfo = map (\(a,b) -> (a,b,Nothing,emptySpanDoc)) + -- | The locations in the typechecked module are slightly messed up in some cases (e.g. HsMatchContext always -- points to the first match) whereas the parsed module has the correct locations. -- Therefore we build up a map from OccName to the corresponding definition in the parsed module @@ -88,10 +95,10 @@ getSpanInfo mods tcm = funBindMap :: ParsedModule -> OccEnv (HsBind GhcPs) funBindMap pm = mkOccEnv $ [ (occName $ unLoc f, bnd) | L _ (Compat.ValD bnd@FunBind{fun_id = f}) <- hsmodDecls $ unLoc $ pm_parsed_source pm ] -getExports :: TypecheckedModule -> [(SpanSource, SrcSpan, Maybe Type)] +getExports :: TypecheckedModule -> [(SpanSource, SrcSpan)] getExports m | Just (_, _, Just exports, _) <- renamedSource m = - [ (Named $ unLoc n, getLoc n, Nothing) + [ (Named $ unLoc n, getLoc n) | (e, _) <- exports , n <- ieLNames $ unLoc e ] @@ -107,47 +114,60 @@ ieLNames _ = [] -- | Get the name and type of a binding. getTypeLHsBind :: (GhcMonad m) - => OccEnv (HsBind GhcPs) + => [TypecheckedModule] + -> OccEnv (HsBind GhcPs) -> LHsBind GhcTc - -> m [(SpanSource, SrcSpan, Maybe Type)] -getTypeLHsBind funBinds (L _spn FunBind{fun_id = pid}) - | Just FunBind {fun_matches = MG{mg_alts=L _ matches}} <- lookupOccEnv funBinds (occName $ unLoc pid) = - return [(Named (getName (unLoc pid)), getLoc mc_fun, Just (varType (unLoc pid))) | match <- matches, FunRhs{mc_fun = mc_fun} <- [m_ctxt $ unLoc match] ] + -> m [(SpanSource, SrcSpan, Maybe Type, SpanDoc)] +getTypeLHsBind tms funBinds (L _spn FunBind{fun_id = pid}) + | Just FunBind {fun_matches = MG{mg_alts=L _ matches}} <- lookupOccEnv funBinds (occName $ unLoc pid) = do + let name = getName (unLoc pid) + docs <- getDocumentationTryGhc tms name + return [(Named name, getLoc mc_fun, Just (varType (unLoc pid)), docs) | match <- matches, FunRhs{mc_fun = mc_fun} <- [m_ctxt $ unLoc match] ] -- In theory this shouldn’t ever fail but if it does, we can at least show the first clause. -getTypeLHsBind _ (L _spn FunBind{fun_id = pid,fun_matches = MG{}}) = - return [(Named $ getName (unLoc pid), getLoc pid, Just (varType (unLoc pid)))] -getTypeLHsBind _ _ = return [] +getTypeLHsBind tms _ (L _spn FunBind{fun_id = pid,fun_matches = MG{}}) = do + let name = getName (unLoc pid) + docs <- getDocumentationTryGhc tms name + return [(Named name, getLoc pid, Just (varType (unLoc pid)), docs)] +getTypeLHsBind _ _ _ = return [] -- | Get the name and type of an expression. getTypeLHsExpr :: (GhcMonad m) - => TypecheckedModule + => [TypecheckedModule] -> LHsExpr GhcTc - -> m (Maybe (SpanSource, SrcSpan, Maybe Type)) -getTypeLHsExpr _ e = do + -> m (Maybe (SpanSource, SrcSpan, Maybe Type, SpanDoc)) +getTypeLHsExpr tms e = do hs_env <- getSession (_, mbe) <- liftIO (deSugarExpr hs_env e) - return $ - case mbe of - Just expr -> - Just (getSpanSource (unLoc e), getLoc e, Just (CoreUtils.exprType expr)) - Nothing -> Nothing + case mbe of + Just expr -> do + let ss = getSpanSource (unLoc e) + docs <- case ss of + Named n -> getDocumentationTryGhc tms n + _ -> return emptySpanDoc + return $ Just (ss, getLoc e, Just (CoreUtils.exprType expr), docs) + Nothing -> return Nothing where getSpanSource :: HsExpr GhcTc -> SpanSource + getSpanSource (HsLit U lit) = Lit (showGhc lit) + getSpanSource (HsOverLit U lit) = Lit (showGhc lit) getSpanSource (HsVar U (L _ i)) = Named (getName i) getSpanSource (HsConLikeOut U (RealDataCon dc)) = Named (dataConName dc) getSpanSource RecordCon {rcon_con_name} = Named (getName rcon_con_name) getSpanSource (HsWrap U _ xpr) = getSpanSource xpr getSpanSource (HsPar U xpr) = getSpanSource (unLoc xpr) - getSpanSource _ = NoSource + getSpanSource _ = NoSource -- | Get the name and type of a pattern. getTypeLPat :: (GhcMonad m) - => TypecheckedModule + => [TypecheckedModule] -> Pat GhcTc - -> m (Maybe (SpanSource, SrcSpan, Maybe Type)) -getTypeLPat _ pat = - let (src, spn) = getSpanSource pat in - return $ Just (src, spn, Just (hsPatType pat)) + -> m (Maybe (SpanSource, SrcSpan, Maybe Type, SpanDoc)) +getTypeLPat tms pat = do + let (src, spn) = getSpanSource pat + docs <- case src of + Named n -> getDocumentationTryGhc tms n + _ -> return emptySpanDoc + return $ Just (src, spn, Just (hsPatType pat), docs) where getSpanSource :: Pat GhcTc -> (SpanSource, SrcSpan) getSpanSource (VarPat U (L spn vid)) = (Named (getName vid), spn) @@ -157,40 +177,40 @@ getTypeLPat _ pat = getLHsType :: GhcMonad m - => TypecheckedModule + => [TypecheckedModule] -> LHsType GhcRn - -> m [(SpanSource, SrcSpan, Maybe Type)] -getLHsType _ (L spn (HsTyVar U _ v)) = pure [(Named $ unLoc v, spn, Nothing)] + -> m [(SpanSource, SrcSpan, Maybe Type, SpanDoc)] +getLHsType tms (L spn (HsTyVar U _ v)) = do + let n = unLoc v + docs <- getDocumentationTryGhc tms n +#ifdef GHC_LIB + let ty = Right Nothing +#else + ty <- catchSrcErrors "completion" $ do + name' <- lookupName n + return $ name' >>= safeTyThingType +#endif + let ty' = case ty of + Right (Just x) -> Just x + _ -> Nothing + pure [(Named n, spn, ty', docs)] getLHsType _ _ = pure [] importInfo :: [(Located ModuleName, Maybe NormalizedFilePath)] - -> [(SpanSource, SrcSpan, Maybe Type)] + -> [(SpanSource, SrcSpan)] importInfo = mapMaybe (uncurry wrk) where - wrk :: Located ModuleName -> Maybe NormalizedFilePath -> Maybe (SpanSource, SrcSpan, Maybe Type) + wrk :: Located ModuleName -> Maybe NormalizedFilePath -> Maybe (SpanSource, SrcSpan) wrk modName = \case Nothing -> Nothing - Just fp -> Just (fpToSpanSource $ fromNormalizedFilePath fp, getLoc modName, Nothing) + Just fp -> Just (fpToSpanSource $ fromNormalizedFilePath fp, getLoc modName) -- TODO make this point to the module name fpToSpanSource :: FilePath -> SpanSource fpToSpanSource fp = SpanS $ RealSrcSpan $ zeroSpan $ mkFastString fp --- | Get ALL source spans in the source. -listifyAllSpans :: (Typeable a, Data m) => m -> [Located a] -listifyAllSpans tcs = - Data.Generics.listify p tcs - where p (L spn _) = isGoodSrcSpan spn --- This is a version of `listifyAllSpans` specialized on picking out --- patterns. It comes about since GHC now defines `type LPat p = Pat --- p` (no top-level locations). -listifyAllSpans' :: Typeable a - => TypecheckedSource -> [Pat a] -listifyAllSpans' tcs = Data.Generics.listify (const True) tcs - - -- | Pretty print the types into a 'SpanInfo'. -toSpanInfo :: (SpanSource, SrcSpan, Maybe Type) -> Maybe SpanInfo -toSpanInfo (name,mspan,typ) = +toSpanInfo :: (SpanSource, SrcSpan, Maybe Type, SpanDoc) -> Maybe SpanInfo +toSpanInfo (name,mspan,typ,docs) = case mspan of RealSrcSpan spn -> -- GHC’s line and column numbers are 1-based while LSP’s line and column @@ -200,5 +220,6 @@ toSpanInfo (name,mspan,typ) = (srcSpanEndLine spn - 1) (srcSpanEndCol spn - 1) typ - name) + name + docs) _ -> Nothing diff --git a/src/Development/IDE/Spans/Common.hs b/src/Development/IDE/Spans/Common.hs new file mode 100644 index 000000000..7505e9d49 --- /dev/null +++ b/src/Development/IDE/Spans/Common.hs @@ -0,0 +1,160 @@ +{-# LANGUAGE CPP #-} +#include "ghc-api-version.h" + +module Development.IDE.Spans.Common ( + showGhc +, listifyAllSpans +, listifyAllSpans' +, safeTyThingId +#ifndef GHC_LIB +, safeTyThingType +#endif +, SpanDoc(..) +, emptySpanDoc +, spanDocToMarkdown +) where + +import Data.Data +import qualified Data.Generics +import qualified Data.Text as T + +import GHC +import Outputable +import DynFlags +import ConLike +import DataCon +#ifndef GHC_LIB +import Var +#endif + +#if MIN_GHC_API_VERSION(8,6,0) +import Data.Char (isSpace) +import qualified Documentation.Haddock.Parser as H +import qualified Documentation.Haddock.Types as H +#endif + +showGhc :: Outputable a => a -> String +showGhc = showPpr unsafeGlobalDynFlags + +-- | Get ALL source spans in the source. +listifyAllSpans :: (Typeable a, Data m) => m -> [Located a] +listifyAllSpans tcs = + Data.Generics.listify p tcs + where p (L spn _) = isGoodSrcSpan spn +-- This is a version of `listifyAllSpans` specialized on picking out +-- patterns. It comes about since GHC now defines `type LPat p = Pat +-- p` (no top-level locations). +listifyAllSpans' :: Typeable a + => TypecheckedSource -> [Pat a] +listifyAllSpans' tcs = Data.Generics.listify (const True) tcs + +#ifndef GHC_LIB +-- From haskell-ide-engine/src/Haskell/Ide/Engine/Support/HieExtras.hs +safeTyThingType :: TyThing -> Maybe Type +safeTyThingType thing + | Just i <- safeTyThingId thing = Just (varType i) +safeTyThingType (ATyCon tycon) = Just (tyConKind tycon) +safeTyThingType _ = Nothing +#endif + +safeTyThingId :: TyThing -> Maybe Id +safeTyThingId (AnId i) = Just i +safeTyThingId (AConLike (RealDataCon dc)) = Just $ dataConWrapId dc +safeTyThingId _ = Nothing + +-- Possible documentation for an element in the code +data SpanDoc + = SpanDocString HsDocString + | SpanDocText [T.Text] + deriving Show + +emptySpanDoc :: SpanDoc +emptySpanDoc = SpanDocText [] + +spanDocToMarkdown :: SpanDoc -> [T.Text] +#if MIN_GHC_API_VERSION(8,6,0) +spanDocToMarkdown (SpanDocString docs) + = [T.pack $ haddockToMarkdown $ H.toRegular $ H._doc $ H.parseParas Nothing $ unpackHDS docs] +#else +spanDocToMarkdown (SpanDocString _) + = [] +#endif +spanDocToMarkdown (SpanDocText txt) = txt + +#if MIN_GHC_API_VERSION(8,6,0) +-- Simple (and a bit hacky) conversion from Haddock markup to Markdown +haddockToMarkdown + :: H.DocH String String -> String + +haddockToMarkdown H.DocEmpty + = "" +haddockToMarkdown (H.DocAppend d1 d2) + = haddockToMarkdown d1 Prelude.<> 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)) + = "![](" ++ url ++ ")" +haddockToMarkdown (H.DocPic (H.Picture url (Just label))) + = "![" ++ label ++ "](" ++ url ++ ")" +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) + +-- we cannot render math by default +haddockToMarkdown (H.DocMathInline _) + = "*cannot render inline math formula*" +haddockToMarkdown (H.DocMathDisplay _) + = "\n\n*cannot render display math formula*\n\n" + +-- TODO: render tables +haddockToMarkdown (H.DocTable _t) + = "\n\n*tables are not yet supported*\n\n" + +-- things I don't really know how to handle +haddockToMarkdown (H.DocProperty _) + = "" -- don't really know what to do +#endif \ No newline at end of file diff --git a/src/Development/IDE/Spans/Documentation.hs b/src/Development/IDE/Spans/Documentation.hs index 616a9c5a6..e3fed96d4 100644 --- a/src/Development/IDE/Spans/Documentation.hs +++ b/src/Development/IDE/Spans/Documentation.hs @@ -15,32 +15,26 @@ 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 Development.IDE.Spans.Common 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] + :: GhcMonad m + => [TypecheckedModule] -> Name - -> IO [T.Text] + -> m SpanDoc #if MIN_GHC_API_VERSION(8,6,0) -getDocumentationTryGhc packageState tcs name = do - res <- runGhcEnv packageState $ catchSrcErrors "docs" $ getDocs name +getDocumentationTryGhc tcs name = do + res <- 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 + Right (Right (Just docs, _)) -> return $ SpanDocString docs + _ -> return $ SpanDocText $ getDocumentation tcs name #else -getDocumentationTryGhc _packageState tcs name = do - return $ getDocumentation tcs name +getDocumentationTryGhc tcs name = do + return $ SpanDocText $ getDocumentation tcs name #endif getDocumentation @@ -114,82 +108,4 @@ docHeaders = mapMaybe (\(L _ x) -> wrk x) AnnLineComment s -> if "-- |" `isPrefixOf` s then Just $ T.pack s else Nothing - _ -> Nothing - -#if MIN_GHC_API_VERSION(8,6,0) --- Simple (and a bit hacky) conversion from Haddock markup to Markdown -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)) - = "![](" ++ url ++ ")" -haddockToMarkdown (H.DocPic (H.Picture url (Just label))) - = "![" ++ label ++ "](" ++ url ++ ")" -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) - --- we cannot render math by default -haddockToMarkdown (H.DocMathInline _) - = "*cannot render inline math formula*" -haddockToMarkdown (H.DocMathDisplay _) - = "\n\n*cannot render display math formula*\n\n" - --- TODO: render tables -haddockToMarkdown (H.DocTable _t) - = "\n\n*tables are not yet supported*\n\n" - --- things I don't really know how to handle -haddockToMarkdown (H.DocProperty _) - = "" -- don't really know what to do -#endif + _ -> Nothing \ No newline at end of file diff --git a/src/Development/IDE/Spans/Type.hs b/src/Development/IDE/Spans/Type.hs index 437132292..1823666bc 100644 --- a/src/Development/IDE/Spans/Type.hs +++ b/src/Development/IDE/Spans/Type.hs @@ -15,6 +15,7 @@ import GHC import Control.DeepSeq import OccName import Development.IDE.GHC.Util +import Development.IDE.Spans.Common -- | Type of some span of source code. Most of these fields are -- unboxed but Haddock doesn't show that. @@ -34,11 +35,14 @@ data SpanInfo = -- any. This can be useful for accessing a variety of -- information about the identifier such as module, -- locality, definition location, etc. + ,spaninfoDocs :: !SpanDoc + -- ^ Documentation for the element } instance Show SpanInfo where - show (SpanInfo sl sc el ec t n) = + show (SpanInfo sl sc el ec t n docs) = unwords ["(SpanInfo", show sl, show sc, show el, show ec - , show $ maybe "NoType" prettyPrint t, "(" <> show n <> "))"] + , show $ maybe "NoType" prettyPrint t, "(" <> show n <> "))" + , "docs(" <> show docs <> ")"] instance NFData SpanInfo where rnf = rwhnf @@ -47,6 +51,7 @@ instance NFData SpanInfo where -- we don't always get a name out so sometimes manually annotating source is more appropriate data SpanSource = Named Name | SpanS SrcSpan + | Lit String | NoSource deriving (Eq) @@ -54,9 +59,10 @@ instance Show SpanSource where show = \case Named n -> "Named " ++ occNameString (occName n) SpanS sp -> "Span " ++ show sp + Lit lit -> "Lit " ++ lit NoSource -> "NoSource" getNameM :: SpanSource -> Maybe Name getNameM = \case Named name -> Just name - _ -> Nothing + _ -> Nothing \ No newline at end of file diff --git a/test/exe/Main.hs b/test/exe/Main.hs index d5a1c63f1..e7fca7448 100644 --- a/test/exe/Main.hs +++ b/test/exe/Main.hs @@ -1083,7 +1083,7 @@ findDefinitionAndHoverTests = let _ -> liftIO $ assertFailure $ "test not expecting this kind of hover info" <> show hover extractLineColFromHoverMsg :: T.Text -> [T.Text] - extractLineColFromHoverMsg = T.splitOn ":" . head . T.splitOn "**" . last . T.splitOn (sourceFileName <> ":") + extractLineColFromHoverMsg = T.splitOn ":" . head . T.splitOn "*" . last . T.splitOn (sourceFileName <> ":") checkHoverRange :: Range -> Maybe Range -> T.Text -> Session () checkHoverRange expectedRange rangeInHover msg = @@ -1150,7 +1150,7 @@ findDefinitionAndHoverTests = let intL40 = Position 40 34 ; kindI = [ExpectHoverText [":: *\n"]] tvrL40 = Position 40 37 ; kindV = [ExpectHoverText [":: * -> *\n"]] intL41 = Position 41 20 ; litI = [ExpectHoverText ["7518"]] - chrL36 = Position 36 25 ; litC = [ExpectHoverText ["'t'"]] + chrL36 = Position 37 24 ; litC = [ExpectHoverText ["'f'"]] txtL8 = Position 8 14 ; litT = [ExpectHoverText ["\"dfgv\""]] lstL43 = Position 43 12 ; litL = [ExpectHoverText ["[ 8391 :: Int, 6268 ]"]] outL45 = Position 45 3 ; outSig = [ExpectHoverText ["outer", "Bool"], mkR 46 0 46 5] @@ -1183,11 +1183,11 @@ findDefinitionAndHoverTests = let , test yes yes mclL37 mcl "top-level fn 2nd clause #246" , test yes yes spaceL37 space "top-level fn on space #315" , test no broken docL41 doc "documentation #7" - , test no broken eitL40 kindE "kind of Either #273" - , test no broken intL40 kindI "kind of Int #273" + , test no yes eitL40 kindE "kind of Either #273" + , test no yes intL40 kindI "kind of Int #273" , test no broken tvrL40 kindV "kind of (* -> *) type variable #273" - , test no broken intL41 litI "literal Int in hover info #274" - , test no broken chrL36 litC "literal Char in hover info #274" + , test no yes intL41 litI "literal Int in hover info #274" + , test no yes chrL36 litC "literal Char in hover info #274" , test no broken txtL8 litT "literal Text in hover info #274" , test no broken lstL43 litL "literal List in hover info #274" , test no broken docL41 constr "type constraint in hover info #283"