packages feed

hsinspect-0.0.6: library/HsInspect/Search.hs

{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ViewPatterns #-}

module HsInspect.Search
  ( search,
    Hit,
  )
where

import BinIface
  ( CheckHiWay (..),
    TraceBinIFaceReading (..),
    readBinIface,
  )
import Control.Monad
import Control.Monad.IO.Class
import Data.List (isSuffixOf)
import Data.Set (Set)
import qualified Data.Set as Set
import qualified GHC
import GHC.PackageDb
import HsInspect.Sexp
import HsInspect.Util
import HscTypes (ModIface (..))
import Json
import qualified Name as GHC
import PackageConfig
import Packages (explicitPackages)
import TcRnMonad (initTcRnIf)

search :: GHC.GhcMonad m => String -> m [Hit]
search _query = do
  -- TODO support home modules
  -- TODO where is base?
  dflags <- GHC.getSessionDynFlags

  -- TODO this logic is used in Packages / Modules, share
  let Just ((snd =<<) -> allPkgs) = GHC.pkgDatabase dflags
      explicit = Set.fromList . explicitPackages $ GHC.pkgState dflags
      pkgs = filter (\(packageConfigId -> pid) -> Set.member pid explicit) allPkgs
  join <$> traverse getSymbols pkgs
  -- ^ TODO filter and rank the symbols by the search

-- TODO Maybe haddock-html
getSymbols :: GHC.GhcMonad m => PackageConfig -> m [Hit]
getSymbols pkg = do
  let findHis dir = filter (".hi" `isSuffixOf`) <$> liftIO (walk dir)
      exposed = Set.fromList $ fst <$> exposedModules pkg
  his <- join <$> traverse findHis (importDirs pkg)
  join <$> traverse (hiToSymbols exposed) his

hiToSymbols :: GHC.GhcMonad m => Set GHC.ModuleName -> FilePath -> m [Hit]
hiToSymbols exposed hi = do
  env <- GHC.getSession
  iface <-
    liftIO $ initTcRnIf 'z' env () ()
      $ readBinIface IgnoreHiWay QuietBinIFaceReading hi
  let m = mi_module iface
  pure
    $ if not $ Set.member (GHC.moduleName m) exposed
      then []
      else do
        -- FIXME the Name is not very useful, use loadDecls
        decl <- GHC.getName . snd <$> mi_decls iface
        pure $ Hit m decl

data Hit = Hit GHC.Module GHC.Name

instance ToSexp Hit where
  toSexp (Hit _ name) = toSexp . GHC.getOccString $ name

instance ToJson Hit where
  json (Hit _ name) = JSString . GHC.getOccString $ name