nitlight: new tool nitlight (and new module highlight)
[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 # Highliting of Nit AST
16 module highlight
17
18 import modelize_property
19 import frontend
20 import parser_util
21 import html
22
23 # Visitor used to produce a HTML tree based on a AST on a `Source`
24 class HighlightVisitor
25 super Visitor
26
27 # The root of the HTML hierarchy
28 var html = new HTMLTag("span")
29
30 private var token_head: HTMLTag
31
32 private var prod_head: HTMLTag
33
34 private var prod_root: nullable HTMLTag
35
36 # Is the HTML include a nested `<span class"{type_of_node}">` element for each `ANode` of the AST?
37 # Used to have a really huge and verbose HTML (mainly for debug)
38 var with_ast writable = false
39
40 # Enter in a new node
41 # Exit is automatic
42 fun enter(n: HTMLTag)
43 do
44 if prod_root != null then
45 prod_head.add(n)
46 prod_head = n
47 else
48 prod_root = n
49 prod_head = n
50 end
51 end
52
53 # The position in the source file.
54 # Used to print parts of the source betwen tokens of the AST
55 private var pos = 0
56
57 var modelbuilder: ModelBuilder
58
59 init(modelbuilder: ModelBuilder)
60 do
61 self.modelbuilder = modelbuilder
62 html.add_class("nitcode")
63 token_head = html
64 prod_head = html
65 end
66
67 # Used to remember the first node, thus knowing when the whole visit is over
68 private var first_node: nullable ANode
69
70 private var seen_token = new HashSet[Token]
71
72 private fun process_upto_token(node: Token)
73 do
74 # recursively process previous tokens
75 var prev = node.prev_token
76 if prev != null and not seen_token.has(prev) then
77 process_upto_token(prev)
78 prev.accept_highlight_visitor(self)
79 end
80
81 # Add text between `last_token` and `node`
82 var pstart = node.location.pstart
83 if pos < pstart then
84 var text = node.location.file.string.substring(pos, pstart-pos)
85 token_head.append(text)
86 #node.debug("WRT: {token_head.classes} << '{text.escape_to_c}' ")
87 end
88 pos = node.location.pend + 1
89 if pos < pstart then
90 node.debug("pos={pos}, pstart={pstart}, pend={node.location.pend}")
91 end
92
93 seen_token.add node
94 end
95
96 # Dubuging method
97 private fun where(node: ANode, tag: String)
98 do
99 var pr = prod_root
100 if pr == null then
101 node.debug "{tag}-> {token_head.classes} : {prod_head.classes}"
102 else
103 node.debug "{tag}-> {token_head.classes} : {pr.classes}..{prod_head.classes}"
104 end
105 end
106
107 redef fun visit(node)
108 do
109 if first_node == null then first_node = node
110
111 if node isa Token then
112 process_upto_token(node)
113
114 #where(node, "TOK")
115 var pr = prod_root
116 if pr != null then
117 #node.debug("ADD: {token_head.classes} << {pr.classes} ")
118 token_head.add(pr)
119 token_head = prod_head
120 prod_root = null
121 end
122 end
123
124 var oldph = prod_head
125 #where(node, " IN")
126 node.accept_highlight_visitor(self)
127 #where(node, "OUT")
128 var pr = prod_root
129 if pr == null then
130 assert token_head == prod_head
131 else
132 assert token_head != prod_head
133 token_head.add(pr)
134 prod_root = null
135 end
136 prod_head = oldph
137 token_head = oldph
138 #where(node, " IS")
139
140 if node == first_node then
141 html.append(node.location.file.string.substring_from(pos))
142 end
143 end
144
145 # Return a default CSS content related to CSS classes used in the `html` tree.
146 # Could be inlined in the `.html` file of saved as a specific `.css` file.
147 fun css_content: String
148 do
149 return """
150 .nitcode a { color: inherit; text-decoration: inherit; } /* hide links */
151 .nitcode a:hover { text-decoration: underline; } /* underline links */
152 .nitcode span[title]:hover { text-decoration: underline; } /* underline titles */
153 /* lexical raw tokens. independent of usage or semantic: */
154 .nitcode .nc_c { color: gray; font-style: italic; } /* comment */
155 .nitcode .nc_d { color: #3D8127; font-style: italic; } /* documentation comments */
156 .nitcode .nc_k { font-weight: bold; } /* keyword */
157 .nitcode .nc_o {} /* operator */
158 .nitcode .nc_i {} /* standard identifier */
159 .nitcode .nc_t { color: #445588; font-weight: bold; } /* type/class identifier */
160 .nitcode .nc_a { color: #445588; font-style: italic; } /* old style attribute identifier */
161 .nitcode .nc_l { color: #009999; } /* char and number literal */
162 .nitcode .nc_s { color: #8F1546; } /* string literal */
163 /* syntactic token usage. added because of their position in the AST */
164 .nitcode .nc_ast { color: blue; } /* assert label */
165 .nitcode .nc_la { color: blue; } /* break/continue label */
166 .nitcode .nc_m { color: #445588; } /* module name */
167 /* syntactic groups */
168 .nitcode .nc_def { font-weight: bold; color: blue; } /* name used in a definition */
169 .nitcode .nc_def.nc_a { color: blue; } /* name used in a attribute definition */
170 .nitcode .nc_def.nc_t { color: blue; } /* name used in a class or vt definition */
171 .nitcode .nc_ss { color: #9E6BEB; } /* superstrings */
172 .nitcode .nc_cdef {} /* A whole class definition */
173 .nitcode .nc_pdef {} /* A whole property definition */
174 /* semantic token usage */
175 .nitcode .nc_v { font-style: italic; } /* local variable or parameter */
176 .nitcode .nc_vt { font-style: italic; } /* virtual type or formal type */
177
178 .nitcode .nc_error { border: 1px red solid;} /* not used */
179 """
180 end
181 end
182
183 redef class ANode
184 private fun accept_highlight_visitor(v: HighlightVisitor)
185 do
186 if v.with_ast then
187 var res = new HTMLTag("span")
188 res.add_class(class_name)
189 v.enter res
190 end
191 visit_all(v)
192 end
193 private fun decorate_tag(res: HTMLTag, token: Token)
194 do
195 #debug("no decoration for {token.inspect}")
196 #res.add_class("nc_error")
197 end
198 end
199
200 redef class AStdClassdef
201 redef fun accept_highlight_visitor(v)
202 do
203 var res = new HTMLTag("span")
204 res.add_class("nc_cdef")
205 var md = mclassdef
206 if md != null then
207 var a = new HTMLTag("a")
208 a.attr("id", md.to_s)
209 res.add(a)
210 end
211 v.enter res
212 super
213 end
214 redef fun decorate_tag(res, token)
215 do
216 res.add_class("nc_def")
217
218 var md = mclassdef
219 if md == null then return
220 var mc = md.mclass
221 res.attrs["title"] = mc.full_name
222 var mi = mc.intro
223 if md != mi then
224 res.attrs["link"] = mi.mmodule.name + ".html#" + mi.to_s
225 end
226 end
227 end
228 redef class APropdef
229 redef fun accept_highlight_visitor(v)
230 do
231 var res = new HTMLTag("span")
232 res.add_class("nc_pdef")
233 var mpd
234 mpd = mpropdef
235 if mpd != null then res.add(tag(mpd))
236 if self isa AAttrPropdef then
237 mpd = mreadpropdef
238 if mpd != null then res.add(tag(mpd))
239 mpd = mwritepropdef
240 if mpd != null then res.add(tag(mpd))
241 end
242 v.enter res
243 super
244 end
245
246 private fun tag(mpd: MPropDef): HTMLTag
247 do
248 var a = new HTMLTag("a")
249 a.attr("id", mpd.to_s)
250 return a
251 end
252 end
253
254 redef class Token
255 # Produce an HTMLTag with the correct contents and CSS classes
256 # Subclasses can redefine it to decorate the tag
257 protected fun make_tag(v: HighlightVisitor): HTMLTag
258 do
259 var res = new HTMLTag("span")
260 res.text(text)
261 return res
262 end
263
264 # Use `empty_tag` to create the tag ; then fill it and add it to the html
265 redef fun accept_highlight_visitor(v)
266 do
267 var n = make_tag(v)
268 if n.attrs.is_empty and n.classes.is_empty then
269 for c in n.children do
270 v.token_head.add(c)
271 end
272 else if n.attrs.has_key("link") then
273 var a = new HTMLTag("a")
274 a.attrs["href"] = n.attrs["link"]
275 n.attrs.keys.remove("link")
276 a.add(n)
277 v.token_head.add(a)
278 else
279 v.token_head.add(n)
280 end
281 #debug("WRT: {v.token_head.classes} << '{text.escape_to_c}' ")
282 end
283 end
284 redef class TokenKeyword
285 redef fun make_tag(v)
286 do
287 var res = super
288 res.add_class("nc_k")
289 return res
290 end
291 end
292 redef class TokenOperator
293 redef fun make_tag(v)
294 do
295 var res = super
296 var p = parent
297 if p != null then p.decorate_tag(res, self)
298 res.add_class("nc_o")
299 return res
300 end
301 end
302
303 redef class Variable
304 private fun decorate_tag(res: HTMLTag, token: Token)
305 do
306 if declared_type == null then return
307 res.attrs["title"] = name + ": " + declared_type.to_s
308 end
309 end
310
311 redef class AVarFormExpr
312 redef fun decorate_tag(res, token)
313 do
314 res.add_class("nc_v")
315 var variable = self.variable
316 if variable == null then return
317 variable.decorate_tag(res, token)
318 end
319 end
320
321 redef class AVardeclExpr
322 redef fun decorate_tag(res, token)
323 do
324 res.add_class("nc_v")
325 var variable = self.variable
326 if variable == null then return
327 variable.decorate_tag(res, token)
328 end
329 end
330
331 redef class AForExpr
332 redef fun decorate_tag(res, token)
333 do
334 res.add_class("nc_v")
335 var vs = variables
336 if vs == null then return
337 var idx = n_ids.index_of(token.as(TId))
338 var variable = vs[idx]
339 variable.decorate_tag(res, token)
340 end
341 end
342
343 redef class AParam
344 redef fun decorate_tag(res, token)
345 do
346 res.add_class("nc_v")
347 var mp = mparameter
348 if mp == null then return
349 res.attrs["title"] = mp.name + ": " + mp.mtype.to_s
350 end
351 end
352
353 redef class AAssertExpr
354 redef fun decorate_tag(res, token)
355 do
356 res.add_class("nc_ast")
357 end
358 end
359
360 redef class ALabel
361 redef fun decorate_tag(res, token)
362 do
363 res.add_class("nc_la")
364 end
365 end
366
367 redef class ASendExpr
368 redef fun decorate_tag(res, token)
369 do
370 if callsite == null then return
371 var mpropdef = callsite.mpropdef
372 res.attrs["title"] = mpropdef.to_s + callsite.msignature.to_s
373 res.attrs["link"] = mpropdef.mclassdef.mmodule.name + ".html#" + mpropdef.to_s
374 end
375 end
376
377 redef class ANewExpr
378 redef fun decorate_tag(res, token)
379 do
380 if callsite == null then return
381 var mpropdef = callsite.mpropdef
382 res.attrs["title"] = mpropdef.to_s + callsite.msignature.to_s
383 res.attrs["link"] = mpropdef.mclassdef.mmodule.name + ".html#" + mpropdef.to_s
384 end
385 end
386
387 redef class AAssignOp
388 redef fun decorate_tag(res, v)
389 do
390 var p = parent
391 assert p isa AReassignFormExpr
392
393 var callsite = p.reassign_callsite
394 if callsite == null then return
395 var mpropdef = callsite.mpropdef
396 res.attrs["title"] = mpropdef.to_s + callsite.msignature.to_s
397 res.attrs["link"] = mpropdef.mclassdef.mmodule.name + ".html#" + mpropdef.to_s
398 end
399 end
400
401 redef class AModuleName
402 redef fun decorate_tag(res, token)
403 do
404 parent.decorate_tag(res, token)
405 end
406 end
407
408 redef class AModuledecl
409 redef fun decorate_tag(res, token)
410 do
411 res.add_class("nc_def")
412 res.add_class("nc_m")
413 var p = parent
414 assert p isa AModule
415 var mm = p.mmodule
416 if mm == null then return
417 res.attrs["title"] = mm.full_name
418 end
419 end
420
421 redef class AStdImport
422 redef fun decorate_tag(res, token)
423 do
424 res.add_class("nc_m")
425 var mm = mmodule
426 if mm == null then return
427 res.attrs["title"] = mm.full_name
428 res.attrs["link"] = mm.name + ".html"
429 end
430 end
431
432 redef class AAttrPropdef
433 redef fun decorate_tag(res, token)
434 do
435 res.add_class("nc_def")
436 var mpd: nullable MPropDef
437 mpd = mreadpropdef
438 if mpd == null then mpd = mpropdef
439 if mpd == null then return
440 var mp = mpd.mproperty
441 res.attrs["title"] = mp.full_name
442 if mp.intro != mpd then
443 mpd = mp.intro
444 res.attrs["link"] = mpd.mclassdef.mmodule.name + ".html#" + mpd.to_s
445 end
446 end
447 end
448
449 redef class TId
450 redef fun make_tag(v)
451 do
452 var res = super
453 var p = parent
454 if p != null then p.decorate_tag(res, self)
455 res.add_class("nc_i")
456 return res
457 end
458 end
459 redef class AMethid
460 redef fun accept_highlight_visitor(v)
461 do
462 var res = new HTMLTag("span")
463 res.add_class("nc_def")
464 var p = parent
465 if p isa AMethPropdef then
466 var mpd = p.mpropdef
467 if mpd != null then
468 var mp = mpd.mproperty
469 res.attr("title", mp.full_name)
470 if mp.intro != mpd then
471 mpd = mp.intro
472 var link = mpd.mclassdef.mmodule.name + ".html#" + mpd.to_s
473 var l = new HTMLTag("a")
474 l.attr("href", link)
475 v.enter l
476 end
477 end
478 end
479 v.enter res
480 super
481 end
482 redef fun decorate_tag(res, v)
483 do
484 # nothing to decorate
485 end
486 end
487 redef class TAttrid
488 redef fun make_tag(v)
489 do
490 var res = super
491 var p = parent
492 if p != null then p.decorate_tag(res, self)
493 res.add_class("nc_a")
494 return res
495 end
496 end
497 redef class AAttrFormExpr
498 redef fun decorate_tag(res, v)
499 do
500 var p = mproperty
501 if p == null then return
502 res.attrs["title"] = p.full_name
503 var pi = p.intro
504 res.attrs["link"] = pi.mclassdef.mmodule.name + ".html#" + pi.to_s
505 end
506 end
507 redef class TClassid
508 redef fun make_tag(v)
509 do
510 var res = super
511 var p = parent
512 if p != null then p.decorate_tag(res, self)
513 res.add_class("nc_t")
514 return res
515 end
516 end
517 redef class AType
518 redef fun decorate_tag(res, token)
519 do
520 var mt = mtype
521 if mt == null then return
522 var title = mt.to_s
523 if mt isa MNullableType then mt = mt.mtype
524 if mt isa MVirtualType or mt isa MParameterType then
525 res.add_class("nc_vt")
526 else if mt isa MClassType then
527 title = mt.mclass.full_name
528 res.attrs["link"] = mt.mclass.intro.mmodule.name + ".html#" + mt.mclass.intro.to_s
529 end
530 res.attrs["title"] = title
531 end
532 end
533 redef class AFormaldef
534 redef fun decorate_tag(res, token)
535 do
536 res.add_class("nc_vt")
537 if mtype == null then return
538 res.attrs["title"] = "{mtype.to_s}: {bound.to_s}"
539 end
540 end
541 redef class ATypePropdef
542 redef fun decorate_tag(res, token)
543 do
544 res.add_class("nc_def")
545 var md = mpropdef
546 if md == null then return
547 var mp = mpropdef.mproperty
548 res.attrs["title"] = mp.full_name
549 var mi = mp.intro
550 if md != mi then
551 res.attrs["link"] = mi.mclassdef.mmodule.name + ".html#" + mi.to_s
552 end
553 end
554 end
555 redef class TComment
556 redef fun make_tag(v)
557 do
558 var res = super
559 if parent == null then
560 res.add_class("nc_c")
561 else
562 assert parent isa ADoc
563 end
564 return res
565 end
566 end
567 redef class ADoc
568 redef fun accept_highlight_visitor(v)
569 do
570 var res = new HTMLTag("span")
571 res.add_class("nc_d")
572 v.enter res
573 super
574 end
575 end
576 redef class TokenLiteral
577 redef fun make_tag(v)
578 do
579 var res = super
580 res.add_class("nc_l")
581 var p = parent
582 if p isa AStringFormExpr then p.decorate_tag(res, self)
583 return res
584 end
585 end
586 redef class ASuperstringExpr
587 redef fun accept_highlight_visitor(v)
588 do
589 var res = new HTMLTag("span")
590 res.add_class("nc_ss")
591 v.enter res
592 super
593 end
594 end
595 redef class AStringFormExpr
596 redef fun decorate_tag(res, v)
597 do
598 # Workarount to tag strings
599 res.classes.remove("nc_l")
600 res.add_class("nc_s")
601 end
602 end
603