import html
import pipeline
import astutil
+import serialization
+
+# Fully process a content as a nit source file.
+fun hightlightcode(hl: HighlightVisitor, content: String): 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
+ tc.opt_warn.value = -1 # no output, obviously
+
+ # 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 = "<span class='glyphicon glyphicon-warning-sign lint-error-icon'></span> {{{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 `<span class"{type_of_node}">` element for each `ANode` of the AST?
+ # Should 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 = false is writable
# It is used to refer to some specific entities when generating links.
# If `null` is returned, then no link are generated and `<a>` elements become `<span>`.
#
- # Clients are encouraged to redefine the method in a subclass to control where entities should link to.
- fun hrefto(entitiy: MEntity): nullable String
- do
- return entitiy.href
- end
+ # 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(entity: MEntity): nullable String do return null
init
do
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
var stack = new Array[Prod]
var line = 0
var c: nullable Token = first_token
- var hv = new HighlightVisitor
+ var hv = self
while c != null do
var starting
end
# Add the token
- if c isa TEol then
+ if c isa TEol then
html.append "\n"
else
var tag = full_tag(c, hv)
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.
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 """<div class="dropdown"> <a data-toggle="dropdown" href="#"><b>"""
content.append(title)
content.add_raw_html "</b> "
- content.append(text)
+ if text_is_html == true then
+ content.add_raw_html(text)
+ else content.append(text)
content.add_raw_html """<span class="caret"></span></a>"""
var res = content.open("ul").add_class("dropdown-menu").attr("role", "menu").attr("aria-labelledby", "dLabel")
content.add_raw_html "</div>"
return (new HTMLTag("a")).attr("href", href).text(text)
end
- # Default link
- private fun href: nullable String do return null
+ # 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
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
return res
end
- # The module HTML page
- redef fun href: String
- do
- return c_name + ".html"
- end
-
redef fun linkto(v) do return linkto_text(v, name)
end
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
end
return res
end
-
- # The class HTML page (an anchor in the module page)
- redef fun href: String
- do
- return mmodule.href + "#" + to_s
- end
end
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)
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
return res
end
-
- # The property HTML page (an anchor in the module page)
- redef fun href: String
- do
- return self.mclassdef.mmodule.href + "#" + self.to_s
- end
end
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)
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)
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
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
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
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)
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
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
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
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