Ticket #2764: mimeview-annotate-marked-lines.diff
| File mimeview-annotate-marked-lines.diff, 8.4 KB (added by cboos, 6 years ago) |
|---|
-
htdocs/css/code.css
55 55 padding: 1px 2px; 56 56 vertical-align: top; 57 57 } 58 58 table.code tbody tr.hilite th { 59 background: #ccf; 60 } 61 table.code tbody tr.hilite td { 62 background: #ddf; 63 } 59 64 .image-file { background: #eee; padding: .3em } 60 65 .image-file img { background: url(../imggrid.png) } 61 66 -
trac/mimeview/api.py
24 24 from StringIO import StringIO 25 25 26 26 from trac.core import * 27 from trac.util import escape, to_utf8, Markup 27 from trac.util import escape, to_utf8, Markup, Ranges 28 28 29 29 30 30 __all__ = ['get_mimetype', 'is_binary', 'detect_unicode', 'Mimeview'] … … 244 244 elif isinstance(result, (str, unicode)): 245 245 return Markup(result) 246 246 elif annotations: 247 return Markup(self._annotate(result, annotations)) 247 m = req.args.get('mark') 248 return Markup(self._annotate(result, annotations, m and Ranges(m))) 248 249 else: 249 250 buf = StringIO() 250 251 buf.write('<div class="code"><pre>') … … 256 257 self.log.warning('HTML preview using %s failed (%s)' 257 258 % (renderer, e), exc_info=True) 258 259 259 def _annotate(self, lines, annotations ):260 def _annotate(self, lines, annotations, marks=None): 260 261 buf = StringIO() 261 262 buf.write('<table class="code"><thead><tr>') 262 263 annotators = [] … … 283 284 for annotator in annotators: 284 285 cells.append(annotator.annotate_line(num + 1, line)) 285 286 cells.append('<td>%s</td>\n' % space_re.sub(htmlify, line)) 286 buf.write('<tr>' + '\n'.join(cells) + '</tr>') 287 if marks and num+1 in marks: 288 buf.write('<tr class="%s">' % ('hilite',) + '\n'.join(cells) + '</tr>') 289 else: 290 buf.write('<tr>' + '\n'.join(cells) + '</tr>') 287 291 else: 288 292 if num < 0: 289 293 return '' -
trac/versioncontrol/web_ui/util.py
23 23 TracError, Markup, rss_title 24 24 from trac.wiki import wiki_to_html, wiki_to_oneliner 25 25 26 __all__ = ['get_changes', 'get_path_links', 'get_path_rev_ line',26 __all__ = ['get_changes', 'get_path_links', 'get_path_rev_marks', 27 27 'get_existing_node'] 28 28 29 29 def get_changes(env, repos, revs, full=None, req=None, format=None): … … 71 71 }) 72 72 return links 73 73 74 rev_re = re.compile(r"([^@#]*)[@#]([^#]+)(?:#L( \d+))?")74 rev_re = re.compile(r"([^@#]*)[@#]([^#]+)(?:#L((?:\d[,-]?)+))?") 75 75 76 def get_path_rev_ line(path):76 def get_path_rev_marks(path): 77 77 rev = None 78 line= None78 marks = None 79 79 match = rev_re.search(path) 80 80 if match: 81 81 path = match.group(1) 82 82 rev = match.group(2) 83 83 if match.group(3): 84 line = int(match.group(3))84 marks = match.group(3) 85 85 path = urllib.unquote(path) 86 return path, rev, line86 return path, rev, marks 87 87 88 88 def get_existing_node(env, repos, path, rev): 89 89 try: -
trac/versioncontrol/web_ui/log.py
210 210 yield ('log', self._format_link) 211 211 212 212 def _format_link(self, formatter, ns, path, label): 213 path, rev, line = get_path_rev_line(path)213 path, rev, marks = get_path_rev_marks(path) 214 214 stop_rev = None 215 215 if rev and ':' in rev: 216 216 stop_rev, rev = rev.split(':', 1) -
trac/versioncontrol/web_ui/browser.py
244 244 match = IMG_RE.search(path) 245 245 if formatter.flavor != 'oneliner' and match: 246 246 return '<img src="%s" alt="%s" />' % \ 247 (formatter.href.file(path, format='raw'), label) 248 path, rev, line = get_path_rev_line(path) 249 if line is not None: 250 anchor = '#L%d' % line 247 (formatter.href.browser(path, format='raw'), label) 248 path, rev, marks = get_path_rev_marks(path) 249 if marks is not None: 250 line = re.match(r'\d+', marks).group(0) 251 anchor = '#L%s' % line 251 252 else: 252 253 anchor = '' 253 254 label = urllib.unquote(label) 254 255 return '<a class="source" href="%s%s">%s</a>' \ 255 % (util.escape(formatter.href.browser(path, rev=rev)), anchor, 256 label) 256 % (util.escape(formatter.href.browser(path, rev=rev, 257 mark=marks)), 258 anchor, label) -
trac/util.py
603 603 rearranged += itoa64[v & 0x3f]; v >>= 6 604 604 605 605 return magic + salt + '$' + rearranged 606 607 608 class Ranges(object): 609 """ 610 Holds information about ranges parsed from a string 611 612 >>> x = Ranges("1,2,9-15") 613 >>> 1 in x 614 True 615 >>> 5 in x 616 False 617 >>> 10 in x 618 True 619 >>> 16 in x 620 False 621 >>> [i for i in range(20) if i in x] 622 [1, 2, 9, 10, 11, 12, 13, 14, 15] 623 624 Also supports iteration, which makes that last example a bit simpler: 625 626 >>> list(x) 627 [1, 2, 9, 10, 11, 12, 13, 14, 15] 628 629 Note that it automatically reduces the list and short-circuits when the 630 desired ranges are a relatively small portion of the entire set: 631 632 >>> x = Ranges("99") 633 >>> 1 in x #really fast 634 False 635 >>> x = Ranges("1, 2, 1-2, 2") #reduces this to 1-2 636 >>> x.pairs 637 [(1, 2)] 638 >>> x = Ranges("1-9,2-4") #handle ranges that completely overlap 639 >>> list(x) 640 [1, 2, 3, 4, 5, 6, 7, 8, 9] 641 642 Empty ranges are ok, and ranges can be constructed in pieces, if you 643 so choose: 644 645 >>> x = Ranges() 646 >>> x.appendrange("1, 2, 3") 647 >>> x.appendrange("5-9") 648 >>> x.appendrange("2-3") #reduce'd away 649 >>> list(x) 650 [1, 2, 3, 5, 6, 7, 8, 9] 651 652 """ 653 def __init__(self, r=None): 654 self.pairs = [] 655 self.appendrange(r) 656 657 def appendrange(self, r): 658 """ 659 Add a range (from a string or None) to the current one 660 """ 661 if not r: return 662 p = self.pairs 663 for x in r.split(","): 664 try: 665 a, b = map(int, x.split("-", 1)) 666 except ValueError: 667 a, b = int(x), int(x) 668 p.append((a, b)) 669 self._reduce() 670 671 def _reduce(self): 672 """ 673 Come up with the minimal representation of the ranges 674 """ 675 d = [] #list of indices to delete 676 p = self.pairs 677 p.sort() 678 for i in range(len(p) - 1): 679 if p[i+1][0] <= p[i][1]: #this item overlaps with the next 680 #make the first one include the second 681 p[i] = (p[i][0], max(p[i][1], p[i+1][1])) 682 d.append(i+1) #delete the second on a later pass 683 d.reverse() 684 for i in d: 685 del p[i] 686 self.a = p[0][0] #min value 687 self.b = p[-1][1] #max value 688 689 def __iter__(self): 690 """ 691 This is another way I came up with to do it. Is it faster? 692 693 from itertools import chain 694 return chain(*[xrange(a, b+1) for a, b in self.pairs]) 695 """ 696 for a, b in self.pairs: 697 for i in range(a, b+1): 698 yield i 699 700 def __contains__(self, x): 701 if self.a <= x <= self.b: #short-circuit if outside the possible range 702 for a, b in self.pairs: 703 if a <= x <= b: 704 return True 705 if b > x: #short-circuit if we've gone too far 706 break 707 return False 708 709 if __name__ == "__main__": 710 from doctest import testmod 711 testmod() 712
