{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module Neovim.Test (
runInEmbeddedNeovim,
runInEmbeddedNeovim',
Seconds (..),
TestConfiguration (..),
testWithEmbeddedNeovim,
) where
import Neovim
import Neovim.API.Text (nvim_command, vim_command)
import qualified Neovim.Context.Internal as Internal
import Neovim.RPC.Common (RPCConfig, newRPCConfig)
import Neovim.RPC.EventHandler (runEventHandler)
import Neovim.RPC.SocketReader (runSocketReader)
import Control.Monad.Reader (runReaderT)
import Data.Default (Default)
import Data.Text (pack)
import GHC.IO.Exception (ioe_filename)
import Neovim.Plugin (startPluginThreads)
import Neovim.Util (oneLineErrorMessage)
import Prettyprinter (annotate, vsep)
import Prettyprinter.Render.Terminal (Color (..), color)
import System.Process.Typed (
ExitCode (ExitFailure, ExitSuccess),
Process,
createPipe,
getExitCode,
getStdin,
getStdout,
proc,
setStdin,
setStdout,
startProcess,
stopProcess,
waitExitCode,
)
import UnliftIO (Handle, IOException, async, atomically, cancel, catch, newEmptyMVar, putMVar, putTMVar, throwIO, timeout)
import UnliftIO.Concurrent (takeMVar, threadDelay)
newtype Seconds = Seconds Word
deriving (Int -> Seconds -> ShowS
[Seconds] -> ShowS
Seconds -> String
(Int -> Seconds -> ShowS)
-> (Seconds -> String) -> ([Seconds] -> ShowS) -> Show Seconds
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Seconds -> ShowS
showsPrec :: Int -> Seconds -> ShowS
$cshow :: Seconds -> String
show :: Seconds -> String
$cshowList :: [Seconds] -> ShowS
showList :: [Seconds] -> ShowS
Show)
microSeconds :: Integral i => Seconds -> i
microSeconds :: forall i. Integral i => Seconds -> i
microSeconds (Seconds Word
s) = Word -> i
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word
s i -> i -> i
forall a. Num a => a -> a -> a
* i
1000 i -> i -> i
forall a. Num a => a -> a -> a
* i
1000
newtype TestConfiguration = TestConfiguration
{ TestConfiguration -> Seconds
cancelAfter :: Seconds
}
deriving (Int -> TestConfiguration -> ShowS
[TestConfiguration] -> ShowS
TestConfiguration -> String
(Int -> TestConfiguration -> ShowS)
-> (TestConfiguration -> String)
-> ([TestConfiguration] -> ShowS)
-> Show TestConfiguration
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TestConfiguration -> ShowS
showsPrec :: Int -> TestConfiguration -> ShowS
$cshow :: TestConfiguration -> String
show :: TestConfiguration -> String
$cshowList :: [TestConfiguration] -> ShowS
showList :: [TestConfiguration] -> ShowS
Show)
instance Default TestConfiguration where
def :: TestConfiguration
def =
TestConfiguration
{ cancelAfter :: Seconds
cancelAfter = Word -> Seconds
Seconds Word
2
}
runInEmbeddedNeovim :: TestConfiguration -> Plugin env -> Neovim env a -> IO ()
runInEmbeddedNeovim :: forall env a.
TestConfiguration -> Plugin env -> Neovim env a -> IO ()
runInEmbeddedNeovim TestConfiguration{Seconds
cancelAfter :: TestConfiguration -> Seconds
cancelAfter :: Seconds
..} Plugin env
plugin Neovim env a
action =
IO () -> IO ()
forall a. IO a -> IO ()
warnIfNvimIsNotOnPath IO ()
runTest
where
runTest :: IO ()
runTest = do
MVar a
resultMVar <- IO (MVar a)
forall (m :: * -> *) a. MonadIO m => m (MVar a)
newEmptyMVar
let action' :: Neovim env ()
action' = do
a
result <- Neovim env a
action
MVar StateTransition
q <- (Config env -> MVar StateTransition)
-> Neovim env (MVar StateTransition)
forall env a. (Config env -> a) -> Neovim env a
Internal.asks' Config env -> MVar StateTransition
forall env. Config env -> MVar StateTransition
Internal.transitionTo
MVar StateTransition -> StateTransition -> Neovim env ()
forall (m :: * -> *) a. MonadIO m => MVar a -> a -> m ()
putMVar MVar StateTransition
q StateTransition
Internal.Quit
Async ()
_ <- Neovim env () -> Neovim env (Async ())
forall (m :: * -> *) a. MonadUnliftIO m => m a -> m (Async a)
async (Neovim env () -> Neovim env (Async ()))
-> (Neovim env () -> Neovim env ())
-> Neovim env ()
-> Neovim env (Async ())
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Neovim env () -> Neovim env ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (Neovim env () -> Neovim env (Async ()))
-> Neovim env () -> Neovim env (Async ())
forall a b. (a -> b) -> a -> b
$ Text -> Neovim env ()
forall env. Text -> Neovim env ()
vim_command Text
"qa!"
MVar a -> a -> Neovim env ()
forall (m :: * -> *) a. MonadIO m => MVar a -> a -> m ()
putMVar MVar a
resultMVar a
result
(Process Handle Handle ()
nvimProcess, IO ()
cleanUp) <- Seconds
-> Plugin env
-> Neovim env ()
-> IO (Process Handle Handle (), IO ())
forall env.
Seconds
-> Plugin env
-> Neovim env ()
-> IO (Process Handle Handle (), IO ())
startEmbeddedNvim Seconds
cancelAfter Plugin env
plugin Neovim env ()
action'
Maybe a
result <- Int -> IO a -> IO (Maybe a)
forall (m :: * -> *) a.
MonadUnliftIO m =>
Int -> m a -> m (Maybe a)
timeout (Seconds -> Int
forall i. Integral i => Seconds -> i
microSeconds Seconds
cancelAfter) (MVar a -> IO a
forall (m :: * -> *) a. MonadIO m => MVar a -> m a
takeMVar MVar a
resultMVar)
Process Handle Handle () -> IO ExitCode
forall (m :: * -> *) stdin stdout stderr.
MonadIO m =>
Process stdin stdout stderr -> m ExitCode
waitExitCode Process Handle Handle ()
nvimProcess IO ExitCode -> (ExitCode -> IO ()) -> IO ()
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
ExitFailure Int
i ->
String -> IO ()
forall a. String -> IO a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"Neovim returned with an exit status of: " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
i
ExitCode
ExitSuccess -> case Maybe a
result of
Maybe a
Nothing -> String -> IO ()
forall a. String -> IO a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"Test timed out"
Just a
_ -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
IO ()
cleanUp
type TransitionHandler a = Internal.Config RPCConfig -> IO a
testTransitionHandler :: IO a -> TransitionHandler ()
testTransitionHandler :: forall a. IO a -> TransitionHandler ()
testTransitionHandler IO a
onInitAction Config RPCConfig
cfg =
MVar StateTransition -> IO StateTransition
forall (m :: * -> *) a. MonadIO m => MVar a -> m a
takeMVar (Config RPCConfig -> MVar StateTransition
forall env. Config env -> MVar StateTransition
Internal.transitionTo Config RPCConfig
cfg) IO StateTransition -> (StateTransition -> IO ()) -> IO ()
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
StateTransition
Internal.InitSuccess -> do
IO a -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void IO a
onInitAction
IO a -> TransitionHandler ()
forall a. IO a -> TransitionHandler ()
testTransitionHandler IO a
onInitAction Config RPCConfig
cfg
StateTransition
Internal.Restart -> do
String -> IO ()
forall a. String -> IO a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"Restart unexpected"
Internal.Failure Doc AnsiStyle
e -> do
String -> IO ()
forall a. String -> IO a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String -> IO ()) -> (Text -> String) -> Text -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
forall a. Show a => a -> String
show (Text -> IO ()) -> Text -> IO ()
forall a b. (a -> b) -> a -> b
$ Doc AnsiStyle -> Text
oneLineErrorMessage Doc AnsiStyle
e
StateTransition
Internal.Quit -> do
() -> IO ()
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
runInEmbeddedNeovim' :: TestConfiguration -> Neovim () a -> IO ()
runInEmbeddedNeovim' :: forall a. TestConfiguration -> Neovim () a -> IO ()
runInEmbeddedNeovim' TestConfiguration
testCfg = TestConfiguration -> Plugin () -> Neovim () a -> IO ()
forall env a.
TestConfiguration -> Plugin env -> Neovim env a -> IO ()
runInEmbeddedNeovim TestConfiguration
testCfg Plugin{environment :: ()
environment = (), exports :: [ExportedFunctionality ()]
exports = []}
{-# DEPRECATED testWithEmbeddedNeovim "Use \"runInEmbeddedNeovim def env action\" and open files with nvim_command \"edit file\"" #-}
testWithEmbeddedNeovim ::
Maybe FilePath ->
Seconds ->
env ->
Neovim env a ->
IO ()
testWithEmbeddedNeovim :: forall env a.
Maybe String -> Seconds -> env -> Neovim env a -> IO ()
testWithEmbeddedNeovim Maybe String
file Seconds
timeoutAfter env
env Neovim env a
action =
TestConfiguration -> Plugin env -> Neovim env () -> IO ()
forall env a.
TestConfiguration -> Plugin env -> Neovim env a -> IO ()
runInEmbeddedNeovim
TestConfiguration
forall a. Default a => a
def{cancelAfter = timeoutAfter}
Plugin{environment :: env
environment = env
env, exports :: [ExportedFunctionality env]
exports = []}
(Neovim env ()
forall {env}. Neovim env ()
openTestFile Neovim env () -> Neovim env a -> Neovim env ()
forall a b. Neovim env a -> Neovim env b -> Neovim env a
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
<* Neovim env a
action)
where
openTestFile :: Neovim env ()
openTestFile = case Maybe String
file of
Maybe String
Nothing -> () -> Neovim env ()
forall a. a -> Neovim env a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
Just String
f -> Text -> Neovim env ()
forall env. Text -> Neovim env ()
nvim_command (Text -> Neovim env ()) -> Text -> Neovim env ()
forall a b. (a -> b) -> a -> b
$ String -> Text
pack (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ String
"edit " String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
f
warnIfNvimIsNotOnPath :: IO a -> IO ()
warnIfNvimIsNotOnPath :: forall a. IO a -> IO ()
warnIfNvimIsNotOnPath IO a
test = IO a -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void IO a
test IO () -> (IOException -> IO ()) -> IO ()
forall (m :: * -> *) e a.
(MonadUnliftIO m, Exception e) =>
m a -> (e -> m a) -> m a
`catch` \(IOException
e :: IOException) -> case IOException -> Maybe String
ioe_filename IOException
e of
Just String
"nvim" ->
Doc AnsiStyle -> IO ()
putDoc (Doc AnsiStyle -> IO ())
-> (Doc AnsiStyle -> Doc AnsiStyle) -> Doc AnsiStyle -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AnsiStyle -> Doc AnsiStyle -> Doc AnsiStyle
forall ann. ann -> Doc ann -> Doc ann
annotate (Color -> AnsiStyle
color Color
Red) (Doc AnsiStyle -> IO ()) -> Doc AnsiStyle -> IO ()
forall a b. (a -> b) -> a -> b
$
[Doc AnsiStyle] -> Doc AnsiStyle
forall ann. [Doc ann] -> Doc ann
vsep
[ Doc AnsiStyle
"The neovim executable 'nvim' is not on the PATH."
, Doc AnsiStyle
"You may not be testing fully!"
]
Maybe String
_ ->
IOException -> IO ()
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO IOException
e
startEmbeddedNvim ::
Seconds ->
Plugin env ->
Neovim env () ->
IO (Process Handle Handle (), IO ())
startEmbeddedNvim :: forall env.
Seconds
-> Plugin env
-> Neovim env ()
-> IO (Process Handle Handle (), IO ())
startEmbeddedNvim Seconds
timeoutAfter Plugin env
plugin (Internal.Neovim ReaderT (Config env) IO ()
action) = do
Process Handle Handle ()
nvimProcess <-
ProcessConfig Handle Handle () -> IO (Process Handle Handle ())
forall (m :: * -> *) stdin stdout stderr.
MonadIO m =>
ProcessConfig stdin stdout stderr
-> m (Process stdin stdout stderr)
startProcess (ProcessConfig Handle Handle () -> IO (Process Handle Handle ()))
-> ProcessConfig Handle Handle () -> IO (Process Handle Handle ())
forall a b. (a -> b) -> a -> b
$
StreamSpec 'STInput Handle
-> ProcessConfig () Handle () -> ProcessConfig Handle Handle ()
forall stdin stdin0 stdout stderr.
StreamSpec 'STInput stdin
-> ProcessConfig stdin0 stdout stderr
-> ProcessConfig stdin stdout stderr
setStdin StreamSpec 'STInput Handle
forall (anyStreamType :: StreamType).
StreamSpec anyStreamType Handle
createPipe (ProcessConfig () Handle () -> ProcessConfig Handle Handle ())
-> ProcessConfig () Handle () -> ProcessConfig Handle Handle ()
forall a b. (a -> b) -> a -> b
$
StreamSpec 'STOutput Handle
-> ProcessConfig () () () -> ProcessConfig () Handle ()
forall stdout stdin stdout0 stderr.
StreamSpec 'STOutput stdout
-> ProcessConfig stdin stdout0 stderr
-> ProcessConfig stdin stdout stderr
setStdout StreamSpec 'STOutput Handle
forall (anyStreamType :: StreamType).
StreamSpec anyStreamType Handle
createPipe (ProcessConfig () () () -> ProcessConfig () Handle ())
-> ProcessConfig () () () -> ProcessConfig () Handle ()
forall a b. (a -> b) -> a -> b
$
String -> [String] -> ProcessConfig () () ()
proc String
"nvim" [String
"-n", String
"--clean", String
"--embed"]
Config RPCConfig
cfg <- IO (Maybe String) -> IO RPCConfig -> IO (Config RPCConfig)
forall env. IO (Maybe String) -> IO env -> IO (Config env)
Internal.newConfig (Maybe String -> IO (Maybe String)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe String
forall a. Maybe a
Nothing) IO RPCConfig
forall (io :: * -> *).
(Applicative io, MonadUnliftIO io) =>
io RPCConfig
newRPCConfig
Async ()
socketReader <-
IO () -> IO (Async ())
forall (m :: * -> *) a. MonadUnliftIO m => m a -> m (Async a)
async (IO () -> IO (Async ()))
-> (IO () -> IO ()) -> IO () -> IO (Async ())
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IO () -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO () -> IO (Async ())) -> IO () -> IO (Async ())
forall a b. (a -> b) -> a -> b
$
Handle -> TransitionHandler ()
runSocketReader
(Process Handle Handle () -> Handle
forall stdin stdout stderr. Process stdin stdout stderr -> stdout
getStdout Process Handle Handle ()
nvimProcess)
(Config RPCConfig
cfg{Internal.pluginSettings = Nothing})
Async ()
eventHandler <-
IO () -> IO (Async ())
forall (m :: * -> *) a. MonadUnliftIO m => m a -> m (Async a)
async (IO () -> IO (Async ()))
-> (IO () -> IO ()) -> IO () -> IO (Async ())
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IO () -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO () -> IO (Async ())) -> IO () -> IO (Async ())
forall a b. (a -> b) -> a -> b
$
Handle -> TransitionHandler ()
runEventHandler
(Process Handle Handle () -> Handle
forall stdin stdout stderr. Process stdin stdout stderr -> stdin
getStdin Process Handle Handle ()
nvimProcess)
(Config RPCConfig
cfg{Internal.pluginSettings = Nothing})
let actionCfg :: Config env
actionCfg = env -> Config RPCConfig -> Config env
forall env anotherEnv. env -> Config anotherEnv -> Config env
Internal.retypeConfig (Plugin env -> env
forall env. Plugin env -> env
environment Plugin env
plugin) Config RPCConfig
cfg
action' :: IO ()
action' = ReaderT (Config env) IO () -> Config env -> IO ()
forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT ReaderT (Config env) IO ()
action Config env
actionCfg
[Async ()]
pluginHandlers <-
Config ()
-> [Neovim () NeovimPlugin]
-> IO (Either (Doc AnsiStyle) ([FunctionMapEntry], [Async ()]))
startPluginThreads (() -> Config RPCConfig -> Config ()
forall env anotherEnv. env -> Config anotherEnv -> Config env
Internal.retypeConfig () Config RPCConfig
cfg) [Plugin env -> Neovim () NeovimPlugin
forall (m :: * -> *) env.
Applicative m =>
Plugin env -> m NeovimPlugin
wrapPlugin Plugin env
plugin] IO (Either (Doc AnsiStyle) ([FunctionMapEntry], [Async ()]))
-> (Either (Doc AnsiStyle) ([FunctionMapEntry], [Async ()])
-> IO [Async ()])
-> IO [Async ()]
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Left Doc AnsiStyle
e -> do
MVar StateTransition -> StateTransition -> IO ()
forall (m :: * -> *) a. MonadIO m => MVar a -> a -> m ()
putMVar (Config RPCConfig -> MVar StateTransition
forall env. Config env -> MVar StateTransition
Internal.transitionTo Config RPCConfig
cfg) (StateTransition -> IO ()) -> StateTransition -> IO ()
forall a b. (a -> b) -> a -> b
$ Doc AnsiStyle -> StateTransition
Internal.Failure Doc AnsiStyle
e
[Async ()] -> IO [Async ()]
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure []
Right ([FunctionMapEntry]
funMapEntries, [Async ()]
pluginTids) -> do
STM () -> IO ()
forall (m :: * -> *) a. MonadIO m => STM a -> m a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$
TMVar FunctionMap -> FunctionMap -> STM ()
forall a. TMVar a -> a -> STM ()
putTMVar
(Config RPCConfig -> TMVar FunctionMap
forall env. Config env -> TMVar FunctionMap
Internal.globalFunctionMap Config RPCConfig
cfg)
([FunctionMapEntry] -> FunctionMap
Internal.mkFunctionMap [FunctionMapEntry]
funMapEntries)
MVar StateTransition -> StateTransition -> IO ()
forall (m :: * -> *) a. MonadIO m => MVar a -> a -> m ()
putMVar (Config RPCConfig -> MVar StateTransition
forall env. Config env -> MVar StateTransition
Internal.transitionTo Config RPCConfig
cfg) StateTransition
Internal.InitSuccess
[Async ()] -> IO [Async ()]
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure [Async ()]
pluginTids
Async ()
transitionHandler <- IO () -> IO (Async ())
forall (m :: * -> *) a. MonadUnliftIO m => m a -> m (Async a)
async (IO () -> IO (Async ()))
-> (IO () -> IO ()) -> IO () -> IO (Async ())
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IO () -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO () -> IO (Async ())) -> IO () -> IO (Async ())
forall a b. (a -> b) -> a -> b
$ do
IO () -> TransitionHandler ()
forall a. IO a -> TransitionHandler ()
testTransitionHandler IO ()
action' Config RPCConfig
cfg
Async ()
timeoutAsync <- IO () -> IO (Async ())
forall (m :: * -> *) a. MonadUnliftIO m => m a -> m (Async a)
async (IO () -> IO (Async ()))
-> (IO () -> IO ()) -> IO () -> IO (Async ())
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IO () -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO () -> IO (Async ())) -> IO () -> IO (Async ())
forall a b. (a -> b) -> a -> b
$ do
Int -> IO ()
forall (m :: * -> *). MonadIO m => Int -> m ()
threadDelay (Int -> IO ()) -> Int -> IO ()
forall a b. (a -> b) -> a -> b
$ Seconds -> Int
forall i. Integral i => Seconds -> i
microSeconds Seconds
timeoutAfter
Process Handle Handle () -> IO (Maybe ExitCode)
forall (m :: * -> *) stdin stdout stderr.
MonadIO m =>
Process stdin stdout stderr -> m (Maybe ExitCode)
getExitCode Process Handle Handle ()
nvimProcess IO (Maybe ExitCode) -> (Maybe ExitCode -> IO ()) -> IO ()
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= IO () -> (ExitCode -> IO ()) -> Maybe ExitCode -> IO ()
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Process Handle Handle () -> IO ()
forall (m :: * -> *) stdin stdout stderr.
MonadIO m =>
Process stdin stdout stderr -> m ()
stopProcess Process Handle Handle ()
nvimProcess) (\ExitCode
_ -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ())
let cleanUp :: IO ()
cleanUp =
(Async () -> IO ()) -> [Async ()] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Async () -> IO ()
forall (m :: * -> *) a. MonadIO m => Async a -> m ()
cancel ([Async ()] -> IO ()) -> [Async ()] -> IO ()
forall a b. (a -> b) -> a -> b
$
[Async ()
socketReader, Async ()
eventHandler, Async ()
timeoutAsync, Async ()
transitionHandler]
[Async ()] -> [Async ()] -> [Async ()]
forall a. [a] -> [a] -> [a]
++ [Async ()]
pluginHandlers
(Process Handle Handle (), IO ())
-> IO (Process Handle Handle (), IO ())
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Process Handle Handle ()
nvimProcess, IO ()
cleanUp)