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 # Produce HTML between two tokens
61 protected fun htmlize
(first_token
, last_token
: Token)
63 var stack2
= new Array[HTMLTag]
64 var stack
= new Array[Prod]
66 var c
: nullable Token = first_token
67 var hv
= new HighlightVisitor
71 # Handle start of line
72 var cline
= c
.location
.line_start
74 # Handle starting block productions,
75 # Because c could be a detached token, get prods in
77 var c0
= c
.first_token_in_line
79 if c0
!= null then starting
= c0
.starting_prods
80 if starting
!= null then for p
in starting
do
81 if not p
.is_block
then continue
82 var tag
= p
.make_tag
(hv
)
83 if tag
== null then continue
84 tag
.add_class
("foldable")
85 var infobox
= p
.infobox
(hv
)
86 if infobox
!= null then tag
.attach_infobox
(infobox
)
93 # Add a div for the whole line
94 var tag
= new HTMLTag("span")
95 var p
= line_id_prefix
96 if p
!= "" then tag
.attrs
["id"] = "{p}{cline}"
97 tag
.classes
.add
"line"
104 # Add the blank, verbatim
105 html
.add_raw_html c
.blank_before
107 # Handle starting span production
108 starting
= c
.starting_prods
109 if starting
!= null then for p
in starting
do
110 if not p
.is_span
then continue
111 var tag
= p
.make_tag
(hv
)
112 if tag
== null then continue
113 var infobox
= p
.infobox
(hv
)
114 if infobox
!= null then tag
.attach_infobox
(infobox
)
125 var tag
= c
.make_tag
(hv
)
128 if c
isa TId or c
isa TClassid or c
isa TAttrid or c
isa TokenLiteral or c
isa TokenOperator then
130 if pa
!= null then infobox
= pa
.decorate_tag
(hv
, tag
, c
)
131 else if c
isa TComment and pa
isa ADoc then
132 infobox
= pa
.decorate_tag
(hv
, tag
, c
)
134 if infobox
!= null then tag
.attach_infobox
(infobox
)
138 # Handle ending span productions
139 var ending
= c
.ending_prods
140 if ending
!= null then for p
in ending
do
141 if not p
.is_span
then continue
142 if stack
.is_empty
or p
!= stack
.last
then continue
147 # Handle end of line and end of file
149 if c
== last_token
then n
= null
150 if n
== null or n
.location
.line_start
!= line
then
151 # closes the line div
154 # close the block production divs
155 var c0
= c
.last_token_in_line
157 if c0
!= null then ending
= c0
.ending_prods
158 if ending
!= null then for p
in ending
do
159 if not p
.is_block
then continue
160 if stack
.is_empty
or p
!= stack
.last
then continue
168 assert stack
.is_empty
169 assert stack2
.is_empty
172 # Return a default CSS content related to CSS classes used in the `html` tree.
173 # Could be inlined in the `.html` file of saved as a specific `.css` file.
174 fun css_content
: String
177 .nitcode a { color: inherit; cursor:pointer; }
178 .nitcode .popupable:hover { text-decoration: underline; cursor:help; } /* underline titles */
179 .nitcode .foldable { display: block } /* for block productions*/
180 .nitcode .line{ display: block } /* for lines */
181 .nitcode .line:hover{ background-color: #FFFFE0; } /* current line */
182 .nitcode :target { background-color: #FFF3C2 } /* target highlight*/
183 /* lexical raw tokens. independent of usage or semantic: */
184 .nitcode .nc_c { color: gray; font-style: italic; } /* comment */
185 .nitcode .nc_d { color: #3D8127; font-style: italic; } /* documentation comments */
186 .nitcode .nc_k { font-weight: bold; } /* keyword */
187 .nitcode .nc_o {} /* operator */
188 .nitcode .nc_i {} /* standard identifier */
189 .nitcode .nc_t { color: #445588; font-weight: bold; } /* type/class identifier */
190 .nitcode .nc_a { color: #445588; font-style: italic; } /* old style attribute identifier */
191 .nitcode .nc_l { color: #009999; } /* char and number literal */
192 .nitcode .nc_s { color: #8F1546; } /* string literal */
193 /* syntactic token usage. added because of their position in the AST */
194 .nitcode .nc_ast { color: blue; } /* assert label */
195 .nitcode .nc_la { color: blue; } /* break/continue label */
196 .nitcode .nc_m { color: #445588; } /* module name */
197 /* syntactic groups */
198 .nitcode .nc_def { font-weight: bold; color: blue; } /* name used in a definition */
199 .nitcode .nc_def.nc_a { color: blue; } /* name used in a attribute definition */
200 .nitcode .nc_def.nc_t { color: blue; } /* name used in a class or vt definition */
201 .nitcode .nc_ss { color: #9E6BEB; } /* superstrings */
202 .nitcode .nc_cdef {} /* A whole class definition */
203 .nitcode .nc_pdef {} /* A whole property definition */
204 /* semantic token usage */
205 .nitcode .nc_v { font-style: italic; } /* local variable or parameter */
206 .nitcode .nc_vt { font-style: italic; } /* virtual type or formal type */
208 .nitcode .nc_error { border: 1px red solid;} /* not used */
209 .popover { max-width: 800px !important; }
213 # Additional content to inject in the <head> tag
214 # Note: does not include `css_content`; handle it yourself.
215 fun head_content
: String
217 return """<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">\n"""
220 # Additional content to inject just before the closing </body> tag
221 fun foot_content
: String
224 <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
225 <script src="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
226 <script>$(".popupable").popover({html:true, placement:'top'})/*initialize bootstrap popover*/</script>"""
231 # Attach the infobox to the node by using BootStrap popover
232 fun attach_infobox
(infobox
: HInfoBox)
234 classes
.add
("popupable")
235 attrs
["title"] = infobox
.title
236 var href
= infobox
.href
238 attrs
["data-title"] = """<a href="{{{href}}}">{{{infobox.title}}}</a>"""
240 attrs
["data-content"] = infobox
.content
.write_to_string
241 attrs
["data-toggle"] = "popover"
246 # A generic information container that can be used to decorate AST entities
248 # The visitor used for contextualisation, if needed
249 var visitor
: HighlightVisitor
251 # A short title for the AST element
254 # The primary link where the entity points
256 var href
: nullable String = null
258 # The content of the popuped infobox
259 var content
= new HTMLTag("div")
261 # Append a new field in the popuped infobox
262 fun new_field
(title
: String): HTMLTag
264 content
.open
("b").text
(title
)
266 var res
= content
.open
("span")
271 # Append a new dropdown in the popuped content
272 fun new_dropdown
(title
, text
: String): HTMLTag
274 content
.add_raw_html
"""<div class="dropdown"> <a data-toggle="dropdown" href="#"><b>"""
275 content
.append
(title
)
276 content
.add_raw_html
"</b> "
278 content
.add_raw_html
"""<span class="caret"></span></a>"""
279 var res
= content
.open
("ul").add_class
("dropdown-menu").attr
("role", "menu").attr
("aria-labelledby", "dLabel")
280 content
.add_raw_html
"</div>"
287 # Model entity or whatever that can produce an infobox
288 interface HInfoBoxable
289 # An new infobox documenting the entity
290 fun infobox
(v
: HighlightVisitor): HInfoBox is abstract
292 # A human-readable hyper-text for the entity
293 fun linkto
: HTMLTag is abstract
297 # Append an entry for the doc in the given infobox
298 fun fill_infobox
(res
: HInfoBox)
300 if content
.length
< 2 then
301 res
.new_field
("doc").text
(content
.first
)
304 var c
= res
.new_dropdown
("doc", content
.first
)
305 for x
in content
.iterator
.skip_head
(1) do
307 c
.add_raw_html
"<br>"
319 var res
= new HInfoBox(v
, "module {name}")
321 res
.new_field
("module").add
(linkto
)
323 if mdoc
!= null then mdoc
.fill_infobox
(res
)
324 if in_importation
.greaters
.length
> 1 then
325 var c
= res
.new_dropdown
("imports", "{in_importation.greaters.length-1} modules")
326 for x
in in_importation
.greaters
do
327 if x
== self then continue
328 c
.open
("li").add x
.linkto
334 # The module HTML page
337 return name
+ ".html"
340 redef fun linkto
do return linkto_text
(name
)
342 # Link to the entitiy with a specific text
343 fun linkto_text
(text
: String): HTMLTag
345 return (new HTMLTag("a")).attr
("href", href
).text
(text
)
349 redef class MClassDef
352 var res
= new HInfoBox(v
, "class {mclass.name}")
355 res
.new_field
("class").text
(mclass
.name
)
357 res
.new_field
("redef class").text
(mclass
.name
)
358 res
.new_field
("intro").add mclass
.intro
.linkto_text
("in {mclass.intro.mmodule.to_s}")
361 if mdoc
== null then mdoc
= mclass
.intro
.mdoc
362 if mdoc
!= null then mdoc
.fill_infobox
(res
)
364 if in_hierarchy
== null then return res
366 if in_hierarchy
.greaters
.length
> 1 then
367 var c
= res
.new_dropdown
("hier", "super-classes")
368 for x
in in_hierarchy
.greaters
do
369 if x
== self then continue
370 if not x
.is_intro
then continue
371 c
.open
("li").add x
.linkto
374 if in_hierarchy
.smallers
.length
> 1 then
375 var c
= res
.new_dropdown
("hier", "sub-classes")
376 for x
in in_hierarchy
.smallers
do
377 if x
== self then continue
378 if not x
.is_intro
then continue
379 c
.open
("li").add x
.linkto
382 if mclass
.mclassdefs
.length
> 1 then
383 var c
= res
.new_dropdown
("redefs", "refinements")
384 for x
in mclass
.mclassdefs
do
385 if x
== self then continue
386 c
.open
("li").add x
.linkto_text
("in {x.mmodule}")
392 # The class HTML page (an anchor in the module page)
395 return mmodule
.href
+ "#" + to_s
398 redef fun linkto
do return linkto_text
(mclass
.name
)
400 # Link to the entitiy with a specific text
401 fun linkto_text
(text
: String): HTMLTag
403 return (new HTMLTag("a")).attr
("href", href
).text
(text
)
410 var res
= new HInfoBox(v
, to_s
)
412 if self isa MMethodDef then
413 if msignature
!= null then res
.new_field
("fun").append
(mproperty
.name
).add msignature
.linkto
414 else if self isa MAttributeDef then
415 if static_mtype
!= null then res
.new_field
("fun").append
(mproperty
.name
).add static_mtype
.linkto
416 else if self isa MVirtualTypeDef then
417 if bound
!= null then res
.new_field
("add").append
(mproperty
.name
).add bound
.linkto
419 res
.new_field
("wat?").append
(mproperty
.name
)
424 res
.new_field
("intro").add mproperty
.intro
.linkto_text
("in {mproperty.intro.mclassdef}")
427 if mdoc
== null then mdoc
= mproperty
.intro
.mdoc
428 if mdoc
!= null then mdoc
.fill_infobox
(res
)
429 if mproperty
.mpropdefs
.length
> 1 then
430 var c
= res
.new_dropdown
("redef", "redefinitions")
431 for x
in mproperty
.mpropdefs
do
432 c
.open
("li").add x
.linkto_text
("in {x.mclassdef}")
439 # The property HTML page (an anchor in the module page)
442 return self.mclassdef
.mmodule
.href
+ "#" + self.to_s
445 redef fun linkto
do return linkto_text
(mproperty
.name
)
447 # Link to the entitiy with a specific text
448 fun linkto_text
(text
: String): HTMLTag
450 return (new HTMLTag("a")).attr
("href", href
).text
(text
)
454 redef class MClassType
457 var res
= new HInfoBox(v
, to_s
)
458 res
.href
= mclass
.intro
.href
459 res
.new_field
("class").add mclass
.intro
.linkto
460 var mdoc
= mclass
.mdoc
461 if mdoc
== null then mdoc
= mclass
.intro
.mdoc
462 if mdoc
!= null then mdoc
.fill_infobox
(res
)
467 return mclass
.intro
.linkto
470 redef class MVirtualType
473 var res
= new HInfoBox(v
, to_s
)
474 res
.href
= mproperty
.intro
.href
477 res
.new_field
("virtual type").add pd
.linkto
479 if mdoc
!= null then mdoc
.fill_infobox
(res
)
484 return mproperty
.intro
.linkto
487 redef class MParameterType
490 var res
= new HInfoBox(v
, to_s
)
491 res
.new_field
("parameter type").append
("{name} from class ").add mclass
.intro
.linkto
496 return (new HTMLTag("span")).text
(name
)
500 redef class MNullableType
503 return mtype
.infobox
(v
)
507 var res
= new HTMLTag("span")
508 res
.append
("nullable ").add
(mtype
.linkto
)
513 redef class MSignature
516 var res
= new HTMLTag("span")
518 if not mparameters
.is_empty
then
520 for p
in mparameters
do
528 res
.add p
.mtype
.linkto
532 var ret
= return_mtype
545 var res
= new HInfoBox(v
, "call {mpropdef}")
546 res
.href
= mpropdef
.href
547 res
.new_field
("call").add
(mpropdef
.linkto
).add
(msignature
.linkto
)
548 if mpropdef
.is_intro
then
550 res
.new_field
("intro").add mproperty
.intro
.linkto_text
("in {mproperty.intro.mclassdef}")
552 var mdoc
= mpropdef
.mdoc
553 if mdoc
== null then mdoc
= mproperty
.intro
.mdoc
554 if mdoc
!= null then mdoc
.fill_infobox
(res
)
560 return mpropdef
.linkto
568 var declared_type
= self.declared_type
569 if declared_type
== null then
570 var res
= new HInfoBox(v
, "{name}")
571 res
.new_field
("local var").append
("{name}")
574 var res
= new HInfoBox(v
, "{name}: {declared_type}")
575 res
.new_field
("local var").append
("{name}:").add
(declared_type
.linkto
)
580 return (new HTMLTag("span")).text
(name
)
588 # Optionally creates a tag that encapsulate the AST element on HTML rendering
589 protected fun make_tag
(v
: HighlightVisitor): nullable HTMLTag do return null
591 # Add aditionnal information on a child-token and return an additionnal HInfoBox on it
592 protected fun decorate_tag
(v
: HighlightVisitor, res
: HTMLTag, token
: Token): nullable HInfoBox
594 #debug("no decoration for {token.inspect}")
595 #res.add_class("nc_error")
599 # Return a optional infobox
600 fun infobox
(v
: HighlightVisitor): nullable HInfoBox do return null
603 redef class AStdClassdef
604 redef fun make_tag
(v
)
606 var res
= new HTMLTag("span")
607 res
.add_class
("nc_cdef")
609 if md
!= null then res
.attr
("id", md
.to_s
)
612 redef fun decorate_tag
(v
, res
, token
)
614 res
.add_class
("nc_def")
617 if md
== null then return null
622 redef fun make_tag
(v
)
624 var res
= new HTMLTag("span")
625 res
.add_class
("nc_pdef")
630 res
.attr
("id", mpd
.to_s
)
632 if self isa AAttrPropdef then
634 if mpd
!= null then res
.add
(tag
(mpd
))
636 if mpd
!= null then res
.add
(tag
(mpd
))
641 private fun tag
(mpd
: MPropDef): HTMLTag
643 var a
= new HTMLTag("a")
644 a
.attr
("id", mpd
.to_s
)
650 # Produce an HTMLTag with the correct contents and CSS classes
651 # Subclasses can redefine it to decorate the tag
652 redef fun make_tag
(v
: HighlightVisitor): HTMLTag
654 var res
= new HTMLTag("span")
660 redef class TokenKeyword
661 redef fun make_tag
(v
)
664 res
.add_class
("nc_k")
668 redef class TokenOperator
669 redef fun make_tag
(v
)
673 if p
!= null then p
.decorate_tag
(v
, res
, self)
674 res
.add_class
("nc_o")
679 redef class AVarFormExpr
680 redef fun decorate_tag
(v
, res
, token
)
682 var variable
= self.variable
683 if variable
== null then return null
684 res
.add_class
("nc_v")
685 return variable
.infobox
(v
)
689 redef class AVardeclExpr
690 redef fun decorate_tag
(v
, res
, token
)
692 var variable
= self.variable
693 if variable
== null then return null
694 res
.add_class
("nc_v")
695 return variable
.infobox
(v
)
700 redef fun decorate_tag
(v
, res
, token
)
702 if not token
isa TId then return null
704 if vs
== null then return null
705 res
.add_class
("nc_v")
706 var idx
= n_ids
.index_of
(token
)
707 var variable
= vs
[idx
]
708 return variable
.infobox
(v
)
713 redef fun decorate_tag
(v
, res
, token
)
716 if mp
== null then return null
717 var variable
= self.variable
718 if variable
== null then return null
719 res
.add_class
("nc_v")
720 return variable
.infobox
(v
)
724 redef class AAssertExpr
725 redef fun decorate_tag
(v
, res
, token
)
727 res
.add_class
("nc_ast")
733 redef fun decorate_tag
(v
, res
, token
)
735 res
.add_class
("nc_la")
740 redef class ASendExpr
741 redef fun decorate_tag
(v
, res
, token
)
743 if callsite
== null then return null
744 return callsite
.infobox
(v
)
749 redef fun decorate_tag
(v
, res
, token
)
751 if callsite
== null then return null
752 return callsite
.infobox
(v
)
756 redef class AAssignOp
757 redef fun decorate_tag
(v
, res
, token
)
760 assert p
isa AReassignFormExpr
762 var callsite
= p
.reassign_callsite
763 if callsite
== null then return null
764 return callsite
.infobox
(v
)
768 redef class AModuleName
769 redef fun decorate_tag
(v
, res
, token
)
771 return parent
.decorate_tag
(v
, res
, token
)
775 redef class AModuledecl
776 redef fun decorate_tag
(v
, res
, token
)
778 res
.add_class
("nc_def")
779 res
.add_class
("nc_m")
783 if mm
== null then return null
788 redef class AStdImport
789 redef fun decorate_tag
(v
, res
, token
)
791 res
.add_class
("nc_m")
793 if mm
== null then return null
797 redef class AAttrPropdef
798 redef fun decorate_tag
(v
, res
, token
)
800 res
.add_class
("nc_def")
801 var mpd
: nullable MPropDef
803 if mpd
== null then mpd
= mpropdef
804 if mpd
== null then return null
805 return mpd
.infobox
(v
)
810 redef fun make_tag
(v
)
814 if p
!= null then p
.decorate_tag
(v
, res
, self)
815 res
.add_class
("nc_i")
820 redef fun make_tag
(v
)
822 var res
= new HTMLTag("span")
823 res
.add_class
("nc_def")
826 redef fun decorate_tag
(v
, res
, token
)
829 # nothing to decorate
834 if not p
isa AMethPropdef then return null
836 if mpd
== null then return null
837 return mpd
.infobox
(v
)
841 redef fun make_tag
(v
)
845 if p
!= null then p
.decorate_tag
(v
, res
, self)
846 res
.add_class
("nc_a")
850 redef class AAttrFormExpr
851 redef fun decorate_tag
(v
, res
, token
)
854 if p
== null then return null
855 return p
.intro
.infobox
(v
)
859 redef fun make_tag
(v
)
863 if p
!= null then p
.decorate_tag
(v
, res
, self)
864 res
.add_class
("nc_t")
869 redef fun decorate_tag
(v
, res
, token
)
872 if mt
== null then return null
873 mt
= mt
.as_notnullable
874 if mt
isa MVirtualType or mt
isa MParameterType then
875 res
.add_class
("nc_vt")
880 redef class AFormaldef
881 redef fun decorate_tag
(v
, res
, token
)
883 res
.add_class
("nc_vt")
884 if mtype
== null then return null
885 return mtype
.infobox
(v
)
888 redef class ATypePropdef
889 redef fun decorate_tag
(v
, res
, token
)
891 res
.add_class
("nc_def")
893 if md
== null then return null
898 redef fun make_tag
(v
)
901 if not parent
isa ADoc then
902 res
.add_class
("nc_c")
908 redef fun make_tag
(v
)
910 var res
= new HTMLTag("span")
911 res
.add_class
("nc_d")
915 redef class TokenLiteral
916 redef fun make_tag
(v
)
919 res
.add_class
("nc_l")
921 if p
!= null then p
.decorate_tag
(v
, res
, self)
925 redef class ASuperstringExpr
926 redef fun make_tag
(v
)
928 var res
= new HTMLTag("span")
929 res
.add_class
("nc_ss")
933 redef class AStringFormExpr
934 redef fun decorate_tag
(v
, res
, token
)
936 # Workaround to tag strings
937 res
.classes
.remove
("nc_l")
938 res
.add_class
("nc_s")
943 redef fun decorate_tag
(v
, res
, token
)
946 if t
== null then return null