{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

{- |
Module      :  Neovim.Test
Description :  Testing functions
Copyright   :  (c) Sebastian Witte
License     :  Apache-2.0

Maintainer  :  woozletoff@gmail.com
Stability   :  experimental
Portability :  GHC
-}
module Neovim.Test (
    runInEmbeddedNeovim,
    runInEmbeddedNeovim',
    Seconds (..),
    TestConfiguration (..),
    -- deprecated
    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)

-- | Type synonym for 'Word'.
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
            }

{- | Run a neovim process with @-n --clean --embed@ and execute the
 given action that will have access to the started instance.

The 'TestConfiguration' contains sensible defaults.

'env' is the state of your function that you want to test.
-}
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
                -- vim_command isn't asynchronous, so we need to avoid waiting
                -- for the result of the operation by using 'async' since
                -- neovim cannot send a result if it has 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\"" #-}

{- | The same as 'runInEmbeddedNeovim' with the given file opened via @nvim_command "edit file"@.
 - This method is kept for backwards compatibility.
-}
testWithEmbeddedNeovim ::
    -- | Optional path to a file that should be opened
    Maybe FilePath ->
    -- | Maximum time (in seconds) that a test is allowed to run
    Seconds ->
    -- | Read-only configuration
    env ->
    -- | Test case
    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)