Merge: doc: fixed some typos and other misc. corrections
[nit.git] / src / highlight.nit
index 78d7a23..d2411fb 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Highliting of Nit AST
+# Highlighting of Nit AST
 module highlight
 
-import modelize_property
 import frontend
-import parser_util
-import html
+import astutil
+import console
 
 # Visitor used to produce a HTML tree based on a AST on a `Source`
-class HighlightVisitor
-       super Visitor
-
-       # The root of the HTML hierarchy
-       var html = new HTMLTag("span")
-
-       private var token_head: HTMLTag
-
-       private var prod_head: HTMLTag
-
-       private var prod_root: nullable HTMLTag
-
-       # Is the HTML include a nested `<span class"{type_of_node}">` element for each `ANode` of the AST?
-       # Used to have a really huge and verbose HTML (mainly for debug)
-       var with_ast writable = false
-
-       # Enter in a new node
-       # Exit is automatic
-       fun enter(n: HTMLTag)
-       do
-               if prod_root != null then
-                       prod_head.add(n)
-                       prod_head = n
+class AbstractHighlightVisitor
+       # The first line to generate, null if start at the first line
+       var first_line: nullable Int = null is writable
+
+       # 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, 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
+
+       # Highlight a AST element.
+       fun highlight_node(n: ANode)
+       do
+               n.parentize_tokens
+
+               var f
+               var l
+
+               if n isa Token then
+                       f = n
+                       l = n
                else
-                       prod_root = n
-                       prod_head = n
+                       assert n isa Prod
+                       f = n.first_token
+                       if f == null then return
+                       l = n.last_token
+                       if l == null then return
                end
-       end
-
-       # The position in the source file.
-       # Used to print parts of the source betwen tokens of the AST
-       private var pos = 0
 
-       init
-       do
-               html.add_class("nitcode")
-               token_head = html
-               prod_head = html
-       end
-
-       # Used to remember the first node, thus knowing when the whole visit is over
-       private var first_node: nullable ANode
-
-       private var seen_token = new HashSet[Token]
-
-       private fun process_upto_token(node: Token)
-       do
-               # recursively process previous tokens
-               var prev = node.prev_token
-               if prev != null and not seen_token.has(prev) then
-                       process_upto_token(prev)
-                       prev.accept_highlight_visitor(self)
-               end
-
-               # Add text between `last_token` and `node`
-               var pstart = node.location.pstart
-               if pos < pstart then
-                       var text = node.location.file.string.substring(pos, pstart-pos)
-                       token_head.append(text)
-                       #node.debug("WRT: {token_head.classes} << '{text.escape_to_c}' ")
-               end
-               pos = node.location.pend + 1
-               if pos < pstart then
-                       node.debug("pos={pos}, pstart={pstart}, pend={node.location.pend}")
+               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
 
-               seen_token.add node
-       end
-
-       # Dubuging method
-       private fun where(node: ANode, tag: String)
-       do
-               var pr = prod_root
-               if pr == null then
-                       node.debug "{tag}-> {token_head.classes} : {prod_head.classes}"
-               else
-                       node.debug "{tag}-> {token_head.classes} : {pr.classes}..{prod_head.classes}"
+               var line = first_line
+               if line != null then
+                       while f.location.line_start < line do
+                               f = f.next_token
+                               if f == null then return
+                       end
                end
-       end
-
-       redef fun visit(node)
-       do
-               if first_node == null then first_node = node
 
-               if node isa Token then
-                       process_upto_token(node)
-
-                       #where(node, "TOK")
-                       var pr = prod_root
-                       if pr != null then
-                               #node.debug("ADD: {token_head.classes} << {pr.classes} ")
-                               token_head.add(pr)
-                               token_head = prod_head
-                               prod_root = null
+               line = last_line
+               if line != null then
+                       while l.location.line_end > line do
+                               l = l.prev_token
+                               if l == null then return
                        end
                end
 
