models.main: Remove unused add_or_remove() method
[git-cola.git] / cola / diffparse.py
blob406792947c861bfac5f9199ec75dec61b737b595
1 import os
2 import re
4 from cola.utils import write
7 class DiffParser(object):
8 """Handles parsing diff for use by the interactive index editor."""
9 def __init__(self, model, filename='',
10 cached=True, branch=None, reverse=False):
12 self._header_re = re.compile('^@@ -(\d+),(\d+) \+(\d+),(\d+) @@.*')
13 self._headers = []
15 self._idx = -1
16 self._diffs = []
17 self._diff_spans = []
18 self._diff_offsets = []
20 self.start = None
21 self.end = None
22 self.offset = None
23 self.diffs = []
24 self.selected = []
26 (header, diff) = model.diff_helper(filename=filename,
27 branch=branch,
28 with_diff_header=True,
29 cached=cached and not bool(branch),
30 reverse=cached or bool(branch) or reverse)
31 self.model = model
32 self.diff = diff
33 self.header = header
34 self.parse_diff(diff)
36 # Always index into the non-reversed diff
37 self.fwd_header, self.fwd_diff = \
38 model.diff_helper(filename=filename,
39 branch=branch,
40 with_diff_header=True,
41 cached=cached and not bool(branch),
42 reverse=bool(branch))
44 def write_diff(self,filename,which,selected=False,noop=False):
45 """Writes a new diff corresponding to the user's selection."""
46 if not noop and which < len(self.diffs):
47 diff = self.diffs[which]
48 write(filename, self.header + '\n' + diff + '\n')
49 return True
50 else:
51 return False
53 def diffs(self):
54 """Returns the list of diffs."""
55 return self._diffs
57 def diff_subset(self, diff, start, end):
58 """Processes the diffs and returns a selected subset from that diff.
59 """
60 adds = 0
61 deletes = 0
62 newdiff = []
63 local_offset = 0
64 offset = self._diff_spans[diff][0]
65 diffguts = '\n'.join(self._diffs[diff])
67 for line in self._diffs[diff]:
68 line_start = offset + local_offset
69 local_offset += len(line) + 1 #\n
70 line_end = offset + local_offset
71 # |line1 |line2 |line3 |
72 # |--selection--|
73 # '-start '-end
74 # selection has head of diff (line3)
75 if start < line_start and end > line_start and end < line_end:
76 newdiff.append(line)
77 if line.startswith('+'):
78 adds += 1
79 if line.startswith('-'):
80 deletes += 1
81 # selection has all of diff (line2)
82 elif start <= line_start and end >= line_end:
83 newdiff.append(line)
84 if line.startswith('+'):
85 adds += 1
86 if line.startswith('-'):
87 deletes += 1
88 # selection has tail of diff (line1)
89 elif start >= line_start and start < line_end - 1:
90 newdiff.append(line)
91 if line.startswith('+'):
92 adds += 1
93 if line.startswith('-'):
94 deletes += 1
95 else:
96 # Don't add new lines unless selected
97 if line.startswith('+'):
98 continue
99 elif line.startswith('-'):
100 # Don't remove lines unless selected
101 newdiff.append(' ' + line[1:])
102 else:
103 newdiff.append(line)
105 new_count = self._headers[diff][1] + adds - deletes
106 if new_count != self._headers[diff][3]:
107 header = '@@ -%d,%d +%d,%d @@' % (
108 self._headers[diff][0],
109 self._headers[diff][1],
110 self._headers[diff][2],
111 new_count)
112 newdiff[0] = header
114 return (self.header + '\n' + '\n'.join(newdiff) + '\n')
116 def spans(self):
117 """Returns the line spans of each hunk."""
118 return self._diff_spans
120 def offsets(self):
121 """Returns the offsets."""
122 return self._diff_offsets
124 def set_diff_to_offset(self, offset):
125 """Sets the diff selection to be the hunk at a particular offset."""
126 self.offset = offset
127 self.diffs, self.selected = self.diff_for_offset(offset)
129 def set_diffs_to_range(self, start, end):
130 """Sets the diff selection to be a range of hunks."""
131 self.start = start
132 self.end = end
133 self.diffs, self.selected = self.diffs_for_range(start,end)
135 def diff_for_offset(self, offset):
136 """Returns the hunks for a particular offset."""
137 for idx, diff_offset in enumerate(self._diff_offsets):
138 if offset < diff_offset:
139 return (['\n'.join(self._diffs[idx])], [idx])
140 return ([],[])
142 def diffs_for_range(self, start, end):
143 """Returns the hunks for a selected range."""
144 diffs = []
145 indices = []
146 for idx, span in enumerate(self._diff_spans):
147 has_end_of_diff = start >= span[0] and start < span[1]
148 has_all_of_diff = start <= span[0] and end >= span[1]
149 has_head_of_diff = end >= span[0] and end <= span[1]
151 selected_diff =(has_end_of_diff
152 or has_all_of_diff
153 or has_head_of_diff)
154 if selected_diff:
155 diff = '\n'.join(self._diffs[idx])
156 diffs.append(diff)
157 indices.append(idx)
158 return diffs, indices
160 def parse_diff(self, diff):
161 """Parses a diff and extracts headers, offsets, hunks, etc.
163 total_offset = 0
164 self._idx = -1
165 self._headers = []
167 for idx, line in enumerate(diff.split('\n')):
168 match = self._header_re.match(line)
169 if match:
170 self._headers.append([
171 int(match.group(1)),
172 int(match.group(2)),
173 int(match.group(3)),
174 int(match.group(4))
176 self._diffs.append( [line] )
178 line_len = len(line) + 1 #\n
179 self._diff_spans.append([total_offset,
180 total_offset + line_len])
181 total_offset += line_len
182 self._diff_offsets.append(total_offset)
183 self._idx += 1
184 else:
185 if self._idx < 0:
186 errmsg = 'Malformed diff?\n\n%s' % diff
187 raise AssertionError, errmsg
188 line_len = len(line) + 1
189 total_offset += line_len
191 self._diffs[self._idx].append(line)
192 self._diff_spans[-1][-1] += line_len
193 self._diff_offsets[self._idx] += line_len
195 def process_diff_selection(self, selected, offset, selection,
196 apply_to_worktree=False):
197 """Processes a diff selection and applies changes to git."""
198 if selection:
199 # qt destroys \r\n and makes it \n with no way of going back.
200 # boo! we work around that here.
201 # I think this was win32-specific. We might want to do
202 # this on win32 only (TODO verify)
203 if selection not in self.fwd_diff:
204 special_selection = selection.replace('\n', '\r\n')
205 if special_selection in self.fwd_diff:
206 selection = special_selection
207 else:
208 return
209 start = self.fwd_diff.index(selection)
210 end = start + len(selection)
211 self.set_diffs_to_range(start, end)
212 else:
213 self.set_diff_to_offset(offset)
214 selected = False
215 # Process diff selection only
216 if selected:
217 for idx in self.selected:
218 contents = self.diff_subset(idx, start, end)
219 if contents:
220 tmpfile = self.model.tmp_filename()
221 write(tmpfile, contents)
222 if apply_to_worktree:
223 self.model.apply_diff_to_worktree(tmpfile)
224 else:
225 self.model.apply_diff(tmpfile)
226 os.unlink(tmpfile)
227 # Process a complete hunk
228 else:
229 for idx, diff in enumerate(self.diffs):
230 tmpfile = self.model.tmp_filename()
231 if self.write_diff(tmpfile,idx):
232 if apply_to_worktree:
233 self.model.apply_diff_to_worktree(tmpfile)
234 else:
235 self.model.apply_diff(tmpfile)
236 os.unlink(tmpfile)