Edgewall Software

Ticket #2764: mimeview-annotate-marked-lines.diff

File mimeview-annotate-marked-lines.diff, 8.4 KB (added by cboos, 6 years ago)

Previous patch extended with support for source: #line source support

  • htdocs/css/code.css

     
    5555 padding: 1px 2px; 
    5656 vertical-align: top; 
    5757} 
    58  
     58table.code tbody tr.hilite th { 
     59 background: #ccf; 
     60} 
     61table.code tbody tr.hilite td { 
     62 background: #ddf; 
     63} 
    5964.image-file { background: #eee; padding: .3em } 
    6065.image-file img { background: url(../imggrid.png) } 
    6166 
  • trac/mimeview/api.py

     
    2424    from StringIO import StringIO 
    2525 
    2626from trac.core import * 
    27 from trac.util import escape, to_utf8, Markup 
     27from trac.util import escape, to_utf8, Markup, Ranges 
    2828 
    2929 
    3030__all__ = ['get_mimetype', 'is_binary', 'detect_unicode', 'Mimeview'] 
     
    244244                elif isinstance(result, (str, unicode)): 
    245245                    return Markup(result) 
    246246                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))) 
    248249                else: 
    249250                    buf = StringIO() 
    250251                    buf.write('<div class="code"><pre>') 
     
    256257                self.log.warning('HTML preview using %s failed (%s)' 
    257258                                 % (renderer, e), exc_info=True) 
    258259 
    259     def _annotate(self, lines, annotations): 
     260    def _annotate(self, lines, annotations, marks=None): 
    260261        buf = StringIO() 
    261262        buf.write('<table class="code"><thead><tr>') 
    262263        annotators = [] 
     
    283284            for annotator in annotators: 
    284285                cells.append(annotator.annotate_line(num + 1, line)) 
    285286            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>') 
    287291        else: 
    288292            if num < 0: 
    289293                return '' 
  • trac/versioncontrol/web_ui/util.py

     
    2323                      TracError, Markup, rss_title 
    2424from trac.wiki import wiki_to_html, wiki_to_oneliner 
    2525 
    26 __all__ = ['get_changes', 'get_path_links', 'get_path_rev_line', 
     26__all__ = ['get_changes', 'get_path_links', 'get_path_rev_marks', 
    2727           'get_existing_node'] 
    2828 
    2929def get_changes(env, repos, revs, full=None, req=None, format=None): 
     
    7171        }) 
    7272    return links 
    7373 
    74 rev_re = re.compile(r"([^@#]*)[@#]([^#]+)(?:#L(\d+))?") 
     74rev_re = re.compile(r"([^@#]*)[@#]([^#]+)(?:#L((?:\d[,-]?)+))?") 
    7575 
    76 def get_path_rev_line(path): 
     76def get_path_rev_marks(path): 
    7777    rev = None 
    78     line = None 
     78    marks = None 
    7979    match = rev_re.search(path) 
    8080    if match: 
    8181        path = match.group(1) 
    8282        rev = match.group(2) 
    8383        if match.group(3): 
    84             line = int(match.group(3)) 
     84            marks = match.group(3) 
    8585    path = urllib.unquote(path) 
    86     return path, rev, line 
     86    return path, rev, marks 
    8787 
    8888def get_existing_node(env, repos, path, rev): 
    8989    try:  
  • trac/versioncontrol/web_ui/log.py

     
    210210        yield ('log', self._format_link) 
    211211 
    212212    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) 
    214214        stop_rev = None 
    215215        if rev and ':' in rev: 
    216216            stop_rev, rev = rev.split(':', 1) 
  • trac/versioncontrol/web_ui/browser.py

     
    244244        match = IMG_RE.search(path) 
    245245        if formatter.flavor != 'oneliner' and match: 
    246246            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 
    251252        else: 
    252253            anchor = '' 
    253254        label = urllib.unquote(label) 
    254255        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

     
    603603        rearranged += itoa64[v & 0x3f]; v >>= 6 
    604604 
    605605    return magic + salt + '$' + rearranged 
     606 
     607 
     608class 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 
     709if __name__ == "__main__": 
     710    from doctest import testmod 
     711    testmod() 
     712