-               var oldph = prod_head
-               #where(node, " IN")
-               node.accept_highlight_visitor(self)
-               #where(node, "OUT")
-               var pr = prod_root
-               if pr == null then
-                       assert token_head == prod_head
-               else
-                       assert token_head != prod_head
-                       token_head.add(pr)
-                       prod_root = null
+               if include_whole_lines then
+                       f = f.first_real_token_in_line
+                       l = l.last_real_token_in_line
                end
-               prod_head = oldph
-               token_head = oldph
-               #where(node, " IS")
 
-               if node == first_node then
-                       html.append(node.location.file.string.substring_from(pos))
-               end
+               do_highlight(f, l)
        end
 
-       # Return a default CSS content related to CSS classes used in the `html` tree.
-       # Could be inlined in the `.html` file of saved as a specific `.css` file.
-       fun css_content: String
+       # Highlight a full lexed source file.
+       #
+       # REQUIRE `source.first_token != null`
+       fun highlight_source(source: SourceFile)
        do
-               return """
-.nitcode a { color: inherit; text-decoration: inherit; } /* hide links */
-.nitcode a:hover { text-decoration: underline; } /* underline links */
-.nitcode span[title]:hover { text-decoration: underline; } /* underline titles */
-/* lexical raw tokens. independent of usage or semantic: */
-.nitcode .nc_c { color: gray; font-style: italic; } /* comment */
-.nitcode .nc_d { color: #3D8127; font-style: italic; } /* documentation comments */
-.nitcode .nc_k { font-weight: bold; } /* keyword */
-.nitcode .nc_o {} /* operator */
-.nitcode .nc_i {} /* standard identifier */
-.nitcode .nc_t { color: #445588; font-weight: bold; } /* type/class identifier */
-.nitcode .nc_a { color: #445588; font-style: italic; } /* old style attribute identifier */
-.nitcode .nc_l { color: #009999; } /* char and number literal */
-.nitcode .nc_s { color: #8F1546; } /* string literal */
-/* syntactic token usage. added because of their position in the AST */
-.nitcode .nc_ast { color: blue; } /* assert label */
-.nitcode .nc_la { color: blue; } /* break/continue label */
-.nitcode .nc_m { color: #445588; } /* module name */
-/* syntactic groups */
-.nitcode .nc_def { font-weight: bold; color: blue; } /* name used in a definition */
-  .nitcode .nc_def.nc_a { color: blue; } /* name used in a attribute definition */
-  .nitcode .nc_def.nc_t { color: blue; } /* name used in a class or vt definition */
-.nitcode .nc_ss { color: #9E6BEB; } /* superstrings */
-.nitcode .nc_cdef {} /* A whole class definition */
-.nitcode .nc_pdef {} /* A whole property definition */
-/* semantic token usage */
-.nitcode .nc_v { font-style: italic; } /* local variable or parameter */
-.nitcode .nc_vt { font-style: italic; } /* virtual type or formal type */
-
-.nitcode .nc_error { border: 1px red solid;} /* not used */
-"""
+               do_highlight(source.first_token.as(not null), null)
        end
-end
 
-redef class ANode
-       private fun accept_highlight_visitor(v: HighlightVisitor)
-       do
-               if v.with_ast then
-                       var res = new HTMLTag("span")
-                       res.add_class(class_name)
-                       v.enter res
-               end
-               visit_all(v)
-       end
-       private fun decorate_tag(res: HTMLTag, token: Token)
-       do
-               #debug("no decoration for {token.inspect}")
-               #res.add_class("nc_error")
-       end
+       # Low-level highlighting between 2 tokens
+       protected fun do_highlight(first_token: Token, last_token: nullable Token) is abstract
 end
 
