highlight: move hightlightcode inside HighlightVisitor
[nit.git] / src / highlight.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 # Highlighting of Nit AST
16 module highlight
17
18 import frontend
19 import html
20 import pipeline
21 import astutil
22 import serialization
23
24 # A standalone highlighted piece of code
25 class HLCode
26 super Serializable
27
28 # The highlighter used
29 var hl: HighlightVisitor
30
31 # The raw code source
32 var content: String
33
34 # The pseudo source-file
35 var source: SourceFile
36
37 # JavaScript code to update an existing codemirror editor.
38 fun code_mirror_update: Template
39 do
40
41 var res = new Template
42 res.add """
43 function nitmessage() {
44 editor.operation(function(){
45 for (var i = 0; i < widgets.length; ++i)
46 editor.removeLineWidget(widgets[i]);
47 widgets.length = 0;
48 """
49
50 for m in source.messages do
51 res.add """
52 var l = document.createElement("div");
53 l.className = "lint-error"
54 l.innerHTML = "<span class='glyphicon glyphicon-warning-sign lint-error-icon'></span> {{{m.text.html_escape}}}";
55 var w = editor.addLineWidget({{{m.location.line_start-1}}}, l);
56 widgets.push(w);
57 """
58 end
59 res.add """});}"""
60 return res
61 end
62
63 redef fun core_serialize_to(v)
64 do
65 v.serialize_attribute("code", hl.html.write_to_string)
66 var msgs = new Array[Map[String, Serializable]]
67 for m in source.messages do
68 var o = new Map[String, Serializable]
69 msgs.add o
70 o["line"] = m.location.line_start-1
71 o["message"] = m.text
72 end
73 v.serialize_attribute("messages", msgs)
74 end
75 end
76
77 # Visitor used to produce a HTML tree based on a AST on a `Source`
78 class HighlightVisitor
79 # The root of the HTML hierarchy
80 var html = new HTMLTag("span")
81
82 # Should the HTML include a nested `<span class"{type_of_node}">` element for each `ANode` of the AST?
83 #
84 # Used to have a really huge and verbose HTML (mainly for debug)
85 var with_ast = false is writable
86
87 # Prefixes used in generated IDs for line `<span>` elements.
88 # Useful if more than one highlighted code is present in the same HTML document.
89 #
90 # If set to the empty string, id for lines are disabled.
91 #
92 # Is `"L"` by default.
93 var line_id_prefix = "L" is writable
94
95 # The first line to generate, null if start at the first line
96 var first_line: nullable Int = null is writable
97
98 # The last line to generate, null if finish at the last line
99 var last_line: nullable Int = null is writable
100
101 # When highlighting a node, show its messages (errors, warnings), if any.
102 #
103 # default: true
104 var show_messages = true is writable
105
106 # When highlighting a node, attach a full popupable infobox, if any.
107 #
108 # If `false`, only a simple `title` tooltip is used.
109 #
110 # default: true
111 var show_infobox = true is writable
112
113 # A reference to an entity used in generated `<a>` elements.
114 #
115 # It is used to refer to some specific entities when generating links.
116 # If `null` is returned, then no link are generated and `<a>` elements become `<span>`.
117 #
118 # By default, `null` is returned.
119 # Clients are therefore encouraged to redefine the method in a subclass to control where entities should link to.
120 fun hrefto(entity: MEntity): nullable String do return null
121
122 init
123 do
124 html.add_class("nitcode")
125 end
126
127 # When highlighting a node, also consider the loose tokens around it.
128 #
129 # Loose tokens are tokens discarded from the AST but attached before
130 # or after some non-loose tokens. See `Token::is_loose`.
131 #
132 # When this flag is set to `true`, the loose tokens that are before the
133 # first token and after the last token are also highlighted.
134 #
135 # Default: false.
136 var include_loose_tokens = false is writable
137
138 # When highlighting a node, the first and the last lines are fully included.
139 #
140 # If the highlighted node starts (or ends) in the middle of a line,
141 # this flags forces the whole line to be highlighted.
142 #
143 # Default: false
144 var include_whole_lines = false is writable
145
146 # Highlight a AST element.
147 fun highlight_node(n: ANode)
148 do
149 n.parentize_tokens
150
151 var f
152 var l
153
154 if n isa Token then
155 f = n
156 l = n
157 else
158 assert n isa Prod
159 f = n.first_token
160 if f == null then return
161 l = n.last_token
162 if l == null then return
163 end
164
165 if include_loose_tokens then
166 if f.prev_looses.not_empty then f = f.prev_looses.first
167 if l.next_looses.not_empty then l = l.next_looses.last
168 end
169
170 var line = first_line
171 if line != null then
172 while f.location.line_start < line do
173 f = f.next_token
174 if f == null then return
175 end
176 end
177
178 line = last_line
179 if line != null then
180 while l.location.line_end > line do
181 l = l.prev_token
182 if l == null then return
183 end
184 end
185
186 if include_whole_lines then
187 f = f.first_real_token_in_line
188 l = l.last_real_token_in_line
189 end
190
191 do_highlight(f, l)
192 end
193
194 private fun full_tag(anode: ANode, hv: HighlightVisitor): nullable HTMLTag
195 do
196 var tag = anode.make_tag(hv)
197 if tag == null then return null
198 var infobox = anode.infobox(hv)
199 if infobox == null and anode isa Token then
200 var pa = anode.parent
201 if pa != null then
202 infobox = pa.decorate_tag(hv, tag, anode)
203 end
204 end
205 if infobox != null and not show_infobox then
206 tag.attr("title", infobox.title)
207 tag.classes.add "titled"
208 infobox = null
209 end
210 var messages = anode.location.messages
211 if messages != null and show_messages then
212 tag.css("border-bottom", "solid 2px red")
213 if infobox == null then
214 infobox = new HInfoBox(hv, "Messages")
215 end
216 var c = infobox.new_dropdown("{messages.length} message(s)", "")
217 for m in messages do
218 c.open("li").append(m.text)
219 end
220 end
221 if infobox != null then
222 tag.attach_infobox(infobox)
223 end
224 return tag
225 end
226
227 # Highlight a full lexed source file.
228 #
229 # REQUIRE `source.first_token != null`
230 fun highlight_source(source: SourceFile)
231 do
232 do_highlight(source.first_token.as(not null), null)
233 end
234
235 # Low-level highlighting between 2 tokens
236 protected fun do_highlight(first_token: Token, last_token: nullable Token)
237 do
238 var stack2 = new Array[HTMLTag]
239 var stack = new Array[Prod]
240 var line = 0
241 var c: nullable Token = first_token
242 var hv = self
243 while c != null do
244 var starting
245
246 # Handle start of line
247 var cline = c.location.line_start
248 if cline != line then
249 # Handle starting block productions,
250 # Because c could be a detached token, get prods in
251 # the first AST token
252 var c0 = c.first_token_in_line
253 starting = null
254 if c0 != null then starting = c0.starting_prods
255 if starting != null then for p in starting do
256 if not p.is_block then continue
257 var tag = full_tag(p, hv)
258 if tag == null then continue
259 tag.add_class("foldable")
260 stack2.add(html)
261 html.add tag
262 html = tag
263 stack.add(p)
264 end
265
266 # Add a div for the whole line
267 var tag = new HTMLTag("span")
268 var p = line_id_prefix
269 if p != "" then tag.attrs["id"] = "{p}{cline}"
270 tag.classes.add "line"
271 stack2.add(html)
272 html.add tag
273 html = tag
274 line = cline
275 end
276
277 # Add the blank, verbatim
278 html.add_raw_html c.blank_before
279
280 # Handle starting span production
281 starting = c.starting_prods
282 if starting != null then for p in starting do
283 if not p.is_span then continue
284 var tag = full_tag(p, hv)
285 if tag == null then continue
286 stack2.add(html)
287 html.add tag
288 html = tag
289 stack.add(p)
290 end
291
292 # Add the token
293 if c isa TEol then
294 html.append "\n"
295 else
296 var tag = full_tag(c, hv)
297 if tag != null then html.add tag
298 end
299
300 # Handle ending span productions
301 var ending = c.ending_prods
302 if ending != null then for p in ending do
303 if not p.is_span then continue
304 if stack.is_empty or p != stack.last then continue
305 stack.pop
306 html = stack2.pop
307 end
308
309 # Handle end of line and end of file
310 var n = c.next_token
311 if c == last_token then n = null
312 if n == null or n.location.line_start != line then
313 # closes the line div
314 html = stack2.pop
315
316 # close the block production divs
317 var c0 = c.last_token_in_line
318 ending = null
319 if c0 != null then ending = c0.ending_prods
320 if ending != null then for p in ending do
321 if not p.is_block then continue
322 if stack.is_empty or p != stack.last then continue
323 stack.pop
324 html = stack2.pop
325 end
326 end
327
328 c = n
329 end
330 if not stack2.is_empty then html = stack2.first
331 end
332
333 # Return a default CSS content related to CSS classes used in the `html` tree.
334 # Could be inlined in the `.html` file of saved as a specific `.css` file.
335 fun css_content: String
336 do
337 return """
338 .nitcode a { color: inherit; cursor:pointer; }
339 .nitcode .titled:hover { text-decoration: underline; } /* underline titles */
340 .nitcode .popupable:hover { text-decoration: underline; cursor:help; } /* underline titles */
341 .nitcode .foldable { display: block } /* for block productions*/
342 .nitcode .line{ display: block } /* for lines */
343 .nitcode .line:hover{ background-color: #FFFFE0; } /* current line */
344 .nitcode :target { background-color: #FFF3C2 } /* target highlight*/
345 /* lexical raw tokens. independent of usage or semantic: */
346 .nitcode .nc_c { color: gray; font-style: italic; } /* comment */
347 .nitcode .nc_d { color: #3D8127; font-style: italic; } /* documentation comments */
348 .nitcode .nc_k { font-weight: bold; } /* keyword */
349 .nitcode .nc_o {} /* operator */
350 .nitcode .nc_i {} /* standard identifier */
351 .nitcode .nc_t { color: #445588; font-weight: bold; } /* type/class identifier */
352 .nitcode .nc_a { color: #445588; font-style: italic; } /* old style attribute identifier */
353 .nitcode .nc_l { color: #009999; } /* char and number literal */
354 .nitcode .nc_s { color: #8F1546; } /* string literal */
355 /* syntactic token usage. added because of their position in the AST */
356 .nitcode .nc_ast { color: blue; } /* assert label */
357 .nitcode .nc_la { color: blue; } /* break/continue label */
358 .nitcode .nc_m { color: #445588; } /* module name */
359 /* syntactic groups */
360 .nitcode .nc_def { font-weight: bold; color: blue; } /* name used in a definition */
361 .nitcode .nc_def.nc_a { color: blue; } /* name used in a attribute definition */
362 .nitcode .nc_def.nc_t { color: blue; } /* name used in a class or vt definition */
363 .nitcode .nc_ss { color: #9E6BEB; } /* superstrings */
364 .nitcode .nc_cdef {} /* A whole class definition */
365 .nitcode .nc_pdef {} /* A whole property definition */
366 /* semantic token usage */
367 .nitcode .nc_v { font-style: italic; } /* local variable or parameter */
368 .nitcode .nc_vt { font-style: italic; } /* virtual type or formal type */
369
370 .nitcode .nc_error { border: 1px red solid;} /* not used */
371 .popover { max-width: 800px !important; }
372 """
373 end
374
375 # Additional content to inject in the <head> tag
376 # Note: does not include `css_content`; handle it yourself.
377 fun head_content: String
378 do
379 return """<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">\n"""
380 end
381
382 # Additional content to inject just before the closing </body> tag
383 fun foot_content: String
384 do
385 return """
386 <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
387 <script src="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
388 <script>$(".popupable").popover({html:true, placement:'top'})/*initialize bootstrap popover*/</script>"""
389 end
390
391 # Fully process `content` as a Nit source file.
392 #
393 # Set `print_errors = true` to print errors in the code to the console.
394 fun highlightcode(content: String, print_errors: nullable Bool): HLCode
395 do
396 # Prepare a stand-alone tool context
397 var tc = new ToolContext
398 tc.nit_dir = tc.locate_nit_dir # still use the common lib to have core
399 tc.keep_going = true # no exit, obviously
400 if print_errors != true then tc.opt_warn.value = -1 # no output
401
402 # Prepare an stand-alone model and model builder.
403 # Unfortunately, models are enclosing and append-only.
404 # There is no way (yet?) to have a shared module `core` with
405 # isolated and throwable user modules.
406 var model = new Model
407 var mb = new ModelBuilder(model, tc)
408
409 # Parse the code
410 var source = new SourceFile.from_string("", content + "\n")
411 var lexer = new Lexer(source)
412 var parser = new Parser(lexer)
413 var tree = parser.parse
414
415 var hlcode = new HLCode(self, content, source)
416
417 # Check syntax error
418 var eof = tree.n_eof
419 if eof isa AError then
420 mb.error(eof, eof.message)
421 highlight_source(source)
422 return hlcode
423 end
424 var amodule = tree.n_base.as(not null)
425
426 # Load the AST as a module in the model
427 # Then process it
428 mb.load_rt_module(null, amodule, "")
429 mb.run_phases
430
431 # Highlight the processed module
432 highlight_node(amodule)
433 return hlcode
434 end
435 end
436
437 redef class HTMLTag
438 # Attach the infobox to the node by using BootStrap popover
439 fun attach_infobox(infobox: HInfoBox)
440 do
441 classes.add("popupable")
442 attrs["title"] = infobox.title
443 var href = infobox.href
444 if href != null then
445 attrs["data-title"] = """<a href="{{{href}}}">{{{infobox.title}}}</a>"""
446 end
447 attrs["data-content"] = infobox.content.write_to_string
448 attrs["data-toggle"] = "popover"
449 end
450 end
451
452
453 # A generic information container that can be used to decorate AST entities
454 class HInfoBox
455 # The visitor used for contextualisation, if needed
456 var visitor: HighlightVisitor
457
458 # A short title for the AST element
459 var title: String
460
461 # The primary link where the entity points
462 # null if no link
463 var href: nullable String = null
464
465 # The content of the popuped infobox
466 var content = new HTMLTag("div")
467
468 # Append a new field in the popuped infobox
469 fun new_field(title: String): HTMLTag
470 do
471 content.open("b").text(title)
472 content.append(" ")
473 var res = content.open("span")
474 content.open("br")
475 return res
476 end
477
478 # Append a new dropdown in the popuped content
479 fun new_dropdown(title, text: String, text_is_html: nullable Bool): HTMLTag
480 do
481 content.add_raw_html """<div class="dropdown"> <a data-toggle="dropdown" href="#"><b>"""
482 content.append(title)
483 content.add_raw_html "</b> "
484 if text_is_html == true then
485 content.add_raw_html(text)
486 else content.append(text)
487 content.add_raw_html """<span class="caret"></span></a>"""
488 var res = content.open("ul").add_class("dropdown-menu").attr("role", "menu").attr("aria-labelledby", "dLabel")
489 content.add_raw_html "</div>"
490 return res
491 end
492 end
493
494 ##
495
496 # Model entity or whatever that can produce an infobox
497 interface HInfoBoxable
498 # An new infobox documenting the entity
499 fun infobox(v: HighlightVisitor): HInfoBox is abstract
500 end
501
502 redef class MDoc
503 # Append an entry for the doc in the given infobox
504 fun fill_infobox(res: HInfoBox)
505 do
506 if content.length < 2 then
507 res.new_field("doc").text(content.first)
508 return
509 end
510 var c = res.new_dropdown("doc", content.first)
511 for x in content.iterator.skip_head(1) do
512 c.append x
513 c.add_raw_html "<br>"
514 end
515 end
516 end
517
518 redef class MEntity
519 super HInfoBoxable
520
521 # A HTML version of `to_s` with hyper-links.
522 #
523 # By default, `linkto_text(v, to_s)` is used, c.f. see `linkto_text`.
524 #
525 # For some complex entities, like generic types, multiple `<a>` and `<span>` elements can be generated.
526 # E.g. `Array[Int]` might become `<a>Array</a>[<a>Int</a>]` with the correct `href` attributes
527 # provided by `v.hrefto`.
528 fun linkto(v: HighlightVisitor): HTMLTag do return linkto_text(v, to_s)
529
530 # Link to the `self` with a specific text.
531 #
532 # The whole text is linked with a single `<a>` element.
533 #
534 # The `href` used is provided by `v.hrefto`.
535 # If `href` is null then a `<span>` element is used instead of `<a>`.
536 fun linkto_text(v: HighlightVisitor, text: String): HTMLTag
537 do
538 var href = v.hrefto(self)
539 if href == null then
540 return (new HTMLTag("span")).text(text)
541 end
542 return (new HTMLTag("a")).attr("href", href).text(text)
543 end
544
545 # Append an entry for the doc in the given infobox
546 private fun add_doc_to_infobox(res: HInfoBox)
547 do
548 var mdoc = mdoc_or_fallback
549 if mdoc != null then mdoc.fill_infobox(res)
550 end
551 end
552
553 redef class MModule
554 redef fun infobox(v)
555 do
556 var res = new HInfoBox(v, "module {name}")
557 res.href = v.hrefto(self)
558 res.new_field("module").add(linkto(v))
559 add_doc_to_infobox(res)
560 if in_importation.greaters.length > 1 then
561 var c = res.new_dropdown("imports", "{in_importation.greaters.length-1} modules")
562 for x in in_importation.greaters do
563 if x == self then continue
564 c.open("li").add x.linkto(v)
565 end
566 end
567 return res
568 end
569
570 redef fun linkto(v) do return linkto_text(v, name)
571 end
572
573 redef class MClassDef
574 redef fun infobox(v)
575 do
576 var res = new HInfoBox(v, "class {mclass.name}")
577 res.href = v.hrefto(self)
578 if is_intro then
579 res.new_field("class").text(mclass.name)
580 else
581 res.new_field("redef class").text(mclass.name)
582 res.new_field("intro").add mclass.intro.linkto_text(v, "in {mclass.intro_mmodule.to_s}")
583 end
584 add_doc_to_infobox(res)
585
586 var in_hierarchy = self.in_hierarchy
587 if in_hierarchy == null then return res
588
589 if in_hierarchy.greaters.length > 1 then
590 var c = res.new_dropdown("hier", "super-classes")
591 for x in in_hierarchy.greaters do
592 if x == self then continue
593 if not x.is_intro then continue
594 c.open("li").add x.linkto(v)
595 end
596 end
597 if in_hierarchy.smallers.length > 1 then
598 var c = res.new_dropdown("hier", "sub-classes")
599 for x in in_hierarchy.smallers do
600 if x == self then continue
601 if not x.is_intro then continue
602 c.open("li").add x.linkto(v)
603 end
604 end
605 if mclass.mclassdefs.length > 1 then
606 var c = res.new_dropdown("redefs", "refinements")
607 for x in mclass.mclassdefs do
608 if x == self then continue
609 c.open("li").add x.linkto_text(v, "in {x.mmodule}")
610 end
611 end
612 return res
613 end
614 end
615
616 redef class MPropDef
617 redef fun infobox(v)
618 do
619 var res = new HInfoBox(v, to_s)
620 res.href = v.hrefto(self)
621 if self isa MMethodDef then
622 var msignature = self.msignature
623 if msignature != null then res.new_field("fun").append(mproperty.name).add msignature.linkto(v)
624 else if self isa MAttributeDef then
625 var static_mtype = self.static_mtype
626 if static_mtype != null then res.new_field("fun").append(mproperty.name).add static_mtype.linkto(v)
627 else if self isa MVirtualTypeDef then
628 var bound = self.bound
629 if bound != null then res.new_field("add").append(mproperty.name).add bound.linkto(v)
630 else
631 res.new_field("wat?").append(mproperty.name)
632 end
633
634 if is_intro then
635 else
636 res.new_field("intro").add mproperty.intro.linkto_text(v, "in {mproperty.intro.mclassdef}")
637 end
638 add_doc_to_infobox(res)
639 if mproperty.mpropdefs.length > 1 then
640 var c = res.new_dropdown("redef", "redefinitions")
641 for x in mproperty.mpropdefs do
642 c.open("li").add x.linkto_text(v, "in {x.mclassdef}")
643 end
644 end
645
646 return res
647 end
648 end
649
650 redef class MClassType
651 redef fun infobox(v)
652 do
653 var res = new HInfoBox(v, to_s)
654 res.href = v.hrefto(self)
655 res.new_field("class").add mclass.intro.linkto(v)
656 add_doc_to_infobox(res)
657 return res
658 end
659 redef fun linkto(v)
660 do
661 return mclass.intro.linkto(v)
662 end
663 end
664 redef class MVirtualType
665 redef fun infobox(v)
666 do
667 var res = new HInfoBox(v, to_s)
668 res.href = v.hrefto(mproperty)
669 var p = mproperty
670 res.new_field("virtual type").add p.intro.linkto(v)
671 add_doc_to_infobox(res)
672 return res
673 end
674 redef fun linkto(v)
675 do
676 return mproperty.intro.linkto(v)
677 end
678 end
679 redef class MParameterType
680 redef fun infobox(v)
681 do
682 var res = new HInfoBox(v, to_s)
683 res.new_field("parameter type").append("{name} from class ").add mclass.intro.linkto(v)
684 return res
685 end
686 end
687
688 redef class MNullableType
689 redef fun infobox(v)
690 do
691 return mtype.infobox(v)
692 end
693 redef fun linkto(v)
694 do
695 var res = new HTMLTag("span")
696 res.append("nullable ").add(mtype.linkto(v))
697 return res
698 end
699 end
700
701 redef class MNotNullType
702 redef fun infobox(v)
703 do
704 return mtype.infobox(v)
705 end
706 redef fun linkto(v)
707 do
708 var res = new HTMLTag("span")
709 res.append("not null ").add(mtype.linkto(v))
710 return res
711 end
712 end
713
714 redef class MNullType
715 redef fun infobox(v)
716 do
717 var res = new HInfoBox(v, to_s)
718 return res
719 end
720 redef fun linkto(v)
721 do
722 var res = new HTMLTag("span")
723 res.append("null")
724 return res
725 end
726 end
727
728 redef class MSignature
729 redef fun linkto(v)
730 do
731 var res = new HTMLTag("span")
732 var first = true
733 if not mparameters.is_empty then
734 res.append "("
735 for p in mparameters do
736 if first then
737 first = false
738 else
739 res.append ", "
740 end
741 res.append p.name
742 res.append ": "
743 res.add p.mtype.linkto(v)
744 end
745 res.append ")"
746 end
747 var ret = return_mtype
748 if ret != null then
749 res.append ": "
750 res.add ret.linkto(v)
751 end
752 return res
753 end
754 end
755
756 redef class CallSite
757 redef fun infobox(v)
758 do
759 var res = new HInfoBox(v, "call {mpropdef}")
760 res.href = v.hrefto(mpropdef)
761 res.new_field("call").add(mpropdef.linkto(v)).add(msignature.linkto(v))
762 if mpropdef.is_intro then
763 else
764 res.new_field("intro").add mproperty.intro.linkto_text(v, "in {mproperty.intro.mclassdef}")
765 end
766 add_doc_to_infobox(res)
767
768 return res
769 end
770 redef fun linkto(v)
771 do
772 return mpropdef.linkto(v)
773 end
774 end
775
776 redef class Variable
777 super HInfoBoxable
778 redef fun infobox(v)
779 do
780 var declared_type = self.declared_type
781 if declared_type == null then
782 var res = new HInfoBox(v, "{name}")
783 res.new_field("local var").append("{name}")
784 return res
785 end
786 var res = new HInfoBox(v, "{name}: {declared_type}")
787 res.new_field("local var").append("{name}:").add(declared_type.linkto(v))
788 return res
789 end
790 end
791
792
793 ##
794
795 redef class ANode
796 # Optionally creates a tag that encapsulate the AST element on HTML rendering
797 protected fun make_tag(v: HighlightVisitor): nullable HTMLTag do return null
798
799 # Add aditionnal information on a child-token and return an additionnal HInfoBox on it
800 protected fun decorate_tag(v: HighlightVisitor, res: HTMLTag, token: Token): nullable HInfoBox
801 do
802 #debug("no decoration for {token.inspect}")
803 #res.add_class("nc_error")
804 return null
805 end
806
807 # Return a optional infobox
808 fun infobox(v: HighlightVisitor): nullable HInfoBox do return null
809 end
810
811 redef class AQclassid
812 redef fun decorate_tag(v, res, token)
813 do
814 if token != n_id then return null
815 var parent = self.parent
816 if parent == null then return null
817 return parent.decorate_tag(v, res, token)
818 end
819 end
820
821 redef class AQid
822 redef fun decorate_tag(v, res, token)
823 do
824 if token != n_id then return null
825 var parent = self.parent
826 if parent == null then return null
827 return parent.decorate_tag(v, res, token)
828 end
829 end
830
831 redef class AStdClassdef
832 redef fun make_tag(v)
833 do
834 var res = new HTMLTag("span")
835 res.add_class("nc_cdef")
836 var md = mclassdef
837 if md != null then res.attr("id", md.to_s)
838 return res
839 end
840 redef fun decorate_tag(v, res, token)
841 do
842 if not token isa TClassid then return null
843 res.add_class("nc_def")
844
845 var md = mclassdef
846 if md == null then return null
847 return md.infobox(v)
848 end
849 end
850 redef class APropdef
851 redef fun make_tag(v)
852 do
853 var res = new HTMLTag("span")
854 res.add_class("nc_pdef")
855 var mpd
856 mpd = mpropdef
857 if mpd != null then
858 #res.add(tag(mpd))
859 res.attr("id", mpd.to_s)
860 end
861 if self isa AAttrPropdef then
862 mpd = mreadpropdef
863 if mpd != null then res.add(tag(mpd))
864 mpd = mwritepropdef
865 if mpd != null then res.add(tag(mpd))
866 end
867 return res
868 end
869
870 private fun tag(mpd: MPropDef): HTMLTag
871 do
872 var a = new HTMLTag("a")
873 a.attr("id", mpd.to_s)
874 return a
875 end
876 end
877
878 redef class Token
879 # Produce an HTMLTag with the correct contents and CSS classes
880 # Subclasses can redefine it to decorate the tag
881 redef fun make_tag(v): HTMLTag
882 do
883 var res = new HTMLTag("span")
884 res.text(text)
885 return res
886 end
887 end
888
889 redef class TokenKeyword
890 redef fun make_tag(v)
891 do
892 var res = super
893 res.add_class("nc_k")
894 return res
895 end
896 end
897 redef class TokenOperator
898 redef fun make_tag(v)
899 do
900 var res = super
901 res.add_class("nc_o")
902 return res
903 end
904 end
905
906 redef class AVarFormExpr
907 redef fun decorate_tag(v, res, token)
908 do
909 if token != n_id then return null
910 var variable = self.variable
911 if variable == null then return null
912 res.add_class("nc_v")
913 return variable.infobox(v)
914 end
915 end
916
917 redef class AVardeclExpr
918 redef fun decorate_tag(v, res, token)
919 do
920 if token != n_id then return null
921 var variable = self.variable
922 if variable == null then return null
923 res.add_class("nc_v")
924 return variable.infobox(v)
925 end
926 end
927
928 redef class AForGroup
929 redef fun decorate_tag(v, res, token)
930 do
931 if not token isa TId then return null
932 var vs = variables
933 if vs == null then return null
934 res.add_class("nc_v")
935 var idx = n_ids.index_of(token)
936 var variable = vs[idx]
937 return variable.infobox(v)
938 end
939 end
940
941 redef class AParam
942 redef fun decorate_tag(v, res, token)
943 do
944 if token != n_id then return null
945 var mp = mparameter
946 if mp == null then return null
947 var variable = self.variable
948 if variable == null then return null
949 res.add_class("nc_v")
950 return variable.infobox(v)
951 end
952 end
953
954 redef class AAssertExpr
955 redef fun decorate_tag(v, res, token)
956 do
957 if not token isa TId then return null
958 res.add_class("nc_ast")
959 return null
960 end
961 end
962
963 redef class ALabel
964 redef fun decorate_tag(v, res, token)
965 do
966 if not token isa TId then return null
967 res.add_class("nc_la")
968 return null
969 end
970 end
971
972 redef class ASendExpr
973 redef fun decorate_tag(v, res, token)
974 do
975 var callsite = self.callsite
976 if callsite == null then return null
977 return callsite.infobox(v)
978 end
979 end
980
981 redef class ANewExpr
982 redef fun decorate_tag(v, res, token)
983 do
984 var callsite = self.callsite
985 if callsite == null then return null
986 return callsite.infobox(v)
987 end
988 end
989
990 redef class AAssignOp
991 redef fun decorate_tag(v, res, token)
992 do
993 var p = parent
994 assert p isa AReassignFormExpr
995
996 var callsite = p.reassign_callsite
997 if callsite == null then return null
998 return callsite.infobox(v)
999 end
1000 end
1001
1002 redef class AModuleName
1003 redef fun decorate_tag(v, res, token)
1004 do
1005 var p = parent
1006 if p == null then return null
1007 return p.decorate_tag(v, res, token)
1008 end
1009 end
1010
1011 redef class AModuledecl
1012 redef fun decorate_tag(v, res, token)
1013 do
1014 if not token isa TId then return null
1015 res.add_class("nc_def")
1016 res.add_class("nc_m")
1017 var p = parent
1018 assert p isa AModule
1019 var mm = p.mmodule
1020 if mm == null then return null
1021 return mm.infobox(v)
1022 end
1023 end
1024
1025 redef class AStdImport
1026 redef fun decorate_tag(v, res, token)
1027 do
1028 if not token isa TId then return null
1029 res.add_class("nc_m")
1030 var mm = mmodule
1031 if mm == null then return null
1032 return mm.infobox(v)
1033 end
1034 end
1035 redef class AAttrPropdef
1036 redef fun decorate_tag(v, res, token)
1037 do
1038 if not token isa TId then return null
1039 res.add_class("nc_def")
1040 var mpd: nullable MPropDef
1041 mpd = mreadpropdef
1042 if mpd == null then mpd = mpropdef
1043 if mpd == null then return null
1044 return mpd.infobox(v)
1045 end
1046 end
1047
1048 redef class TId
1049 redef fun make_tag(v)
1050 do
1051 var res = super
1052 res.add_class("nc_i")
1053 return res
1054 end
1055 end
1056 redef class AMethid
1057 redef fun make_tag(v)
1058 do
1059 var res = new HTMLTag("span")
1060 res.add_class("nc_def")
1061 return res
1062 end
1063 redef fun decorate_tag(v, res, token)
1064 do
1065 return null
1066 # nothing to decorate
1067 end
1068 redef fun infobox(v)
1069 do
1070 var p = parent
1071 if not p isa AMethPropdef then return null
1072 var mpd = p.mpropdef
1073 if mpd == null then return null
1074 return mpd.infobox(v)
1075 end
1076 end
1077 redef class TAttrid
1078 redef fun make_tag(v)
1079 do
1080 var res = super
1081 res.add_class("nc_a")
1082 return res
1083 end
1084 end
1085 redef class AAttrFormExpr
1086 redef fun decorate_tag(v, res, token)
1087 do
1088 if not token isa TAttrid then return null
1089 var p = mproperty
1090 if p == null then return null
1091 return p.intro.infobox(v)
1092 end
1093 end
1094 redef class TClassid
1095 redef fun make_tag(v)
1096 do
1097 var res = super
1098 res.add_class("nc_t")
1099 return res
1100 end
1101 end
1102 redef class AType
1103 redef fun decorate_tag(v, res, token)
1104 do
1105 if not token isa TClassid then return null
1106 var mt = mtype
1107 if mt == null then return null
1108 mt = mt.undecorate
1109 if mt isa MFormalType then
1110 res.add_class("nc_vt")
1111 end
1112 return mt.infobox(v)
1113 end
1114 end
1115 redef class AFormaldef
1116 redef fun decorate_tag(v, res, token)
1117 do
1118 if not token isa TClassid then return null
1119 res.add_class("nc_vt")
1120 var mtype = self.mtype
1121 if mtype == null then return null
1122 return mtype.infobox(v)
1123 end
1124 end
1125 redef class ATypePropdef
1126 redef fun decorate_tag(v, res, token)
1127 do
1128 if not token isa TClassid then return null
1129 res.add_class("nc_def")
1130 var md = mpropdef
1131 if md == null then return null
1132 return md.infobox(v)
1133 end
1134 end
1135 redef class TComment
1136 redef fun make_tag(v)
1137 do
1138 var res = super
1139 if is_loose then
1140 res.add_class("nc_c")
1141 end
1142 return res
1143 end
1144 end
1145 redef class ADoc
1146 redef fun make_tag(v)
1147 do
1148 var res = new HTMLTag("span")
1149 res.add_class("nc_d")
1150 return res
1151 end
1152 end
1153 redef class TokenLiteral
1154 redef fun make_tag(v)
1155 do
1156 var res = super
1157 res.add_class("nc_l")
1158 return res
1159 end
1160 end
1161 redef class ASuperstringExpr
1162 redef fun make_tag(v)
1163 do
1164 var res = new HTMLTag("span")
1165 res.add_class("nc_ss")
1166 return res
1167 end
1168 end
1169 redef class AStringFormExpr
1170 redef fun decorate_tag(v, res, token)
1171 do
1172 # Workaround to tag strings
1173 res.classes.remove("nc_l")
1174 res.add_class("nc_s")
1175 return super
1176 end
1177 end
1178 redef class AExpr
1179 redef fun decorate_tag(v, res, token)
1180 do
1181 var t = mtype
1182 if t == null then return null
1183 return t.infobox(v)
1184 end
1185 end