packages feed

agda-language-server-0.2.0: src/Agda/Convert.hs

module Agda.Convert where

import Render ( Block(..), Inlines, renderATop, Render(..) )

import Agda.IR (FromAgda (..))
import qualified Agda.IR as IR
import Agda.Interaction.Base
import Agda.Interaction.BasicOps as B
import Agda.Interaction.EmacsCommand (Lisp)
import Agda.Interaction.Highlighting.Common (chooseHighlightingMethod, toAtoms)
import Agda.Interaction.Highlighting.Precise (Aspects (..), DefinitionSite (..), HighlightingInfo, TokenBased (..))
import qualified Agda.Interaction.Highlighting.Range as Highlighting
import Agda.Interaction.InteractionTop (localStateCommandM)
import Agda.Interaction.Response as R
import Agda.Syntax.Abstract as A
import Agda.Syntax.Abstract.Pretty (prettyATop)
import Agda.Syntax.Common
import Agda.Syntax.Concrete as C
import Agda.Syntax.Internal (alwaysUnblock)
import Agda.Syntax.Position (HasRange (getRange), Range, noRange)
import Agda.Syntax.Scope.Base
import Agda.TypeChecking.Errors (getAllWarningsOfTCErr, prettyError)
import Agda.TypeChecking.Monad hiding (Function)
import Agda.TypeChecking.Pretty (prettyTCM)
import qualified Agda.TypeChecking.Pretty as TCP
import Agda.TypeChecking.Pretty.Warning (filterTCWarnings, prettyTCWarnings, prettyTCWarnings')
import Agda.TypeChecking.Warnings (WarningsAndNonFatalErrors (..))
import Agda.Utils.FileName (filePath)
import Agda.Utils.Function (applyWhen)
import Agda.Utils.IO.TempFile (writeToTempFile)
import Agda.Utils.Impossible (__IMPOSSIBLE__)
import Agda.Utils.Maybe (catMaybes)
import Agda.Utils.Null (empty)
import Agda.Utils.Pretty hiding (render)
import Agda.Utils.RangeMap ( IsBasicRangeMap(toList) ) 
import Agda.Utils.String (delimiter)
import Agda.Utils.Time (CPUTime)
import Agda.VersionCommit (versionWithCommitInfo)
import Control.Monad.State hiding (state)
import qualified Data.Aeson as JSON
import qualified Data.ByteString.Lazy.Char8 as BS8
import qualified Data.List as List
import qualified Data.Map as Map
import Data.String (IsString)
import qualified Render

responseAbbr :: IsString a => Response -> a
responseAbbr res = case res of
  Resp_HighlightingInfo {} -> "Resp_HighlightingInfo"
  Resp_Status {} -> "Resp_Status"
  Resp_JumpToError {} -> "Resp_JumpToError"
  Resp_InteractionPoints {} -> "Resp_InteractionPoints"
  Resp_GiveAction {} -> "Resp_GiveAction"
  Resp_MakeCase {} -> "Resp_MakeCase"
  Resp_SolveAll {} -> "Resp_SolveAll"
  Resp_DisplayInfo {} -> "Resp_DisplayInfo"
  Resp_RunningInfo {} -> "Resp_RunningInfo"
  Resp_ClearRunningInfo {} -> "Resp_ClearRunningInfo"
  Resp_ClearHighlighting {} -> "Resp_ClearHighlighting"
  Resp_DoneAborting {} -> "Resp_DoneAborting"
  Resp_DoneExiting {} -> "Resp_DoneExiting"

----------------------------------

serialize :: Lisp String -> String
serialize = show . pretty

fromResponse :: Response -> TCM IR.Response
fromResponse (Resp_HighlightingInfo info remove method modFile) =
  fromHighlightingInfo info remove method modFile
fromResponse (Resp_DisplayInfo info) = IR.ResponseDisplayInfo <$> fromDisplayInfo info
fromResponse (Resp_ClearHighlighting TokenBased) = return IR.ResponseClearHighlightingTokenBased
fromResponse (Resp_ClearHighlighting NotOnlyTokenBased) = return IR.ResponseClearHighlightingNotOnlyTokenBased
fromResponse Resp_DoneAborting = return IR.ResponseDoneAborting
fromResponse Resp_DoneExiting = return IR.ResponseDoneExiting
fromResponse Resp_ClearRunningInfo = return IR.ResponseClearRunningInfo
fromResponse (Resp_RunningInfo n s) = return $ IR.ResponseRunningInfo n s
fromResponse (Resp_Status s) = return $ IR.ResponseStatus (sChecked s) (sShowImplicitArguments s)
fromResponse (Resp_JumpToError f p) = return $ IR.ResponseJumpToError f (fromIntegral p)
fromResponse (Resp_InteractionPoints is) =
  return $ IR.ResponseInteractionPoints (fmap interactionId is)
fromResponse (Resp_GiveAction (InteractionId i) giveAction) =
  return $ IR.ResponseGiveAction i (fromAgda giveAction)
fromResponse (Resp_MakeCase _ Function pcs) = return $ IR.ResponseMakeCaseFunction pcs
fromResponse (Resp_MakeCase _ ExtendedLambda pcs) = return $ IR.ResponseMakeCaseExtendedLambda pcs
fromResponse (Resp_SolveAll ps) = return $ IR.ResponseSolveAll (fmap prn ps)
  where
    prn (InteractionId i, e) = (i, prettyShow e)

fromHighlightingInfo ::
  HighlightingInfo ->
  RemoveTokenBasedHighlighting ->
  HighlightingMethod ->
  ModuleToSource ->
  TCM IR.Response
fromHighlightingInfo h remove method modFile =
  case chooseHighlightingMethod h method of
    Direct -> return $ IR.ResponseHighlightingInfoDirect info
    Indirect -> IR.ResponseHighlightingInfoIndirect <$> indirect
  where
    fromAspects ::
      (Highlighting.Range, Aspects) ->
      IR.HighlightingInfo
    fromAspects (range, aspects) =
      IR.HighlightingInfo
        (Highlighting.from range)
        (Highlighting.to range)
        (toAtoms aspects)
        (tokenBased aspects == TokenBased)
        (note aspects)
        (defSite <$> definitionSite aspects)
      where
        defSite (DefinitionSite moduleName offset _ _) =
          (filePath (Map.findWithDefault __IMPOSSIBLE__ moduleName modFile), offset)

    infos :: [IR.HighlightingInfo]
    infos = fmap fromAspects (toList h)

    keepHighlighting :: Bool
    keepHighlighting =
      case remove of
        RemoveHighlighting -> False
        KeepHighlighting -> True

    info :: IR.HighlightingInfos
    info = IR.HighlightingInfos keepHighlighting infos

    indirect :: TCM FilePath
    indirect = liftIO $ writeToTempFile (BS8.unpack (JSON.encode info))

fromDisplayInfo :: DisplayInfo -> TCM IR.DisplayInfo
fromDisplayInfo info = case info of
  Info_CompilationOk ws -> do
    -- filter
    let filteredWarnings = filterTCWarnings (tcWarnings ws)
    let filteredErrors = filterTCWarnings (nonFatalErrors ws)
    -- serializes
    warnings <- mapM prettyTCM filteredWarnings
    errors <- mapM prettyTCM filteredErrors
    return $ IR.DisplayInfoCompilationOk (fmap show warnings) (fmap show errors)
  Info_Constraints s -> do
    -- constraints <- forM s $ \e -> do
    --   rendered <- renderTCM e
    --   let raw = show (pretty e)
    --   return $ Unlabeled rendered (Just raw)
    -- return $ IR.DisplayInfoGeneric "Constraints" constraints
    return $ IR.DisplayInfoGeneric "Constraints" [Unlabeled (Render.text $ show $ vcat $ fmap pretty s) Nothing Nothing]
  Info_AllGoalsWarnings (ims, hms) ws -> do
    -- visible metas (goals)
    goals <- mapM convertGoal ims
    -- hidden (unsolved) metas
    metas <- mapM convertHiddenMeta hms

    -- errors / warnings
    -- filter
    let filteredWarnings = filterTCWarnings (tcWarnings ws)
    let filteredErrors = filterTCWarnings (nonFatalErrors ws)
    -- serializes
    warnings <- mapM prettyTCM filteredWarnings
    errors <- mapM prettyTCM filteredErrors

    let isG = not (null goals && null metas)
    let isW = not $ null warnings
    let isE = not $ null errors
    let title =
          List.intercalate "," $
            catMaybes
              [ " Goals" <$ guard isG,
                " Errors" <$ guard isE,
                " Warnings" <$ guard isW,
                " Done" <$ guard (not (isG || isW || isE))
              ]

    return $ IR.DisplayInfoAllGoalsWarnings ("*All" ++ title ++ "*") goals metas (fmap show warnings) (fmap show errors)
    where
      convertHiddenMeta :: OutputConstraint A.Expr NamedMeta -> TCM Block
      convertHiddenMeta m = do
        let i = nmid $ namedMetaOf m
        -- output constrain
        meta <- withMetaId i $ renderATop m
        serialized <- show <$> withMetaId i (prettyATop m)
        -- range
        range <- getMetaRange i

        return $ Unlabeled meta (Just serialized) (Just range)

      convertGoal :: OutputConstraint A.Expr InteractionId -> TCM Block
      convertGoal i = do
        -- output constrain
        goal <-
          withInteractionId (outputFormId $ OutputForm noRange [] alwaysUnblock i) $
            renderATop i

        serialized <-
          withInteractionId (outputFormId $ OutputForm noRange [] alwaysUnblock i) $
            prettyATop i
        return $ Unlabeled goal (Just $ show serialized) Nothing
  Info_Auto s -> return $ IR.DisplayInfoAuto s
  Info_Error err -> do
    s <- showInfoError err
    return $ IR.DisplayInfoError s
  Info_Time s ->
    return $ IR.DisplayInfoTime (show (prettyTimed s))
  Info_NormalForm state cmode time expr -> do
    exprDoc <- evalStateT prettyExpr state
    let doc = maybe empty prettyTimed time $$ exprDoc
    return $ IR.DisplayInfoNormalForm (show doc)
    where
      prettyExpr =
        localStateCommandM $
          lift $
            B.atTopLevel $
              allowNonTerminatingReductions $
                (if computeIgnoreAbstract cmode then ignoreAbstractMode else inConcreteMode) $
                  B.showComputed cmode expr
  Info_InferredType state time expr -> do
    renderedExpr <-
      flip evalStateT state $
        localStateCommandM $
          lift $
            B.atTopLevel $
              Render.renderA expr
    let rendered = case time of
          Nothing -> renderedExpr
          -- TODO: handle this newline
          Just t -> "Time:" Render.<+> Render.render t Render.<+> "\n" Render.<+> renderedExpr
    exprDoc <-
      flip evalStateT state $
        localStateCommandM $
          lift $
            B.atTopLevel $
              TCP.prettyA expr
    let raw = show $ maybe empty prettyTimed time $$ exprDoc
    return $ IR.DisplayInfoGeneric "Inferred Type" [Unlabeled rendered (Just raw) Nothing]
  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 $ fmap pretty modules,
            "Names",
            nest 2 $ align 10 typeDocs
          ]
    return $ IR.DisplayInfoGeneric "Module contents" [Unlabeled (Render.text $ show doc) Nothing Nothing]
  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)
    return $ IR.DisplayInfoGeneric "Search About" [Unlabeled (Render.text $ show doc) Nothing Nothing]
  Info_WhyInScope s cwd v xs ms -> do
    doc <- explainWhyInScope s cwd v xs ms
    return $ IR.DisplayInfoGeneric "Scope Info" [Unlabeled (Render.text $ show doc) Nothing Nothing]
  Info_Context ii ctx -> do
    doc <- localTCState (prettyResponseContexts ii False ctx)
    return $ IR.DisplayInfoGeneric "Context" [Unlabeled (Render.text $ show doc) Nothing Nothing]
  Info_Intro_NotFound ->
    return $ IR.DisplayInfoGeneric "Intro" [Unlabeled (Render.text "No introduction forms found.") Nothing Nothing]
  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)
            ]
    return $ IR.DisplayInfoGeneric "Intro" [Unlabeled (Render.text $ show doc) Nothing Nothing]
  Info_Version ->
    return $ IR.DisplayInfoGeneric "Agda Version" [Unlabeled (Render.text $ "Agda version " ++ versionWithCommitInfo) Nothing Nothing]
  Info_GoalSpecific ii kind -> lispifyGoalSpecificDisplayInfo ii kind

