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
46 # When highlighting a node, show its messages (errors, warnings), if any.
49 var show_messages
= true is writable
51 # When highlighting a node, attach a full popupable infobox, if any.
53 # If `false`, only a simple `title` tooltip is used.
56 var show_infobox
= true is writable
60 html
.add_class
("nitcode")
63 # When highlighting a node, also consider the loose tokens around it.
65 # Loose tokens are tokens discarded from the AST but attached before
66 # or after some non-loose tokens. See `Token::is_loose`.
68 # When this flag is set to `true`, the loose tokens that are before the
69 # first token and after the last token are also highlighted.
72 var include_loose_tokens
= false is writable
74 # When highlighting a node, the first and the last lines are fully included.
76 # If the highlighted node starts (or ends) in the middle of a line,
77 # this flags forces the whole line to be highlighted.
80 var include_whole_lines
= false is writable
82 # The entry-point of the highlighting.
83 # Will fill `html` with the generated HTML content.
84 fun enter_visit
(n
: ANode)
97 if f
== null then return
99 if l
== null then return
102 if include_loose_tokens
then
103 if f
.prev_looses
.not_empty
then f
= f
.prev_looses
.first
104 if l
.next_looses
.not_empty
then l
= l
.next_looses
.last
107 if include_whole_lines
then
108 f
= f
.first_real_token_in_line
109 l
= l
.last_real_token_in_line
115 private fun full_tag
(anode
: ANode, hv
: HighlightVisitor): nullable HTMLTag
117 var tag
= anode
.make_tag
(hv
)
118 if tag
== null then return null
119 var infobox
= anode
.infobox
(hv
)
120 if infobox
== null and anode
isa Token then
121 var pa
= anode
.parent
123 infobox
= pa
.decorate_tag
(hv
, tag
, anode
)
126 if infobox
!= null and not show_infobox
then
127 tag
.attr
("title", infobox
.title
)
128 tag
.classes
.add
"titled"
131 var messages
= anode
.location
.messages
132 if messages
!= null and show_messages
then
133 tag
.css
("border-bottom", "solid 2px red")
134 if infobox
== null then
135 infobox
= new HInfoBox(hv
, "Messages")
137 var c
= infobox
.new_dropdown
("{messages.length} message(s)", "")
139 c
.open
("li").append
(m
.text
)
142 if infobox
!= null then
143 tag
.attach_infobox
(infobox
)
148 # Highlight a full lexed source file.
150 # REQUIRE `source.first_token != null`
151 fun hightlight_source
(source
: SourceFile)
153 htmlize
(source
.first_token
.as(not null), null)
156 # Produce HTML between two tokens
157 protected fun htmlize
(first_token
: Token, last_token
: nullable Token)
159 var stack2
= new Array[HTMLTag]
160 var stack
= new Array[Prod]
162 var c
: nullable Token = first_token
163 var hv
= new HighlightVisitor
167 # Handle start of line
168 var cline
= c
.location
.line_start
169 if cline
!= line
then
170 # Handle starting block productions,
171 # Because c could be a detached token, get prods in
172 # the first AST token
173 var c0
= c
.first_token_in_line
175 if c0
!= null then starting
= c0
.starting_prods
176 if starting
!= null then for p
in starting
do
177 if not p
.is_block
then continue
178 var tag
= full_tag
(p
, hv
)
179 if tag
== null then continue
180 tag
.add_class
("foldable")
187 # Add a div for the whole line
188 var tag
= new HTMLTag("span")
189 var p
= line_id_prefix
190 if p
!= "" then tag
.attrs
["id"] = "{p}{cline}"
191 tag
.classes
.add
"line"
198 # Add the blank, verbatim
199 html
.add_raw_html c
.blank_before
201 # Handle starting span production
202 starting
= c
.starting_prods
203 if starting
!= null then for p
in starting
do
204 if not p
.is_span
then continue
205 var tag
= full_tag
(p
, hv
)
206 if tag
== null then continue
217 var tag
= full_tag
(c
, hv
)
218 if tag
!= null then html
.add tag
221 # Handle ending span productions
222 var ending
= c
.ending_prods
223 if ending
!= null then for p
in ending
do
224 if not p
.is_span
then continue
225 if stack
.is_empty
or p
!= stack
.last
then continue
230 # Handle end of line and end of file
232 if c
== last_token
then n
= null
233 if n
== null or n
.location
.line_start
!= line
then
234 # closes the line div
237 # close the block production divs
238 var c0
= c
.last_token_in_line
240 if c0
!= null then ending
= c0
.ending_prods
241 if ending
!= null then for p
in ending
do
242 if not p
.is_block
then continue
243 if stack
.is_empty
or p
!= stack
.last
then continue
251 #assert stack.is_empty
252 #assert stack2.is_empty
255 # Return a default CSS content related to CSS classes used in the `html` tree.
256 # Could be inlined in the `.html` file of saved as a specific `.css` file.
257 fun css_content
: String
260 .nitcode a { color: inherit; cursor:pointer; }
261 .nitcode .titled:hover { text-decoration: underline; } /* underline titles */
262 .nitcode .popupable:hover { text-decoration: underline; cursor:help; } /* underline titles */
263 .nitcode .foldable { display: block } /* for block productions*/
264 .nitcode .line{ display: block } /* for lines */
265 .nitcode .line:hover{ background-color: #FFFFE0; } /* current line */
266 .nitcode :target { background-color: #FFF3C2 } /* target highlight*/
267 /* lexical raw tokens. independent of usage or semantic: */
268 .nitcode .nc_c { color: gray; font-style: italic; } /* comment */
269 .nitcode .nc_d { color: #3D8127; font-style: italic; } /* documentation comments */
270 .nitcode .nc_k { font-weight: bold; } /* keyword */
271 .nitcode .nc_o {} /* operator */
272 .nitcode .nc_i {} /* standard identifier */
273 .nitcode .nc_t { color: #445588; font-weight: bold; } /* type/class identifier */
274 .nitcode .nc_a { color: #445588; font-style: italic; } /* old style attribute identifier */
275 .nitcode .nc_l { color: #009999; } /* char and number literal */
276 .nitcode .nc_s { color: #8F1546; } /* string literal */
277 /* syntactic token usage. added because of their position in the AST */
278 .nitcode .nc_ast { color: blue; } /* assert label */
279 .nitcode .nc_la { color: blue; } /* break/continue label */
280 .nitcode .nc_m { color: #445588; } /* module name */
281 /* syntactic groups */
282 .nitcode .nc_def { font-weight: bold; color: blue; } /* name used in a definition */
283 .nitcode .nc_def.nc_a { color: blue; } /* name used in a attribute definition */
284 .nitcode .nc_def.nc_t { color: blue; } /* name used in a class or vt definition */
285 .nitcode .nc_ss { color: #9E6BEB; } /* superstrings */
286 .nitcode .nc_cdef {} /* A whole class definition */
287 .nitcode .nc_pdef {} /* A whole property definition */
288 /* semantic token usage */
289 .nitcode .nc_v { font-style: italic; } /* local variable or parameter */
290 .nitcode .nc_vt { font-style: italic; } /* virtual type or formal type */
292 .nitcode .nc_error { border: 1px red solid;} /* not used */
293 .popover { max-width: 800px !important; }
297 # Additional content to inject in the <head> tag
298 # Note: does not include `css_content`; handle it yourself.
299 fun head_content
: String
301 return """<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">\n"""
304 # Additional content to inject just before the closing </body> tag
305 fun foot_content
: String
308 <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
309 <script src="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
310 <script>$(".popupable").popover({html:true, placement:'top'})/*initialize bootstrap popover*/</script>"""
315 # Attach the infobox to the node by using BootStrap popover
316 fun attach_infobox
(infobox
: HInfoBox)
318 classes
.add
("popupable")
319 attrs
["title"] = infobox
.title
320 var href
= infobox
.href
322 attrs
["data-title"] = """<a href="{{{href}}}">{{{infobox.title}}}</a>"""
324 attrs
["data-content"] = infobox
.content
.write_to_string
325 attrs
["data-toggle"] = "popover"
330 # A generic information container that can be used to decorate AST entities
332 # The visitor used for contextualisation, if needed
333 var visitor
: HighlightVisitor
335 # A short title for the AST element
338 # The primary link where the entity points
340 var href
: nullable String = null
342 # The content of the popuped infobox
343 var content
= new HTMLTag("div")
345 # Append a new field in the popuped infobox
346 fun new_field
(title
: String): HTMLTag
348 content
.open
("b").text
(title
)
350 var res
= content
.open
("span")
355 # Append a new dropdown in the popuped content
356 fun new_dropdown
(title
, text
: String): HTMLTag
358 content
.add_raw_html
"""<div class="dropdown"> <a data-toggle="dropdown" href="#"><b>"""
359 content
.append
(title
)
360 content
.add_raw_html
"</b> "
362 content
.add_raw_html
"""<span class="caret"></span></a>"""
363 var res
= content
.open
("ul").add_class
("dropdown-menu").attr
("role", "menu").attr
("aria-labelledby", "dLabel")
364 content
.add_raw_html
"</div>"
371 # Model entity or whatever that can produce an infobox
372 interface HInfoBoxable
373 # An new infobox documenting the entity
374 fun infobox
(v
: HighlightVisitor): HInfoBox is abstract
376 # A human-readable hyper-text for the entity
377 fun linkto
: HTMLTag is abstract
381 # Append an entry for the doc in the given infobox
382 fun fill_infobox
(res
: HInfoBox)
384 if content
.length
< 2 then
385 res
.new_field
("doc").text
(content
.first
)
388 var c
= res
.new_dropdown
("doc", content
.first
)
389 for x
in content
.iterator
.skip_head
(1) do
391 c
.add_raw_html
"<br>"
403 var res
= new HInfoBox(v
, "module {name}")
405 res
.new_field
("module").add
(linkto
)
407 if mdoc
!= null then mdoc
.fill_infobox
(res
)
408 if in_importation
.greaters
.length
> 1 then
409 var c
= res
.new_dropdown
("imports", "{in_importation.greaters.length-1} modules")
410 for x
in in_importation
.greaters
do
411 if x
== self then continue
412 c
.open
("li").add x
.linkto
418 # The module HTML page
421 return c_name
+ ".html"
424 redef fun linkto
do return linkto_text
(name
)
426 # Link to the entitiy with a specific text
427 fun linkto_text
(text
: String): HTMLTag
429 return (new HTMLTag("a")).attr
("href", href
).text
(text
)
433 redef class MClassDef
436 var res
= new HInfoBox(v
, "class {mclass.name}")
439 res
.new_field
("class").text
(mclass
.name
)
441 res
.new_field
("redef class").text
(mclass
.name
)
442 res
.new_field
("intro").add mclass
.intro
.linkto_text
("in {mclass.intro_mmodule.to_s}")
445 if mdoc
== null then mdoc
= mclass
.intro
.mdoc
446 if mdoc
!= null then mdoc
.fill_infobox
(res
)
448 if in_hierarchy
== null then return res
450 if in_hierarchy
.greaters
.length
> 1 then
451 var c
= res
.new_dropdown
("hier", "super-classes")
452 for x
in in_hierarchy
.greaters
do
453 if x
== self then continue
454 if not x
.is_intro
then continue
455 c
.open
("li").add x
.linkto
458 if in_hierarchy
.smallers
.length
> 1 then
459 var c
= res
.new_dropdown
("hier", "sub-classes")
460 for x
in in_hierarchy
.smallers
do
461 if x
== self then continue
462 if not x
.is_intro
then continue
463 c
.open
("li").add x
.linkto
466 if mclass
.mclassdefs
.length
> 1 then
467 var c
= res
.new_dropdown
("redefs", "refinements")
468 for x
in mclass
.mclassdefs
do
469 if x
== self then continue
470 c
.open
("li").add x
.linkto_text
("in {x.mmodule}")
476 # The class HTML page (an anchor in the module page)
479 return mmodule
.href
+ "#" + to_s
482 redef fun linkto
do return linkto_text
(mclass
.name
)
484 # Link to the entitiy with a specific text
485 fun linkto_text
(text
: String): HTMLTag
487 return (new HTMLTag("a")).attr
("href", href
).text
(text
)
494 var res
= new HInfoBox(v
, to_s
)
496 if self isa MMethodDef then
497 if msignature
!= null then res
.new_field
("fun").append
(mproperty
.name
).add msignature
.linkto
498 else if self isa MAttributeDef then
499 if static_mtype
!= null then res
.new_field
("fun").append
(mproperty
.name
).add static_mtype
.linkto
500 else if self isa MVirtualTypeDef then
501 if bound
!= null then res
.new_field
("add").append
(mproperty
.name
).add bound
.linkto
503 res
.new_field
("wat?").append
(mproperty
.name
)
508 res
.new_field
("intro").add mproperty
.intro
.linkto_text
("in {mproperty.intro.mclassdef}")
511 if mdoc
== null then mdoc
= mproperty
.intro
.mdoc
512 if mdoc
!= null then mdoc
.fill_infobox
(res
)
513 if mproperty
.mpropdefs
.length
> 1 then
514 var c
= res
.new_dropdown
("redef", "redefinitions")
515 for x
in mproperty
.mpropdefs
do
516 c
.open
("li").add x
.linkto_text
("in {x.mclassdef}")
523 # The property HTML page (an anchor in the module page)
526 return self.mclassdef
.mmodule
.href
+ "#" + self.to_s
529 redef fun linkto
do return linkto_text
(mproperty
.name
)
531 # Link to the entitiy with a specific text
532 fun linkto_text
(text
: String): HTMLTag
534 return (new HTMLTag("a")).attr
("href", href
).text
(text
)
538 redef class MClassType
541 var res
= new HInfoBox(v
, to_s
)
542 res
.href
= mclass
.intro
.href
543 res
.new_field
("class").add mclass
.intro
.linkto
544 var mdoc
= mclass
.mdoc
545 if mdoc
== null then mdoc
= mclass
.intro
.mdoc
546 if mdoc
!= null then mdoc
.fill_infobox
(res
)
551 return mclass
.intro
.linkto
554 redef class MVirtualType
557 var res
= new HInfoBox(v
, to_s
)
558 res
.href
= mproperty
.intro
.href
561 res
.new_field
("virtual type").add pd
.linkto
563 if mdoc
!= null then mdoc
.fill_infobox
(res
)
568 return mproperty
.intro
.linkto
571 redef class MParameterType
574 var res
= new HInfoBox(v
, to_s
)
575 res
.new_field
("parameter type").append
("{name} from class ").add mclass
.intro
.linkto
580 return (new HTMLTag("span")).text
(name
)
584 redef class MNullableType
587 return mtype
.infobox
(v
)
591 var res
= new HTMLTag("span")
592 res
.append
("nullable ").add
(mtype
.linkto
)
597 redef class MNotNullType
600 return mtype
.infobox
(v
)
604 var res
= new HTMLTag("span")
605 res
.append
("not null ").add
(mtype
.linkto
)
610 redef class MNullType
613 var res
= new HInfoBox(v
, to_s
)
618 var res
= new HTMLTag("span")
624 redef class MSignature
627 var res
= new HTMLTag("span")
629 if not mparameters
.is_empty
then
631 for p
in mparameters
do
639 res
.add p
.mtype
.linkto
643 var ret
= return_mtype
655 var res
= new HInfoBox(v
, "call {mpropdef}")
656 res
.href
= mpropdef
.href
657 res
.new_field
("call").add
(mpropdef
.linkto
).add
(msignature
.linkto
)
658 if mpropdef
.is_intro
then
660 res
.new_field
("intro").add mproperty
.intro
.linkto_text
("in {mproperty.intro.mclassdef}")
662 var mdoc
= mpropdef
.mdoc
663 if mdoc
== null then mdoc
= mproperty
.intro
.mdoc
664 if mdoc
!= null then mdoc
.fill_infobox
(res
)
670 return mpropdef
.linkto
678 var declared_type
= self.declared_type
679 if declared_type
== null then
680 var res
= new HInfoBox(v
, "{name}")
681 res
.new_field
("local var").append
("{name}")
684 var res
= new HInfoBox(v
, "{name}: {declared_type}")
685 res
.new_field
("local var").append
("{name}:").add
(declared_type
.linkto
)
690 return (new HTMLTag("span")).text
(name
)
698 # Optionally creates a tag that encapsulate the AST element on HTML rendering
699 protected fun make_tag
(v
: HighlightVisitor): nullable HTMLTag do return null
701 # Add aditionnal information on a child-token and return an additionnal HInfoBox on it
702 protected fun decorate_tag
(v
: HighlightVisitor, res
: HTMLTag, token
: Token): nullable HInfoBox
704 #debug("no decoration for {token.inspect}")
705 #res.add_class("nc_error")
709 # Return a optional infobox
710 fun infobox
(v
: HighlightVisitor): nullable HInfoBox do return null
713 redef class AQclassid
714 redef fun decorate_tag
(v
, res
, token
)
716 if token
!= n_id
then return null
717 return parent
.decorate_tag
(v
, res
, token
)
722 redef fun decorate_tag
(v
, res
, token
)
724 if token
!= n_id
then return null
725 return parent
.decorate_tag
(v
, res
, token
)
729 redef class AStdClassdef
730 redef fun make_tag
(v
)
732 var res
= new HTMLTag("span")
733 res
.add_class
("nc_cdef")
735 if md
!= null then res
.attr
("id", md
.to_s
)
738 redef fun decorate_tag
(v
, res
, token
)
740 if not token
isa TClassid then return null
741 res
.add_class
("nc_def")
744 if md
== null then return null
749 redef fun make_tag
(v
)
751 var res
= new HTMLTag("span")
752 res
.add_class
("nc_pdef")
757 res
.attr
("id", mpd
.to_s
)
759 if self isa AAttrPropdef then
761 if mpd
!= null then res
.add
(tag
(mpd
))
763 if mpd
!= null then res
.add
(tag
(mpd
))
768 private fun tag
(mpd
: MPropDef): HTMLTag
770 var a
= new HTMLTag("a")
771 a
.attr
("id", mpd
.to_s
)
777 # Produce an HTMLTag with the correct contents and CSS classes
778 # Subclasses can redefine it to decorate the tag
779 redef fun make_tag
(v
: HighlightVisitor): HTMLTag
781 var res
= new HTMLTag("span")
787 redef class TokenKeyword
788 redef fun make_tag
(v
)
791 res
.add_class
("nc_k")
795 redef class TokenOperator
796 redef fun make_tag
(v
)
799 res
.add_class
("nc_o")
804 redef class AVarFormExpr
805 redef fun decorate_tag
(v
, res
, token
)
807 if token
!= n_id
then return null
808 var variable
= self.variable
809 if variable
== null then return null
810 res
.add_class
("nc_v")
811 return variable
.infobox
(v
)
815 redef class AVardeclExpr
816 redef fun decorate_tag
(v
, res
, token
)
818 if token
!= n_id
then return null
819 var variable
= self.variable
820 if variable
== null then return null
821 res
.add_class
("nc_v")
822 return variable
.infobox
(v
)
826 redef class AForGroup
827 redef fun decorate_tag
(v
, res
, token
)
829 if not token
isa TId then return null
831 if vs
== null then return null
832 res
.add_class
("nc_v")
833 var idx
= n_ids
.index_of
(token
)
834 var variable
= vs
[idx
]
835 return variable
.infobox
(v
)
840 redef fun decorate_tag
(v
, res
, token
)
842 if token
!= n_id
then return null
844 if mp
== null then return null
845 var variable
= self.variable
846 if variable
== null then return null
847 res
.add_class
("nc_v")
848 return variable
.infobox
(v
)
852 redef class AAssertExpr
853 redef fun decorate_tag
(v
, res
, token
)
855 if not token
isa TId then return null
856 res
.add_class
("nc_ast")
862 redef fun decorate_tag
(v
, res
, token
)
864 if not token
isa TId then return null
865 res
.add_class
("nc_la")
870 redef class ASendExpr
871 redef fun decorate_tag
(v
, res
, token
)
873 if callsite
== null then return null
874 return callsite
.infobox
(v
)
879 redef fun decorate_tag
(v
, res
, token
)
881 if callsite
== null then return null
882 return callsite
.infobox
(v
)
886 redef class AAssignOp
887 redef fun decorate_tag
(v
, res
, token
)
890 assert p
isa AReassignFormExpr
892 var callsite
= p
.reassign_callsite
893 if callsite
== null then return null
894 return callsite
.infobox
(v
)
898 redef class AModuleName
899 redef fun decorate_tag
(v
, res
, token
)
901 return parent
.decorate_tag
(v
, res
, token
)
905 redef class AModuledecl
906 redef fun decorate_tag
(v
, res
, token
)
908 if not token
isa TId then return null
909 res
.add_class
("nc_def")
910 res
.add_class
("nc_m")
914 if mm
== null then return null
919 redef class AStdImport
920 redef fun decorate_tag
(v
, res
, token
)
922 if not token
isa TId then return null
923 res
.add_class
("nc_m")
925 if mm
== null then return null
929 redef class AAttrPropdef
930 redef fun decorate_tag
(v
, res
, token
)
932 if not token
isa TId then return null
933 res
.add_class
("nc_def")
934 var mpd
: nullable MPropDef
936 if mpd
== null then mpd
= mpropdef
937 if mpd
== null then return null
938 return mpd
.infobox
(v
)
943 redef fun make_tag
(v
)
946 res
.add_class
("nc_i")
951 redef fun make_tag
(v
)
953 var res
= new HTMLTag("span")
954 res
.add_class
("nc_def")
957 redef fun decorate_tag
(v
, res
, token
)
960 # nothing to decorate
965 if not p
isa AMethPropdef then return null
967 if mpd
== null then return null
968 return mpd
.infobox
(v
)
972 redef fun make_tag
(v
)
975 res
.add_class
("nc_a")
979 redef class AAttrFormExpr
980 redef fun decorate_tag
(v
, res
, token
)
982 if not token
isa TAttrid then return null
984 if p
== null then return null
985 return p
.intro
.infobox
(v
)
989 redef fun make_tag
(v
)
992 res
.add_class
("nc_t")
997 redef fun decorate_tag
(v
, res
, token
)
999 if not token
isa TClassid then return null
1001 if mt
== null then return null
1003 if mt
isa MFormalType then
1004 res
.add_class
("nc_vt")
1006 return mt
.infobox
(v
)
1009 redef class AFormaldef
1010 redef fun decorate_tag
(v
, res
, token
)
1012 if not token
isa TClassid then return null
1013 res
.add_class
("nc_vt")
1014 if mtype
== null then return null
1015 return mtype
.infobox
(v
)
1018 redef class ATypePropdef
1019 redef fun decorate_tag
(v
, res
, token
)
1021 if not token
isa TClassid then return null
1022 res
.add_class
("nc_def")
1024 if md
== null then return null
1025 return md
.infobox
(v
)
1028 redef class TComment
1029 redef fun make_tag
(v
)
1033 res
.add_class
("nc_c")
1039 redef fun make_tag
(v
)
1041 var res
= new HTMLTag("span")
1042 res
.add_class
("nc_d")
1046 redef class TokenLiteral
1047 redef fun make_tag
(v
)
1050 res
.add_class
("nc_l")
1054 redef class ASuperstringExpr
1055 redef fun make_tag
(v
)
1057 var res
= new HTMLTag("span")
1058 res
.add_class
("nc_ss")
1062 redef class AStringFormExpr
1063 redef fun decorate_tag
(v
, res
, token
)
1065 # Workaround to tag strings
1066 res
.classes
.remove
("nc_l")
1067 res
.add_class
("nc_s")
1072 redef fun decorate_tag
(v
, res
, token
)
1075 if t
== null then return null