Agda-2.7.0: src/full/Agda/Interaction/EmacsTop.hs
{-# OPTIONS_GHC -Wunused-imports #-}
module Agda.Interaction.EmacsTop
( mimicGHCi
, namedMetaOf
, showGoals
, showInfoError
, explainWhyInScope
, prettyResponseContext
, prettyTypeOfMeta
) where
import Control.Monad
import Control.Monad.IO.Class ( MonadIO(..) )
import Control.Monad.State ( evalStateT )
import Control.Monad.Trans ( lift )
import qualified Data.List as List
import Agda.Syntax.Common
import Agda.Syntax.Common.Pretty
import Agda.Syntax.Abstract.Pretty (prettyATop)
import Agda.Syntax.Concrete as C
import Agda.TypeChecking.Errors ( explainWhyInScope, getAllWarningsOfTCErr, renderError, verbalize )
import qualified Agda.TypeChecking.Pretty as TCP
import Agda.TypeChecking.Pretty (prettyTCM)
import Agda.TypeChecking.Pretty.Warning (prettyTCWarnings)
import Agda.TypeChecking.Monad
import Agda.Interaction.AgdaTop
import Agda.Interaction.Base
import Agda.Interaction.BasicOps as B
import Agda.Interaction.Response as R
import Agda.Interaction.EmacsCommand hiding (putResponse)
import Agda.Interaction.Highlighting.Emacs
import Agda.Interaction.Highlighting.Precise (TokenBased(..))
import Agda.Interaction.InteractionTop (localStateCommandM)
import Agda.Utils.Function (applyWhen)
import Agda.Utils.Null (empty)
import Agda.Utils.Maybe
import Agda.Utils.String
import Agda.Utils.Time (CPUTime)
import Agda.VersionCommit
----------------------------------
-- | 'mimicGHCi' is a fake ghci interpreter for the Emacs frontend
-- and for interaction tests.
--
-- 'mimicGHCi' reads the Emacs frontend commands from stdin,
-- interprets them and print the result into stdout.
mimicGHCi :: TCM () -> TCM ()
mimicGHCi = repl (liftIO . mapM_ (putStrLn . prettyShow) <=< lispifyResponse) "Agda2> "
-- | Convert Response to an elisp value for the interactive emacs frontend.
lispifyResponse :: Response -> TCM [Lisp String]
lispifyResponse (Resp_HighlightingInfo info remove method modFile) =
(:[]) <$> liftIO (lispifyHighlightingInfo info remove method modFile)
lispifyResponse (Resp_DisplayInfo info) = lispifyDisplayInfo info
lispifyResponse (Resp_ClearHighlighting tokenBased) =
return [ L $ A "agda2-highlight-clear" :
case tokenBased of
NotOnlyTokenBased -> []
TokenBased ->
[ Q (lispifyTokenBased tokenBased) ]
]
lispifyResponse Resp_DoneAborting = return [ L [ A "agda2-abort-done" ] ]
lispifyResponse Resp_DoneExiting = return [ L [ A "agda2-exit-done" ] ]
lispifyResponse Resp_ClearRunningInfo = return [ clearRunningInfo ]
lispifyResponse (Resp_RunningInfo n s)
| n <= 1 = return [ displayRunningInfo s ]
| otherwise = return [ L [A "agda2-verbose", A (quote s)] ]
lispifyResponse (Resp_Status s)
= return [ L [ A "agda2-status-action"
, A (quote $ List.intercalate "," $ catMaybes [checked, showImpl, showIrr])
]
]
where
checked = boolToMaybe (sChecked s) "Checked"
showImpl = boolToMaybe (sShowImplicitArguments s) "ShowImplicit"
showIrr = boolToMaybe (sShowIrrelevantArguments s) "ShowIrrelevant"
lispifyResponse (Resp_JumpToError f p) = return
[ lastTag 3 $
L [ A "agda2-maybe-goto", Q $ L [A (quote f), A ".", A (show p)] ]
]
lispifyResponse (Resp_InteractionPoints is) = return
[ lastTag 1 $
L [A "agda2-goals-action", Q $ L $ map showNumIId is]
]
lispifyResponse (Resp_GiveAction ii s)
= return [ L [ A "agda2-give-action", showNumIId ii, A s' ] ]
where
s' = case s of
Give_String str -> quote str
Give_Paren -> "'paren"
Give_NoParen -> "'no-paren"
lispifyResponse (Resp_MakeCase ii variant pcs) = return
[ lastTag 2 $ L [ A cmd, Q $ L $ map (A . quote) pcs ] ]
where
cmd = case variant of
R.Function -> "agda2-make-case-action"
R.ExtendedLambda -> "agda2-make-case-action-extendlam"
lispifyResponse (Resp_SolveAll ps) = return
[ lastTag 2 $
L [ A "agda2-solveAll-action", Q . L $ concatMap prn ps ]
]
where
prn (ii,e)= [showNumIId ii, A $ quote $ prettyShow e]
-- TODO: For now, I piggy-back on the Resp_SolveAll implementation
lispifyResponse (Resp_Mimer ii msol) = return $ case msol of
Nothing ->
[ lastTag 1 $ -- TODO: What is this lastTag used for?
L [ A "agda2-info-action", A $ quote "*Mimer*", A $ quote "No solution found" ]
]
Just str ->
[ lastTag 1 $ -- TODO: What is this lastTag used for?
L [ A "agda2-solve-action", showNumIId ii, A $ quote str ]
]
lispifyDisplayInfo :: DisplayInfo -> TCM [Lisp String]
lispifyDisplayInfo info = case info of
Info_CompilationOk backend ws -> do
warnings <- prettyTCWarnings (tcWarnings ws)
errors <- prettyTCWarnings (nonFatalErrors ws)
let
msg = concat
[ "The module was successfully compiled with backend "
, prettyShow backend
, ".\n"
]
-- abusing the goals field since we ignore the title
(body, _) = formatWarningsAndErrors msg warnings errors
format body "*Compilation result*"
Info_Constraints s -> do
doc <- TCP.vcat $ map prettyTCM s
format (render doc) "*Constraints*"
Info_AllGoalsWarnings ms ws -> do
goals <- showGoals ms
warnings <- prettyTCWarnings (tcWarnings ws)
errors <- prettyTCWarnings (nonFatalErrors ws)
let (body, title) = formatWarningsAndErrors goals warnings errors
format body ("*All" ++ title ++ "*")
Info_Auto s -> format s "*Auto*"
Info_Error err -> do
s <- showInfoError err
format s "*Error*"
Info_Time s -> format (render $ prettyTimed s) "*Time*"
Info_NormalForm state cmode time expr -> do
exprDoc <- evalStateT prettyExpr state
let doc = maybe empty prettyTimed time $$ exprDoc
lbl | cmode == HeadCompute = "*Head Normal Form*"
| otherwise = "*Normal Form*"
format (render doc) lbl
where
prettyExpr = localStateCommandM
$ lift
$ B.atTopLevel
$ allowNonTerminatingReductions
$ (if computeIgnoreAbstract cmode then ignoreAbstractMode else inConcreteMode)
$ (B.showComputed cmode)
$ expr
Info_InferredType state time expr -> do
exprDoc <- evalStateT prettyExpr state
let doc = maybe empty prettyTimed time $$ exprDoc
format (render doc) "*Inferred Type*"
where
prettyExpr = localStateCommandM
$ lift
$ B.atTopLevel
$ TCP.prettyA
$ expr
Info_ModuleContents modules tel types -> do
doc <- localTCState $ do
typeDocs <- addContext tel $ forM types $ \ (x, t) -> do
doc <- prettyTCM t
return (prettyShow x, ":" <+> doc)
return $ vcat
[ "Modules"
, nest 2 $ vcat $ map pretty modules
, "Names"
, nest 2 $ align 10 typeDocs
]
format (render doc) "*Module contents*"
Info_SearchAbout hits names -> do
hitDocs <- forM hits $ \ (x, t) -> do
doc <- prettyTCM t
return (prettyShow x, ":" <+> doc)
let doc = "Definitions about" <+>
text (List.intercalate ", " $ words names) $$ nest 2 (align 10 hitDocs)
format (render doc) "*Search About*"
Info_WhyInScope why -> do
doc <- explainWhyInScope why
format (render doc) "*Scope Info*"
Info_Context ii ctx -> do
doc <- localTCState (prettyResponseContext ii False ctx)
format (render doc) "*Context*"
Info_Intro_NotFound -> format "No introduction forms found." "*Intro*"
Info_Intro_ConstructorUnknown ss -> do
let doc = sep [ "Don't know which constructor to introduce of"
, let mkOr [] = []
mkOr [x, y] = [text x <+> "or" <+> text y]
mkOr (x:xs) = text x : mkOr xs
in nest 2 $ fsep $ punctuate comma (mkOr ss)
]
format (render doc) "*Intro*"
Info_Version -> format ("Agda version " ++ versionWithCommitInfo) "*Agda Version*"
Info_GoalSpecific ii kind -> lispifyGoalSpecificDisplayInfo ii kind
lispifyGoalSpecificDisplayInfo :: InteractionId -> GoalDisplayInfo -> TCM [Lisp String]
lispifyGoalSpecificDisplayInfo ii kind = localTCState $ withInteractionId ii $
case kind of
Goal_HelperFunction helperType -> do
doc <- inTopContext $ prettyATop helperType
return [ L [ A "agda2-info-action-and-copy"
, A $ quote "*Helper function*"
, A $ quote (render doc ++ "\n")
, A "nil"
]
]
Goal_NormalForm cmode expr -> do
doc <- showComputed cmode expr
format (render doc) "*Normal Form*" -- show?
Goal_GoalType norm aux ctx bndry constraints -> do
ctxDoc <- prettyResponseContext ii True ctx
goalDoc <- prettyTypeOfMeta norm ii
let boundaryDoc hd bndry
| null bndry = []
| otherwise = [ text $ delimiter hd
, vcat $ map pretty bndry
]
auxDoc <- case aux of
GoalOnly -> return empty
GoalAndHave expr bndry -> do
doc <- prettyATop expr
return $ ("Have:" <+> doc) $$ vcat (boundaryDoc ("Boundary (actual)") bndry)
GoalAndElaboration term -> do
doc <- TCP.prettyTCM term
return $ "Elaborates to:" <+> doc
let constraintsDoc
| null constraints = []
| otherwise =
[ TCP.text $ delimiter "Constraints"
, TCP.vcat $ map prettyTCM constraints
]
doc <- TCP.vcat $
[ "Goal:" TCP.<+> return goalDoc
, return (vcat (boundaryDoc "Boundary (wanted)" bndry))
, return auxDoc
, TCP.text (replicate 60 '\x2014')
, return ctxDoc
] ++ constraintsDoc
format (render doc) "*Goal type etc.*"
Goal_CurrentGoal norm -> do
doc <- prettyTypeOfMeta norm ii
format (render doc) "*Current Goal*"
Goal_InferredType expr -> do
doc <- prettyATop expr
format (render doc) "*Inferred Type*"
-- | Format responses of DisplayInfo
format :: String -> String -> TCM [Lisp String]
format content bufname = return [ display_info' False bufname content ]
-- | Adds a \"last\" tag to a response.
lastTag :: Integer -> Lisp String -> Lisp String
lastTag n r = Cons (Cons (A "last") (A $ show n)) r
-- | Show an iteraction point identifier as an elisp expression.
showNumIId :: InteractionId -> Lisp String
showNumIId = A . show . interactionId
--------------------------------------------------------------------------------
-- | Given strings of goals, warnings and errors, return a pair of the
-- body and the title for the info buffer
formatWarningsAndErrors :: String -> String -> String -> (String, String)
formatWarningsAndErrors g w e = (body, title)
where
isG = not $ null g
isW = not $ null w
isE = not $ null e
title = List.intercalate "," $ catMaybes
[ " Goals" <$ guard isG
, " Errors" <$ guard isE
, " Warnings" <$ guard isW
, " Done" <$ guard (not (isG || isW || isE))
]
body = List.intercalate "\n" $ catMaybes
[ g <$ guard isG
, delimiter "Error" <$ guard (isE && (isG || isW))
, e <$ guard isE
, delimiter "Warnings" <$ guard (isW && (isG || isE))
, w <$ guard isW
]
-- | Serializing Info_Error
showInfoError :: Info_Error -> TCM String
showInfoError (Info_GenericError err) = do
e <- renderError err
w <- prettyTCWarnings =<< getAllWarningsOfTCErr err
let (body, _) = formatWarningsAndErrors "" w e
return body
showInfoError (Info_CompilationError warnings) = do
s <- prettyTCWarnings warnings
return $ unlines
[ "You need to fix the following errors before you can compile the module:"
, ""
, s
]
showInfoError (Info_HighlightingParseError ii) =
return $ "Highlighting failed to parse expression in " ++ show ii
showInfoError (Info_HighlightingScopeCheckError ii) =
return $ "Highlighting failed to scope check expression in " ++ show ii
-- | Pretty-prints the context of the given meta-variable.
prettyResponseContext
:: InteractionId -- ^ Context of this meta-variable.
-> Bool -- ^ Print the elements in reverse order?
-> [ResponseContextEntry]
-> TCM Doc
prettyResponseContext ii rev ctx = withInteractionId ii $ do
mod <- currentModality
align 10 . concat . applyWhen rev reverse <$> do
forM ctx $ \ (ResponseContextEntry n x (Arg ai expr) letv nis) -> do
let
prettyCtxName :: String
prettyCtxName
| n == x = prettyShow x
| isInScope n == InScope = prettyShow n ++ " = " ++ prettyShow x
| otherwise = prettyShow x
-- Some attributes are useful to report whenever they are not
-- in the default state.
attribute :: String
attribute = c ++ if null c then "" else " "
where c = prettyShow (getCohesion ai)
extras :: [Doc]
extras = concat $
[ [ "not in scope" | isInScope nis == C.NotInScope ]
-- Print "erased" if hypothesis is erased but goal is non-erased.
, [ "erased" | not $ getQuantity ai `moreQuantity` getQuantity mod ]
-- Print relevance of hypothesis relative to relevance of the goal. (Issue #6706.)
, [ text $ verbalize r
| let r = getRelevance mod `inverseComposeRelevance` getRelevance ai
, r /= Relevant ]
-- Print "instance" if variable is considered by instance search.
, [ "instance" | isInstance ai ]
]
ty <- prettyATop expr
maybeVal <- traverse prettyATop letv
return $
(attribute ++ prettyCtxName, ":" <+> ty <+> (parenSep extras)) :
[ (prettyShow x, "=" <+> val) | val <- maybeToList maybeVal ]
where
parenSep :: [Doc] -> Doc
parenSep docs
| null docs = empty
| otherwise = (" " <+>) $ parens $ fsep $ punctuate comma docs
-- | Pretty-prints the type of the meta-variable.
prettyTypeOfMeta :: Rewrite -> InteractionId -> TCM Doc
prettyTypeOfMeta norm ii = do
form <- B.typeOfMeta norm ii
case form of
OfType _ e -> prettyATop e
_ -> prettyATop form
-- | Prefix prettified CPUTime with "Time:"
prettyTimed :: CPUTime -> Doc
prettyTimed time = "Time:" <+> pretty time