X-Git-Url: http://nitlanguage.org diff --git a/src/highlight.nit b/src/highlight.nit index 6399c5e..da18cce 100644 --- a/src/highlight.nit +++ b/src/highlight.nit @@ -19,13 +19,113 @@ import frontend import html import pipeline import astutil +import serialization + +# Fully process `content` as a Nit source file. +# +# Set `print_errors = true` to print errors in the code to the console. +fun hightlightcode(hl: HighlightVisitor, content: String, print_errors: nullable Bool): HLCode +do + # Prepare a stand-alone tool context + var tc = new ToolContext + tc.nit_dir = tc.locate_nit_dir # still use the common lib to have core + tc.keep_going = true # no exit, obviously + if print_errors != true then tc.opt_warn.value = -1 # no output + + # Prepare an stand-alone model and model builder. + # Unfortunately, models are enclosing and append-only. + # There is no way (yet?) to have a shared module `core` with + # isolated and throwable user modules. + var model = new Model + var mb = new ModelBuilder(model, tc) + + # Parse the code + var source = new SourceFile.from_string("", content + "\n") + var lexer = new Lexer(source) + var parser = new Parser(lexer) + var tree = parser.parse + + var hlcode = new HLCode(hl, content, source) + + # Check syntax error + var eof = tree.n_eof + if eof isa AError then + mb.error(eof, eof.message) + hl.hightlight_source(source) + return hlcode + end + var amodule = tree.n_base.as(not null) + + # Load the AST as a module in the model + # Then process it + mb.load_rt_module(null, amodule, "") + mb.run_phases + + # Highlight the processed module + hl.enter_visit(amodule) + return hlcode +end + +# A standalone highlighted piece of code +class HLCode + super Serializable + + # The highlighter used + var hl: HighlightVisitor + + # The raw code source + var content: String + + # The pseudo source-file + var source: SourceFile + + # JavaScript code to update an existing codemirror editor. + fun code_mirror_update: Template + do + + var res = new Template + res.add """ + function nitmessage() { + editor.operation(function(){ + for (var i = 0; i < widgets.length; ++i) + editor.removeLineWidget(widgets[i]); + widgets.length = 0; +""" + + for m in source.messages do + res.add """ + var l = document.createElement("div"); + l.className = "lint-error" + l.innerHTML = " {{{m.text.html_escape}}}"; + var w = editor.addLineWidget({{{m.location.line_start-1}}}, l); + widgets.push(w); +""" + end + res.add """});}""" + return res + end + + redef fun core_serialize_to(v) + do + v.serialize_attribute("code", hl.html.write_to_string) + var msgs = new Array[Map[String, Serializable]] + for m in source.messages do + var o = new Map[String, Serializable] + msgs.add o + o["line"] = m.location.line_start-1 + o["message"] = m.text + end + v.serialize_attribute("messages", msgs) + end +end # Visitor used to produce a HTML tree based on a AST on a `Source` class HighlightVisitor # The root of the HTML hierarchy var html = new HTMLTag("span") - # Is the HTML include a nested `` element for each `ANode` of the AST? + # Should 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 = false is writable @@ -62,7 +162,7 @@ class HighlightVisitor # # By default, `null` is returned. # Clients are therefore encouraged to redefine the method in a subclass to control where entities should link to. - fun hrefto(entitiy: MEntity): nullable String do return null + fun hrefto(entity: MEntity): nullable String do return null init do @@ -88,9 +188,8 @@ class HighlightVisitor # 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) + # Highlight a AST element. + fun highlight_node(n: ANode) do n.parentize_tokens @@ -113,12 +212,28 @@ class HighlightVisitor if l.next_looses.not_empty then l = l.next_looses.last end + 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 + + 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 + if include_whole_lines then f = f.first_real_token_in_line l = l.last_real_token_in_line end - htmlize(f, l) + do_highlight(f, l) end private fun full_tag(anode: ANode, hv: HighlightVisitor): nullable HTMLTag @@ -157,13 +272,13 @@ class HighlightVisitor # Highlight a full lexed source file. # # REQUIRE `source.first_token != null` - fun hightlight_source(source: SourceFile) + fun highlight_source(source: SourceFile) do - htmlize(source.first_token.as(not null), null) + do_highlight(source.first_token.as(not null), null) end - # Produce HTML between two tokens - protected fun htmlize(first_token: Token, last_token: nullable Token) + # Low-level highlighting between 2 tokens + protected fun do_highlight(first_token: Token, last_token: nullable Token) do var stack2 = new Array[HTMLTag] var stack = new Array[Prod] @@ -220,7 +335,7 @@ class HighlightVisitor end # Add the token - if c isa TEol then + if c isa TEol then html.append "\n" else var tag = full_tag(c, hv) @@ -257,8 +372,7 @@ class HighlightVisitor c = n end - #assert stack.is_empty - #assert stack2.is_empty + if not stack2.is_empty then html = stack2.first end # Return a default CSS content related to CSS classes used in the `html` tree. @@ -362,12 +476,14 @@ class HInfoBox end # Append a new dropdown in the popuped content - fun new_dropdown(title, text: String): HTMLTag + fun new_dropdown(title, text: String, text_is_html: nullable Bool): HTMLTag do content.add_raw_html """" @@ -425,6 +541,13 @@ redef class MEntity end return (new HTMLTag("a")).attr("href", href).text(text) end + + # Append an entry for the doc in the given infobox + private fun add_doc_to_infobox(res: HInfoBox) + do + var mdoc = mdoc_or_fallback + if mdoc != null then mdoc.fill_infobox(res) + end end redef class MModule @@ -433,8 +556,7 @@ redef class MModule var res = new HInfoBox(v, "module {name}") res.href = v.hrefto(self) res.new_field("module").add(linkto(v)) - var mdoc = self.mdoc - if mdoc != null then mdoc.fill_infobox(res) + add_doc_to_infobox(res) if in_importation.greaters.length > 1 then var c = res.new_dropdown("imports", "{in_importation.greaters.length-1} modules") for x in in_importation.greaters do @@ -459,10 +581,9 @@ redef class MClassDef res.new_field("redef class").text(mclass.name) res.new_field("intro").add mclass.intro.linkto_text(v, "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) + add_doc_to_infobox(res) + var in_hierarchy = self.in_hierarchy if in_hierarchy == null then return res if in_hierarchy.greaters.length > 1 then @@ -498,10 +619,13 @@ redef class MPropDef var res = new HInfoBox(v, to_s) res.href = v.hrefto(self) if self isa MMethodDef then + var msignature = self.msignature if msignature != null then res.new_field("fun").append(mproperty.name).add msignature.linkto(v) else if self isa MAttributeDef then + var static_mtype = self.static_mtype if static_mtype != null then res.new_field("fun").append(mproperty.name).add static_mtype.linkto(v) else if self isa MVirtualTypeDef then + var bound = self.bound if bound != null then res.new_field("add").append(mproperty.name).add bound.linkto(v) else res.new_field("wat?").append(mproperty.name) @@ -511,9 +635,7 @@ redef class MPropDef else res.new_field("intro").add mproperty.intro.linkto_text(v, "in {mproperty.intro.mclassdef}") end - var mdoc = self.mdoc - if mdoc == null then mdoc = mproperty.intro.mdoc - if mdoc != null then mdoc.fill_infobox(res) + add_doc_to_infobox(res) if mproperty.mpropdefs.length > 1 then var c = res.new_dropdown("redef", "redefinitions") for x in mproperty.mpropdefs do @@ -531,9 +653,7 @@ redef class MClassType var res = new HInfoBox(v, to_s) res.href = v.hrefto(self) res.new_field("class").add mclass.intro.linkto(v) - var mdoc = mclass.mdoc - if mdoc == null then mdoc = mclass.intro.mdoc - if mdoc != null then mdoc.fill_infobox(res) + add_doc_to_infobox(res) return res end redef fun linkto(v) @@ -547,10 +667,8 @@ redef class MVirtualType var res = new HInfoBox(v, to_s) res.href = v.hrefto(mproperty) var p = mproperty - var pd = p.intro - res.new_field("virtual type").add pd.linkto(v) - var mdoc = pd.mdoc - if mdoc != null then mdoc.fill_infobox(res) + res.new_field("virtual type").add p.intro.linkto(v) + add_doc_to_infobox(res) return res end redef fun linkto(v) @@ -645,9 +763,7 @@ redef class CallSite else res.new_field("intro").add mproperty.intro.linkto_text(v, "in {mproperty.intro.mclassdef}") end - var mdoc = mpropdef.mdoc - if mdoc == null then mdoc = mproperty.intro.mdoc - if mdoc != null then mdoc.fill_infobox(res) + add_doc_to_infobox(res) return res end @@ -696,6 +812,8 @@ redef class AQclassid redef fun decorate_tag(v, res, token) do if token != n_id then return null + var parent = self.parent + if parent == null then return null return parent.decorate_tag(v, res, token) end end @@ -704,6 +822,8 @@ redef class AQid redef fun decorate_tag(v, res, token) do if token != n_id then return null + var parent = self.parent + if parent == null then return null return parent.decorate_tag(v, res, token) end end @@ -758,7 +878,7 @@ end redef class Token # Produce an HTMLTag with the correct contents and CSS classes # Subclasses can redefine it to decorate the tag - redef fun make_tag(v: HighlightVisitor): HTMLTag + redef fun make_tag(v): HTMLTag do var res = new HTMLTag("span") res.text(text) @@ -852,6 +972,7 @@ end redef class ASendExpr redef fun decorate_tag(v, res, token) do + var callsite = self.callsite if callsite == null then return null return callsite.infobox(v) end @@ -860,6 +981,7 @@ end redef class ANewExpr redef fun decorate_tag(v, res, token) do + var callsite = self.callsite if callsite == null then return null return callsite.infobox(v) end @@ -880,7 +1002,9 @@ end redef class AModuleName redef fun decorate_tag(v, res, token) do - return parent.decorate_tag(v, res, token) + var p = parent + if p == null then return null + return p.decorate_tag(v, res, token) end end @@ -993,6 +1117,7 @@ redef class AFormaldef do if not token isa TClassid then return null res.add_class("nc_vt") + var mtype = self.mtype if mtype == null then return null return mtype.infobox(v) end