{-# LANGUAGE MultiParamTypeClasses #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Haskell.Debug.Adapter.State.Utility where
import qualified System.Log.Logger as L
import qualified Text.Read as R
import qualified Data.List as L
import Control.Monad.Except
import Control.Concurrent (threadDelay)
import qualified Haskell.DAP as DAP
import Haskell.Debug.Adapter.Type
import Haskell.Debug.Adapter.Constant
import qualified Haskell.Debug.Adapter.Utility as U
import qualified Haskell.Debug.Adapter.GHCi as P
unsupported :: String -> AppContext WrapStateRequest
unsupported reqStr = do
let msg = "InitState does not support this request. " ++ reqStr
throwError msg
setBreakpointsRequest :: DAP.SetBreakpointsRequest -> AppContext (Maybe StateTransit)
setBreakpointsRequest req = flip catchError errHdl $ do
let args = DAP.argumentsSetBreakpointsRequest req
dap = ":dap-set-breakpoints "
cmd = dap ++ U.showDAP args
dbg = dap ++ show args
P.cmdAndOut cmd
U.debugEV _LOG_APP dbg
P.expectH $ P.funcCallBk lineCallBk
return Nothing
where
lineCallBk :: Bool -> String -> AppContext ()
lineCallBk True s = U.sendStdoutEvent s
lineCallBk False s
| L.isPrefixOf _DAP_HEADER s = do
U.debugEV _LOG_APP s
dapHdl $ drop (length _DAP_HEADER) s
| otherwise = U.sendStdoutEventLF s
dapHdl :: String -> AppContext ()
dapHdl str = case R.readEither str of
Left err -> errHdl err >> return ()
Right (Left err) -> errHdl err >> return ()
Right (Right body) -> do
resSeq <- U.getIncreasedResponseSequence
let res = DAP.defaultSetBreakpointsResponse {
DAP.seqSetBreakpointsResponse = resSeq
, DAP.request_seqSetBreakpointsResponse = DAP.seqSetBreakpointsRequest req
, DAP.successSetBreakpointsResponse = True
, DAP.bodySetBreakpointsResponse = body
}
U.addResponse $ SetBreakpointsResponse res
errHdl :: String -> AppContext (Maybe StateTransit)
errHdl msg = do
U.errorEV _LOG_APP msg
resSeq <- U.getIncreasedResponseSequence
let res = DAP.defaultSetBreakpointsResponse {
DAP.seqSetBreakpointsResponse = resSeq
, DAP.request_seqSetBreakpointsResponse = DAP.seqSetBreakpointsRequest req
, DAP.successSetBreakpointsResponse = False
, DAP.messageSetBreakpointsResponse = msg
}
U.addResponse $ SetBreakpointsResponse res
return Nothing
setExceptionBreakpointsRequest :: DAP.SetExceptionBreakpointsRequest -> AppContext (Maybe StateTransit)
setExceptionBreakpointsRequest req = do
let args = DAP.argumentsSetExceptionBreakpointsRequest req
filters = DAP.filtersSetExceptionBreakpointsRequestArguments args
mapM_ go $ getOptions filters
resSeq <- U.getIncreasedResponseSequence
let res = DAP.defaultSetExceptionBreakpointsResponse {
DAP.seqSetExceptionBreakpointsResponse = resSeq
, DAP.request_seqSetExceptionBreakpointsResponse = DAP.seqSetExceptionBreakpointsRequest req
, DAP.successSetExceptionBreakpointsResponse = True
}
U.addResponse $ SetExceptionBreakpointsResponse res
return Nothing
where
getOptions filters
| null filters = ["-fno-break-on-exception", "-fno-break-on-error"]
| filters == ["break-on-error"] = ["-fno-break-on-exception", "-fbreak-on-error"]
| filters == ["break-on-exception"] = ["-fbreak-on-exception", "-fno-break-on-error"]
| otherwise = ["-fbreak-on-exception", "-fbreak-on-error" ]
go opt = do
let cmd = ":set " ++ opt
P.cmdAndOut cmd
P.expectH $ P.stdoutCallBk
setFunctionBreakpointsRequest :: DAP.SetFunctionBreakpointsRequest -> AppContext (Maybe StateTransit)
setFunctionBreakpointsRequest req = flip catchError errHdl $ do
let args = DAP.argumentsSetFunctionBreakpointsRequest req
dap = ":dap-set-function-breakpoints "
cmd = dap ++ U.showDAP args
dbg = dap ++ show args
P.cmdAndOut cmd
U.debugEV _LOG_APP dbg
P.expectH $ P.funcCallBk lineCallBk
return Nothing
where
lineCallBk :: Bool -> String -> AppContext ()
lineCallBk True s = U.sendStdoutEvent s
lineCallBk False s
| L.isPrefixOf _DAP_HEADER s = do
U.debugEV _LOG_APP s
dapHdl $ drop (length _DAP_HEADER) s
| otherwise = U.sendStdoutEventLF s
dapHdl :: String -> AppContext ()
dapHdl str = case R.readEither str of
Left err -> errHdl err >> return ()
Right (Left err) -> errHdl err >> return ()
Right (Right body) -> do
resSeq <- U.getIncreasedResponseSequence
let res = DAP.defaultSetFunctionBreakpointsResponse {
DAP.seqSetFunctionBreakpointsResponse = resSeq
, DAP.request_seqSetFunctionBreakpointsResponse = DAP.seqSetFunctionBreakpointsRequest req
, DAP.successSetFunctionBreakpointsResponse = True
, DAP.bodySetFunctionBreakpointsResponse = body
}
U.addResponse $ SetFunctionBreakpointsResponse res
errHdl :: String -> AppContext (Maybe StateTransit)
errHdl msg = do
U.errorEV _LOG_APP msg
resSeq <- U.getIncreasedResponseSequence
let res = DAP.defaultSetFunctionBreakpointsResponse {
DAP.seqSetFunctionBreakpointsResponse = resSeq
, DAP.request_seqSetFunctionBreakpointsResponse = DAP.seqSetFunctionBreakpointsRequest req
, DAP.successSetFunctionBreakpointsResponse = False
, DAP.messageSetFunctionBreakpointsResponse = msg
}
U.addResponse $ SetFunctionBreakpointsResponse res
return Nothing
terminateGHCi :: AppContext ()
terminateGHCi = do
let cmd = ":quit"
P.cmdAndOut cmd
P.expectEOF $ P.stdoutCallBk
return ()
evaluateRequest :: DAP.EvaluateRequest -> AppContext (Maybe StateTransit)
evaluateRequest req = do
let args = DAP.argumentsEvaluateRequest req
dap = ":dap-evaluate "
cmd = dap ++ U.showDAP args
dbg = dap ++ show args
P.cmdAndOut cmd
U.debugEV _LOG_APP dbg
P.expectH $ P.funcCallBk lineCallBk
return Nothing
where
lineCallBk :: Bool -> String -> AppContext ()
lineCallBk True s = U.sendStdoutEvent s
lineCallBk False s
| L.isPrefixOf _DAP_HEADER s = do
U.debugEV _LOG_APP s
dapHdl $ drop (length _DAP_HEADER) s
| otherwise = do
liftIO $ L.errorM _LOG_APP s
U.sendStdoutEventLF s
dapHdl :: String -> AppContext ()
dapHdl str = case R.readEither str of
Left err -> errHdl $ err ++ " : " ++ str
Right (Left err) -> errHdl err
Right (Right body) -> do
resSeq <- U.getIncreasedResponseSequence
let res = DAP.defaultEvaluateResponse {
DAP.seqEvaluateResponse = resSeq
, DAP.request_seqEvaluateResponse = DAP.seqEvaluateRequest req
, DAP.successEvaluateResponse = True
, DAP.bodyEvaluateResponse = body
}
U.addResponse $ EvaluateResponse res
errHdl :: String -> AppContext ()
errHdl msg = do
U.sendErrorEventLF msg
resSeq <- U.getIncreasedResponseSequence
let res = DAP.defaultEvaluateResponse {
DAP.seqEvaluateResponse = resSeq
, DAP.request_seqEvaluateResponse = DAP.seqEvaluateRequest req
, DAP.successEvaluateResponse = False
, DAP.messageEvaluateResponse = msg
}
U.addResponse $ EvaluateResponse res
completionsRequest :: DAP.CompletionsRequest -> AppContext (Maybe StateTransit)
completionsRequest req = flip catchError errHdl $ do
let args = DAP.argumentsCompletionsRequest req
key = DAP.textCompletionsRequestArguments args
size = "0-50"
cmd = ":complete repl " ++ size ++ " \"" ++ key ++ "\""
P.cmdAndOut cmd
outs <- P.expectH P.stdoutCallBk
resSeq <- U.getIncreasedResponseSequence
let items = createItems outs
body = DAP.defaultCompletionsResponseBody {
DAP.targetsCompletionsResponseBody = items
}
res = DAP.defaultCompletionsResponse {
DAP.seqCompletionsResponse = resSeq
, DAP.request_seqCompletionsResponse = DAP.seqCompletionsRequest req
, DAP.successCompletionsResponse = True
, DAP.bodyCompletionsResponse = body
}
U.addResponse $ CompletionsResponse res
return Nothing
where
errHdl :: String -> AppContext (Maybe StateTransit)
errHdl msg = do
liftIO $ L.errorM _LOG_APP msg
resSeq <- U.getIncreasedResponseSequence
let res = DAP.defaultCompletionsResponse {
DAP.seqCompletionsResponse = resSeq
, DAP.request_seqCompletionsResponse = DAP.seqCompletionsRequest req
, DAP.successCompletionsResponse = False
, DAP.messageCompletionsResponse = msg
}
U.addResponse $ CompletionsResponse res
return Nothing
createItems :: [String] -> [DAP.CompletionsItem]
createItems = map (createItem . normalize) . extracCompleteList
createItem :: String -> DAP.CompletionsItem
createItem (':':xs) = DAP.CompletionsItem xs
createItem xs = DAP.CompletionsItem xs
normalize :: String -> String
normalize xs
| 2 < length xs = tail . init $ xs
| otherwise = xs
extracCompleteList :: [String] -> [String]
extracCompleteList [] = []
extracCompleteList (_:[]) = []
extracCompleteList (_:_:[]) = []
extracCompleteList xs = tail . init $ xs
loadHsFile :: FilePath -> AppContext ()
loadHsFile file = do
let cmd = ":load "++ file
P.cmdAndOut cmd
P.expectH P.stdoutCallBk
return ()
terminateRequest :: DAP.TerminateRequest -> AppContext ()
terminateRequest req = do
terminateGHCi
liftIO $ threadDelay _1_SEC
resSeq <- U.getIncreasedResponseSequence
let res = DAP.defaultTerminateResponse {
DAP.seqTerminateResponse = resSeq
, DAP.request_seqTerminateResponse = DAP.seqTerminateRequest req
, DAP.successTerminateResponse = True
}
U.addResponse $ TerminateResponse res
U.sendTerminatedEvent
U.sendExitedEvent
internalTerminateRequest :: AppContext ()
internalTerminateRequest = do
terminateGHCi
liftIO $ threadDelay _1_SEC
U.sendTerminatedEvent
U.sendExitedEvent