##// END OF EJS Templates
annotations: replace annotated source code viewer with renderer...
dan -
r986:e7837355 default
parent child Browse files
Show More
@@ -0,0 +1,147 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 from itertools import groupby
23
24 from pygments import lex
25 # PYGMENTS_TOKEN_TYPES is used in a hot loop keep attribute lookups to a minimum
26 from pygments.token import STANDARD_TYPES as PYGMENTS_TOKEN_TYPES
27
28 from rhodecode.lib.helpers import get_lexer_for_filenode
29
30 def tokenize_file(content, lexer):
31 """
32 Use pygments to tokenize some content based on a lexer
33 ensuring all original new lines and whitespace is preserved
34 """
35
36 lexer.stripall = False
37 lexer.stripnl = False
38 lexer.ensurenl = False
39 return lex(content, lexer)
40
41
42 def pygment_token_class(token_type):
43 """ Convert a pygments token type to html class name """
44
45 fname = PYGMENTS_TOKEN_TYPES.get(token_type)
46 if fname:
47 return fname
48
49 aname = ''
50 while fname is None:
51 aname = '-' + token_type[-1] + aname
52 token_type = token_type.parent
53 fname = PYGMENTS_TOKEN_TYPES.get(token_type)
54
55 return fname + aname
56
57
58 def tokens_as_lines(tokens, split_string=u'\n'):
59 """
60 Take a list of (TokenType, text) tuples and split them by a string
61
62 eg. [(TEXT, 'some\ntext')] => [(TEXT, 'some'), (TEXT, 'text')]
63 """
64
65 buffer = []
66 for token_type, token_text in tokens:
67 parts = token_text.split(split_string)
68 for part in parts[:-1]:
69 buffer.append((token_type, part))
70 yield buffer
71 buffer = []
72
73 buffer.append((token_type, parts[-1]))
74
75 if buffer:
76 yield buffer
77
78
79 def filenode_as_lines_tokens(filenode):
80 """
81 Return a generator of lines with pygment tokens for a filenode eg:
82
83 [
84 (1, line1_tokens_list),
85 (2, line1_tokens_list]),
86 ]
87 """
88
89 return enumerate(
90 tokens_as_lines(
91 tokenize_file(
92 filenode.content, get_lexer_for_filenode(filenode)
93 )
94 ),
95 1)
96
97
98 def filenode_as_annotated_lines_tokens(filenode):
99 """
100 Take a file node and return a list of annotations => lines, if no annotation
101 is found, it will be None.
102
103 eg:
104
105 [
106 (annotation1, [
107 (1, line1_tokens_list),
108 (2, line2_tokens_list),
109 ]),
110 (annotation2, [
111 (3, line1_tokens_list),
112 ]),
113 (None, [
114 (4, line1_tokens_list),
115 ]),
116 (annotation1, [
117 (5, line1_tokens_list),
118 (6, line2_tokens_list),
119 ])
120 ]
121 """
122
123
124 # cache commit_getter lookups
125 commit_cache = {}
126 def _get_annotation(commit_id, commit_getter):
127 if commit_id not in commit_cache:
128 commit_cache[commit_id] = commit_getter()
129 return commit_cache[commit_id]
130
131 annotation_lookup = {
132 line_no: _get_annotation(commit_id, commit_getter)
133 for line_no, commit_id, commit_getter, line_content
134 in filenode.annotate
135 }
136
137 annotations_lines = ((annotation_lookup.get(line_no), line_no, tokens)
138 for line_no, tokens
139 in filenode_as_lines_tokens(filenode))
140
141 grouped_annotations_lines = groupby(annotations_lines, lambda x: x[0])
142
143 for annotation, group in grouped_annotations_lines:
144 yield (
145 annotation, [(line_no, tokens)
146 for (_, line_no, tokens) in group]
147 )
@@ -0,0 +1,70 b''
1 <%def name="render_line(line_num, tokens,
2 annotation=None,
3 bgcolor=None)">
4 <%
5 # avoid module lookups for performance
6 from rhodecode.lib.codeblocks import pygment_token_class
7 from rhodecode.lib.helpers import html_escape
8 %>
9 <tr class="cb-line cb-line-fresh"
10 %if annotation:
11 data-revision="${annotation.revision}"
12 %endif
13 >
14 <td class="cb-lineno" id="L${line_num}">
15 <a data-line-no="${line_num}" href="#L${line_num}"></a>
16 </td>
17 <td class="cb-content cb-content-fresh"
18 %if bgcolor:
19 style="background: ${bgcolor}"
20 %endif
21 >${
22 ''.join(
23 '<span class="%s">%s</span>' %
24 (pygment_token_class(token_type), html_escape(token_text))
25 for token_type, token_text in tokens) + '\n' | n
26 }</td>
27 ## this ugly list comp is necessary for performance
28 </tr>
29 </%def>
30
31 <%def name="render_annotation_lines(annotation, lines, color_hasher)">
32 <%
33 rowspan = len(lines) + 1 # span the line's <tr> and annotation <tr>
34 %>
35 %if not annotation:
36 <tr class="cb-annotate">
37 <td class="cb-annotate-message" rowspan="${rowspan}"></td>
38 <td class="cb-annotate-revision" rowspan="${rowspan}"></td>
39 </tr>
40 %else:
41 <tr class="cb-annotate">
42 <td class="cb-annotate-info tooltip"
43 rowspan="${rowspan}"
44 title="Author: ${annotation.author | entity}<br>Date: ${annotation.date}<br>Message: ${annotation.message | entity}"
45 >
46 ${h.gravatar_with_user(annotation.author, 16) | n}
47 <strong class="cb-annotate-message">${
48 h.truncate(annotation.message, len(lines) * 30)
49 }</strong>
50 </td>
51 <td
52 class="cb-annotate-revision"
53 rowspan="${rowspan}"
54 data-revision="${annotation.revision}"
55 onclick="$('[data-revision=${annotation.revision}]').toggleClass('cb-line-fresh')"
56 style="background: ${color_hasher(annotation.raw_id)}">
57 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=annotation.raw_id)}">
58 r${annotation.revision}
59 </a>
60 </td>
61 </tr>
62 %endif
63
64 %for line_num, tokens in lines:
65 ${render_line(line_num, tokens,
66 bgcolor=color_hasher(annotation and annotation.raw_id or ''),
67 annotation=annotation,
68 )}
69 %endfor
70 </%def>
@@ -36,6 +36,8 b' from webob.exc import HTTPNotFound, HTTP'
36 36 from rhodecode.controllers.utils import parse_path_ref
37 37 from rhodecode.lib import diffs, helpers as h, caches
38 38 from rhodecode.lib.compat import OrderedDict
39 from rhodecode.lib.codeblocks import (
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
39 41 from rhodecode.lib.utils import jsonify, action_logger
40 42 from rhodecode.lib.utils2 import (
41 43 convert_line_endings, detect_mode, safe_str, str2bool)
@@ -221,9 +223,15 b' class FilesController(BaseRepoController'
221 223 c.file_author = True
222 224 c.file_tree = ''
223 225 if c.file.is_file():
224 c.renderer = (
225 c.renderer and h.renderer_from_filename(c.file.path))
226 226 c.file_last_commit = c.file.last_commit
227 if c.annotate: # annotation has precedence over renderer
228 c.annotated_lines = filenode_as_annotated_lines_tokens(
229 c.file)
230 else:
231 c.renderer = (
232 c.renderer and h.renderer_from_filename(c.file.path))
233 if not c.renderer:
234 c.lines = filenode_as_lines_tokens(c.file)
227 235
228 236 c.on_branch_head = self._is_valid_head(
229 237 commit_id, c.rhodecode_repo)
@@ -67,7 +67,6 b' from webhelpers.html.tags import _set_in'
67 67 convert_boolean_attrs, NotGiven, _make_safe_id_component
68 68 from webhelpers2.number import format_byte_size
69 69
70 from rhodecode.lib.annotate import annotate_highlight
71 70 from rhodecode.lib.action_parser import action_parser
72 71 from rhodecode.lib.ext_json import json
73 72 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
@@ -125,17 +124,18 b' def asset(path, ver=None):'
125 124 'rhodecode:public/{}'.format(path), _query=query)
126 125
127 126
128 def html_escape(text, html_escape_table=None):
127 default_html_escape_table = {
128 ord('&'): u'&amp;',
129 ord('<'): u'&lt;',
130 ord('>'): u'&gt;',
131 ord('"'): u'&quot;',
132 ord("'"): u'&#39;',
133 }
134
135
136 def html_escape(text, html_escape_table=default_html_escape_table):
129 137 """Produce entities within text."""
130 if not html_escape_table:
131 html_escape_table = {
132 "&": "&amp;",
133 '"': "&quot;",
134 "'": "&apos;",
135 ">": "&gt;",
136 "<": "&lt;",
137 }
138 return "".join(html_escape_table.get(c, c) for c in text)
138 return text.translate(html_escape_table)
139 139
140 140
141 141 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
@@ -500,6 +500,86 b' def get_matching_line_offsets(lines, ter'
500 500 return matching_lines
501 501
502 502
503 def hsv_to_rgb(h, s, v):
504 """ Convert hsv color values to rgb """
505
506 if s == 0.0:
507 return v, v, v
508 i = int(h * 6.0) # XXX assume int() truncates!
509 f = (h * 6.0) - i
510 p = v * (1.0 - s)
511 q = v * (1.0 - s * f)
512 t = v * (1.0 - s * (1.0 - f))
513 i = i % 6
514 if i == 0:
515 return v, t, p
516 if i == 1:
517 return q, v, p
518 if i == 2:
519 return p, v, t
520 if i == 3:
521 return p, q, v
522 if i == 4:
523 return t, p, v
524 if i == 5:
525 return v, p, q
526
527
528 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
529 """
530 Generator for getting n of evenly distributed colors using
531 hsv color and golden ratio. It always return same order of colors
532
533 :param n: number of colors to generate
534 :param saturation: saturation of returned colors
535 :param lightness: lightness of returned colors
536 :returns: RGB tuple
537 """
538
539 golden_ratio = 0.618033988749895
540 h = 0.22717784590367374
541
542 for _ in xrange(n):
543 h += golden_ratio
544 h %= 1
545 HSV_tuple = [h, saturation, lightness]
546 RGB_tuple = hsv_to_rgb(*HSV_tuple)
547 yield map(lambda x: str(int(x * 256)), RGB_tuple)
548
549
550 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
551 """
552 Returns a function which when called with an argument returns a unique
553 color for that argument, eg.
554
555 :param n: number of colors to generate
556 :param saturation: saturation of returned colors
557 :param lightness: lightness of returned colors
558 :returns: css RGB string
559
560 >>> color_hash = color_hasher()
561 >>> color_hash('hello')
562 'rgb(34, 12, 59)'
563 >>> color_hash('hello')
564 'rgb(34, 12, 59)'
565 >>> color_hash('other')
566 'rgb(90, 224, 159)'
567 """
568
569 color_dict = {}
570 cgenerator = unique_color_generator(
571 saturation=saturation, lightness=lightness)
572
573 def get_color_string(thing):
574 if thing in color_dict:
575 col = color_dict[thing]
576 else:
577 col = color_dict[thing] = cgenerator.next()
578 return "rgb(%s)" % (', '.join(col))
579
580 return get_color_string
581
582
503 583 def get_lexer_safe(mimetype=None, filepath=None):
504 584 """
505 585 Tries to return a relevant pygments lexer using mimetype/filepath name,
@@ -536,92 +616,6 b' def pygmentize(filenode, **kwargs):'
536 616 CodeHtmlFormatter(**kwargs)))
537 617
538 618
539 def pygmentize_annotation(repo_name, filenode, **kwargs):
540 """
541 pygmentize function for annotation
542
543 :param filenode:
544 """
545
546 color_dict = {}
547
548 def gen_color(n=10000):
549 """generator for getting n of evenly distributed colors using
550 hsv color and golden ratio. It always return same order of colors
551
552 :returns: RGB tuple
553 """
554
555 def hsv_to_rgb(h, s, v):
556 if s == 0.0:
557 return v, v, v
558 i = int(h * 6.0) # XXX assume int() truncates!
559 f = (h * 6.0) - i
560 p = v * (1.0 - s)
561 q = v * (1.0 - s * f)
562 t = v * (1.0 - s * (1.0 - f))
563 i = i % 6
564 if i == 0:
565 return v, t, p
566 if i == 1:
567 return q, v, p
568 if i == 2:
569 return p, v, t
570 if i == 3:
571 return p, q, v
572 if i == 4:
573 return t, p, v
574 if i == 5:
575 return v, p, q
576
577 golden_ratio = 0.618033988749895
578 h = 0.22717784590367374
579
580 for _ in xrange(n):
581 h += golden_ratio
582 h %= 1
583 HSV_tuple = [h, 0.95, 0.95]
584 RGB_tuple = hsv_to_rgb(*HSV_tuple)
585 yield map(lambda x: str(int(x * 256)), RGB_tuple)
586
587 cgenerator = gen_color()
588
589 def get_color_string(commit_id):
590 if commit_id in color_dict:
591 col = color_dict[commit_id]
592 else:
593 col = color_dict[commit_id] = cgenerator.next()
594 return "color: rgb(%s)! important;" % (', '.join(col))
595
596 def url_func(repo_name):
597
598 def _url_func(commit):
599 author = commit.author
600 date = commit.date
601 message = tooltip(commit.message)
602
603 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
604 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
605 "</b> %s<br/></div>")
606
607 tooltip_html = tooltip_html % (author, date, message)
608 lnk_format = '%5s:%s' % ('r%s' % commit.idx, commit.short_id)
609 uri = link_to(
610 lnk_format,
611 url('changeset_home', repo_name=repo_name,
612 revision=commit.raw_id),
613 style=get_color_string(commit.raw_id),
614 class_='tooltip',
615 title=tooltip_html
616 )
617
618 uri += '\n'
619 return uri
620 return _url_func
621
622 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
623
624
625 619 def is_following_repo(repo_name, user_id):
626 620 from rhodecode.model.scm import ScmModel
627 621 return ScmModel().is_following_repo(repo_name, user_id)
@@ -45,7 +45,8 b''
45 45 @font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif;
46 46 @font-family-serif: Georgia, "Times New Roman", Times, serif;
47 47 //** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`.
48 @font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
48 @font-family-monospace: Consolas, "Liberation Mono", Menlo, Monaco, Courier, monospace;
49
49 50 @font-family-base: @font-family-sans-serif;
50 51
51 52 @font-size-base: 14px;
@@ -506,20 +506,19 b' div.codeblock {'
506 506
507 507 th,
508 508 td {
509 padding: .5em !important;
510 border: @border-thickness solid @border-default-color !important;
509 padding: .5em;
510 border: @border-thickness solid @border-default-color;
511 511 }
512 512 }
513 513
514 514 table {
515 width: 0 !important;
516 border: 0px !important;
515 border: 0px;
517 516 margin: 0;
518 517 letter-spacing: normal;
519
520
518
519
521 520 td {
522 border: 0px !important;
521 border: 0px;
523 522 vertical-align: top;
524 523 }
525 524 }
@@ -569,7 +568,7 b' div.annotatediv { margin-left: 2px; marg'
569 568 .CodeMirror ::-moz-selection { background: @rchighlightblue; }
570 569
571 570 .code { display: block; border:0px !important; }
572 .code-highlight,
571 .code-highlight, /* TODO: dan: merge codehilite into code-highlight */
573 572 .codehilite {
574 573 .hll { background-color: #ffffcc }
575 574 .c { color: #408080; font-style: italic } /* Comment */
@@ -641,3 +640,110 b' pre.literal-block, .codehilite pre{'
641 640 .border-radius(@border-radius);
642 641 background-color: @grey7;
643 642 }
643
644
645 /* START NEW CODE BLOCK CSS */
646
647 table.cb {
648 width: 100%;
649 border-collapse: collapse;
650 margin-bottom: 10px;
651
652 * {
653 box-sizing: border-box;
654 }
655
656 /* intentionally general selector since .cb-line-selected must override it
657 and they both use !important since the td itself may have a random color
658 generated by annotation blocks. TLDR: if you change it, make sure
659 annotated block selection and line selection in file view still work */
660 .cb-line-fresh .cb-content {
661 background: white !important;
662 }
663
664 tr.cb-annotate {
665 border-top: 1px solid #eee;
666
667 &+ .cb-line {
668 border-top: 1px solid #eee;
669 }
670
671 &:first-child {
672 border-top: none;
673 &+ .cb-line {
674 border-top: none;
675 }
676 }
677 }
678
679 td {
680 vertical-align: top;
681 padding: 2px 10px;
682
683 &.cb-content {
684 white-space: pre-wrap;
685 font-family: @font-family-monospace;
686 font-size: 12.35px;
687
688 span {
689 word-break: break-word;
690 }
691 }
692
693 &.cb-lineno {
694 padding: 0;
695 height: 1px; /* this allows the <a> link to fill to 100% height of the td */
696 width: 50px;
697 color: rgba(0, 0, 0, 0.3);
698 text-align: right;
699 border-right: 1px solid #eee;
700 font-family: @font-family-monospace;
701
702 a::before {
703 content: attr(data-line-no);
704 }
705 &.cb-line-selected {
706 background: @comment-highlight-color !important;
707 }
708
709 a {
710 display: block;
711 height: 100%;
712 color: rgba(0, 0, 0, 0.3);
713 padding: 0 10px; /* vertical padding is 0 so that height: 100% works */
714 line-height: 18px; /* use this instead of vertical padding */
715 }
716 }
717
718 &.cb-content {
719 &.cb-line-selected {
720 background: @comment-highlight-color !important;
721 }
722 }
723
724 &.cb-annotate-info {
725 width: 320px;
726 min-width: 320px;
727 max-width: 320px;
728 padding: 5px 2px;
729 font-size: 13px;
730
731 strong.cb-annotate-message {
732 padding: 5px 0;
733 white-space: pre-line;
734 display: inline-block;
735 }
736 .rc-user {
737 float: none;
738 padding: 0 6px 0 17px;
739 min-width: auto;
740 min-height: auto;
741 }
742 }
743
744 &.cb-annotate-revision {
745 cursor: pointer;
746 text-align: right;
747 }
748 }
749 }
@@ -266,7 +266,35 b' function offsetScroll(element, offset){'
266 266 }
267 267 });
268 268
269 $('.compare_view_files').on(
269 $('body').on( /* TODO: replace the $('.compare_view_files').on('click') below
270 when new diffs are integrated */
271 'click', '.cb-lineno a', function(event) {
272
273 if ($(this).attr('data-line-no') !== ""){
274 $('.cb-line-selected').removeClass('cb-line-selected');
275 var td = $(this).parent();
276 td.addClass('cb-line-selected'); // line number td
277 td.next().addClass('cb-line-selected'); // line content td
278
279 // Replace URL without jumping to it if browser supports.
280 // Default otherwise
281 if (history.pushState) {
282 var new_location = location.href.rstrip('#');
283 if (location.hash) {
284 new_location = new_location.replace(location.hash, "");
285 }
286
287 // Make new anchor url
288 new_location = new_location + $(this).attr('href');
289 history.pushState(true, document.title, new_location);
290
291 return false;
292 }
293 }
294 });
295
296 $('.compare_view_files').on( /* TODO: replace this with .cb function above
297 when new diffs are integrated */
270 298 'click', 'tr.line .lineno a',function(event) {
271 299 if ($(this).text() != ""){
272 300 $('tr.line').removeClass('selected');
@@ -365,10 +393,11 b' function offsetScroll(element, offset){'
365 393 // Select the line that comes from the url anchor
366 394 // At the time of development, Chrome didn't seem to support jquery's :target
367 395 // element, so I had to scroll manually
368 if (location.hash) {
396
397 if (location.hash) { /* TODO: dan: remove this and replace with code block
398 below when new diffs are ready */
369 399 var result = splitDelimitedHash(location.hash);
370 400 var loc = result.loc;
371 var remainder = result.remainder;
372 401 if (loc.length > 1){
373 402 var lineno = $(loc+'.lineno');
374 403 if (lineno.length > 0){
@@ -378,11 +407,70 b' function offsetScroll(element, offset){'
378 407 tr[0].scrollIntoView();
379 408
380 409 $.Topic('/ui/plugins/code/anchor_focus').prepareOrPublish({
381 tr:tr,
382 remainder:remainder});
410 tr: tr,
411 remainder: result.remainder});
383 412 }
384 413 }
385 414 }
386 415
416 if (location.hash) { /* TODO: dan: use this to replace the code block above
417 when new diffs are ready */
418 var result = splitDelimitedHash(location.hash);
419 var loc = result.loc;
420 if (loc.length > 1) {
421 var page_highlights = loc.substring(
422 loc.indexOf('#') + 1).split('L');
423
424 if (page_highlights.length > 1) {
425 var highlight_ranges = page_highlights[1].split(",");
426 var h_lines = [];
427 for (var pos in highlight_ranges) {
428 var _range = highlight_ranges[pos].split('-');
429 if (_range.length === 2) {
430 var start = parseInt(_range[0]);
431 var end = parseInt(_range[1]);
432 if (start < end) {
433 for (var i = start; i <= end; i++) {
434 h_lines.push(i);
435 }
436 }
437 }
438 else {
439 h_lines.push(parseInt(highlight_ranges[pos]));
440 }
441 }
442 for (pos in h_lines) {
443 var line_td = $('td.cb-lineno#L' + h_lines[pos]);
444 if (line_td.length) {
445 line_td.addClass('cb-line-selected'); // line number td
446 line_td.next().addClass('cb-line-selected'); // line content
447 }
448 }
449 var first_line_td = $('td.cb-lineno#L' + h_lines[0]);
450 if (first_line_td.length) {
451 var elOffset = first_line_td.offset().top;
452 var elHeight = first_line_td.height();
453 var windowHeight = $(window).height();
454 var offset;
455
456 if (elHeight < windowHeight) {
457 offset = elOffset - ((windowHeight / 4) - (elHeight / 2));
458 }
459 else {
460 offset = elOffset;
461 }
462 $(function() { // let browser scroll to hash first, then
463 // scroll the line to the middle of page
464 setTimeout(function() {
465 $('html, body').animate({ scrollTop: offset });
466 }, 100);
467 });
468 $.Topic('/ui/plugins/code/anchor_focus').prepareOrPublish({
469 lineno: first_line_td,
470 remainder: result.remainder});
471 }
472 }
473 }
474 }
387 475 collapsableContent();
388 476 });
@@ -147,43 +147,6 b''
147 147 if (source_page) {
148 148 // variants for with source code, not tree view
149 149
150 if (location.href.indexOf('#') != -1) {
151 page_highlights = location.href.substring(location.href.indexOf('#') + 1).split('L');
152 if (page_highlights.length == 2) {
153 highlight_ranges = page_highlights[1].split(",");
154
155 var h_lines = [];
156 for (pos in highlight_ranges) {
157 var _range = highlight_ranges[pos].split('-');
158 if (_range.length == 2) {
159 var start = parseInt(_range[0]);
160 var end = parseInt(_range[1]);
161 if (start < end) {
162 for (var i = start; i <= end; i++) {
163 h_lines.push(i);
164 }
165 }
166 }
167 else {
168 h_lines.push(parseInt(highlight_ranges[pos]));
169 }
170 }
171
172 for (pos in h_lines) {
173 // @comment-highlight-color
174 $('#L' + h_lines[pos]).css('background-color', '#ffd887');
175 }
176
177 var _first_line = $('#L' + h_lines[0]).get(0);
178 if (_first_line) {
179 var line = $('#L' + h_lines[0]);
180 if (line.length > 0){
181 offsetScroll(line, 70);
182 }
183 }
184 }
185 }
186
187 150 // select code link event
188 151 $("#hlcode").mouseup(getSelectionLink);
189 152
@@ -1,3 +1,4 b''
1 <%namespace name="sourceblock" file="/codeblocks/source.html"/>
1 2
2 3 <div id="codeblock" class="codeblock">
3 4 <div class="codeblock-header">
@@ -51,12 +52,22 b''
51 52 </div>
52 53 %else:
53 54 % if c.file.size < c.cut_off_limit:
54 %if c.annotate:
55 ${h.pygmentize_annotation(c.repo_name,c.file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
56 %elif c.renderer:
55 %if c.renderer and not c.annotate:
57 56 ${h.render(c.file.content, renderer=c.renderer)}
58 57 %else:
59 ${h.pygmentize(c.file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
58 <table class="cb codehilite">
59 %if c.annotate:
60 <% color_hasher = h.color_hasher() %>
61 %for annotation, lines in c.annotated_lines:
62 ${sourceblock.render_annotation_lines(annotation, lines, color_hasher)}
63 %endfor
64 %else:
65 %for line_num, tokens in c.lines:
66 ${sourceblock.render_line(line_num, tokens)}
67 %endfor
68 %endif
69 </table>
70 </div>
60 71 %endif
61 72 %else:
62 73 ${_('File is too big to display')} ${h.link_to(_('Show as raw'),
@@ -81,7 +81,7 b' def _commit_change('
81 81 return commit
82 82
83 83
84
84
85 85 @pytest.mark.usefixtures("app")
86 86 class TestFilesController:
87 87
@@ -270,9 +270,9 b' class TestFilesController:'
270 270 annotate=True))
271 271
272 272 expected_revisions = {
273 'hg': 'r356:25213a5fbb04',
274 'git': 'r345:c994f0de03b2',
275 'svn': 'r208:209',
273 'hg': 'r356',
274 'git': 'r345',
275 'svn': 'r208',
276 276 }
277 277 response.mustcontain(expected_revisions[backend.alias])
278 278
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now