X-Git-Url: http://nitlanguage.org diff --git a/src/highlight.nit b/src/highlight.nit index 78d7a23..d2411fb 100644 --- a/src/highlight.nit +++ b/src/highlight.nit @@ -12,589 +12,146 @@ # 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 `` 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 -