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