Merge: hightlight: improve infrastructure
[nit.git] / src / highlight.nit
index f4268c0..d11b6f7 100644 (file)
@@ -43,22 +43,118 @@ class HighlightVisitor
        # The last line to generate, null if finish at the last line
        var last_line: nullable Int = null is writable
 
+       # When highlighting a node, show its messages (errors, warnings), if any.
+       #
+       # default: true
+       var show_messages = true is writable
+
+       # When highlighting a node, attach a full popupable infobox, if any.
+       #
+       # If `false`, only a simple `title` tooltip is used.
+       #
+       # default: true
+       var show_infobox = true is writable
+
        init
        do
                html.add_class("nitcode")
        end
 
+       # When highlighting a node, also consider the loose tokens around it.
+       #
+       # Loose tokens are tokens discarded from the AST but attached before
+       # or after some non-loose tokens. See `Token::is_loose`.
+       #
+       # When this flag is set to `true`, the loose tokens that are before the
+       # first token and after the last token are also highlighted.
+       #
+       # Default: false.
+       var include_loose_tokens = false is writable
+
+       # When highlighting a node, the first and the last lines are fully included.
+       #
+       # If the highlighted node starts (or ends) in the middle of a line,
+       # this flags forces the whole line to be highlighted.
+       #
+       # Default: false
+       var include_whole_lines = false is writable
+
        # The entry-point of the highlighting.
        # Will fill `html` with the generated HTML content.
        fun enter_visit(n: ANode)
        do
                n.parentize_tokens
-               var s = n.location.file
-               htmlize(s.first_token.as(not null), s.last_token.as(not null))
+
+               var f
+               var l
+
+               if n isa Token then
+                       f = n
+                       l = n
+               else
+                       assert n isa Prod
+                       f = n.first_token
+                       if f == null then return
+                       l = n.last_token
+                       if l == null then return
+               end
+
+               if include_loose_tokens then
+                       if f.prev_looses.not_empty then f = f.prev_looses.first
+                       if l.next_looses.not_empty then l = l.next_looses.last
+               end
+
+               if include_whole_lines then
+                       f = f.first_real_token_in_line
+                       l = l.last_real_token_in_line
+               end
+
+               htmlize(f, l)
+       end
+
+       private fun full_tag(anode: ANode, hv: HighlightVisitor): nullable HTMLTag
+       do
+               var tag = anode.make_tag(hv)
+               if tag == null then return null
+               var infobox = anode.infobox(hv)
+               if infobox == null and anode isa Token then
+                       var pa = anode.parent
+                       if pa != null then
+                               infobox = pa.decorate_tag(hv, tag, anode)
+                       end
+               end
+               if infobox != null and not show_infobox then
+                       tag.attr("title", infobox.title)
+                       tag.classes.add "titled"
+                       infobox = null
+               end
+               var messages = anode.location.messages
+               if messages != null and show_messages then
+                       tag.css("border-bottom", "solid 2px red")
+                       if infobox == null then
+                               infobox = new HInfoBox(hv, "Messages")
+                       end
+                       var c = infobox.new_dropdown("{messages.length} message(s)", "")
+                       for m in messages do
+                               c.open("li").append(m.text)
+                       end
+               end
+               if infobox != null then
+                       tag.attach_infobox(infobox)
+               end
+               return tag
+       end
+
+       # Highlight a full lexed source file.
+       #
+       # REQUIRE `source.first_token != null`
+       fun hightlight_source(source: SourceFile)
+       do
+               htmlize(source.first_token.as(not null), null)
        end
 
        # Produce HTML between two tokens
-       protected fun htmlize(first_token, last_token: Token)
+       protected fun htmlize(first_token: Token, last_token: nullable Token)
        do
                var stack2 = new Array[HTMLTag]
                var stack = new Array[Prod]
@@ -79,11 +175,9 @@ class HighlightVisitor
                                if c0 != null then starting = c0.starting_prods
                                if starting != null then for p in starting do
                                        if not p.is_block then continue
-                                       var tag = p.make_tag(hv)
+                                       var tag = full_tag(p, hv)
                                        if tag == null then continue
                                        tag.add_class("foldable")
