diff --git a/src/Haskell/Ide/Engine/Plugin/Hoogle.hs b/src/Haskell/Ide/Engine/Plugin/Hoogle.hs index ad2d83376..48d74515f 100644 --- a/src/Haskell/Ide/Engine/Plugin/Hoogle.hs +++ b/src/Haskell/Ide/Engine/Plugin/Hoogle.hs @@ -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 @@ -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" @@ -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 @@ -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) <> "```"] @@ -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 @@ -130,6 +171,9 @@ 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 @@ -137,6 +181,9 @@ lookupCmd = CmdSync $ \term -> do 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 @@ -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)) @@ -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