diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a39266..18b9f5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Revision history for dap +## Unreleased -- YYYY-mm-dd + +* `Adaptor` has an additional type parameter denoting the type of the request + we are responding to. Crucially, this will be `Request` when responding to a + DAP request (e.g. in `send***` functions). + On the other hand, this will be `()` for the `withAdaptor` continuation + argument of `registerNewDebugSession` which unlifts `Adaptor` to `IO` + because, when unlifting, we are not replying to any request. + ## 0.1.0.0 -- YYYY-mm-dd * First version. Released on an unsuspecting world. diff --git a/dap.cabal b/dap.cabal index 610e7a9..283d929 100644 --- a/dap.cabal +++ b/dap.cabal @@ -27,17 +27,17 @@ library DAP.Types DAP.Utils build-depends: - aeson >= 2.0.3 && < 2.1, + aeson >= 2.0.3 && < 2.3, aeson-pretty >= 0.8.9 && < 0.9, base < 5, - bytestring >= 0.11.4 && < 0.12, + bytestring >= 0.11.4 && < 0.13, containers >= 0.6.5 && < 0.7, lifted-base >= 0.2.3 && < 0.3, monad-control >= 1.0.3 && < 1.1, - mtl >= 2.2.2 && < 2.3, + mtl >= 2.2.2 && < 2.4, network >= 3.1.2 && < 3.2, network-simple >= 0.4.5 && < 0.5, - text >= 1.2.5 && < 1.3, + text >= 1.2.5 && < 2.2, time >= 1.11.1 && < 1.12, unordered-containers >= 0.2.19 && < 0.3, stm >= 2.5.0 && < 2.6, diff --git a/default.nix b/default.nix index cafaa35..7081181 100644 --- a/default.nix +++ b/default.nix @@ -1,6 +1,6 @@ { pkgs ? import {} }: let - dap = pkgs.haskell.packages.ghc927.callCabal2nix "dap" ./. {}; + dap = pkgs.haskell.packages.ghc966.callCabal2nix "dap" ./. {}; in { inherit dap pkgs; diff --git a/src/DAP.hs b/src/DAP.hs index 8c0d63e..0f3bb57 100644 --- a/src/DAP.hs +++ b/src/DAP.hs @@ -1,3 +1,4 @@ +---------------------------------------------------------------------------- module DAP ( module DAP.Adaptor , module DAP.Event @@ -6,10 +7,11 @@ module DAP , module DAP.Server , module DAP.Types ) where - +---------------------------------------------------------------------------- import DAP.Adaptor import DAP.Event import DAP.Internal import DAP.Response import DAP.Server import DAP.Types +---------------------------------------------------------------------------- diff --git a/src/DAP/Adaptor.hs b/src/DAP/Adaptor.hs index 344387a..2f246f0 100644 --- a/src/DAP/Adaptor.hs +++ b/src/DAP/Adaptor.hs @@ -50,18 +50,21 @@ module DAP.Adaptor -- * Internal function used to execute actions on behalf of the DAP server -- from child threads (useful for handling asynchronous debugger events). , runAdaptorWith + , runAdaptor ) where ---------------------------------------------------------------------------- -import Control.Concurrent.MVar ( modifyMVar_, MVar ) import Control.Concurrent.Lifted ( fork, killThread ) import Control.Exception ( throwIO ) import Control.Concurrent.STM ( atomically, readTVarIO, modifyTVar' ) -import Control.Monad ( when, unless ) +import Control.Monad ( when, unless, void ) import Control.Monad.Except ( runExceptT, throwError ) -import Control.Monad.State ( runStateT, gets, MonadIO(liftIO), gets, modify' ) +import Control.Monad.State ( runStateT, gets, gets, modify' ) +import Control.Monad.IO.Class ( liftIO ) +import Control.Monad.Reader ( asks, ask, runReaderT ) import Data.Aeson ( FromJSON, Result (..), fromJSON ) import Data.Aeson.Encode.Pretty ( encodePretty ) import Data.Aeson.Types ( object, Key, KeyValue((.=)), ToJSON ) +import Data.IORef ( readIORef, writeIORef ) import Data.Text ( unpack, pack ) import Network.Socket ( SockAddr ) import System.IO ( Handle ) @@ -73,18 +76,18 @@ import DAP.Types import DAP.Utils import DAP.Internal ---------------------------------------------------------------------------- -logWarn :: BL8.ByteString -> Adaptor app () +logWarn :: BL8.ByteString -> Adaptor app request () logWarn msg = logWithAddr WARN Nothing (withBraces msg) ---------------------------------------------------------------------------- -logError :: BL8.ByteString -> Adaptor app () +logError :: BL8.ByteString -> Adaptor app request () logError msg = logWithAddr ERROR Nothing (withBraces msg) ---------------------------------------------------------------------------- -logInfo :: BL8.ByteString -> Adaptor app () +logInfo :: BL8.ByteString -> Adaptor app request () logInfo msg = logWithAddr INFO Nothing (withBraces msg) ---------------------------------------------------------------------------- -- | Meant for internal consumption, used to signify a message has been -- SENT from the server -debugMessage :: BL8.ByteString -> Adaptor app () +debugMessage :: BL8.ByteString -> Adaptor app request () debugMessage msg = do shouldLog <- getDebugLogging addr <- getAddress @@ -93,7 +96,7 @@ debugMessage msg = do $ logger DEBUG addr (Just SENT) msg ---------------------------------------------------------------------------- -- | Meant for external consumption -logWithAddr :: Level -> Maybe DebugStatus -> BL8.ByteString -> Adaptor app () +logWithAddr :: Level -> Maybe DebugStatus -> BL8.ByteString -> Adaptor app request () logWithAddr level status msg = do addr <- getAddress liftIO (logger level addr status msg) @@ -113,24 +116,26 @@ logger level addr maybeDebug msg = do , msg ] ---------------------------------------------------------------------------- -getDebugLogging :: Adaptor app Bool -getDebugLogging = gets (debugLogging . serverConfig) +getDebugLogging :: Adaptor app request Bool +getDebugLogging = asks (debugLogging . serverConfig) ---------------------------------------------------------------------------- -getServerCapabilities :: Adaptor app Capabilities -getServerCapabilities = gets (serverCapabilities . serverConfig) +getServerCapabilities :: Adaptor app request Capabilities +getServerCapabilities = asks (serverCapabilities . serverConfig) ---------------------------------------------------------------------------- -getAddress :: Adaptor app SockAddr -getAddress = gets address +getAddress :: Adaptor app request SockAddr +getAddress = asks address ---------------------------------------------------------------------------- -getHandle :: Adaptor app Handle -getHandle = gets handle +getHandle :: Adaptor app request Handle +getHandle = asks handle ---------------------------------------------------------------------------- -getRequestSeqNum :: Adaptor app Seq -getRequestSeqNum = gets (requestSeqNum . request) +getRequestSeqNum :: Adaptor app Request Seq +getRequestSeqNum = asks (requestSeqNum . request) ---------------------------------------------------------------------------- -getDebugSessionId :: Adaptor app SessionId +getDebugSessionId :: Adaptor app request SessionId getDebugSessionId = do - gets sessionId >>= \case + var <- asks (sessionId) + res <- liftIO $ readIORef var + case res of Nothing -> sessionNotFound Just sessionId -> pure sessionId where @@ -138,13 +143,15 @@ getDebugSessionId = do let err = "No Debug Session has started" sendError (ErrorMessage (pack err)) Nothing ---------------------------------------------------------------------------- -setDebugSessionId :: SessionId -> Adaptor app () -setDebugSessionId session = modify' $ \s -> s { sessionId = Just session } +setDebugSessionId :: SessionId -> Adaptor app request () +setDebugSessionId session = do + var <- asks sessionId + liftIO $ writeIORef var (Just session) ---------------------------------------------------------------------------- registerNewDebugSession :: SessionId -> app - -> [((Adaptor app () -> IO ()) -> IO ())] + -> [(Adaptor app () () -> IO ()) -> IO ()] -- ^ Actions to run debugger (operates in a forked thread that gets killed when disconnect is set) -- Long running operation, meant to be used as a sink for -- the debugger to emit events and for the adaptor to forward to the editor @@ -161,29 +168,32 @@ registerNewDebugSession -- > withAdaptor $ sendOutputEvent defaultOutputEvent { outputEventOutput = output } -- > ] -- - -> Adaptor app () + -> Adaptor app request () registerNewDebugSession k v debuggerConcurrentActions = do - store <- gets appStore - adaptorStateMVar <- gets adaptorStateMVar + store <- asks appStore + lcl <- ask + let lcl' = lcl { request = () } + let emptyState = AdaptorState MessageTypeEvent [] debuggerThreadState <- liftIO $ DebuggerThreadState - <$> sequence [fork $ action (runAdaptorWith adaptorStateMVar) | action <- debuggerConcurrentActions] + <$> sequence [fork $ action (runAdaptorWith lcl' emptyState) | action <- debuggerConcurrentActions] liftIO . atomically $ modifyTVar' store (H.insert k (debuggerThreadState, v)) - setDebugSessionId k logInfo $ BL8.pack $ "Registered new debug session: " <> unpack k + setDebugSessionId k + ---------------------------------------------------------------------------- -updateDebugSession :: (app -> app) -> Adaptor app () +updateDebugSession :: (app -> app) -> Adaptor app request () updateDebugSession updateFun = do sessionId <- getDebugSessionId - store <- gets appStore + store <- asks appStore liftIO . atomically $ modifyTVar' store (H.adjust (fmap updateFun) sessionId) ---------------------------------------------------------------------------- -getDebugSession :: Adaptor a a +getDebugSession :: Adaptor a r a getDebugSession = do (_, _, app) <- getDebugSessionWithThreadIdAndSessionId pure app ---------------------------------------------------------------------------- -getDebugSessionWithThreadIdAndSessionId :: Adaptor app (SessionId, DebuggerThreadState, app) +getDebugSessionWithThreadIdAndSessionId :: Adaptor app request (SessionId, DebuggerThreadState, app) getDebugSessionWithThreadIdAndSessionId = do sessionId <- getDebugSessionId appStore <- liftIO . readTVarIO =<< getAppStore @@ -203,7 +213,7 @@ getDebugSessionWithThreadIdAndSessionId = do -- | Whenever a debug Session ends (cleanly or otherwise) this function -- will remove the local debugger communication state from the global state ---------------------------------------------------------------------------- -destroyDebugSession :: Adaptor app () +destroyDebugSession :: Adaptor app request () destroyDebugSession = do (sessionId, DebuggerThreadState {..}, _) <- getDebugSessionWithThreadIdAndSessionId store <- getAppStore @@ -212,17 +222,17 @@ destroyDebugSession = do atomically $ modifyTVar' store (H.delete sessionId) logInfo $ BL8.pack $ "SessionId " <> unpack sessionId <> " ended" ---------------------------------------------------------------------------- -getAppStore :: Adaptor app (AppStore app) -getAppStore = gets appStore +getAppStore :: Adaptor app request (AppStore app) +getAppStore = asks appStore ---------------------------------------------------------------------------- -getCommand :: Adaptor app Command -getCommand = command <$> gets request +getCommand :: Adaptor app Request Command +getCommand = command <$> asks request ---------------------------------------------------------------------------- -- | 'sendRaw' (internal use only) -- Sends a raw JSON payload to the editor. No "seq", "type" or "command" fields are set. -- The message is still encoded with the ProtocolMessage Header, byte count, and CRLF. -- -sendRaw :: ToJSON value => value -> Adaptor app () +sendRaw :: ToJSON value => value -> Adaptor app request () sendRaw value = do handle <- getHandle address <- getAddress @@ -234,7 +244,7 @@ sendRaw value = do -- i.e. "request_seq" and "command". -- We also have to be sure to reset the message payload ---------------------------------------------------------------------------- -send :: Adaptor app () -> Adaptor app () +send :: Adaptor app Request () -> Adaptor app Request () send action = do () <- action cmd <- getCommand @@ -250,16 +260,39 @@ send action = do -- "seq" and "type" must be set for all protocol messages setField "type" messageType - unless (messageType == MessageTypeEvent) $ - setField "seq" seqNum + unless (messageType == MessageTypeEvent) (setField "seq" seqNum) -- Once all fields are set, fetch the payload for sending payload <- object <$> gets payload -- Send payload to client from debug adaptor writeToHandle address handle payload + resetAdaptorStatePayload +---------------------------------------------------------------------------- +-- | Write event to Handle +sendEvent + :: Adaptor app request () + -> Adaptor app request () +sendEvent action = do + () <- action + handle <- getHandle + messageType <- gets messageType + address <- getAddress + let errorMsg = + "Use 'send' function when responding to a DAP request, 'sendEvent'\ + \ is for responding to events" + case messageType of + MessageTypeResponse -> + sendError (ErrorMessage errorMsg) Nothing + MessageTypeRequest -> + sendError (ErrorMessage errorMsg) Nothing + MessageTypeEvent -> + setField "type" messageType - -- Reset payload each time a send occurs + -- Once all fields are set, fetch the payload for sending + payload <- object <$> gets payload + -- Send payload to client from debug adaptor + writeToHandle address handle payload resetAdaptorStatePayload ---------------------------------------------------------------------------- -- | Writes payload to the given 'Handle' using the local connection lock @@ -269,7 +302,7 @@ writeToHandle => SockAddr -> Handle -> event - -> Adaptor app () + -> Adaptor app request () writeToHandle _ handle evt = do let msg = encodeBaseProtocolMessage evt debugMessage ("\n" <> encodePretty evt) @@ -277,23 +310,26 @@ writeToHandle _ handle evt = do ---------------------------------------------------------------------------- -- | Resets Adaptor's payload ---------------------------------------------------------------------------- -resetAdaptorStatePayload :: Adaptor app () +resetAdaptorStatePayload :: Adaptor app request () resetAdaptorStatePayload = modify' $ \s -> s { payload = [] } ---------------------------------------------------------------------------- -sendSuccesfulResponse :: Adaptor app () -> Adaptor app () +sendSuccesfulResponse :: Adaptor app Request () -> Adaptor app Request () sendSuccesfulResponse action = do send $ do setType MessageTypeResponse setSuccess True action ---------------------------------------------------------------------------- -sendSuccesfulEmptyResponse :: Adaptor app () +sendSuccesfulEmptyResponse :: Adaptor app Request () sendSuccesfulEmptyResponse = sendSuccesfulResponse (pure ()) ---------------------------------------------------------------------------- -- | Sends successful event -sendSuccesfulEvent :: EventType -> Adaptor app () -> Adaptor app () +sendSuccesfulEvent + :: EventType + -> Adaptor app request () + -> Adaptor app request () sendSuccesfulEvent event action = do - send $ do + sendEvent $ do setEvent event setType MessageTypeEvent action @@ -305,7 +341,7 @@ sendSuccesfulEvent event action = do sendError :: ErrorMessage -> Maybe Message - -> Adaptor app a + -> Adaptor app request a sendError errorMessage maybeMessage = do throwError (errorMessage, maybeMessage) ---------------------------------------------------------------------------- @@ -314,7 +350,7 @@ sendError errorMessage maybeMessage = do sendErrorResponse :: ErrorMessage -> Maybe Message - -> Adaptor app () + -> Adaptor app Request () sendErrorResponse errorMessage maybeMessage = do send $ do setType MessageTypeResponse @@ -324,24 +360,24 @@ sendErrorResponse errorMessage maybeMessage = do ---------------------------------------------------------------------------- setErrorMessage :: ErrorMessage - -> Adaptor app () + -> Adaptor app request () setErrorMessage v = setField "message" v ---------------------------------------------------------------------------- -- | Sends successful event setSuccess :: Bool - -> Adaptor app () + -> Adaptor app request () setSuccess = setField "success" ---------------------------------------------------------------------------- setBody :: ToJSON value => value - -> Adaptor app () + -> Adaptor app request () setBody value = setField "body" value ---------------------------------------------------------------------------- setType :: MessageType - -> Adaptor app () + -> Adaptor app request () setType messageType = do modify' $ \adaptorState -> adaptorState @@ -350,14 +386,14 @@ setType messageType = do ---------------------------------------------------------------------------- setEvent :: EventType - -> Adaptor app () + -> Adaptor app request () setEvent = setField "event" ---------------------------------------------------------------------------- setField :: ToJSON value => Key -> value - -> Adaptor app () + -> Adaptor app request () setField key value = do currentPayload <- gets payload modify' $ \adaptorState -> @@ -367,18 +403,18 @@ setField key value = do ---------------------------------------------------------------------------- withConnectionLock :: IO () - -> Adaptor app () + -> Adaptor app request () withConnectionLock action = do - lock <- gets handleLock + lock <- asks handleLock liftIO (withLock lock action) ---------------------------------------------------------------------------- -- | Attempt to parse arguments from the Request ---------------------------------------------------------------------------- getArguments :: (Show value, FromJSON value) - => Adaptor app value + => Adaptor app Request value getArguments = do - maybeArgs <- gets (args . request) + maybeArgs <- asks (args . request) let msg = "No args found for this message" case maybeArgs of Nothing -> do @@ -390,18 +426,21 @@ getArguments = do x -> do logError (BL8.pack (show x)) liftIO $ throwIO (ParseException (show x)) - ---------------------------------------------------------------------------- -- | Evaluates Adaptor action by using and updating the state in the MVar -runAdaptorWith :: MVar (AdaptorState app) -> Adaptor app () -> IO () -runAdaptorWith adaptorStateMVar action = do - modifyMVar_ adaptorStateMVar (flip runAdaptor (resetAdaptorStatePayload >> action)) - +runAdaptorWith + :: AdaptorLocal app request + -> AdaptorState + -> Adaptor app request () + -> IO () +runAdaptorWith lcl st (Adaptor action) = + void (runStateT (runReaderT (runExceptT action) lcl) st) ---------------------------------------------------------------------------- -- | Utility for evaluating a monad transformer stack -runAdaptor :: AdaptorState app -> Adaptor app () -> IO (AdaptorState app) -runAdaptor adaptorState (Adaptor client) = - runStateT (runExceptT client) adaptorState >>= \case - (Left (errorMessage, maybeMessage), nextState) -> - runAdaptor nextState (sendErrorResponse errorMessage maybeMessage) - (Right (), nextState) -> pure nextState +runAdaptor :: AdaptorLocal app Request -> AdaptorState -> Adaptor app Request () -> IO () +runAdaptor lcl s (Adaptor client) = + runStateT (runReaderT (runExceptT client) lcl) s >>= \case + (Left (errorMessage, maybeMessage), s') -> + runAdaptor lcl s' (sendErrorResponse errorMessage maybeMessage) + (Right (), _) -> pure () +---------------------------------------------------------------------------- diff --git a/src/DAP/Event.hs b/src/DAP/Event.hs index 5ea5a64..f2439b0 100644 --- a/src/DAP/Event.hs +++ b/src/DAP/Event.hs @@ -50,13 +50,13 @@ module DAP.Event import DAP.Types import DAP.Adaptor ---------------------------------------------------------------------------- -sendBreakpointEvent :: BreakpointEvent -> Adaptor app () +sendBreakpointEvent :: BreakpointEvent -> Adaptor app Request () sendBreakpointEvent = sendSuccesfulEvent EventTypeBreakpoint . setBody ---------------------------------------------------------------------------- -sendCapabilitiesEvent :: CapabilitiesEvent -> Adaptor app () +sendCapabilitiesEvent :: CapabilitiesEvent -> Adaptor app Request () sendCapabilitiesEvent = sendSuccesfulEvent EventTypeCapabilities . setBody ---------------------------------------------------------------------------- -sendContinuedEvent :: ContinuedEvent -> Adaptor app () +sendContinuedEvent :: ContinuedEvent -> Adaptor app Request () sendContinuedEvent = sendSuccesfulEvent EventTypeContinued . setBody ---------------------------------------------------------------------------- defaultContinuedEvent :: ContinuedEvent @@ -66,7 +66,7 @@ defaultContinuedEvent , continuedEventAllThreadsContinued = False } ---------------------------------------------------------------------------- -sendExitedEvent :: ExitedEvent -> Adaptor app () +sendExitedEvent :: ExitedEvent -> Adaptor app Request () sendExitedEvent = sendSuccesfulEvent EventTypeExited . setBody ---------------------------------------------------------------------------- defaultExitedEvent :: ExitedEvent @@ -75,10 +75,10 @@ defaultExitedEvent { exitedEventExitCode = 0 } ---------------------------------------------------------------------------- -sendInitializedEvent :: Adaptor app () +sendInitializedEvent :: Adaptor app Request () sendInitializedEvent = sendSuccesfulEvent EventTypeInitialized (pure ()) ---------------------------------------------------------------------------- -sendInvalidatedEvent :: InvalidatedEvent -> Adaptor app () +sendInvalidatedEvent :: InvalidatedEvent -> Adaptor app Request () sendInvalidatedEvent = sendSuccesfulEvent EventTypeInvalidated . setBody ---------------------------------------------------------------------------- defaultInvalidatedEvent :: InvalidatedEvent @@ -90,10 +90,10 @@ defaultInvalidatedEvent } ---------------------------------------------------------------------------- -sendLoadedSourceEvent :: LoadedSourceEvent -> Adaptor app () +sendLoadedSourceEvent :: LoadedSourceEvent -> Adaptor app Request () sendLoadedSourceEvent = sendSuccesfulEvent EventTypeLoadedSource . setBody ---------------------------------------------------------------------------- -sendMemoryEvent :: MemoryEvent -> Adaptor app () +sendMemoryEvent :: MemoryEvent -> Adaptor app Request () sendMemoryEvent = sendSuccesfulEvent EventTypeMemory . setBody ---------------------------------------------------------------------------- defaultMemoryEvent :: MemoryEvent @@ -104,10 +104,10 @@ defaultMemoryEvent , memoryEventCount = 0 } ---------------------------------------------------------------------------- -sendModuleEvent :: ModuleEvent -> Adaptor app () +sendModuleEvent :: ModuleEvent -> Adaptor app Request () sendModuleEvent = sendSuccesfulEvent EventTypeModule . setBody ---------------------------------------------------------------------------- -sendOutputEvent :: OutputEvent -> Adaptor app () +sendOutputEvent :: OutputEvent -> Adaptor app request () sendOutputEvent = sendSuccesfulEvent EventTypeOutput . setBody ---------------------------------------------------------------------------- defaultOutputEvent :: OutputEvent @@ -123,7 +123,7 @@ defaultOutputEvent , outputEventData = Nothing } ---------------------------------------------------------------------------- -sendProcessEvent :: ProcessEvent -> Adaptor app () +sendProcessEvent :: ProcessEvent -> Adaptor app Request () sendProcessEvent = sendSuccesfulEvent EventTypeProcess . setBody ---------------------------------------------------------------------------- defaultProcessEvent :: ProcessEvent @@ -136,7 +136,7 @@ defaultProcessEvent , processEventPointerSize = Nothing } ---------------------------------------------------------------------------- -sendProgressEndEvent :: ProgressEndEvent -> Adaptor app () +sendProgressEndEvent :: ProgressEndEvent -> Adaptor app Request () sendProgressEndEvent = sendSuccesfulEvent EventTypeProgressEnd . setBody ---------------------------------------------------------------------------- defaultProgressEndEvent :: ProgressEndEvent @@ -146,7 +146,7 @@ defaultProgressEndEvent , progressEndEventMessage = Nothing } ---------------------------------------------------------------------------- -sendProgressStartEvent :: ProgressStartEvent -> Adaptor app () +sendProgressStartEvent :: ProgressStartEvent -> Adaptor app Request () sendProgressStartEvent = sendSuccesfulEvent EventTypeProgressStart . setBody ---------------------------------------------------------------------------- defaultProgressStartEvent :: ProgressStartEvent @@ -160,7 +160,7 @@ defaultProgressStartEvent , progressStartEventPercentage = Nothing } ---------------------------------------------------------------------------- -sendProgressUpdateEvent :: ProgressUpdateEvent -> Adaptor app () +sendProgressUpdateEvent :: ProgressUpdateEvent -> Adaptor app Request () sendProgressUpdateEvent = sendSuccesfulEvent EventTypeProgressUpdate . setBody ---------------------------------------------------------------------------- defaultProgressUpdateEvent :: ProgressUpdateEvent @@ -171,7 +171,7 @@ defaultProgressUpdateEvent , progressUpdateEventPercentage = Nothing } ---------------------------------------------------------------------------- -sendStoppedEvent :: StoppedEvent -> Adaptor app () +sendStoppedEvent :: StoppedEvent -> Adaptor app Request () sendStoppedEvent = sendSuccesfulEvent EventTypeStopped . setBody ---------------------------------------------------------------------------- defaultStoppedEvent :: StoppedEvent @@ -186,7 +186,7 @@ defaultStoppedEvent , stoppedEventHitBreakpointIds = [] } ---------------------------------------------------------------------------- -sendTerminatedEvent :: TerminatedEvent -> Adaptor app () +sendTerminatedEvent :: TerminatedEvent -> Adaptor app Request () sendTerminatedEvent = sendSuccesfulEvent EventTypeTerminated . setBody ---------------------------------------------------------------------------- defaultTerminatedEvent :: TerminatedEvent @@ -195,7 +195,7 @@ defaultTerminatedEvent { terminatedEventRestart = False } ---------------------------------------------------------------------------- -sendThreadEvent :: ThreadEvent -> Adaptor app () +sendThreadEvent :: ThreadEvent -> Adaptor app Request () sendThreadEvent = sendSuccesfulEvent EventTypeThread . setBody ---------------------------------------------------------------------------- defaultThreadEvent :: ThreadEvent diff --git a/src/DAP/Response.hs b/src/DAP/Response.hs index 271a4b0..8aa9794 100644 --- a/src/DAP/Response.hs +++ b/src/DAP/Response.hs @@ -64,13 +64,13 @@ import DAP.Adaptor import DAP.Types ---------------------------------------------------------------------------- -- | AttachResponse has no body by default -sendAttachResponse :: Adaptor app () +sendAttachResponse :: Adaptor app Request () sendAttachResponse = sendSuccesfulEmptyResponse ---------------------------------------------------------------------------- -- | BreakpointLocationResponse has no body by default sendBreakpointLocationsResponse :: [BreakpointLocation] - -> Adaptor app () + -> Adaptor app Request () sendBreakpointLocationsResponse = sendSuccesfulResponse . setBody @@ -79,7 +79,7 @@ sendBreakpointLocationsResponse -- | 'SetDataBreakpointsResponse' sendSetDataBreakpointsResponse :: [Breakpoint] - -> Adaptor app () + -> Adaptor app Request () sendSetDataBreakpointsResponse = sendSuccesfulResponse . setBody @@ -88,7 +88,7 @@ sendSetDataBreakpointsResponse -- | BreakpointResponse has no body by default sendSetBreakpointsResponse :: [Breakpoint] - -> Adaptor app () + -> Adaptor app Request () sendSetBreakpointsResponse = sendSuccesfulResponse . setBody @@ -97,7 +97,7 @@ sendSetBreakpointsResponse -- | SetInstructionsBreakpointResponse has no body by default sendSetInstructionBreakpointsResponse :: [Breakpoint] - -> Adaptor app () + -> Adaptor app Request () sendSetInstructionBreakpointsResponse = sendSuccesfulResponse . setBody @@ -106,7 +106,7 @@ sendSetInstructionBreakpointsResponse -- | SetFunctionBreakpointResponse has no body by default sendSetFunctionBreakpointsResponse :: [Breakpoint] - -> Adaptor app () + -> Adaptor app Request () sendSetFunctionBreakpointsResponse = sendSuccesfulResponse . setBody @@ -115,7 +115,7 @@ sendSetFunctionBreakpointsResponse -- | SetExceptionBreakpointsResponse has no body by default sendSetExceptionBreakpointsResponse :: [Breakpoint] - -> Adaptor app () + -> Adaptor app Request () sendSetExceptionBreakpointsResponse = sendSuccesfulResponse . setBody @@ -124,147 +124,147 @@ sendSetExceptionBreakpointsResponse -- | ContinueResponse sendContinueResponse :: ContinueResponse - -> Adaptor app () + -> Adaptor app Request () sendContinueResponse continueResponse = do sendSuccesfulResponse (setBody continueResponse) ---------------------------------------------------------------------------- -- | ConfigurationDoneResponse sendConfigurationDoneResponse - :: Adaptor app () + :: Adaptor app Request () sendConfigurationDoneResponse = do sendSuccesfulEmptyResponse ---------------------------------------------------------------------------- -- | LaunchResponse sendLaunchResponse - :: Adaptor app () + :: Adaptor app Request () sendLaunchResponse = sendSuccesfulEmptyResponse ---------------------------------------------------------------------------- -- | RestartResponse sendRestartResponse - :: Adaptor app () + :: Adaptor app Request () sendRestartResponse = sendSuccesfulEmptyResponse ---------------------------------------------------------------------------- -- | DisconnectResponse sendDisconnectResponse - :: Adaptor app () + :: Adaptor app Request () sendDisconnectResponse = sendSuccesfulEmptyResponse ---------------------------------------------------------------------------- -- | TerminateResponse sendTerminateResponse - :: Adaptor app () + :: Adaptor app Request () sendTerminateResponse = sendSuccesfulEmptyResponse ---------------------------------------------------------------------------- -- | NextResponse sendNextResponse - :: Adaptor app () + :: Adaptor app Request () sendNextResponse = sendSuccesfulEmptyResponse ---------------------------------------------------------------------------- -- | StepInResponse sendStepInResponse - :: Adaptor app () + :: Adaptor app Request () sendStepInResponse = sendSuccesfulEmptyResponse ---------------------------------------------------------------------------- -- | StepOutResponse sendStepOutResponse - :: Adaptor app () + :: Adaptor app Request () sendStepOutResponse = sendSuccesfulEmptyResponse ---------------------------------------------------------------------------- -- | StepBackResponse sendStepBackResponse - :: Adaptor app () + :: Adaptor app Request () sendStepBackResponse = sendSuccesfulEmptyResponse ---------------------------------------------------------------------------- -- | ReverseContinueResponse sendReverseContinueResponse - :: Adaptor app () + :: Adaptor app Request () sendReverseContinueResponse = sendSuccesfulEmptyResponse ---------------------------------------------------------------------------- -- | RestartFrameResponse sendRestartFrameResponse - :: Adaptor app () + :: Adaptor app Request () sendRestartFrameResponse = sendSuccesfulEmptyResponse ---------------------------------------------------------------------------- -- | InitializeReponse sendInitializeResponse - :: Adaptor app () + :: Adaptor app Request () sendInitializeResponse = do capabilities <- getServerCapabilities sendSuccesfulResponse (setBody capabilities) ---------------------------------------------------------------------------- -- | GotoResponse sendGotoResponse - :: Adaptor app () + :: Adaptor app Request () sendGotoResponse = sendSuccesfulEmptyResponse ---------------------------------------------------------------------------- -- | GotoTargetsResponse sendGotoTargetsResponse - :: Adaptor app () + :: Adaptor app Request () sendGotoTargetsResponse = sendSuccesfulEmptyResponse ---------------------------------------------------------------------------- -- | PauseResponse sendPauseResponse - :: Adaptor app () + :: Adaptor app Request () sendPauseResponse = sendSuccesfulEmptyResponse ---------------------------------------------------------------------------- -- | TerminateThreadsResponse sendTerminateThreadsResponse - :: Adaptor app () + :: Adaptor app Request () sendTerminateThreadsResponse = sendSuccesfulEmptyResponse ---------------------------------------------------------------------------- -sendModulesResponse :: ModulesResponse -> Adaptor app () +sendModulesResponse :: ModulesResponse -> Adaptor app Request () sendModulesResponse = sendSuccesfulResponse . setBody ---------------------------------------------------------------------------- -sendStackTraceResponse :: StackTraceResponse -> Adaptor app () +sendStackTraceResponse :: StackTraceResponse -> Adaptor app Request () sendStackTraceResponse = sendSuccesfulResponse . setBody ---------------------------------------------------------------------------- -sendSourceResponse :: SourceResponse -> Adaptor app () +sendSourceResponse :: SourceResponse -> Adaptor app Request () sendSourceResponse = sendSuccesfulResponse . setBody ---------------------------------------------------------------------------- -sendThreadsResponse :: [Thread] -> Adaptor app () +sendThreadsResponse :: [Thread] -> Adaptor app Request () sendThreadsResponse = sendSuccesfulResponse . setBody . ThreadsResponse ---------------------------------------------------------------------------- -sendLoadedSourcesResponse :: [Source] -> Adaptor app () +sendLoadedSourcesResponse :: [Source] -> Adaptor app Request () sendLoadedSourcesResponse = sendSuccesfulResponse . setBody . LoadedSourcesResponse ---------------------------------------------------------------------------- -sendWriteMemoryResponse :: WriteMemoryResponse -> Adaptor app () +sendWriteMemoryResponse :: WriteMemoryResponse -> Adaptor app Request () sendWriteMemoryResponse = sendSuccesfulResponse . setBody ---------------------------------------------------------------------------- -sendReadMemoryResponse :: ReadMemoryResponse -> Adaptor app () +sendReadMemoryResponse :: ReadMemoryResponse -> Adaptor app Request () sendReadMemoryResponse = sendSuccesfulResponse . setBody ---------------------------------------------------------------------------- -sendCompletionsResponse :: CompletionsResponse -> Adaptor app () +sendCompletionsResponse :: CompletionsResponse -> Adaptor app Request () sendCompletionsResponse = sendSuccesfulResponse . setBody ---------------------------------------------------------------------------- -sendDataBreakpointInfoResponse :: DataBreakpointInfoResponse -> Adaptor app () +sendDataBreakpointInfoResponse :: DataBreakpointInfoResponse -> Adaptor app Request () sendDataBreakpointInfoResponse = sendSuccesfulResponse . setBody ---------------------------------------------------------------------------- -sendDisassembleResponse :: DisassembleResponse -> Adaptor app () +sendDisassembleResponse :: DisassembleResponse -> Adaptor app Request () sendDisassembleResponse = sendSuccesfulResponse . setBody ---------------------------------------------------------------------------- -sendEvaluateResponse :: EvaluateResponse -> Adaptor app () +sendEvaluateResponse :: EvaluateResponse -> Adaptor app Request () sendEvaluateResponse = sendSuccesfulResponse . setBody ---------------------------------------------------------------------------- -sendExceptionInfoResponse :: ExceptionInfoResponse -> Adaptor app () +sendExceptionInfoResponse :: ExceptionInfoResponse -> Adaptor app Request () sendExceptionInfoResponse = sendSuccesfulResponse . setBody ---------------------------------------------------------------------------- -sendScopesResponse :: ScopesResponse -> Adaptor app () +sendScopesResponse :: ScopesResponse -> Adaptor app Request () sendScopesResponse = sendSuccesfulResponse . setBody ---------------------------------------------------------------------------- -sendSetExpressionResponse :: SetExpressionResponse -> Adaptor app () +sendSetExpressionResponse :: SetExpressionResponse -> Adaptor app Request () sendSetExpressionResponse = sendSuccesfulResponse . setBody ---------------------------------------------------------------------------- -sendSetVariableResponse :: SetVariableResponse -> Adaptor app () +sendSetVariableResponse :: SetVariableResponse -> Adaptor app Request () sendSetVariableResponse = sendSuccesfulResponse . setBody ---------------------------------------------------------------------------- -sendStepInTargetsResponse :: StepInTargetsResponse -> Adaptor app () +sendStepInTargetsResponse :: StepInTargetsResponse -> Adaptor app Request () sendStepInTargetsResponse = sendSuccesfulResponse . setBody ---------------------------------------------------------------------------- -sendVariablesResponse :: VariablesResponse -> Adaptor app () +sendVariablesResponse :: VariablesResponse -> Adaptor app Request () sendVariablesResponse = sendSuccesfulResponse . setBody ---------------------------------------------------------------------------- -sendRunInTerminalResponse :: RunInTerminalResponse -> Adaptor app () +sendRunInTerminalResponse :: RunInTerminalResponse -> Adaptor app Request () sendRunInTerminalResponse = sendSuccesfulResponse . setBody ---------------------------------------------------------------------------- -sendStartDebuggingResponse :: Adaptor app () +sendStartDebuggingResponse :: Adaptor app Request () sendStartDebuggingResponse = sendSuccesfulEmptyResponse ---------------------------------------------------------------------------- diff --git a/src/DAP/Server.hs b/src/DAP/Server.hs index 4288479..be2d659 100644 --- a/src/DAP/Server.hs +++ b/src/DAP/Server.hs @@ -21,10 +21,8 @@ module DAP.Server , readPayload ) where ---------------------------------------------------------------------------- -import Control.Concurrent.MVar ( MVar ) import Control.Monad ( when ) -import Control.Concurrent.MVar ( newMVar, newEmptyMVar, modifyMVar_ - , putMVar, readMVar ) +import Control.Concurrent.MVar ( newMVar ) import Control.Concurrent.STM ( newTVarIO ) import Control.Exception ( SomeException , IOException @@ -32,11 +30,11 @@ import Control.Exception ( SomeException , fromException , throwIO ) import Control.Monad ( void ) -import Control.Monad.State ( gets ) import Data.Aeson ( decodeStrict, eitherDecode, Value, FromJSON ) import Data.Aeson.Encode.Pretty ( encodePretty ) import Data.ByteString ( ByteString ) import Data.Char ( isDigit ) +import Data.IORef ( newIORef ) import Network.Simple.TCP ( serve, HostPreference(Host) ) import Network.Socket ( socketToHandle, withSocketsDo, SockAddr ) import System.IO ( hClose, hSetNewlineMode, Handle, Newline(CRLF) @@ -55,7 +53,7 @@ import DAP.Adaptor runDAPServer :: ServerConfig -- ^ Top-level Server configuration, global across all debug sessions - -> (Command -> Adaptor app ()) + -> (Command -> Adaptor app Request ()) -- ^ A function to facilitate communication between DAP clients, debug adaptors and debuggers -> IO () runDAPServer serverConfig@ServerConfig {..} communicate = withSocketsDo $ do @@ -67,8 +65,7 @@ runDAPServer serverConfig@ServerConfig {..} communicate = withSocketsDo $ do putStrLn $ "TCP connection established from " ++ show address handle <- socketToHandle socket ReadWriteMode hSetNewlineMode handle NewlineMode { inputNL = CRLF, outputNL = CRLF } - request <- getRequest handle address serverConfig - adaptorStateMVar <- initAdaptorState handle address appStore serverConfig request + adaptorStateMVar <- initAdaptorState handle address appStore serverConfig serviceClient communicate adaptorStateMVar `catch` exceptionHandler handle address debugLogging -- | Initializes the Adaptor @@ -78,40 +75,30 @@ initAdaptorState -> SockAddr -> AppStore app -> ServerConfig - -> Request - -> IO (MVar (AdaptorState app)) -initAdaptorState handle address appStore serverConfig request = do + -> IO (AdaptorLocal app ()) +initAdaptorState handle address appStore serverConfig = do handleLock <- newMVar () - sessionId <- pure Nothing - adaptorStateMVar <- newEmptyMVar - putMVar adaptorStateMVar AdaptorState - { messageType = MessageTypeResponse - , payload = [] - , .. + sessionId <- newIORef Nothing + let request = () + pure AdaptorLocal + { .. } - pure adaptorStateMVar ---------------------------------------------------------------------------- -- | Communication loop between editor and adaptor -- Evaluates the current 'Request' located in the 'AdaptorState' -- Fetches, updates and recurses on the next 'Request' -- serviceClient - :: (Command -> Adaptor app ()) - -> MVar (AdaptorState app) + :: (Command -> Adaptor app Request ()) + -> AdaptorLocal app r -> IO () -serviceClient communicate adaptorStateMVar = do - runAdaptorWith adaptorStateMVar $ do - request <- gets request - communicate (command request) - - -- HINT: getRequest is a blocking action so we use readMVar to leave MVar available - AdaptorState { address, handle, serverConfig } <- readMVar adaptorStateMVar +serviceClient communicate lcl = do + let AdaptorLocal { address, handle, serverConfig } = lcl nextRequest <- getRequest handle address serverConfig - modifyMVar_ adaptorStateMVar $ \s -> pure s { request = nextRequest } - - -- loop: serve the next request - serviceClient communicate adaptorStateMVar - + let st = AdaptorState MessageTypeResponse [] + let lcl' = lcl { request = nextRequest } + runAdaptorWith lcl' st (communicate (command nextRequest)) + serviceClient communicate lcl ---------------------------------------------------------------------------- -- | Handle exceptions from client threads, parse and log accordingly exceptionHandler :: Handle -> SockAddr -> Bool -> SomeException -> IO () @@ -169,7 +156,7 @@ parseHeader bytes = do pure (Right contentLength) Nothing -> pure $ Left ("Invalid payload: " <> BS.unpack bytes) - +---------------------------------------------------------------------------- -- | Helper function to parse a 'ProtocolMessage', extracting it's body. -- used for testing. -- @@ -182,3 +169,4 @@ readPayload handle = do Right count -> do body <- BS.hGet handle count pure $ eitherDecode (BL8.fromStrict body) +---------------------------------------------------------------------------- diff --git a/src/DAP/Types.hs b/src/DAP/Types.hs index 7ecd755..e18bb60 100644 --- a/src/DAP/Types.hs +++ b/src/DAP/Types.hs @@ -97,6 +97,7 @@ module DAP.Types -- * Client , Adaptor (..) , AdaptorState (..) + , AdaptorLocal(..) , AppStore -- * Errors , AdaptorException (..) @@ -202,16 +203,20 @@ module DAP.Types , DebuggerThreadState (..) ) where ---------------------------------------------------------------------------- +import Control.Applicative ( (<|>) ) import Control.Monad.Base ( MonadBase ) import Control.Monad.Except ( MonadError, ExceptT ) import Control.Monad.Trans.Control ( MonadBaseControl ) import Control.Concurrent ( ThreadId ) import Control.Concurrent.MVar ( MVar ) -import Control.Applicative ( (<|>) ) +import Control.Monad.IO.Class ( MonadIO ) +import Control.Monad.Reader ( MonadReader, ReaderT ) +import Control.Monad.State ( MonadState, StateT ) +import Data.IORef ( IORef ) import Data.Typeable ( typeRep ) import Control.Concurrent.STM ( TVar ) import Control.Exception ( Exception ) -import Control.Monad.State ( StateT, MonadState, MonadIO ) +import Control.Monad.Reader ( ) import Data.Aeson ( (.:), (.:?), withObject, withText, object , FromJSON(parseJSON), Value, KeyValue((.=)) , ToJSON(toJSON), genericParseJSON, defaultOptions @@ -235,18 +240,19 @@ import DAP.Utils ( capitalize, getName, genericP -- the current event / response being constructed and the type of the message. -- Of note: A 'StateT' is used because 'adaptorPayload' should not be shared -- with other threads. -newtype Adaptor store a = - Adaptor (ExceptT (ErrorMessage, Maybe Message) (StateT (AdaptorState store) IO) a) +newtype Adaptor store r a = + Adaptor (ExceptT (ErrorMessage, Maybe Message) (ReaderT (AdaptorLocal store r) (StateT AdaptorState IO)) a) deriving newtype ( Monad - , MonadIO, Applicative, Functor, MonadState (AdaptorState store) + , MonadIO, Applicative, Functor, MonadReader (AdaptorLocal store r) + , MonadState AdaptorState , MonadBaseControl IO , MonadError (ErrorMessage, Maybe Message) , MonadBase IO ) ---------------------------------------------------------------------------- -- | The adaptor state is local to a single connection / thread -data AdaptorState app +data AdaptorState = AdaptorState { messageType :: MessageType -- ^ Current message type being created @@ -258,7 +264,12 @@ data AdaptorState app -- This should never be manually modified by the end user -- The payload is accumulated automatically by usage of the API -- - , appStore :: AppStore app + -- + } +---------------------------------------------------------------------------- +-- | The adaptor local config +data AdaptorLocal app request = AdaptorLocal + { appStore :: AppStore app -- ^ Global app store, accessible on a per session basis -- Initialized during 'attach' sessions -- @@ -269,23 +280,20 @@ data AdaptorState app , handle :: Handle -- ^ Connection Handle -- - , request :: Request - -- ^ Connection Request information -- , address :: SockAddr -- ^ Address of Connection -- - , sessionId :: Maybe SessionId + , sessionId :: IORef (Maybe SessionId) -- ^ Session ID -- Local to the current connection's debugger session -- - , adaptorStateMVar :: MVar (AdaptorState app) - -- ^ Shared state for serializable concurrency - -- , handleLock :: MVar () -- ^ A lock for writing to a Handle. One lock is created per connection -- and exists for the duration of that connection -- + , request :: request + -- ^ Connection Request information, if we are responding to a request. } ---------------------------------------------------------------------------- @@ -296,7 +304,7 @@ type SessionId = Text -- allows initalized debuggers to emit custom events -- when they receive messages from the debugger type AppStore app = TVar (H.HashMap SessionId (DebuggerThreadState, app)) - +---------------------------------------------------------------------------- -- | 'DebuggerThreadState' -- State to hold both the thread that executes the debugger and the thread used -- to propagate output events from the debugger + debuggee to the editor (via the @@ -305,7 +313,6 @@ data DebuggerThreadState = DebuggerThreadState { debuggerThreads :: [ThreadId] } - ---------------------------------------------------------------------------- data ServerConfig = ServerConfig @@ -4027,3 +4034,4 @@ data Level = DEBUG | INFO | WARN | ERROR ---------------------------------------------------------------------------- data DebugStatus = SENT | RECEIVED deriving (Show, Eq) +---------------------------------------------------------------------------- diff --git a/test/Main.hs b/test/Main.hs index a50e272..9fdc467 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -106,7 +106,7 @@ main = withServer $ -- mockServerTalk :: Command - -> Adaptor app () + -> Adaptor app Request () mockServerTalk CommandInitialize = do sendInitializeResponse sendInitializedEvent