{-# 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