ghc-debugger-0.1.0.0: ghc-debug-adapter/Development/Debug/Adapter/Handles.hs
{-# LANGUAGE OverloadedStrings, OverloadedRecordDot, CPP, DeriveAnyClass, DeriveGeneric, DerivingVia, LambdaCase, RecordWildCards #-}
module Development.Debug.Adapter.Handles
( handleLogger
, withInterceptedStdout
, withInterceptedStderr
, withInterceptedStdoutForwarding
, withInterceptedStderrForwarding
) where
import DAP
import System.IO ()
import DAP.Log
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Control.Concurrent.MVar
import GHC.IO.Handle.FD
import GHC.IO.Handle
import System.Process
import Control.Exception
import Control.Monad
import Control.Concurrent.Async
{- Note [Debugger Handles]
~~~~~~~~~~~~~~~~~~~~~~~~~~
The debugger executes arbritary user programs which will output to stdout and stderr.
This output needs to be sent to the debug client. Any output we wish to put in a
log or elsewhere needs to be directed to another handle.
-}
handleLogger :: Handle -> IO (LogAction IO T.Text)
handleLogger out_handle = do
handleLock <- newMVar ()
return $ LogAction $ \msg -> do
withLock handleLock $ do
T.hPutStrLn out_handle msg
-- | Redirect stdout globally, run the continuation with a bypass handle for real stdout.
withInterceptedStdout :: (Handle -- ^ realStdout, write to this handle sends output to stdout
-> Handle -- ^ interceptedStdout, stdout captured from attempting to write to stdout handle.
-> IO ()) -> IO ()
withInterceptedStdout k = do
withPipe $ \readOutHandle writeOutHandle -> do
hSetBuffering readOutHandle NoBuffering
hSetBuffering writeOutHandle LineBuffering
withStdoutBypass writeOutHandle $ \realStdout -> do
k realStdout readOutHandle
-- | Redirect stderr globally, run the continuation with a bypass handle for real stderr.
withInterceptedStderr :: (Handle -- ^ realStderr, write to this handle sends output to stderr
-> Handle -- ^ interceptedStderr, stdout captured from attempting to write to stderr handle.
-> IO ()) -> IO ()
withInterceptedStderr k = do
withPipe $ \readErrHandle writeErrHandle -> do
hSetBuffering readErrHandle NoBuffering
hSetBuffering writeErrHandle LineBuffering
withStderrBypass writeErrHandle $ \realStderr -> do
k realStderr readErrHandle
-- | Intercept stderr, and spawn a thread which forwards the input
-- onwards using the supplied IO action.
withInterceptedStderrForwarding :: (T.Text -> IO ())
-- ^ All stderr input that is intercepted is forwarded to this thread
-> (Handle -> IO ())
-- ^ The continuation receives the REAL STDERR
-> IO ()
withInterceptedStderrForwarding write_stderr k = do
withInterceptedStderr $ \realStderr interceptedStderr -> do
withAsync (forwardingThread write_stderr interceptedStderr) $ \_ -> do
k realStderr
-- | Intercept stdout, and spawn a thread which forwards the input
-- onwards using the supplied IO action.
withInterceptedStdoutForwarding :: (T.Text -> IO ())
-- ^ All stdout input that is intercepted is forwarded to this thread
-> (Handle -> IO ())
-- ^ The continuation receives the REAL STDOUT
-> IO ()
withInterceptedStdoutForwarding write_stdout k = do
withInterceptedStdout $ \realStdout interceptedStdout -> do
withAsync (forwardingThread write_stdout interceptedStdout) $ \_ ->
k realStdout
--------------------------------------------------------------------------------
-- Auxiliary
--------------------------------------------------------------------------------
-- | Temporarily bypass the intercepted stdout/stderr to write directly to the original stdout/stderr.
-- This is useful for debugging or for sending output that should not be intercepted.
withStdoutBypass, withStderrBypass
:: Handle
-> (Handle -> IO r)
-> IO r
withStdoutBypass interceptH = withHandleBypass stdout interceptH
withStderrBypass interceptH = withHandleBypass stderr interceptH
-- | Capture all output written to a given handle and redirect it to the other
-- one; the continuation can use the "real" copy of the first handle to write
-- to it while bypassing the redirection.
--
-- This is useful for debugging or for sending output that should not be intercepted.
withHandleBypass :: Handle
-- ^ Text written to this handle...
-> Handle
-- ^ ...will be redirected to this handle
-> (Handle -> IO r)
-- ^ Continuation receives as an argument a "real" copy of the handle that is now being redirected.
-- If you write to this Handle, it will write to the original one and *bypass* the redirection (ie it will not be redirected)
-> IO r
withHandleBypass originalHandle interceptWriteHandle action =
bracket setup clean action
where
setup = do
realHandle <- hDuplicate originalHandle
hFlush originalHandle
hDuplicateTo interceptWriteHandle originalHandle
hSetBuffering originalHandle LineBuffering
return realHandle
clean realHandle = do
hFlush originalHandle
hDuplicateTo realHandle originalHandle
hClose realHandle
-- | Thread to read from the intercepted stdout pipe and forward onwards
forwardingThread :: (T.Text -> IO ()) -> Handle -> IO ()
forwardingThread write_action fromPipe = forever $ do
line <- T.hGetLine fromPipe
write_action line
withPipe :: (Handle -> Handle -> IO r) -> IO r
withPipe action = bracket createPipe closeBoth (uncurry action)
where
closeBoth (readH, writeH) = do
hClose readH
hClose writeH