ghc-debugger-0.1.0.0: ghc-debug-adapter/Development/Debug/Adapter/Breakpoints.hs
{-# LANGUAGE OverloadedStrings, OverloadedRecordDot, RecordWildCards, PatternSynonyms #-}
module Development.Debug.Adapter.Breakpoints where
import qualified Data.Text as T
import qualified Data.Map as Map
import qualified Data.IntSet as IS
import Control.Monad
import Data.Maybe
import qualified GHC
import DAP
import GHC.Debugger.Interface.Messages hiding (Command, Response)
import Development.Debug.Adapter
import Development.Debug.Adapter.Interface
-- | BreakpointLocations command
commandBreakpointLocations :: DebugAdaptor ()
commandBreakpointLocations = do
BreakpointLocationsArguments{..} <- getArguments
file <- fileFromSourcePath breakpointLocationsArgumentsSource
DidGetBreakpoints mspan <-
sendSync (GetBreakpointsAt (ModuleBreak file breakpointLocationsArgumentsLine breakpointLocationsArgumentsColumn))
let locs = case mspan of
Nothing -> []
Just SourceSpan {..} ->
[ BreakpointLocation
{ breakpointLocationLine = startLine
, breakpointLocationColumn = Just startCol
, breakpointLocationEndLine = Just endLine
, breakpointLocationEndColumn = Just endCol
}
]
sendBreakpointLocationsResponse locs
-- | Execute adaptor command set module breakpoints
commandSetBreakpoints :: DebugAdaptor ()
commandSetBreakpoints = do
SetBreakpointsArguments {..} <- getArguments
file <- fileFromSourcePath setBreakpointsArgumentsSource
let breaks_wanted = fromMaybe [] setBreakpointsArgumentsBreakpoints
-- Clear existing module breakpoints
DidClearBreakpoints <- sendSync (ClearModBreakpoints file)
-- Set requested ones
breaks <- forM breaks_wanted $ \bp -> do
DidSetBreakpoint bf <-
sendSync (SetBreakpoint (ModuleBreak file (DAP.sourceBreakpointLine bp) (DAP.sourceBreakpointColumn bp)))
registerBreakFound bf
sendSetBreakpointsResponse (concat breaks)
-- | Execute adaptor command set function breakpoints
commandSetFunctionBreakpoints :: DebugAdaptor ()
commandSetFunctionBreakpoints = do
SetFunctionBreakpointsArguments{..} <- getArguments
let
breaks_wanted = mapMaybe functionBreakpointName setFunctionBreakpointsArgumentsBreakpoints
-- Clear existing function breakpoints
DidClearBreakpoints <- sendSync ClearFunctionBreakpoints
-- Set requested ones
breaks <- forM breaks_wanted $ \bp -> do
DidSetBreakpoint bf <-
sendSync (SetBreakpoint (FunctionBreak (T.unpack bp)))
registerBreakFound bf
sendSetFunctionBreakpointsResponse (concat breaks)
-- | Execute adaptor command set exception breakpoints
commandSetExceptionBreakpoints :: DebugAdaptor ()
commandSetExceptionBreakpoints = do
SetExceptionBreakpointsArguments{..} <- getArguments
-- Clear old exception breakpoints
DidRemoveBreakpoint _ <- sendSync (DelBreakpoint OnExceptionsBreak)
DidRemoveBreakpoint _ <- sendSync (DelBreakpoint OnUncaughtExceptionsBreak)
let breakOnExceptions = BREAK_ON_EXCEPTION `elem` setExceptionBreakpointsArgumentsFilters
let breakOnError = BREAK_ON_ERROR `elem` setExceptionBreakpointsArgumentsFilters
when breakOnExceptions $ do
DidSetBreakpoint _ <- sendSync (SetBreakpoint OnExceptionsBreak)
pure ()
when breakOnError $ do
DidSetBreakpoint _ <- sendSync (SetBreakpoint OnUncaughtExceptionsBreak)
pure ()
sendSetExceptionBreakpointsResponse
[ defaultBreakpoint | True <- [breakOnError, breakOnExceptions] ]
--------------------------------------------------------------------------------
-- * Aux
--------------------------------------------------------------------------------
pattern BREAK_ON_EXCEPTION, BREAK_ON_ERROR :: T.Text
pattern BREAK_ON_EXCEPTION = "break-on-exception"
pattern BREAK_ON_ERROR = "break-on-error"
-- | Turn a ghc-debugger 'BreakFound' into a DAP 'Breakpoint'.
--
-- Additionally, gets a fresh Id for the breakpoint and registers it on the breakpoint map
registerBreakFound :: BreakFound -> DebugAdaptor [DAP.Breakpoint]
registerBreakFound b =
case b of
ManyBreaksFound bs -> concat <$> mapM registerBreakFound bs
BreakNotFound -> pure [ DAP.defaultBreakpoint { DAP.breakpointVerified = False } ]
BreakFoundNoLoc _ch -> pure [ DAP.defaultBreakpoint { DAP.breakpointVerified = True } ]
BreakFound _ch iid ss -> do
bid <- registerNewBreakpoint iid
source <- fileToSource ss.file
pure [ DAP.defaultBreakpoint
{ DAP.breakpointVerified = True
, DAP.breakpointSource = Just source
, DAP.breakpointLine = Just ss.startLine
, DAP.breakpointEndLine = Just ss.endLine
, DAP.breakpointColumn = Just ss.startCol
, DAP.breakpointEndColumn = Just ss.endCol
, DAP.breakpointId = Just bid
} ]
-- | Adds new BreakpointId for a givent StgPoint
registerNewBreakpoint :: GHC.BreakpointId -> DebugAdaptor BreakpointId
registerNewBreakpoint breakpoint = do
bkpId <- getFreshBreakpointId
updateDebugSession $ \das@DAS{..} -> das {breakpointMap = Map.insertWith mappend breakpoint (IS.singleton bkpId) breakpointMap}
pure bkpId
-- | Generate fresh breakpoint Id.
getFreshBreakpointId :: DebugAdaptor BreakpointId
getFreshBreakpointId = do
bkpId <- nextFreshBreakpointId <$> getDebugSession
updateDebugSession $ \s -> s { nextFreshBreakpointId = nextFreshBreakpointId s + 1 }
pure bkpId
-- | Get the file from a DAP Source
--
-- TODO: Handles sourceReferences too
fileFromSourcePath :: Source -> DebugAdaptor FilePath
fileFromSourcePath source = do
let
file = T.unpack $
fromMaybe (error "sourceReference unsupported") $
sourcePath source
return file