Skip to content
This repository was archived by the owner on Oct 7, 2020. It is now read-only.

Document Hoogle Plugin #1173

Merged
merged 2 commits into from
Apr 14, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 80 additions & 1 deletion src/Haskell/Ide/Engine/Plugin/Hoogle.hs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ data HoogleError = NoDb | NoResults deriving (Eq,Ord,Show)

newtype HoogleDb = HoogleDb (Maybe FilePath)

-- | Convert Hoogle Error's to Ide Error's.
-- Can be used to present errors to the client.
hoogleErrorToIdeError :: HoogleError -> IdeError
hoogleErrorToIdeError NoResults =
IdeError PluginError "No results found" Null
Expand All @@ -55,6 +57,16 @@ hoogleErrorToIdeError NoDb =
instance ExtensionClass HoogleDb where
initialValue = HoogleDb Nothing

-- | Initialise the Hoogle Database.
-- Search for the Hoogle Database and set it in the global config if found.
-- Looks first into custom hoogle database locations, then in the default location.
-- Note, that the FilePath must be an absolute path, otherwise Hoogle can not
-- find the database.
--
-- If no hoogle database has been found, Nothing is returned
-- and we will have no access to the hoogle database.
-- However, it is still safe to use the hoogle API,
-- e.g. either error or default values are returned.
initializeHoogleDb :: IdeGhcM (Maybe FilePath)
initializeHoogleDb = do
explicitDbLocation <- liftIO $ lookupEnv "HIE_HOOGLE_DATABASE"
Expand Down Expand Up @@ -83,6 +95,15 @@ infoCmd' expr = do
else
return $ T.pack $ targetInfo $ head res

-- | Command to get the prettified documentation of an hoogle identifier.
-- Identifier should be understandable for hoogle.
-- If documentation can be found for it, the result will be rendered
-- in markdown for the lsp-client. If multiple results have been found,
-- only the first result will be shown.
--
-- If no result can be found for the identifier, a hoogle error is returned
-- that can be shown to the client by converting it
-- to an IdeError with 'hoogleErrorToIdeError'.
infoCmdFancyRender :: T.Text -> IdeM (Either HoogleError T.Text)
infoCmdFancyRender expr = do
HoogleDb mdb <- get
Expand All @@ -92,6 +113,8 @@ infoCmdFancyRender expr = do
else
return $ renderTarget $ head res

-- | Render the target in valid markdown.
-- Transform haddock documentation into markdown.
renderTarget :: Target -> T.Text
renderTarget t = T.intercalate "\n\n" $
["```haskell\n" <> unHTML (T.pack $ targetItem t) <> "```"]
Expand All @@ -114,12 +137,30 @@ renderTarget t = T.intercalate "\n\n" $

------------------------------------------------------------------------

-- | Search for modules that satisfy the given search text.
-- Will return at most five, unique results.
--
-- If an error occurs, such as no hoogle database has been found,
-- or the search term has no match, an empty list will be returned.
searchModules :: T.Text -> IdeM [T.Text]
searchModules = fmap (nub . take 5) . searchTargets (fmap (T.pack . fst) . targetModule)

-- | Search for packages that satisfy the given search text.
-- Will return at most five, unique results.
--
-- If an error occurs, such as no hoogle database has been found,
-- or the search term has no match, an empty list will be returned.
searchPackages :: T.Text -> IdeM [T.Text]
searchPackages = fmap (nub . take 5) . searchTargets (fmap (T.pack . fst) . targetPackage)

-- | Search for Targets that fit to the given Text and satisfy the given predicate.
-- Limits the amount of matches to at most ten.
-- Applies the predicate to the first ten matches. May also return zero matches,
-- although there are matches, if none of the first ten matches
-- satisfies the predicate.
--
-- If an error occurs, such as no hoogle database has been found,
-- or the search term has no match, an empty list will be returned.
searchTargets :: (Target -> Maybe a) -> T.Text -> IdeM [a]
searchTargets f term = do
HoogleDb mdb <- get
Expand All @@ -130,13 +171,19 @@ searchTargets f term = do

------------------------------------------------------------------------

-- | Lookup the given Text in the local Hoogle database.
-- Is limited to collect at most ten matches.
-- May fail with a HoogleError that can be shown to the user.
lookupCmd :: CommandFunc T.Text [T.Text]
lookupCmd = CmdSync $ \term -> do
res <- liftToGhc $ bimap hoogleErrorToIdeError id <$> lookupCmd' 10 term
return $ case res of
Left err -> IdeResultFail err
Right x -> IdeResultOk x

-- | Lookup the given Text in the local Hoogle database.
-- Takes the first `n` matches.
-- May fail with a HoogleError that can be shown to the user.
lookupCmd' :: Int -> T.Text -> IdeM (Either HoogleError [T.Text])
lookupCmd' n term = do
HoogleDb mdb <- get
Expand All @@ -145,12 +192,36 @@ lookupCmd' n term = do

------------------------------------------------------------------------

-- | Run a query for Hoogle on the given Hoogle database.
-- If no Database is given, no search is executed.
-- If the Database cannot be found at the given location, an IOException will be thrown.
-- Note, that the database file must be an absolute path.
-- The target may be of the form: 'take', 'take :: Int -> [a] -> [a]', 'Data.List'.
-- In general, it is very similar to the Web Api.
-- Found targets can be consumed with the given callback function.
-- You can limit the amount of results, by taking only the first ten results.
-- Example call:
--
-- @
-- runHoogleQuery
-- (Just "/home/user/.hoogle/default-haskell-5.0.17.hoo")
-- (Data.Text.pack "take :: Int -> [a] -> [a]")
-- (Right . Prelude.take 10)
-- @
-- This limits the results to ten and looks for a function `take` that has the given signature.
--
-- HoogleError's can be translated to IdeErrors with @hoogleErrorToIdeError@
-- and shown to the client.
runHoogleQuery :: Maybe FilePath -> T.Text -> ([Target] -> Either HoogleError a) -> IO (Either HoogleError a)
runHoogleQuery Nothing _ _ = return $ Left NoDb
runHoogleQuery (Just db) quer f = do
res <- searchHoogle db quer
return (f res)


-- | Run a query for Hoogle on the given Hoogle database.
-- If the database can not be found, an IOException is thrown.
-- The target may be of the form: `take`, `take :: Int -> [a] -> [a]`
searchHoogle :: FilePath -> T.Text -> IO [Target]
searchHoogle dbf quer = withDatabase dbf (return . flip searchDatabase (T.unpack quer))

Expand All @@ -167,7 +238,15 @@ docRules (Just "containers") modName =
fromMaybe modName $ T.stripSuffix ".Base" modName
docRules _ modName = modName

getDocsForName :: T.Text -> Maybe T.Text -> T.Text -> IdeM (Maybe T.Text)
-- | Get the Documentation for a given identifier in a given module.
-- May also specify the according package, to avoid name clashes.
-- Results is a prettified Text that can be sent and shown to the client.
--
-- Might fail, if the identifier can not be found.
getDocsForName :: T.Text -- ^ Identifier within a module.
-> Maybe T.Text -- ^ Optional package name to avoid name clashes.
-> T.Text -- ^ Name of the module to search in.
-> IdeM (Maybe T.Text) -- ^ Prettified hoogle documentation of target.
getDocsForName name pkg modName' = do
let modName = docRules pkg modName'
query = name
Expand Down