diff --git a/src/Borceux.lagda.md b/src/Borceux.lagda.md index 1ee1ca97b..ce229d281 100644 --- a/src/Borceux.lagda.md +++ b/src/Borceux.lagda.md @@ -1116,3 +1116,5 @@ _ = const-nato * Exercise 8.4.6: * (⇒) `dependent-product→lcc`{.Agda} * (⇐) `lcc→dependent-product`{.Agda} + +[[marked graph homomorphism]] diff --git a/support/shake/app/Definitions.hs b/support/shake/app/Definitions.hs index 5f2c8a5a0..27f3bc1e7 100644 --- a/support/shake/app/Definitions.hs +++ b/support/shake/app/Definitions.hs @@ -113,7 +113,7 @@ newtype LinkTargetQ = LinkTargetQ Text type instance RuleResult GlossaryQ = Glossary type instance RuleResult ModuleGlossaryQ = Glossary -type instance RuleResult LinkTargetQ = Text +type instance RuleResult LinkTargetQ = (Text, Text) addDefinition :: Mangled -> Definition -> Glossary -> Glossary addDefinition _ Definition{definitionCopy = True} ge = ge @@ -154,7 +154,7 @@ glossaryRules = do _ <- addOracle \(LinkTargetQ target) -> do glo <- getEntries <$> askOracle GlossaryQ case Map.lookup (mangleLink target) glo of - Just def -> pure $ definitionTarget def + Just def -> pure (definitionAnchor def, definitionTarget def) Nothing -> error $ "Unknown wiki-link target: " ++ Text.unpack target @@ -203,10 +203,10 @@ isWikiLink (Link attr contents (url, title)) | "wikilink" == title = pure $ WikiLink url contents attr isWikiLink _ = Nothing -getWikiLinkUrl :: Text -> Action Text +getWikiLinkUrl :: Text -> Action (Text, Text) getWikiLinkUrl = askOracle . LinkTargetQ getWikiLink :: WikiLink -> Action Inline -getWikiLink (WikiLink dest contents attr) = do - url <- getWikiLinkUrl dest - pure $ Link attr contents (url, "") +getWikiLink (WikiLink dest contents (id, cls, kv)) = do + (anchor, url) <- getWikiLinkUrl dest + pure $ Link (id, cls, ("data-target", anchor):kv) contents (url, "") diff --git a/support/shake/app/HTML/Backend.hs b/support/shake/app/HTML/Backend.hs index 1ab3edca1..9c8756868 100644 --- a/support/shake/app/HTML/Backend.hs +++ b/support/shake/app/HTML/Backend.hs @@ -1,7 +1,9 @@ -- Copyright (c) 2005-2021 remains with the Agda authors. See /support/shake/LICENSE.agda -- | Backend for generating highlighted, hyperlinked HTML from Agda sources. -{-# LANGUAGE FlexibleContexts, BlockArguments, LambdaCase, DerivingStrategies, OverloadedStrings #-} +{-# LANGUAGE FlexibleContexts, BlockArguments, LambdaCase, DerivingStrategies, OverloadedStrings, NondecreasingIndentation #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE TypeFamilies #-} module HTML.Backend ( htmlBackend , compileOneModule @@ -14,56 +16,77 @@ import HTML.Base import Prelude hiding ((!!), concatMap) import Control.Monad.Identity import Control.Monad.Except +import Control.Monad.Reader import qualified Data.HashMap.Strict as Hm import qualified Data.Map.Strict as Map import qualified Data.HashSet as HashSet import qualified Data.Text as Text +import qualified Data.Set as Set + import Data.HashMap.Strict (HashMap) import Data.List.NonEmpty (NonEmpty((:|))) import Data.Map.Strict (Map) import Data.Foldable import Data.HashSet (HashSet) import Data.Aeson +import Data.Maybe import Data.IORef import Data.Text (Text) import Data.List +import Data.Set (Set) -import Agda.Compiler.Backend +import Agda.Compiler.Backend hiding (topLevelModuleName) import Agda.Compiler.Common +import Control.DeepSeq + import qualified Agda.Syntax.Concrete.Generic as Con import qualified Agda.Syntax.Internal.Generic as I import qualified Agda.Syntax.Abstract.Views as A +import qualified Agda.Syntax.Common.Aspect as Asp +import qualified Agda.Syntax.Scope.Base as Scope +import qualified Agda.Syntax.Concrete as Con import qualified Agda.Syntax.Internal as I import qualified Agda.Syntax.Abstract as A -import qualified Agda.Syntax.Concrete as Con -import qualified Agda.Syntax.Scope.Base as Scope +import qualified Agda.Syntax.Concrete as C import Agda.Syntax.Translation.InternalToAbstract ( Reify(reify) ) -import Agda.Syntax.Translation.AbstractToConcrete (abstractToConcrete_) +import Agda.Syntax.Translation.AbstractToConcrete (abstractToConcrete_, abstractToConcreteCtx) import Agda.Syntax.TopLevelModuleName +import Agda.Syntax.Abstract.Pretty (prettyA, prettyATop) import Agda.Syntax.Abstract.Views import Agda.Syntax.Common.Pretty +import Agda.Syntax.Scope.Monad (modifyCurrentScope, getCurrentModule, freshConcreteName) import Agda.Syntax.Abstract hiding (Type) import Agda.Syntax.Position import Agda.Syntax.Internal (Type, domName) +import Agda.Syntax.Fixity (Precedence(TopCtx)) import Agda.Syntax.Common import Agda.Syntax.Info +import qualified Agda.TypeChecking.Monad.Base as I +import qualified Agda.TypeChecking.Pretty as P +import Agda.TypeChecking.Substitute +import Agda.TypeChecking.Telescope +import Agda.TypeChecking.Records (isRecord) import Agda.TypeChecking.Reduce (instantiateFull, reduceDefCopyTCM, normalise) +import Agda.TypeChecking.Level (reallyUnLevelView) +import qualified Agda.Utils.Maybe.Strict as S import Agda.Utils.FileName +import Agda.Utils.Lens +import Agda.Utils.Size import System.FilePath hiding (normalise) -import Debug.Trace -import qualified Agda.Utils.Maybe.Strict as S -import Agda.Syntax.Scope.Monad (modifyCurrentScope) -import Agda.Syntax.Abstract.Pretty (prettyA) -import Agda.TypeChecking.Records (isRecord) -import Data.Set (Set) -import qualified Data.Set as Set -import Agda.TypeChecking.Level (reallyUnLevelView) + +import Text.Show.Pretty (ppShow) + +import HTML.Render +import Debug.Trace (traceShow) +import Agda.Syntax.Internal.Names (namesIn') +import Agda.Interaction.Response (Response_boot(Resp_RunningInfo)) +import Agda.Interaction.Options.Lenses (LensVerbosity(setVerbosity)) data HtmlCompileEnv = HtmlCompileEnv { htmlCompileEnvOpts :: HtmlOptions @@ -163,12 +186,13 @@ compileDefHtml env _ _ _ compileDefHtml env _menv _isMain def = withCurrentModule (qnameModule (defName def)) $ case definitionAnchor env def of Just mn -> do - ty <- typeToText def + (tooltip, ty) <- typeToText def let ident = Identifier - { idAnchor = mn - , idIdent = Text.pack (render (pretty (qnameName (defName def)))) - , idType = ty + { idAnchor = mn + , idIdent = Text.pack (render (pretty (qnameName (defName def)))) + , idType = ty + , idTooltip = tooltip } pure (Just (mn, ident)) Nothing -> do @@ -196,6 +220,7 @@ postModuleHtml env menv _isMain _modName _defs = do . htmlCompileEnvOpts . htmlModEnvCompileEnv $ menv + htmlSrc <- srcFileOfInterface (htmlModEnvName menv) <$> curIF runLogHtmlWithMonadDebug $ generatePage htmlSrc pure $ HtmlModule $ foldr ins mempty _defs @@ -233,23 +258,6 @@ compileOneModule _pn opts types iface = do compDef env menv def = setCurrentRange (defName def) $ compileDefHtml env menv NotMain def -killQual :: Con.Expr -> Con.Expr -killQual = Con.mapExpr (wrap . forget) where - work :: Con.QName -> Con.QName - work (Con.Qual _ x) = work x - work x = x - - forget :: Con.Expr -> Con.Expr - forget (Con.KnownOpApp r w qual names args) = Con.OpApp w qual names args - forget (Con.KnownIdent r nm) = Con.Ident nm - forget x = x - - wrap :: Con.Expr -> Con.Expr - wrap (Con.Ident v) = Con.Ident (work v) - wrap (Con.KnownIdent v w) = Con.KnownIdent v (work w) - wrap (Con.OpApp v qual names args) = Con.OpApp v (work qual) names args - wrap x = x - getClass :: QName -> TCM (Set QName) getClass q = isRecord q >>= \case Just RecordData{ _recFields = f } -> pure $! Set.fromList (map I.unDom f) @@ -264,43 +272,96 @@ usedInstances = I.foldTerm \case Nothing -> pure mempty _ -> pure mempty -typeToText :: Definition -> TCM Text +typeToText :: Definition -> TCM (Text, Text) typeToText d = do ui <- usedInstances (I.unEl (defType d)) + fv <- getDefFreeVars (defName d) + ty <- locallyReduceDefs (OnlyReduceDefs ui) $ normalise (defType d) - expr <- runNoCopy (reify ty) - fmap (Text.pack . render . pretty . killQual) . - abstractToConcrete_ . removeImpls $ expr + + topm <- topLevelModuleName (qnameModule (defName d)) + + let + n k = Asp.Name (Just k) False + asp = \case + I.Axiom{} -> n Asp.Postulate + DataOrRecSig{} -> n Asp.Datatype + GeneralizableVar{} -> n Asp.Generalizable + AbstractDefn d -> asp d + d@Function{} + | isProperProjection d -> n Asp.Field + | otherwise -> n Asp.Function + Datatype{} -> n Asp.Datatype + Record{} -> n Asp.Record{} + Constructor{conSrcCon = c} -> + n $ Asp.Constructor (I.conInductive c) + I.Primitive{} -> n Asp.Primitive + PrimitiveSort{} -> Asp.PrimitiveType + + aspect = asp (theDef d) + + a = Asp.Aspects + { Asp.aspect = Just aspect + , Asp.otherAspects = mempty + , Asp.note = "" + , Asp.definitionSite = toDefinitionSite topm (nameBindingSite (qnameName (defName d))) + , Asp.tokenBased = Asp.TokenBased + } + + expr <- removeImpls <$> reify ty + + here <- currentTopLevelModule + tooltip <- fmap renderToHtml $ P.vcat + [ annotate a <$> P.pretty (qnameName (defName d)) + , P.nest 2 (P.colon P.<+> prettyATop expr) + ] + plain <- Text.pack . render <$> prettyATop expr + pure (tooltip, plain) + +toDefinitionSite :: TopLevelModuleName -> Range -> Maybe Asp.DefinitionSite +toDefinitionSite topm r = do + p <- fmap (fromIntegral . posPos) . rStart $ r + pure $ Asp.DefinitionSite + { Asp.defSiteModule = topm + , Asp.defSitePos = fromIntegral p + , Asp.defSiteHere = True + , Asp.defSiteAnchor = Nothing + } + +killQual :: Con.Expr -> Con.Expr +killQual = Con.mapExpr wrap where + work :: Con.QName -> Con.QName + work (Con.Qual _ x) = work x + work x = x + + wrap :: Con.Expr -> Con.Expr + wrap (Con.Ident v) = Con.Ident (work v) + wrap (Con.KnownIdent v w) = Con.KnownIdent v (work w) + wrap (Con.KnownOpApp v a b c d) = Con.KnownOpApp v a (work b) c d + wrap (Con.OpApp v qual names args) = Con.OpApp v (work qual) names args + wrap x = x removeImpls :: A.Expr -> A.Expr removeImpls (A.Pi _ (x :| xs) e) = - makePi (map (A.mapExpr removeImpls) $ filter ((/= Hidden) . getHiding) (x:xs)) (removeImpls e) + let + fixup :: TypedBinding -> TypedBinding + fixup q@(TBind rng inf as _) + | Hidden <- getHiding q = TBind rng inf as underscore + | otherwise = q + in makePi (map (A.mapExpr removeImpls) $ map fixup (x:xs)) (removeImpls e) removeImpls (A.Fun span arg ret) = A.Fun span (removeImpls <$> arg) (removeImpls ret) removeImpls e = e makePi :: [A.TypedBinding] -> A.Expr -> A.Expr -makePi [] = id -makePi (b:bs) = A.Pi exprNoRange (b :| bs) - -newtype NoCopy a = NoCopy { runNoCopy :: TCM a } - deriving newtype (Functor, Applicative, Monad, MonadFail) - deriving newtype - ( HasBuiltins, MonadDebug, MonadReduce, HasOptions, MonadTCEnv - , ReadTCState, MonadAddContext, MonadInteractionPoints - , MonadFresh NameId - ) - -noCopyOp :: Definition -> Definition -noCopyOp def | defCopy def, Con.numHoles (defName def) >= 1 = traceShow (pretty (defName def)) $ def{defCopy = False} -noCopyOp def = def - -instance HasConstInfo NoCopy where - getConstInfo = NoCopy . fmap noCopyOp . getConstInfo - getConstInfo' = NoCopy . fmap (fmap noCopyOp) . getConstInfo' - getRewriteRulesFor = NoCopy . getRewriteRulesFor - -instance PureTCM NoCopy +makePi [] e = e +makePi (b:bs) (A.Pi _ bs' e') = makePi (b:bs ++ toList bs') e' +makePi (b:bs) e = A.Pi exprNoRange (b `mergeTBinds` bs) e + +mergeTBinds :: A.TypedBinding -> [A.TypedBinding] -> NonEmpty A.TypedBinding +mergeTBinds (A.TBind r i as Underscore{}) (A.TBind _ _ as' Underscore{}:bs) = + mergeTBinds (A.TBind r i (as <> as') underscore) bs +mergeTBinds x xs = x :| xs definitionAnchor :: HtmlCompileEnv -> Definition -> Maybe Text definitionAnchor _ def | defCopy def = Nothing diff --git a/support/shake/app/HTML/Base.hs b/support/shake/app/HTML/Base.hs index 408fbb3ba..d2f1162b0 100644 --- a/support/shake/app/HTML/Base.hs +++ b/support/shake/app/HTML/Base.hs @@ -2,7 +2,7 @@ -- | Function for generating highlighted, hyperlinked HTML from Agda -- sources. -{-# LANGUAGE FlexibleInstances, DeriveGeneric, OverloadedStrings, DeriveAnyClass #-} +{-# LANGUAGE FlexibleInstances, DeriveGeneric, OverloadedStrings, DeriveAnyClass, BlockArguments #-} module HTML.Base ( HtmlOptions(..) , defaultHtmlOptions @@ -18,23 +18,25 @@ module HTML.Base import Prelude hiding ((!!), concatMap) +import Control.Monad.Trans.Reader ( ReaderT(runReaderT), ask ) +import Control.Monad.Trans ( MonadIO(..), lift ) import Control.DeepSeq import Control.Monad -import Control.Monad.Trans ( MonadIO(..), lift ) -import Control.Monad.Trans.Reader ( ReaderT(runReaderT), ask ) +import qualified Data.HashMap.Strict as Hm +import qualified Data.Text.Lazy as T +import qualified Data.HashSet as Hs +import qualified Data.IntMap as IntMap +import qualified Data.List as List +import qualified Data.Text as Ts +import Data.HashMap.Strict (HashMap) +import Data.List.Split (splitWhen) +import Data.Text.Lazy (Text) import Data.Function ( on ) import Data.Foldable (toList, concatMap) +import Data.HashSet (HashSet) import Data.Maybe import Data.Aeson -import qualified Data.IntMap as IntMap -import qualified Data.List as List -import Data.List.Split (splitWhen) -import Data.Text.Lazy (Text) -import qualified Data.Text.Lazy as T -import qualified Data.Text as Ts -import Data.HashMap.Strict (HashMap) -import qualified Data.HashMap.Strict as Hm import GHC.Generics (Generic) @@ -74,6 +76,8 @@ import qualified Agda.Utils.IO.UTF8 as UTF8 import Agda.Utils.Impossible +import HTML.Render + -- | Determine how to highlight the file data HtmlHighlight = HighlightAll | HighlightCode | HighlightAuto @@ -81,15 +85,6 @@ data HtmlHighlight = HighlightAll | HighlightCode | HighlightAuto instance NFData HtmlHighlight --- | Data about an identifier -data Identifier = Identifier - { idIdent :: Ts.Text - , idAnchor :: Ts.Text - , idType :: Ts.Text - } - deriving (Eq, Show, Ord, Generic, ToJSON, FromJSON, NFData) - - highlightOnlyCode :: HtmlHighlight -> FileType -> Bool highlightOnlyCode HighlightAll _ = False highlightOnlyCode HighlightCode _ = True @@ -168,35 +163,50 @@ renderSourceFile :: HashMap Ts.Text Identifier -> HtmlOptions -> HtmlInputSourceFile - -> Text -renderSourceFile types opts = renderSourcePage where - htmlHighlight = htmlOptHighlight opts - renderSourcePage (HtmlInputSourceFile moduleName fileType sourceCode hinfo) = - page opts onlyCode moduleName pageContents - where - tokens = tokenStream sourceCode hinfo - onlyCode = highlightOnlyCode htmlHighlight fileType - pageContents = code types onlyCode fileType tokens + -> (Text, [Ts.Text]) +renderSourceFile types opts (HtmlInputSourceFile moduleName fileType sourceCode hinfo) = + let + htmlHighlight = htmlOptHighlight opts + + tokens = tokenStream sourceCode hinfo + onlyCode = highlightOnlyCode htmlHighlight fileType + + used :: Int -> [TokenInfo] -> (HashMap Ts.Text (Int, Identifier), [Ts.Text]) + used !n ((_, _, a):ts) + | Just ds <- definitionSite a + , Just id <- Hm.lookup (Ts.pack (definitionSiteToAnchor ds)) types + , (map, list) <- used (n + 1) ts + = (Hm.insert (idAnchor id) (n, id) map, idTooltip id:list) + | otherwise = used n ts + used _ [] = mempty + + (order, usedts) = used 0 tokens + pageContents = code order onlyCode fileType moduleName tokens + in + rnf order `seq` + ( page opts onlyCode moduleName pageContents + , usedts + ) defaultPageGen :: (MonadIO m, MonadLogHtml m) => HashMap Ts.Text Identifier -> HtmlOptions - -> HtmlInputSourceFile -> m () + -> HtmlInputSourceFile + -> m () defaultPageGen types opts srcFile@(HtmlInputSourceFile moduleName ft _ _) = do let - ext = highlightedFileExt (htmlOptHighlight opts) ft - target = htmlOptDir opts > modToFile moduleName ext - typeTarget = htmlOptDir opts > modToFile moduleName "json" - html = renderSourceFile types opts srcFile + ext = highlightedFileExt (htmlOptHighlight opts) ft + target = htmlOptDir opts > modToFile moduleName ext + typeTarget = htmlOptDir opts > modToFile moduleName "json" + ourTypes = htmlOptDir opts > modToFile moduleName "used" + (html, used) = renderSourceFile types opts srcFile logHtml $ render $ "Generating HTML for" <+> pretty moduleName writeRenderedHtml html target - liftIO $ encodeFile typeTarget types - --- | Converts module names to the corresponding HTML file names. -modToFile :: TopLevelModuleName -> String -> FilePath -modToFile m ext = Network.URI.Encode.encode $ render (pretty m) <.> ext + liftIO do + encodeFile typeTarget types + encodeFile ourTypes used -- | Generates a highlighted, hyperlinked version of the given module. @@ -208,11 +218,6 @@ writeRenderedHtml writeRenderedHtml html target = liftIO $ UTF8.writeTextToFile target html --- | Attach multiple Attributes - -(!!) :: Html -> [Attribute] -> Html -h !! as = h ! mconcat as - -- | Constructs the web page, including headers. page @@ -271,12 +276,13 @@ tokenStream contents info = -- | Constructs the HTML displaying the code. code - :: HashMap Ts.Text Identifier + :: HashMap Ts.Text (Int, Identifier) -> Bool -- ^ Whether to generate non-code contents as-is -> FileType -- ^ Source file type + -> TopLevelModuleName -> [TokenInfo] -> Html -code types _onlyCode _fileType = mconcat . map mkMd . splitByMarkup +code types _onlyCode _fileType mod = mconcat . map mkMd . splitByMarkup where trd (_, _, a) = a @@ -287,7 +293,7 @@ code types _onlyCode _fileType = mconcat . map mkMd . splitByMarkup mkHtml (pos, s, mi) = -- Andreas, 2017-06-16, issue #2605: -- Do not create anchors for whitespace. - applyUnless (mi == mempty) (annotate pos mi) $ toHtml s + applyUnless (mi == mempty) (aspectsToHtml (Just mod) types (Just pos) mi) $ toHtml s backgroundOrAgdaToHtml :: TokenInfo -> Html backgroundOrAgdaToHtml token@(_, s, mi) = case aspect mi of @@ -304,74 +310,3 @@ code types _onlyCode _fileType = mconcat . map mkMd . splitByMarkup formatCode = Html5.pre ! Attr.class_ "Agda" $ mconcat $ backgroundOrAgdaToHtml <$> tokens formatNonCode = mconcat $ backgroundOrAgdaToHtml <$> tokens - - -- Put anchors that enable referencing that token. - -- We put a fail safe numeric anchor (file position) for internal references - -- (issue #2756), as well as a heuristic name anchor for external references - -- (issue #2604). - annotate :: Int -> Aspects -> Html -> Html - annotate pos mi = - applyWhen hereAnchor (anchorage nameAttributes mempty <>) . anchorage posAttributes - where - -- Warp an anchor ( tag) with the given attributes around some HTML. - anchorage :: [Attribute] -> Html -> Html - anchorage attrs html = Html5.a html !! attrs - - -- File position anchor (unique, reliable). - posAttributes :: [Attribute] - posAttributes = concat - [ [Attr.id $ stringValue $ show pos ] - , concat (maybeToList (link <$> definitionSite mi)) - , Attr.class_ (stringValue $ unwords classes) <$ guard (not $ null classes) - ] - - -- Named anchor (not reliable, but useful in the general case for outside refs). - nameAttributes :: [Attribute] - nameAttributes = [ Attr.id $ stringValue $ fromMaybe __IMPOSSIBLE__ $ mDefSiteAnchor ] - - classes = concat - [ otherAspectClasses (toList $ otherAspects mi) - , concatMap aspectClasses (aspect mi) - ] - - aspectClasses (Name mKind op) = kindClass ++ opClass - where - kindClass = toList $ fmap showKind mKind - - showKind (Constructor Inductive) = "InductiveConstructor" - showKind (Constructor CoInductive) = "CoinductiveConstructor" - showKind k = show k - - opClass = ["Operator" | op] - aspectClasses a = [show a] - - otherAspectClasses = map show - - -- Should we output a named anchor? - -- Only if we are at the definition site now (@here@) - -- and such a pretty named anchor exists (see 'defSiteAnchor'). - hereAnchor :: Bool - hereAnchor = here && isJust mDefSiteAnchor - - mDefinitionSite :: Maybe DefinitionSite - mDefinitionSite = definitionSite mi - - -- Are we at the definition site now? - here :: Bool - here = maybe False defSiteHere mDefinitionSite - - mDefSiteAnchor :: Maybe String - mDefSiteAnchor = maybe __IMPOSSIBLE__ defSiteAnchor mDefinitionSite - - link :: DefinitionSite -> [Attribute] - link (DefinitionSite m defPos _here _aName) = - [ Attr.href $ stringValue $ anchor ] - ++ maybeToList (Html5.dataAttribute "type" . textValue . idType <$> ident_) - where - anchor :: String - anchor = - applyUnless (defPos <= 1) - (++ "#" ++ Network.URI.Encode.encode (show defPos)) - (Network.URI.Encode.encode $ modToFile m "html") - ident_ :: Maybe Identifier - ident_ = Hm.lookup (Ts.pack anchor) types diff --git a/support/shake/app/HTML/Render.hs b/support/shake/app/HTML/Render.hs new file mode 100644 index 000000000..54cd1dacf --- /dev/null +++ b/support/shake/app/HTML/Render.hs @@ -0,0 +1,171 @@ +{-# LANGUAGE DeriveGeneric, DeriveAnyClass, OverloadedStrings, BlockArguments #-} +module HTML.Render where + +import Prelude hiding ((!!)) + +import Agda.Interaction.Highlighting.Common + +import Agda.Syntax.TopLevelModuleName (TopLevelModuleName) +import Agda.Syntax.Common.Aspect +import Agda.Syntax.Common.Pretty + +import Agda.Utils.Impossible (__IMPOSSIBLE__) +import Agda.Utils.Function + +import Control.DeepSeq +import Control.Monad + +import qualified Data.HashMap.Strict as Hm +import qualified Data.Text.Lazy as Tl +import qualified Data.Text as Text +import Data.HashMap.Strict (HashMap) +import Data.Foldable +import Data.Hashable +import Data.Aeson +import Data.Maybe +import Data.Text (Text) + +import GHC.Generics + +import qualified Network.URI.Encode + +import System.FilePath + +import qualified Text.PrettyPrint.Annotated.HughesPJ as Ppr +import qualified Text.PrettyPrint.Annotated as Ppr + +import qualified Text.Blaze.Html5.Attributes as Attr +import Text.Blaze.Html.Renderer.Text ( renderHtml ) +import Text.Blaze.Html5 as Html hiding (map) + +data DocTree = Node Aspects [DocTree] | Text Text.Text | Mark (Maybe Aspects) + +renderToHtml :: Doc -> Text +renderToHtml = finish . Ppr.fullRenderAnn Ppr.PageMode 100 1.5 cont [] where + consText (Ppr.Chr c) (Text t:ts) = Text (c `Text.cons` t):ts + consText (Ppr.Str c) (Text t:ts) = Text (Text.pack c <> t):ts + consText (Ppr.PStr c) (Text t:ts) = Text (Text.pack c <> t):ts + consText (Ppr.Chr c) ts = Text (Text.singleton c):ts + consText (Ppr.Str c) ts = Text (Text.pack c):ts + consText (Ppr.PStr c) ts = Text (Text.pack c):ts + + annotate acc (Mark (Just t):ts) = Node t (reverse acc):ts + annotate acc (Mark Nothing:ts) = reverse acc <> ts + annotate acc (t:ts) = annotate (t:acc) ts + annotate acc [] = __IMPOSSIBLE__ + + cont :: Ppr.AnnotDetails Aspects -> [DocTree] -> [DocTree] + cont ann acc = case ann of + Ppr.AnnotStart -> annotate [] acc + Ppr.NoAnnot d _ -> consText d acc + Ppr.AnnotEnd a + | _:_ <- toAtoms a -> Mark (Just a):acc + | otherwise -> Mark Nothing:acc -- uncurry (<>) (break acc) + + toBlaze :: DocTree -> Html + toBlaze (Mark _) = __IMPOSSIBLE__ + toBlaze (Text t) = Html.text t + toBlaze (Node a t) = Html.span do + aspectsToHtml Nothing mempty Nothing a $ + traverse_ toBlaze t + unless (null (note a)) do + Html.span (string (note a)) !! [Attr.class_ "Note"] + + finish = Tl.toStrict . renderHtml . wrapper + wrapper = (!! [Attr.class_ "Agda"]) . Html.pre . traverse_ toBlaze + +-- | Data about an identifier +data Identifier = Identifier + { idIdent :: Text + , idAnchor :: Text + , idType :: Text + , idTooltip :: Text + } + deriving (Eq, Show, Ord, Generic, ToJSON, FromJSON, NFData) + +instance Hashable Identifier where + hashWithSalt s = hashWithSalt s . idAnchor + +-- | Attach multiple Attributes +(!!) :: Html -> [Attribute] -> Html +h !! as = h ! mconcat as + +-- | Converts module names to the corresponding HTML file names. +modToFile :: TopLevelModuleName -> String -> FilePath +modToFile m ext = Network.URI.Encode.encode $ render (pretty m) <.> ext + +-- Put anchors that enable referencing that token. +-- We put a fail safe numeric anchor (file position) for internal references +-- (issue #2756), as well as a heuristic name anchor for external references +-- (issue #2604). +aspectsToHtml :: Maybe TopLevelModuleName -> HashMap Text.Text (Int, Identifier) -> Maybe Int -> Aspects -> Html -> Html +aspectsToHtml ourmod types pos mi = + applyWhen hereAnchor (anchorage nameAttributes mempty <>) . anchorage posAttributes + where + -- Warp an anchor ( tag) with the given attributes around some HTML. + anchorage :: [Attribute] -> Html -> Html + anchorage attrs html = Html.a html !! attrs + + -- File position anchor (unique, reliable). + posAttributes :: [Attribute] + posAttributes = concat + [ [Attr.id $ stringValue $ show pos | Just pos <- [pos]] + , concat (maybeToList (link <$> definitionSite mi)) + , Attr.class_ (stringValue $ unwords classes) <$ guard (not $ null classes) + ] + + -- Named anchor (not reliable, but useful in the general case for outside refs). + nameAttributes :: [Attribute] + nameAttributes = [ Attr.id $ stringValue $ fromMaybe __IMPOSSIBLE__ $ mDefSiteAnchor ] + + classes = concat + [ otherAspectClasses (toList $ otherAspects mi) + , concatMap aspectClasses (aspect mi) + ] + + aspectClasses (Name mKind op) = kindClass ++ opClass + where + kindClass = toList $ fmap showKind mKind + + showKind (Constructor Inductive) = "InductiveConstructor" + showKind (Constructor CoInductive) = "CoinductiveConstructor" + showKind k = show k + + opClass = ["Operator" | op] + aspectClasses a = [show a] + + otherAspectClasses = map show + + -- Should we output a named anchor? + -- Only if we are at the definition site now (@here@) + -- and such a pretty named anchor exists (see 'defSiteAnchor'). + hereAnchor :: Bool + hereAnchor = here && isJust mDefSiteAnchor + + mDefinitionSite :: Maybe DefinitionSite + mDefinitionSite = definitionSite mi + + -- Are we at the definition site now? + here :: Bool + here = maybe False defSiteHere mDefinitionSite + + mDefSiteAnchor :: Maybe String + mDefSiteAnchor = maybe __IMPOSSIBLE__ defSiteAnchor mDefinitionSite + + link :: DefinitionSite -> [Html.Attribute] + link ds@(DefinitionSite m defPos _here _aName) = + let + anchor :: String + anchor = definitionSiteToAnchor ds + + ident_ :: Maybe Int + ident_ = fst <$> Hm.lookup (Text.pack anchor) types + in [ Attr.href $ stringValue $ anchor ] + ++ maybeToList (Html.dataAttribute "module" . stringValue . show . pretty <$> ourmod) + ++ maybeToList (Html.dataAttribute "identifier" . stringValue . show <$> ident_) + +definitionSiteToAnchor :: DefinitionSite -> String +definitionSiteToAnchor (DefinitionSite m defPos _ _) = + applyUnless (defPos <= 1) + (++ "#" ++ Network.URI.Encode.encode (show defPos)) + (Network.URI.Encode.encode $ modToFile m "html") diff --git a/support/shake/app/Main.hs b/support/shake/app/Main.hs index bce65c9e5..8ceae2d33 100755 --- a/support/shake/app/Main.hs +++ b/support/shake/app/Main.hs @@ -82,25 +82,33 @@ rules = do let modName = dropExtension (takeFileName out) modKind <- Map.lookup modName <$> getOurModules - skipAgda <- getSkipAgda - if skipAgda - then - let - input = case modName of + let + input + | not skipAgda = "_build/html0" > modName + | otherwise = case modName of "all-pages" -> "_build/all-pages" _ -> "src" > map (\c -> if c == '.' then '/' else c) modName - in - case modKind of - Just WithText -> buildMarkdown modName (input <.> ".lagda.md") out - _ -> copyFile' (input <.> ".agda") out -- Wrong, but eh! - else - let input = "_build/html0" > modName in - case modKind of - Just WithText -> do buildMarkdown modName (input <.> ".md") out - _ -> copyFile' (input <.> ".html") out + + inext = case modKind of + Just WithText + | skipAgda -> "lagda.md" + | otherwise -> "md" + _ -> if skipAgda then "agda" else "html" + + case modKind of + Just WithText -> buildMarkdown modName (input <.> inext) out + _ -> copyFile' (input <.> inext) out -- Wrong, but eh! + + unless skipAgda $ need ["_build/html/types" > modName <.> "json"] "_build/search/*.json" %> \out -> need ["_build/html" > takeFileName out -<.> "html"] + "_build/html/types/*.json" %> \out -> do + let + mn = takeFileName out + it = "_build/html0/" > mn -<.> "used" + need [it] + copyFile' it $ "_build/html/types/" > mn "_build/html/static/search.json" %> \out -> do skipAgda <- getSkipAgda @@ -111,12 +119,18 @@ rules = do traced "Writing search data" $ encodeFile out (concat searchData) -- Compile Quiver to SVG. This is used by 'buildMarkdown'. - "_build/html/light-*.svg" %> \out -> do - let inp = "_build/diagrams" > drop (length ("light-" :: String)) (takeFileName out) -<.> "tex" + "_build/html/**/*.light.svg" %> \out -> do + let + inp = "_build/diagrams" + > takeFileName (takeDirectory out) + > takeBaseName out -<.> "tex" buildDiagram (getPreambleFor False) inp out False - "_build/html/dark-*.svg" %> \out -> do - let inp = "_build/diagrams" > drop (length ("dark-" :: String)) (takeFileName out) -<.> "tex" + "_build/html/**/*.dark.svg" %> \out -> do + let + inp = "_build/diagrams" + > takeFileName (takeDirectory out) + > takeBaseName out -<.> "tex" buildDiagram (getPreambleFor True) inp out True "_build/html/css/*.css" %> \out -> do @@ -154,8 +168,11 @@ rules = do need and kicks off the above job to build them. -} phony "all" do - agda <- getAllModules >>= \modules -> - pure ["_build/html" > f <.> "html" | (f, _) <- Map.toList modules] + skipAgda <- getSkipAgda + agda <- getAllModules >>= \modules -> pure do + (f, _) <- Map.toList modules + [ "_build/html" > f <.> "html" ] <> + [ "_build/html/types" > f <.> "json" | not skipAgda ] static <- getDirectoryFiles "support/static/" ["**/*"] >>= \files -> pure ["_build/html/static" > f | f <- files] need $ diff --git a/support/shake/app/Shake/AgdaCompile.hs b/support/shake/app/Shake/AgdaCompile.hs index 71c071b59..8fbef9e90 100644 --- a/support/shake/app/Shake/AgdaCompile.hs +++ b/support/shake/app/Shake/AgdaCompile.hs @@ -38,6 +38,8 @@ import Agda.Syntax.TopLevelModuleName , hashRawTopLevelModuleName ) import Agda.Syntax.Position (noRange) +import qualified Agda.Utils.Maybe.Strict as S +import qualified Agda.Utils.Trie as Trie import Agda.Utils.FileName import Agda.Utils.Hash (Hash) import Agda.Utils.Lens ((^.)) @@ -119,13 +121,14 @@ agdaRules = do -- Add a couple of forwarding rules for emitting the actual HTML/MD "_build/html0/*.html" %> \file -> need [file -<.> "json"] - "_build/html0/*.md" %> \file -> need [file -<.> "json"] + "_build/html0/*.used" %> \file -> need [file -<.> "json"] + "_build/html0/*.md" %> \file -> need [file -<.> "json"] -- We generate the all-types JSON from the all-pages types JSON - it's just a -- schema change. "_build/all-types.json" %> \file -> do types <- getTypes "all-pages" - liftIO $ encodeFile file (Hm.elems types) + liftIO $ encodeFile file $ map (\t -> t{idTooltip = ""}) (Hm.elems types) -- | Compile the top-level Agda file. compileAgda :: IORef (Maybe TCState) -> Action CompileA @@ -180,14 +183,16 @@ emitAgda emitAgda (CompileA tcState _) getTypes modName = do basepn <- filePath <$> liftIO (absolute "src/") - let tlModName = toTopLevel tcState modName - iface = getInterface tcState tlModName + let + tlModName = toTopLevel tcState modName + iface = getInterface tcState tlModName types <- parallel . map (getTypes . render . pretty . fst) $ iImportedModules iface skipTypes <- getSkipTypes ((), _) <- quietly . traced "agda html" . runTCM initEnv tcState + . withScope_ (iInsideScope iface) . locallyTC eActiveBackendName (const $ Just "HTML") $ do compileOneModule basepn defaultHtmlOptions { htmlOptGenTypes = not skipTypes } (mconcat types) diff --git a/support/shake/app/Shake/KaTeX.hs b/support/shake/app/Shake/KaTeX.hs index 2820ac0f6..a1ecd33e6 100644 --- a/support/shake/app/Shake/KaTeX.hs +++ b/support/shake/app/Shake/KaTeX.hs @@ -5,11 +5,14 @@ module Shake.KaTeX ( katexRules , getDisplayMath , getInlineMath + , prerenderMaths , getParsedPreamble , getPreambleFor ) where +import Control.Monad + import qualified Data.ByteString.Lazy as LazyBS import qualified Data.Text.Encoding as Text import qualified Data.Text.IO as Text @@ -44,6 +47,9 @@ getDisplayMath contents = askOracle (LatexEquation (True, contents)) getInlineMath :: Text -> Action Text getInlineMath contents = askOracle (LatexEquation (False, contents)) +prerenderMaths :: [Text] -> [Text] -> Action () +prerenderMaths display inline = void . askOracles $ [LatexEquation (True, c) | c <- display] <> [LatexEquation (False, c) | c <- inline] + -- | Get the preamble for a given build ('True' = dark mode) getPreambleFor :: Bool -> Action Text getPreambleFor = askOracle . LatexPreamble @@ -67,7 +73,7 @@ katexRules = versioned 2 do let dark = if bool then darkSettings else mempty pure (preambleToLatex pre <> Text.pack "\n" <> dark) - _ <- addOracleCache \(LatexEquation (display, tex)) -> do + _ <- versioned 2 $ addOracleCache \(LatexEquation (display, tex)) -> do pre <- askOracle (ParsedPreamble ()) let args = ["-T", "-F", "html"] ++ ["-d" | display] diff --git a/support/shake/app/Shake/LinkGraph.hs b/support/shake/app/Shake/LinkGraph.hs index cc1044efc..3cb101394 100644 --- a/support/shake/app/Shake/LinkGraph.hs +++ b/support/shake/app/Shake/LinkGraph.hs @@ -47,7 +47,7 @@ linksRules = do let searchAnchors = Set.fromList (map idAnchor searchData) agdaIdents :: [Identifier] <- readJSONFile "_build/all-types.json" let agdaAnchors = Set.fromList [ Text.concat [filename, "#", ident] - | Identifier ident anchor _type <- agdaIdents + | Identifier ident anchor _type _tooltip <- agdaIdents , let (filename, _) = Text.break (== '#') anchor ] pure $ Set.unions [moduleAnchors, searchAnchors, agdaAnchors] diff --git a/support/shake/app/Shake/LinkReferences.hs b/support/shake/app/Shake/LinkReferences.hs index a676e1029..e53c15221 100644 --- a/support/shake/app/Shake/LinkReferences.hs +++ b/support/shake/app/Shake/LinkReferences.hs @@ -42,12 +42,13 @@ linkReferences modname (Pandoc meta blocks) = Pandoc meta (walk link blocks) link x = x renderReference :: Reference -> Text -> Inline -renderReference (Reference href cls) t = - Span ("", ["Agda"], []) [Link ("", [cls], []) [Code nullAttr t] (href, "")] +renderReference (Reference href cls ty) t = + Span ("", ["Agda"], []) [Link ("", [cls], maybeToList (("data-identifier",) <$> ty)) [Code nullAttr t] (href, "")] data Reference = Reference { refHref :: Text , refClass :: Text + , refType :: Maybe Text } deriving (Eq, Show) @@ -65,7 +66,7 @@ parseSymbolRefs = go mempty . concatMap getHTML where go map (TagOpen "a" meta:TagText t:TagClose "a":xs) | Just cls <- lookup "class" meta , Just href <- lookup "href" meta - = go (addIfNotPresent t (Reference href cls) map) xs + = go (addIfNotPresent t (Reference href cls (lookup "data-identifier" meta)) map) xs | otherwise = go map xs diff --git a/support/shake/app/Shake/Markdown.hs b/support/shake/app/Shake/Markdown.hs index 251a7f4f1..b62cb486b 100644 --- a/support/shake/app/Shake/Markdown.hs +++ b/support/shake/app/Shake/Markdown.hs @@ -16,6 +16,7 @@ import Control.Applicative import qualified Data.ByteString.Lazy as LazyBS import qualified Data.Text.Encoding as Text import qualified Data.Map.Lazy as Map +import qualified Data.Sequence as Seq import qualified Data.Text.IO as Text import qualified Data.Text as Text import qualified Data.Set as Set @@ -214,6 +215,7 @@ buildMarkdown modname input output = do = Meta . Map.insert "title" (mStr title) . Map.insert "source" (mStr permalink) + . Map.insert "module" (mStr modname) . Map.insert "bibliography" (mStr bibliographyName) . Map.insert "link-citations" (MetaBool True) . unMeta @@ -224,9 +226,16 @@ buildMarkdown modname input output = do either (fail . show) pure =<< runIO do (,) <$> processCitations pandoc <*> getReferences Nothing pandoc - liftIO $ Dir.createDirectoryIfMissing False "_build/diagrams" + liftIO $ Dir.createDirectoryIfMissing True $ "_build/diagrams" > modname - let refMap = Map.fromList $ map (\x -> (Cite.unItemId . Cite.referenceId $ x, x)) references + let + refMap = Map.fromList $ map (\x -> (Cite.unItemId . Cite.referenceId $ x, x)) references + (display, inline) = flip query markdown \case + Math DisplayMath contents -> (Seq.singleton contents, mempty) + Math InlineMath contents -> (mempty, Seq.singleton contents) + _ -> mempty + + prerenderMaths (toList display) (toList inline) Pandoc meta@(Meta metamap) blocks <- walkM (patchInline refMap) @@ -241,10 +250,10 @@ buildMarkdown modname input output = do baseUrl <- getBaseUrl - filtered <- parallel $ map (runWriterT . walkM (patchBlock baseUrl)) blocks - let (bs, mss) = unzip filtered - MarkdownState references = fold mss - markdown = Pandoc meta bs + filtered <- parallel $ map (runWriterT . walkM (patchBlock baseUrl modname)) blocks + let (bs, mss) = unzip filtered + MarkdownState references defs = fold mss + markdown = Pandoc meta bs digest <- do cssDigest <- getFileDigest "_build/html/css/default.css" @@ -264,10 +273,18 @@ buildMarkdown modname input output = do traverse_ (checkMarkup input) tags traced "writing" do - Text.writeFile output $ renderHTML5 tags + Dir.createDirectoryIfMissing False "_build/html/fragments" Dir.createDirectoryIfMissing False "_build/search" + + Text.writeFile output $ renderHTML5 tags encodeFile ("_build/search" > modname <.> "json") search + for_ (Map.toList defs) \(key, bs) -> traced "writing fragment" do + text <- either (fail . show) pure =<< + runIO (renderMarkdown authors references modname baseUrl digest (Pandoc mempty bs)) + + Text.writeFile ("_build/html/fragments" > Text.unpack (getMangled key) <.> "html") text + -- | Find the original Agda file from a 1Lab module name. findModule :: MonadIO m => String -> m FilePath findModule modname = do @@ -322,10 +339,18 @@ patchInline _ (Str s) patchInline _ h = pure h -newtype MarkdownState = MarkdownState - { mdReferences :: [Val Text] -- ^ List of references extracted from Pandoc's "reference" div. +data MarkdownState = MarkdownState + { mdReferences :: [Val Text] + -- ^ List of references extracted from Pandoc's "reference" div. + , mdDefinitions :: Map.Map Mangled [Block] + -- ^ List of definition blocks } - deriving newtype (Semigroup, Monoid) + +instance Semigroup MarkdownState where + MarkdownState a b <> MarkdownState a' b' = MarkdownState (a <> a') (b <> b') + +instance Monoid MarkdownState where + mempty = MarkdownState mempty mempty diagramResource :: Resource diagramResource = unsafePerformIO $ newResourceIO "diagram" 1 @@ -335,22 +360,23 @@ diagramResource = unsafePerformIO $ newResourceIO "diagram" 1 patchBlock :: (MonadIO f, MonadFail f, MonadWriter MarkdownState f, MonadTrans t, f ~ t Action) => String + -> String -> Block -> f Block -- Make all headers links, and add an anchor emoji. -patchBlock _ (Header i a@(ident, _, _) inl) = pure $ Header i a +patchBlock _ _ (Header i a@(ident, _, _) inl) = pure $ Header i a $ htmlInl (Text.concat [""]) : inl ++ [htmlInl "🔗"] -- Replace quiver code blocks with a link to an SVG file, and depend on the SVG file. -patchBlock _ (CodeBlock (id, classes, attrs) contents) | "quiver" `elem` classes = do +patchBlock _ mod (CodeBlock (id, classes, attrs) contents) | "quiver" `elem` classes = do let - digest = showDigest . sha1 . LazyBS.fromStrict $ Text.encodeUtf8 contents + digest = take 12 . showDigest . sha1 . LazyBS.fromStrict $ Text.encodeUtf8 contents title = fromMaybe "commutative diagram" (lookup "title" attrs) - light = "_build/html/light-" <> digest <.> "svg" - dark = "_build/html/dark-" <> digest <.> "svg" + lfn = mod > digest <.> "light.svg" + dfn = mod > digest <.> "dark.svg" height <- lift do -- We have to lock the diagram directory to prevent race conditions @@ -359,25 +385,25 @@ patchBlock _ (CodeBlock (id, classes, attrs) contents) | "quiver" `elem` classes -- This isn't the best in terms of atomicity, but it does preserve -- the nice property that diagrams are globally shared by their -- contents. - withResource diagramResource 1 $ liftIO $ - Text.writeFile ("_build/diagrams" > digest <.> "tex") contents + withResource diagramResource 1 $ liftIO do + Text.writeFile ("_build/diagrams" > mod > digest <.> "tex") contents - need [ light, dark ] - diagramHeight light + need [ "_build/html" > lfn, "_build/html" > dfn ] + diagramHeight ("_build/html" > lfn) let attrs' = ("style", "--height: " <> Text.pack (show height) <> "px;"):attrs pure $ Div ("", ["diagram-container"], []) - [ Plain [ Image (id, "diagram diagram-light":classes, attrs') [] (Text.pack ("light-" <> digest <.> "svg"), title) ] - , Plain [ Image (id, "diagram diagram-dark":classes, attrs') [] (Text.pack ("dark-" <> digest <.> "svg"), title) ] + [ Plain [ Image (id, "diagram diagram-light":classes, attrs') [] (Text.pack lfn, title) ] + , Plain [ Image (id, "diagram diagram-dark":classes, attrs') [] (Text.pack dfn, title) ] ] -patchBlock base (Div attr@("recent-additions", _, _) []) = +patchBlock base _ (Div attr@("recent-additions", _, _) []) = Div attr . map (renderCommit base) <$> lift recentAdditions -- Find the references block, parse the references, and remove it. We write -- the references as part of our template instead. -patchBlock _ (Div ("refs", _, _) body) = do +patchBlock _ _ (Div ("refs", _, _) body) = do for_ body \ref -> case ref of (Div (id, _, _) body) -> do -- If our citation is a single paragraph, don't wrap it in
.
@@ -397,7 +423,13 @@ patchBlock _ (Div ("refs", _, _) body) = do
_ -> fail ("Unknown reference node " ++ show ref)
pure $ Plain [] -- TODO: pandoc-types 1.23 removed Null
-patchBlock _ h = pure h
+patchBlock _ _ b@(Div (id, [only], kv) bs) | "definition" == only, not (Text.null id) = do
+ let
+ isfn (Note _) = True
+ isfn _ = False
+ b <$ tell (MarkdownState mempty (Map.singleton (mangleLink id) (walk (filter (not . isfn)) bs)))
+
+patchBlock _ _ h = pure h
-- | Render our Pandoc document using the given template variables.
renderMarkdown
@@ -414,12 +446,15 @@ renderMarkdown authors references modname baseUrl digest markdown@(Pandoc (Meta
isTalk = isJust $ Map.lookup "talk" meta
templateName
- | isTalk = talkTemplateName
- | otherwise = pageTemplateName
+ | isTalk = Just talkTemplateName
+ | mempty == meta = Nothing
+ | otherwise = Just pageTemplateName
+
+ get nm = getTemplate nm
+ >>= runWithPartials . compileTemplate nm
+ >>= either (throwError . PandocTemplateError . Text.pack) pure
- template <- getTemplate templateName
- >>= runWithPartials . compileTemplate templateName
- >>= either (throwError . PandocTemplateError . Text.pack) pure
+ template <- traverse get templateName
let
authors' = case authors of
@@ -436,7 +471,7 @@ renderMarkdown authors references modname baseUrl digest markdown@(Pandoc (Meta
]
opts = def
- { writerTemplate = Just template
+ { writerTemplate = template
, writerTableOfContents = not isTalk
, writerVariables = context
, writerExtensions = getDefaultExtensions "html"
diff --git a/support/web/css/code.scss b/support/web/css/code.scss
index 8b85703ad..3549710e5 100644
--- a/support/web/css/code.scss
+++ b/support/web/css/code.scss
@@ -167,19 +167,3 @@ code.at, span.at { color: theme(--code-constructor); } /* Attribute */
code.do, span.do { color: theme(--code-string); } /* Documentation */
code.an, span.an { color: theme(--code-string); } /* Annotation */
code.cv, span.cv { color: theme(--code-string); } /* CommentVar */
-
-.type-tooltip, div.sourceCode.type-tooltip {
- position: absolute;
- z-index: 100;
-
- font-size: 0.8em;
- color: theme(--code-fg);
- background: theme(--code-bg);
-
- &.sourceCode {
- padding: 0.3em;
- border: 0;
- };
-
- box-shadow: 2px 2px 6px theme(--shadow);
-}
diff --git a/support/web/css/components/popup.scss b/support/web/css/components/popup.scss
new file mode 100644
index 000000000..ea29e1b2d
--- /dev/null
+++ b/support/web/css/components/popup.scss
@@ -0,0 +1,65 @@
+div.hover-popup, div.hover-popup.sourceCode {
+ &.popup-hidden { display: none; }
+ @include widescreen { display: block; }
+
+ position: absolute;
+ z-index: 100;
+
+ color: theme(--code-fg);
+ background: theme(--code-bg);
+
+ --font-size: 1.125rem;
+ --code-font-size: 1rem;
+
+ box-shadow: 2px 2px 6px theme(--shadow);
+
+ >:first-child { margin-block-start: 0; }
+ >:last-child { margin-block-end: 0; }
+
+ padding: 0.3em;
+ border: 0;
+ margin: 0;
+
+ &:before {
+ display: none;
+ }
+}
+
+.text-popup {
+ min-width: 22em;
+ max-width: 26em;
+ width: min-content;
+
+ margin-left: 1.25em;
+}
+
+
+@mixin keyframes-for($dir, $dy) {
+ @keyframes popup-fade-in-#{$dir} {
+ 0% {
+ opacity: 0;
+ transform: translate(0, $dy);
+ }
+ 100% {
+ opacity: 1;
+ transform: translate(0, 0);
+ }
+ }
+
+ @keyframes popup-fade-out-#{$dir} {
+ 0% {
+ opacity: 1;
+ transform: translate(0, 0);
+ }
+ 100% {
+ opacity: 0;
+ transform: translate(0, $dy);
+ }
+ }
+
+ .popup-fade-in-#{$dir} { animation: 0.3s popup-fade-in-#{$dir}; }
+ .popup-fade-out-#{$dir} { animation: 0.3s popup-fade-out-#{$dir}; }
+}
+
+@include keyframes-for(up, 10px);
+@include keyframes-for(down, -10px);
diff --git a/support/web/css/default.scss b/support/web/css/default.scss
index f5a5e29e3..3a0089226 100644
--- a/support/web/css/default.scss
+++ b/support/web/css/default.scss
@@ -8,6 +8,7 @@
@import "components/commit.scss";
@import "components/search.scss";
@import "components/modal.scss";
+@import "components/popup.scss";
@import "components/toc.scss";
@import "code.scss";
@@ -25,10 +26,12 @@ html, body, main, div#post-toc-container {
max-width: 100vw;
}
-body {
+body, p {
font-family: var(--serif), var(--sans-serif);
font-size: var(--font-size);
+}
+body {
width: 100%;
&.text-page {
@@ -264,7 +267,11 @@ div.diagram-container {
}
}
-main a[href], div#return > a[href], div#top > a[href], aside#toc > div#toc-container ul a[href] {
+main a[href],
+div#return > a[href],
+div#top > a[href],
+aside#toc > div#toc-container ul a[href],
+div.hover-popup a[href] {
color: theme(--primary);
text-decoration: none;
diff --git a/support/web/css/mixins.scss b/support/web/css/mixins.scss
index a6228eb1e..4e80017d6 100644
--- a/support/web/css/mixins.scss
+++ b/support/web/css/mixins.scss
@@ -19,6 +19,15 @@
left: calc(-1 * #{$left-border-distance} - #{$left-border-width});
}
}
+
+ @at-root div.hover-popup & {
+ border-inline-start: 0;
+ padding-inline-start: 0;
+
+ &:before {
+ display: none;
+ }
+ }
}
@mixin material($bg) {
diff --git a/support/web/js/highlight-hover.ts b/support/web/js/highlight-hover.ts
index 88a2926db..7e406a7ee 100644
--- a/support/web/js/highlight-hover.ts
+++ b/support/web/js/highlight-hover.ts
@@ -2,12 +2,77 @@
// https://github.com/haskell/haddock/blob/ghc-8.8/LICENSE
// Slightly modified by Tesla Ice Zhang
-let links: Array