# 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
-