lispifyGoalSpecificDisplayInfo :: InteractionId -> GoalDisplayInfo -> TCM IR.DisplayInfo
lispifyGoalSpecificDisplayInfo ii kind = localTCState $
  B.withInteractionId ii $
    case kind of
      Goal_HelperFunction helperType -> do
        doc <- inTopContext $ prettyATop helperType
        return $ IR.DisplayInfoGeneric "Helper function" [Unlabeled (Render.text $ show doc ++ "\n") Nothing Nothing]
      Goal_NormalForm cmode expr -> do
        doc <- showComputed cmode expr
        return $ IR.DisplayInfoGeneric "Normal Form" [Unlabeled (Render.text $ show doc) Nothing Nothing]
      Goal_GoalType norm aux resCtxs boundaries constraints -> do
        goalSect <- do
          (rendered, raw) <- prettyTypeOfMeta norm ii
          return [Labeled rendered (Just raw) Nothing "Goal" "special"]

        auxSect <- case aux of
          GoalOnly -> return []
          GoalAndHave expr -> do
            rendered <- renderATop expr
            raw <- show <$> prettyATop expr
            return [Labeled rendered (Just raw) Nothing "Have" "special"]
          GoalAndElaboration term -> do
            let rendered = render term
            raw <- show <$> TCP.prettyTCM term
            return [Labeled rendered (Just raw) Nothing "Elaborates to" "special"]
        let boundarySect =
              if null boundaries
                then []
                else
                  Header "Boundary" :
                  fmap (\boundary -> Unlabeled (render boundary) (Just $ show $ pretty boundary) Nothing) boundaries
        contextSect <- reverse . concat <$> mapM (renderResponseContext ii) resCtxs
        let constraintSect =
                if null constraints
                  then []
                  else
                    Header "Constraints" :
                    fmap (\constraint -> Unlabeled (render constraint) (Just $ show $ pretty constraint) Nothing) constraints

        return $
          IR.DisplayInfoGeneric "Goal type etc" $ goalSect ++ auxSect ++ boundarySect ++ contextSect ++ constraintSect
      Goal_CurrentGoal norm -> do
        (rendered, raw) <- prettyTypeOfMeta norm ii
        return $ IR.DisplayInfoCurrentGoal (Unlabeled rendered (Just raw) Nothing)
      Goal_InferredType expr -> do
        rendered <- renderATop expr
        raw <- show <$> prettyATop expr
        return $ IR.DisplayInfoInferredType (Unlabeled rendered (Just raw) Nothing)

