eric6/UI/DiffDialog.py

changeset 7770
49f3377aebf1
parent 7639
422fd05e9c91
child 7779
757334671130
equal deleted inserted replaced
7769:c9f64088224b 7770:49f3377aebf1
23 from .DiffHighlighter import DiffHighlighter 23 from .DiffHighlighter import DiffHighlighter
24 24
25 import Utilities 25 import Utilities
26 import Preferences 26 import Preferences
27 27
28 from difflib import SequenceMatcher 28 from difflib import unified_diff, context_diff
29
30
31 def unified_diff(a, b, fromfile='', tofile='', fromfiledate='',
32 tofiledate='', n=3, lineterm='\n'):
33 """
34 Compare two sequences of lines; generate the delta as a unified diff.
35
36 Unified diffs are a compact way of showing line changes and a few
37 lines of context. The number of context lines is set by 'n' which
38 defaults to three.
39
40 By default, the diff control lines (those with ---, +++, or @@) are
41 created with a trailing newline. This is helpful so that inputs
42 created from file.readlines() result in diffs that are suitable for
43 file.writelines() since both the inputs and outputs have trailing
44 newlines.
45
46 For inputs that do not have trailing newlines, set the lineterm
47 argument to "" so that the output will be uniformly newline free.
48
49 The unidiff format normally has a header for filenames and modification
50 times. Any or all of these may be specified using strings for
51 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'. The modification
52 times are normally expressed in the format returned by time.ctime().
53
54 Example:
55
56 <pre>
57 &gt;&gt;&gt; for line in unified_diff('one two three four'.split(),
58 ... 'zero one tree four'.split(), 'Original', 'Current',
59 ... 'Sat Jan 26 23:30:50 1991', 'Fri Jun 06 10:20:52 2003',
60 ... lineterm=''):
61 ... print line
62 --- Original Sat Jan 26 23:30:50 1991
63 +++ Current Fri Jun 06 10:20:52 2003
64 @@ -1,4 +1,4 @@
65 +zero
66 one
67 -two
68 -three
69 +tree
70 four
71 </pre>
72
73 @param a first sequence of lines (list of strings)
74 @param b second sequence of lines (list of strings)
75 @param fromfile filename of the first file (string)
76 @param tofile filename of the second file (string)
77 @param fromfiledate modification time of the first file (string)
78 @param tofiledate modification time of the second file (string)
79 @param n number of lines of context (integer)
80 @param lineterm line termination string (string)
81 @return a generator yielding lines of differences
82 """
83 started = False
84 for group in SequenceMatcher(None, a, b).get_grouped_opcodes(n):
85 if not started:
86 yield '--- {0}\t{1}{2}'.format(fromfile, fromfiledate, lineterm)
87 yield '+++ {0}\t{1}{2}'.format(tofile, tofiledate, lineterm)
88 started = True
89 i1 = group[0][1]
90 i2 = group[-1][2]
91 j1 = group[0][3]
92 j2 = group[-1][4]
93 yield "@@ -{0:d},{1:d} +{2:d},{3:d} @@{4}".format(
94 i1 + 1, i2 - i1, j1 + 1, j2 - j1, lineterm)
95 for tag, i1, i2, j1, j2 in group:
96 if tag == 'equal':
97 for line in a[i1:i2]:
98 yield ' ' + line
99 continue
100 if tag == 'replace' or tag == 'delete':
101 for line in a[i1:i2]:
102 yield '-' + line
103 if tag == 'replace' or tag == 'insert':
104 for line in b[j1:j2]:
105 yield '+' + line
106
107
108 def context_diff(a, b, fromfile='', tofile='',
109 fromfiledate='', tofiledate='', n=3, lineterm='\n'):
110 r"""
111 Compare two sequences of lines; generate the delta as a context diff.
112
113 Context diffs are a compact way of showing line changes and a few
114 lines of context. The number of context lines is set by 'n' which
115 defaults to three.
116
117 By default, the diff control lines (those with *** or ---) are
118 created with a trailing newline. This is helpful so that inputs
119 created from file.readlines() result in diffs that are suitable for
120 file.writelines() since both the inputs and outputs have trailing
121 newlines.
122
123 For inputs that do not have trailing newlines, set the lineterm
124 argument to "" so that the output will be uniformly newline free.
125
126 The context diff format normally has a header for filenames and
127 modification times. Any or all of these may be specified using
128 strings for 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
129 The modification times are normally expressed in the format returned
130 by time.ctime(). If not specified, the strings default to blanks.
131
132 Example:
133
134 <pre>
135 &gt;&gt;&gt; print ''.join(
136 ... context_diff('one\ntwo\nthree\nfour\n'.splitlines(1),
137 ... 'zero\none\ntree\nfour\n'.splitlines(1), 'Original', 'Current',
138 ... 'Sat Jan 26 23:30:50 1991', 'Fri Jun 06 10:22:46 2003')),
139 *** Original Sat Jan 26 23:30:50 1991
140 --- Current Fri Jun 06 10:22:46 2003
141 ***************
142 *** 1,4 ****
143 one
144 ! two
145 ! three
146 four
147 --- 1,4 ----
148 + zero
149 one
150 ! tree
151 four
152 </pre>
153
154 @param a first sequence of lines (list of strings)
155 @param b second sequence of lines (list of strings)
156 @param fromfile filename of the first file (string)
157 @param tofile filename of the second file (string)
158 @param fromfiledate modification time of the first file (string)
159 @param tofiledate modification time of the second file (string)
160 @param n number of lines of context (integer)
161 @param lineterm line termination string (string)
162 @return a generator yielding lines of differences
163 """
164 started = False
165 prefixmap = {'insert': '+ ', 'delete': '- ', 'replace': '! ',
166 'equal': ' '}
167 for group in SequenceMatcher(None, a, b).get_grouped_opcodes(n):
168 if not started:
169 yield '*** {0}\t{1}{2}'.format(fromfile, fromfiledate, lineterm)
170 yield '--- {0}\t{1}{2}'.format(tofile, tofiledate, lineterm)
171 started = True
172
173 yield '***************{0}'.format(lineterm)
174 if group[-1][2] - group[0][1] >= 2:
175 yield '*** {0:d},{1:d} ****{2}'.format(
176 group[0][1] + 1, group[-1][2], lineterm)
177 else:
178 yield '*** {0:d} ****{1}'.format(group[-1][2], lineterm)
179 visiblechanges = [e for e in group if e[0] in ('replace', 'delete')]
180 if visiblechanges:
181 for tag, i1, i2, _, _ in group:
182 if tag != 'insert':
183 for line in a[i1:i2]:
184 yield prefixmap[tag] + line
185
186 if group[-1][4] - group[0][3] >= 2:
187 yield '--- {0:d},{1:d} ----{2}'.format(
188 group[0][3] + 1, group[-1][4], lineterm)
189 else:
190 yield '--- {0:d} ----{1}'.format(group[-1][4], lineterm)
191 visiblechanges = [e for e in group if e[0] in ('replace', 'insert')]
192 if visiblechanges:
193 for tag, _, _, j1, j2 in group:
194 if tag != 'delete':
195 for line in b[j1:j2]:
196 yield prefixmap[tag] + line
197 29
198 30
199 class DiffDialog(QWidget, Ui_DiffDialog): 31 class DiffDialog(QWidget, Ui_DiffDialog):
200 """ 32 """
201 Class implementing a dialog to compare two files. 33 Class implementing a dialog to compare two files.

eric ide

mercurial