##// END OF EJS Templates
crecord: throws error instead of crashing for large diffs...
Laurent Charignon -
r25821:d68544b6 default
parent child Browse files
Show More
@@ -1,1631 +1,1635
1 # stuff related specifically to patch manipulation / parsing
1 # stuff related specifically to patch manipulation / parsing
2 #
2 #
3 # Copyright 2008 Mark Edgington <edgimar@gmail.com>
3 # Copyright 2008 Mark Edgington <edgimar@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 #
7 #
8 # This code is based on the Mark Edgington's crecord extension.
8 # This code is based on the Mark Edgington's crecord extension.
9 # (Itself based on Bryan O'Sullivan's record extension.)
9 # (Itself based on Bryan O'Sullivan's record extension.)
10
10
11 from i18n import _
11 from i18n import _
12 import patch as patchmod
12 import patch as patchmod
13 import util, encoding
13 import util, encoding
14
14
15 import os, re, sys, struct, signal, tempfile, locale, cStringIO
15 import os, re, sys, struct, signal, tempfile, locale, cStringIO
16
16
17 # This is required for ncurses to display non-ASCII characters in default user
17 # This is required for ncurses to display non-ASCII characters in default user
18 # locale encoding correctly. --immerrr
18 # locale encoding correctly. --immerrr
19 locale.setlocale(locale.LC_ALL, '')
19 locale.setlocale(locale.LC_ALL, '')
20
20
21 # os.name is one of: 'posix', 'nt', 'dos', 'os2', 'mac', or 'ce'
21 # os.name is one of: 'posix', 'nt', 'dos', 'os2', 'mac', or 'ce'
22 if os.name == 'posix':
22 if os.name == 'posix':
23 import curses
23 import curses
24 import fcntl, termios
24 import fcntl, termios
25 else:
25 else:
26 # I have no idea if wcurses works with crecord...
26 # I have no idea if wcurses works with crecord...
27 try:
27 try:
28 import wcurses as curses
28 import wcurses as curses
29 except ImportError:
29 except ImportError:
30 # wcurses is not shipped on Windows by default
30 # wcurses is not shipped on Windows by default
31 pass
31 pass
32
32
33 try:
33 try:
34 curses
34 curses
35 except NameError:
35 except NameError:
36 if os.name != 'nt': # Temporary hack to get running on Windows again
36 if os.name != 'nt': # Temporary hack to get running on Windows again
37 raise util.Abort(
37 raise util.Abort(
38 _('the python curses/wcurses module is not available/installed'))
38 _('the python curses/wcurses module is not available/installed'))
39
39
40 _origstdout = sys.__stdout__ # used by gethw()
40 _origstdout = sys.__stdout__ # used by gethw()
41
41
42 class patchnode(object):
42 class patchnode(object):
43 """abstract class for patch graph nodes
43 """abstract class for patch graph nodes
44 (i.e. patchroot, header, hunk, hunkline)
44 (i.e. patchroot, header, hunk, hunkline)
45 """
45 """
46
46
47 def firstchild(self):
47 def firstchild(self):
48 raise NotImplementedError("method must be implemented by subclass")
48 raise NotImplementedError("method must be implemented by subclass")
49
49
50 def lastchild(self):
50 def lastchild(self):
51 raise NotImplementedError("method must be implemented by subclass")
51 raise NotImplementedError("method must be implemented by subclass")
52
52
53 def allchildren(self):
53 def allchildren(self):
54 "Return a list of all of the direct children of this node"
54 "Return a list of all of the direct children of this node"
55 raise NotImplementedError("method must be implemented by subclass")
55 raise NotImplementedError("method must be implemented by subclass")
56 def nextsibling(self):
56 def nextsibling(self):
57 """
57 """
58 Return the closest next item of the same type where there are no items
58 Return the closest next item of the same type where there are no items
59 of different types between the current item and this closest item.
59 of different types between the current item and this closest item.
60 If no such item exists, return None.
60 If no such item exists, return None.
61
61
62 """
62 """
63 raise NotImplementedError("method must be implemented by subclass")
63 raise NotImplementedError("method must be implemented by subclass")
64
64
65 def prevsibling(self):
65 def prevsibling(self):
66 """
66 """
67 Return the closest previous item of the same type where there are no
67 Return the closest previous item of the same type where there are no
68 items of different types between the current item and this closest item.
68 items of different types between the current item and this closest item.
69 If no such item exists, return None.
69 If no such item exists, return None.
70
70
71 """
71 """
72 raise NotImplementedError("method must be implemented by subclass")
72 raise NotImplementedError("method must be implemented by subclass")
73
73
74 def parentitem(self):
74 def parentitem(self):
75 raise NotImplementedError("method must be implemented by subclass")
75 raise NotImplementedError("method must be implemented by subclass")
76
76
77
77
78 def nextitem(self, constrainlevel=True, skipfolded=True):
78 def nextitem(self, constrainlevel=True, skipfolded=True):
79 """
79 """
80 If constrainLevel == True, return the closest next item
80 If constrainLevel == True, return the closest next item
81 of the same type where there are no items of different types between
81 of the same type where there are no items of different types between
82 the current item and this closest item.
82 the current item and this closest item.
83
83
84 If constrainLevel == False, then try to return the next item
84 If constrainLevel == False, then try to return the next item
85 closest to this item, regardless of item's type (header, hunk, or
85 closest to this item, regardless of item's type (header, hunk, or
86 HunkLine).
86 HunkLine).
87
87
88 If skipFolded == True, and the current item is folded, then the child
88 If skipFolded == True, and the current item is folded, then the child
89 items that are hidden due to folding will be skipped when determining
89 items that are hidden due to folding will be skipped when determining
90 the next item.
90 the next item.
91
91
92 If it is not possible to get the next item, return None.
92 If it is not possible to get the next item, return None.
93
93
94 """
94 """
95 try:
95 try:
96 itemfolded = self.folded
96 itemfolded = self.folded
97 except AttributeError:
97 except AttributeError:
98 itemfolded = False
98 itemfolded = False
99 if constrainlevel:
99 if constrainlevel:
100 return self.nextsibling()
100 return self.nextsibling()
101 elif skipfolded and itemfolded:
101 elif skipfolded and itemfolded:
102 nextitem = self.nextsibling()
102 nextitem = self.nextsibling()
103 if nextitem is None:
103 if nextitem is None:
104 try:
104 try:
105 nextitem = self.parentitem().nextsibling()
105 nextitem = self.parentitem().nextsibling()
106 except AttributeError:
106 except AttributeError:
107 nextitem = None
107 nextitem = None
108 return nextitem
108 return nextitem
109 else:
109 else:
110 # try child
110 # try child
111 item = self.firstchild()
111 item = self.firstchild()
112 if item is not None:
112 if item is not None:
113 return item
113 return item
114
114
115 # else try next sibling
115 # else try next sibling
116 item = self.nextsibling()
116 item = self.nextsibling()
117 if item is not None:
117 if item is not None:
118 return item
118 return item
119
119
120 try:
120 try:
121 # else try parent's next sibling
121 # else try parent's next sibling
122 item = self.parentitem().nextsibling()
122 item = self.parentitem().nextsibling()
123 if item is not None:
123 if item is not None:
124 return item
124 return item
125
125
126 # else return grandparent's next sibling (or None)
126 # else return grandparent's next sibling (or None)
127 return self.parentitem().parentitem().nextsibling()
127 return self.parentitem().parentitem().nextsibling()
128
128
129 except AttributeError: # parent and/or grandparent was None
129 except AttributeError: # parent and/or grandparent was None
130 return None
130 return None
131
131
132 def previtem(self, constrainlevel=True, skipfolded=True):
132 def previtem(self, constrainlevel=True, skipfolded=True):
133 """
133 """
134 If constrainLevel == True, return the closest previous item
134 If constrainLevel == True, return the closest previous item
135 of the same type where there are no items of different types between
135 of the same type where there are no items of different types between
136 the current item and this closest item.
136 the current item and this closest item.
137
137
138 If constrainLevel == False, then try to return the previous item
138 If constrainLevel == False, then try to return the previous item
139 closest to this item, regardless of item's type (header, hunk, or
139 closest to this item, regardless of item's type (header, hunk, or
140 HunkLine).
140 HunkLine).
141
141
142 If skipFolded == True, and the current item is folded, then the items
142 If skipFolded == True, and the current item is folded, then the items
143 that are hidden due to folding will be skipped when determining the
143 that are hidden due to folding will be skipped when determining the
144 next item.
144 next item.
145
145
146 If it is not possible to get the previous item, return None.
146 If it is not possible to get the previous item, return None.
147
147
148 """
148 """
149 if constrainlevel:
149 if constrainlevel:
150 return self.prevsibling()
150 return self.prevsibling()
151 else:
151 else:
152 # try previous sibling's last child's last child,
152 # try previous sibling's last child's last child,
153 # else try previous sibling's last child, else try previous sibling
153 # else try previous sibling's last child, else try previous sibling
154 prevsibling = self.prevsibling()
154 prevsibling = self.prevsibling()
155 if prevsibling is not None:
155 if prevsibling is not None:
156 prevsiblinglastchild = prevsibling.lastchild()
156 prevsiblinglastchild = prevsibling.lastchild()
157 if ((prevsiblinglastchild is not None) and
157 if ((prevsiblinglastchild is not None) and
158 not prevsibling.folded):
158 not prevsibling.folded):
159 prevsiblinglclc = prevsiblinglastchild.lastchild()
159 prevsiblinglclc = prevsiblinglastchild.lastchild()
160 if ((prevsiblinglclc is not None) and
160 if ((prevsiblinglclc is not None) and
161 not prevsiblinglastchild.folded):
161 not prevsiblinglastchild.folded):
162 return prevsiblinglclc
162 return prevsiblinglclc
163 else:
163 else:
164 return prevsiblinglastchild
164 return prevsiblinglastchild
165 else:
165 else:
166 return prevsibling
166 return prevsibling
167
167
168 # try parent (or None)
168 # try parent (or None)
169 return self.parentitem()
169 return self.parentitem()
170
170
171 class patch(patchnode, list): # todo: rename patchroot
171 class patch(patchnode, list): # todo: rename patchroot
172 """
172 """
173 list of header objects representing the patch.
173 list of header objects representing the patch.
174
174
175 """
175 """
176 def __init__(self, headerlist):
176 def __init__(self, headerlist):
177 self.extend(headerlist)
177 self.extend(headerlist)
178 # add parent patch object reference to each header
178 # add parent patch object reference to each header
179 for header in self:
179 for header in self:
180 header.patch = self
180 header.patch = self
181
181
182 class uiheader(patchnode):
182 class uiheader(patchnode):
183 """patch header
183 """patch header
184
184
185 xxx shoudn't we move this to mercurial/patch.py ?
185 xxx shoudn't we move this to mercurial/patch.py ?
186 """
186 """
187
187
188 def __init__(self, header):
188 def __init__(self, header):
189 self.nonuiheader = header
189 self.nonuiheader = header
190 # flag to indicate whether to apply this chunk
190 # flag to indicate whether to apply this chunk
191 self.applied = True
191 self.applied = True
192 # flag which only affects the status display indicating if a node's
192 # flag which only affects the status display indicating if a node's
193 # children are partially applied (i.e. some applied, some not).
193 # children are partially applied (i.e. some applied, some not).
194 self.partial = False
194 self.partial = False
195
195
196 # flag to indicate whether to display as folded/unfolded to user
196 # flag to indicate whether to display as folded/unfolded to user
197 self.folded = True
197 self.folded = True
198
198
199 # list of all headers in patch
199 # list of all headers in patch
200 self.patch = None
200 self.patch = None
201
201
202 # flag is False if this header was ever unfolded from initial state
202 # flag is False if this header was ever unfolded from initial state
203 self.neverunfolded = True
203 self.neverunfolded = True
204 self.hunks = [uihunk(h, self) for h in self.hunks]
204 self.hunks = [uihunk(h, self) for h in self.hunks]
205
205
206
206
207 def prettystr(self):
207 def prettystr(self):
208 x = cStringIO.StringIO()
208 x = cStringIO.StringIO()
209 self.pretty(x)
209 self.pretty(x)
210 return x.getvalue()
210 return x.getvalue()
211
211
212 def nextsibling(self):
212 def nextsibling(self):
213 numheadersinpatch = len(self.patch)
213 numheadersinpatch = len(self.patch)
214 indexofthisheader = self.patch.index(self)
214 indexofthisheader = self.patch.index(self)
215
215
216 if indexofthisheader < numheadersinpatch - 1:
216 if indexofthisheader < numheadersinpatch - 1:
217 nextheader = self.patch[indexofthisheader + 1]
217 nextheader = self.patch[indexofthisheader + 1]
218 return nextheader
218 return nextheader
219 else:
219 else:
220 return None
220 return None
221
221
222 def prevsibling(self):
222 def prevsibling(self):
223 indexofthisheader = self.patch.index(self)
223 indexofthisheader = self.patch.index(self)
224 if indexofthisheader > 0:
224 if indexofthisheader > 0:
225 previousheader = self.patch[indexofthisheader - 1]
225 previousheader = self.patch[indexofthisheader - 1]
226 return previousheader
226 return previousheader
227 else:
227 else:
228 return None
228 return None
229
229
230 def parentitem(self):
230 def parentitem(self):
231 """
231 """
232 there is no 'real' parent item of a header that can be selected,
232 there is no 'real' parent item of a header that can be selected,
233 so return None.
233 so return None.
234 """
234 """
235 return None
235 return None
236
236
237 def firstchild(self):
237 def firstchild(self):
238 "return the first child of this item, if one exists. otherwise None."
238 "return the first child of this item, if one exists. otherwise None."
239 if len(self.hunks) > 0:
239 if len(self.hunks) > 0:
240 return self.hunks[0]
240 return self.hunks[0]
241 else:
241 else:
242 return None
242 return None
243
243
244 def lastchild(self):
244 def lastchild(self):
245 "return the last child of this item, if one exists. otherwise None."
245 "return the last child of this item, if one exists. otherwise None."
246 if len(self.hunks) > 0:
246 if len(self.hunks) > 0:
247 return self.hunks[-1]
247 return self.hunks[-1]
248 else:
248 else:
249 return None
249 return None
250
250
251 def allchildren(self):
251 def allchildren(self):
252 "return a list of all of the direct children of this node"
252 "return a list of all of the direct children of this node"
253 return self.hunks
253 return self.hunks
254
254
255 def __getattr__(self, name):
255 def __getattr__(self, name):
256 return getattr(self.nonuiheader, name)
256 return getattr(self.nonuiheader, name)
257
257
258 class uihunkline(patchnode):
258 class uihunkline(patchnode):
259 "represents a changed line in a hunk"
259 "represents a changed line in a hunk"
260 def __init__(self, linetext, hunk):
260 def __init__(self, linetext, hunk):
261 self.linetext = linetext
261 self.linetext = linetext
262 self.applied = True
262 self.applied = True
263 # the parent hunk to which this line belongs
263 # the parent hunk to which this line belongs
264 self.hunk = hunk
264 self.hunk = hunk
265 # folding lines currently is not used/needed, but this flag is needed
265 # folding lines currently is not used/needed, but this flag is needed
266 # in the previtem method.
266 # in the previtem method.
267 self.folded = False
267 self.folded = False
268
268
269 def prettystr(self):
269 def prettystr(self):
270 return self.linetext
270 return self.linetext
271
271
272 def nextsibling(self):
272 def nextsibling(self):
273 numlinesinhunk = len(self.hunk.changedlines)
273 numlinesinhunk = len(self.hunk.changedlines)
274 indexofthisline = self.hunk.changedlines.index(self)
274 indexofthisline = self.hunk.changedlines.index(self)
275
275
276 if (indexofthisline < numlinesinhunk - 1):
276 if (indexofthisline < numlinesinhunk - 1):
277 nextline = self.hunk.changedlines[indexofthisline + 1]
277 nextline = self.hunk.changedlines[indexofthisline + 1]
278 return nextline
278 return nextline
279 else:
279 else:
280 return None
280 return None
281
281
282 def prevsibling(self):
282 def prevsibling(self):
283 indexofthisline = self.hunk.changedlines.index(self)
283 indexofthisline = self.hunk.changedlines.index(self)
284 if indexofthisline > 0:
284 if indexofthisline > 0:
285 previousline = self.hunk.changedlines[indexofthisline - 1]
285 previousline = self.hunk.changedlines[indexofthisline - 1]
286 return previousline
286 return previousline
287 else:
287 else:
288 return None
288 return None
289
289
290 def parentitem(self):
290 def parentitem(self):
291 "return the parent to the current item"
291 "return the parent to the current item"
292 return self.hunk
292 return self.hunk
293
293
294 def firstchild(self):
294 def firstchild(self):
295 "return the first child of this item, if one exists. otherwise None."
295 "return the first child of this item, if one exists. otherwise None."
296 # hunk-lines don't have children
296 # hunk-lines don't have children
297 return None
297 return None
298
298
299 def lastchild(self):
299 def lastchild(self):
300 "return the last child of this item, if one exists. otherwise None."
300 "return the last child of this item, if one exists. otherwise None."
301 # hunk-lines don't have children
301 # hunk-lines don't have children
302 return None
302 return None
303
303
304 class uihunk(patchnode):
304 class uihunk(patchnode):
305 """ui patch hunk, wraps a hunk and keep track of ui behavior """
305 """ui patch hunk, wraps a hunk and keep track of ui behavior """
306 maxcontext = 3
306 maxcontext = 3
307
307
308 def __init__(self, hunk, header):
308 def __init__(self, hunk, header):
309 self._hunk = hunk
309 self._hunk = hunk
310 self.changedlines = [uihunkline(line, self) for line in hunk.hunk]
310 self.changedlines = [uihunkline(line, self) for line in hunk.hunk]
311 self.header = header
311 self.header = header
312 # used at end for detecting how many removed lines were un-applied
312 # used at end for detecting how many removed lines were un-applied
313 self.originalremoved = self.removed
313 self.originalremoved = self.removed
314
314
315 # flag to indicate whether to display as folded/unfolded to user
315 # flag to indicate whether to display as folded/unfolded to user
316 self.folded = True
316 self.folded = True
317 # flag to indicate whether to apply this chunk
317 # flag to indicate whether to apply this chunk
318 self.applied = True
318 self.applied = True
319 # flag which only affects the status display indicating if a node's
319 # flag which only affects the status display indicating if a node's
320 # children are partially applied (i.e. some applied, some not).
320 # children are partially applied (i.e. some applied, some not).
321 self.partial = False
321 self.partial = False
322
322
323 def nextsibling(self):
323 def nextsibling(self):
324 numhunksinheader = len(self.header.hunks)
324 numhunksinheader = len(self.header.hunks)
325 indexofthishunk = self.header.hunks.index(self)
325 indexofthishunk = self.header.hunks.index(self)
326
326
327 if (indexofthishunk < numhunksinheader - 1):
327 if (indexofthishunk < numhunksinheader - 1):
328 nexthunk = self.header.hunks[indexofthishunk + 1]
328 nexthunk = self.header.hunks[indexofthishunk + 1]
329 return nexthunk
329 return nexthunk
330 else:
330 else:
331 return None
331 return None
332
332
333 def prevsibling(self):
333 def prevsibling(self):
334 indexofthishunk = self.header.hunks.index(self)
334 indexofthishunk = self.header.hunks.index(self)
335 if indexofthishunk > 0:
335 if indexofthishunk > 0:
336 previoushunk = self.header.hunks[indexofthishunk - 1]
336 previoushunk = self.header.hunks[indexofthishunk - 1]
337 return previoushunk
337 return previoushunk
338 else:
338 else:
339 return None
339 return None
340
340
341 def parentitem(self):
341 def parentitem(self):
342 "return the parent to the current item"
342 "return the parent to the current item"
343 return self.header
343 return self.header
344
344
345 def firstchild(self):
345 def firstchild(self):
346 "return the first child of this item, if one exists. otherwise None."
346 "return the first child of this item, if one exists. otherwise None."
347 if len(self.changedlines) > 0:
347 if len(self.changedlines) > 0:
348 return self.changedlines[0]
348 return self.changedlines[0]
349 else:
349 else:
350 return None
350 return None
351
351
352 def lastchild(self):
352 def lastchild(self):
353 "return the last child of this item, if one exists. otherwise None."
353 "return the last child of this item, if one exists. otherwise None."
354 if len(self.changedlines) > 0:
354 if len(self.changedlines) > 0:
355 return self.changedlines[-1]
355 return self.changedlines[-1]
356 else:
356 else:
357 return None
357 return None
358
358
359 def allchildren(self):
359 def allchildren(self):
360 "return a list of all of the direct children of this node"
360 "return a list of all of the direct children of this node"
361 return self.changedlines
361 return self.changedlines
362 def countchanges(self):
362 def countchanges(self):
363 """changedlines -> (n+,n-)"""
363 """changedlines -> (n+,n-)"""
364 add = len([l for l in self.changedlines if l.applied
364 add = len([l for l in self.changedlines if l.applied
365 and l.prettystr()[0] == '+'])
365 and l.prettystr()[0] == '+'])
366 rem = len([l for l in self.changedlines if l.applied
366 rem = len([l for l in self.changedlines if l.applied
367 and l.prettystr()[0] == '-'])
367 and l.prettystr()[0] == '-'])
368 return add, rem
368 return add, rem
369
369
370 def getfromtoline(self):
370 def getfromtoline(self):
371 # calculate the number of removed lines converted to context lines
371 # calculate the number of removed lines converted to context lines
372 removedconvertedtocontext = self.originalremoved - self.removed
372 removedconvertedtocontext = self.originalremoved - self.removed
373
373
374 contextlen = (len(self.before) + len(self.after) +
374 contextlen = (len(self.before) + len(self.after) +
375 removedconvertedtocontext)
375 removedconvertedtocontext)
376 if self.after and self.after[-1] == '\\ no newline at end of file\n':
376 if self.after and self.after[-1] == '\\ no newline at end of file\n':
377 contextlen -= 1
377 contextlen -= 1
378 fromlen = contextlen + self.removed
378 fromlen = contextlen + self.removed
379 tolen = contextlen + self.added
379 tolen = contextlen + self.added
380
380
381 # diffutils manual, section "2.2.2.2 detailed description of unified
381 # diffutils manual, section "2.2.2.2 detailed description of unified
382 # format": "an empty hunk is considered to end at the line that
382 # format": "an empty hunk is considered to end at the line that
383 # precedes the hunk."
383 # precedes the hunk."
384 #
384 #
385 # so, if either of hunks is empty, decrease its line start. --immerrr
385 # so, if either of hunks is empty, decrease its line start. --immerrr
386 # but only do this if fromline > 0, to avoid having, e.g fromline=-1.
386 # but only do this if fromline > 0, to avoid having, e.g fromline=-1.
387 fromline, toline = self.fromline, self.toline
387 fromline, toline = self.fromline, self.toline
388 if fromline != 0:
388 if fromline != 0:
389 if fromlen == 0:
389 if fromlen == 0:
390 fromline -= 1
390 fromline -= 1
391 if tolen == 0:
391 if tolen == 0:
392 toline -= 1
392 toline -= 1
393
393
394 fromtoline = '@@ -%d,%d +%d,%d @@%s\n' % (
394 fromtoline = '@@ -%d,%d +%d,%d @@%s\n' % (
395 fromline, fromlen, toline, tolen,
395 fromline, fromlen, toline, tolen,
396 self.proc and (' ' + self.proc))
396 self.proc and (' ' + self.proc))
397 return fromtoline
397 return fromtoline
398
398
399 def write(self, fp):
399 def write(self, fp):
400 # updated self.added/removed, which are used by getfromtoline()
400 # updated self.added/removed, which are used by getfromtoline()
401 self.added, self.removed = self.countchanges()
401 self.added, self.removed = self.countchanges()
402 fp.write(self.getfromtoline())
402 fp.write(self.getfromtoline())
403
403
404 hunklinelist = []
404 hunklinelist = []
405 # add the following to the list: (1) all applied lines, and
405 # add the following to the list: (1) all applied lines, and
406 # (2) all unapplied removal lines (convert these to context lines)
406 # (2) all unapplied removal lines (convert these to context lines)
407 for changedline in self.changedlines:
407 for changedline in self.changedlines:
408 changedlinestr = changedline.prettystr()
408 changedlinestr = changedline.prettystr()
409 if changedline.applied:
409 if changedline.applied:
410 hunklinelist.append(changedlinestr)
410 hunklinelist.append(changedlinestr)
411 elif changedlinestr[0] == "-":
411 elif changedlinestr[0] == "-":
412 hunklinelist.append(" " + changedlinestr[1:])
412 hunklinelist.append(" " + changedlinestr[1:])
413
413
414 fp.write(''.join(self.before + hunklinelist + self.after))
414 fp.write(''.join(self.before + hunklinelist + self.after))
415
415
416 pretty = write
416 pretty = write
417
417
418 def prettystr(self):
418 def prettystr(self):
419 x = cStringIO.StringIO()
419 x = cStringIO.StringIO()
420 self.pretty(x)
420 self.pretty(x)
421 return x.getvalue()
421 return x.getvalue()
422
422
423 def __getattr__(self, name):
423 def __getattr__(self, name):
424 return getattr(self._hunk, name)
424 return getattr(self._hunk, name)
425 def __repr__(self):
425 def __repr__(self):
426 return '<hunk %r@%d>' % (self.filename(), self.fromline)
426 return '<hunk %r@%d>' % (self.filename(), self.fromline)
427
427
428 def filterpatch(ui, chunks, chunkselector, operation=None):
428 def filterpatch(ui, chunks, chunkselector, operation=None):
429 """interactively filter patch chunks into applied-only chunks"""
429 """interactively filter patch chunks into applied-only chunks"""
430
430
431 if operation is None:
431 if operation is None:
432 operation = _('confirm')
432 operation = _('confirm')
433 chunks = list(chunks)
433 chunks = list(chunks)
434 # convert chunks list into structure suitable for displaying/modifying
434 # convert chunks list into structure suitable for displaying/modifying
435 # with curses. create a list of headers only.
435 # with curses. create a list of headers only.
436 headers = [c for c in chunks if isinstance(c, patchmod.header)]
436 headers = [c for c in chunks if isinstance(c, patchmod.header)]
437
437
438 # if there are no changed files
438 # if there are no changed files
439 if len(headers) == 0:
439 if len(headers) == 0:
440 return []
440 return []
441 uiheaders = [uiheader(h) for h in headers]
441 uiheaders = [uiheader(h) for h in headers]
442 # let user choose headers/hunks/lines, and mark their applied flags
442 # let user choose headers/hunks/lines, and mark their applied flags
443 # accordingly
443 # accordingly
444 chunkselector(ui, uiheaders)
444 chunkselector(ui, uiheaders)
445 appliedhunklist = []
445 appliedhunklist = []
446 for hdr in uiheaders:
446 for hdr in uiheaders:
447 if (hdr.applied and
447 if (hdr.applied and
448 (hdr.special() or len([h for h in hdr.hunks if h.applied]) > 0)):
448 (hdr.special() or len([h for h in hdr.hunks if h.applied]) > 0)):
449 appliedhunklist.append(hdr)
449 appliedhunklist.append(hdr)
450 fixoffset = 0
450 fixoffset = 0
451 for hnk in hdr.hunks:
451 for hnk in hdr.hunks:
452 if hnk.applied:
452 if hnk.applied:
453 appliedhunklist.append(hnk)
453 appliedhunklist.append(hnk)
454 # adjust the 'to'-line offset of the hunk to be correct
454 # adjust the 'to'-line offset of the hunk to be correct
455 # after de-activating some of the other hunks for this file
455 # after de-activating some of the other hunks for this file
456 if fixoffset:
456 if fixoffset:
457 #hnk = copy.copy(hnk) # necessary??
457 #hnk = copy.copy(hnk) # necessary??
458 hnk.toline += fixoffset
458 hnk.toline += fixoffset
459 else:
459 else:
460 fixoffset += hnk.removed - hnk.added
460 fixoffset += hnk.removed - hnk.added
461
461
462 return appliedhunklist
462 return appliedhunklist
463
463
464 def gethw():
464 def gethw():
465 """
465 """
466 magically get the current height and width of the window (without initscr)
466 magically get the current height and width of the window (without initscr)
467
467
468 this is a rip-off of a rip-off - taken from the bpython code. it is
468 this is a rip-off of a rip-off - taken from the bpython code. it is
469 useful / necessary because otherwise curses.initscr() must be called,
469 useful / necessary because otherwise curses.initscr() must be called,
470 which can leave the terminal in a nasty state after exiting.
470 which can leave the terminal in a nasty state after exiting.
471
471
472 """
472 """
473 h, w = struct.unpack(
473 h, w = struct.unpack(
474 "hhhh", fcntl.ioctl(_origstdout, termios.TIOCGWINSZ, "\000"*8))[0:2]
474 "hhhh", fcntl.ioctl(_origstdout, termios.TIOCGWINSZ, "\000"*8))[0:2]
475 return h, w
475 return h, w
476
476
477 def chunkselector(ui, headerlist):
477 def chunkselector(ui, headerlist):
478 """
478 """
479 curses interface to get selection of chunks, and mark the applied flags
479 curses interface to get selection of chunks, and mark the applied flags
480 of the chosen chunks.
480 of the chosen chunks.
481
481
482 """
482 """
483 ui.write(_('starting interactive selection\n'))
483 ui.write(_('starting interactive selection\n'))
484 chunkselector = curseschunkselector(headerlist, ui)
484 chunkselector = curseschunkselector(headerlist, ui)
485 f = signal.getsignal(signal.SIGTSTP)
485 f = signal.getsignal(signal.SIGTSTP)
486 curses.wrapper(chunkselector.main)
486 curses.wrapper(chunkselector.main)
487 if chunkselector.initerr is not None:
487 if chunkselector.initerr is not None:
488 raise util.Abort(chunkselector.initerr)
488 raise util.Abort(chunkselector.initerr)
489 # ncurses does not restore signal handler for SIGTSTP
489 # ncurses does not restore signal handler for SIGTSTP
490 signal.signal(signal.SIGTSTP, f)
490 signal.signal(signal.SIGTSTP, f)
491
491
492 def testdecorator(testfn, f):
492 def testdecorator(testfn, f):
493 def u(*args, **kwargs):
493 def u(*args, **kwargs):
494 return f(testfn, *args, **kwargs)
494 return f(testfn, *args, **kwargs)
495 return u
495 return u
496
496
497 def testchunkselector(testfn, ui, headerlist):
497 def testchunkselector(testfn, ui, headerlist):
498 """
498 """
499 test interface to get selection of chunks, and mark the applied flags
499 test interface to get selection of chunks, and mark the applied flags
500 of the chosen chunks.
500 of the chosen chunks.
501
501
502 """
502 """
503 chunkselector = curseschunkselector(headerlist, ui)
503 chunkselector = curseschunkselector(headerlist, ui)
504 if testfn and os.path.exists(testfn):
504 if testfn and os.path.exists(testfn):
505 testf = open(testfn)
505 testf = open(testfn)
506 testcommands = map(lambda x: x.rstrip('\n'), testf.readlines())
506 testcommands = map(lambda x: x.rstrip('\n'), testf.readlines())
507 testf.close()
507 testf.close()
508 while True:
508 while True:
509 if chunkselector.handlekeypressed(testcommands.pop(0), test=True):
509 if chunkselector.handlekeypressed(testcommands.pop(0), test=True):
510 break
510 break
511
511
512 class curseschunkselector(object):
512 class curseschunkselector(object):
513 def __init__(self, headerlist, ui):
513 def __init__(self, headerlist, ui):
514 # put the headers into a patch object
514 # put the headers into a patch object
515 self.headerlist = patch(headerlist)
515 self.headerlist = patch(headerlist)
516
516
517 self.ui = ui
517 self.ui = ui
518
518
519 self.errorstr = None
519 self.errorstr = None
520 # list of all chunks
520 # list of all chunks
521 self.chunklist = []
521 self.chunklist = []
522 for h in headerlist:
522 for h in headerlist:
523 self.chunklist.append(h)
523 self.chunklist.append(h)
524 self.chunklist.extend(h.hunks)
524 self.chunklist.extend(h.hunks)
525
525
526 # dictionary mapping (fgcolor, bgcolor) pairs to the
526 # dictionary mapping (fgcolor, bgcolor) pairs to the
527 # corresponding curses color-pair value.
527 # corresponding curses color-pair value.
528 self.colorpairs = {}
528 self.colorpairs = {}
529 # maps custom nicknames of color-pairs to curses color-pair values
529 # maps custom nicknames of color-pairs to curses color-pair values
530 self.colorpairnames = {}
530 self.colorpairnames = {}
531
531
532 # the currently selected header, hunk, or hunk-line
532 # the currently selected header, hunk, or hunk-line
533 self.currentselecteditem = self.headerlist[0]
533 self.currentselecteditem = self.headerlist[0]
534
534
535 # updated when printing out patch-display -- the 'lines' here are the
535 # updated when printing out patch-display -- the 'lines' here are the
536 # line positions *in the pad*, not on the screen.
536 # line positions *in the pad*, not on the screen.
537 self.selecteditemstartline = 0
537 self.selecteditemstartline = 0
538 self.selecteditemendline = None
538 self.selecteditemendline = None
539
539
540 # define indentation levels
540 # define indentation levels
541 self.headerindentnumchars = 0
541 self.headerindentnumchars = 0
542 self.hunkindentnumchars = 3
542 self.hunkindentnumchars = 3
543 self.hunklineindentnumchars = 6
543 self.hunklineindentnumchars = 6
544
544
545 # the first line of the pad to print to the screen
545 # the first line of the pad to print to the screen
546 self.firstlineofpadtoprint = 0
546 self.firstlineofpadtoprint = 0
547
547
548 # keeps track of the number of lines in the pad
548 # keeps track of the number of lines in the pad
549 self.numpadlines = None
549 self.numpadlines = None
550
550
551 self.numstatuslines = 2
551 self.numstatuslines = 2
552
552
553 # keep a running count of the number of lines printed to the pad
553 # keep a running count of the number of lines printed to the pad
554 # (used for determining when the selected item begins/ends)
554 # (used for determining when the selected item begins/ends)
555 self.linesprintedtopadsofar = 0
555 self.linesprintedtopadsofar = 0
556
556
557 # the first line of the pad which is visible on the screen
557 # the first line of the pad which is visible on the screen
558 self.firstlineofpadtoprint = 0
558 self.firstlineofpadtoprint = 0
559
559
560 # stores optional text for a commit comment provided by the user
560 # stores optional text for a commit comment provided by the user
561 self.commenttext = ""
561 self.commenttext = ""
562
562
563 # if the last 'toggle all' command caused all changes to be applied
563 # if the last 'toggle all' command caused all changes to be applied
564 self.waslasttoggleallapplied = True
564 self.waslasttoggleallapplied = True
565
565
566 def uparrowevent(self):
566 def uparrowevent(self):
567 """
567 """
568 try to select the previous item to the current item that has the
568 try to select the previous item to the current item that has the
569 most-indented level. for example, if a hunk is selected, try to select
569 most-indented level. for example, if a hunk is selected, try to select
570 the last hunkline of the hunk prior to the selected hunk. or, if
570 the last hunkline of the hunk prior to the selected hunk. or, if
571 the first hunkline of a hunk is currently selected, then select the
571 the first hunkline of a hunk is currently selected, then select the
572 hunk itself.
572 hunk itself.
573
573
574 if the currently selected item is already at the top of the screen,
574 if the currently selected item is already at the top of the screen,
575 scroll the screen down to show the new-selected item.
575 scroll the screen down to show the new-selected item.
576
576
577 """
577 """
578 currentitem = self.currentselecteditem
578 currentitem = self.currentselecteditem
579
579
580 nextitem = currentitem.previtem(constrainlevel=False)
580 nextitem = currentitem.previtem(constrainlevel=False)
581
581
582 if nextitem is None:
582 if nextitem is None:
583 # if no parent item (i.e. currentitem is the first header), then
583 # if no parent item (i.e. currentitem is the first header), then
584 # no change...
584 # no change...
585 nextitem = currentitem
585 nextitem = currentitem
586
586
587 self.currentselecteditem = nextitem
587 self.currentselecteditem = nextitem
588
588
589 def uparrowshiftevent(self):
589 def uparrowshiftevent(self):
590 """
590 """
591 select (if possible) the previous item on the same level as the
591 select (if possible) the previous item on the same level as the
592 currently selected item. otherwise, select (if possible) the
592 currently selected item. otherwise, select (if possible) the
593 parent-item of the currently selected item.
593 parent-item of the currently selected item.
594
594
595 if the currently selected item is already at the top of the screen,
595 if the currently selected item is already at the top of the screen,
596 scroll the screen down to show the new-selected item.
596 scroll the screen down to show the new-selected item.
597
597
598 """
598 """
599 currentitem = self.currentselecteditem
599 currentitem = self.currentselecteditem
600 nextitem = currentitem.previtem()
600 nextitem = currentitem.previtem()
601 # if there's no previous item on this level, try choosing the parent
601 # if there's no previous item on this level, try choosing the parent
602 if nextitem is None:
602 if nextitem is None:
603 nextitem = currentitem.parentitem()
603 nextitem = currentitem.parentitem()
604 if nextitem is None:
604 if nextitem is None:
605 # if no parent item (i.e. currentitem is the first header), then
605 # if no parent item (i.e. currentitem is the first header), then
606 # no change...
606 # no change...
607 nextitem = currentitem
607 nextitem = currentitem
608
608
609 self.currentselecteditem = nextitem
609 self.currentselecteditem = nextitem
610
610
611 def downarrowevent(self):
611 def downarrowevent(self):
612 """
612 """
613 try to select the next item to the current item that has the
613 try to select the next item to the current item that has the
614 most-indented level. for example, if a hunk is selected, select
614 most-indented level. for example, if a hunk is selected, select
615 the first hunkline of the selected hunk. or, if the last hunkline of
615 the first hunkline of the selected hunk. or, if the last hunkline of
616 a hunk is currently selected, then select the next hunk, if one exists,
616 a hunk is currently selected, then select the next hunk, if one exists,
617 or if not, the next header if one exists.
617 or if not, the next header if one exists.
618
618
619 if the currently selected item is already at the bottom of the screen,
619 if the currently selected item is already at the bottom of the screen,
620 scroll the screen up to show the new-selected item.
620 scroll the screen up to show the new-selected item.
621
621
622 """
622 """
623 #self.startprintline += 1 #debug
623 #self.startprintline += 1 #debug
624 currentitem = self.currentselecteditem
624 currentitem = self.currentselecteditem
625
625
626 nextitem = currentitem.nextitem(constrainlevel=False)
626 nextitem = currentitem.nextitem(constrainlevel=False)
627 # if there's no next item, keep the selection as-is
627 # if there's no next item, keep the selection as-is
628 if nextitem is None:
628 if nextitem is None:
629 nextitem = currentitem
629 nextitem = currentitem
630
630
631 self.currentselecteditem = nextitem
631 self.currentselecteditem = nextitem
632
632
633 def downarrowshiftevent(self):
633 def downarrowshiftevent(self):
634 """
634 """
635 if the cursor is already at the bottom chunk, scroll the screen up and
635 if the cursor is already at the bottom chunk, scroll the screen up and
636 move the cursor-position to the subsequent chunk. otherwise, only move
636 move the cursor-position to the subsequent chunk. otherwise, only move
637 the cursor position down one chunk.
637 the cursor position down one chunk.
638
638
639 """
639 """
640 # todo: update docstring
640 # todo: update docstring
641
641
642 currentitem = self.currentselecteditem
642 currentitem = self.currentselecteditem
643 nextitem = currentitem.nextitem()
643 nextitem = currentitem.nextitem()
644 # if there's no previous item on this level, try choosing the parent's
644 # if there's no previous item on this level, try choosing the parent's
645 # nextitem.
645 # nextitem.
646 if nextitem is None:
646 if nextitem is None:
647 try:
647 try:
648 nextitem = currentitem.parentitem().nextitem()
648 nextitem = currentitem.parentitem().nextitem()
649 except AttributeError:
649 except AttributeError:
650 # parentitem returned None, so nextitem() can't be called
650 # parentitem returned None, so nextitem() can't be called
651 nextitem = None
651 nextitem = None
652 if nextitem is None:
652 if nextitem is None:
653 # if no next item on parent-level, then no change...
653 # if no next item on parent-level, then no change...
654 nextitem = currentitem
654 nextitem = currentitem
655
655
656 self.currentselecteditem = nextitem
656 self.currentselecteditem = nextitem
657
657
658 def rightarrowevent(self):
658 def rightarrowevent(self):
659 """
659 """
660 select (if possible) the first of this item's child-items.
660 select (if possible) the first of this item's child-items.
661
661
662 """
662 """
663 currentitem = self.currentselecteditem
663 currentitem = self.currentselecteditem
664 nextitem = currentitem.firstchild()
664 nextitem = currentitem.firstchild()
665
665
666 # turn off folding if we want to show a child-item
666 # turn off folding if we want to show a child-item
667 if currentitem.folded:
667 if currentitem.folded:
668 self.togglefolded(currentitem)
668 self.togglefolded(currentitem)
669
669
670 if nextitem is None:
670 if nextitem is None:
671 # if no next item on parent-level, then no change...
671 # if no next item on parent-level, then no change...
672 nextitem = currentitem
672 nextitem = currentitem
673
673
674 self.currentselecteditem = nextitem
674 self.currentselecteditem = nextitem
675
675
676 def leftarrowevent(self):
676 def leftarrowevent(self):
677 """
677 """
678 if the current item can be folded (i.e. it is an unfolded header or
678 if the current item can be folded (i.e. it is an unfolded header or
679 hunk), then fold it. otherwise try select (if possible) the parent
679 hunk), then fold it. otherwise try select (if possible) the parent
680 of this item.
680 of this item.
681
681
682 """
682 """
683 currentitem = self.currentselecteditem
683 currentitem = self.currentselecteditem
684
684
685 # try to fold the item
685 # try to fold the item
686 if not isinstance(currentitem, uihunkline):
686 if not isinstance(currentitem, uihunkline):
687 if not currentitem.folded:
687 if not currentitem.folded:
688 self.togglefolded(item=currentitem)
688 self.togglefolded(item=currentitem)
689 return
689 return
690
690
691 # if it can't be folded, try to select the parent item
691 # if it can't be folded, try to select the parent item
692 nextitem = currentitem.parentitem()
692 nextitem = currentitem.parentitem()
693
693
694 if nextitem is None:
694 if nextitem is None:
695 # if no item on parent-level, then no change...
695 # if no item on parent-level, then no change...
696 nextitem = currentitem
696 nextitem = currentitem
697 if not nextitem.folded:
697 if not nextitem.folded:
698 self.togglefolded(item=nextitem)
698 self.togglefolded(item=nextitem)
699
699
700 self.currentselecteditem = nextitem
700 self.currentselecteditem = nextitem
701
701
702 def leftarrowshiftevent(self):
702 def leftarrowshiftevent(self):
703 """
703 """
704 select the header of the current item (or fold current item if the
704 select the header of the current item (or fold current item if the
705 current item is already a header).
705 current item is already a header).
706
706
707 """
707 """
708 currentitem = self.currentselecteditem
708 currentitem = self.currentselecteditem
709
709
710 if isinstance(currentitem, uiheader):
710 if isinstance(currentitem, uiheader):
711 if not currentitem.folded:
711 if not currentitem.folded:
712 self.togglefolded(item=currentitem)
712 self.togglefolded(item=currentitem)
713 return
713 return
714
714
715 # select the parent item recursively until we're at a header
715 # select the parent item recursively until we're at a header
716 while True:
716 while True:
717 nextitem = currentitem.parentitem()
717 nextitem = currentitem.parentitem()
718 if nextitem is None:
718 if nextitem is None:
719 break
719 break
720 else:
720 else:
721 currentitem = nextitem
721 currentitem = nextitem
722
722
723 self.currentselecteditem = currentitem
723 self.currentselecteditem = currentitem
724
724
725 def updatescroll(self):
725 def updatescroll(self):
726 "scroll the screen to fully show the currently-selected"
726 "scroll the screen to fully show the currently-selected"
727 selstart = self.selecteditemstartline
727 selstart = self.selecteditemstartline
728 selend = self.selecteditemendline
728 selend = self.selecteditemendline
729 #selnumlines = selend - selstart
729 #selnumlines = selend - selstart
730 padstart = self.firstlineofpadtoprint
730 padstart = self.firstlineofpadtoprint
731 padend = padstart + self.yscreensize - self.numstatuslines - 1
731 padend = padstart + self.yscreensize - self.numstatuslines - 1
732 # 'buffered' pad start/end values which scroll with a certain
732 # 'buffered' pad start/end values which scroll with a certain
733 # top/bottom context margin
733 # top/bottom context margin
734 padstartbuffered = padstart + 3
734 padstartbuffered = padstart + 3
735 padendbuffered = padend - 3
735 padendbuffered = padend - 3
736
736
737 if selend > padendbuffered:
737 if selend > padendbuffered:
738 self.scrolllines(selend - padendbuffered)
738 self.scrolllines(selend - padendbuffered)
739 elif selstart < padstartbuffered:
739 elif selstart < padstartbuffered:
740 # negative values scroll in pgup direction
740 # negative values scroll in pgup direction
741 self.scrolllines(selstart - padstartbuffered)
741 self.scrolllines(selstart - padstartbuffered)
742
742
743
743
744 def scrolllines(self, numlines):
744 def scrolllines(self, numlines):
745 "scroll the screen up (down) by numlines when numlines >0 (<0)."
745 "scroll the screen up (down) by numlines when numlines >0 (<0)."
746 self.firstlineofpadtoprint += numlines
746 self.firstlineofpadtoprint += numlines
747 if self.firstlineofpadtoprint < 0:
747 if self.firstlineofpadtoprint < 0:
748 self.firstlineofpadtoprint = 0
748 self.firstlineofpadtoprint = 0
749 if self.firstlineofpadtoprint > self.numpadlines - 1:
749 if self.firstlineofpadtoprint > self.numpadlines - 1:
750 self.firstlineofpadtoprint = self.numpadlines - 1
750 self.firstlineofpadtoprint = self.numpadlines - 1
751
751
752 def toggleapply(self, item=None):
752 def toggleapply(self, item=None):
753 """
753 """
754 toggle the applied flag of the specified item. if no item is specified,
754 toggle the applied flag of the specified item. if no item is specified,
755 toggle the flag of the currently selected item.
755 toggle the flag of the currently selected item.
756
756
757 """
757 """
758 if item is None:
758 if item is None:
759 item = self.currentselecteditem
759 item = self.currentselecteditem
760
760
761 item.applied = not item.applied
761 item.applied = not item.applied
762
762
763 if isinstance(item, uiheader):
763 if isinstance(item, uiheader):
764 item.partial = False
764 item.partial = False
765 if item.applied:
765 if item.applied:
766 # apply all its hunks
766 # apply all its hunks
767 for hnk in item.hunks:
767 for hnk in item.hunks:
768 hnk.applied = True
768 hnk.applied = True
769 # apply all their hunklines
769 # apply all their hunklines
770 for hunkline in hnk.changedlines:
770 for hunkline in hnk.changedlines:
771 hunkline.applied = True
771 hunkline.applied = True
772 else:
772 else:
773 # un-apply all its hunks
773 # un-apply all its hunks
774 for hnk in item.hunks:
774 for hnk in item.hunks:
775 hnk.applied = False
775 hnk.applied = False
776 hnk.partial = False
776 hnk.partial = False
777 # un-apply all their hunklines
777 # un-apply all their hunklines
778 for hunkline in hnk.changedlines:
778 for hunkline in hnk.changedlines:
779 hunkline.applied = False
779 hunkline.applied = False
780 elif isinstance(item, uihunk):
780 elif isinstance(item, uihunk):
781 item.partial = False
781 item.partial = False
782 # apply all it's hunklines
782 # apply all it's hunklines
783 for hunkline in item.changedlines:
783 for hunkline in item.changedlines:
784 hunkline.applied = item.applied
784 hunkline.applied = item.applied
785
785
786 siblingappliedstatus = [hnk.applied for hnk in item.header.hunks]
786 siblingappliedstatus = [hnk.applied for hnk in item.header.hunks]
787 allsiblingsapplied = not (False in siblingappliedstatus)
787 allsiblingsapplied = not (False in siblingappliedstatus)
788 nosiblingsapplied = not (True in siblingappliedstatus)
788 nosiblingsapplied = not (True in siblingappliedstatus)
789
789
790 siblingspartialstatus = [hnk.partial for hnk in item.header.hunks]
790 siblingspartialstatus = [hnk.partial for hnk in item.header.hunks]
791 somesiblingspartial = (True in siblingspartialstatus)
791 somesiblingspartial = (True in siblingspartialstatus)
792
792
793 #cases where applied or partial should be removed from header
793 #cases where applied or partial should be removed from header
794
794
795 # if no 'sibling' hunks are applied (including this hunk)
795 # if no 'sibling' hunks are applied (including this hunk)
796 if nosiblingsapplied:
796 if nosiblingsapplied:
797 if not item.header.special():
797 if not item.header.special():
798 item.header.applied = False
798 item.header.applied = False
799 item.header.partial = False
799 item.header.partial = False
800 else: # some/all parent siblings are applied
800 else: # some/all parent siblings are applied
801 item.header.applied = True
801 item.header.applied = True
802 item.header.partial = (somesiblingspartial or
802 item.header.partial = (somesiblingspartial or
803 not allsiblingsapplied)
803 not allsiblingsapplied)
804
804
805 elif isinstance(item, uihunkline):
805 elif isinstance(item, uihunkline):
806 siblingappliedstatus = [ln.applied for ln in item.hunk.changedlines]
806 siblingappliedstatus = [ln.applied for ln in item.hunk.changedlines]
807 allsiblingsapplied = not (False in siblingappliedstatus)
807 allsiblingsapplied = not (False in siblingappliedstatus)
808 nosiblingsapplied = not (True in siblingappliedstatus)
808 nosiblingsapplied = not (True in siblingappliedstatus)
809
809
810 # if no 'sibling' lines are applied
810 # if no 'sibling' lines are applied
811 if nosiblingsapplied:
811 if nosiblingsapplied:
812 item.hunk.applied = False
812 item.hunk.applied = False
813 item.hunk.partial = False
813 item.hunk.partial = False
814 elif allsiblingsapplied:
814 elif allsiblingsapplied:
815 item.hunk.applied = True
815 item.hunk.applied = True
816 item.hunk.partial = False
816 item.hunk.partial = False
817 else: # some siblings applied
817 else: # some siblings applied
818 item.hunk.applied = True
818 item.hunk.applied = True
819 item.hunk.partial = True
819 item.hunk.partial = True
820
820
821 parentsiblingsapplied = [hnk.applied for hnk
821 parentsiblingsapplied = [hnk.applied for hnk
822 in item.hunk.header.hunks]
822 in item.hunk.header.hunks]
823 noparentsiblingsapplied = not (True in parentsiblingsapplied)
823 noparentsiblingsapplied = not (True in parentsiblingsapplied)
824 allparentsiblingsapplied = not (False in parentsiblingsapplied)
824 allparentsiblingsapplied = not (False in parentsiblingsapplied)
825
825
826 parentsiblingspartial = [hnk.partial for hnk
826 parentsiblingspartial = [hnk.partial for hnk
827 in item.hunk.header.hunks]
827 in item.hunk.header.hunks]
828 someparentsiblingspartial = (True in parentsiblingspartial)
828 someparentsiblingspartial = (True in parentsiblingspartial)
829
829
830 # if all parent hunks are not applied, un-apply header
830 # if all parent hunks are not applied, un-apply header
831 if noparentsiblingsapplied:
831 if noparentsiblingsapplied:
832 if not item.hunk.header.special():
832 if not item.hunk.header.special():
833 item.hunk.header.applied = False
833 item.hunk.header.applied = False
834 item.hunk.header.partial = False
834 item.hunk.header.partial = False
835 # set the applied and partial status of the header if needed
835 # set the applied and partial status of the header if needed
836 else: # some/all parent siblings are applied
836 else: # some/all parent siblings are applied
837 item.hunk.header.applied = True
837 item.hunk.header.applied = True
838 item.hunk.header.partial = (someparentsiblingspartial or
838 item.hunk.header.partial = (someparentsiblingspartial or
839 not allparentsiblingsapplied)
839 not allparentsiblingsapplied)
840
840
841 def toggleall(self):
841 def toggleall(self):
842 "toggle the applied flag of all items."
842 "toggle the applied flag of all items."
843 if self.waslasttoggleallapplied: # then unapply them this time
843 if self.waslasttoggleallapplied: # then unapply them this time
844 for item in self.headerlist:
844 for item in self.headerlist:
845 if item.applied:
845 if item.applied:
846 self.toggleapply(item)
846 self.toggleapply(item)
847 else:
847 else:
848 for item in self.headerlist:
848 for item in self.headerlist:
849 if not item.applied:
849 if not item.applied:
850 self.toggleapply(item)
850 self.toggleapply(item)
851 self.waslasttoggleallapplied = not self.waslasttoggleallapplied
851 self.waslasttoggleallapplied = not self.waslasttoggleallapplied
852
852
853 def togglefolded(self, item=None, foldparent=False):
853 def togglefolded(self, item=None, foldparent=False):
854 "toggle folded flag of specified item (defaults to currently selected)"
854 "toggle folded flag of specified item (defaults to currently selected)"
855 if item is None:
855 if item is None:
856 item = self.currentselecteditem
856 item = self.currentselecteditem
857 if foldparent or (isinstance(item, uiheader) and item.neverunfolded):
857 if foldparent or (isinstance(item, uiheader) and item.neverunfolded):
858 if not isinstance(item, uiheader):
858 if not isinstance(item, uiheader):
859 # we need to select the parent item in this case
859 # we need to select the parent item in this case
860 self.currentselecteditem = item = item.parentitem()
860 self.currentselecteditem = item = item.parentitem()
861 elif item.neverunfolded:
861 elif item.neverunfolded:
862 item.neverunfolded = False
862 item.neverunfolded = False
863
863
864 # also fold any foldable children of the parent/current item
864 # also fold any foldable children of the parent/current item
865 if isinstance(item, uiheader): # the original or 'new' item
865 if isinstance(item, uiheader): # the original or 'new' item
866 for child in item.allchildren():
866 for child in item.allchildren():
867 child.folded = not item.folded
867 child.folded = not item.folded
868
868
869 if isinstance(item, (uiheader, uihunk)):
869 if isinstance(item, (uiheader, uihunk)):
870 item.folded = not item.folded
870 item.folded = not item.folded
871
871
872
872
873 def alignstring(self, instr, window):
873 def alignstring(self, instr, window):
874 """
874 """
875 add whitespace to the end of a string in order to make it fill
875 add whitespace to the end of a string in order to make it fill
876 the screen in the x direction. the current cursor position is
876 the screen in the x direction. the current cursor position is
877 taken into account when making this calculation. the string can span
877 taken into account when making this calculation. the string can span
878 multiple lines.
878 multiple lines.
879
879
880 """
880 """
881 y, xstart = window.getyx()
881 y, xstart = window.getyx()
882 width = self.xscreensize
882 width = self.xscreensize
883 # turn tabs into spaces
883 # turn tabs into spaces
884 instr = instr.expandtabs(4)
884 instr = instr.expandtabs(4)
885 strwidth = encoding.colwidth(instr)
885 strwidth = encoding.colwidth(instr)
886 numspaces = (width - ((strwidth + xstart) % width) - 1)
886 numspaces = (width - ((strwidth + xstart) % width) - 1)
887 return instr + " " * numspaces + "\n"
887 return instr + " " * numspaces + "\n"
888
888
889 def printstring(self, window, text, fgcolor=None, bgcolor=None, pair=None,
889 def printstring(self, window, text, fgcolor=None, bgcolor=None, pair=None,
890 pairname=None, attrlist=None, towin=True, align=True, showwhtspc=False):
890 pairname=None, attrlist=None, towin=True, align=True, showwhtspc=False):
891 """
891 """
892 print the string, text, with the specified colors and attributes, to
892 print the string, text, with the specified colors and attributes, to
893 the specified curses window object.
893 the specified curses window object.
894
894
895 the foreground and background colors are of the form
895 the foreground and background colors are of the form
896 curses.color_xxxx, where xxxx is one of: [black, blue, cyan, green,
896 curses.color_xxxx, where xxxx is one of: [black, blue, cyan, green,
897 magenta, red, white, yellow]. if pairname is provided, a color
897 magenta, red, white, yellow]. if pairname is provided, a color
898 pair will be looked up in the self.colorpairnames dictionary.
898 pair will be looked up in the self.colorpairnames dictionary.
899
899
900 attrlist is a list containing text attributes in the form of
900 attrlist is a list containing text attributes in the form of
901 curses.a_xxxx, where xxxx can be: [bold, dim, normal, standout,
901 curses.a_xxxx, where xxxx can be: [bold, dim, normal, standout,
902 underline].
902 underline].
903
903
904 if align == True, whitespace is added to the printed string such that
904 if align == True, whitespace is added to the printed string such that
905 the string stretches to the right border of the window.
905 the string stretches to the right border of the window.
906
906
907 if showwhtspc == True, trailing whitespace of a string is highlighted.
907 if showwhtspc == True, trailing whitespace of a string is highlighted.
908
908
909 """
909 """
910 # preprocess the text, converting tabs to spaces
910 # preprocess the text, converting tabs to spaces
911 text = text.expandtabs(4)
911 text = text.expandtabs(4)
912 # strip \n, and convert control characters to ^[char] representation
912 # strip \n, and convert control characters to ^[char] representation
913 text = re.sub(r'[\x00-\x08\x0a-\x1f]',
913 text = re.sub(r'[\x00-\x08\x0a-\x1f]',
914 lambda m:'^' + chr(ord(m.group()) + 64), text.strip('\n'))
914 lambda m:'^' + chr(ord(m.group()) + 64), text.strip('\n'))
915
915
916 if pair is not None:
916 if pair is not None:
917 colorpair = pair
917 colorpair = pair
918 elif pairname is not None:
918 elif pairname is not None:
919 colorpair = self.colorpairnames[pairname]
919 colorpair = self.colorpairnames[pairname]
920 else:
920 else:
921 if fgcolor is None:
921 if fgcolor is None:
922 fgcolor = -1
922 fgcolor = -1
923 if bgcolor is None:
923 if bgcolor is None:
924 bgcolor = -1
924 bgcolor = -1
925 if (fgcolor, bgcolor) in self.colorpairs:
925 if (fgcolor, bgcolor) in self.colorpairs:
926 colorpair = self.colorpairs[(fgcolor, bgcolor)]
926 colorpair = self.colorpairs[(fgcolor, bgcolor)]
927 else:
927 else:
928 colorpair = self.getcolorpair(fgcolor, bgcolor)
928 colorpair = self.getcolorpair(fgcolor, bgcolor)
929 # add attributes if possible
929 # add attributes if possible
930 if attrlist is None:
930 if attrlist is None:
931 attrlist = []
931 attrlist = []
932 if colorpair < 256:
932 if colorpair < 256:
933 # then it is safe to apply all attributes
933 # then it is safe to apply all attributes
934 for textattr in attrlist:
934 for textattr in attrlist:
935 colorpair |= textattr
935 colorpair |= textattr
936 else:
936 else:
937 # just apply a select few (safe?) attributes
937 # just apply a select few (safe?) attributes
938 for textattr in (curses.A_UNDERLINE, curses.A_BOLD):
938 for textattr in (curses.A_UNDERLINE, curses.A_BOLD):
939 if textattr in attrlist:
939 if textattr in attrlist:
940 colorpair |= textattr
940 colorpair |= textattr
941
941
942 y, xstart = self.chunkpad.getyx()
942 y, xstart = self.chunkpad.getyx()
943 t = "" # variable for counting lines printed
943 t = "" # variable for counting lines printed
944 # if requested, show trailing whitespace
944 # if requested, show trailing whitespace
945 if showwhtspc:
945 if showwhtspc:
946 origlen = len(text)
946 origlen = len(text)
947 text = text.rstrip(' \n') # tabs have already been expanded
947 text = text.rstrip(' \n') # tabs have already been expanded
948 strippedlen = len(text)
948 strippedlen = len(text)
949 numtrailingspaces = origlen - strippedlen
949 numtrailingspaces = origlen - strippedlen
950
950
951 if towin:
951 if towin:
952 window.addstr(text, colorpair)
952 window.addstr(text, colorpair)
953 t += text
953 t += text
954
954
955 if showwhtspc:
955 if showwhtspc:
956 wscolorpair = colorpair | curses.A_REVERSE
956 wscolorpair = colorpair | curses.A_REVERSE
957 if towin:
957 if towin:
958 for i in range(numtrailingspaces):
958 for i in range(numtrailingspaces):
959 window.addch(curses.ACS_CKBOARD, wscolorpair)
959 window.addch(curses.ACS_CKBOARD, wscolorpair)
960 t += " " * numtrailingspaces
960 t += " " * numtrailingspaces
961
961
962 if align:
962 if align:
963 if towin:
963 if towin:
964 extrawhitespace = self.alignstring("", window)
964 extrawhitespace = self.alignstring("", window)
965 window.addstr(extrawhitespace, colorpair)
965 window.addstr(extrawhitespace, colorpair)
966 else:
966 else:
967 # need to use t, since the x position hasn't incremented
967 # need to use t, since the x position hasn't incremented
968 extrawhitespace = self.alignstring(t, window)
968 extrawhitespace = self.alignstring(t, window)
969 t += extrawhitespace
969 t += extrawhitespace
970
970
971 # is reset to 0 at the beginning of printitem()
971 # is reset to 0 at the beginning of printitem()
972
972
973 linesprinted = (xstart + len(t)) / self.xscreensize
973 linesprinted = (xstart + len(t)) / self.xscreensize
974 self.linesprintedtopadsofar += linesprinted
974 self.linesprintedtopadsofar += linesprinted
975 return t
975 return t
976
976
977 def updatescreen(self):
977 def updatescreen(self):
978 self.statuswin.erase()
978 self.statuswin.erase()
979 self.chunkpad.erase()
979 self.chunkpad.erase()
980
980
981 printstring = self.printstring
981 printstring = self.printstring
982
982
983 # print out the status lines at the top
983 # print out the status lines at the top
984 try:
984 try:
985 if self.errorstr is not None:
985 if self.errorstr is not None:
986 printstring(self.statuswin, self.errorstr, pairname='legend')
986 printstring(self.statuswin, self.errorstr, pairname='legend')
987 printstring(self.statuswin, 'Press any key to continue',
987 printstring(self.statuswin, 'Press any key to continue',
988 pairname='legend')
988 pairname='legend')
989 self.statuswin.refresh()
989 self.statuswin.refresh()
990 return
990 return
991 printstring(self.statuswin,
991 printstring(self.statuswin,
992 "SELECT CHUNKS: (j/k/up/dn/pgup/pgdn) move cursor; "
992 "SELECT CHUNKS: (j/k/up/dn/pgup/pgdn) move cursor; "
993 "(space/A) toggle hunk/all; (e)dit hunk;",
993 "(space/A) toggle hunk/all; (e)dit hunk;",
994 pairname="legend")
994 pairname="legend")
995 printstring(self.statuswin,
995 printstring(self.statuswin,
996 " (f)old/unfold; (c)onfirm applied; (q)uit; (?) help "
996 " (f)old/unfold; (c)onfirm applied; (q)uit; (?) help "
997 "| [X]=hunk applied **=folded",
997 "| [X]=hunk applied **=folded",
998 pairname="legend")
998 pairname="legend")
999 except curses.error:
999 except curses.error:
1000 pass
1000 pass
1001
1001
1002 # print out the patch in the remaining part of the window
1002 # print out the patch in the remaining part of the window
1003 try:
1003 try:
1004 self.printitem()
1004 self.printitem()
1005 self.updatescroll()
1005 self.updatescroll()
1006 self.chunkpad.refresh(self.firstlineofpadtoprint, 0,
1006 self.chunkpad.refresh(self.firstlineofpadtoprint, 0,
1007 self.numstatuslines, 0,
1007 self.numstatuslines, 0,
1008 self.yscreensize + 1 - self.numstatuslines,
1008 self.yscreensize + 1 - self.numstatuslines,
1009 self.xscreensize)
1009 self.xscreensize)
1010 except curses.error:
1010 except curses.error:
1011 pass
1011 pass
1012
1012
1013 # refresh([pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol])
1013 # refresh([pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol])
1014 self.statuswin.refresh()
1014 self.statuswin.refresh()
1015
1015
1016 def getstatusprefixstring(self, item):
1016 def getstatusprefixstring(self, item):
1017 """
1017 """
1018 create a string to prefix a line with which indicates whether 'item'
1018 create a string to prefix a line with which indicates whether 'item'
1019 is applied and/or folded.
1019 is applied and/or folded.
1020
1020
1021 """
1021 """
1022 # create checkbox string
1022 # create checkbox string
1023 if item.applied:
1023 if item.applied:
1024 if not isinstance(item, uihunkline) and item.partial:
1024 if not isinstance(item, uihunkline) and item.partial:
1025 checkbox = "[~]"
1025 checkbox = "[~]"
1026 else:
1026 else:
1027 checkbox = "[x]"
1027 checkbox = "[x]"
1028 else:
1028 else:
1029 checkbox = "[ ]"
1029 checkbox = "[ ]"
1030
1030
1031 try:
1031 try:
1032 if item.folded:
1032 if item.folded:
1033 checkbox += "**"
1033 checkbox += "**"
1034 if isinstance(item, uiheader):
1034 if isinstance(item, uiheader):
1035 # one of "m", "a", or "d" (modified, added, deleted)
1035 # one of "m", "a", or "d" (modified, added, deleted)
1036 filestatus = item.changetype
1036 filestatus = item.changetype
1037
1037
1038 checkbox += filestatus + " "
1038 checkbox += filestatus + " "
1039 else:
1039 else:
1040 checkbox += " "
1040 checkbox += " "
1041 if isinstance(item, uiheader):
1041 if isinstance(item, uiheader):
1042 # add two more spaces for headers
1042 # add two more spaces for headers
1043 checkbox += " "
1043 checkbox += " "
1044 except AttributeError: # not foldable
1044 except AttributeError: # not foldable
1045 checkbox += " "
1045 checkbox += " "
1046
1046
1047 return checkbox
1047 return checkbox
1048
1048
1049 def printheader(self, header, selected=False, towin=True,
1049 def printheader(self, header, selected=False, towin=True,
1050 ignorefolding=False):
1050 ignorefolding=False):
1051 """
1051 """
1052 print the header to the pad. if countlines is True, don't print
1052 print the header to the pad. if countlines is True, don't print
1053 anything, but just count the number of lines which would be printed.
1053 anything, but just count the number of lines which would be printed.
1054
1054
1055 """
1055 """
1056 outstr = ""
1056 outstr = ""
1057 text = header.prettystr()
1057 text = header.prettystr()
1058 chunkindex = self.chunklist.index(header)
1058 chunkindex = self.chunklist.index(header)
1059
1059
1060 if chunkindex != 0 and not header.folded:
1060 if chunkindex != 0 and not header.folded:
1061 # add separating line before headers
1061 # add separating line before headers
1062 outstr += self.printstring(self.chunkpad, '_' * self.xscreensize,
1062 outstr += self.printstring(self.chunkpad, '_' * self.xscreensize,
1063 towin=towin, align=False)
1063 towin=towin, align=False)
1064 # select color-pair based on if the header is selected
1064 # select color-pair based on if the header is selected
1065 colorpair = self.getcolorpair(name=selected and "selected" or "normal",
1065 colorpair = self.getcolorpair(name=selected and "selected" or "normal",
1066 attrlist=[curses.A_BOLD])
1066 attrlist=[curses.A_BOLD])
1067
1067
1068 # print out each line of the chunk, expanding it to screen width
1068 # print out each line of the chunk, expanding it to screen width
1069
1069
1070 # number of characters to indent lines on this level by
1070 # number of characters to indent lines on this level by
1071 indentnumchars = 0
1071 indentnumchars = 0
1072 checkbox = self.getstatusprefixstring(header)
1072 checkbox = self.getstatusprefixstring(header)
1073 if not header.folded or ignorefolding:
1073 if not header.folded or ignorefolding:
1074 textlist = text.split("\n")
1074 textlist = text.split("\n")
1075 linestr = checkbox + textlist[0]
1075 linestr = checkbox + textlist[0]
1076 else:
1076 else:
1077 linestr = checkbox + header.filename()
1077 linestr = checkbox + header.filename()
1078 outstr += self.printstring(self.chunkpad, linestr, pair=colorpair,
1078 outstr += self.printstring(self.chunkpad, linestr, pair=colorpair,
1079 towin=towin)
1079 towin=towin)
1080 if not header.folded or ignorefolding:
1080 if not header.folded or ignorefolding:
1081 if len(textlist) > 1:
1081 if len(textlist) > 1:
1082 for line in textlist[1:]:
1082 for line in textlist[1:]:
1083 linestr = " "*(indentnumchars + len(checkbox)) + line
1083 linestr = " "*(indentnumchars + len(checkbox)) + line
1084 outstr += self.printstring(self.chunkpad, linestr,
1084 outstr += self.printstring(self.chunkpad, linestr,
1085 pair=colorpair, towin=towin)
1085 pair=colorpair, towin=towin)
1086
1086
1087 return outstr
1087 return outstr
1088
1088
1089 def printhunklinesbefore(self, hunk, selected=False, towin=True,
1089 def printhunklinesbefore(self, hunk, selected=False, towin=True,
1090 ignorefolding=False):
1090 ignorefolding=False):
1091 "includes start/end line indicator"
1091 "includes start/end line indicator"
1092 outstr = ""
1092 outstr = ""
1093 # where hunk is in list of siblings
1093 # where hunk is in list of siblings
1094 hunkindex = hunk.header.hunks.index(hunk)
1094 hunkindex = hunk.header.hunks.index(hunk)
1095
1095
1096 if hunkindex != 0:
1096 if hunkindex != 0:
1097 # add separating line before headers
1097 # add separating line before headers
1098 outstr += self.printstring(self.chunkpad, ' '*self.xscreensize,
1098 outstr += self.printstring(self.chunkpad, ' '*self.xscreensize,
1099 towin=towin, align=False)
1099 towin=towin, align=False)
1100
1100
1101 colorpair = self.getcolorpair(name=selected and "selected" or "normal",
1101 colorpair = self.getcolorpair(name=selected and "selected" or "normal",
1102 attrlist=[curses.A_BOLD])
1102 attrlist=[curses.A_BOLD])
1103
1103
1104 # print out from-to line with checkbox
1104 # print out from-to line with checkbox
1105 checkbox = self.getstatusprefixstring(hunk)
1105 checkbox = self.getstatusprefixstring(hunk)
1106
1106
1107 lineprefix = " "*self.hunkindentnumchars + checkbox
1107 lineprefix = " "*self.hunkindentnumchars + checkbox
1108 frtoline = " " + hunk.getfromtoline().strip("\n")
1108 frtoline = " " + hunk.getfromtoline().strip("\n")
1109
1109
1110
1110
1111 outstr += self.printstring(self.chunkpad, lineprefix, towin=towin,
1111 outstr += self.printstring(self.chunkpad, lineprefix, towin=towin,
1112 align=False) # add uncolored checkbox/indent
1112 align=False) # add uncolored checkbox/indent
1113 outstr += self.printstring(self.chunkpad, frtoline, pair=colorpair,
1113 outstr += self.printstring(self.chunkpad, frtoline, pair=colorpair,
1114 towin=towin)
1114 towin=towin)
1115
1115
1116 if hunk.folded and not ignorefolding:
1116 if hunk.folded and not ignorefolding:
1117 # skip remainder of output
1117 # skip remainder of output
1118 return outstr
1118 return outstr
1119
1119
1120 # print out lines of the chunk preceeding changed-lines
1120 # print out lines of the chunk preceeding changed-lines
1121 for line in hunk.before:
1121 for line in hunk.before:
1122 linestr = " "*(self.hunklineindentnumchars + len(checkbox)) + line
1122 linestr = " "*(self.hunklineindentnumchars + len(checkbox)) + line
1123 outstr += self.printstring(self.chunkpad, linestr, towin=towin)
1123 outstr += self.printstring(self.chunkpad, linestr, towin=towin)
1124
1124
1125 return outstr
1125 return outstr
1126
1126
1127 def printhunklinesafter(self, hunk, towin=True, ignorefolding=False):
1127 def printhunklinesafter(self, hunk, towin=True, ignorefolding=False):
1128 outstr = ""
1128 outstr = ""
1129 if hunk.folded and not ignorefolding:
1129 if hunk.folded and not ignorefolding:
1130 return outstr
1130 return outstr
1131
1131
1132 # a bit superfluous, but to avoid hard-coding indent amount
1132 # a bit superfluous, but to avoid hard-coding indent amount
1133 checkbox = self.getstatusprefixstring(hunk)
1133 checkbox = self.getstatusprefixstring(hunk)
1134 for line in hunk.after:
1134 for line in hunk.after:
1135 linestr = " "*(self.hunklineindentnumchars + len(checkbox)) + line
1135 linestr = " "*(self.hunklineindentnumchars + len(checkbox)) + line
1136 outstr += self.printstring(self.chunkpad, linestr, towin=towin)
1136 outstr += self.printstring(self.chunkpad, linestr, towin=towin)
1137
1137
1138 return outstr
1138 return outstr
1139
1139
1140 def printhunkchangedline(self, hunkline, selected=False, towin=True):
1140 def printhunkchangedline(self, hunkline, selected=False, towin=True):
1141 outstr = ""
1141 outstr = ""
1142 checkbox = self.getstatusprefixstring(hunkline)
1142 checkbox = self.getstatusprefixstring(hunkline)
1143
1143
1144 linestr = hunkline.prettystr().strip("\n")
1144 linestr = hunkline.prettystr().strip("\n")
1145
1145
1146 # select color-pair based on whether line is an addition/removal
1146 # select color-pair based on whether line is an addition/removal
1147 if selected:
1147 if selected:
1148 colorpair = self.getcolorpair(name="selected")
1148 colorpair = self.getcolorpair(name="selected")
1149 elif linestr.startswith("+"):
1149 elif linestr.startswith("+"):
1150 colorpair = self.getcolorpair(name="addition")
1150 colorpair = self.getcolorpair(name="addition")
1151 elif linestr.startswith("-"):
1151 elif linestr.startswith("-"):
1152 colorpair = self.getcolorpair(name="deletion")
1152 colorpair = self.getcolorpair(name="deletion")
1153 elif linestr.startswith("\\"):
1153 elif linestr.startswith("\\"):
1154 colorpair = self.getcolorpair(name="normal")
1154 colorpair = self.getcolorpair(name="normal")
1155
1155
1156 lineprefix = " "*self.hunklineindentnumchars + checkbox
1156 lineprefix = " "*self.hunklineindentnumchars + checkbox
1157 outstr += self.printstring(self.chunkpad, lineprefix, towin=towin,
1157 outstr += self.printstring(self.chunkpad, lineprefix, towin=towin,
1158 align=False) # add uncolored checkbox/indent
1158 align=False) # add uncolored checkbox/indent
1159 outstr += self.printstring(self.chunkpad, linestr, pair=colorpair,
1159 outstr += self.printstring(self.chunkpad, linestr, pair=colorpair,
1160 towin=towin, showwhtspc=True)
1160 towin=towin, showwhtspc=True)
1161 return outstr
1161 return outstr
1162
1162
1163 def printitem(self, item=None, ignorefolding=False, recursechildren=True,
1163 def printitem(self, item=None, ignorefolding=False, recursechildren=True,
1164 towin=True):
1164 towin=True):
1165 """
1165 """
1166 use __printitem() to print the the specified item.applied.
1166 use __printitem() to print the the specified item.applied.
1167 if item is not specified, then print the entire patch.
1167 if item is not specified, then print the entire patch.
1168 (hiding folded elements, etc. -- see __printitem() docstring)
1168 (hiding folded elements, etc. -- see __printitem() docstring)
1169 """
1169 """
1170 if item is None:
1170 if item is None:
1171 item = self.headerlist
1171 item = self.headerlist
1172 if recursechildren:
1172 if recursechildren:
1173 self.linesprintedtopadsofar = 0
1173 self.linesprintedtopadsofar = 0
1174
1174
1175 outstr = []
1175 outstr = []
1176 self.__printitem(item, ignorefolding, recursechildren, outstr,
1176 self.__printitem(item, ignorefolding, recursechildren, outstr,
1177 towin=towin)
1177 towin=towin)
1178 return ''.join(outstr)
1178 return ''.join(outstr)
1179
1179
1180 def outofdisplayedarea(self):
1180 def outofdisplayedarea(self):
1181 y, _ = self.chunkpad.getyx() # cursor location
1181 y, _ = self.chunkpad.getyx() # cursor location
1182 # * 2 here works but an optimization would be the max number of
1182 # * 2 here works but an optimization would be the max number of
1183 # consecutive non selectable lines
1183 # consecutive non selectable lines
1184 # i.e the max number of context line for any hunk in the patch
1184 # i.e the max number of context line for any hunk in the patch
1185 miny = min(0, self.firstlineofpadtoprint - self.yscreensize)
1185 miny = min(0, self.firstlineofpadtoprint - self.yscreensize)
1186 maxy = self.firstlineofpadtoprint + self.yscreensize * 2
1186 maxy = self.firstlineofpadtoprint + self.yscreensize * 2
1187 return y < miny or y > maxy
1187 return y < miny or y > maxy
1188
1188
1189 def handleselection(self, item, recursechildren):
1189 def handleselection(self, item, recursechildren):
1190 selected = (item is self.currentselecteditem)
1190 selected = (item is self.currentselecteditem)
1191 if selected and recursechildren:
1191 if selected and recursechildren:
1192 # assumes line numbering starting from line 0
1192 # assumes line numbering starting from line 0
1193 self.selecteditemstartline = self.linesprintedtopadsofar
1193 self.selecteditemstartline = self.linesprintedtopadsofar
1194 selecteditemlines = self.getnumlinesdisplayed(item,
1194 selecteditemlines = self.getnumlinesdisplayed(item,
1195 recursechildren=False)
1195 recursechildren=False)
1196 self.selecteditemendline = (self.selecteditemstartline +
1196 self.selecteditemendline = (self.selecteditemstartline +
1197 selecteditemlines - 1)
1197 selecteditemlines - 1)
1198 return selected
1198 return selected
1199
1199
1200 def __printitem(self, item, ignorefolding, recursechildren, outstr,
1200 def __printitem(self, item, ignorefolding, recursechildren, outstr,
1201 towin=True):
1201 towin=True):
1202 """
1202 """
1203 recursive method for printing out patch/header/hunk/hunk-line data to
1203 recursive method for printing out patch/header/hunk/hunk-line data to
1204 screen. also returns a string with all of the content of the displayed
1204 screen. also returns a string with all of the content of the displayed
1205 patch (not including coloring, etc.).
1205 patch (not including coloring, etc.).
1206
1206
1207 if ignorefolding is True, then folded items are printed out.
1207 if ignorefolding is True, then folded items are printed out.
1208
1208
1209 if recursechildren is False, then only print the item without its
1209 if recursechildren is False, then only print the item without its
1210 child items.
1210 child items.
1211
1211
1212 """
1212 """
1213 if towin and self.outofdisplayedarea():
1213 if towin and self.outofdisplayedarea():
1214 return
1214 return
1215
1215
1216 selected = self.handleselection(item, recursechildren)
1216 selected = self.handleselection(item, recursechildren)
1217
1217
1218 # patch object is a list of headers
1218 # patch object is a list of headers
1219 if isinstance(item, patch):
1219 if isinstance(item, patch):
1220 if recursechildren:
1220 if recursechildren:
1221 for hdr in item:
1221 for hdr in item:
1222 self.__printitem(hdr, ignorefolding,
1222 self.__printitem(hdr, ignorefolding,
1223 recursechildren, outstr, towin)
1223 recursechildren, outstr, towin)
1224 # todo: eliminate all isinstance() calls
1224 # todo: eliminate all isinstance() calls
1225 if isinstance(item, uiheader):
1225 if isinstance(item, uiheader):
1226 outstr.append(self.printheader(item, selected, towin=towin,
1226 outstr.append(self.printheader(item, selected, towin=towin,
1227 ignorefolding=ignorefolding))
1227 ignorefolding=ignorefolding))
1228 if recursechildren:
1228 if recursechildren:
1229 for hnk in item.hunks:
1229 for hnk in item.hunks:
1230 self.__printitem(hnk, ignorefolding,
1230 self.__printitem(hnk, ignorefolding,
1231 recursechildren, outstr, towin)
1231 recursechildren, outstr, towin)
1232 elif (isinstance(item, uihunk) and
1232 elif (isinstance(item, uihunk) and
1233 ((not item.header.folded) or ignorefolding)):
1233 ((not item.header.folded) or ignorefolding)):
1234 # print the hunk data which comes before the changed-lines
1234 # print the hunk data which comes before the changed-lines
1235 outstr.append(self.printhunklinesbefore(item, selected, towin=towin,
1235 outstr.append(self.printhunklinesbefore(item, selected, towin=towin,
1236 ignorefolding=ignorefolding))
1236 ignorefolding=ignorefolding))
1237 if recursechildren:
1237 if recursechildren:
1238 for l in item.changedlines:
1238 for l in item.changedlines:
1239 self.__printitem(l, ignorefolding,
1239 self.__printitem(l, ignorefolding,
1240 recursechildren, outstr, towin)
1240 recursechildren, outstr, towin)
1241 outstr.append(self.printhunklinesafter(item, towin=towin,
1241 outstr.append(self.printhunklinesafter(item, towin=towin,
1242 ignorefolding=ignorefolding))
1242 ignorefolding=ignorefolding))
1243 elif (isinstance(item, uihunkline) and
1243 elif (isinstance(item, uihunkline) and
1244 ((not item.hunk.folded) or ignorefolding)):
1244 ((not item.hunk.folded) or ignorefolding)):
1245 outstr.append(self.printhunkchangedline(item, selected,
1245 outstr.append(self.printhunkchangedline(item, selected,
1246 towin=towin))
1246 towin=towin))
1247
1247
1248 return outstr
1248 return outstr
1249
1249
1250 def getnumlinesdisplayed(self, item=None, ignorefolding=False,
1250 def getnumlinesdisplayed(self, item=None, ignorefolding=False,
1251 recursechildren=True):
1251 recursechildren=True):
1252 """
1252 """
1253 return the number of lines which would be displayed if the item were
1253 return the number of lines which would be displayed if the item were
1254 to be printed to the display. the item will not be printed to the
1254 to be printed to the display. the item will not be printed to the
1255 display (pad).
1255 display (pad).
1256 if no item is given, assume the entire patch.
1256 if no item is given, assume the entire patch.
1257 if ignorefolding is True, folded items will be unfolded when counting
1257 if ignorefolding is True, folded items will be unfolded when counting
1258 the number of lines.
1258 the number of lines.
1259
1259
1260 """
1260 """
1261 # temporarily disable printing to windows by printstring
1261 # temporarily disable printing to windows by printstring
1262 patchdisplaystring = self.printitem(item, ignorefolding,
1262 patchdisplaystring = self.printitem(item, ignorefolding,
1263 recursechildren, towin=False)
1263 recursechildren, towin=False)
1264 numlines = len(patchdisplaystring) / self.xscreensize
1264 numlines = len(patchdisplaystring) / self.xscreensize
1265 return numlines
1265 return numlines
1266
1266
1267 def sigwinchhandler(self, n, frame):
1267 def sigwinchhandler(self, n, frame):
1268 "handle window resizing"
1268 "handle window resizing"
1269 try:
1269 try:
1270 curses.endwin()
1270 curses.endwin()
1271 self.yscreensize, self.xscreensize = gethw()
1271 self.yscreensize, self.xscreensize = gethw()
1272 self.statuswin.resize(self.numstatuslines, self.xscreensize)
1272 self.statuswin.resize(self.numstatuslines, self.xscreensize)
1273 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1273 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1274 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1274 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1275 # todo: try to resize commit message window if possible
1275 # todo: try to resize commit message window if possible
1276 except curses.error:
1276 except curses.error:
1277 pass
1277 pass
1278
1278
1279 def getcolorpair(self, fgcolor=None, bgcolor=None, name=None,
1279 def getcolorpair(self, fgcolor=None, bgcolor=None, name=None,
1280 attrlist=None):
1280 attrlist=None):
1281 """
1281 """
1282 get a curses color pair, adding it to self.colorpairs if it is not
1282 get a curses color pair, adding it to self.colorpairs if it is not
1283 already defined. an optional string, name, can be passed as a shortcut
1283 already defined. an optional string, name, can be passed as a shortcut
1284 for referring to the color-pair. by default, if no arguments are
1284 for referring to the color-pair. by default, if no arguments are
1285 specified, the white foreground / black background color-pair is
1285 specified, the white foreground / black background color-pair is
1286 returned.
1286 returned.
1287
1287
1288 it is expected that this function will be used exclusively for
1288 it is expected that this function will be used exclusively for
1289 initializing color pairs, and not curses.init_pair().
1289 initializing color pairs, and not curses.init_pair().
1290
1290
1291 attrlist is used to 'flavor' the returned color-pair. this information
1291 attrlist is used to 'flavor' the returned color-pair. this information
1292 is not stored in self.colorpairs. it contains attribute values like
1292 is not stored in self.colorpairs. it contains attribute values like
1293 curses.A_BOLD.
1293 curses.A_BOLD.
1294
1294
1295 """
1295 """
1296 if (name is not None) and name in self.colorpairnames:
1296 if (name is not None) and name in self.colorpairnames:
1297 # then get the associated color pair and return it
1297 # then get the associated color pair and return it
1298 colorpair = self.colorpairnames[name]
1298 colorpair = self.colorpairnames[name]
1299 else:
1299 else:
1300 if fgcolor is None:
1300 if fgcolor is None:
1301 fgcolor = -1
1301 fgcolor = -1
1302 if bgcolor is None:
1302 if bgcolor is None:
1303 bgcolor = -1
1303 bgcolor = -1
1304 if (fgcolor, bgcolor) in self.colorpairs:
1304 if (fgcolor, bgcolor) in self.colorpairs:
1305 colorpair = self.colorpairs[(fgcolor, bgcolor)]
1305 colorpair = self.colorpairs[(fgcolor, bgcolor)]
1306 else:
1306 else:
1307 pairindex = len(self.colorpairs) + 1
1307 pairindex = len(self.colorpairs) + 1
1308 curses.init_pair(pairindex, fgcolor, bgcolor)
1308 curses.init_pair(pairindex, fgcolor, bgcolor)
1309 colorpair = self.colorpairs[(fgcolor, bgcolor)] = (
1309 colorpair = self.colorpairs[(fgcolor, bgcolor)] = (
1310 curses.color_pair(pairindex))
1310 curses.color_pair(pairindex))
1311 if name is not None:
1311 if name is not None:
1312 self.colorpairnames[name] = curses.color_pair(pairindex)
1312 self.colorpairnames[name] = curses.color_pair(pairindex)
1313
1313
1314 # add attributes if possible
1314 # add attributes if possible
1315 if attrlist is None:
1315 if attrlist is None:
1316 attrlist = []
1316 attrlist = []
1317 if colorpair < 256:
1317 if colorpair < 256:
1318 # then it is safe to apply all attributes
1318 # then it is safe to apply all attributes
1319 for textattr in attrlist:
1319 for textattr in attrlist:
1320 colorpair |= textattr
1320 colorpair |= textattr
1321 else:
1321 else:
1322 # just apply a select few (safe?) attributes
1322 # just apply a select few (safe?) attributes
1323 for textattrib in (curses.A_UNDERLINE, curses.A_BOLD):
1323 for textattrib in (curses.A_UNDERLINE, curses.A_BOLD):
1324 if textattrib in attrlist:
1324 if textattrib in attrlist:
1325 colorpair |= textattrib
1325 colorpair |= textattrib
1326 return colorpair
1326 return colorpair
1327
1327
1328 def initcolorpair(self, *args, **kwargs):
1328 def initcolorpair(self, *args, **kwargs):
1329 "same as getcolorpair."
1329 "same as getcolorpair."
1330 self.getcolorpair(*args, **kwargs)
1330 self.getcolorpair(*args, **kwargs)
1331
1331
1332 def helpwindow(self):
1332 def helpwindow(self):
1333 "print a help window to the screen. exit after any keypress."
1333 "print a help window to the screen. exit after any keypress."
1334 helptext = """ [press any key to return to the patch-display]
1334 helptext = """ [press any key to return to the patch-display]
1335
1335
1336 crecord allows you to interactively choose among the changes you have made,
1336 crecord allows you to interactively choose among the changes you have made,
1337 and confirm only those changes you select for further processing by the command
1337 and confirm only those changes you select for further processing by the command
1338 you are running (commit/shelve/revert), after confirming the selected
1338 you are running (commit/shelve/revert), after confirming the selected
1339 changes, the unselected changes are still present in your working copy, so you
1339 changes, the unselected changes are still present in your working copy, so you
1340 can use crecord multiple times to split large changes into smaller changesets.
1340 can use crecord multiple times to split large changes into smaller changesets.
1341 the following are valid keystrokes:
1341 the following are valid keystrokes:
1342
1342
1343 [space] : (un-)select item ([~]/[x] = partly/fully applied)
1343 [space] : (un-)select item ([~]/[x] = partly/fully applied)
1344 a : (un-)select all items
1344 a : (un-)select all items
1345 up/down-arrow [k/j] : go to previous/next unfolded item
1345 up/down-arrow [k/j] : go to previous/next unfolded item
1346 pgup/pgdn [K/J] : go to previous/next item of same type
1346 pgup/pgdn [K/J] : go to previous/next item of same type
1347 right/left-arrow [l/h] : go to child item / parent item
1347 right/left-arrow [l/h] : go to child item / parent item
1348 shift-left-arrow [H] : go to parent header / fold selected header
1348 shift-left-arrow [H] : go to parent header / fold selected header
1349 f : fold / unfold item, hiding/revealing its children
1349 f : fold / unfold item, hiding/revealing its children
1350 F : fold / unfold parent item and all of its ancestors
1350 F : fold / unfold parent item and all of its ancestors
1351 m : edit / resume editing the commit message
1351 m : edit / resume editing the commit message
1352 e : edit the currently selected hunk
1352 e : edit the currently selected hunk
1353 a : toggle amend mode (hg rev >= 2.2)
1353 a : toggle amend mode (hg rev >= 2.2)
1354 c : confirm selected changes
1354 c : confirm selected changes
1355 r : review/edit and confirm selected changes
1355 r : review/edit and confirm selected changes
1356 q : quit without confirming (no changes will be made)
1356 q : quit without confirming (no changes will be made)
1357 ? : help (what you're currently reading)"""
1357 ? : help (what you're currently reading)"""
1358
1358
1359 helpwin = curses.newwin(self.yscreensize, 0, 0, 0)
1359 helpwin = curses.newwin(self.yscreensize, 0, 0, 0)
1360 helplines = helptext.split("\n")
1360 helplines = helptext.split("\n")
1361 helplines = helplines + [" "]*(
1361 helplines = helplines + [" "]*(
1362 self.yscreensize - self.numstatuslines - len(helplines) - 1)
1362 self.yscreensize - self.numstatuslines - len(helplines) - 1)
1363 try:
1363 try:
1364 for line in helplines:
1364 for line in helplines:
1365 self.printstring(helpwin, line, pairname="legend")
1365 self.printstring(helpwin, line, pairname="legend")
1366 except curses.error:
1366 except curses.error:
1367 pass
1367 pass
1368 helpwin.refresh()
1368 helpwin.refresh()
1369 try:
1369 try:
1370 helpwin.getkey()
1370 helpwin.getkey()
1371 except curses.error:
1371 except curses.error:
1372 pass
1372 pass
1373
1373
1374 def confirmationwindow(self, windowtext):
1374 def confirmationwindow(self, windowtext):
1375 "display an informational window, then wait for and return a keypress."
1375 "display an informational window, then wait for and return a keypress."
1376
1376
1377 confirmwin = curses.newwin(self.yscreensize, 0, 0, 0)
1377 confirmwin = curses.newwin(self.yscreensize, 0, 0, 0)
1378 try:
1378 try:
1379 lines = windowtext.split("\n")
1379 lines = windowtext.split("\n")
1380 for line in lines:
1380 for line in lines:
1381 self.printstring(confirmwin, line, pairname="selected")
1381 self.printstring(confirmwin, line, pairname="selected")
1382 except curses.error:
1382 except curses.error:
1383 pass
1383 pass
1384 self.stdscr.refresh()
1384 self.stdscr.refresh()
1385 confirmwin.refresh()
1385 confirmwin.refresh()
1386 try:
1386 try:
1387 response = chr(self.stdscr.getch())
1387 response = chr(self.stdscr.getch())
1388 except ValueError:
1388 except ValueError:
1389 response = None
1389 response = None
1390
1390
1391 return response
1391 return response
1392
1392
1393 def confirmcommit(self, review=False):
1393 def confirmcommit(self, review=False):
1394 """ask for 'y' to be pressed to confirm selected. return True if
1394 """ask for 'y' to be pressed to confirm selected. return True if
1395 confirmed."""
1395 confirmed."""
1396 if review:
1396 if review:
1397 confirmtext = (
1397 confirmtext = (
1398 """if you answer yes to the following, the your currently chosen patch chunks
1398 """if you answer yes to the following, the your currently chosen patch chunks
1399 will be loaded into an editor. you may modify the patch from the editor, and
1399 will be loaded into an editor. you may modify the patch from the editor, and
1400 save the changes if you wish to change the patch. otherwise, you can just
1400 save the changes if you wish to change the patch. otherwise, you can just
1401 close the editor without saving to accept the current patch as-is.
1401 close the editor without saving to accept the current patch as-is.
1402
1402
1403 note: don't add/remove lines unless you also modify the range information.
1403 note: don't add/remove lines unless you also modify the range information.
1404 failing to follow this rule will result in the commit aborting.
1404 failing to follow this rule will result in the commit aborting.
1405
1405
1406 are you sure you want to review/edit and confirm the selected changes [yn]?
1406 are you sure you want to review/edit and confirm the selected changes [yn]?
1407 """)
1407 """)
1408 else:
1408 else:
1409 confirmtext = (
1409 confirmtext = (
1410 "are you sure you want to confirm the selected changes [yn]? ")
1410 "are you sure you want to confirm the selected changes [yn]? ")
1411
1411
1412 response = self.confirmationwindow(confirmtext)
1412 response = self.confirmationwindow(confirmtext)
1413 if response is None:
1413 if response is None:
1414 response = "n"
1414 response = "n"
1415 if response.lower().startswith("y"):
1415 if response.lower().startswith("y"):
1416 return True
1416 return True
1417 else:
1417 else:
1418 return False
1418 return False
1419
1419
1420 def recenterdisplayedarea(self):
1420 def recenterdisplayedarea(self):
1421 """
1421 """
1422 once we scrolled with pg up pg down we can be pointing outside of the
1422 once we scrolled with pg up pg down we can be pointing outside of the
1423 display zone. we print the patch with towin=False to compute the
1423 display zone. we print the patch with towin=False to compute the
1424 location of the selected item eventhough it is outside of the displayed
1424 location of the selected item eventhough it is outside of the displayed
1425 zone and then update the scroll.
1425 zone and then update the scroll.
1426 """
1426 """
1427 self.printitem(towin=False)
1427 self.printitem(towin=False)
1428 self.updatescroll()
1428 self.updatescroll()
1429
1429
1430 def toggleedit(self, item=None, test=False):
1430 def toggleedit(self, item=None, test=False):
1431 """
1431 """
1432 edit the currently chelected chunk
1432 edit the currently chelected chunk
1433 """
1433 """
1434 def updateui(self):
1434 def updateui(self):
1435 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1435 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1436 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1436 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1437 self.updatescroll()
1437 self.updatescroll()
1438 self.stdscr.refresh()
1438 self.stdscr.refresh()
1439 self.statuswin.refresh()
1439 self.statuswin.refresh()
1440 self.stdscr.keypad(1)
1440 self.stdscr.keypad(1)
1441
1441
1442 def editpatchwitheditor(self, chunk):
1442 def editpatchwitheditor(self, chunk):
1443 if chunk is None:
1443 if chunk is None:
1444 self.ui.write(_('cannot edit patch for whole file'))
1444 self.ui.write(_('cannot edit patch for whole file'))
1445 self.ui.write("\n")
1445 self.ui.write("\n")
1446 return None
1446 return None
1447 if chunk.header.binary():
1447 if chunk.header.binary():
1448 self.ui.write(_('cannot edit patch for binary file'))
1448 self.ui.write(_('cannot edit patch for binary file'))
1449 self.ui.write("\n")
1449 self.ui.write("\n")
1450 return None
1450 return None
1451 # patch comment based on the git one (based on comment at end of
1451 # patch comment based on the git one (based on comment at end of
1452 # http://mercurial.selenic.com/wiki/recordextension)
1452 # http://mercurial.selenic.com/wiki/recordextension)
1453 phelp = '---' + _("""
1453 phelp = '---' + _("""
1454 to remove '-' lines, make them ' ' lines (context).
1454 to remove '-' lines, make them ' ' lines (context).
1455 to remove '+' lines, delete them.
1455 to remove '+' lines, delete them.
1456 lines starting with # will be removed from the patch.
1456 lines starting with # will be removed from the patch.
1457
1457
1458 if the patch applies cleanly, the edited hunk will immediately be
1458 if the patch applies cleanly, the edited hunk will immediately be
1459 added to the record list. if it does not apply cleanly, a rejects
1459 added to the record list. if it does not apply cleanly, a rejects
1460 file will be generated: you can use that when you try again. if
1460 file will be generated: you can use that when you try again. if
1461 all lines of the hunk are removed, then the edit is aborted and
1461 all lines of the hunk are removed, then the edit is aborted and
1462 the hunk is left unchanged.
1462 the hunk is left unchanged.
1463 """)
1463 """)
1464 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
1464 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
1465 suffix=".diff", text=True)
1465 suffix=".diff", text=True)
1466 ncpatchfp = None
1466 ncpatchfp = None
1467 try:
1467 try:
1468 # write the initial patch
1468 # write the initial patch
1469 f = os.fdopen(patchfd, "w")
1469 f = os.fdopen(patchfd, "w")
1470 chunk.header.write(f)
1470 chunk.header.write(f)
1471 chunk.write(f)
1471 chunk.write(f)
1472 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1472 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1473 f.close()
1473 f.close()
1474 # start the editor and wait for it to complete
1474 # start the editor and wait for it to complete
1475 editor = self.ui.geteditor()
1475 editor = self.ui.geteditor()
1476 ret = self.ui.system("%s \"%s\"" % (editor, patchfn),
1476 ret = self.ui.system("%s \"%s\"" % (editor, patchfn),
1477 environ={'hguser': self.ui.username()})
1477 environ={'hguser': self.ui.username()})
1478 if ret != 0:
1478 if ret != 0:
1479 self.errorstr = "Editor exited with status %d" % ret
1479 self.errorstr = "Editor exited with status %d" % ret
1480 return None
1480 return None
1481 # remove comment lines
1481 # remove comment lines
1482 patchfp = open(patchfn)
1482 patchfp = open(patchfn)
1483 ncpatchfp = cStringIO.StringIO()
1483 ncpatchfp = cStringIO.StringIO()
1484 for line in patchfp:
1484 for line in patchfp:
1485 if not line.startswith('#'):
1485 if not line.startswith('#'):
1486 ncpatchfp.write(line)
1486 ncpatchfp.write(line)
1487 patchfp.close()
1487 patchfp.close()
1488 ncpatchfp.seek(0)
1488 ncpatchfp.seek(0)
1489 newpatches = patchmod.parsepatch(ncpatchfp)
1489 newpatches = patchmod.parsepatch(ncpatchfp)
1490 finally:
1490 finally:
1491 os.unlink(patchfn)
1491 os.unlink(patchfn)
1492 del ncpatchfp
1492 del ncpatchfp
1493 return newpatches
1493 return newpatches
1494 if item is None:
1494 if item is None:
1495 item = self.currentselecteditem
1495 item = self.currentselecteditem
1496 if isinstance(item, uiheader):
1496 if isinstance(item, uiheader):
1497 return
1497 return
1498 if isinstance(item, uihunkline):
1498 if isinstance(item, uihunkline):
1499 item = item.parentitem()
1499 item = item.parentitem()
1500 if not isinstance(item, uihunk):
1500 if not isinstance(item, uihunk):
1501 return
1501 return
1502
1502
1503 beforeadded, beforeremoved = item.added, item.removed
1503 beforeadded, beforeremoved = item.added, item.removed
1504 newpatches = editpatchwitheditor(self, item)
1504 newpatches = editpatchwitheditor(self, item)
1505 if newpatches is None:
1505 if newpatches is None:
1506 if not test:
1506 if not test:
1507 updateui(self)
1507 updateui(self)
1508 return
1508 return
1509 header = item.header
1509 header = item.header
1510 editedhunkindex = header.hunks.index(item)
1510 editedhunkindex = header.hunks.index(item)
1511 hunksbefore = header.hunks[:editedhunkindex]
1511 hunksbefore = header.hunks[:editedhunkindex]
1512 hunksafter = header.hunks[editedhunkindex + 1:]
1512 hunksafter = header.hunks[editedhunkindex + 1:]
1513 newpatchheader = newpatches[0]
1513 newpatchheader = newpatches[0]
1514 newhunks = [uihunk(h, header) for h in newpatchheader.hunks]
1514 newhunks = [uihunk(h, header) for h in newpatchheader.hunks]
1515 newadded = sum([h.added for h in newhunks])
1515 newadded = sum([h.added for h in newhunks])
1516 newremoved = sum([h.removed for h in newhunks])
1516 newremoved = sum([h.removed for h in newhunks])
1517 offset = (newadded - beforeadded) - (newremoved - beforeremoved)
1517 offset = (newadded - beforeadded) - (newremoved - beforeremoved)
1518
1518
1519 for h in hunksafter:
1519 for h in hunksafter:
1520 h.toline += offset
1520 h.toline += offset
1521 for h in newhunks:
1521 for h in newhunks:
1522 h.folded = False
1522 h.folded = False
1523 header.hunks = hunksbefore + newhunks + hunksafter
1523 header.hunks = hunksbefore + newhunks + hunksafter
1524 if self.emptypatch():
1524 if self.emptypatch():
1525 header.hunks = hunksbefore + [item] + hunksafter
1525 header.hunks = hunksbefore + [item] + hunksafter
1526 self.currentselecteditem = header
1526 self.currentselecteditem = header
1527
1527
1528 if not test:
1528 if not test:
1529 updateui(self)
1529 updateui(self)
1530
1530
1531 def emptypatch(self):
1531 def emptypatch(self):
1532 item = self.headerlist
1532 item = self.headerlist
1533 if not item:
1533 if not item:
1534 return True
1534 return True
1535 for header in item:
1535 for header in item:
1536 if header.hunks:
1536 if header.hunks:
1537 return False
1537 return False
1538 return True
1538 return True
1539
1539
1540 def handlekeypressed(self, keypressed, test=False):
1540 def handlekeypressed(self, keypressed, test=False):
1541 if keypressed in ["k", "KEY_UP"]:
1541 if keypressed in ["k", "KEY_UP"]:
1542 self.uparrowevent()
1542 self.uparrowevent()
1543 if keypressed in ["K", "KEY_PPAGE"]:
1543 if keypressed in ["K", "KEY_PPAGE"]:
1544 self.uparrowshiftevent()
1544 self.uparrowshiftevent()
1545 elif keypressed in ["j", "KEY_DOWN"]:
1545 elif keypressed in ["j", "KEY_DOWN"]:
1546 self.downarrowevent()
1546 self.downarrowevent()
1547 elif keypressed in ["J", "KEY_NPAGE"]:
1547 elif keypressed in ["J", "KEY_NPAGE"]:
1548 self.downarrowshiftevent()
1548 self.downarrowshiftevent()
1549 elif keypressed in ["l", "KEY_RIGHT"]:
1549 elif keypressed in ["l", "KEY_RIGHT"]:
1550 self.rightarrowevent()
1550 self.rightarrowevent()
1551 elif keypressed in ["h", "KEY_LEFT"]:
1551 elif keypressed in ["h", "KEY_LEFT"]:
1552 self.leftarrowevent()
1552 self.leftarrowevent()
1553 elif keypressed in ["H", "KEY_SLEFT"]:
1553 elif keypressed in ["H", "KEY_SLEFT"]:
1554 self.leftarrowshiftevent()
1554 self.leftarrowshiftevent()
1555 elif keypressed in ["q"]:
1555 elif keypressed in ["q"]:
1556 raise util.Abort(_('user quit'))
1556 raise util.Abort(_('user quit'))
1557 elif keypressed in ["c"]:
1557 elif keypressed in ["c"]:
1558 if self.confirmcommit():
1558 if self.confirmcommit():
1559 return True
1559 return True
1560 elif keypressed in ["r"]:
1560 elif keypressed in ["r"]:
1561 if self.confirmcommit(review=True):
1561 if self.confirmcommit(review=True):
1562 return True
1562 return True
1563 elif test and keypressed in ['X']:
1563 elif test and keypressed in ['X']:
1564 return True
1564 return True
1565 elif keypressed in [' '] or (test and keypressed in ["TOGGLE"]):
1565 elif keypressed in [' '] or (test and keypressed in ["TOGGLE"]):
1566 self.toggleapply()
1566 self.toggleapply()
1567 elif keypressed in ['A']:
1567 elif keypressed in ['A']:
1568 self.toggleall()
1568 self.toggleall()
1569 elif keypressed in ['e']:
1569 elif keypressed in ['e']:
1570 self.toggleedit(test=test)
1570 self.toggleedit(test=test)
1571 elif keypressed in ["f"]:
1571 elif keypressed in ["f"]:
1572 self.togglefolded()
1572 self.togglefolded()
1573 elif keypressed in ["F"]:
1573 elif keypressed in ["F"]:
1574 self.togglefolded(foldparent=True)
1574 self.togglefolded(foldparent=True)
1575 elif keypressed in ["?"]:
1575 elif keypressed in ["?"]:
1576 self.helpwindow()
1576 self.helpwindow()
1577 self.stdscr.clear()
1577 self.stdscr.clear()
1578 self.stdscr.refresh()
1578 self.stdscr.refresh()
1579
1579
1580 def main(self, stdscr):
1580 def main(self, stdscr):
1581 """
1581 """
1582 method to be wrapped by curses.wrapper() for selecting chunks.
1582 method to be wrapped by curses.wrapper() for selecting chunks.
1583
1583
1584 """
1584 """
1585 signal.signal(signal.SIGWINCH, self.sigwinchhandler)
1585 signal.signal(signal.SIGWINCH, self.sigwinchhandler)
1586 self.stdscr = stdscr
1586 self.stdscr = stdscr
1587 # error during initialization, cannot be printed in the curses
1587 # error during initialization, cannot be printed in the curses
1588 # interface, it should be printed by the calling code
1588 # interface, it should be printed by the calling code
1589 self.initerr = None
1589 self.initerr = None
1590 self.yscreensize, self.xscreensize = self.stdscr.getmaxyx()
1590 self.yscreensize, self.xscreensize = self.stdscr.getmaxyx()
1591
1591
1592 curses.start_color()
1592 curses.start_color()
1593 curses.use_default_colors()
1593 curses.use_default_colors()
1594
1594
1595 # available colors: black, blue, cyan, green, magenta, white, yellow
1595 # available colors: black, blue, cyan, green, magenta, white, yellow
1596 # init_pair(color_id, foreground_color, background_color)
1596 # init_pair(color_id, foreground_color, background_color)
1597 self.initcolorpair(None, None, name="normal")
1597 self.initcolorpair(None, None, name="normal")
1598 self.initcolorpair(curses.COLOR_WHITE, curses.COLOR_MAGENTA,
1598 self.initcolorpair(curses.COLOR_WHITE, curses.COLOR_MAGENTA,
1599 name="selected")
1599 name="selected")
1600 self.initcolorpair(curses.COLOR_RED, None, name="deletion")
1600 self.initcolorpair(curses.COLOR_RED, None, name="deletion")
1601 self.initcolorpair(curses.COLOR_GREEN, None, name="addition")
1601 self.initcolorpair(curses.COLOR_GREEN, None, name="addition")
1602 self.initcolorpair(curses.COLOR_WHITE, curses.COLOR_BLUE, name="legend")
1602 self.initcolorpair(curses.COLOR_WHITE, curses.COLOR_BLUE, name="legend")
1603 # newwin([height, width,] begin_y, begin_x)
1603 # newwin([height, width,] begin_y, begin_x)
1604 self.statuswin = curses.newwin(self.numstatuslines, 0, 0, 0)
1604 self.statuswin = curses.newwin(self.numstatuslines, 0, 0, 0)
1605 self.statuswin.keypad(1) # interpret arrow-key, etc. esc sequences
1605 self.statuswin.keypad(1) # interpret arrow-key, etc. esc sequences
1606
1606
1607 # figure out how much space to allocate for the chunk-pad which is
1607 # figure out how much space to allocate for the chunk-pad which is
1608 # used for displaying the patch
1608 # used for displaying the patch
1609
1609
1610 # stupid hack to prevent getnumlinesdisplayed from failing
1610 # stupid hack to prevent getnumlinesdisplayed from failing
1611 self.chunkpad = curses.newpad(1, self.xscreensize)
1611 self.chunkpad = curses.newpad(1, self.xscreensize)
1612
1612
1613 # add 1 so to account for last line text reaching end of line
1613 # add 1 so to account for last line text reaching end of line
1614 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1614 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1615
1616 try:
1615 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1617 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1616
1618 except curses.error:
1619 self.initerr = _('this diff is too large to be displayed')
1620 return
1617 # initialize selecteitemendline (initial start-line is 0)
1621 # initialize selecteitemendline (initial start-line is 0)
1618 self.selecteditemendline = self.getnumlinesdisplayed(
1622 self.selecteditemendline = self.getnumlinesdisplayed(
1619 self.currentselecteditem, recursechildren=False)
1623 self.currentselecteditem, recursechildren=False)
1620
1624
1621 while True:
1625 while True:
1622 self.updatescreen()
1626 self.updatescreen()
1623 try:
1627 try:
1624 keypressed = self.statuswin.getkey()
1628 keypressed = self.statuswin.getkey()
1625 if self.errorstr is not None:
1629 if self.errorstr is not None:
1626 self.errorstr = None
1630 self.errorstr = None
1627 continue
1631 continue
1628 except curses.error:
1632 except curses.error:
1629 keypressed = "foobar"
1633 keypressed = "foobar"
1630 if self.handlekeypressed(keypressed):
1634 if self.handlekeypressed(keypressed):
1631 break
1635 break
General Comments 0
You need to be logged in to leave comments. Login now