1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Highlighting of Nit AST
23 # Visitor used to produce a HTML tree based on a AST on a `Source`
24 class HighlightVisitor
25 # The root of the HTML hierarchy
26 var html
= new HTMLTag("span")
28 # Is the HTML include a nested `<span class"{type_of_node}">` element for each `ANode` of the AST?
29 # Used to have a really huge and verbose HTML (mainly for debug)
30 var with_ast
= false is writable
32 # Prefixes used in generated IDs for line `<span>` elements.
33 # Useful if more than one highlighted code is present in the same HTML document.
35 # If set to the empty string, id for lines are disabled.
37 # Is `"L"` by default.
38 var line_id_prefix
= "L" is writable
40 # The first line to generate, null if start at the first line
41 var first_line
: nullable Int = null is writable
43 # The last line to generate, null if finish at the last line
44 var last_line
: nullable Int = null is writable
48 html
.add_class
("nitcode")
51 # The entry-point of the highlighting.
52 # Will fill `html` with the generated HTML content.
53 fun enter_visit
(n
: ANode)
56 var s
= n
.location
.file
57 htmlize
(s
.first_token
.as(not null), s
.last_token
.as(not null))
60 private fun full_tag
(anode
: ANode, hv
: HighlightVisitor): nullable HTMLTag
62 var tag
= anode
.make_tag
(hv
)
63 if tag
== null then return null
64 var infobox
= anode
.infobox
(hv
)
65 if infobox
== null and anode
isa Token then
69 if c
isa TId or c
isa TClassid or c
isa TAttrid or c
isa TokenLiteral or c
isa TokenOperator or c
isa TComment and pa
isa ADoc then
70 infobox
= pa
.decorate_tag
(hv
, tag
, anode
)
74 var messages
= anode
.location
.messages
75 if messages
!= null then
76 tag
.css
("border-bottom", "solid 2px red")
77 if infobox
== null then
78 infobox
= new HInfoBox(hv
, "Messages")
80 var c
= infobox
.new_dropdown
("{messages.length} message(s)", "")
82 c
.open
("li").append
(m
.text
)
85 if infobox
!= null then
86 tag
.attach_infobox
(infobox
)
91 # Produce HTML between two tokens
92 protected fun htmlize
(first_token
, last_token
: Token)
94 var stack2
= new Array[HTMLTag]
95 var stack
= new Array[Prod]
97 var c
: nullable Token = first_token
98 var hv
= new HighlightVisitor
102 # Handle start of line
103 var cline
= c
.location
.line_start
104 if cline
!= line
then
105 # Handle starting block productions,
106 # Because c could be a detached token, get prods in
107 # the first AST token
108 var c0
= c
.first_token_in_line
110 if c0
!= null then starting
= c0
.starting_prods
111 if starting
!= null then for p
in starting
do
112 if not p
.is_block
then continue
113 var tag
= full_tag
(p
, hv
)
114 if tag
== null then continue
115 tag
.add_class
("foldable")
122 # Add a div for the whole line
123 var tag
= new HTMLTag("span")
124 var p
= line_id_prefix
125 if p
!= "" then tag
.attrs
["id"] = "{p}{cline}"
126 tag
.classes
.add
"line"
133 # Add the blank, verbatim
134 html
.add_raw_html c
.blank_before
136 # Handle starting span production
137 starting
= c
.starting_prods
138 if starting
!= null then for p
in starting
do
139 if not p
.is_span
then continue
140 var tag
= full_tag
(p
, hv
)
141 if tag
== null then continue
152 var tag
= full_tag
(c
, hv
)
153 if tag
!= null then html
.add tag
156 # Handle ending span productions
157 var ending
= c
.ending_prods
158 if ending
!= null then for p
in ending
do
159 if not p
.is_span
then continue
160 if stack
.is_empty
or p
!= stack
.last
then continue
165 # Handle end of line and end of file
167 if c
== last_token
then n
= null
168 if n
== null or n
.location
.line_start
!= line
then
169 # closes the line div
172 # close the block production divs
173 var c0
= c
.last_token_in_line
175 if c0
!= null then ending
= c0
.ending_prods
176 if ending
!= null then for p
in ending
do
177 if not p
.is_block
then continue
178 if stack
.is_empty
or p
!= stack
.last
then continue
186 #assert stack.is_empty
187 #assert stack2.is_empty
190 # Return a default CSS content related to CSS classes used in the `html` tree.
191 # Could be inlined in the `.html` file of saved as a specific `.css` file.
192 fun css_content
: String
195 .nitcode a { color: inherit; cursor:pointer; }
196 .nitcode .popupable:hover { text-decoration: underline; cursor:help; } /* underline titles */
197 .nitcode .foldable { display: block } /* for block productions*/
198 .nitcode .line{ display: block } /* for lines */
199 .nitcode .line:hover{ background-color: #FFFFE0; } /* current line */
200 .nitcode :target { background-color: #FFF3C2 } /* target highlight*/
201 /* lexical raw tokens. independent of usage or semantic: */
202 .nitcode .nc_c { color: gray; font-style: italic; } /* comment */
203 .nitcode .nc_d { color: #3D8127; font-style: italic; } /* documentation comments */
204 .nitcode .nc_k { font-weight: bold; } /* keyword */
205 .nitcode .nc_o {} /* operator */
206 .nitcode .nc_i {} /* standard identifier */
207 .nitcode .nc_t { color: #445588; font-weight: bold; } /* type/class identifier */
208 .nitcode .nc_a { color: #445588; font-style: italic; } /* old style attribute identifier */
209 .nitcode .nc_l { color: #009999; } /* char and number literal */
210 .nitcode .nc_s { color: #8F1546; } /* string literal */
211 /* syntactic token usage. added because of their position in the AST */
212 .nitcode .nc_ast { color: blue; } /* assert label */
213 .nitcode .nc_la { color: blue; } /* break/continue label */
214 .nitcode .nc_m { color: #445588; } /* module name */
215 /* syntactic groups */
216 .nitcode .nc_def { font-weight: bold; color: blue; } /* name used in a definition */
217 .nitcode .nc_def.nc_a { color: blue; } /* name used in a attribute definition */
218 .nitcode .nc_def.nc_t { color: blue; } /* name used in a class or vt definition */
219 .nitcode .nc_ss { color: #9E6BEB; } /* superstrings */
220 .nitcode .nc_cdef {} /* A whole class definition */
221 .nitcode .nc_pdef {} /* A whole property definition */
222 /* semantic token usage */
223 .nitcode .nc_v { font-style: italic; } /* local variable or parameter */
224 .nitcode .nc_vt { font-style: italic; } /* virtual type or formal type */
226 .nitcode .nc_error { border: 1px red solid;} /* not used */
227 .popover { max-width: 800px !important; }
231 # Additional content to inject in the <head> tag
232 # Note: does not include `css_content`; handle it yourself.
233 fun head_content
: String
235 return """<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">\n"""
238 # Additional content to inject just before the closing </body> tag
239 fun foot_content
: String
242 <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
243 <script src="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
244 <script>$(".popupable").popover({html:true, placement:'top'})/*initialize bootstrap popover*/</script>"""
249 # Attach the infobox to the node by using BootStrap popover
250 fun attach_infobox
(infobox
: HInfoBox)
252 classes
.add
("popupable")
253 attrs
["title"] = infobox
.title
254 var href
= infobox
.href
256 attrs
["data-title"] = """<a href="{{{href}}}">{{{infobox.title}}}</a>"""
258 attrs
["data-content"] = infobox
.content
.write_to_string
259 attrs
["data-toggle"] = "popover"
264 # A generic information container that can be used to decorate AST entities
266 # The visitor used for contextualisation, if needed
267 var visitor
: HighlightVisitor
269 # A short title for the AST element
272 # The primary link where the entity points
274 var href
: nullable String = null
276 # The content of the popuped infobox
277 var content
= new HTMLTag("div")
279 # Append a new field in the popuped infobox
280 fun new_field
(title
: String): HTMLTag
282 content
.open
("b").text
(title
)
284 var res
= content
.open
("span")
289 # Append a new dropdown in the popuped content
290 fun new_dropdown
(title
, text
: String): HTMLTag
292 content
.add_raw_html
"""<div class="dropdown"> <a data-toggle="dropdown" href="#"><b>"""
293 content
.append
(title
)
294 content
.add_raw_html
"</b> "
296 content
.add_raw_html
"""<span class="caret"></span></a>"""
297 var res
= content
.open
("ul").add_class
("dropdown-menu").attr
("role", "menu").attr
("aria-labelledby", "dLabel")
298 content
.add_raw_html
"</div>"
305 # Model entity or whatever that can produce an infobox
306 interface HInfoBoxable
307 # An new infobox documenting the entity
308 fun infobox
(v
: HighlightVisitor): HInfoBox is abstract
310 # A human-readable hyper-text for the entity
311 fun linkto
: HTMLTag is abstract
315 # Append an entry for the doc in the given infobox
316 fun fill_infobox
(res
: HInfoBox)
318 if content
.length
< 2 then
319 res
.new_field
("doc").text
(content
.first
)
322 var c
= res
.new_dropdown
("doc", content
.first
)
323 for x
in content
.iterator
.skip_head
(1) do
325 c
.add_raw_html
"<br>"
337 var res
= new HInfoBox(v
, "module {name}")
339 res
.new_field
("module").add
(linkto
)
341 if mdoc
!= null then mdoc
.fill_infobox
(res
)
342 if in_importation
.greaters
.length
> 1 then
343 var c
= res
.new_dropdown
("imports", "{in_importation.greaters.length-1} modules")
344 for x
in in_importation
.greaters
do
345 if x
== self then continue
346 c
.open
("li").add x
.linkto
352 # The module HTML page
355 return name
+ ".html"
358 redef fun linkto
do return linkto_text
(name
)
360 # Link to the entitiy with a specific text
361 fun linkto_text
(text
: String): HTMLTag
363 return (new HTMLTag("a")).attr
("href", href
).text
(text
)
367 redef class MClassDef
370 var res
= new HInfoBox(v
, "class {mclass.name}")
373 res
.new_field
("class").text
(mclass
.name
)
375 res
.new_field
("redef class").text
(mclass
.name
)
376 res
.new_field
("intro").add mclass
.intro
.linkto_text
("in {mclass.intro.mmodule.to_s}")
379 if mdoc
== null then mdoc
= mclass
.intro
.mdoc
380 if mdoc
!= null then mdoc
.fill_infobox
(res
)
382 if in_hierarchy
== null then return res
384 if in_hierarchy
.greaters
.length
> 1 then
385 var c
= res
.new_dropdown
("hier", "super-classes")
386 for x
in in_hierarchy
.greaters
do
387 if x
== self then continue
388 if not x
.is_intro
then continue
389 c
.open
("li").add x
.linkto
392 if in_hierarchy
.smallers
.length
> 1 then
393 var c
= res
.new_dropdown
("hier", "sub-classes")
394 for x
in in_hierarchy
.smallers
do
395 if x
== self then continue
396 if not x
.is_intro
then continue
397 c
.open
("li").add x
.linkto
400 if mclass
.mclassdefs
.length
> 1 then
401 var c
= res
.new_dropdown
("redefs", "refinements")
402 for x
in mclass
.mclassdefs
do
403 if x
== self then continue
404 c
.open
("li").add x
.linkto_text
("in {x.mmodule}")
410 # The class HTML page (an anchor in the module page)
413 return mmodule
.href
+ "#" + to_s
416 redef fun linkto
do return linkto_text
(mclass
.name
)
418 # Link to the entitiy with a specific text
419 fun linkto_text
(text
: String): HTMLTag
421 return (new HTMLTag("a")).attr
("href", href
).text
(text
)
428 var res
= new HInfoBox(v
, to_s
)
430 if self isa MMethodDef then
431 if msignature
!= null then res
.new_field
("fun").append
(mproperty
.name
).add msignature
.linkto
432 else if self isa MAttributeDef then
433 if static_mtype
!= null then res
.new_field
("fun").append
(mproperty
.name
).add static_mtype
.linkto
434 else if self isa MVirtualTypeDef then
435 if bound
!= null then res
.new_field
("add").append
(mproperty
.name
).add bound
.linkto
437 res
.new_field
("wat?").append
(mproperty
.name
)
442 res
.new_field
("intro").add mproperty
.intro
.linkto_text
("in {mproperty.intro.mclassdef}")
445 if mdoc
== null then mdoc
= mproperty
.intro
.mdoc
446 if mdoc
!= null then mdoc
.fill_infobox
(res
)
447 if mproperty
.mpropdefs
.length
> 1 then
448 var c
= res
.new_dropdown
("redef", "redefinitions")
449 for x
in mproperty
.mpropdefs
do
450 c
.open
("li").add x
.linkto_text
("in {x.mclassdef}")
457 # The property HTML page (an anchor in the module page)
460 return self.mclassdef
.mmodule
.href
+ "#" + self.to_s
463 redef fun linkto
do return linkto_text
(mproperty
.name
)
465 # Link to the entitiy with a specific text
466 fun linkto_text
(text
: String): HTMLTag
468 return (new HTMLTag("a")).attr
("href", href
).text
(text
)
472 redef class MClassType
475 var res
= new HInfoBox(v
, to_s
)
476 res
.href
= mclass
.intro
.href
477 res
.new_field
("class").add mclass
.intro
.linkto
478 var mdoc
= mclass
.mdoc
479 if mdoc
== null then mdoc
= mclass
.intro
.mdoc
480 if mdoc
!= null then mdoc
.fill_infobox
(res
)
485 return mclass
.intro
.linkto
488 redef class MVirtualType
491 var res
= new HInfoBox(v
, to_s
)
492 res
.href
= mproperty
.intro
.href
495 res
.new_field
("virtual type").add pd
.linkto
497 if mdoc
!= null then mdoc
.fill_infobox
(res
)
502 return mproperty
.intro
.linkto
505 redef class MParameterType
508 var res
= new HInfoBox(v
, to_s
)
509 res
.new_field
("parameter type").append
("{name} from class ").add mclass
.intro
.linkto
514 return (new HTMLTag("span")).text
(name
)
518 redef class MNullableType
521 return mtype
.infobox
(v
)
525 var res
= new HTMLTag("span")
526 res
.append
("nullable ").add
(mtype
.linkto
)
531 redef class MNotNullType
534 return mtype
.infobox
(v
)
538 var res
= new HTMLTag("span")
539 res
.append
("not null ").add
(mtype
.linkto
)
544 redef class MNullType
547 var res
= new HInfoBox(v
, to_s
)
552 var res
= new HTMLTag("span")
558 redef class MSignature
561 var res
= new HTMLTag("span")
563 if not mparameters
.is_empty
then
565 for p
in mparameters
do
573 res
.add p
.mtype
.linkto
577 var ret
= return_mtype
590 var res
= new HInfoBox(v
, "call {mpropdef}")
591 res
.href
= mpropdef
.href
592 res
.new_field
("call").add
(mpropdef
.linkto
).add
(msignature
.linkto
)
593 if mpropdef
.is_intro
then
595 res
.new_field
("intro").add mproperty
.intro
.linkto_text
("in {mproperty.intro.mclassdef}")
597 var mdoc
= mpropdef
.mdoc
598 if mdoc
== null then mdoc
= mproperty
.intro
.mdoc
599 if mdoc
!= null then mdoc
.fill_infobox
(res
)
605 return mpropdef
.linkto
613 var declared_type
= self.declared_type
614 if declared_type
== null then
615 var res
= new HInfoBox(v
, "{name}")
616 res
.new_field
("local var").append
("{name}")
619 var res
= new HInfoBox(v
, "{name}: {declared_type}")
620 res
.new_field
("local var").append
("{name}:").add
(declared_type
.linkto
)
625 return (new HTMLTag("span")).text
(name
)
633 # Optionally creates a tag that encapsulate the AST element on HTML rendering
634 protected fun make_tag
(v
: HighlightVisitor): nullable HTMLTag do return null
636 # Add aditionnal information on a child-token and return an additionnal HInfoBox on it
637 protected fun decorate_tag
(v
: HighlightVisitor, res
: HTMLTag, token
: Token): nullable HInfoBox
639 #debug("no decoration for {token.inspect}")
640 #res.add_class("nc_error")
644 # Return a optional infobox
645 fun infobox
(v
: HighlightVisitor): nullable HInfoBox do return null
648 redef class AStdClassdef
649 redef fun make_tag
(v
)
651 var res
= new HTMLTag("span")
652 res
.add_class
("nc_cdef")
654 if md
!= null then res
.attr
("id", md
.to_s
)
657 redef fun decorate_tag
(v
, res
, token
)
659 if not token
isa TClassid then return null
660 res
.add_class
("nc_def")
663 if md
== null then return null
668 redef fun make_tag
(v
)
670 var res
= new HTMLTag("span")
671 res
.add_class
("nc_pdef")
676 res
.attr
("id", mpd
.to_s
)
678 if self isa AAttrPropdef then
680 if mpd
!= null then res
.add
(tag
(mpd
))
682 if mpd
!= null then res
.add
(tag
(mpd
))
687 private fun tag
(mpd
: MPropDef): HTMLTag
689 var a
= new HTMLTag("a")
690 a
.attr
("id", mpd
.to_s
)
696 # Produce an HTMLTag with the correct contents and CSS classes
697 # Subclasses can redefine it to decorate the tag
698 redef fun make_tag
(v
: HighlightVisitor): HTMLTag
700 var res
= new HTMLTag("span")
706 redef class TokenKeyword
707 redef fun make_tag
(v
)
710 res
.add_class
("nc_k")
714 redef class TokenOperator
715 redef fun make_tag
(v
)
719 if p
!= null then p
.decorate_tag
(v
, res
, self)
720 res
.add_class
("nc_o")
725 redef class AVarFormExpr
726 redef fun decorate_tag
(v
, res
, token
)
728 var variable
= self.variable
729 if variable
== null then return null
730 res
.add_class
("nc_v")
731 return variable
.infobox
(v
)
735 redef class AVardeclExpr
736 redef fun decorate_tag
(v
, res
, token
)
738 var variable
= self.variable
739 if variable
== null then return null
740 res
.add_class
("nc_v")
741 return variable
.infobox
(v
)
746 redef fun decorate_tag
(v
, res
, token
)
748 if not token
isa TId then return null
750 if vs
== null then return null
751 res
.add_class
("nc_v")
752 var idx
= n_ids
.index_of
(token
)
753 var variable
= vs
[idx
]
754 return variable
.infobox
(v
)
759 redef fun decorate_tag
(v
, res
, token
)
762 if mp
== null then return null
763 var variable
= self.variable
764 if variable
== null then return null
765 res
.add_class
("nc_v")
766 return variable
.infobox
(v
)
770 redef class AAssertExpr
771 redef fun decorate_tag
(v
, res
, token
)
773 res
.add_class
("nc_ast")
779 redef fun decorate_tag
(v
, res
, token
)
781 res
.add_class
("nc_la")
786 redef class ASendExpr
787 redef fun decorate_tag
(v
, res
, token
)
789 if callsite
== null then return null
790 return callsite
.infobox
(v
)
795 redef fun decorate_tag
(v
, res
, token
)
797 if callsite
== null then return null
798 return callsite
.infobox
(v
)
802 redef class AAssignOp
803 redef fun decorate_tag
(v
, res
, token
)
806 assert p
isa AReassignFormExpr
808 var callsite
= p
.reassign_callsite
809 if callsite
== null then return null
810 return callsite
.infobox
(v
)
814 redef class AModuleName
815 redef fun decorate_tag
(v
, res
, token
)
817 return parent
.decorate_tag
(v
, res
, token
)
821 redef class AModuledecl
822 redef fun decorate_tag
(v
, res
, token
)
824 res
.add_class
("nc_def")
825 res
.add_class
("nc_m")
829 if mm
== null then return null
834 redef class AStdImport
835 redef fun decorate_tag
(v
, res
, token
)
837 res
.add_class
("nc_m")
839 if mm
== null then return null
843 redef class AAttrPropdef
844 redef fun decorate_tag
(v
, res
, token
)
846 res
.add_class
("nc_def")
847 var mpd
: nullable MPropDef
849 if mpd
== null then mpd
= mpropdef
850 if mpd
== null then return null
851 return mpd
.infobox
(v
)
856 redef fun make_tag
(v
)
860 if p
!= null then p
.decorate_tag
(v
, res
, self)
861 res
.add_class
("nc_i")
866 redef fun make_tag
(v
)
868 var res
= new HTMLTag("span")
869 res
.add_class
("nc_def")
872 redef fun decorate_tag
(v
, res
, token
)
875 # nothing to decorate
880 if not p
isa AMethPropdef then return null
882 if mpd
== null then return null
883 return mpd
.infobox
(v
)
887 redef fun make_tag
(v
)
891 if p
!= null then p
.decorate_tag
(v
, res
, self)
892 res
.add_class
("nc_a")
896 redef class AAttrFormExpr
897 redef fun decorate_tag
(v
, res
, token
)
900 if p
== null then return null
901 return p
.intro
.infobox
(v
)
905 redef fun make_tag
(v
)
909 if p
!= null then p
.decorate_tag
(v
, res
, self)
910 res
.add_class
("nc_t")
915 redef fun decorate_tag
(v
, res
, token
)
918 if mt
== null then return null
920 if mt
isa MFormalType then
921 res
.add_class
("nc_vt")
926 redef class AFormaldef
927 redef fun decorate_tag
(v
, res
, token
)
929 res
.add_class
("nc_vt")
930 if mtype
== null then return null
931 return mtype
.infobox
(v
)
934 redef class ATypePropdef
935 redef fun decorate_tag
(v
, res
, token
)
937 res
.add_class
("nc_def")
939 if md
== null then return null
944 redef fun make_tag
(v
)
947 if not parent
isa ADoc then
948 res
.add_class
("nc_c")
954 redef fun make_tag
(v
)
956 var res
= new HTMLTag("span")
957 res
.add_class
("nc_d")
961 redef class TokenLiteral
962 redef fun make_tag
(v
)
965 res
.add_class
("nc_l")
967 if p
!= null then p
.decorate_tag
(v
, res
, self)
971 redef class ASuperstringExpr
972 redef fun make_tag
(v
)
974 var res
= new HTMLTag("span")
975 res
.add_class
("nc_ss")
979 redef class AStringFormExpr
980 redef fun decorate_tag
(v
, res
, token
)
982 # Workaround to tag strings
983 res
.classes
.remove
("nc_l")
984 res
.add_class
("nc_s")
989 redef fun decorate_tag
(v
, res
, token
)
992 if t
== null then return null