Property definitions

nitc $ HtmlightVisitor :: defaultinit
# Visitor used to produce a HTML tree based on a AST on a `Source`
class HtmlightVisitor
	super AbstractHighlightVisitor

	# The root of the HTML hierarchy
	var html = new HTMLTag("span")

	# 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

	# Prefixes used in generated IDs for line `<span>` 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

	# 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

	# A reference to an entity used in generated `<a>` elements.
	#
	# 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>`.
	#
	# 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
		html.add_class("nitcode")
	end

	private fun full_tag(anode: ANode): nullable HTMLTag
	do
		var tag = anode.make_tag(self)
		if tag == null then return null
		var infobox = anode.infobox(self)
		if infobox == null and anode isa Token then
			var pa = anode.parent
			if pa != null then
				infobox = pa.decorate_tag(self, tag, anode)
			end
		end
		if infobox != null and not show_infobox then
			var href = infobox.href
			if href != null then
				# If there is an href, we inject a link around
				var tag2 = new HTMLTag("a")
				tag2.add tag
				tag = tag2
				tag.attr("href", href)
			end
			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(self, "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

	# Low-level highlighting between 2 tokens
	redef fun do_highlight(first_token, last_token)
	do
		var stack2 = new Array[HTMLTag]
		var stack = new Array[Prod]
		var line = 0
		var c: nullable Token = first_token
		while c != null do
			var starting

			# Handle start of line
			var cline = c.location.line_start
			if cline != line then
				# Handle starting block productions,
				# Because c could be a detached token, get prods in
				# the first AST token
				var c0 = c.first_token_in_line
				starting = null
				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 = full_tag(p)
					if tag == null then continue
					tag.add_class("foldable")
					stack2.add(html)
					html.add tag
					html = tag
					stack.add(p)
				end

				# Add a div for the whole line
				var tag = new HTMLTag("span")
				var p = line_id_prefix
				if p != "" then tag.attrs["id"] = "{p}{cline}"
				tag.classes.add "line"
				stack2.add(html)
				html.add tag
				html = tag
				line = cline
			end

			# Add the blank, verbatim
			html.add_raw_html c.blank_before

			# Handle starting span production
			starting = c.starting_prods
			if starting != null then for p in starting do
				if not p.is_span then continue
				var tag = full_tag(p)
				if tag == null then continue
				stack2.add(html)
				html.add tag
				html = tag
				stack.add(p)
			end

			# Add the token
			if c isa TEol then
				html.append "\n"
			else
				var tag = full_tag(c)
				if tag != null then html.add tag
			end

			# Handle ending span productions
			var ending = c.ending_prods
			if ending != null then for p in ending do
				if not p.is_span then continue
				if stack.is_empty or p != stack.last then continue
				stack.pop
				html = stack2.pop
			end

			# Handle end of line and end of file
			var n = c.next_token
			if c == last_token then n = null
			if n == null or n.location.line_start != line  then
				# closes the line div
				html = stack2.pop

				# close the block production divs
				var c0 = c.last_token_in_line
				ending = null
				if c0 != null then ending = c0.ending_prods
				if ending != null then for p in ending do
					if not p.is_block then continue
					if stack.is_empty or p != stack.last then continue
					stack.pop
					html = stack2.pop
				end
			end

			c = n
		end
		if not stack2.is_empty then html = stack2.first
	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
	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 */
.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 */
.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 */
.popover { max-width: 800px !important; }
"""
	end

	# Additional content to inject in the <head> tag
	# Note: does not include `css_content`; handle it yourself.
	fun head_content: String
	do
		return """<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">\n"""
	end

	# Additional content to inject just before the closing </body> tag
	fun foot_content: String
	do
		return """
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<script>$(".popupable").popover({html:true, placement:'top'})/*initialize bootstrap popover*/</script>"""
	end

	# Fully process `content` as a Nit source file.
	#
	# Set `print_errors = true` to print errors in the code to the console.
	fun highlightcode(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(self, content, source)

		# Check syntax error
		var eof = tree.n_eof
		if eof isa AError then
			mb.error(eof, eof.message)
			highlight_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
		highlight_node(amodule)
		return hlcode
	end
end
src/htmlight.nit:76,1--357,3