packages feed

hls-eval-plugin-0.1.0.0: src/Ide/Plugin/Eval/Util.hs

{-# LANGUAGE ImplicitParams #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# OPTIONS_GHC -Wno-orphans #-}

-- |Debug utilities
module Ide.Plugin.Eval.Util (
    asS,
    timed,
    isLiterate,
    handleMaybe,
    handleMaybeM,
    response,
    response',
    gStrictTry,
    logWith,
) where

import Control.Monad (join)
import Control.Monad.IO.Class (MonadIO (liftIO))
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Except (
    ExceptT (..),
    runExceptT,
    throwE,
 )
import Data.Aeson (Value (Null))
import Data.Bifunctor (first)
import Data.String (IsString (fromString))
import qualified Data.Text as T
import Development.IDE (
    IdeState,
    Priority (..),
    ideLogger,
    logPriority,
 )
import Exception (ExceptionMonad, SomeException (..), evaluate, gcatch)
import GHC.Exts (toList)
import GHC.Stack (HasCallStack, callStack, srcLocFile, srcLocStartCol, srcLocStartLine)
import Language.Haskell.LSP.Types (
    ErrorCode (InternalError),
    ResponseError (ResponseError),
 )
import Outputable (
    Outputable (ppr),
    ppr,
    showSDocUnsafe,
 )
import System.FilePath (takeExtension)
import System.Time.Extra (
    duration,
    showDuration,
 )

asS :: Outputable a => a -> String
asS = showSDocUnsafe . ppr

timed :: MonadIO m => (t -> String -> m a) -> t -> m b -> m b
timed out name op = do
    (secs, r) <- duration op
    _ <- out name (showDuration secs)
    return r

-- |Log using hie logger, reports source position of logging statement
logWith :: (HasCallStack, MonadIO m, Show a1, Show a2) => IdeState -> a1 -> a2 -> m ()
logWith state key val =
    liftIO . logPriority (ideLogger state) logLevel $
        T.unwords
            [T.pack logWithPos, asT key, asT val]
  where
    logWithPos =
        let stk = toList callStack
            pr pos = concat [srcLocFile pos, ":", show . srcLocStartLine $ pos, ":", show . srcLocStartCol $ pos]
         in if null stk then "" else pr . snd . head $ stk

    asT :: Show a => a -> T.Text
    asT = T.pack . show

-- | Set to Info to see extensive debug info in hie log, set to Debug in production
logLevel :: Priority
logLevel = Debug -- Info

isLiterate :: FilePath -> Bool
isLiterate x = takeExtension x `elem` [".lhs", ".lhs-boot"]

handleMaybe :: Monad m => e -> Maybe b -> ExceptT e m b
handleMaybe msg = maybe (throwE msg) return

handleMaybeM :: Monad m => e -> m (Maybe b) -> ExceptT e m b
handleMaybeM msg act = maybe (throwE msg) return =<< lift act

response :: Functor f => ExceptT String f c -> f (Either ResponseError c)
response =
    fmap (first (\msg -> ResponseError InternalError (fromString msg) Nothing))
        . runExceptT

response' :: ExceptT String IO a -> IO (Either ResponseError Value, Maybe a)
response' act = do
    res <- gStrictTry $ runExceptT act
    case join res of
        Left e ->
            return
                (Left (ResponseError InternalError (fromString e) Nothing), Nothing)
        Right a -> return (Right Null, Just a)

gStrictTry :: ExceptionMonad m => m b -> m (Either String b)
gStrictTry op =
    gcatch
        (op >>= fmap Right . gevaluate)
        showErr

gevaluate :: MonadIO m => a -> m a
gevaluate = liftIO . evaluate

showErr :: Monad m => SomeException -> m (Either String b)
showErr = return . Left . show