ghci-dap-0.0.26.0: app/GHCi/DAP/Command.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE CPP #-}
module GHCi.DAP.Command where
import qualified GHCi.GhcApiCompat as GAC
import Control.Monad.IO.Class
import qualified GHC as G
import qualified GHCi.UI as Gi
import qualified GHCi.UI.Monad as Gi hiding (runStmt)
import Control.Concurrent
import Control.Monad
import qualified Control.Monad.Catch as E
import System.Console.Haskeline
import System.Directory
import qualified Data.Map as M
import qualified Data.List as L
import GHCi.DAP.Type
import GHCi.DAP.Constant
import GHCi.DAP.Utility
import qualified Haskell.DAP as D
#if __GLASGOW_HASKELL__ >= 900
import qualified GHC.Data.StringBuffer as SB (lexemeToString, len)
#else
#endif
-- |
--
dapCommands :: [Gi.Command]
dapCommands = map mkCmd [
("dap-launch", Gi.keepGoing launchCmd, noCompletion)
, ("dap-context-modules", Gi.keepGoing contextModulesCmd, noCompletion)
, ("dap-set-breakpoints", Gi.keepGoing setBpCmd, noCompletion)
, ("dap-set-function-breakpoints", Gi.keepGoing setFuncBpsCmd, noCompletion)
, ("dap-set-function-breakpoint", Gi.keepGoing setFuncBpCmd, noCompletion)
, ("dap-delete-breakpoint", Gi.keepGoing delBpCmd, noCompletion)
, ("dap-stacktrace", Gi.keepGoing dapStackTraceCmd, noCompletion)
, ("dap-scopes", Gi.keepGoing dapScopesCmd, noCompletion)
, ("dap-variables", Gi.keepGoing dapVariablesCmd, noCompletion)
, ("dap-evaluate", Gi.keepGoing dapEvalCmd, noCompletion)
, ("dap-continue", Gi.keepGoing dapContinueCmd, noCompletion)
, ("dap-next", Gi.keepGoing nextCmd, noCompletion)
, ("dap-step-in", Gi.keepGoing stepInCmd, noCompletion)
, ("dap-source", Gi.keepGoing sourceCmd, noCompletion)
]
where
mkCmd (n,a,c) = Gi.Command {
Gi.cmdName = n
, Gi.cmdAction = a
, Gi.cmdHidden = False
, Gi.cmdCompletionFunc = c
}
------------------------------------------------------------------------------------------------
-- DAP Command :dap-launch
------------------------------------------------------------------------------------------------
-- |
--
launchCmd :: String -> Gi.GHCi ()
launchCmd argsStr = flip gcatch errHdl $ do
decodeDAP argsStr
>>= launchCmd_
>>= printDAP
-- |
--
launchCmd_ :: D.LaunchRequestArguments
-> Gi.GHCi (Either String ())
launchCmd_ arg = do
setLogLevel
setForceInspect
return $ Right ()
where
-- |
--
setLogLevel :: Gi.GHCi ()
setLogLevel = do
let lv = case D.logLevelLaunchRequestArguments arg of
"EMERGENCY" -> ErrorLogLevel
"ALERT" -> ErrorLogLevel
"CRITICAL" -> ErrorLogLevel
"ERROR" -> ErrorLogLevel
"WARNING" -> WarnLogLevel
"NOTICE" -> WarnLogLevel
"INFO" -> InfoLogLevel
"DEBUG" -> DebugLogLevel
_ -> WarnLogLevel
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
ctx <- liftIO $ takeMVar ctxMVar
liftIO $ putMVar ctxMVar ctx {logLevelDAPContext = lv}
-- |
--
setForceInspect :: Gi.GHCi ()
setForceInspect = do
let isForce = case D.forceInspectLaunchRequestArguments arg of
Nothing -> False
Just a -> a
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
ctx <- liftIO $ takeMVar ctxMVar
liftIO $ putMVar ctxMVar ctx {isInspectVariableDAPContext = isForce}
------------------------------------------------------------------------------------------------
-- DAP Command :dap-context-modules
------------------------------------------------------------------------------------------------
-- |
--
contextModulesCmd :: String -> Gi.GHCi ()
contextModulesCmd _ = flip gcatch errHdl $ do
contextModulesCmd_ >>= printDAP
-- |
--
contextModulesCmd_ :: Gi.GHCi (Either String ())
contextModulesCmd_ = do
modSums <- Gi.getLoadedModules
let modNames = map GAC.ms_mod_name modSums
modNameStrs = map G.moduleNameString modNames
Gi.setContext modNames []
infoL $ "context modules. " ++ show modNameStrs
return $ Right ()
------------------------------------------------------------------------------------------------
-- DAP Command :dap-set-breakpoints
------------------------------------------------------------------------------------------------
-- |
--
setBpCmd :: String -> Gi.GHCi ()
setBpCmd argsStr = flip gcatch errHdl $ do
decodeDAP argsStr
>>= setBpCmd_
>>= printDAP
-- |
--
setBpCmd_ :: D.SetBreakpointsRequestArguments
-> Gi.GHCi (Either String D.SetBreakpointsResponseBody)
setBpCmd_ args =
deleteBreakpoints >> addBreakpoints
where
-- |
--
deleteBreakpoints :: Gi.GHCi ()
deleteBreakpoints = do
bps <- getDelBPs
debugL $ "<dapSetBreakpointsCommand> delete src bps " ++ show bps
mapM_ delBreakpoint bps
-- |
--
addBreakpoints :: Gi.GHCi (Either String D.SetBreakpointsResponseBody)
addBreakpoints = do
let srcBPs = D.breakpointsSetBreakpointsRequestArguments args
addBps <- mapM addBP srcBPs
updateBpCtx addBps
return $ Right $ D.SetBreakpointsResponseBody $ map takeBp addBps
-- |
--
getDelBPs :: Gi.GHCi [Int]
getDelBPs = do
mod <- getModule
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
ctx <- liftIO $ takeMVar ctxMVar
let bpNOs = M.keys $ M.filter ((isModuleMatch mod)) $ srcBPsDAPContext ctx
newSrcBPs = M.filter (not . (isModuleMatch mod)) $ srcBPsDAPContext ctx
liftIO $ putMVar ctxMVar $ ctx {srcBPsDAPContext = newSrcBPs}
return bpNOs
-- |
--
isModuleMatch :: ModuleName -> SourceBreakpointInfo -> Bool
isModuleMatch mod bpInfo = mod == modNameSourceBreakpointInfo bpInfo
-- |
--
getModule :: Gi.GHCi ModuleName
getModule = do
let srcInfo = D.sourceSetBreakpointsRequestArguments args
srcPath = D.pathSource srcInfo
modSums <- Gi.getLoadedModules
let modPaths = map takeModPath modSums
findModule srcPath modPaths >>= \case
Just (m, p) -> do
debugL $ "<getModule> " ++ p ++ " -> " ++ m
return m
Nothing -> throwError $ "<getModule> loaded module can not find from path. <" ++ srcPath ++ "> " ++ show modPaths
-- |
--
takeModPath :: GAC.ModSummary -> (ModuleName, FilePath)
takeModPath ms = (G.moduleNameString (G.ms_mod_name ms), GAC.msHsFilePath ms)
-- |
--
addBP :: D.SourceBreakpoint -> Gi.GHCi (ModuleName, D.SourceBreakpoint, D.Breakpoint)
addBP srcBP = do
mod <- getModule
let lineNo = show $ D.lineSourceBreakpoint srcBP
colNo = getColNo $ D.columnSourceBreakpoint srcBP
argStr = mod ++ " " ++ lineNo ++ " " ++ colNo
bp <- addBreakpoint argStr
return (mod, srcBP, bp)
-- |
--
getColNo :: Maybe Int -> String
getColNo Nothing = ""
getColNo (Just 1) = ""
getColNo (Just a) = show a
-- |
--
updateBpCtx :: [(ModuleName, D.SourceBreakpoint, D.Breakpoint)] -> Gi.GHCi ()
updateBpCtx bps = do
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
ctx <- liftIO $ takeMVar ctxMVar
let cur = srcBPsDAPContext ctx
new = M.fromList $ foldr convSrcBps [] bps
liftIO $ putMVar ctxMVar $ ctx{srcBPsDAPContext = (M.union cur new)}
-- |
--
convSrcBps :: (ModuleName, D.SourceBreakpoint, D.Breakpoint)
-> [(Int, SourceBreakpointInfo)]
-> [(Int, SourceBreakpointInfo)]
convSrcBps (mod, srcBp, bp) acc = case D.idBreakpoint bp of
Nothing -> acc
Just no -> (no, SourceBreakpointInfo mod srcBp 0) : acc
-- |
--
takeBp :: (ModuleName, D.SourceBreakpoint, D.Breakpoint) -> D.Breakpoint
takeBp (_, _, bp) = bp
------------------------------------------------------------------------------------------------
-- DAP Command :dap-set-function-breakpoints
------------------------------------------------------------------------------------------------
-- |
--
setFuncBpsCmd :: String -> Gi.GHCi ()
setFuncBpsCmd argsStr = flip gcatch errHdl $ do
decodeDAP argsStr
>>= setFuncBpsCmd_
>>= printDAP
-- |
--
setFuncBpsCmd_ :: D.SetFunctionBreakpointsRequestArguments
-> Gi.GHCi (Either String D.SetFunctionBreakpointsResponseBody)
setFuncBpsCmd_ args = deleteBreakpoints >> addBreakpoints
where
-- |
--
deleteBreakpoints :: Gi.GHCi ()
deleteBreakpoints = do
bps <- getDelBPs
debugL $ "<dapSetFunctionBreakpointsCommand> delete func bps " ++ show bps
mapM_ delBreakpoint bps
-- |
--
addBreakpoints :: Gi.GHCi (Either String D.SetFunctionBreakpointsResponseBody)
addBreakpoints = do
let funcBPs = D.breakpointsSetFunctionBreakpointsRequestArguments args
addBps <- mapM addBP funcBPs
updateBpCtx addBps
return $ Right $ D.SetFunctionBreakpointsResponseBody $ map snd addBps
-- |
--
getDelBPs :: Gi.GHCi [Int]
getDelBPs = do
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
ctx <- liftIO $ takeMVar ctxMVar
let bpNOs = M.keys $ funcBPsDAPContext ctx
liftIO $ putMVar ctxMVar $ ctx {funcBPsDAPContext = M.fromList []}
return bpNOs
-- |
--
addBP :: D.FunctionBreakpoint -> Gi.GHCi (D.FunctionBreakpoint, D.Breakpoint)
addBP funcBP = do
let argStr = D.nameFunctionBreakpoint funcBP
bp <- addBreakpoint argStr
return (funcBP, bp)
-- |
--
updateBpCtx :: [(D.FunctionBreakpoint, D.Breakpoint)] -> Gi.GHCi ()
updateBpCtx bps = do
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
ctx <- liftIO $ takeMVar ctxMVar
let new = foldr getBpNo [] bps
liftIO $ putMVar ctxMVar $ ctx{funcBPsDAPContext = M.fromList new}
-- |
--
getBpNo :: (D.FunctionBreakpoint, D.Breakpoint)
-> [(Int, (D.FunctionBreakpoint, Int))]
-> [(Int, (D.FunctionBreakpoint, Int))]
getBpNo (funcBP, bp) acc = case D.idBreakpoint bp of
Nothing -> acc
Just no -> (no, (funcBP, 0)) : acc
------------------------------------------------------------------------------------------------
-- DAP Command :dap-set-function-breakpoint
------------------------------------------------------------------------------------------------
-- |
--
setFuncBpCmd :: String -> Gi.GHCi ()
setFuncBpCmd argsStr = flip gcatch errHdl $ do
decodeDAP argsStr
>>= setFuncBpCmd_
>>= printDAP
-- |
--
setFuncBpCmd_ :: (FilePath, D.FunctionBreakpoint)
-> Gi.GHCi (Either String D.Breakpoint)
setFuncBpCmd_ (startup, funcBP) = do
modName <- getModuleByFile
let funcName = D.nameFunctionBreakpoint funcBP
argStr = modName ++ "." ++ funcName
bp <- addBreakpoint argStr
updateBpCtx (funcBP, bp)
return $ Right bp
where
-- |
--
getModuleByFile :: Gi.GHCi String
getModuleByFile = do
modSums <- Gi.getLoadedModules
let modPaths = map takeModPath modSums
findModule startup modPaths >>= \case
Just (m, p) -> do
debugL $ "<getModuleByFile> " ++ p ++ " -> " ++ m
return m
Nothing -> throwError $ "<getModuleByFile> loaded module can not find from path. <" ++ startup ++ "> " ++ show modPaths
-- |
--
updateBpCtx :: (D.FunctionBreakpoint, D.Breakpoint) -> Gi.GHCi ()
updateBpCtx (funcBP, bp) = case D.idBreakpoint bp of
Nothing -> throwError "breakpoint number not found."
Just no -> do
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
ctx <- liftIO $ takeMVar ctxMVar
let funcBpMap = funcBPsDAPContext ctx
liftIO $ putMVar ctxMVar $ ctx{funcBPsDAPContext = M.insert no (funcBP, 0) funcBpMap}
------------------------------------------------------------------------------------------------
-- DAP Command :dap-delete-breakpoint
------------------------------------------------------------------------------------------------
-- |
--
delBpCmd :: String -> Gi.GHCi ()
delBpCmd argsStr = flip gcatch errHdl $ do
decodeDAP argsStr
>>= delBpCmd_
>>= printDAP
-- |
--
delBpCmd_ :: D.Breakpoint -> Gi.GHCi (Either String ())
delBpCmd_ D.Breakpoint{D.idBreakpoint = Nothing} = throwError "breakpoint number not found."
delBpCmd_ D.Breakpoint{D.idBreakpoint = Just bid} = do
delBreakpoint bid
updateBpCtx
return $ Right ()
where
-- |
--
updateBpCtx :: Gi.GHCi ()
updateBpCtx = do
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
ctx <- liftIO $ takeMVar ctxMVar
let funcBpMap = funcBPsDAPContext ctx
liftIO $ putMVar ctxMVar $ ctx{funcBPsDAPContext = M.delete bid funcBpMap}
------------------------------------------------------------------------------------------------
-- DAP Command :dap-stacktrace
------------------------------------------------------------------------------------------------
-- |
--
dapStackTraceCmd :: String -> Gi.GHCi ()
dapStackTraceCmd argsStr = flip gcatch errHdl $ do
decodeDAP argsStr
>>= dapStackTraceCmd_
>>= printDAP
-- |
--
dapStackTraceCmd_ :: D.StackTraceRequestArguments
-> Gi.GHCi (Either String D.StackTraceResponseBody)
dapStackTraceCmd_ _ = do
clearStackTraceResult
Gi.historyCmd ""
getStackTraceResult >>= \case
Nothing -> throwError $ "no stacktrace found."
Just res -> withResult res
where
-- |
--
clearStackTraceResult :: Gi.GHCi ()
clearStackTraceResult = do
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
ctx <- liftIO $ takeMVar ctxMVar
liftIO $ putMVar ctxMVar ctx {stackTraceResultDAPContext = Nothing}
-- |
--
getStackTraceResult :: Gi.GHCi (Maybe (G.Resume, [G.History]))
getStackTraceResult = do
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
ctx <- liftIO $ readMVar ctxMVar
return $ stackTraceResultDAPContext ctx
-- |
--
withResult :: (G.Resume, [G.History]) -> Gi.GHCi (Either String D.StackTraceResponseBody)
withResult (r, hs) = do
hists <- mapM resumeHist2stackFrame hs
let traces = if isExceptionResume r
then hists
else resume2stackframe r : hists
let traceWithId = setFrameIdx 0 traces
traceWithId' <- liftIO $ mapM convertToAbs traceWithId
return $ Right D.defaultStackTraceResponseBody {
D.stackFramesStackTraceResponseBody = traceWithId'
, D.totalFramesStackTraceResponseBody = length traceWithId'
}
-- |
--
convertToAbs :: D.StackFrame -> IO D.StackFrame
convertToAbs sf@D.StackFrame{D.sourceStackFrame=ssf}
| D.pathSource ssf == _STACK_FRAME_UNHELPFULL_SPAN = return sf
| otherwise = E.catch (pathToAbs sf ssf) (constSF sf)
-- |
--
pathToAbs :: D.StackFrame -> D.Source -> IO D.StackFrame
pathToAbs sf ssf = do
ps <- canonicalizePath $ D.pathSource ssf
return sf{D.sourceStackFrame=ssf{D.pathSource = ps}}
-- |
--
constSF :: D.StackFrame -> E.SomeException -> IO D.StackFrame
constSF sf e = do
putStrLn $ show InfoLogLevel ++ show e
return sf
-- |
--
resumeHist2stackFrame :: G.History -> Gi.GHCi D.StackFrame
resumeHist2stackFrame hist= do
span <- G.getHistorySpan hist
let name = L.intercalate ":" (G.historyEnclosingDecls hist)
return $ genStackFrame span name
-- |
--
resume2stackframe :: G.Resume -> D.StackFrame
resume2stackframe r = genStackFrame (G.resumeSpan r) (getStackFrameTitle r)
-- |
--
setFrameIdx :: Int -> [D.StackFrame] -> [D.StackFrame]
setFrameIdx _ [] = []
setFrameIdx idx (x:xs) = x{D.idStackFrame = idx} : setFrameIdx (idx+1) xs
-- |
--
getStackFrameTitle :: G.Resume -> String
#if __GLASGOW_HASKELL__ >= 912 || __GLASGOW_HASKELL__ >= 910 && __GLASGOW_HASKELL_PATCHLEVEL1__ >= 2
getStackFrameTitle r = maybe "unknown" (G.moduleNameString . G.moduleName . G.ibi_tick_mod) (G.resumeBreakpointId r)
#else
getStackFrameTitle r = maybe "unknown" (G.moduleNameString . G.moduleName . G.breakInfo_module) (G.resumeBreakInfo r)
#endif
++ "."
++ G.resumeDecl r
-- |
--
genStackFrame :: G.SrcSpan -> String -> D.StackFrame
#if __GLASGOW_HASKELL__ >= 900
genStackFrame (G.RealSrcSpan dat _) name = D.defaultStackFrame {
#else
genStackFrame (G.RealSrcSpan dat) name = D.defaultStackFrame {
#endif
D.idStackFrame = 0
, D.nameStackFrame = name
, D.sourceStackFrame = D.defaultSource {D.pathSource = (GAC.unpackFS . G.srcSpanFile) dat}
, D.lineStackFrame = G.srcSpanStartLine dat
, D.columnStackFrame = G.srcSpanStartCol dat
, D.endLineStackFrame = G.srcSpanEndLine dat
, D.endColumnStackFrame = G.srcSpanEndCol dat
}
genStackFrame (G.UnhelpfulSpan _) name = D.defaultStackFrame {
D.idStackFrame = 0
, D.nameStackFrame = name
, D.sourceStackFrame = D.defaultSource {D.pathSource = _STACK_FRAME_UNHELPFULL_SPAN}
, D.lineStackFrame = 0
, D.columnStackFrame = 0
, D.endLineStackFrame = 0
, D.endColumnStackFrame = 0
}
------------------------------------------------------------------------------------------------
-- DAP Command :dap-scopes
------------------------------------------------------------------------------------------------
-- |
--
dapScopesCmd :: String -> Gi.GHCi ()
dapScopesCmd argsStr = flip gcatch errHdl $ do
decodeDAP argsStr
>>= dapScopesCmd_
>>= printDAP
-- |
--
dapScopesCmd_ :: D.ScopesRequestArguments
-> Gi.GHCi (Either String D.ScopesResponseBody)
dapScopesCmd_ args = moveScope >> makeResponse
where
-- |
--
moveScope :: Gi.GHCi ()
moveScope = do
let curIdx = D.frameIdScopesRequestArguments args
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
oldIdx <- liftIO $ frameIdDAPContext <$> readMVar ctxMVar
let moveIdx = curIdx - oldIdx
tyThings <- withMoveIdx moveIdx
gobalTT <- getGlobalBindings
ctx <- liftIO $ takeMVar ctxMVar
liftIO $ putMVar ctxMVar ctx {
variableReferenceMapDAPContext = M.empty
, bindingDAPContext = tyThings
, bindingGlobalDAPContext = gobalTT
, frameIdDAPContext = curIdx
}
-- |
--
makeResponse :: Gi.GHCi (Either String D.ScopesResponseBody)
makeResponse = return $ Right D.ScopesResponseBody {
D.scopesScopesResponseBody = [
D.defaultScope{
D.nameScope = _GHCi_SCOPE
, D.variablesReferenceScope = 1
, D.namedVariablesScope = Nothing
, D.indexedVariablesScope = Nothing
, D.expensiveScope = False
}
,
D.defaultScope{
D.nameScope = _GHCi_GLOBAL_SCOPE
, D.variablesReferenceScope = 2
, D.namedVariablesScope = Nothing
, D.indexedVariablesScope = Nothing
, D.expensiveScope = False
}
]
}
-- |
--
withMoveIdx :: Int -> Gi.GHCi [GAC.TyThing]
withMoveIdx moveIdx
| 0 == moveIdx = G.getBindings
| 0 < moveIdx = back moveIdx
| otherwise = forward (negate moveIdx)
#if __GLASGOW_HASKELL__ >= 908
-- |
--
getGlobalBindings :: Gi.GHCi [GAC.TyThing]
getGlobalBindings = GAC.withSession $ \hsc_env -> do
let ic = GAC.hsc_IC hsc_env
gb = GAC.ic_rn_gbl_env ic
es = GAC.globalRdrEnvElts gb
ns = map GAC.gre_name es
foldM withName [] $ reverse ns
#elif __GLASGOW_HASKELL__ >= 904
-- |
--
getGlobalBindings :: Gi.GHCi [GAC.TyThing]
getGlobalBindings = GAC.withSession $ \hsc_env -> do
let ic = GAC.hsc_IC hsc_env
gb = GAC.icReaderEnv ic
es = GAC.globalRdrEnvElts gb
ns = foldr contName [] $ map GAC.gre_name es
foldM withName [] $ reverse ns
where
contName :: GAC.GreName -> [G.Name] -> [G.Name]
contName (GAC.NormalGreName n) xs = n:xs
contName (GAC.FieldGreName n) xs = GAC.fieldLabelPrintableName n:xs
#elif __GLASGOW_HASKELL__ >= 902
-- |
--
getGlobalBindings :: Gi.GHCi [GAC.TyThing]
getGlobalBindings = GAC.withSession $ \hsc_env -> do
let ic = GAC.hsc_IC hsc_env
gb = GAC.ic_rn_gbl_env ic
es = GAC.globalRdrEnvElts gb
ns = foldr contName [] $ map GAC.gre_name es
foldM withName [] $ reverse ns
where
contName :: GAC.GreName -> [G.Name] -> [G.Name]
contName (GAC.NormalGreName n) xs = n:xs
contName (GAC.FieldGreName n) xs = GAC.fieldLabelPrintableName n:xs
#else
-- |
--
getGlobalBindings :: Gi.GHCi [GAC.TyThing]
getGlobalBindings = GAC.withSession $ \hsc_env -> do
let ic = GAC.hsc_IC hsc_env
gb = GAC.ic_rn_gbl_env ic
es = GAC.globalRdrEnvElts gb
ns = map GAC.gre_name es
foldM withName [] $ reverse ns
#endif
-- |
--
back :: Int -> Gi.GHCi [GAC.TyThing]
back num = do
clearBindingNames
Gi.backCmd $ show num
names <- getBindingNames
foldM withName [] $ reverse names
-- |
--
forward :: Int -> Gi.GHCi [GAC.TyThing]
forward num = do
clearBindingNames
Gi.forwardCmd $ show num
names <- getBindingNames
foldM withName [] $ reverse names
-- |
--
withName :: [GAC.TyThing] -> G.Name -> Gi.GHCi [GAC.TyThing]
withName acc n = G.lookupName n >>= \case
Just ty -> return (ty : acc)
Nothing -> do
dflags <- GAC.getDynFlags
errorL $ "variable not found. " ++ GAC.showSDoc dflags (GAC.ppr n)
return acc
------------------------------------------------------------------------------------------------
-- DAP Command :dap-variables
------------------------------------------------------------------------------------------------
-- |
--
dapVariablesCmd :: String -> Gi.GHCi ()
dapVariablesCmd argsStr = flip gcatch errHdl $ do
decodeDAP argsStr
>>= dapVariablesCmd_
>>= printDAP
-- |
--
dapVariablesCmd_ :: D.VariablesRequestArguments
-> Gi.GHCi (Either String D.VariablesResponseBody)
dapVariablesCmd_ args = do
let idx = D.variablesReferenceVariablesRequestArguments args
vals <- getBindingVariables idx
return $ Right $ D.VariablesResponseBody $ L.sortBy compName vals
where
-- |
--
compName :: D.Variable -> D.Variable -> Ordering
compName a b = compare (D.nameVariable a) (D.nameVariable b)
-- |
--
getBindingVariables :: Int -> Gi.GHCi [D.Variable]
getBindingVariables idx
| 1 == idx = getBindingVariablesLocal
| 2 == idx = getBindingVariablesGlobal
| otherwise = getBindingVariablesNode idx
-- |
--
getBindingVariablesLocal :: Gi.GHCi [D.Variable]
getBindingVariablesLocal = do
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
bindings <- liftIO $ bindingDAPContext <$> readMVar ctxMVar
getBindingVariablesRoot bindings
-- |
--
getBindingVariablesGlobal :: Gi.GHCi [D.Variable]
getBindingVariablesGlobal = do
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
bindings <- liftIO $ bindingGlobalDAPContext <$> readMVar ctxMVar
getBindingVariablesRoot bindings
-- |
--
getBindingVariablesRoot :: [G.TyThing] -> Gi.GHCi [D.Variable]
getBindingVariablesRoot bindings = do
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
ctx <- liftIO $ readMVar ctxMVar
let isInspect = isInspectVariableDAPContext ctx
--
-- ref: https://gitlab.haskell.org/ghc/ghc/-/issues/19394
-- ignore console messages.
-- <interactive>: ^^ Could not load 'ghczmprim_GHCziPrim_seq_closure', dependency unresolved. See top entry above.
--
vars <- mapM (tyThing2Var isInspect) bindings
return vars
-- |
--
getBindingVariablesNode :: Int -> Gi.GHCi [D.Variable]
getBindingVariablesNode idx = do
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
ctx <- liftIO $ readMVar ctxMVar
case M.lookup idx (variableReferenceMapDAPContext ctx) of
Just (t, str) -> term2Vars t str
Nothing -> throwError $ "variable id:" ++ show idx ++ " not found."
where
-- |
--
term2Vars :: GAC.Term -> String -> Gi.GHCi [D.Variable]
term2Vars (GAC.Term _ (Right dc) _ subTerms) str = do
let labels = if 0 == length (GAC.dataConFieldLabels dc)
then map (\i->"_" ++ show i) [1..(length subTerms)]
else map (GAC.unpackFS . GAC.fls2fs . GAC.flLabel) (GAC.dataConFieldLabels dc)
mapM (flip term2Var str) $ zip labels subTerms
term2Vars (GAC.Term _ (Left _) _ subTerms) str = do
let labels = map (\i->"_" ++ show i) [1..(length subTerms)]
mapM (flip term2Var str) $ zip labels subTerms
term2Vars t str = do
dflags <- GAC.getDynFlags
let tstr = GAC.showSDoc dflags (GAC.ppr t)
warnL $ "unsupported map term type. " ++ tstr ++ ". idx:" ++ show idx ++ ", name:" ++ str
return []
------------------------------------------------------------------------------------------------
-- DAP Command :dap-evaluate
------------------------------------------------------------------------------------------------
-- |
--
dapEvalCmd :: String -> Gi.GHCi ()
dapEvalCmd argsStr = flip gcatch errHdl $ do
decodeDAP argsStr
>>= dapEvalCmd_
>>= printDAP
-- |
--
dapEvalCmd_ :: D.EvaluateRequestArguments
-> Gi.GHCi (Either String D.EvaluateResponseBody)
dapEvalCmd_ args = case D.contextEvaluateRequestArguments args of
Nothing -> runRepl args
Just "repl" -> runRepl args
Just "watch" -> runOther args
Just "hover" -> runOther args
_ -> runOther args
where
-- |
--
runRepl :: D.EvaluateRequestArguments -> Gi.GHCi (Either String D.EvaluateResponseBody)
runRepl args = runStmt $ D.expressionEvaluateRequestArguments args
-- |
--
runStmt :: String -> Gi.GHCi (Either String D.EvaluateResponseBody)
runStmt "" =
return $ Right D.defaultEvaluateResponseBody {
D.resultEvaluateResponseBody = "no input."
, D.typeEvaluateResponseBody = "no input."
, D.variablesReferenceEvaluateResponseBody = 0
}
runStmt stmt = do
var <- runStmtVar stmt
return $ Right D.defaultEvaluateResponseBody {
D.resultEvaluateResponseBody = D.valueVariable var
, D.typeEvaluateResponseBody = D.typeVariable var
, D.variablesReferenceEvaluateResponseBody = D.variablesReferenceVariable var
}
-- |
--
runOther :: D.EvaluateRequestArguments -> Gi.GHCi (Either String D.EvaluateResponseBody)
runOther args = do
let nameStr = D.expressionEvaluateRequestArguments args
names <- GAC.parseName nameStr
var <- names2Var nameStr names
let varStr = if "_" == D.valueVariable var
then "_ :: " ++ D.typeVariable var
else D.valueVariable var
return $ Right D.defaultEvaluateResponseBody {
D.resultEvaluateResponseBody = varStr
, D.typeEvaluateResponseBody = D.typeVariable var
, D.variablesReferenceEvaluateResponseBody = D.variablesReferenceVariable var
}
------------------------------------------------------------------------------------------------
-- DAP Command :dap-continue
------------------------------------------------------------------------------------------------
-- |
--
dapContinueCmd :: String -> Gi.GHCi ()
dapContinueCmd argsStr = flip gcatch errHdl $ do
decodeDAP argsStr
>>= dapContinueCmd_
>>= printDAP
-- |
--
dapContinueCmd_ :: D.ContinueRequestArguments
-> Gi.GHCi (Either String D.StoppedEventBody)
dapContinueCmd_ args = do
seb <- case D.exprContinueRequestArguments args of
Just exp -> startTrace exp
Nothing -> continue
return $ Right seb
where
-- |
--
startTrace :: String -> Gi.GHCi D.StoppedEventBody
startTrace expr = do
clearContinueExecResult
-- ghci maybe throw error cause of force inspecting variable.
gcatch (Gi.traceCmd expr) unexpectErrHdl
handleResult
-- |
--
continue :: Gi.GHCi D.StoppedEventBody
continue = startTrace ""
-- |
--
handleResult :: Gi.GHCi D.StoppedEventBody
handleResult = hasBreaked >>= \case
False -> genStoppedEventBody "breakpoint"
True -> isBreakthrough >>= \case
False -> genStoppedEventBody "breakpoint"
True -> continue
-- |
--
hasBreaked :: Gi.GHCi Bool
hasBreaked = getContinueExecResult >>= \case
#if __GLASGOW_HASKELL__ >= 912 || __GLASGOW_HASKELL__ >= 910 && __GLASGOW_HASKELL_PATCHLEVEL1__ >= 2
Just G.ExecBreak {G.breakPointId = Just _} -> return True
#else
Just G.ExecBreak {G.breakInfo = Just _} -> return True
#endif
_ -> return False
-- |
--
isBreakthrough :: Gi.GHCi Bool
isBreakthrough = G.getResumeContext >>= \case
[] -> warnL "invalid resume state. resume not found."
>> return False
#if __GLASGOW_HASKELL__ >= 912 || __GLASGOW_HASKELL__ >= 910 && __GLASGOW_HASKELL_PATCHLEVEL1__ >= 2
(r:_) -> pure (G.resumeBreakpointId r)
#else
(r:_) -> pure (G.resumeBreakInfo r)
#endif
>>= Gi.toBreakIdAndLocation
>>= withBreakInfo
-- |
-- @return
-- True -> thruough
-- False -> break
--
withBreakInfo :: Maybe (Int, Gi.BreakLocation) -> Gi.GHCi Bool
withBreakInfo Nothing = do
warnL "invalid resume break info state."
return False
withBreakInfo (Just (no, _)) = findSrcBP no >>= \case
Just srcBP -> withSrcBP no srcBP
Nothing -> findFuncBP no >>= \case
Just fncBP -> withFuncBP no fncBP
Nothing -> do
warnL $ "invalid break no. " ++ show no
return False
-- |
--
findSrcBP :: Int -> Gi.GHCi (Maybe SourceBreakpointInfo)
findSrcBP no = do
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
srcBPs <- liftIO $ srcBPsDAPContext <$> readMVar ctxMVar
return $ M.lookup no srcBPs
-- |
--
findFuncBP :: Int -> Gi.GHCi (Maybe (D.FunctionBreakpoint, Int))
findFuncBP no = do
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
funcBPs <- liftIO $ funcBPsDAPContext <$> readMVar ctxMVar
return $ M.lookup no funcBPs
-- |
-- @return
-- True -> thruough
-- False -> break
--
withSrcBP :: Int -> SourceBreakpointInfo -> Gi.GHCi Bool
withSrcBP no bpInfo =
let bpCond = D.conditionSourceBreakpoint (srcBPSourceBreakpointInfo bpInfo)
bpLog = D.logMessageSourceBreakpoint (srcBPSourceBreakpointInfo bpInfo)
in
srcBreakthroughCounterHandler no bpInfo >>= \case
Just res -> return res
Nothing -> breakthroughCondtionHandler no bpCond >>= \case
Just res -> return res
Nothing -> logPointHandler no bpLog >>= \case
Just res -> return res
Nothing -> return False
-- |
-- @return
-- True -> thruough
-- False -> break
--
withFuncBP :: Int -> (D.FunctionBreakpoint, Int) -> Gi.GHCi Bool
withFuncBP no bpInfo =
let bpCond = D.conditionFunctionBreakpoint (fst bpInfo)
in
funcBreakthroughCounterHandler no bpInfo >>= \case
Just res -> return res
Nothing -> breakthroughCondtionHandler no bpCond >>= \case
Just res -> return res
Nothing -> return False
-- |
--
srcBreakthroughCounterHandler :: Int -> SourceBreakpointInfo -> Gi.GHCi (Maybe Bool)
srcBreakthroughCounterHandler _ SourceBreakpointInfo {
srcBPSourceBreakpointInfo = D.SourceBreakpoint {
D.hitConditionSourceBreakpoint = Nothing
}
} = return Nothing
srcBreakthroughCounterHandler no bpInfo@SourceBreakpointInfo {
srcBPSourceBreakpointInfo = D.SourceBreakpoint {
D.hitConditionSourceBreakpoint = Just condStr
}
, hitCntSourceBreakpointInfo = curCnt} = do
let newCnt = curCnt + 1
stmt = if L.isInfixOf "_CNT" condStr
then "let _CNT = " ++ show newCnt ++ " in " ++ condStr
else "let _CNT = " ++ show newCnt ++ " in _CNT " ++ condStr
updateSrcBreakCounter no bpInfo{hitCntSourceBreakpointInfo = newCnt}
var <- runStmtVar stmt
when ("Bool" /= D.typeVariable var) $ do
warnL $ "hit condition statement result type is not Bool. BPNO:" ++ show no ++ " " ++ stmt ++ " -> " ++ show var
debugL $ "hit condition statement result. " ++ stmt ++ " -> " ++ show var
return $ Just ("False" == D.valueVariable var)
-- |
--
updateSrcBreakCounter :: Int -> SourceBreakpointInfo -> Gi.GHCi ()
updateSrcBreakCounter no bpInfo = do
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
ctx <- liftIO $ takeMVar ctxMVar
let cur = srcBPsDAPContext ctx
new = M.insert no bpInfo cur
liftIO $ putMVar ctxMVar ctx{srcBPsDAPContext = new}
-- |
--
funcBreakthroughCounterHandler :: Int -> (D.FunctionBreakpoint, Int) -> Gi.GHCi (Maybe Bool)
funcBreakthroughCounterHandler _ (D.FunctionBreakpoint{D.hitConditionFunctionBreakpoint = Nothing}, _) = return Nothing
funcBreakthroughCounterHandler no info@(D.FunctionBreakpoint{D.hitConditionFunctionBreakpoint = Just condStr}, curCnt) = do
let newCnt = curCnt + 1
stmt = if L.isInfixOf "_CNT" condStr
then "let _CNT = " ++ show newCnt ++ " in " ++ condStr
else "let _CNT = " ++ show newCnt ++ " in _CNT " ++ condStr
updateFuncBreakCounter no (fst info, newCnt)
var <- runStmtVar stmt
when ("Bool" /= D.typeVariable var) $ do
warnL $ "hit condition statement result type is not Bool. BPNO:" ++ show no ++ " " ++ stmt ++ " -> " ++ show var
debugL $ "hit condition statement result. " ++ stmt ++ " -> " ++ show var
return $ Just ("False" == D.valueVariable var)
-- |
--
updateFuncBreakCounter :: Int -> (D.FunctionBreakpoint, Int) -> Gi.GHCi ()
updateFuncBreakCounter no bpInfo = do
ctxMVar <- Gi.dapContextGHCiState <$> Gi.getGHCiState
ctx <- liftIO $ takeMVar ctxMVar
let cur = funcBPsDAPContext ctx
new = M.insert no bpInfo cur
liftIO $ putMVar ctxMVar ctx{funcBPsDAPContext = new}
-- |
-- @return
-- True -> breakthrough
-- False -> break
--
breakthroughCondtionHandler :: Int -> Maybe String -> Gi.GHCi (Maybe Bool)
breakthroughCondtionHandler _ Nothing = return Nothing
breakthroughCondtionHandler no (Just stmt) = do
var <- runStmtVar stmt
when ("Bool" /= D.typeVariable var) $ do
warnL $ "condition statement result type is not Bool. BPNO:" ++ show no ++ " " ++ stmt ++ " -> " ++ show var
return $ Just ("False" == D.valueVariable var)
-- |
-- @return
-- must be True -> breakthrough
--
logPointHandler :: Int -> Maybe String -> Gi.GHCi (Maybe Bool)
logPointHandler _ Nothing = return Nothing
logPointHandler _ (Just stmt) = do
var <- runStmtVar stmt
let msg = D.valueVariable var ++ "\n"
body = D.defaultOutputEventBody { D.outputOutputEventBody = msg
, D.categoryOutputEventBody = "console"}
printOutputEventDAP (Right body)
return $ Just True
------------------------------------------------------------------------------------------------
-- DAP Command :dap-next
------------------------------------------------------------------------------------------------
-- |
--
nextCmd :: String -> Gi.GHCi ()
nextCmd argsStr = flip gcatch errHdl $ do
decodeDAP argsStr
>>= nextCmd_
>>= printDAP
-- |
--
nextCmd_ :: D.NextRequestArguments
-> Gi.GHCi (Either String D.StoppedEventBody)
nextCmd_ _ = do
clearContinueExecResult
-- ghci maybe throw error cause of force inspecting variable.
gcatch (Gi.stepLocalCmd "") unexpectErrHdl
Right <$> genStoppedEventBody "step"
------------------------------------------------------------------------------------------------
-- DAP Command :dap-step-in
------------------------------------------------------------------------------------------------
-- |
--
stepInCmd :: String -> Gi.GHCi ()
stepInCmd argsStr = flip gcatch errHdl $ do
decodeDAP argsStr
>>= stepInCmd_
>>= printDAP
-- |
--
stepInCmd_ :: D.StepInRequestArguments
-> Gi.GHCi (Either String D.StoppedEventBody)
stepInCmd_ _ = do
clearContinueExecResult
-- ghci maybe throw error cause of force inspecting variable.
gcatch (Gi.stepCmd "") unexpectErrHdl
Right <$> genStoppedEventBody "step"
------------------------------------------------------------------------------------------------
-- DAP Command :dap-source
------------------------------------------------------------------------------------------------
-- |
--
sourceCmd :: String -> Gi.GHCi ()
sourceCmd argsStr = flip gcatch errHdl $ do
decodeDAP argsStr
>>= sourceCmd_
>>= printDAP
-- |
--
sourceCmd_ :: D.SourceRequestArguments
-> Gi.GHCi (Either String D.SourceResponseBody)
#if __GLASGOW_HASKELL__ >= 900
sourceCmd_ args = do
modSums <- Gi.getLoadedModules
case D.sourceSourceRequestArguments args of
Nothing -> throwError "<sourceCmd_> deprecated data: sourceReference property in SourceRequest is not supported"
Just srcInfo -> do
let srcPath = D.pathSource srcInfo
modPaths = map takeModPath modSums
summary = L.find (\sum -> G.ms_hspp_file sum == srcPath) modSums
case summary of
Nothing -> throwError $ "<sourceCmd_> loaded module can not find from path. <" ++ srcPath ++ "> " ++ show modPaths
Just summary -> do
case G.ms_hspp_buf summary of
Nothing -> throwError $ "<sourceCmd_> loaded module can not find from path. <" ++ srcPath ++ "> " ++ show modPaths
Just strBuf -> do
let content = SB.lexemeToString strBuf (SB.len strBuf)
return $ Right D.defaultSourceResponseBody {
D.contentSourceResponseBody = content
}
#else
sourceCmd_ _ = throwError "<sourceCmd_> supported from ghc9."
#endif