-- -- | Format responses of DisplayInfo
-- formatPrim :: Bool -> [Block] -> String -> TCM IR.DisplayInfo
-- formatPrim _copy items header = return $ IR.DisplayInfoGeneric header items

-- -- | Format responses of DisplayInfo ("agda2-info-action")
-- format :: [Block] -> String -> TCM IR.DisplayInfo
-- format = formatPrim False

-- -- | Format responses of DisplayInfo ("agda2-info-action-copy")
-- formatAndCopy :: [Block] -> String -> TCM IR.DisplayInfo
-- formatAndCopy = formatPrim True

--------------------------------------------------------------------------------

-- | Serializing Info_Error
showInfoError :: Info_Error -> TCM String
showInfoError (Info_GenericError err) = do
  e <- prettyError err
  w <- prettyTCWarnings' =<< getAllWarningsOfTCErr err

  let errorMsg =
        if null w
          then e
          else delimiter "Error" ++ "\n" ++ e
  let warningMsg =
        List.intercalate "\n" $
          delimiter "Warning(s)" :
          filter (not . null) w
  return $
    if null w
      then errorMsg
      else errorMsg ++ "\n\n" ++ warningMsg
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

explainWhyInScope ::
  FilePath ->
  String ->
  Maybe LocalVar ->
  [AbstractName] ->
  [AbstractModule] ->
  TCM Doc