-redef class AStdClassdef
-       redef fun accept_highlight_visitor(v)
-       do
-               var res = new HTMLTag("span")
-               res.add_class("nc_cdef")
-               var md = mclassdef
-               if md != null then
-                       var a = new HTMLTag("a")
-                       a.attr("id", md.to_s)
-                       res.add(a)
-               end
-               v.enter res
-               super
-       end
-       redef fun decorate_tag(res, token)
-       do
-               res.add_class("nc_def")
-
-               var md = mclassdef
-               if md == null then return
-               var mc = md.mclass
-               res.attrs["title"] = mc.full_name
-               var mi = mc.intro
-               if md != mi then
-                       res.attrs["link"] = mi.mmodule.name + ".html#" + mi.to_s
-               end
-       end
-end
-redef class APropdef
-       redef fun accept_highlight_visitor(v)
-       do
-               var res = new HTMLTag("span")
-               res.add_class("nc_pdef")
-               var mpd
-               mpd = mpropdef
-               if mpd != null then res.add(tag(mpd))
-               if self isa AAttrPropdef then
-                       mpd = mreadpropdef
-                       if mpd != null then res.add(tag(mpd))
-                       mpd = mwritepropdef
-                       if mpd != null then res.add(tag(mpd))
-               end
-               v.enter res
-               super
-       end
+# Text-based highlighter that use ANSI escape sequence for colors
+class AnsiHighlightVisitor
+       super AbstractHighlightVisitor
 
-       private fun tag(mpd: MPropDef): HTMLTag
-       do
-               var a = new HTMLTag("a")
-               a.attr("id", mpd.to_s)
-               return a
-       end
-end
+       # The produced highlighting
+       var result = new Template
 
-redef class Token
-       # Produce an HTMLTag with the correct contents and CSS classes
-       # Subclasses can redefine it to decorate the tag
-       protected fun make_tag(v: HighlightVisitor): HTMLTag
+       redef fun do_highlight(f, l)
        do
-               var res = new HTMLTag("span")
-               res.text(text)
-               return res
-       end
+               var c
+               c = f
+               while c != null do
+                       if c != f then result.add(c.blank_before)
+                       result.add c.ansi_colored
 
-       # Use `empty_tag` to create the tag ; then fill it and add it to the html
-       redef fun accept_highlight_visitor(v)
-       do
-               var n = make_tag(v)
-               if n.attrs.is_empty and n.classes.is_empty then
-                       for c in n.children do
-                               v.token_head.add(c)
+                       if c == l then
+                               c = null
+                       else
+                               c = c.next_token
                        end
-               else if n.attrs.has_key("link") then
-                       var a = new HTMLTag("a")
-                       a.attrs["href"] = n.attrs["link"]
-                       n.attrs.keys.remove("link")
-                       a.add(n)
-                       v.token_head.add(a)
-               else
-                       v.token_head.add(n)
                end
-               #debug("WRT: {v.token_head.classes} << '{text.escape_to_c}' ")
-       end
-end
-redef class TokenKeyword
-       redef fun make_tag(v)
-       do
-               var res = super
-               res.add_class("nc_k")
-               return res
-       end
-end
-redef class TokenOperator
-       redef fun make_tag(v)
-       do
-               var res = super
-               var p = parent
-               if p != null then p.decorate_tag(res, self)
-               res.add_class("nc_o")
-               return res
-       end
-end
-
-redef class Variable
-       private fun decorate_tag(res: HTMLTag, token: Token)
-       do
-               if declared_type == null then return
-               res.attrs["title"] = name + ": " + declared_type.to_s
-       end
-end
-
-redef class AVarFormExpr
-       redef fun decorate_tag(res, token)
-       do
-               res.add_class("nc_v")
-               var variable = self.variable
-               if variable == null then return
-               variable.decorate_tag(res, token)
-       end
-end
-
-redef class AVardeclExpr
-       redef fun decorate_tag(res, token)
-       do
-               res.add_class("nc_v")
-               var variable = self.variable
-               if variable == null then return
-               variable.decorate_tag(res, token)
-       end
-end
-
-redef class AForExpr
-       redef fun decorate_tag(res, token)
-       do
-               res.add_class("nc_v")
-               var vs = variables
-               if vs == null then return
-               var idx = n_ids.index_of(token.as(TId))
-               var variable = vs[idx]
-               variable.decorate_tag(res, token)
-       end
-end
-
-redef class AParam
-       redef fun decorate_tag(res, token)
-       do
-               res.add_class("nc_v")
-               var mp = mparameter
-               if mp == null then return
-               res.attrs["title"] = mp.name + ": " + mp.mtype.to_s
-       end
-end
-
-redef class AAssertExpr
-       redef fun decorate_tag(res, token)
-       do
-               res.add_class("nc_ast")
-       end
-end
-
-redef class ALabel
-       redef fun decorate_tag(res, token)
-       do
-               res.add_class("nc_la")
-       end
-end
-
-redef class ASendExpr
-       redef fun decorate_tag(res, token)
-       do
-               if callsite == null then return
-               var mpropdef = callsite.mpropdef
-               res.attrs["title"] = mpropdef.to_s + callsite.msignature.to_s
-               res.attrs["link"] = mpropdef.mclassdef.mmodule.name + ".html#" + mpropdef.to_s
        end
 end
 