-                                       var infobox = p.infobox(hv)
-                                       if infobox != null then tag.attach_infobox(infobox)
                                        stack2.add(html)
                                        html.add tag
                                        html = tag
@@ -108,10 +202,8 @@ class HighlightVisitor
                        starting = c.starting_prods
                        if starting != null then for p in starting do
                                if not p.is_span then continue
-                               var tag = p.make_tag(hv)
+                               var tag = full_tag(p, hv)
                                if tag == null then continue
-                               var infobox = p.infobox(hv)
-                               if infobox != null then tag.attach_infobox(infobox)
                                stack2.add(html)
                                html.add tag
                                html = tag
@@ -122,17 +214,8 @@ class HighlightVisitor
                        if c isa TEol then 
                                html.append "\n"
                        else
-                               var tag = c.make_tag(hv)
-                               var pa = c.parent
-                               var infobox = null
-                               if c isa TId or c isa TClassid or c isa TAttrid or c isa TokenLiteral or c isa TokenOperator then
-                                       assert c != null
-                                       if pa != null then infobox = pa.decorate_tag(hv, tag, c)
-                               else if c isa TComment and pa isa ADoc then
-                                       infobox = pa.decorate_tag(hv, tag, c)
-                               end
-                               if infobox != null then tag.attach_infobox(infobox)
-                               html.add tag
+                               var tag = full_tag(c, hv)
+                               if tag != null then html.add tag
                        end
 
                        # Handle ending span productions
@@ -165,8 +248,8 @@ class HighlightVisitor
 
                        c = n
                end
-               assert stack.is_empty
-               assert stack2.is_empty
+               #assert stack.is_empty
+               #assert stack2.is_empty
        end
 
        # Return a default CSS content related to CSS classes used in the `html` tree.