explainWhyInScope s _ Nothing [] [] = TCP.text (s ++ " is not in scope.")
explainWhyInScope s _ v xs ms =
  TCP.vcat
    [ TCP.text (s ++ " is in scope as"),
      TCP.nest 2 $ TCP.vcat [variable v xs, modules ms]
    ]
  where
    -- variable :: Maybe _ -> [_] -> TCM Doc
    variable Nothing vs = names vs
    variable (Just x) vs
      | null vs = asVar
      | otherwise =
        TCP.vcat
          [ TCP.sep [asVar, TCP.nest 2 $ shadowing x],
            TCP.nest 2 $ names vs
          ]
      where
        asVar :: TCM Doc
        asVar =
          "* a variable bound at" TCP.<+> TCP.prettyTCM (nameBindingSite $ localVar x)
        shadowing :: LocalVar -> TCM Doc
        shadowing (LocalVar _ _ []) = "shadowing"
        shadowing _ = "in conflict with"
    names = TCP.vcat . fmap pName
    modules = TCP.vcat . fmap pMod

    pKind = \case
      AxiomName                -> "postulate"
      ConName                  -> "constructor"
      CoConName                -> "coinductive constructor"
      DataName                 -> "data type"
      DisallowedGeneralizeName -> "generalizable variable from let open"
      FldName                  -> "record field"
      FunName                  -> "defined name"
      GeneralizeName           -> "generalizable variable"
      MacroName                -> "macro name"
      PatternSynName           -> "pattern synonym"
      PrimName                 -> "primitive function"
      QuotableName             -> "quotable name"
      -- previously DefName:
      RecName                  -> "record type"
      OtherDefName             -> "defined name"

    pName :: AbstractName -> TCM Doc
    pName a =
      TCP.sep
        [ "* a"
            TCP.<+> pKind (anameKind a)
            TCP.<+> TCP.text (prettyShow $ anameName a),
          TCP.nest 2 "brought into scope by"
        ]
        TCP.$$ TCP.nest 2 (pWhy (nameBindingSite $ qnameName $ anameName a) (anameLineage a))
    pMod :: AbstractModule -> TCM Doc
    pMod a =
      TCP.sep
        [ "* a module" TCP.<+> TCP.text (prettyShow $ amodName a),
          TCP.nest 2 "brought into scope by"
        ]
        TCP.$$ TCP.nest 2 (pWhy (nameBindingSite $ qnameName $ mnameToQName $ amodName a) (amodLineage a))

    pWhy :: Range -> WhyInScope -> TCM Doc
    pWhy r Defined = "- its definition at" TCP.<+> TCP.prettyTCM r
    pWhy r (Opened (C.QName x) w) | isNoName x = pWhy r w
    pWhy r (Opened m w) =
      "- the opening of"
        TCP.<+> TCP.prettyTCM m
        TCP.<+> "at"
        TCP.<+> TCP.prettyTCM (getRange m)
        TCP.$$ pWhy r w
    pWhy r (Applied m w) =
      "- the application of"
        TCP.<+> TCP.prettyTCM m
        TCP.<+> "at"
        TCP.<+> TCP.prettyTCM (getRange m)
        TCP.$$ pWhy r w

