module Neovim.Main
where
import Neovim.Config
import Neovim.Plugin as P
import qualified Neovim.Plugin.ConfigHelper as ConfigHelper
import qualified Config.Dyre as Dyre
import qualified Config.Dyre.Relaunch as Dyre
import Control.Concurrent
import Control.Concurrent.STM
import Data.Monoid
import Neovim.Context
import Neovim.Debug
import Neovim.RPC.Common as RPC
import Neovim.RPC.EventHandler
import Neovim.RPC.SocketReader
import Options.Applicative
import System.IO (stdin, stdout)
data CommandLineOptions =
Opt { providerName :: String
, hostPort :: Maybe (String, Int)
, unix :: Maybe FilePath
, env :: Bool
, logOpts :: Maybe (FilePath, Priority)
}
optParser :: Parser CommandLineOptions
optParser = Opt
<$> strArgument
(metavar "NAME"
<> help "Name that associates the plugin provider with neovim")
<*> optional ((,)
<$> strOption
(long "host"
<> short 'a'
<> metavar "HOSTNAME"
<> help "Connect to the specified host. (requires -p)")
<*> option auto
(long "port"
<> short 'p'
<> metavar "PORT"
<> help "Connect to the specified port. (requires -a)"))
<*> optional (strOption
(long "unix"
<> short 'u'
<> help "Connect to the given unix domain socket."))
<*> switch
( long "environment"
<> short 'e'
<> help "Read connection information from $NVIM_LISTEN_ADDRESS.")
<*> optional ((,)
<$> strOption
(long "log-file"
<> short 'l'
<> help "File to log to.")
<*> option auto
(long "log-level"
<> short 'v'
<> help ("Log level. Must be one of: " ++ (unwords . map show) logLevels)))
where
logLevels :: [Priority]
logLevels = [ DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY ]
opts :: ParserInfo CommandLineOptions
opts = info (helper <*> optParser)
(fullDesc
<> header "Start a neovim plugin provider for Haskell plugins."
<> progDesc "This is still work in progress. Feel free to contribute.")
neovim :: NeovimConfig -> IO ()
neovim conf =
let params = Dyre.defaultParams
{ Dyre.showError = \cfg errM -> cfg { errorMessage = Just errM }
, Dyre.projectName = "nvim"
, Dyre.realMain = realMain
, Dyre.statusOut = debugM "Dyre"
, Dyre.ghcOpts = ["-threaded", "-rtsopts", "-with-rtsopts=-N"]
}
in Dyre.wrapMain params (conf { dyreParams = Just params })
realMain :: NeovimConfig -> IO ()
realMain cfg = do
os <- execParser opts
maybe disableLogger (uncurry withLogger) (logOpts os <|> logOptions cfg) $ do
logM "Neovim.Main" DEBUG "Starting up neovim haskell plguin provider"
runPluginProvider os cfg
runPluginProvider :: CommandLineOptions -> NeovimConfig -> IO ()
runPluginProvider os = case (hostPort os, unix os) of
(Just (h,p), _) -> let s = TCP p h in run s s
(_, Just fp) -> let s = UnixSocket fp in run s s
_ | env os -> run Environment Environment
_ -> run (Stdout stdout) (Stdout stdin)
where
run evHandlerSocket sockreaderSocket cfg = do
rpcConfig <- newRPCConfig
q <- newTQueueIO
quitter <- newEmptyMVar
let conf = ConfigWrapper q quitter (providerName os) ()
allPlugins = maybe id ((:) . ConfigHelper.plugin) (dyreParams cfg) $ plugins cfg
startPluginThreads (conf { customConfig = RPC.functions rpcConfig }) allPlugins >>= \case
Left e -> errorM "Neovim.Main" $ "Error initializing plugins: " <> e
Right pluginTidsWithQueues -> do
let rpcEnv = conf { customConfig = rpcConfig }
ehTid <- forkIO $ runEventHandler evHandlerSocket rpcEnv
_ <- forkIO $ register (conf { customConfig = RPC.functions rpcConfig }) pluginTidsWithQueues
let pluginTids = concatMap (map fst . snd) pluginTidsWithQueues
rTid <- forkIO $ runSocketReader sockreaderSocket rpcEnv
debugM "Neovim.Main" "Waiting for threads to finish."
finish (rTid:ehTid:pluginTids) =<< readMVar quitter
finish :: [ThreadId] -> QuitAction -> IO ()
finish threads = \case
Restart -> do
debugM "Neovim.Main" "Trying to restart nvim-hs"
mapM_ killThread threads
Dyre.relaunchMaster Nothing
Quit -> return ()