packages feed

tricorder-0.6.0.0: src/Tricorder/Daemon/Ghci/Output.hs

module Tricorder.Daemon.Ghci.Output
    ( parseShowModules
    , parseShowTargets
    )
where

import Data.Char (isSpace)
import Text.Megaparsec
import Text.Megaparsec.Char (char)
import Prelude hiding (many)

import Data.Text qualified as T

import Tricorder.Daemon.Ghci.LineParser (LineParser, LineStream (..))
import Tricorder.Daemon.Ghci.TextParser (TextParser)

import Tricorder.Daemon.Ghci.LineParser qualified as LineParser
import Tricorder.Daemon.Ghci.TextParser qualified as TextParser


-- | Parse the output of @:show modules@ into (module name, file path) pairs.
parseShowModules :: [Text] -> [(Text, FilePath)]
parseShowModules ls =
    case parse (catMaybes <$> many showModuleLine <* eof) "" (LineStream ls) of
        Right items -> items
        Left _ -> []


-- | Parse or skip one line of @:show modules@ output.
showModuleLine :: LineParser (Maybe (Text, FilePath))
showModuleLine = do
    line <- anySingle
    pure $ TextParser.run showModuleLineP (LineParser.stripAnsi line)


showModuleLineP :: TextParser (Text, FilePath)
showModuleLineP = do
    modName <- takeWhile1P Nothing (not . isSpace)
    _ <- takeWhileP Nothing (/= '(')
    _ <- char '('
    _ <- char ' '
    filePath <- takeWhile1P Nothing (\c -> c /= ',' && c /= ')')
    pure (modName, toString (T.stripEnd filePath))


-- | Parse the output of @:show targets@.
--
-- Each line names one target. In @cabal repl --enable-multi-repl@ these are
-- dotted module names (e.g. @"Foo.Bar"@); in plain @ghci@ sessions they can
-- also be file paths. A leading @*@ marks the active interactive target and
-- is stripped. Blank lines are skipped.
parseShowTargets :: [Text] -> [Text]
parseShowTargets = mapMaybe parseLine
  where
    parseLine raw =
        let cleaned = T.strip $ T.dropWhile (== '*') $ T.strip $ LineParser.stripAnsi raw
        in  if T.null cleaned then Nothing else Just cleaned