-- | Pretty-prints the context of the given meta-variable.
prettyResponseContexts ::
  -- | Context of this meta-variable.
  InteractionId ->
  -- | Print the elements in reverse order?
  Bool ->
  [ResponseContextEntry] ->
  TCM Doc
prettyResponseContexts ii rev ctxs = do
  rows <- mapM (prettyResponseContext ii) ctxs
  return $ align 10 $ concat $ applyWhen rev reverse rows

-- | Pretty-prints the context of the given meta-variable.
prettyResponseContext ::
  -- | Context of this meta-variable.
  InteractionId ->
  ResponseContextEntry ->
  TCM [(String, Doc)]
prettyResponseContext ii (ResponseContextEntry n x (Arg ai expr) letv nis) = withInteractionId ii $ do
  modality <- asksTC getModality
  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 by goal is non-erased.
              ["erased" | not $ getQuantity ai `moreQuantity` getQuantity modality],
              -- Print irrelevant if hypothesis is strictly less relevant than goal.
              ["irrelevant" | not $ getRelevance ai `moreRelevant` getRelevance modality],
              -- Print instance if variable is considered by instance search
              ["instance" | isInstance ai]
            ]
    ty <- prettyATop expr

    letv' <- case letv of
      Nothing -> return []
      Just val -> do
        val' <- prettyATop val
        return [(prettyShow x, "=" <+> val')]

    return $
      (attribute ++ prettyCtxName, ":" <+> ty <+> parenSep extras) : letv'
  where
    parenSep :: [Doc] -> Doc
    parenSep docs
      | null docs = empty
      | otherwise = (" " <+>) $ parens $ fsep $ punctuate comma docs

-- | Render the context of the given meta-variable.
renderResponseContext ::
  -- | Context of this meta-variable.
  InteractionId ->
  ResponseContextEntry ->
  TCM [Block]
renderResponseContext ii (ResponseContextEntry n x (Arg ai expr) letv nis) = withInteractionId ii $ do
  modality <- asksTC getModality
  do
    let
        rawCtxName :: String
        rawCtxName
          | n == x = prettyShow x
          | isInScope n == InScope = prettyShow n ++ " = " ++ prettyShow x
          | otherwise = prettyShow x

        renderedCtxName :: Inlines
        renderedCtxName
          | n == x = render x
          | isInScope n == InScope = render n Render.<+> "=" Render.<+> render x
          | otherwise = render x

        -- Some attributes are useful to report whenever they are not
        -- in the default state.
        rawAttribute :: String
        rawAttribute = c ++ if null c then "" else " "
          where
            c = prettyShow (getCohesion ai)

        renderedAttribute :: Inlines
        renderedAttribute = c <> if null (show c) then "" else " "
          where
            c = render (getCohesion ai)

        extras :: IsString a => [a]
        extras =
          concat
            [ ["not in scope" | isInScope nis == C.NotInScope],
              -- Print erased if hypothesis is erased by goal is non-erased.
              ["erased" | not $ getQuantity ai `moreQuantity` getQuantity modality],
              -- Print irrelevant if hypothesis is strictly less relevant than goal.
              ["irrelevant" | not $ getRelevance ai `moreRelevant` getRelevance modality],
              -- Print instance if variable is considered by instance search
              ["instance" | isInstance ai]
            ]

        extras2 :: [Inlines]
        extras2 =
          concat
            [ ["not in scope" | isInScope nis == C.NotInScope],
              -- Print erased if hypothesis is erased by goal is non-erased.
              ["erased" | not $ getQuantity ai `moreQuantity` getQuantity modality],
              -- Print irrelevant if hypothesis is strictly less relevant than goal.
              ["irrelevant" | not $ getRelevance ai `moreRelevant` getRelevance modality],
              -- Print instance if variable is considered by instance search
              ["instance" | isInstance ai]
            ]

    -- raw
    rawExpr <- prettyATop expr
    let rawType = show $ align 10 [(rawAttribute ++ rawCtxName, ":" <+> rawExpr <+> parenSep extras)]
    -- rendered  
    renderedExpr <- renderATop expr
    let renderedType = (renderedCtxName <> renderedAttribute) Render.<+> ":" Render.<+> renderedExpr Render.<+> parenSep2 extras2
      -- (Render.fsep $ Render.punctuate "," extras)

    -- result 
    let typeItem = Unlabeled renderedType (Just rawType) Nothing

    valueItem <- case letv of
      Nothing -> return []
      Just val -> do
        valText <- renderATop val
        valString <- prettyATop val
        let renderedValue = Render.render x Render.<+> "=" Render.<+> valText
        let rawValue = show $ align 10 [(prettyShow x, "=" <+> valString)]
        return
          [ Unlabeled renderedValue (Just rawValue) Nothing
          ]

    return $ typeItem : valueItem
  where
    parenSep :: [Doc] -> Doc
    parenSep docs
      | null docs = empty
      | otherwise = (" " <+>) $ parens $ fsep $ punctuate comma docs

    parenSep2 :: [Inlines] -> Inlines
    parenSep2 docs
      | null docs = mempty
      | otherwise = (" " Render.<+>) $ Render.parens $ Render.fsep $ Render.punctuate "," docs


-- | Pretty-prints the type of the meta-variable.
prettyTypeOfMeta :: Rewrite -> InteractionId -> TCM (Inlines, String)
prettyTypeOfMeta norm ii = do
  form <- B.typeOfMeta norm ii
  case form of
    OfType _ e -> do
      rendered <- renderATop e
      raw <- show <$> prettyATop e
      return (rendered, raw)
    _ -> do
      rendered <- renderATop form
      raw <- show <$> prettyATop form
      return (rendered, raw)

-- | Prefix prettified CPUTime with "Time:"
prettyTimed :: CPUTime -> Doc
prettyTimed time = "Time:" <+> pretty time