packages feed

ghci-dap-0.0.12.0: app/GHCi/DAP/Command.hs

{-# LANGUAGE LambdaCase #-}

module GHCi.DAP.Command where

import qualified GHC as G
import GhcMonad
import HscTypes
import RdrName
import Outputable
import Exception
import FastString
import DataCon
import DynFlags
import RtClosureInspect
import qualified GHCi.UI as Gi
import qualified GHCi.UI.Monad as Gi hiding (runStmt)

import Control.Monad.Trans.Class
import Control.Concurrent
import Control.Monad

import qualified Data.Map as M
import qualified Data.List as L

import System.Console.Haskeline

import qualified Haskell.DAP as D
import GHCi.DAP.Type
import GHCi.DAP.Constant
import GHCi.DAP.Utility


-- |
--
dapCommands :: [Gi.Command]
dapCommands = map mkCmd [
    ("dap-launch",                   dapCmdRunner launchCmd,        noCompletion)
  , ("dap-set-breakpoints",          dapCmdRunner setBpCmd,         noCompletion)
  , ("dap-set-function-breakpoints", dapCmdRunner setFuncBpsCmd,    noCompletion)
  , ("dap-set-function-breakpoint",  dapCmdRunner setFuncBpCmd,     noCompletion)
  , ("dap-delete-breakpoint",        dapCmdRunner delBpCmd,         noCompletion)
  , ("dap-stacktrace",               dapCmdRunner dapStackTraceCmd, noCompletion)
  , ("dap-scopes",                   dapCmdRunner dapScopesCmd,     noCompletion)
  , ("dap-variables",                dapCmdRunner dapVariablesCmd,  noCompletion)
  , ("dap-evaluate",                 dapCmdRunner dapEvalCmd,       noCompletion)
  , ("dap-continue",                 dapCmdRunner dapContinueCmd,   noCompletion)
  , ("dap-next",                     dapCmdRunner nextCmd,          noCompletion)
  , ("dap-step-in",                  dapCmdRunner stepInCmd,        noCompletion)
  ]
  where
    mkCmd :: (String, String -> InputT Gi.GHCi Bool, CompletionFunc Gi.GHCi)
          -> Gi.Command
    mkCmd (n,a,c) = Gi.Command {
                    Gi.cmdName = n
                  , Gi.cmdAction = a
                  , Gi.cmdHidden = False
                  , Gi.cmdCompletionFunc = c
                  }

    -- |
    --
    dapCmdRunner :: (String -> Gi.GHCi ())
                -> String
                -> InputT Gi.GHCi Bool
    dapCmdRunner cmd str = do
      
      lift $ cmd str

      return False


------------------------------------------------------------------------------------------------
--  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-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

      case filter (isPathMatch srcPath) modPaths of
        ((m, p):[]) -> do
          debugL $ "<dapSetBreakpointsCommand> " ++ p ++ " -> " ++ m
          return m

        _ -> throwError $ "loaded module can not find from path. <" ++ srcPath ++ "> " ++  show modPaths

    -- |
    --
    takeModPath :: ModSummary -> (String, FilePath)
    takeModPath ms = (G.moduleNameString (G.ms_mod_name ms), G.ms_hspp_file ms)

    -- |
    --
    isPathMatch :: FilePath -> (String, FilePath) -> Bool
    isPathMatch srcPath (_, p) = (nzPath srcPath) == (nzPath p)

    -- |
    --
    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

      case filter (isPathMatch startup) modPaths of
        ((m, p):[]) -> do
          debugL $ "<dapSetFuncBreakpointCommand> " ++ p ++ " -> " ++ m
          return m
        _ -> throwError $ "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

      return $ Right D.defaultStackTraceResponseBody {
          D.stackFramesStackTraceResponseBody = traceWithId
        , D.totalFramesStackTraceResponseBody = length traceWithId
        }

    -- |
    --
    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
    getStackFrameTitle r =  maybe "unknown" (G.moduleNameString  . G.moduleName . G.breakInfo_module) (G.resumeBreakInfo r)
                         ++ "."
                         ++ G.resumeDecl r

    -- |
    --
    genStackFrame :: G.SrcSpan -> String -> D.StackFrame
    genStackFrame (G.RealSrcSpan dat) name = D.defaultStackFrame {
        D.idStackFrame        = 0
      , D.nameStackFrame      = name
      , D.sourceStackFrame    = D.defaultSource {D.pathSource = (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 = "UnhelpfulSpan"}
      , 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 [TyThing]
    withMoveIdx moveIdx
      | 0 == moveIdx = G.getBindings
      | 0 < moveIdx = back moveIdx
      | otherwise = forward (negate moveIdx)
  
    -- |
    --
    getGlobalBindings :: Gi.GHCi [TyThing]
    getGlobalBindings = withSession $ \hsc_env -> do
      let ic = hsc_IC hsc_env
          gb = ic_rn_gbl_env ic
          es = globalRdrEnvElts gb
          ns = map gre_name es
      foldM withName [] $ reverse ns

    -- |
    --
    back :: Int -> Gi.GHCi [TyThing]
    back num = do
      clearBindingNames
      Gi.backCmd $ show num
      names <- getBindingNames
      foldM withName [] $ reverse names

    -- |
    --
    forward :: Int -> Gi.GHCi [TyThing]
    forward num = do
      clearBindingNames
      Gi.forwardCmd $ show num
      names <- getBindingNames
      foldM withName [] $ reverse names

    -- |
    --
    withName :: [TyThing] -> G.Name -> Gi.GHCi [TyThing]
    withName acc n = G.lookupName n >>= \case
      Just ty -> return (ty : acc)
      Nothing ->  do
        dflags <- getDynFlags
        errorL $ "variable not found. " ++ showSDoc dflags (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
  mapM (tyThing2Var isInspect) bindings

    
-- |
--
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 :: Term -> String -> Gi.GHCi [D.Variable]
    term2Vars (Term _ (Right dc) _ subTerms) str = do
      let labels = if 0 == length (dataConFieldLabels dc)
                     then map (\i->"_" ++ show i) [1..(length subTerms)]
                     else map (unpackFS . flLabel) (dataConFieldLabels dc)
      mapM (flip term2Var str) $ zip labels subTerms

    term2Vars (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 <- getDynFlags
      let tstr = showSDoc dflags (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 <- G.parseName nameStr
      var   <- names2Var nameStr names
      return $ Right D.defaultEvaluateResponseBody {
               D.resultEvaluateResponseBody = D.valueVariable var
             , 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
      True  -> isBreakthrough >>= \case
        False -> genStoppedEventBody
        True  -> continue

    -- |
    --
    hasBreaked :: Gi.GHCi Bool
    hasBreaked = getContinueExecResult >>= \case
      Just G.ExecBreak {G.breakInfo = Just _} -> return True
      _ -> return False

    -- |
    --
    isBreakthrough :: Gi.GHCi Bool
    isBreakthrough = G.getResumeContext >>= \case
      []    -> warnL "invalid resume state. resume not found."
            >> return False
      (r:_) -> pure (G.resumeBreakInfo r)
           >>= 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


------------------------------------------------------------------------------------------------
--  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