-redef class ANewExpr
-       redef fun decorate_tag(res, token)
-       do
-               if callsite == null then return
-               var mpropdef = callsite.mpropdef
-               res.attrs["title"] = mpropdef.to_s + callsite.msignature.to_s
-               res.attrs["link"] = mpropdef.mclassdef.mmodule.name + ".html#" + mpropdef.to_s
-       end
-end
-
-redef class AAssignOp
-       redef fun decorate_tag(res, v)
-       do
-               var p = parent
-               assert p isa AReassignFormExpr
-
-               var callsite = p.reassign_callsite
-               if callsite == null then return
-               var mpropdef = callsite.mpropdef
-               res.attrs["title"] = mpropdef.to_s + callsite.msignature.to_s
-               res.attrs["link"] = mpropdef.mclassdef.mmodule.name + ".html#" + mpropdef.to_s
-       end
-end
-
-redef class AModuleName
-       redef fun decorate_tag(res, token)
-       do
-               parent.decorate_tag(res, token)
-       end
-end
-
-redef class AModuledecl
-       redef fun decorate_tag(res, token)
-       do
-               res.add_class("nc_def")
-               res.add_class("nc_m")
-               var p = parent
-               assert p isa AModule
-               var mm = p.mmodule
-               if mm == null then return
-               res.attrs["title"] = mm.full_name
-       end
+redef class Token
+       # Return the ANSI colored text
+       fun ansi_colored: String do return text
 end
 
-redef class AStdImport
-       redef fun decorate_tag(res, token)
-       do
-               res.add_class("nc_m")
-               var mm = mmodule
-               if mm == null then return
-               res.attrs["title"] = mm.full_name
-               res.attrs["link"] = mm.name + ".html"
-       end
+redef class TComment
+       redef fun ansi_colored do return super.blue
 end
 
-redef class AAttrPropdef
-       redef fun decorate_tag(res, token)
-       do
-               res.add_class("nc_def")
-               var mpd: nullable MPropDef
-               mpd = mreadpropdef
-               if mpd == null then mpd = mpropdef
-               if mpd == null then return
-               var mp = mpd.mproperty
-               res.attrs["title"] = mp.full_name
-               if mp.intro != mpd then
-                       mpd = mp.intro
-                       res.attrs["link"] = mpd.mclassdef.mmodule.name + ".html#" + mpd.to_s
-               end
-       end
+redef class TokenKeyword
+       redef fun ansi_colored do return super.yellow
 end
 
