{-# LANGUAGE MultiParamTypeClasses #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Haskell.Debug.Adapter.State.Init.Launch where
import Control.Monad.IO.Class
import Control.Monad.Except
import Control.Monad.State
import Control.Concurrent
import Control.Lens
import System.Directory
import System.FilePath
import Text.Parsec
import qualified Text.Read as R
import qualified System.Log.Logger as L
import qualified Data.ByteString.Lazy as LB
import qualified Data.List as L
import qualified Data.Version as V
import qualified Haskell.DAP as DAP
import qualified Haskell.Debug.Adapter.Utility as U
import qualified Haskell.Debug.Adapter.State.Utility as SU
import Haskell.Debug.Adapter.Type
import Haskell.Debug.Adapter.Constant
import qualified Haskell.Debug.Adapter.Logger as L
import qualified Haskell.Debug.Adapter.GHCi as P
instance StateActivityIF InitStateData DAP.LaunchRequest where
action _ (LaunchRequest req) = do
liftIO $ L.debugM _LOG_APP $ "InitState LaunchRequest called. " ++ show req
app req
app :: DAP.LaunchRequest -> AppContext (Maybe StateTransit)
app req = flip catchError errHdl $ do
setUpConfig req
setUpLogger req
createTasksJsonFile
U.sendStdoutEvent "Configuration read.\n"
U.sendConsoleEvent "Starting GHCi.\n"
U.sendErrorEvent "Wait for a moment.\n\n"
startGHCi req
setPrompt
launchCmd req
setMainArgs
loadStarupFile
modify $ \s-> s{_launchReqSeqAppStores = DAP.seqLaunchRequest req}
initSeq <- U.getIncreasedResponseSequence
U.addResponse $ InitializedEvent $ DAP.defaultInitializedEvent {DAP.seqInitializedEvent = initSeq}
return $ Just Init_GHCiRun
where
errHdl :: String -> AppContext (Maybe StateTransit)
errHdl msg = do
liftIO $ L.errorM _LOG_APP msg
resSeq <- U.getIncreasedResponseSequence
let res = DAP.defaultLaunchResponse {
DAP.seqLaunchResponse = resSeq
, DAP.request_seqLaunchResponse = DAP.seqLaunchRequest req
, DAP.successLaunchResponse = False
, DAP.messageLaunchResponse = msg
}
U.addResponse $ LaunchResponse res
return Nothing
setUpConfig :: DAP.LaunchRequest -> AppContext ()
setUpConfig req = do
let args = DAP.argumentsLaunchRequest req
appStores <- get
let wsMVar = appStores^.workspaceAppStores
ws = DAP.workspaceLaunchRequestArguments args
_ <- liftIO $ takeMVar wsMVar
liftIO $ putMVar wsMVar ws
liftIO $ L.debugM _LOG_APP $ "workspace is " ++ ws
let logPRMVar = appStores^.logPriorityAppStores
logPR <- getLogPriority $ DAP.logLevelLaunchRequestArguments args
_ <- liftIO $ takeMVar logPRMVar
liftIO $ putMVar logPRMVar logPR
put appStores {
_startupAppStores = U.replace [_SEP_WIN] [_SEP_UNIX] (DAP.startupLaunchRequestArguments args)
, _startupFuncAppStores = maybe "" (\s->if null (U.strip s) then "" else (U.strip s)) (DAP.startupFuncLaunchRequestArguments args)
, _startupArgsAppStores = maybe "" (id) (DAP.startupArgsLaunchRequestArguments args)
, _stopOnEntryAppStores = DAP.stopOnEntryLaunchRequestArguments args
, _mainArgsAppStores = maybe "" (id) (DAP.mainArgsLaunchRequestArguments args)
}
where
getLogPriority logPRStr = case R.readEither logPRStr of
Right lv -> return lv
Left err -> do
U.sendErrorEvent $ "log priority is invalid. WARNING set. [" ++ err ++ "]\n"
return L.WARNING
setUpLogger :: DAP.LaunchRequest -> AppContext ()
setUpLogger req = do
let args = DAP.argumentsLaunchRequest req
ctx <- get
logPR <- liftIO $ readMVar $ ctx^.logPriorityAppStores
liftIO $ L.setUpLogger (DAP.logFileLaunchRequestArguments args) logPR
createTasksJsonFile :: AppContext ()
createTasksJsonFile = do
ctx <- get
wsPath <- liftIO $ readMVar $ ctx^.workspaceAppStores
let jsonDir = wsPath </> ".vscode"
jsonFile = jsonDir </> "tasks.json"
catchError
(checkDir jsonDir >> checkFile jsonFile >> createFile jsonFile)
errHdl
where
errHdl msg = liftIO $ L.debugM _LOG_APP msg
checkDir jsonDir = liftIO (doesDirectoryExist jsonDir) >>= \case
False -> throwError $ "setting folder not found. skip saveing tasks.json. DIR:" ++ jsonDir
True -> return ()
checkFile jsonFile = liftIO (doesFileExist jsonFile) >>= \case
True -> throwError $ "tasks.json file exists. " ++ jsonFile
False -> return ()
createFile jsonFile = do
liftIO $ U.saveFileBSL jsonFile _TASKS_JSON_FILE_CONTENTS
U.sendConsoleEvent $ "create tasks.json file. " ++ jsonFile ++ "\n"
startGHCi :: DAP.LaunchRequest -> AppContext ()
startGHCi req = do
let args = DAP.argumentsLaunchRequest req
initPmpt = maybe _GHCI_PROMPT id (DAP.ghciInitialPromptLaunchRequestArguments args)
envs = DAP.ghciEnvLaunchRequestArguments args
cmdStr = DAP.ghciCmdLaunchRequestArguments args
cmdList = filter (not.null) $ U.split " " cmdStr
cmd = head cmdList
U.debugEV _LOG_APP $ show cmdList
opts <- addWithGHC (tail cmdList)
appStores <- get
cwd <- U.liftIOE $ readMVar $ appStores^.workspaceAppStores
U.liftIOE $ L.debugM _LOG_APP $ "ghci initial prompt [" ++ initPmpt ++ "]."
U.sendConsoleEventLF $ "CWD: " ++ cwd
U.sendConsoleEventLF $ "CMD: " ++ L.intercalate " " (cmd : opts)
U.sendConsoleEventLF ""
P.startGHCi cmd opts cwd envs
res <- P.expectInitPmpt initPmpt
updateGHCiVersion res
where
updateGHCiVersion acc = case parse verParser "getGHCiVersion" (unlines acc) of
Right v -> do
U.debugEV _LOG_APP $ "GHCi version is " ++ V.showVersion v
updateGHCiVersion' v
Left e -> do
U.sendConsoleEventLF $ "Can not parse ghci version. [" ++ show e ++ "]. Assumes " ++ V.showVersion _BASE_GHCI_VERSION ++ "."
updateGHCiVersion' _BASE_GHCI_VERSION
verParser = do
_ <- manyTill anyChar (try (string "GHCi, version "))
v1 <- manyTill digit (char '.')
v2 <- manyTill digit (char '.')
v3 <- manyTill digit (char ':')
return $ V.makeVersion [read v1, read v2, read v3]
updateGHCiVersion' v = do
mver <- view ghciVerAppStores <$> get
U.liftIOE $ putMVar mver v
setPrompt :: AppContext ()
setPrompt = do
p <- view ghciPmptAppStores <$> get
let pmpt = _DAP_CMD_END2 ++ "\\n" ++ p
cmd = ":set prompt \""++pmpt++"\""
cmd2 = ":set prompt-cont \""++pmpt++"\""
P.command cmd
P.expectPmpt
P.command cmd2
P.expectPmpt
return ()
launchCmd :: DAP.LaunchRequest -> AppContext ()
launchCmd req = do
let args = DAP.argumentsLaunchRequest req
dap = ":dap-launch "
cmd = dap ++ U.showDAP args
dbg = dap ++ show args
P.command cmd
U.debugEV _LOG_APP dbg
P.expectPmpt
return ()
setMainArgs :: AppContext ()
setMainArgs = view mainArgsAppStores <$> get >>= \case
[] -> return ()
args -> do
let cmd = ":set args "++args
P.command cmd
P.expectPmpt
return ()
loadStarupFile :: AppContext ()
loadStarupFile = do
file <- view startupAppStores <$> get
SU.loadHsFile file
let cmd = ":dap-context-modules "
P.command cmd
P.expectPmpt
return ()
addWithGHC :: [String] -> AppContext [String]
addWithGHC [] = return []
addWithGHC cmds
| L.elem "--with-ghc=haskell-dap" cmds = do
U.infoEV _LOG_APP "can not use haskell-dap. deleting \"--with-ghc=haskell-dap\""
addWithGHC $ L.delete "--with-ghc=haskell-dap" cmds
| withGhciExists cmds = return cmds
| "ghci" == head cmds = do
U.infoEV _LOG_APP "\"--with-ghc\" option not found. adding \"--with-ghc=ghci-dap\""
return $ head cmds:"--with-ghc=ghci-dap":tail cmds
| otherwise = return cmds
where
withGhciExists [] = False
withGhciExists (x:xs)
| L.isPrefixOf "--with-ghc=" x = True
| otherwise = withGhciExists xs
_TASKS_JSON_FILE_CONTENTS :: LB.ByteString
_TASKS_JSON_FILE_CONTENTS = U.str2lbs $ U.join "\n" $
[
"{"
, " // atuomatically created by phoityne-vscode"
, " "
, " \"version\": \"2.0.0\","
, " \"presentation\": {"
, " \"reveal\": \"always\","
, " \"panel\": \"new\""
, " },"
, " \"tasks\": ["
, " {"
, " \"group\": {"
, " \"kind\": \"build\","
, " \"isDefault\": true"
, " },"
, " \"label\": \"stack build\","
, " \"type\": \"shell\","
, " \"command\": \"echo START_STACK_BUILD && cd ${workspaceRoot} && stack build && echo END_STACK_BUILD \""
, " },"
, " { "
, " \"group\": \"build\","
, " \"type\": \"shell\","
, " \"label\": \"stack clean & build\","
, " \"command\": \"echo START_STACK_CLEAN_AND_BUILD && cd ${workspaceRoot} && stack clean && stack build && echo END_STACK_CLEAN_AND_BUILD \""
, " },"
, " { "
, " \"group\": {"
, " \"kind\": \"test\","
, " \"isDefault\": true"
, " },"
, " \"type\": \"shell\","
, " \"label\": \"stack test\","
, " \"command\": \"echo START_STACK_TEST && cd ${workspaceRoot} && stack test && echo END_STACK_TEST \""
, " },"
, " { "
, " \"isBackground\": true,"
, " \"type\": \"shell\","
, " \"label\": \"stack watch\","
, " \"command\": \"echo START_STACK_WATCH && cd ${workspaceRoot} && stack build --test --no-run-tests --file-watch && echo END_STACK_WATCH \""
, " }"
, " ]"
, "}"
]