@@ -175,6 +258,7 @@ class HighlightVisitor
        do
                return """
 .nitcode a { color: inherit; cursor:pointer; }
+.nitcode .titled:hover { text-decoration: underline; } /* underline titles */
 .nitcode .popupable:hover { text-decoration: underline; cursor:help; } /* underline titles */
 .nitcode .foldable { display: block } /* for block productions*/
 .nitcode .line{ display: block } /* for lines */
@@ -334,7 +418,7 @@ redef class MModule
        # The module HTML page
        fun href: String
        do
-               return name + ".html"
+               return c_name + ".html"
        end
 
        redef fun linkto do return linkto_text(name)
@@ -355,7 +439,7 @@ redef class MClassDef
                        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("in {mclass.intro.mmodule.to_s}")
+                       res.new_field("intro").add mclass.intro.linkto_text("in {mclass.intro_mmodule.to_s}")
                end
                var mdoc = self.mdoc
                if mdoc == null then mdoc = mclass.intro.mdoc
@@ -510,6 +594,33 @@ redef class MNullableType
        end
 end
 
+redef class MNotNullType
+       redef fun infobox(v)
+       do
+               return mtype.infobox(v)
+       end
+       redef fun linkto
+       do
+               var res = new HTMLTag("span")
+               res.append("not null ").add(mtype.linkto)
+               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
+       do
+               var res = new HTMLTag("span")
+               res.append("null")
+               return res
+       end
+end
+
 redef class MSignature
        redef fun linkto
        do
@@ -539,7 +650,6 @@ redef class MSignature
 end
 
 redef class CallSite
-       super HInfoBoxable
        redef fun infobox(v)
        do
                var res = new HInfoBox(v, "call {mpropdef}")
@@ -600,6 +710,22 @@ redef class ANode
        fun infobox(v: HighlightVisitor): nullable HInfoBox do return null
 end
 
+redef class AQclassid
+       redef fun decorate_tag(v, res, token)
+       do
+               if token != n_id then return null
+               return parent.decorate_tag(v, res, token)
+       end
+end
+
+redef class AQid
+       redef fun decorate_tag(v, res, token)
+       do
+               if token != n_id then return null
+               return parent.decorate_tag(v, res, token)
+       end
+end
+
 redef class AStdClassdef
        redef fun make_tag(v)
        do
@@ -611,6 +737,7 @@ redef class AStdClassdef
        end
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TClassid then return null
                res.add_class("nc_def")
 
                var md = mclassdef
@@ -669,8 +796,6 @@ redef class TokenOperator
        redef fun make_tag(v)
        do
                var res = super
-               var p = parent
-               if p != null then p.decorate_tag(v, res, self)
                res.add_class("nc_o")
                return res
        end
@@ -679,6 +804,7 @@ end
 redef class AVarFormExpr
        redef fun decorate_tag(v, res, token)
        do
+               if token != n_id then return null
                var variable = self.variable
                if variable == null then return null
                res.add_class("nc_v")
@@ -689,6 +815,7 @@ end
 redef class AVardeclExpr
        redef fun decorate_tag(v, res, token)
        do
+               if token != n_id then return null
                var variable = self.variable
                if variable == null then return null
                res.add_class("nc_v")
@@ -696,7 +823,7 @@ redef class AVardeclExpr
        end
 end
 
-redef class AForExpr
+redef class AForGroup
        redef fun decorate_tag(v, res, token)
        do
                if not token isa TId then return null
@@ -712,6 +839,7 @@ end
 redef class AParam
        redef fun decorate_tag(v, res, token)
        do
+               if token != n_id then return null
                var mp = mparameter
                if mp == null then return null
                var variable = self.variable
@@ -724,6 +852,7 @@ end
 redef class AAssertExpr
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TId then return null
                res.add_class("nc_ast")
                return null
        end
@@ -732,6 +861,7 @@ end
 redef class ALabel
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TId then return null
                res.add_class("nc_la")
                return null
        end
@@ -775,6 +905,7 @@ end
 redef class AModuledecl
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TId then return null
                res.add_class("nc_def")
                res.add_class("nc_m")
                var p = parent
@@ -788,6 +919,7 @@ end
 redef class AStdImport
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TId then return null
                res.add_class("nc_m")
                var mm = mmodule
                if mm == null then return null
@@ -797,6 +929,7 @@ end
 redef class AAttrPropdef
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TId then return null
                res.add_class("nc_def")
                var mpd: nullable MPropDef
                mpd = mreadpropdef
@@ -810,8 +943,6 @@ redef class TId
        redef fun make_tag(v)
        do
                var res = super
-               var p = parent
-               if p != null then p.decorate_tag(v, res, self)
                res.add_class("nc_i")
                return res
        end
@@ -841,8 +972,6 @@ redef class TAttrid
        redef fun make_tag(v)
        do
                var res = super
-               var p = parent
-               if p != null then p.decorate_tag(v, res, self)
                res.add_class("nc_a")
                return res
        end
@@ -850,6 +979,7 @@ end
 redef class AAttrFormExpr
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TAttrid then return null
                var p = mproperty
                if p == null then return null
                return p.intro.infobox(v)
@@ -859,8 +989,6 @@ redef class TClassid
        redef fun make_tag(v)
        do
                var res = super
-               var p = parent
-               if p != null then p.decorate_tag(v, res, self)
                res.add_class("nc_t")
                return res
        end
@@ -868,9 +996,10 @@ end
 redef class AType
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TClassid then return null
                var mt = mtype
                if mt == null then return null
-               mt = mt.as_notnullable
+               mt = mt.undecorate
                if mt isa MFormalType then
                        res.add_class("nc_vt")
                end
@@ -880,6 +1009,7 @@ end
 redef class AFormaldef
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TClassid then return null
                res.add_class("nc_vt")
                if mtype == null then return null
                return mtype.infobox(v)
@@ -888,6 +1018,7 @@ end
 redef class ATypePropdef
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TClassid then return null
                res.add_class("nc_def")
                var md = mpropdef
                if md == null then return null
@@ -898,7 +1029,7 @@ redef class TComment
        redef fun make_tag(v)
        do
                var res = super
-               if not parent isa ADoc then
+               if is_loose then
                        res.add_class("nc_c")
                end
                return res
@@ -917,8 +1048,6 @@ redef class TokenLiteral
        do
                var res = super
                res.add_class("nc_l")
-               var p = parent
-               if p != null then p.decorate_tag(v, res, self)
                return res
        end
 end
@@ -936,7 +1065,7 @@ redef class AStringFormExpr
                # Workaround to tag strings
                res.classes.remove("nc_l")
                res.add_class("nc_s")
-               return null
+               return super
        end
 end
 redef class AExpr