-redef class TId
-       redef fun make_tag(v)
-       do
-               var res = super
-               var p = parent
-               if p != null then p.decorate_tag(res, self)
-               res.add_class("nc_i")
-               return res
-       end
-end
-redef class AMethid
-       redef fun accept_highlight_visitor(v)
-       do
-               var res = new HTMLTag("span")
-               res.add_class("nc_def")
-               var p = parent
-               if p isa AMethPropdef then
-                       var mpd = p.mpropdef
-                       if mpd != null then
-                               var mp = mpd.mproperty
-                               res.attr("title", mp.full_name)
-                               if mp.intro != mpd then
-                                       mpd = mp.intro
-                                       var link = mpd.mclassdef.mmodule.name + ".html#" + mpd.to_s
-                                       var l = new HTMLTag("a")
-                                       l.attr("href", link)
-                                       v.enter l
-                               end
-                       end
-               end
-               v.enter res
-               super
-       end
-       redef fun decorate_tag(res, v)
-       do
-               # nothing to decorate
-       end
-end
-redef class TAttrid
-       redef fun make_tag(v)
-       do
-               var res = super
-               var p = parent
-               if p != null then p.decorate_tag(res, self)
-               res.add_class("nc_a")
-               return res
-       end
-end
-redef class AAttrFormExpr
-       redef fun decorate_tag(res, v)
-       do
-               var p = mproperty
-               if p == null then return
-               res.attrs["title"] = p.full_name
-               var pi = p.intro
-               res.attrs["link"] = pi.mclassdef.mmodule.name + ".html#" + pi.to_s
-       end
-end
 redef class TClassid
-       redef fun make_tag(v)
-       do
-               var res = super
-               var p = parent
-               if p != null then p.decorate_tag(res, self)
-               res.add_class("nc_t")
-               return res
-       end
-end
-redef class AType
-       redef fun decorate_tag(res, token)
-       do
-               var mt = mtype
-               if mt == null then return
-               var title = mt.to_s
-               if mt isa MNullableType then mt = mt.mtype
-               if mt isa MVirtualType or mt isa MParameterType then
-                       res.add_class("nc_vt")
-               else if mt isa MClassType then
-                       title = mt.mclass.full_name
-                       res.attrs["link"] = mt.mclass.intro.mmodule.name + ".html#" + mt.mclass.intro.to_s
-               end
-               res.attrs["title"] = title
-       end
-end
-redef class AFormaldef
-       redef fun decorate_tag(res, token)
-       do
-               res.add_class("nc_vt")
-               if mtype == null then return
-               res.attrs["title"] = "{mtype.to_s}: {bound.to_s}"
-       end
-end
-redef class ATypePropdef
-       redef fun decorate_tag(res, token)
-       do
-               res.add_class("nc_def")
-               var md = mpropdef
-               if md == null then return
-               var mp = mpropdef.mproperty
-               res.attrs["title"] = mp.full_name
-               var mi = mp.intro
-               if md != mi then
-                       res.attrs["link"] = mi.mclassdef.mmodule.name + ".html#" + mi.to_s
-               end
-       end
-end
-redef class TComment
-       redef fun make_tag(v)
-       do
-               var res = super
-               if parent == null then
-                       res.add_class("nc_c")
-               else
-                       assert parent isa ADoc
-               end
-               return res
-       end
-end
-redef class ADoc
-       redef fun accept_highlight_visitor(v)
-       do
-               var res = new HTMLTag("span")
-               res.add_class("nc_d")
-               v.enter res
-               super
-       end
+       redef fun ansi_colored do return super.green
 end
+
 redef class TokenLiteral
-       redef fun make_tag(v)
-       do
-               var res = super
-               res.add_class("nc_l")
-               var p = parent
-               if p isa AStringFormExpr then p.decorate_tag(res, self)
-               return res
-       end
-end
-redef class ASuperstringExpr
-       redef fun accept_highlight_visitor(v)
-       do
-               var res = new HTMLTag("span")
-               res.add_class("nc_ss")
-               v.enter res
-               super
-       end
+       redef fun ansi_colored do return super.red
 end
-redef class AStringFormExpr
-       redef fun decorate_tag(res, v)
-       do
-               # Workarount to tag strings
-               res.classes.remove("nc_l")
-               res.add_class("nc_s")
-       end
-end
-