nvim-hs-0.0.1: library/Neovim/Context.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE LambdaCase #-}
{- |
Module : Neovim.Context
Description : The Neovim context
Copyright : (c) Sebastian Witte
License : Apache-2.0
Maintainer : woozletoff@gmail.com
Stability : experimental
-}
module Neovim.Context (
asks,
ask,
eventQueue,
get,
put,
modify,
gets,
Neovim,
Neovim',
NeovimException(..),
ConfigWrapper(..),
runNeovim,
forkNeovim,
err,
restart,
quit,
QuitAction(..),
throwError,
module Control.Monad.IO.Class,
) where
import Control.Concurrent (MVar, ThreadId, forkIO, putMVar)
import Control.Concurrent.STM
import Control.Exception
import Control.Monad.Except
import Control.Monad.IO.Class
import Control.Monad.Reader hiding (ask, asks)
import qualified Control.Monad.Reader as R
import Control.Monad.State
import Data.Data (Typeable)
import Neovim.Plugin.IPC (SomeMessage)
import System.Log.Logger
-- | A wrapper for a reader value that contains extra fields required to
-- communicate with the messagepack-rpc components.
data ConfigWrapper a = ConfigWrapper
{ _eventQueue :: TQueue SomeMessage
-- ^ A queue of messages that the event handler will propagate to
-- appropriate threads and handlers.
, _quit :: MVar QuitAction
-- ^ The main thread will wait for this 'MVar' to be filled with a value
-- and then perform an action appropriate for the value of type
-- 'QuitAction'.
, _providerName :: String
-- ^ Name that is used to identify this provider. Assigning such a name is
-- done in the neovim config (e.g. ~\/.nvim\/nvimrc).
, customConfig :: a
-- ^ Plugin author supplyable custom configuration. It can be queried via
-- 'myConf'.
}
data QuitAction = Quit
-- ^ Quit the plugin provider.
| Restart
-- ^ Restart the plugin provider.
deriving (Show, Read, Eq, Ord, Enum, Bounded)
eventQueue :: Neovim r st (TQueue SomeMessage)
eventQueue = R.asks _eventQueue
-- | This is the environment in which all plugins are initially started.
-- Stateless functions use '()' for the static configuration and the mutable
-- state and there is another type alias for that case: 'Neovim''.
--
-- Functions have to run in this transformer stack to communicate with neovim.
-- If parts of your own functions dont need to communicate with neovim, it is
-- good practice to factor them out. This allows you to write tests and spot
-- errors easier. Essentially, you should treat this similar to 'IO' in general
-- haskell programs.
type Neovim r st = StateT st (ReaderT (ConfigWrapper r) IO)
-- | Convenience alias for @'Neovim' () ()@.
type Neovim' = Neovim () ()
-- | Initialize a 'Neovim' context by supplying an 'InternalEnvironment'.
runNeovim :: ConfigWrapper r
-> st
-> Neovim r st a
-> IO (Either String (a, st))
runNeovim r st a = (try . runReaderT (runStateT a st)) r >>= \case
Left e -> do
liftIO . errorM "Context" $ "Converting Exception to Error message: " ++ show e
return . Left $ show (e :: SomeException)
Right res -> return $ Right res
-- | Fork a neovim thread with the given custom config value and a custom
-- state. The result of the thread is discarded and only the 'ThreadId' is
-- returend immediately.
forkNeovim :: ir -> ist -> Neovim ir ist a -> Neovim r st ThreadId
forkNeovim r st a = do
cfg <- R.ask
liftIO . forkIO . void $ runNeovim (cfg { customConfig = r }) st a
data NeovimException
= ErrorMessage String
deriving (Typeable, Show)
instance Exception NeovimException
-- | @throw . ErrorMessage@
err :: String -> Neovim r st a
err = throw . ErrorMessage
-- | Retrieve something from the configuration with respect to the first
-- function. Works exactly like 'R.asks'.
asks :: (r -> a) -> Neovim r st a
asks q = R.asks (q . customConfig)
-- | Retrieve the Cunfiguration (i.e. read-only state) from the 'Neovim'
-- context.
ask :: Neovim r st r
ask = R.asks customConfig
-- | Initiate a restart of the plugin provider.
restart :: Neovim r st ()
restart = liftIO . flip putMVar Restart =<< R.asks _quit
-- | Initiate the termination of the plugin provider.
quit :: Neovim r st ()
quit = liftIO . flip putMVar Quit =<< R.asks _quit