src/cm/ext/diff.py
author raph
Mon, 23 Nov 2009 15:14:29 +0100
changeset 0 40c8f766c9b8
permissions -rw-r--r--
import from internal svn r 4007
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
0
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
     1
# -*- coding: utf-8 -*-
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
     2
#
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
     3
# Copyright (C) 2004-2009 Edgewall Software
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
     4
# Copyright (C) 2004-2006 Christopher Lenz <cmlenz@gmx.de>
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
     5
# All rights reserved.
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
     6
#
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
     7
# This software is licensed as described in the file COPYING, which
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
     8
# you should have received as part of this distribution. The terms
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
     9
# are also available at http://trac.edgewall.org/wiki/TracLicense.
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    10
#
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    11
# This software consists of voluntary contributions made by many
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    12
# individuals. For the exact contribution history, see the revision
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    13
# history and logs, available at http://trac.edgewall.org/log/.
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    14
#
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    15
# Author: Christopher Lenz <cmlenz@gmx.de>
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    16
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    17
from trac.util.html import escape, Markup
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    18
from trac.util.text import expandtabs
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    19
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    20
from difflib import SequenceMatcher
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    21
import re
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    22
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    23
__all__ = ['get_diff_options', 'hdf_diff', 'diff_blocks', 'unified_diff']
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    24
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    25
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    26
def _get_change_extent(str1, str2):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    27
    """
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    28
    Determines the extent of differences between two strings. Returns a tuple
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    29
    containing the offset at which the changes start, and the negative offset
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    30
    at which the changes end. If the two strings have neither a common prefix
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    31
    nor a common suffix, (0, 0) is returned.
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    32
    """
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    33
    start = 0
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    34
    limit = min(len(str1), len(str2))
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    35
    while start < limit and str1[start] == str2[start]:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    36
        start += 1
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    37
    end = -1
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    38
    limit = limit - start
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    39
    while -end <= limit and str1[end] == str2[end]:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    40
        end -= 1
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    41
    return (start, end + 1)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    42
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    43
def _get_opcodes(fromlines, tolines, ignore_blank_lines=False,
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    44
                 ignore_case=False, ignore_space_changes=False):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    45
    """
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    46
    Generator built on top of SequenceMatcher.get_opcodes().
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    47
    
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    48
    This function detects line changes that should be ignored and emits them
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    49
    as tagged as 'equal', possibly joined with the preceding and/or following
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    50
    'equal' block.
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    51
    """
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    52
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    53
    def is_ignorable(tag, fromlines, tolines):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    54
        if tag == 'delete' and ignore_blank_lines:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    55
            if ''.join(fromlines) == '':
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    56
                return True
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    57
        elif tag == 'insert' and ignore_blank_lines:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    58
            if ''.join(tolines) == '':
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    59
                return True
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    60
        elif tag == 'replace' and (ignore_case or ignore_space_changes):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    61
            if len(fromlines) != len(tolines):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    62
                return False
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    63
            def f(str):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    64
                if ignore_case:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    65
                    str = str.lower()
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    66
                if ignore_space_changes:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    67
                    str = ' '.join(str.split())
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    68
                return str
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    69
            for i in range(len(fromlines)):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    70
                if f(fromlines[i]) != f(tolines[i]):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    71
                    return False
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    72
            return True
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    73
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    74
    matcher = SequenceMatcher(None, fromlines, tolines)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    75
    previous = None
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    76
    for tag, i1, i2, j1, j2 in matcher.get_opcodes():
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    77
        if tag == 'equal':
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    78
            if previous:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    79
                previous = (tag, previous[1], i2, previous[3], j2)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    80
            else:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    81
                previous = (tag, i1, i2, j1, j2)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    82
        else:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    83
            if is_ignorable(tag, fromlines[i1:i2], tolines[j1:j2]):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    84
                if previous:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    85
                    previous = 'equal', previous[1], i2, previous[3], j2
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    86
                else:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    87
                    previous = 'equal', i1, i2, j1, j2
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    88
                continue
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    89
            if previous:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    90
                yield previous
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    91
            yield tag, i1, i2, j1, j2
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    92
            previous = None
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    93
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    94
    if previous:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    95
        yield previous
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    96
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    97
def _group_opcodes(opcodes, n=3):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    98
    """
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
    99
    Python 2.2 doesn't have SequenceMatcher.get_grouped_opcodes(), so let's
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   100
    provide equivalent here. The opcodes parameter can be any iterable or
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   101
    sequence.
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   102
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   103
    This function can also be used to generate full-context diffs by passing 
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   104
    None for the parameter n.
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   105
    """
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   106
    # Full context produces all the opcodes
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   107
    if n is None:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   108
        yield list(opcodes)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   109
        return
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   110
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   111
    # Otherwise we leave at most n lines with the tag 'equal' before and after
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   112
    # every change
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   113
    nn = n + n
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   114
    group = []
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   115
    for idx, (tag, i1, i2, j1, j2) in enumerate(opcodes):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   116
        if idx == 0 and tag == 'equal': # Fixup leading unchanged block
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   117
            i1, j1 = max(i1, i2 - n), max(j1, j2 - n)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   118
        elif tag == 'equal' and i2 - i1 > nn:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   119
            group.append((tag, i1, min(i2, i1 + n), j1, min(j2, j1 + n)))
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   120
            yield group
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   121
            group = []
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   122
            i1, j1 = max(i1, i2 - n), max(j1, j2 - n)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   123
        group.append((tag, i1, i2, j1 ,j2))
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   124
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   125
    if group and not (len(group) == 1 and group[0][0] == 'equal'):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   126
        if group[-1][0] == 'equal': # Fixup trailing unchanged block
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   127
            tag, i1, i2, j1, j2 = group[-1]
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   128
            group[-1] = tag, i1, min(i2, i1 + n), j1, min(j2, j1 + n)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   129
        yield group
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   130
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   131
def hdf_diff(*args, **kwargs):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   132
    return diff_blocks(*args, **kwargs)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   133
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   134
def diff_blocks(fromlines, tolines, context=None, tabwidth=8,
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   135
                ignore_blank_lines=0, ignore_case=0, ignore_space_changes=0):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   136
    """Return an array that is adequate for adding to the data dictionary
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   137
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   138
    See the diff_div.html template.
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   139
    """
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   140
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   141
    type_map = {'replace': 'mod', 'delete': 'rem', 'insert': 'add',
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   142
                'equal': 'unmod'}
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   143
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   144
    space_re = re.compile(' ( +)|^ ')
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   145
    def htmlify(match):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   146
        div, mod = divmod(len(match.group(0)), 2)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   147
        return div * '&nbsp; ' + mod * '&nbsp;'
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   148
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   149
    def markup_intraline_changes(opcodes):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   150
        for tag, i1, i2, j1, j2 in opcodes:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   151
            if tag == 'replace' and i2 - i1 == j2 - j1:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   152
                for i in range(i2 - i1):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   153
                    fromline, toline = fromlines[i1 + i], tolines[j1 + i]
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   154
                    (start, end) = _get_change_extent(fromline, toline)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   155
                    if start != 0 or end != 0:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   156
                        last = end+len(fromline)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   157
                        fromlines[i1+i] = fromline[:start] + '\0' + fromline[start:last] + \
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   158
                                       '\1' + fromline[last:]
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   159
                        last = end+len(toline)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   160
                        tolines[j1+i] = toline[:start] + '\0' + toline[start:last] + \
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   161
                                     '\1' + toline[last:]
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   162
            yield tag, i1, i2, j1, j2
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   163
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   164
    changes = []
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   165
    opcodes = _get_opcodes(fromlines, tolines, ignore_blank_lines, ignore_case,
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   166
                           ignore_space_changes)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   167
    for group in _group_opcodes(opcodes, context):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   168
        blocks = []
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   169
        last_tag = None
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   170
        for tag, i1, i2, j1, j2 in markup_intraline_changes(group):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   171
            if tag != last_tag:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   172
                blocks.append({'type': type_map[tag],
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   173
                               'base': {'offset': i1, 'lines': []},
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   174
                               'changed': {'offset': j1, 'lines': []}})
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   175
            if tag == 'equal':
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   176
                for line in fromlines[i1:i2]:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   177
                    line = line.expandtabs(tabwidth)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   178
                    line = space_re.sub(htmlify, escape(line, quotes=False))
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   179
                    blocks[-1]['base']['lines'].append(Markup(unicode(line)))
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   180
                for line in tolines[j1:j2]:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   181
                    line = line.expandtabs(tabwidth)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   182
                    line = space_re.sub(htmlify, escape(line, quotes=False))
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   183
                    blocks[-1]['changed']['lines'].append(Markup(unicode(line)))
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   184
            else:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   185
                if tag in ('replace', 'delete'):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   186
                    for line in fromlines[i1:i2]:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   187
                        line = expandtabs(line, tabwidth, '\0\1')
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   188
                        line = escape(line, quotes=False)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   189
                        line = '<del>'.join([space_re.sub(htmlify, seg)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   190
                                             for seg in line.split('\0')])
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   191
                        line = line.replace('\1', '</del>')
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   192
                        blocks[-1]['base']['lines'].append(
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   193
                            Markup(unicode(line)))
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   194
                if tag in ('replace', 'insert'):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   195
                    for line in tolines[j1:j2]:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   196
                        line = expandtabs(line, tabwidth, '\0\1')
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   197
                        line = escape(line, quotes=False)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   198
                        line = '<ins>'.join([space_re.sub(htmlify, seg)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   199
                                             for seg in line.split('\0')])
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   200
                        line = line.replace('\1', '</ins>')
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   201
                        blocks[-1]['changed']['lines'].append(
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   202
                            Markup(unicode(line)))
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   203
        changes.append(blocks)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   204
    return changes
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   205
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   206
def unified_diff(fromlines, tolines, context=None, ignore_blank_lines=0,
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   207
                 ignore_case=0, ignore_space_changes=0):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   208
    opcodes = _get_opcodes(fromlines, tolines, ignore_blank_lines, ignore_case,
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   209
                           ignore_space_changes)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   210
    for group in _group_opcodes(opcodes, context):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   211
        i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   212
        if i1 == 0 and i2 == 0:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   213
            i1, i2 = -1, -1 # support for 'A'dd changes
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   214
        yield '@@ -%d,%d +%d,%d @@' % (i1 + 1, i2 - i1, j1 + 1, j2 - j1)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   215
        for tag, i1, i2, j1, j2 in group:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   216
            if tag == 'equal':
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   217
                for line in fromlines[i1:i2]:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   218
                    yield ' ' + line
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   219
            else:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   220
                if tag in ('replace', 'delete'):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   221
                    for line in fromlines[i1:i2]:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   222
                        yield '-' + line
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   223
                if tag in ('replace', 'insert'):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   224
                    for line in tolines[j1:j2]:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   225
                        yield '+' + line
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   226
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   227
def get_diff_options(req):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   228
    options_data = {}
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   229
    data = {'options': options_data}
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   230
    
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   231
    def get_bool_option(name, default=0):
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   232
        pref = int(req.session.get('diff_' + name, default))
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   233
        arg = int(req.args.has_key(name))
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   234
        if req.args.has_key('update') and arg != pref:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   235
            req.session['diff_' + name] = arg
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   236
        else:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   237
            arg = pref
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   238
        return arg
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   239
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   240
    pref = req.session.get('diff_style', 'inline')
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   241
    style = req.args.get('style', pref)
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   242
    if req.args.has_key('update') and style != pref:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   243
        req.session['diff_style'] = style
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   244
    data['style'] = style
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   245
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   246
    pref = int(req.session.get('diff_contextlines', 2))
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   247
    try:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   248
        arg = int(req.args.get('contextlines', pref))
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   249
    except ValueError:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   250
        arg = -1
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   251
    if req.args.has_key('update') and arg != pref:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   252
        req.session['diff_contextlines'] = arg
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   253
    options = ['-U%d' % arg]
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   254
    options_data['contextlines'] = arg
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   255
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   256
    arg = get_bool_option('ignoreblanklines')
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   257
    if arg:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   258
        options.append('-B')
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   259
    options_data['ignoreblanklines'] = arg
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   260
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   261
    arg = get_bool_option('ignorecase')
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   262
    if arg:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   263
        options.append('-i')
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   264
    options_data['ignorecase'] = arg
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   265
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   266
    arg = get_bool_option('ignorewhitespace')
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   267
    if arg:
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   268
        options.append('-b')
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   269
    options_data['ignorewhitespace'] = arg
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   270
40c8f766c9b8 import from internal svn r 4007
raph
parents:
diff changeset
   271
    return (style, options, data)