X-Git-Url: http://nitlanguage.org diff --git a/src/highlight.nit b/src/highlight.nit index 06075b4..d11b6f7 100644 --- a/src/highlight.nit +++ b/src/highlight.nit @@ -12,7 +12,7 @@ # 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 frontend @@ -29,30 +29,135 @@ class HighlightVisitor # Used to have a really huge and verbose HTML (mainly for debug) var with_ast = false is writable + # Prefixes used in generated IDs for line `` elements. + # Useful if more than one highlighted code is present in the same HTML document. + # + # If set to the empty string, id for lines are disabled. + # + # Is `"L"` by default. + var line_id_prefix = "L" is writable + # 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, 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] - var closes = new Array[Prod] var line = 0 var c: nullable Token = first_token var hv = new HighlightVisitor @@ -70,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 @@ -83,7 +186,8 @@ class HighlightVisitor # Add a div for the whole line var tag = new HTMLTag("span") - tag.attrs["id"] = "L{cline}" + var p = line_id_prefix + if p != "" then tag.attrs["id"] = "{p}{cline}" tag.classes.add "line" stack2.add(html) html.add tag @@ -98,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 @@ -112,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 @@ -155,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. @@ -165,10 +258,11 @@ 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 */ -pre.nitcode .foldable { display: block } /* for block productions*/ -pre.nitcode .line{ display: block } /* for lines */ -pre.nitcode .line:hover{ background-color: #FFFFE0; } /* current line */ +.nitcode .foldable { display: block } /* for block productions*/ +.nitcode .line{ display: block } /* for lines */ +.nitcode .line:hover{ background-color: #FFFFE0; } /* current line */ .nitcode :target { background-color: #FFF3C2 } /* target highlight*/ /* lexical raw tokens. independent of usage or semantic: */ .nitcode .nc_c { color: gray; font-style: italic; } /* comment */ @@ -321,9 +415,10 @@ redef class MModule return res end + # The module HTML page fun href: String do - return name + ".html" + return c_name + ".html" end redef fun linkto do return linkto_text(name) @@ -344,12 +439,14 @@ 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 if mdoc != null then mdoc.fill_infobox(res) + 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 @@ -376,6 +473,7 @@ redef class MClassDef return res end + # The class HTML page (an anchor in the module page) fun href: String do return mmodule.href + "#" + to_s @@ -396,11 +494,11 @@ redef class MPropDef var res = new HInfoBox(v, to_s) res.href = href if self isa MMethodDef then - res.new_field("fun").append(mproperty.name).add msignature.linkto + if msignature != null then res.new_field("fun").append(mproperty.name).add msignature.linkto else if self isa MAttributeDef then - res.new_field("fun").append(mproperty.name).add static_mtype.linkto + if static_mtype != null then res.new_field("fun").append(mproperty.name).add static_mtype.linkto else if self isa MVirtualTypeDef then - res.new_field("add").append(mproperty.name).add bound.linkto + if bound != null then res.new_field("add").append(mproperty.name).add bound.linkto else res.new_field("wat?").append(mproperty.name) end @@ -422,6 +520,7 @@ redef class MPropDef return res end + # The property HTML page (an anchor in the module page) fun href: String do return self.mclassdef.mmodule.href + "#" + self.to_s @@ -495,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 @@ -524,7 +650,6 @@ redef class MSignature end redef class CallSite - super HInfoBoxable redef fun infobox(v) do var res = new HInfoBox(v, "call {mpropdef}") @@ -585,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 @@ -596,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 @@ -654,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 @@ -664,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") @@ -674,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") @@ -681,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 @@ -697,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 @@ -709,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 @@ -717,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 @@ -760,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 @@ -773,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 @@ -782,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 @@ -795,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 @@ -826,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 @@ -835,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) @@ -844,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 @@ -853,10 +996,11 @@ 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 - if mt isa MVirtualType or mt isa MParameterType then + mt = mt.undecorate + if mt isa MFormalType then res.add_class("nc_vt") end return mt.infobox(v) @@ -865,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) @@ -873,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 @@ -883,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 @@ -902,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 @@ -921,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 @@ -932,4 +1076,3 @@ redef class AExpr return t.infobox(v) end end -