nvim-hs-2.3.2.3: src/Neovim/RPC/EventHandler.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{- |
Module : Neovim.RPC.EventHandler
Description : Event handling loop
Copyright : (c) Sebastian Witte
License : Apache-2.0
Maintainer : woozletoff@gmail.com
Stability : experimental
-}
module Neovim.RPC.EventHandler (
runEventHandler,
) where
import Neovim.Classes (NvimObject (toObject))
import Neovim.Context (MonadIO (..), asks)
import qualified Neovim.Context.Internal as Internal
import Neovim.Plugin.IPC.Classes (
FunctionCall (..),
Message (fromMessage),
Request (Request),
SomeMessage,
readSomeMessage,
)
import qualified Neovim.RPC.Classes as MsgpackRPC
import Neovim.RPC.Common (RPCConfig (nextMessageId, recipients))
import Neovim.RPC.FunctionCall (atomically')
import Conduit as C (
ConduitM,
ConduitT,
Flush (..),
ResourceT,
await,
runConduit,
runResourceT,
sinkHandleFlush,
yield,
(.|),
)
import Control.Monad (forever)
import Control.Monad.Reader (
MonadReader,
ReaderT (runReaderT),
)
import Data.ByteString (ByteString)
import qualified Data.Map as Map
import Data.Serialize (encode)
import System.IO (Handle)
import System.Log.Logger (debugM)
import UnliftIO (MonadUnliftIO, modifyTVar', readTVar)
import Prelude
{- | This function will establish a connection to the given socket and write
msgpack-rpc requests to it.
-}
runEventHandler ::
Handle ->
Internal.Config RPCConfig ->
IO ()
runEventHandler writeableHandle env =
runEventHandlerContext env . runConduit $ do
eventHandlerSource
.| eventHandler
.| sinkHandleFlush writeableHandle
-- | Convenient monad transformer stack for the event handler
newtype EventHandler a
= EventHandler (ResourceT (ReaderT (Internal.Config RPCConfig) IO) a)
deriving
( Functor
, Applicative
, Monad
, MonadIO
, MonadUnliftIO
, MonadReader (Internal.Config RPCConfig)
)
runEventHandlerContext ::
Internal.Config RPCConfig -> EventHandler a -> IO a
runEventHandlerContext env (EventHandler a) =
runReaderT (runResourceT a) env
eventHandlerSource :: ConduitT () SomeMessage EventHandler ()
eventHandlerSource =
asks Internal.eventQueue >>= \q ->
forever $ yield =<< readSomeMessage q
eventHandler :: ConduitM SomeMessage EncodedResponse EventHandler ()
eventHandler =
await >>= \case
Nothing ->
return () -- i.e. close the conduit -- TODO signal shutdown globally
Just message -> do
handleMessage (fromMessage message, fromMessage message)
eventHandler
type EncodedResponse = C.Flush ByteString
yield' :: (MonadUnliftIO io) => MsgpackRPC.Message -> ConduitM i EncodedResponse io ()
yield' o = do
liftIO . debugM "EventHandler" $ "Sending: " ++ show o
yield . Chunk . encode $ toObject o
yield Flush
handleMessage ::
(Maybe FunctionCall, Maybe MsgpackRPC.Message) ->
ConduitM i EncodedResponse EventHandler ()
handleMessage = \case
(Just (FunctionCall fn params reply time), _) -> do
cfg <- asks Internal.customConfig
messageId <- atomically' $ do
i <- readTVar (nextMessageId cfg)
modifyTVar' (nextMessageId cfg) succ
modifyTVar' (recipients cfg) $ Map.insert i (time, reply)
return i
yield' $ MsgpackRPC.Request (Request fn messageId params)
(_, Just r@MsgpackRPC.Response{}) ->
yield' r
(_, Just n@MsgpackRPC.Notification{}) ->
yield' n
_ ->
return () -- i.e. skip to next message