cornelis-0.2.0.0: src/Cornelis/Agda.hs
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}
module Cornelis.Agda where
import Control.Concurrent.Chan.Unagi (newChan, readChan, writeChan)
import Control.Lens
import Control.Monad (forever, replicateM_, when)
import Control.Monad.IO.Class
import Control.Monad.State
import Cornelis.Debug (reportExceptions)
import Cornelis.InfoWin (buildInfoBuffer)
import Cornelis.Types
import Cornelis.Types.Agda
import Cornelis.Utils
import Data.Aeson
import Data.IORef
import qualified Data.Map as M
import qualified Data.Text as T
import qualified Data.Text.Lazy as LT
import Data.Text.Lazy.Encoding (encodeUtf8)
import qualified Data.Text.Lazy.IO as LT
import Neovim hiding (err)
import Neovim.API.Text
import System.IO hiding (hGetLine)
import System.Process
------------------------------------------------------------------------------
-- | When true, dump out received JSON as it arrives.
debugJson :: Bool
debugJson = False
------------------------------------------------------------------------------
-- | Create an 'Agda' environment for the given buffer. This spawns an
-- asynchronous thread that keeps an agda process alive as long as vim is open.
--
-- TODO(sandy): This leaks the process when the buffer is closed.
spawnAgda :: Buffer -> Neovim CornelisEnv Agda
spawnAgda buffer = do
(m_in, m_out, _, hdl) <-
liftIO $ createProcess $
(proc "agda" ["--interaction-json"])
{ std_in = CreatePipe , std_out = CreatePipe }
(c_in, c_out) <- liftIO newChan
ready <- liftIO $ newIORef True
case (m_in, m_out) of
(Just hin, Just hout) -> do
liftIO $ do
hSetBuffering hin NoBuffering
hSetBuffering hout NoBuffering
let whenReady act = liftIO $ do
-- Agda outputs "JSON> " when it is ready
-- We skip it, and set the ready flag
c <- hLookAhead hout
case c of
'J' -> replicateM_ 6 (hGetChar hout) >> act
_ -> pure ()
-- On the first load, we make ourselves ready before Agda tells us anything
void $ neovimAsync $ (whenReady (pure ()) >>) . forever $ reportExceptions $ do
whenReady $ atomicWriteIORef ready True
resp <- liftIO $ LT.hGetLine hout
chan <- asks ce_stream
case eitherDecode @Response $ encodeUtf8 resp of
_ | LT.null resp -> pure ()
Left err -> vim_report_error $ T.pack err
Right res -> do
case res of
HighlightingInfo _ _ -> pure ()
_ -> when debugJson $ vim_report_error $ T.pack $ show resp
liftIO $ writeChan chan $ AgdaResp buffer res
void $ neovimAsync $ liftIO $ forever $ do
msg <- readChan c_out
atomicWriteIORef ready False
hPutStrLn hin msg
pure $ Agda buffer ready c_in hdl
(_, _) -> error "can't start agda"
------------------------------------------------------------------------------
-- | Drop a prefix from the text, if it exists.
dropPrefix :: LT.Text -> LT.Text -> LT.Text
dropPrefix pref msg
| LT.isPrefixOf pref msg = LT.drop (LT.length pref) msg
| otherwise = msg
------------------------------------------------------------------------------
-- | Send an 'Interaction' to an 'Agda'.
runIOTCM :: Interaction -> Agda -> Neovim env ()
runIOTCM i agda = do
iotcm <- buildIOTCM i $ a_buffer agda
-- liftIO $ appendFile "/tmp/agda.spy" $ show iotcm
liftIO $ writeChan (a_req agda) (show iotcm)
------------------------------------------------------------------------------
-- | Construct an 'IOTCM' for a buffer.
buildIOTCM :: Interaction -> Buffer -> Neovim env IOTCM
buildIOTCM i buffer = do
fp <- buffer_get_name buffer
pure $ IOTCM fp NonInteractive Direct i
------------------------------------------------------------------------------
-- | Get the current buffer and run the continuation.
withCurrentBuffer :: (Buffer -> Neovim env a) -> Neovim env a
withCurrentBuffer f = vim_get_current_buffer >>= f
------------------------------------------------------------------------------
-- | Ensure we have a 'BufferStuff' attached to the current buffer.
withAgda :: Neovim CornelisEnv a -> Neovim CornelisEnv a
withAgda m = do
buffer <- vim_get_current_buffer
gets (M.lookup buffer . cs_buffers) >>= \case
Just _ -> m
Nothing -> do
agda <- spawnAgda buffer
iw <- buildInfoBuffer
modify' $ #cs_buffers %~ M.insert buffer BufferStuff
{ bs_agda_proc = agda
, bs_ips = mempty
, bs_ip_exts = mempty
, bs_goto_sites = mempty
, bs_goals = AllGoalsWarnings [] [] [] []
, bs_info_win = iw
, bs_code_map = mempty
}
m
------------------------------------------------------------------------------
-- | Get the 'Agda' environment for a given buffer.
getAgda :: Buffer -> Neovim CornelisEnv Agda
getAgda buffer = gets $ bs_agda_proc . (M.! buffer) . cs_buffers