+
+ 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)