+redef class HTMLTag
+ # Attach the infobox to the node by using BootStrap popover
+ fun attach_infobox(infobox: HInfoBox)
+ do
+ classes.add("popupable")
+ attrs["title"] = infobox.title
+ var href = infobox.href
+ if href != null then
+ attrs["data-title"] = """<a href="{{{href}}}">{{{infobox.title}}}</a>"""
+ end
+ attrs["data-content"] = infobox.content.write_to_string
+ attrs["data-toggle"] = "popover"
+ end
+end
+
+
+# A generic information container that can be used to decorate AST entities
+class HInfoBox
+ # The visitor used for contextualisation, if needed
+ var visitor: HighlightVisitor
+
+ # A short title for the AST element
+ var title: String
+
+ # The primary link where the entity points
+ # null if no link
+ var href: nullable String = null
+
+ # The content of the popuped infobox
+ var content = new HTMLTag("div")
+
+ # Append a new field in the popuped infobox
+ fun new_field(title: String): HTMLTag
+ do
+ content.open("b").text(title)
+ content.append(" ")
+ var res = content.open("span")
+ content.open("br")
+ return res
+ end
+
+ # Append a new dropdown in the popuped content
+ fun new_dropdown(title, text: String): HTMLTag
+ do
+ content.add_raw_html """<div class="dropdown"> <a data-toggle="dropdown" href="#"><b>"""
+ content.append(title)
+ content.add_raw_html "</b> "
+ content.append(text)
+ content.add_raw_html """<span class="caret"></span></a>"""
+ var res = content.open("ul").add_class("dropdown-menu").attr("role", "menu").attr("aria-labelledby", "dLabel")
+ content.add_raw_html "</div>"
+ return res
+ end
+end
+
+##
+
+# Model entity or whatever that can produce an infobox
+interface HInfoBoxable
+ # An new infobox documenting the entity
+ fun infobox(v: HighlightVisitor): HInfoBox is abstract
+end
+
+redef class MDoc
+ # Append an entry for the doc in the given infobox
+ fun fill_infobox(res: HInfoBox)
+ do
+ if content.length < 2 then
+ res.new_field("doc").text(content.first)
+ return
+ end
+ var c = res.new_dropdown("doc", content.first)
+ for x in content.iterator.skip_head(1) do
+ c.append x
+ c.add_raw_html "<br>"
+ end
+ end
+end
+
+redef class MEntity
+ super HInfoBoxable
+
+ # A HTML version of `to_s` with hyper-links.
+ #
+ # By default, `linkto_text(v, to_s)` is used, c.f. see `linkto_text`.
+ #
+ # For some complex entities, like generic types, multiple `<a>` and `<span>` elements can be generated.
+ # E.g. `Array[Int]` might become `<a>Array</a>[<a>Int</a>]` with the correct `href` attributes
+ # provided by `v.hrefto`.
+ fun linkto(v: HighlightVisitor): HTMLTag do return linkto_text(v, to_s)
+
+ # Link to the `self` with a specific text.
+ #
+ # The whole text is linked with a single `<a>` element.
+ #
+ # The `href` used is provided by `v.hrefto`.
+ # If `href` is null then a `<span>` element is used instead of `<a>`.
+ fun linkto_text(v: HighlightVisitor, text: String): HTMLTag
+ do
+ var href = v.hrefto(self)
+ if href == null then
+ return (new HTMLTag("span")).text(text)
+ end
+ return (new HTMLTag("a")).attr("href", href).text(text)
+ end
+end
+
+redef class MModule
+ redef fun infobox(v)
+ do
+ var res = new HInfoBox(v, "module {name}")
+ res.href = v.hrefto(self)
+ res.new_field("module").add(linkto(v))
+ var mdoc = self.mdoc
+ if mdoc != null then mdoc.fill_infobox(res)
+ if in_importation.greaters.length > 1 then
+ var c = res.new_dropdown("imports", "{in_importation.greaters.length-1} modules")
+ for x in in_importation.greaters do
+ if x == self then continue
+ c.open("li").add x.linkto(v)
+ end
+ end
+ return res
+ end
+
+ redef fun linkto(v) do return linkto_text(v, name)
+end
+
+redef class MClassDef
+ redef fun infobox(v)
+ do
+ var res = new HInfoBox(v, "class {mclass.name}")
+ res.href = v.hrefto(self)
+ if is_intro then
+ res.new_field("class").text(mclass.name)
+ else
+ res.new_field("redef class").text(mclass.name)
+ res.new_field("intro").add mclass.intro.linkto_text(v, "in {mclass.intro_mmodule.to_s}")
+ end
+ var mdoc = self.mdoc
+ if mdoc == null then mdoc = mclass.intro.mdoc
+ if mdoc != null then mdoc.fill_infobox(res)
+
+ var in_hierarchy = self.in_hierarchy
+ if in_hierarchy == null then return res
+
+ if in_hierarchy.greaters.length > 1 then
+ var c = res.new_dropdown("hier", "super-classes")
+ for x in in_hierarchy.greaters do
+ if x == self then continue
+ if not x.is_intro then continue
+ c.open("li").add x.linkto(v)
+ end
+ end
+ if in_hierarchy.smallers.length > 1 then
+ var c = res.new_dropdown("hier", "sub-classes")
+ for x in in_hierarchy.smallers do
+ if x == self then continue
+ if not x.is_intro then continue
+ c.open("li").add x.linkto(v)
+ end
+ end
+ if mclass.mclassdefs.length > 1 then
+ var c = res.new_dropdown("redefs", "refinements")
+ for x in mclass.mclassdefs do
+ if x == self then continue
+ c.open("li").add x.linkto_text(v, "in {x.mmodule}")
+ end
+ end
+ return res
+ end
+end
+
+redef class MPropDef
+ redef fun infobox(v)
+ do
+ var res = new HInfoBox(v, to_s)
+ res.href = v.hrefto(self)
+ if self isa MMethodDef then
+ var msignature = self.msignature
+ if msignature != null then res.new_field("fun").append(mproperty.name).add msignature.linkto(v)
+ else if self isa MAttributeDef then
+ var static_mtype = self.static_mtype
+ if static_mtype != null then res.new_field("fun").append(mproperty.name).add static_mtype.linkto(v)
+ else if self isa MVirtualTypeDef then
+ var bound = self.bound
+ if bound != null then res.new_field("add").append(mproperty.name).add bound.linkto(v)
+ else
+ res.new_field("wat?").append(mproperty.name)
+ end
+
+ if is_intro then
+ else
+ res.new_field("intro").add mproperty.intro.linkto_text(v, "in {mproperty.intro.mclassdef}")
+ end
+ var mdoc = self.mdoc
+ if mdoc == null then mdoc = mproperty.intro.mdoc
+ if mdoc != null then mdoc.fill_infobox(res)
+ if mproperty.mpropdefs.length > 1 then
+ var c = res.new_dropdown("redef", "redefinitions")
+ for x in mproperty.mpropdefs do
+ c.open("li").add x.linkto_text(v, "in {x.mclassdef}")
+ end
+ end
+
+ return res
+ end
+end
+
+redef class MClassType
+ redef fun infobox(v)
+ do
+ var res = new HInfoBox(v, to_s)
+ res.href = v.hrefto(self)
+ res.new_field("class").add mclass.intro.linkto(v)
+ var mdoc = mclass.mdoc
+ if mdoc == null then mdoc = mclass.intro.mdoc
+ if mdoc != null then mdoc.fill_infobox(res)
+ return res
+ end
+ redef fun linkto(v)
+ do
+ return mclass.intro.linkto(v)
+ end
+end
+redef class MVirtualType
+ redef fun infobox(v)
+ do
+ var res = new HInfoBox(v, to_s)
+ res.href = v.hrefto(mproperty)
+ var p = mproperty
+ var pd = p.intro
+ res.new_field("virtual type").add pd.linkto(v)
+ var mdoc = pd.mdoc
+ if mdoc != null then mdoc.fill_infobox(res)
+ return res
+ end
+ redef fun linkto(v)
+ do
+ return mproperty.intro.linkto(v)
+ end
+end
+redef class MParameterType
+ redef fun infobox(v)
+ do
+ var res = new HInfoBox(v, to_s)
+ res.new_field("parameter type").append("{name} from class ").add mclass.intro.linkto(v)
+ return res
+ end
+end
+
+redef class MNullableType
+ redef fun infobox(v)
+ do
+ return mtype.infobox(v)
+ end
+ redef fun linkto(v)
+ do
+ var res = new HTMLTag("span")
+ res.append("nullable ").add(mtype.linkto(v))
+ return res
+ end
+end
+
+redef class MNotNullType
+ redef fun infobox(v)
+ do
+ return mtype.infobox(v)
+ end
+ redef fun linkto(v)
+ do
+ var res = new HTMLTag("span")
+ res.append("not null ").add(mtype.linkto(v))
+ return res
+ end
+end
+
+redef class MNullType
+ redef fun infobox(v)
+ do
+ var res = new HInfoBox(v, to_s)
+ return res
+ end
+ redef fun linkto(v)