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 # When highlighting a node, also consider the loose tokens around it.
53 # Loose tokens are tokens discarded from the AST but attached before
54 # or after some non-loose tokens. See `Token::is_loose`.
56 # When this flag is set to `true`, the loose tokens that are before the
57 # first token and after the last token are also highlighted.
60 var include_loose_tokens
= false is writable
62 # When highlighting a node, the first and the last lines are fully included.
64 # If the highlighted node starts (or ends) in the middle of a line,
65 # this flags forces the whole line to be highlighted.
68 var include_whole_lines
= false is writable
70 # The entry-point of the highlighting.
71 # Will fill `html` with the generated HTML content.
72 fun enter_visit
(n
: ANode)
85 if f
== null then return
87 if l
== null then return
90 if include_loose_tokens
then
91 if f
.prev_looses
.not_empty
then f
= f
.prev_looses
.first
92 if l
.next_looses
.not_empty
then l
= l
.next_looses
.last
95 if include_whole_lines
then
96 f
= f
.first_real_token_in_line
97 l
= l
.last_real_token_in_line
103 private fun full_tag
(anode
: ANode, hv
: HighlightVisitor): nullable HTMLTag
105 var tag
= anode
.make_tag
(hv
)
106 if tag
== null then return null
107 var infobox
= anode
.infobox
(hv
)
108 if infobox
== null and anode
isa Token then
109 var pa
= anode
.parent
112 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
113 infobox
= pa
.decorate_tag
(hv
, tag
, anode
)
117 var messages
= anode
.location
.messages
118 if messages
!= null then
119 tag
.css
("border-bottom", "solid 2px red")
120 if infobox
== null then
121 infobox
= new HInfoBox(hv
, "Messages")
123 var c
= infobox
.new_dropdown
("{messages.length} message(s)", "")
125 c
.open
("li").append
(m
.text
)
128 if infobox
!= null then
129 tag
.attach_infobox
(infobox
)
134 # Highlight a full lexed source file.
136 # REQUIRE `source.first_token != null`
137 fun hightlight_source
(source
: SourceFile)
139 htmlize
(source
.first_token
.as(not null), null)
142 # Produce HTML between two tokens
143 protected fun htmlize
(first_token
: Token, last_token
: nullable Token)
145 var stack2
= new Array[HTMLTag]
146 var stack
= new Array[Prod]
148 var c
: nullable Token = first_token
149 var hv
= new HighlightVisitor
153 # Handle start of line
154 var cline
= c
.location
.line_start
155 if cline
!= line
then
156 # Handle starting block productions,
157 # Because c could be a detached token, get prods in
158 # the first AST token
159 var c0
= c
.first_token_in_line
161 if c0
!= null then starting
= c0
.starting_prods
162 if starting
!= null then for p
in starting
do
163 if not p
.is_block
then continue
164 var tag
= full_tag
(p
, hv
)
165 if tag
== null then continue
166 tag
.add_class
("foldable")
173 # Add a div for the whole line
174 var tag
= new HTMLTag("span")
175 var p
= line_id_prefix
176 if p
!= "" then tag
.attrs
["id"] = "{p}{cline}"
177 tag
.classes
.add
"line"
184 # Add the blank, verbatim
185 html
.add_raw_html c
.blank_before
187 # Handle starting span production
188 starting
= c
.starting_prods
189 if starting
!= null then for p
in starting
do
190 if not p
.is_span
then continue
191 var tag
= full_tag
(p
, hv
)
192 if tag
== null then continue
203 var tag
= full_tag
(c
, hv
)
204 if tag
!= null then html
.add tag
207 # Handle ending span productions
208 var ending
= c
.ending_prods
209 if ending
!= null then for p
in ending
do
210 if not p
.is_span
then continue
211 if stack
.is_empty
or p
!= stack
.last
then continue
216 # Handle end of line and end of file
218 if c
== last_token
then n
= null
219 if n
== null or n
.location
.line_start
!= line
then
220 # closes the line div
223 # close the block production divs
224 var c0
= c
.last_token_in_line
226 if c0
!= null then ending
= c0
.ending_prods
227 if ending
!= null then for p
in ending
do
228 if not p
.is_block
then continue
229 if stack
.is_empty
or p
!= stack
.last
then continue
237 #assert stack.is_empty
238 #assert stack2.is_empty
241 # Return a default CSS content related to CSS classes used in the `html` tree.
242 # Could be inlined in the `.html` file of saved as a specific `.css` file.
243 fun css_content
: String
246 .nitcode a { color: inherit; cursor:pointer; }
247 .nitcode .popupable:hover { text-decoration: underline; cursor:help; } /* underline titles */
248 .nitcode .foldable { display: block } /* for block productions*/
249 .nitcode .line{ display: block } /* for lines */
250 .nitcode .line:hover{ background-color: #FFFFE0; } /* current line */
251 .nitcode :target { background-color: #FFF3C2 } /* target highlight*/
252 /* lexical raw tokens. independent of usage or semantic: */
253 .nitcode .nc_c { color: gray; font-style: italic; } /* comment */
254 .nitcode .nc_d { color: #3D8127; font-style: italic; } /* documentation comments */
255 .nitcode .nc_k { font-weight: bold; } /* keyword */
256 .nitcode .nc_o {} /* operator */
257 .nitcode .nc_i {} /* standard identifier */
258 .nitcode .nc_t { color: #445588; font-weight: bold; } /* type/class identifier */
259 .nitcode .nc_a { color: #445588; font-style: italic; } /* old style attribute identifier */
260 .nitcode .nc_l { color: #009999; } /* char and number literal */
261 .nitcode .nc_s { color: #8F1546; } /* string literal */
262 /* syntactic token usage. added because of their position in the AST */
263 .nitcode .nc_ast { color: blue; } /* assert label */
264 .nitcode .nc_la { color: blue; } /* break/continue label */
265 .nitcode .nc_m { color: #445588; } /* module name */
266 /* syntactic groups */
267 .nitcode .nc_def { font-weight: bold; color: blue; } /* name used in a definition */
268 .nitcode .nc_def.nc_a { color: blue; } /* name used in a attribute definition */
269 .nitcode .nc_def.nc_t { color: blue; } /* name used in a class or vt definition */
270 .nitcode .nc_ss { color: #9E6BEB; } /* superstrings */
271 .nitcode .nc_cdef {} /* A whole class definition */
272 .nitcode .nc_pdef {} /* A whole property definition */
273 /* semantic token usage */
274 .nitcode .nc_v { font-style: italic; } /* local variable or parameter */
275 .nitcode .nc_vt { font-style: italic; } /* virtual type or formal type */
277 .nitcode .nc_error { border: 1px red solid;} /* not used */
278 .popover { max-width: 800px !important; }
282 # Additional content to inject in the <head> tag
283 # Note: does not include `css_content`; handle it yourself.
284 fun head_content
: String
286 return """<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">\n"""
289 # Additional content to inject just before the closing </body> tag
290 fun foot_content
: String
293 <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
294 <script src="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
295 <script>$(".popupable").popover({html:true, placement:'top'})/*initialize bootstrap popover*/</script>"""
300 # Attach the infobox to the node by using BootStrap popover
301 fun attach_infobox
(infobox
: HInfoBox)
303 classes
.add
("popupable")
304 attrs
["title"] = infobox
.title
305 var href
= infobox
.href
307 attrs
["data-title"] = """<a href="{{{href}}}">{{{infobox.title}}}</a>"""
309 attrs
["data-content"] = infobox
.content
.write_to_string
310 attrs
["data-toggle"] = "popover"
315 # A generic information container that can be used to decorate AST entities
317 # The visitor used for contextualisation, if needed
318 var visitor
: HighlightVisitor
320 # A short title for the AST element
323 # The primary link where the entity points
325 var href
: nullable String = null
327 # The content of the popuped infobox
328 var content
= new HTMLTag("div")
330 # Append a new field in the popuped infobox
331 fun new_field
(title
: String): HTMLTag
333 content
.open
("b").text
(title
)
335 var res
= content
.open
("span")
340 # Append a new dropdown in the popuped content
341 fun new_dropdown
(title
, text
: String): HTMLTag
343 content
.add_raw_html
"""<div class="dropdown"> <a data-toggle="dropdown" href="#"><b>"""
344 content
.append
(title
)
345 content
.add_raw_html
"</b> "
347 content
.add_raw_html
"""<span class="caret"></span></a>"""
348 var res
= content
.open
("ul").add_class
("dropdown-menu").attr
("role", "menu").attr
("aria-labelledby", "dLabel")
349 content
.add_raw_html
"</div>"
356 # Model entity or whatever that can produce an infobox
357 interface HInfoBoxable
358 # An new infobox documenting the entity
359 fun infobox
(v
: HighlightVisitor): HInfoBox is abstract
361 # A human-readable hyper-text for the entity
362 fun linkto
: HTMLTag is abstract
366 # Append an entry for the doc in the given infobox
367 fun fill_infobox
(res
: HInfoBox)
369 if content
.length
< 2 then
370 res
.new_field
("doc").text
(content
.first
)
373 var c
= res
.new_dropdown
("doc", content
.first
)
374 for x
in content
.iterator
.skip_head
(1) do
376 c
.add_raw_html
"<br>"
388 var res
= new HInfoBox(v
, "module {name}")
390 res
.new_field
("module").add
(linkto
)
392 if mdoc
!= null then mdoc
.fill_infobox
(res
)
393 if in_importation
.greaters
.length
> 1 then
394 var c
= res
.new_dropdown
("imports", "{in_importation.greaters.length-1} modules")
395 for x
in in_importation
.greaters
do
396 if x
== self then continue
397 c
.open
("li").add x
.linkto
403 # The module HTML page
406 return c_name
+ ".html"
409 redef fun linkto
do return linkto_text
(name
)
411 # Link to the entitiy with a specific text
412 fun linkto_text
(text
: String): HTMLTag
414 return (new HTMLTag("a")).attr
("href", href
).text
(text
)
418 redef class MClassDef
421 var res
= new HInfoBox(v
, "class {mclass.name}")
424 res
.new_field
("class").text
(mclass
.name
)
426 res
.new_field
("redef class").text
(mclass
.name
)
427 res
.new_field
("intro").add mclass
.intro
.linkto_text
("in {mclass.intro_mmodule.to_s}")
430 if mdoc
== null then mdoc
= mclass
.intro
.mdoc
431 if mdoc
!= null then mdoc
.fill_infobox
(res
)
433 if in_hierarchy
== null then return res
435 if in_hierarchy
.greaters
.length
> 1 then
436 var c
= res
.new_dropdown
("hier", "super-classes")
437 for x
in in_hierarchy
.greaters
do
438 if x
== self then continue
439 if not x
.is_intro
then continue
440 c
.open
("li").add x
.linkto
443 if in_hierarchy
.smallers
.length
> 1 then
444 var c
= res
.new_dropdown
("hier", "sub-classes")
445 for x
in in_hierarchy
.smallers
do
446 if x
== self then continue
447 if not x
.is_intro
then continue
448 c
.open
("li").add x
.linkto
451 if mclass
.mclassdefs
.length
> 1 then
452 var c
= res
.new_dropdown
("redefs", "refinements")
453 for x
in mclass
.mclassdefs
do
454 if x
== self then continue
455 c
.open
("li").add x
.linkto_text
("in {x.mmodule}")
461 # The class HTML page (an anchor in the module page)
464 return mmodule
.href
+ "#" + to_s
467 redef fun linkto
do return linkto_text
(mclass
.name
)
469 # Link to the entitiy with a specific text
470 fun linkto_text
(text
: String): HTMLTag
472 return (new HTMLTag("a")).attr
("href", href
).text
(text
)
479 var res
= new HInfoBox(v
, to_s
)
481 if self isa MMethodDef then
482 if msignature
!= null then res
.new_field
("fun").append
(mproperty
.name
).add msignature
.linkto
483 else if self isa MAttributeDef then
484 if static_mtype
!= null then res
.new_field
("fun").append
(mproperty
.name
).add static_mtype
.linkto
485 else if self isa MVirtualTypeDef then
486 if bound
!= null then res
.new_field
("add").append
(mproperty
.name
).add bound
.linkto
488 res
.new_field
("wat?").append
(mproperty
.name
)
493 res
.new_field
("intro").add mproperty
.intro
.linkto_text
("in {mproperty.intro.mclassdef}")
496 if mdoc
== null then mdoc
= mproperty
.intro
.mdoc
497 if mdoc
!= null then mdoc
.fill_infobox
(res
)
498 if mproperty
.mpropdefs
.length
> 1 then
499 var c
= res
.new_dropdown
("redef", "redefinitions")
500 for x
in mproperty
.mpropdefs
do
501 c
.open
("li").add x
.linkto_text
("in {x.mclassdef}")
508 # The property HTML page (an anchor in the module page)
511 return self.mclassdef
.mmodule
.href
+ "#" + self.to_s
514 redef fun linkto
do return linkto_text
(mproperty
.name
)
516 # Link to the entitiy with a specific text
517 fun linkto_text
(text
: String): HTMLTag
519 return (new HTMLTag("a")).attr
("href", href
).text
(text
)
523 redef class MClassType
526 var res
= new HInfoBox(v
, to_s
)
527 res
.href
= mclass
.intro
.href
528 res
.new_field
("class").add mclass
.intro
.linkto
529 var mdoc
= mclass
.mdoc
530 if mdoc
== null then mdoc
= mclass
.intro
.mdoc
531 if mdoc
!= null then mdoc
.fill_infobox
(res
)
536 return mclass
.intro
.linkto
539 redef class MVirtualType
542 var res
= new HInfoBox(v
, to_s
)
543 res
.href
= mproperty
.intro
.href
546 res
.new_field
("virtual type").add pd
.linkto
548 if mdoc
!= null then mdoc
.fill_infobox
(res
)
553 return mproperty
.intro
.linkto
556 redef class MParameterType
559 var res
= new HInfoBox(v
, to_s
)
560 res
.new_field
("parameter type").append
("{name} from class ").add mclass
.intro
.linkto
565 return (new HTMLTag("span")).text
(name
)
569 redef class MNullableType
572 return mtype
.infobox
(v
)
576 var res
= new HTMLTag("span")
577 res
.append
("nullable ").add
(mtype
.linkto
)
582 redef class MNotNullType
585 return mtype
.infobox
(v
)
589 var res
= new HTMLTag("span")
590 res
.append
("not null ").add
(mtype
.linkto
)
595 redef class MNullType
598 var res
= new HInfoBox(v
, to_s
)
603 var res
= new HTMLTag("span")
609 redef class MSignature
612 var res
= new HTMLTag("span")
614 if not mparameters
.is_empty
then
616 for p
in mparameters
do
624 res
.add p
.mtype
.linkto
628 var ret
= return_mtype
640 var res
= new HInfoBox(v
, "call {mpropdef}")
641 res
.href
= mpropdef
.href
642 res
.new_field
("call").add
(mpropdef
.linkto
).add
(msignature
.linkto
)
643 if mpropdef
.is_intro
then
645 res
.new_field
("intro").add mproperty
.intro
.linkto_text
("in {mproperty.intro.mclassdef}")
647 var mdoc
= mpropdef
.mdoc
648 if mdoc
== null then mdoc
= mproperty
.intro
.mdoc
649 if mdoc
!= null then mdoc
.fill_infobox
(res
)
655 return mpropdef
.linkto
663 var declared_type
= self.declared_type
664 if declared_type
== null then
665 var res
= new HInfoBox(v
, "{name}")
666 res
.new_field
("local var").append
("{name}")
669 var res
= new HInfoBox(v
, "{name}: {declared_type}")
670 res
.new_field
("local var").append
("{name}:").add
(declared_type
.linkto
)
675 return (new HTMLTag("span")).text
(name
)
683 # Optionally creates a tag that encapsulate the AST element on HTML rendering
684 protected fun make_tag
(v
: HighlightVisitor): nullable HTMLTag do return null
686 # Add aditionnal information on a child-token and return an additionnal HInfoBox on it
687 protected fun decorate_tag
(v
: HighlightVisitor, res
: HTMLTag, token
: Token): nullable HInfoBox
689 #debug("no decoration for {token.inspect}")
690 #res.add_class("nc_error")
694 # Return a optional infobox
695 fun infobox
(v
: HighlightVisitor): nullable HInfoBox do return null
698 redef class AStdClassdef
699 redef fun make_tag
(v
)
701 var res
= new HTMLTag("span")
702 res
.add_class
("nc_cdef")
704 if md
!= null then res
.attr
("id", md
.to_s
)
707 redef fun decorate_tag
(v
, res
, token
)
709 if not token
isa TClassid then return null
710 res
.add_class
("nc_def")
713 if md
== null then return null
718 redef fun make_tag
(v
)
720 var res
= new HTMLTag("span")
721 res
.add_class
("nc_pdef")
726 res
.attr
("id", mpd
.to_s
)
728 if self isa AAttrPropdef then
730 if mpd
!= null then res
.add
(tag
(mpd
))
732 if mpd
!= null then res
.add
(tag
(mpd
))
737 private fun tag
(mpd
: MPropDef): HTMLTag
739 var a
= new HTMLTag("a")
740 a
.attr
("id", mpd
.to_s
)
746 # Produce an HTMLTag with the correct contents and CSS classes
747 # Subclasses can redefine it to decorate the tag
748 redef fun make_tag
(v
: HighlightVisitor): HTMLTag
750 var res
= new HTMLTag("span")
756 redef class TokenKeyword
757 redef fun make_tag
(v
)
760 res
.add_class
("nc_k")
764 redef class TokenOperator
765 redef fun make_tag
(v
)
769 if p
!= null then p
.decorate_tag
(v
, res
, self)
770 res
.add_class
("nc_o")
775 redef class AVarFormExpr
776 redef fun decorate_tag
(v
, res
, token
)
778 var variable
= self.variable
779 if variable
== null then return null
780 res
.add_class
("nc_v")
781 return variable
.infobox
(v
)
785 redef class AVardeclExpr
786 redef fun decorate_tag
(v
, res
, token
)
788 var variable
= self.variable
789 if variable
== null then return null
790 res
.add_class
("nc_v")
791 return variable
.infobox
(v
)
795 redef class AForGroup
796 redef fun decorate_tag
(v
, res
, token
)
798 if not token
isa TId then return null
800 if vs
== null then return null
801 res
.add_class
("nc_v")
802 var idx
= n_ids
.index_of
(token
)
803 var variable
= vs
[idx
]
804 return variable
.infobox
(v
)
809 redef fun decorate_tag
(v
, res
, token
)
812 if mp
== null then return null
813 var variable
= self.variable
814 if variable
== null then return null
815 res
.add_class
("nc_v")
816 return variable
.infobox
(v
)
820 redef class AAssertExpr
821 redef fun decorate_tag
(v
, res
, token
)
823 res
.add_class
("nc_ast")
829 redef fun decorate_tag
(v
, res
, token
)
831 res
.add_class
("nc_la")
836 redef class ASendExpr
837 redef fun decorate_tag
(v
, res
, token
)
839 if callsite
== null then return null
840 return callsite
.infobox
(v
)
845 redef fun decorate_tag
(v
, res
, token
)
847 if callsite
== null then return null
848 return callsite
.infobox
(v
)
852 redef class AAssignOp
853 redef fun decorate_tag
(v
, res
, token
)
856 assert p
isa AReassignFormExpr
858 var callsite
= p
.reassign_callsite
859 if callsite
== null then return null
860 return callsite
.infobox
(v
)
864 redef class AModuleName
865 redef fun decorate_tag
(v
, res
, token
)
867 return parent
.decorate_tag
(v
, res
, token
)
871 redef class AModuledecl
872 redef fun decorate_tag
(v
, res
, token
)
874 res
.add_class
("nc_def")
875 res
.add_class
("nc_m")
879 if mm
== null then return null
884 redef class AStdImport
885 redef fun decorate_tag
(v
, res
, token
)
887 res
.add_class
("nc_m")
889 if mm
== null then return null
893 redef class AAttrPropdef
894 redef fun decorate_tag
(v
, res
, token
)
896 res
.add_class
("nc_def")
897 var mpd
: nullable MPropDef
899 if mpd
== null then mpd
= mpropdef
900 if mpd
== null then return null
901 return mpd
.infobox
(v
)
906 redef fun make_tag
(v
)
910 if p
!= null then p
.decorate_tag
(v
, res
, self)
911 res
.add_class
("nc_i")
916 redef fun make_tag
(v
)
918 var res
= new HTMLTag("span")
919 res
.add_class
("nc_def")
922 redef fun decorate_tag
(v
, res
, token
)
925 # nothing to decorate
930 if not p
isa AMethPropdef then return null
932 if mpd
== null then return null
933 return mpd
.infobox
(v
)
937 redef fun make_tag
(v
)
941 if p
!= null then p
.decorate_tag
(v
, res
, self)
942 res
.add_class
("nc_a")
946 redef class AAttrFormExpr
947 redef fun decorate_tag
(v
, res
, token
)
950 if p
== null then return null
951 return p
.intro
.infobox
(v
)
955 redef fun make_tag
(v
)
959 if p
!= null then p
.decorate_tag
(v
, res
, self)
960 res
.add_class
("nc_t")
965 redef fun decorate_tag
(v
, res
, token
)
968 if mt
== null then return null
970 if mt
isa MFormalType then
971 res
.add_class
("nc_vt")
976 redef class AFormaldef
977 redef fun decorate_tag
(v
, res
, token
)
979 res
.add_class
("nc_vt")
980 if mtype
== null then return null
981 return mtype
.infobox
(v
)
984 redef class ATypePropdef
985 redef fun decorate_tag
(v
, res
, token
)
987 res
.add_class
("nc_def")
989 if md
== null then return null
994 redef fun make_tag
(v
)
998 res
.add_class
("nc_c")
1004 redef fun make_tag
(v
)
1006 var res
= new HTMLTag("span")
1007 res
.add_class
("nc_d")
1011 redef class TokenLiteral
1012 redef fun make_tag
(v
)
1015 res
.add_class
("nc_l")
1017 if p
!= null then p
.decorate_tag
(v
, res
, self)
1021 redef class ASuperstringExpr
1022 redef fun make_tag
(v
)
1024 var res
= new HTMLTag("span")
1025 res
.add_class
("nc_ss")
1029 redef class AStringFormExpr
1030 redef fun decorate_tag
(v
, res
, token
)
1032 # Workaround to tag strings
1033 res
.classes
.remove
("nc_l")
1034 res
.add_class
("nc_s")
1039 redef fun decorate_tag
(v
, res
, token
)
1042 if t
== null then return null