{-# LANGUAGE CPP #-}
module Reactor where
import GHC.Wasm.Prim
import Options
import Server (serverDefn)
import qualified Data.Text as T
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import qualified Data.Text.Lazy as TL
import Data.Text.Encoding (encodeUtf8)
import Data.Text.Lazy.Encoding (decodeUtf8)
import Control.Concurrent (MVar, newEmptyMVar, takeMVar, putMVar)
import Foreign.StablePtr (StablePtr, newStablePtr, freeStablePtr, deRefStablePtr)
import Data.ByteString.Lazy (LazyByteString)
import Language.LSP.Server (ServerConfig(..), runServerWithConfig)
import qualified Data.Attoparsec.ByteString as Attoparsec
data ReactorEnv = ReactorEnv
{ options :: Options
, incomingMessage :: MVar B.StrictByteString
, outgoingMessage :: MVar String
}
initialEnv :: IO ReactorEnv
initialEnv = ReactorEnv <$> getOptionsFromArgv <*> newEmptyMVar <*> newEmptyMVar
type ServerHandle = StablePtr ReactorEnv
foreign export javascript "run_setup"
runSetup :: IO ()
foreign export javascript "new_language_server"
newLanguageServer :: IO ServerHandle
foreign export javascript "run_language_server"
runLanguageServer :: ServerHandle -> IO Int
foreign export javascript "free_language_server"
freeLanguageServer :: ServerHandle -> IO ()
foreign export javascript "send_message"
sendMessage :: ServerHandle -> JSString -> IO ()
foreign export javascript "recv_message"
recvMessage :: ServerHandle -> IO JSString
runSetup :: IO ()
#if MIN_VERSION_Agda(2,8,0)
runSetup = setup True
#else
runSetup = error "This Agda version does not have setup functionality."
#endif
newLanguageServer :: IO ServerHandle
newLanguageServer = initialEnv >>= newStablePtr
freeLanguageServer :: ServerHandle -> IO ()
freeLanguageServer = freeStablePtr
runLanguageServer :: ServerHandle -> IO Int
runLanguageServer hdl = do
env <- deRefStablePtr hdl
let
serverInwards :: IO B.StrictByteString
serverInwards = takeMVar (incomingMessage env)
serverOutwards :: BL.LazyByteString -> IO ()
serverOutwards s = (return . TL.unpack . decodeUtf8) s >>= putMVar (outgoingMessage env)
runFromReactor serverInwards serverOutwards (options env)
runFromReactor :: IO B.StrictByteString -> (LazyByteString -> IO ()) -> Options -> IO Int
runFromReactor serverInwards serverOutwards options = do
runServerWithConfig serverConfig (serverDefn options)
where
serverConfig :: ServerConfig Config
serverConfig = ServerConfig
{ ioLogger = mempty
, lspLogger = mempty
, inwards = serverInwards
, outwards = serverOutwards
, prepareOutwards = id
, parseInwards = do
-- using takeByteString here will make it return partial result,
-- requiring another empty string to signal its end
chunk <- Attoparsec.getChunk
case chunk of
Nothing -> pure B.empty
Just xs -> Attoparsec.take $ B.length xs
}
sendMessage :: ServerHandle -> JSString -> IO ()
sendMessage hdl s = do
env <- deRefStablePtr hdl
let input = fromJSString s
putMVar (incomingMessage env) $ (encodeUtf8 . T.pack) input
return ()
recvMessage :: ServerHandle -> IO JSString
recvMessage hdl = do
env <- deRefStablePtr hdl
str <- takeMVar (outgoingMessage env)
return $ toJSString str
-- for shimming types when GHC.Wasm.Prim is not available, e.g., when using HLS
#if 0
data JSVal = JSVal {}
newtype JSString = JSString JSVal
fromJSString :: JSString -> String
fromJSString = undefined
toJSString :: String -> JSString
toJSString = undefined
#endif