packages feed

haskell-debug-adapter-0.0.40.0: src/Haskell/Debug/Adapter/Control.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

module Haskell.Debug.Adapter.Control (
    ArgData(..)
  , run
  ) where

import System.IO
import qualified System.Log.Logger as L
import qualified Control.Exception.Safe as E
import Control.Concurrent.Async
import Control.Concurrent.MVar
import Data.Version (showVersion)
import Paths_haskell_debug_adapter (version)
import Control.Lens

import Haskell.Debug.Adapter.Type
import Haskell.Debug.Adapter.Constant
import qualified Haskell.Debug.Adapter.Application as A
import qualified Haskell.Debug.Adapter.Request as RQ
import qualified Haskell.Debug.Adapter.Response as RP
import qualified Haskell.Debug.Adapter.Watch as W


-- |
--   Start HDA.
--   Default implementation is using STDIN/STDOUT handle.
--
--   Here is an example for using TCP Socket.
--
-- > import Network.Socket
-- >
-- > sock <- socket AF_INET Stream defaultProtocol
-- > let host = tupleToHostAddress (0, 0, 0, 0)
-- >     port = 9999
-- >     reqQ = 5
-- >
-- > bind sock $ SockAddrInet port host
-- > listen sock reqQ
-- >
-- > (conn, _) <- accept sock
-- > hdl <- socketToHandle conn ReadWriteMode
-- >
-- > run def hdl hdl
-- >
--
--   Port 9999 could be specified in the launch.json with "debugServer" attribute.
--
-- > "debugServer : 9999"
--
run :: ArgData -- ^command line arguments.
    -> Handle  -- ^IN handle. used to get request from the debug adapter client.
    -> Handle  -- ^OUT handle. used to response to the debug adapter client.
    -> IO ()
run argDat inHdl outHdl = E.bracket initialize finalize go

  where
    -- |
    --
    initialize = do
      L.debugM _LOG_NAME $ "initialize called."

      hSetBuffering inHdl NoBuffering
      hSetEncoding  inHdl utf8

      hSetBuffering outHdl NoBuffering
      hSetEncoding  outHdl utf8

      reqStore <- newMVar []
      resStore <- newMVar []
      evtStore <- newMVar []
      wsStore  <- newMVar ""
      logPRStore <- newMVar L.WARNING
      procStore  <- newEmptyMVar
      verStore   <- newEmptyMVar

      return AppStores {
        -- Read Only
          _appNameAppStores     = "haskell-debug-adapter"
        , _appVerAppStores      = showVersion version
        , _inHandleAppStores    = inHdl
        , _outHandleAppStores   = outHdl
        , _asyncsAppStores      = []
        , _stdioLogFileAppStores = argDat^.stdioLogFileArgData

        -- Read/Write from Application
        , _appStateWAppStores   = WrapAppState InitState
        , _resSeqAppStores      = 0
        , _startupAppStores     = ""
        , _startupFuncAppStores = ""
        , _startupArgsAppStores = ""
        , _stopOnEntryAppStores = False
        , _ghciPmptAppStores    = _GHCI_PROMPT_HDA
        , _mainArgsAppStores    = ""
        , _launchReqSeqAppStores = -1
        , _debugReRunableAppStores = False

        -- Read/Write ASync
        , _reqStoreAppStores    = reqStore
        , _resStoreAppStores    = resStore
        , _eventStoreAppStores  = evtStore
        , _workspaceAppStores   = wsStore
        , _logPriorityAppStores = logPRStore
        , _ghciProcAppStores    = procStore
        , _ghciVerAppStores     = verStore
        }

    -- |
    --
    finalize _ = do
      L.debugM _LOG_NAME $ "finalize called."
      L.removeAllHandlers

    -- |
    --
    go appData = do

      L.debugM _LOG_NAME $ "start thread manager."

      let ths = [
                  RQ.run appData    -- request handler
                , A.run  appData    -- main app
                , RP.run appData    -- response handler
                -- , W.run  appData    -- file watch
                ]

      -- 
      -- suspend file watch.
      -- because on vbox CentOS Stream release 9, throw error.
      --   Error: couldn't start native file manager: fdType: unsupported operation (unknown file type)
      --
      -- https://github.com/haskell-fswatch/hfsnotify/blob/master/src/System/FSNotify.hs#L167C1-L178C7
      --   case confWatchMode conf of
      --     WatchModePoll interval -> WatchManager conf <$> liftIO (createPollManager interval) <*> cleanupVar <*> globalWatchChan
      -- #ifndef OS_BSD
      --     WatchModeOS -> liftIO (initSession ()) >>= createManager
      -- #endif
      -- 
      --   where
      -- #ifndef OS_BSD
      --     createManager :: Either Text NativeManager -> IO WatchManager
      --     createManager (Right nativeManager) = WatchManager conf nativeManager <$> cleanupVar <*> globalWatchChan
      --     createManager (Left err) = throwIO $ userError $ T.unpack $ "Error: couldn't start native file manager: " <> err
      -- #endif
      -- 
      -- https://github.com/haskell-fswatch/hfsnotify/blob/master/src/System/FSNotify/Linux.hs#L94C1-L97C45
      -- instance FileListener INotifyListener () where
      --   initSession _ = E.handle (\(e :: IOException) -> return $ Left $ fromString $ show e) $ do
      --     inotify <- INo.initINotify
      --     return $ Right $ INotifyListener inotify
      -- 
      -- https://hackage.haskell.org/package/hinotify-0.4.1/docs/src/System.INotify.html#initINotify
      -- initINotify :: IO INotify
      -- initINotify = do
      --     throwErrnoIfMinus1 "initINotify" c_inotify_init
      --     (fd,fd_type) <- FD.mkFD fdint ReadMode (Just (Stream,0,0))
      -- 




      as <- mapM async ths
      waitAnyCatchCancel as >>= \case
        (_, Right _) -> L.infoM _LOG_NAME $ "some threads stopped. exit."
        (_, Left e)  -> L.criticalM _LOG_NAME $ "some threads stopped. " ++ show e