##// END OF EJS Templates
crecord: fix blue artifact bug coming back from help screen...
Laurent Charignon -
r25419:6e62a5b3 default
parent child Browse files
Show More
@@ -1,1603 +1,1605 b''
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 curses.wrapper(chunkselector.main)
485 curses.wrapper(chunkselector.main)
486
486
487 def testdecorator(testfn, f):
487 def testdecorator(testfn, f):
488 def u(*args, **kwargs):
488 def u(*args, **kwargs):
489 return f(testfn, *args, **kwargs)
489 return f(testfn, *args, **kwargs)
490 return u
490 return u
491
491
492 def testchunkselector(testfn, ui, headerlist):
492 def testchunkselector(testfn, ui, headerlist):
493 """
493 """
494 test interface to get selection of chunks, and mark the applied flags
494 test interface to get selection of chunks, and mark the applied flags
495 of the chosen chunks.
495 of the chosen chunks.
496
496
497 """
497 """
498 chunkselector = curseschunkselector(headerlist, ui)
498 chunkselector = curseschunkselector(headerlist, ui)
499 if testfn and os.path.exists(testfn):
499 if testfn and os.path.exists(testfn):
500 testf = open(testfn)
500 testf = open(testfn)
501 testcommands = map(lambda x: x.rstrip('\n'), testf.readlines())
501 testcommands = map(lambda x: x.rstrip('\n'), testf.readlines())
502 testf.close()
502 testf.close()
503 while True:
503 while True:
504 if chunkselector.handlekeypressed(testcommands.pop(0), test=True):
504 if chunkselector.handlekeypressed(testcommands.pop(0), test=True):
505 break
505 break
506
506
507 class curseschunkselector(object):
507 class curseschunkselector(object):
508 def __init__(self, headerlist, ui):
508 def __init__(self, headerlist, ui):
509 # put the headers into a patch object
509 # put the headers into a patch object
510 self.headerlist = patch(headerlist)
510 self.headerlist = patch(headerlist)
511
511
512 self.ui = ui
512 self.ui = ui
513
513
514 # list of all chunks
514 # list of all chunks
515 self.chunklist = []
515 self.chunklist = []
516 for h in headerlist:
516 for h in headerlist:
517 self.chunklist.append(h)
517 self.chunklist.append(h)
518 self.chunklist.extend(h.hunks)
518 self.chunklist.extend(h.hunks)
519
519
520 # dictionary mapping (fgcolor, bgcolor) pairs to the
520 # dictionary mapping (fgcolor, bgcolor) pairs to the
521 # corresponding curses color-pair value.
521 # corresponding curses color-pair value.
522 self.colorpairs = {}
522 self.colorpairs = {}
523 # maps custom nicknames of color-pairs to curses color-pair values
523 # maps custom nicknames of color-pairs to curses color-pair values
524 self.colorpairnames = {}
524 self.colorpairnames = {}
525
525
526 # the currently selected header, hunk, or hunk-line
526 # the currently selected header, hunk, or hunk-line
527 self.currentselecteditem = self.headerlist[0]
527 self.currentselecteditem = self.headerlist[0]
528
528
529 # updated when printing out patch-display -- the 'lines' here are the
529 # updated when printing out patch-display -- the 'lines' here are the
530 # line positions *in the pad*, not on the screen.
530 # line positions *in the pad*, not on the screen.
531 self.selecteditemstartline = 0
531 self.selecteditemstartline = 0
532 self.selecteditemendline = None
532 self.selecteditemendline = None
533
533
534 # define indentation levels
534 # define indentation levels
535 self.headerindentnumchars = 0
535 self.headerindentnumchars = 0
536 self.hunkindentnumchars = 3
536 self.hunkindentnumchars = 3
537 self.hunklineindentnumchars = 6
537 self.hunklineindentnumchars = 6
538
538
539 # the first line of the pad to print to the screen
539 # the first line of the pad to print to the screen
540 self.firstlineofpadtoprint = 0
540 self.firstlineofpadtoprint = 0
541
541
542 # keeps track of the number of lines in the pad
542 # keeps track of the number of lines in the pad
543 self.numpadlines = None
543 self.numpadlines = None
544
544
545 self.numstatuslines = 2
545 self.numstatuslines = 2
546
546
547 # keep a running count of the number of lines printed to the pad
547 # keep a running count of the number of lines printed to the pad
548 # (used for determining when the selected item begins/ends)
548 # (used for determining when the selected item begins/ends)
549 self.linesprintedtopadsofar = 0
549 self.linesprintedtopadsofar = 0
550
550
551 # the first line of the pad which is visible on the screen
551 # the first line of the pad which is visible on the screen
552 self.firstlineofpadtoprint = 0
552 self.firstlineofpadtoprint = 0
553
553
554 # stores optional text for a commit comment provided by the user
554 # stores optional text for a commit comment provided by the user
555 self.commenttext = ""
555 self.commenttext = ""
556
556
557 # if the last 'toggle all' command caused all changes to be applied
557 # if the last 'toggle all' command caused all changes to be applied
558 self.waslasttoggleallapplied = True
558 self.waslasttoggleallapplied = True
559
559
560 def uparrowevent(self):
560 def uparrowevent(self):
561 """
561 """
562 try to select the previous item to the current item that has the
562 try to select the previous item to the current item that has the
563 most-indented level. for example, if a hunk is selected, try to select
563 most-indented level. for example, if a hunk is selected, try to select
564 the last hunkline of the hunk prior to the selected hunk. or, if
564 the last hunkline of the hunk prior to the selected hunk. or, if
565 the first hunkline of a hunk is currently selected, then select the
565 the first hunkline of a hunk is currently selected, then select the
566 hunk itself.
566 hunk itself.
567
567
568 if the currently selected item is already at the top of the screen,
568 if the currently selected item is already at the top of the screen,
569 scroll the screen down to show the new-selected item.
569 scroll the screen down to show the new-selected item.
570
570
571 """
571 """
572 currentitem = self.currentselecteditem
572 currentitem = self.currentselecteditem
573
573
574 nextitem = currentitem.previtem(constrainlevel=False)
574 nextitem = currentitem.previtem(constrainlevel=False)
575
575
576 if nextitem is None:
576 if nextitem is None:
577 # if no parent item (i.e. currentitem is the first header), then
577 # if no parent item (i.e. currentitem is the first header), then
578 # no change...
578 # no change...
579 nextitem = currentitem
579 nextitem = currentitem
580
580
581 self.currentselecteditem = nextitem
581 self.currentselecteditem = nextitem
582
582
583 def uparrowshiftevent(self):
583 def uparrowshiftevent(self):
584 """
584 """
585 select (if possible) the previous item on the same level as the
585 select (if possible) the previous item on the same level as the
586 currently selected item. otherwise, select (if possible) the
586 currently selected item. otherwise, select (if possible) the
587 parent-item of the currently selected item.
587 parent-item of the currently selected item.
588
588
589 if the currently selected item is already at the top of the screen,
589 if the currently selected item is already at the top of the screen,
590 scroll the screen down to show the new-selected item.
590 scroll the screen down to show the new-selected item.
591
591
592 """
592 """
593 currentitem = self.currentselecteditem
593 currentitem = self.currentselecteditem
594 nextitem = currentitem.previtem()
594 nextitem = currentitem.previtem()
595 # if there's no previous item on this level, try choosing the parent
595 # if there's no previous item on this level, try choosing the parent
596 if nextitem is None:
596 if nextitem is None:
597 nextitem = currentitem.parentitem()
597 nextitem = currentitem.parentitem()
598 if nextitem is None:
598 if nextitem is None:
599 # if no parent item (i.e. currentitem is the first header), then
599 # if no parent item (i.e. currentitem is the first header), then
600 # no change...
600 # no change...
601 nextitem = currentitem
601 nextitem = currentitem
602
602
603 self.currentselecteditem = nextitem
603 self.currentselecteditem = nextitem
604
604
605 def downarrowevent(self):
605 def downarrowevent(self):
606 """
606 """
607 try to select the next item to the current item that has the
607 try to select the next item to the current item that has the
608 most-indented level. for example, if a hunk is selected, select
608 most-indented level. for example, if a hunk is selected, select
609 the first hunkline of the selected hunk. or, if the last hunkline of
609 the first hunkline of the selected hunk. or, if the last hunkline of
610 a hunk is currently selected, then select the next hunk, if one exists,
610 a hunk is currently selected, then select the next hunk, if one exists,
611 or if not, the next header if one exists.
611 or if not, the next header if one exists.
612
612
613 if the currently selected item is already at the bottom of the screen,
613 if the currently selected item is already at the bottom of the screen,
614 scroll the screen up to show the new-selected item.
614 scroll the screen up to show the new-selected item.
615
615
616 """
616 """
617 #self.startprintline += 1 #debug
617 #self.startprintline += 1 #debug
618 currentitem = self.currentselecteditem
618 currentitem = self.currentselecteditem
619
619
620 nextitem = currentitem.nextitem(constrainlevel=False)
620 nextitem = currentitem.nextitem(constrainlevel=False)
621 # if there's no next item, keep the selection as-is
621 # if there's no next item, keep the selection as-is
622 if nextitem is None:
622 if nextitem is None:
623 nextitem = currentitem
623 nextitem = currentitem
624
624
625 self.currentselecteditem = nextitem
625 self.currentselecteditem = nextitem
626
626
627 def downarrowshiftevent(self):
627 def downarrowshiftevent(self):
628 """
628 """
629 if the cursor is already at the bottom chunk, scroll the screen up and
629 if the cursor is already at the bottom chunk, scroll the screen up and
630 move the cursor-position to the subsequent chunk. otherwise, only move
630 move the cursor-position to the subsequent chunk. otherwise, only move
631 the cursor position down one chunk.
631 the cursor position down one chunk.
632
632
633 """
633 """
634 # todo: update docstring
634 # todo: update docstring
635
635
636 currentitem = self.currentselecteditem
636 currentitem = self.currentselecteditem
637 nextitem = currentitem.nextitem()
637 nextitem = currentitem.nextitem()
638 # if there's no previous item on this level, try choosing the parent's
638 # if there's no previous item on this level, try choosing the parent's
639 # nextitem.
639 # nextitem.
640 if nextitem is None:
640 if nextitem is None:
641 try:
641 try:
642 nextitem = currentitem.parentitem().nextitem()
642 nextitem = currentitem.parentitem().nextitem()
643 except AttributeError:
643 except AttributeError:
644 # parentitem returned None, so nextitem() can't be called
644 # parentitem returned None, so nextitem() can't be called
645 nextitem = None
645 nextitem = None
646 if nextitem is None:
646 if nextitem is None:
647 # if no next item on parent-level, then no change...
647 # if no next item on parent-level, then no change...
648 nextitem = currentitem
648 nextitem = currentitem
649
649
650 self.currentselecteditem = nextitem
650 self.currentselecteditem = nextitem
651
651
652 def rightarrowevent(self):
652 def rightarrowevent(self):
653 """
653 """
654 select (if possible) the first of this item's child-items.
654 select (if possible) the first of this item's child-items.
655
655
656 """
656 """
657 currentitem = self.currentselecteditem
657 currentitem = self.currentselecteditem
658 nextitem = currentitem.firstchild()
658 nextitem = currentitem.firstchild()
659
659
660 # turn off folding if we want to show a child-item
660 # turn off folding if we want to show a child-item
661 if currentitem.folded:
661 if currentitem.folded:
662 self.togglefolded(currentitem)
662 self.togglefolded(currentitem)
663
663
664 if nextitem is None:
664 if nextitem is None:
665 # if no next item on parent-level, then no change...
665 # if no next item on parent-level, then no change...
666 nextitem = currentitem
666 nextitem = currentitem
667
667
668 self.currentselecteditem = nextitem
668 self.currentselecteditem = nextitem
669
669
670 def leftarrowevent(self):
670 def leftarrowevent(self):
671 """
671 """
672 if the current item can be folded (i.e. it is an unfolded header or
672 if the current item can be folded (i.e. it is an unfolded header or
673 hunk), then fold it. otherwise try select (if possible) the parent
673 hunk), then fold it. otherwise try select (if possible) the parent
674 of this item.
674 of this item.
675
675
676 """
676 """
677 currentitem = self.currentselecteditem
677 currentitem = self.currentselecteditem
678
678
679 # try to fold the item
679 # try to fold the item
680 if not isinstance(currentitem, uihunkline):
680 if not isinstance(currentitem, uihunkline):
681 if not currentitem.folded:
681 if not currentitem.folded:
682 self.togglefolded(item=currentitem)
682 self.togglefolded(item=currentitem)
683 return
683 return
684
684
685 # if it can't be folded, try to select the parent item
685 # if it can't be folded, try to select the parent item
686 nextitem = currentitem.parentitem()
686 nextitem = currentitem.parentitem()
687
687
688 if nextitem is None:
688 if nextitem is None:
689 # if no item on parent-level, then no change...
689 # if no item on parent-level, then no change...
690 nextitem = currentitem
690 nextitem = currentitem
691 if not nextitem.folded:
691 if not nextitem.folded:
692 self.togglefolded(item=nextitem)
692 self.togglefolded(item=nextitem)
693
693
694 self.currentselecteditem = nextitem
694 self.currentselecteditem = nextitem
695
695
696 def leftarrowshiftevent(self):
696 def leftarrowshiftevent(self):
697 """
697 """
698 select the header of the current item (or fold current item if the
698 select the header of the current item (or fold current item if the
699 current item is already a header).
699 current item is already a header).
700
700
701 """
701 """
702 currentitem = self.currentselecteditem
702 currentitem = self.currentselecteditem
703
703
704 if isinstance(currentitem, uiheader):
704 if isinstance(currentitem, uiheader):
705 if not currentitem.folded:
705 if not currentitem.folded:
706 self.togglefolded(item=currentitem)
706 self.togglefolded(item=currentitem)
707 return
707 return
708
708
709 # select the parent item recursively until we're at a header
709 # select the parent item recursively until we're at a header
710 while True:
710 while True:
711 nextitem = currentitem.parentitem()
711 nextitem = currentitem.parentitem()
712 if nextitem is None:
712 if nextitem is None:
713 break
713 break
714 else:
714 else:
715 currentitem = nextitem
715 currentitem = nextitem
716
716
717 self.currentselecteditem = currentitem
717 self.currentselecteditem = currentitem
718
718
719 def updatescroll(self):
719 def updatescroll(self):
720 "scroll the screen to fully show the currently-selected"
720 "scroll the screen to fully show the currently-selected"
721 selstart = self.selecteditemstartline
721 selstart = self.selecteditemstartline
722 selend = self.selecteditemendline
722 selend = self.selecteditemendline
723 #selnumlines = selend - selstart
723 #selnumlines = selend - selstart
724 padstart = self.firstlineofpadtoprint
724 padstart = self.firstlineofpadtoprint
725 padend = padstart + self.yscreensize - self.numstatuslines - 1
725 padend = padstart + self.yscreensize - self.numstatuslines - 1
726 # 'buffered' pad start/end values which scroll with a certain
726 # 'buffered' pad start/end values which scroll with a certain
727 # top/bottom context margin
727 # top/bottom context margin
728 padstartbuffered = padstart + 3
728 padstartbuffered = padstart + 3
729 padendbuffered = padend - 3
729 padendbuffered = padend - 3
730
730
731 if selend > padendbuffered:
731 if selend > padendbuffered:
732 self.scrolllines(selend - padendbuffered)
732 self.scrolllines(selend - padendbuffered)
733 elif selstart < padstartbuffered:
733 elif selstart < padstartbuffered:
734 # negative values scroll in pgup direction
734 # negative values scroll in pgup direction
735 self.scrolllines(selstart - padstartbuffered)
735 self.scrolllines(selstart - padstartbuffered)
736
736
737
737
738 def scrolllines(self, numlines):
738 def scrolllines(self, numlines):
739 "scroll the screen up (down) by numlines when numlines >0 (<0)."
739 "scroll the screen up (down) by numlines when numlines >0 (<0)."
740 self.firstlineofpadtoprint += numlines
740 self.firstlineofpadtoprint += numlines
741 if self.firstlineofpadtoprint < 0:
741 if self.firstlineofpadtoprint < 0:
742 self.firstlineofpadtoprint = 0
742 self.firstlineofpadtoprint = 0
743 if self.firstlineofpadtoprint > self.numpadlines - 1:
743 if self.firstlineofpadtoprint > self.numpadlines - 1:
744 self.firstlineofpadtoprint = self.numpadlines - 1
744 self.firstlineofpadtoprint = self.numpadlines - 1
745
745
746 def toggleapply(self, item=None):
746 def toggleapply(self, item=None):
747 """
747 """
748 toggle the applied flag of the specified item. if no item is specified,
748 toggle the applied flag of the specified item. if no item is specified,
749 toggle the flag of the currently selected item.
749 toggle the flag of the currently selected item.
750
750
751 """
751 """
752 if item is None:
752 if item is None:
753 item = self.currentselecteditem
753 item = self.currentselecteditem
754
754
755 item.applied = not item.applied
755 item.applied = not item.applied
756
756
757 if isinstance(item, uiheader):
757 if isinstance(item, uiheader):
758 item.partial = False
758 item.partial = False
759 if item.applied:
759 if item.applied:
760 # apply all its hunks
760 # apply all its hunks
761 for hnk in item.hunks:
761 for hnk in item.hunks:
762 hnk.applied = True
762 hnk.applied = True
763 # apply all their hunklines
763 # apply all their hunklines
764 for hunkline in hnk.changedlines:
764 for hunkline in hnk.changedlines:
765 hunkline.applied = True
765 hunkline.applied = True
766 else:
766 else:
767 # un-apply all its hunks
767 # un-apply all its hunks
768 for hnk in item.hunks:
768 for hnk in item.hunks:
769 hnk.applied = False
769 hnk.applied = False
770 hnk.partial = False
770 hnk.partial = False
771 # un-apply all their hunklines
771 # un-apply all their hunklines
772 for hunkline in hnk.changedlines:
772 for hunkline in hnk.changedlines:
773 hunkline.applied = False
773 hunkline.applied = False
774 elif isinstance(item, uihunk):
774 elif isinstance(item, uihunk):
775 item.partial = False
775 item.partial = False
776 # apply all it's hunklines
776 # apply all it's hunklines
777 for hunkline in item.changedlines:
777 for hunkline in item.changedlines:
778 hunkline.applied = item.applied
778 hunkline.applied = item.applied
779
779
780 siblingappliedstatus = [hnk.applied for hnk in item.header.hunks]
780 siblingappliedstatus = [hnk.applied for hnk in item.header.hunks]
781 allsiblingsapplied = not (False in siblingappliedstatus)
781 allsiblingsapplied = not (False in siblingappliedstatus)
782 nosiblingsapplied = not (True in siblingappliedstatus)
782 nosiblingsapplied = not (True in siblingappliedstatus)
783
783
784 siblingspartialstatus = [hnk.partial for hnk in item.header.hunks]
784 siblingspartialstatus = [hnk.partial for hnk in item.header.hunks]
785 somesiblingspartial = (True in siblingspartialstatus)
785 somesiblingspartial = (True in siblingspartialstatus)
786
786
787 #cases where applied or partial should be removed from header
787 #cases where applied or partial should be removed from header
788
788
789 # if no 'sibling' hunks are applied (including this hunk)
789 # if no 'sibling' hunks are applied (including this hunk)
790 if nosiblingsapplied:
790 if nosiblingsapplied:
791 if not item.header.special():
791 if not item.header.special():
792 item.header.applied = False
792 item.header.applied = False
793 item.header.partial = False
793 item.header.partial = False
794 else: # some/all parent siblings are applied
794 else: # some/all parent siblings are applied
795 item.header.applied = True
795 item.header.applied = True
796 item.header.partial = (somesiblingspartial or
796 item.header.partial = (somesiblingspartial or
797 not allsiblingsapplied)
797 not allsiblingsapplied)
798
798
799 elif isinstance(item, uihunkline):
799 elif isinstance(item, uihunkline):
800 siblingappliedstatus = [ln.applied for ln in item.hunk.changedlines]
800 siblingappliedstatus = [ln.applied for ln in item.hunk.changedlines]
801 allsiblingsapplied = not (False in siblingappliedstatus)
801 allsiblingsapplied = not (False in siblingappliedstatus)
802 nosiblingsapplied = not (True in siblingappliedstatus)
802 nosiblingsapplied = not (True in siblingappliedstatus)
803
803
804 # if no 'sibling' lines are applied
804 # if no 'sibling' lines are applied
805 if nosiblingsapplied:
805 if nosiblingsapplied:
806 item.hunk.applied = False
806 item.hunk.applied = False
807 item.hunk.partial = False
807 item.hunk.partial = False
808 elif allsiblingsapplied:
808 elif allsiblingsapplied:
809 item.hunk.applied = True
809 item.hunk.applied = True
810 item.hunk.partial = False
810 item.hunk.partial = False
811 else: # some siblings applied
811 else: # some siblings applied
812 item.hunk.applied = True
812 item.hunk.applied = True
813 item.hunk.partial = True
813 item.hunk.partial = True
814
814
815 parentsiblingsapplied = [hnk.applied for hnk
815 parentsiblingsapplied = [hnk.applied for hnk
816 in item.hunk.header.hunks]
816 in item.hunk.header.hunks]
817 noparentsiblingsapplied = not (True in parentsiblingsapplied)
817 noparentsiblingsapplied = not (True in parentsiblingsapplied)
818 allparentsiblingsapplied = not (False in parentsiblingsapplied)
818 allparentsiblingsapplied = not (False in parentsiblingsapplied)
819
819
820 parentsiblingspartial = [hnk.partial for hnk
820 parentsiblingspartial = [hnk.partial for hnk
821 in item.hunk.header.hunks]
821 in item.hunk.header.hunks]
822 someparentsiblingspartial = (True in parentsiblingspartial)
822 someparentsiblingspartial = (True in parentsiblingspartial)
823
823
824 # if all parent hunks are not applied, un-apply header
824 # if all parent hunks are not applied, un-apply header
825 if noparentsiblingsapplied:
825 if noparentsiblingsapplied:
826 if not item.hunk.header.special():
826 if not item.hunk.header.special():
827 item.hunk.header.applied = False
827 item.hunk.header.applied = False
828 item.hunk.header.partial = False
828 item.hunk.header.partial = False
829 # set the applied and partial status of the header if needed
829 # set the applied and partial status of the header if needed
830 else: # some/all parent siblings are applied
830 else: # some/all parent siblings are applied
831 item.hunk.header.applied = True
831 item.hunk.header.applied = True
832 item.hunk.header.partial = (someparentsiblingspartial or
832 item.hunk.header.partial = (someparentsiblingspartial or
833 not allparentsiblingsapplied)
833 not allparentsiblingsapplied)
834
834
835 def toggleall(self):
835 def toggleall(self):
836 "toggle the applied flag of all items."
836 "toggle the applied flag of all items."
837 if self.waslasttoggleallapplied: # then unapply them this time
837 if self.waslasttoggleallapplied: # then unapply them this time
838 for item in self.headerlist:
838 for item in self.headerlist:
839 if item.applied:
839 if item.applied:
840 self.toggleapply(item)
840 self.toggleapply(item)
841 else:
841 else:
842 for item in self.headerlist:
842 for item in self.headerlist:
843 if not item.applied:
843 if not item.applied:
844 self.toggleapply(item)
844 self.toggleapply(item)
845 self.waslasttoggleallapplied = not self.waslasttoggleallapplied
845 self.waslasttoggleallapplied = not self.waslasttoggleallapplied
846
846
847 def togglefolded(self, item=None, foldparent=False):
847 def togglefolded(self, item=None, foldparent=False):
848 "toggle folded flag of specified item (defaults to currently selected)"
848 "toggle folded flag of specified item (defaults to currently selected)"
849 if item is None:
849 if item is None:
850 item = self.currentselecteditem
850 item = self.currentselecteditem
851 if foldparent or (isinstance(item, uiheader) and item.neverunfolded):
851 if foldparent or (isinstance(item, uiheader) and item.neverunfolded):
852 if not isinstance(item, uiheader):
852 if not isinstance(item, uiheader):
853 # we need to select the parent item in this case
853 # we need to select the parent item in this case
854 self.currentselecteditem = item = item.parentitem()
854 self.currentselecteditem = item = item.parentitem()
855 elif item.neverunfolded:
855 elif item.neverunfolded:
856 item.neverunfolded = False
856 item.neverunfolded = False
857
857
858 # also fold any foldable children of the parent/current item
858 # also fold any foldable children of the parent/current item
859 if isinstance(item, uiheader): # the original or 'new' item
859 if isinstance(item, uiheader): # the original or 'new' item
860 for child in item.allchildren():
860 for child in item.allchildren():
861 child.folded = not item.folded
861 child.folded = not item.folded
862
862
863 if isinstance(item, (uiheader, uihunk)):
863 if isinstance(item, (uiheader, uihunk)):
864 item.folded = not item.folded
864 item.folded = not item.folded
865
865
866
866
867 def alignstring(self, instr, window):
867 def alignstring(self, instr, window):
868 """
868 """
869 add whitespace to the end of a string in order to make it fill
869 add whitespace to the end of a string in order to make it fill
870 the screen in the x direction. the current cursor position is
870 the screen in the x direction. the current cursor position is
871 taken into account when making this calculation. the string can span
871 taken into account when making this calculation. the string can span
872 multiple lines.
872 multiple lines.
873
873
874 """
874 """
875 y, xstart = window.getyx()
875 y, xstart = window.getyx()
876 width = self.xscreensize
876 width = self.xscreensize
877 # turn tabs into spaces
877 # turn tabs into spaces
878 instr = instr.expandtabs(4)
878 instr = instr.expandtabs(4)
879 strwidth = encoding.colwidth(instr)
879 strwidth = encoding.colwidth(instr)
880 numspaces = (width - ((strwidth + xstart) % width) - 1)
880 numspaces = (width - ((strwidth + xstart) % width) - 1)
881 return instr + " " * numspaces + "\n"
881 return instr + " " * numspaces + "\n"
882
882
883 def printstring(self, window, text, fgcolor=None, bgcolor=None, pair=None,
883 def printstring(self, window, text, fgcolor=None, bgcolor=None, pair=None,
884 pairname=None, attrlist=None, towin=True, align=True, showwhtspc=False):
884 pairname=None, attrlist=None, towin=True, align=True, showwhtspc=False):
885 """
885 """
886 print the string, text, with the specified colors and attributes, to
886 print the string, text, with the specified colors and attributes, to
887 the specified curses window object.
887 the specified curses window object.
888
888
889 the foreground and background colors are of the form
889 the foreground and background colors are of the form
890 curses.color_xxxx, where xxxx is one of: [black, blue, cyan, green,
890 curses.color_xxxx, where xxxx is one of: [black, blue, cyan, green,
891 magenta, red, white, yellow]. if pairname is provided, a color
891 magenta, red, white, yellow]. if pairname is provided, a color
892 pair will be looked up in the self.colorpairnames dictionary.
892 pair will be looked up in the self.colorpairnames dictionary.
893
893
894 attrlist is a list containing text attributes in the form of
894 attrlist is a list containing text attributes in the form of
895 curses.a_xxxx, where xxxx can be: [bold, dim, normal, standout,
895 curses.a_xxxx, where xxxx can be: [bold, dim, normal, standout,
896 underline].
896 underline].
897
897
898 if align == True, whitespace is added to the printed string such that
898 if align == True, whitespace is added to the printed string such that
899 the string stretches to the right border of the window.
899 the string stretches to the right border of the window.
900
900
901 if showwhtspc == True, trailing whitespace of a string is highlighted.
901 if showwhtspc == True, trailing whitespace of a string is highlighted.
902
902
903 """
903 """
904 # preprocess the text, converting tabs to spaces
904 # preprocess the text, converting tabs to spaces
905 text = text.expandtabs(4)
905 text = text.expandtabs(4)
906 # strip \n, and convert control characters to ^[char] representation
906 # strip \n, and convert control characters to ^[char] representation
907 text = re.sub(r'[\x00-\x08\x0a-\x1f]',
907 text = re.sub(r'[\x00-\x08\x0a-\x1f]',
908 lambda m:'^' + chr(ord(m.group()) + 64), text.strip('\n'))
908 lambda m:'^' + chr(ord(m.group()) + 64), text.strip('\n'))
909
909
910 if pair is not None:
910 if pair is not None:
911 colorpair = pair
911 colorpair = pair
912 elif pairname is not None:
912 elif pairname is not None:
913 colorpair = self.colorpairnames[pairname]
913 colorpair = self.colorpairnames[pairname]
914 else:
914 else:
915 if fgcolor is None:
915 if fgcolor is None:
916 fgcolor = -1
916 fgcolor = -1
917 if bgcolor is None:
917 if bgcolor is None:
918 bgcolor = -1
918 bgcolor = -1
919 if (fgcolor, bgcolor) in self.colorpairs:
919 if (fgcolor, bgcolor) in self.colorpairs:
920 colorpair = self.colorpairs[(fgcolor, bgcolor)]
920 colorpair = self.colorpairs[(fgcolor, bgcolor)]
921 else:
921 else:
922 colorpair = self.getcolorpair(fgcolor, bgcolor)
922 colorpair = self.getcolorpair(fgcolor, bgcolor)
923 # add attributes if possible
923 # add attributes if possible
924 if attrlist is None:
924 if attrlist is None:
925 attrlist = []
925 attrlist = []
926 if colorpair < 256:
926 if colorpair < 256:
927 # then it is safe to apply all attributes
927 # then it is safe to apply all attributes
928 for textattr in attrlist:
928 for textattr in attrlist:
929 colorpair |= textattr
929 colorpair |= textattr
930 else:
930 else:
931 # just apply a select few (safe?) attributes
931 # just apply a select few (safe?) attributes
932 for textattr in (curses.A_UNDERLINE, curses.A_BOLD):
932 for textattr in (curses.A_UNDERLINE, curses.A_BOLD):
933 if textattr in attrlist:
933 if textattr in attrlist:
934 colorpair |= textattr
934 colorpair |= textattr
935
935
936 y, xstart = self.chunkpad.getyx()
936 y, xstart = self.chunkpad.getyx()
937 t = "" # variable for counting lines printed
937 t = "" # variable for counting lines printed
938 # if requested, show trailing whitespace
938 # if requested, show trailing whitespace
939 if showwhtspc:
939 if showwhtspc:
940 origlen = len(text)
940 origlen = len(text)
941 text = text.rstrip(' \n') # tabs have already been expanded
941 text = text.rstrip(' \n') # tabs have already been expanded
942 strippedlen = len(text)
942 strippedlen = len(text)
943 numtrailingspaces = origlen - strippedlen
943 numtrailingspaces = origlen - strippedlen
944
944
945 if towin:
945 if towin:
946 window.addstr(text, colorpair)
946 window.addstr(text, colorpair)
947 t += text
947 t += text
948
948
949 if showwhtspc:
949 if showwhtspc:
950 wscolorpair = colorpair | curses.A_REVERSE
950 wscolorpair = colorpair | curses.A_REVERSE
951 if towin:
951 if towin:
952 for i in range(numtrailingspaces):
952 for i in range(numtrailingspaces):
953 window.addch(curses.ACS_CKBOARD, wscolorpair)
953 window.addch(curses.ACS_CKBOARD, wscolorpair)
954 t += " " * numtrailingspaces
954 t += " " * numtrailingspaces
955
955
956 if align:
956 if align:
957 if towin:
957 if towin:
958 extrawhitespace = self.alignstring("", window)
958 extrawhitespace = self.alignstring("", window)
959 window.addstr(extrawhitespace, colorpair)
959 window.addstr(extrawhitespace, colorpair)
960 else:
960 else:
961 # need to use t, since the x position hasn't incremented
961 # need to use t, since the x position hasn't incremented
962 extrawhitespace = self.alignstring(t, window)
962 extrawhitespace = self.alignstring(t, window)
963 t += extrawhitespace
963 t += extrawhitespace
964
964
965 # is reset to 0 at the beginning of printitem()
965 # is reset to 0 at the beginning of printitem()
966
966
967 linesprinted = (xstart + len(t)) / self.xscreensize
967 linesprinted = (xstart + len(t)) / self.xscreensize
968 self.linesprintedtopadsofar += linesprinted
968 self.linesprintedtopadsofar += linesprinted
969 return t
969 return t
970
970
971 def updatescreen(self):
971 def updatescreen(self):
972 self.statuswin.erase()
972 self.statuswin.erase()
973 self.chunkpad.erase()
973 self.chunkpad.erase()
974
974
975 printstring = self.printstring
975 printstring = self.printstring
976
976
977 # print out the status lines at the top
977 # print out the status lines at the top
978 try:
978 try:
979 printstring(self.statuswin,
979 printstring(self.statuswin,
980 "SELECT CHUNKS: (j/k/up/dn/pgup/pgdn) move cursor; "
980 "SELECT CHUNKS: (j/k/up/dn/pgup/pgdn) move cursor; "
981 "(space/A) toggle hunk/all; (e)dit hunk;",
981 "(space/A) toggle hunk/all; (e)dit hunk;",
982 pairname="legend")
982 pairname="legend")
983 printstring(self.statuswin,
983 printstring(self.statuswin,
984 " (f)old/unfold; (c)onfirm applied; (q)uit; (?) help "
984 " (f)old/unfold; (c)onfirm applied; (q)uit; (?) help "
985 "| [X]=hunk applied **=folded",
985 "| [X]=hunk applied **=folded",
986 pairname="legend")
986 pairname="legend")
987 except curses.error:
987 except curses.error:
988 pass
988 pass
989
989
990 # print out the patch in the remaining part of the window
990 # print out the patch in the remaining part of the window
991 try:
991 try:
992 self.printitem()
992 self.printitem()
993 self.updatescroll()
993 self.updatescroll()
994 self.chunkpad.refresh(self.firstlineofpadtoprint, 0,
994 self.chunkpad.refresh(self.firstlineofpadtoprint, 0,
995 self.numstatuslines, 0,
995 self.numstatuslines, 0,
996 self.yscreensize + 1 - self.numstatuslines,
996 self.yscreensize + 1 - self.numstatuslines,
997 self.xscreensize)
997 self.xscreensize)
998 except curses.error:
998 except curses.error:
999 pass
999 pass
1000
1000
1001 # refresh([pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol])
1001 # refresh([pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol])
1002 self.statuswin.refresh()
1002 self.statuswin.refresh()
1003
1003
1004 def getstatusprefixstring(self, item):
1004 def getstatusprefixstring(self, item):
1005 """
1005 """
1006 create a string to prefix a line with which indicates whether 'item'
1006 create a string to prefix a line with which indicates whether 'item'
1007 is applied and/or folded.
1007 is applied and/or folded.
1008
1008
1009 """
1009 """
1010 # create checkbox string
1010 # create checkbox string
1011 if item.applied:
1011 if item.applied:
1012 if not isinstance(item, uihunkline) and item.partial:
1012 if not isinstance(item, uihunkline) and item.partial:
1013 checkbox = "[~]"
1013 checkbox = "[~]"
1014 else:
1014 else:
1015 checkbox = "[x]"
1015 checkbox = "[x]"
1016 else:
1016 else:
1017 checkbox = "[ ]"
1017 checkbox = "[ ]"
1018
1018
1019 try:
1019 try:
1020 if item.folded:
1020 if item.folded:
1021 checkbox += "**"
1021 checkbox += "**"
1022 if isinstance(item, uiheader):
1022 if isinstance(item, uiheader):
1023 # one of "m", "a", or "d" (modified, added, deleted)
1023 # one of "m", "a", or "d" (modified, added, deleted)
1024 filestatus = item.changetype
1024 filestatus = item.changetype
1025
1025
1026 checkbox += filestatus + " "
1026 checkbox += filestatus + " "
1027 else:
1027 else:
1028 checkbox += " "
1028 checkbox += " "
1029 if isinstance(item, uiheader):
1029 if isinstance(item, uiheader):
1030 # add two more spaces for headers
1030 # add two more spaces for headers
1031 checkbox += " "
1031 checkbox += " "
1032 except AttributeError: # not foldable
1032 except AttributeError: # not foldable
1033 checkbox += " "
1033 checkbox += " "
1034
1034
1035 return checkbox
1035 return checkbox
1036
1036
1037 def printheader(self, header, selected=False, towin=True,
1037 def printheader(self, header, selected=False, towin=True,
1038 ignorefolding=False):
1038 ignorefolding=False):
1039 """
1039 """
1040 print the header to the pad. if countlines is True, don't print
1040 print the header to the pad. if countlines is True, don't print
1041 anything, but just count the number of lines which would be printed.
1041 anything, but just count the number of lines which would be printed.
1042
1042
1043 """
1043 """
1044 outstr = ""
1044 outstr = ""
1045 text = header.prettystr()
1045 text = header.prettystr()
1046 chunkindex = self.chunklist.index(header)
1046 chunkindex = self.chunklist.index(header)
1047
1047
1048 if chunkindex != 0 and not header.folded:
1048 if chunkindex != 0 and not header.folded:
1049 # add separating line before headers
1049 # add separating line before headers
1050 outstr += self.printstring(self.chunkpad, '_' * self.xscreensize,
1050 outstr += self.printstring(self.chunkpad, '_' * self.xscreensize,
1051 towin=towin, align=False)
1051 towin=towin, align=False)
1052 # select color-pair based on if the header is selected
1052 # select color-pair based on if the header is selected
1053 colorpair = self.getcolorpair(name=selected and "selected" or "normal",
1053 colorpair = self.getcolorpair(name=selected and "selected" or "normal",
1054 attrlist=[curses.A_BOLD])
1054 attrlist=[curses.A_BOLD])
1055
1055
1056 # print out each line of the chunk, expanding it to screen width
1056 # print out each line of the chunk, expanding it to screen width
1057
1057
1058 # number of characters to indent lines on this level by
1058 # number of characters to indent lines on this level by
1059 indentnumchars = 0
1059 indentnumchars = 0
1060 checkbox = self.getstatusprefixstring(header)
1060 checkbox = self.getstatusprefixstring(header)
1061 if not header.folded or ignorefolding:
1061 if not header.folded or ignorefolding:
1062 textlist = text.split("\n")
1062 textlist = text.split("\n")
1063 linestr = checkbox + textlist[0]
1063 linestr = checkbox + textlist[0]
1064 else:
1064 else:
1065 linestr = checkbox + header.filename()
1065 linestr = checkbox + header.filename()
1066 outstr += self.printstring(self.chunkpad, linestr, pair=colorpair,
1066 outstr += self.printstring(self.chunkpad, linestr, pair=colorpair,
1067 towin=towin)
1067 towin=towin)
1068 if not header.folded or ignorefolding:
1068 if not header.folded or ignorefolding:
1069 if len(textlist) > 1:
1069 if len(textlist) > 1:
1070 for line in textlist[1:]:
1070 for line in textlist[1:]:
1071 linestr = " "*(indentnumchars + len(checkbox)) + line
1071 linestr = " "*(indentnumchars + len(checkbox)) + line
1072 outstr += self.printstring(self.chunkpad, linestr,
1072 outstr += self.printstring(self.chunkpad, linestr,
1073 pair=colorpair, towin=towin)
1073 pair=colorpair, towin=towin)
1074
1074
1075 return outstr
1075 return outstr
1076
1076
1077 def printhunklinesbefore(self, hunk, selected=False, towin=True,
1077 def printhunklinesbefore(self, hunk, selected=False, towin=True,
1078 ignorefolding=False):
1078 ignorefolding=False):
1079 "includes start/end line indicator"
1079 "includes start/end line indicator"
1080 outstr = ""
1080 outstr = ""
1081 # where hunk is in list of siblings
1081 # where hunk is in list of siblings
1082 hunkindex = hunk.header.hunks.index(hunk)
1082 hunkindex = hunk.header.hunks.index(hunk)
1083
1083
1084 if hunkindex != 0:
1084 if hunkindex != 0:
1085 # add separating line before headers
1085 # add separating line before headers
1086 outstr += self.printstring(self.chunkpad, ' '*self.xscreensize,
1086 outstr += self.printstring(self.chunkpad, ' '*self.xscreensize,
1087 towin=towin, align=False)
1087 towin=towin, align=False)
1088
1088
1089 colorpair = self.getcolorpair(name=selected and "selected" or "normal",
1089 colorpair = self.getcolorpair(name=selected and "selected" or "normal",
1090 attrlist=[curses.A_BOLD])
1090 attrlist=[curses.A_BOLD])
1091
1091
1092 # print out from-to line with checkbox
1092 # print out from-to line with checkbox
1093 checkbox = self.getstatusprefixstring(hunk)
1093 checkbox = self.getstatusprefixstring(hunk)
1094
1094
1095 lineprefix = " "*self.hunkindentnumchars + checkbox
1095 lineprefix = " "*self.hunkindentnumchars + checkbox
1096 frtoline = " " + hunk.getfromtoline().strip("\n")
1096 frtoline = " " + hunk.getfromtoline().strip("\n")
1097
1097
1098
1098
1099 outstr += self.printstring(self.chunkpad, lineprefix, towin=towin,
1099 outstr += self.printstring(self.chunkpad, lineprefix, towin=towin,
1100 align=False) # add uncolored checkbox/indent
1100 align=False) # add uncolored checkbox/indent
1101 outstr += self.printstring(self.chunkpad, frtoline, pair=colorpair,
1101 outstr += self.printstring(self.chunkpad, frtoline, pair=colorpair,
1102 towin=towin)
1102 towin=towin)
1103
1103
1104 if hunk.folded and not ignorefolding:
1104 if hunk.folded and not ignorefolding:
1105 # skip remainder of output
1105 # skip remainder of output
1106 return outstr
1106 return outstr
1107
1107
1108 # print out lines of the chunk preceeding changed-lines
1108 # print out lines of the chunk preceeding changed-lines
1109 for line in hunk.before:
1109 for line in hunk.before:
1110 linestr = " "*(self.hunklineindentnumchars + len(checkbox)) + line
1110 linestr = " "*(self.hunklineindentnumchars + len(checkbox)) + line
1111 outstr += self.printstring(self.chunkpad, linestr, towin=towin)
1111 outstr += self.printstring(self.chunkpad, linestr, towin=towin)
1112
1112
1113 return outstr
1113 return outstr
1114
1114
1115 def printhunklinesafter(self, hunk, towin=True, ignorefolding=False):
1115 def printhunklinesafter(self, hunk, towin=True, ignorefolding=False):
1116 outstr = ""
1116 outstr = ""
1117 if hunk.folded and not ignorefolding:
1117 if hunk.folded and not ignorefolding:
1118 return outstr
1118 return outstr
1119
1119
1120 # a bit superfluous, but to avoid hard-coding indent amount
1120 # a bit superfluous, but to avoid hard-coding indent amount
1121 checkbox = self.getstatusprefixstring(hunk)
1121 checkbox = self.getstatusprefixstring(hunk)
1122 for line in hunk.after:
1122 for line in hunk.after:
1123 linestr = " "*(self.hunklineindentnumchars + len(checkbox)) + line
1123 linestr = " "*(self.hunklineindentnumchars + len(checkbox)) + line
1124 outstr += self.printstring(self.chunkpad, linestr, towin=towin)
1124 outstr += self.printstring(self.chunkpad, linestr, towin=towin)
1125
1125
1126 return outstr
1126 return outstr
1127
1127
1128 def printhunkchangedline(self, hunkline, selected=False, towin=True):
1128 def printhunkchangedline(self, hunkline, selected=False, towin=True):
1129 outstr = ""
1129 outstr = ""
1130 checkbox = self.getstatusprefixstring(hunkline)
1130 checkbox = self.getstatusprefixstring(hunkline)
1131
1131
1132 linestr = hunkline.prettystr().strip("\n")
1132 linestr = hunkline.prettystr().strip("\n")
1133
1133
1134 # select color-pair based on whether line is an addition/removal
1134 # select color-pair based on whether line is an addition/removal
1135 if selected:
1135 if selected:
1136 colorpair = self.getcolorpair(name="selected")
1136 colorpair = self.getcolorpair(name="selected")
1137 elif linestr.startswith("+"):
1137 elif linestr.startswith("+"):
1138 colorpair = self.getcolorpair(name="addition")
1138 colorpair = self.getcolorpair(name="addition")
1139 elif linestr.startswith("-"):
1139 elif linestr.startswith("-"):
1140 colorpair = self.getcolorpair(name="deletion")
1140 colorpair = self.getcolorpair(name="deletion")
1141 elif linestr.startswith("\\"):
1141 elif linestr.startswith("\\"):
1142 colorpair = self.getcolorpair(name="normal")
1142 colorpair = self.getcolorpair(name="normal")
1143
1143
1144 lineprefix = " "*self.hunklineindentnumchars + checkbox
1144 lineprefix = " "*self.hunklineindentnumchars + checkbox
1145 outstr += self.printstring(self.chunkpad, lineprefix, towin=towin,
1145 outstr += self.printstring(self.chunkpad, lineprefix, towin=towin,
1146 align=False) # add uncolored checkbox/indent
1146 align=False) # add uncolored checkbox/indent
1147 outstr += self.printstring(self.chunkpad, linestr, pair=colorpair,
1147 outstr += self.printstring(self.chunkpad, linestr, pair=colorpair,
1148 towin=towin, showwhtspc=True)
1148 towin=towin, showwhtspc=True)
1149 return outstr
1149 return outstr
1150
1150
1151 def printitem(self, item=None, ignorefolding=False, recursechildren=True,
1151 def printitem(self, item=None, ignorefolding=False, recursechildren=True,
1152 towin=True):
1152 towin=True):
1153 """
1153 """
1154 use __printitem() to print the the specified item.applied.
1154 use __printitem() to print the the specified item.applied.
1155 if item is not specified, then print the entire patch.
1155 if item is not specified, then print the entire patch.
1156 (hiding folded elements, etc. -- see __printitem() docstring)
1156 (hiding folded elements, etc. -- see __printitem() docstring)
1157 """
1157 """
1158 if item is None:
1158 if item is None:
1159 item = self.headerlist
1159 item = self.headerlist
1160 if recursechildren:
1160 if recursechildren:
1161 self.linesprintedtopadsofar = 0
1161 self.linesprintedtopadsofar = 0
1162
1162
1163 outstr = []
1163 outstr = []
1164 self.__printitem(item, ignorefolding, recursechildren, outstr,
1164 self.__printitem(item, ignorefolding, recursechildren, outstr,
1165 towin=towin)
1165 towin=towin)
1166 return ''.join(outstr)
1166 return ''.join(outstr)
1167
1167
1168 def outofdisplayedarea(self):
1168 def outofdisplayedarea(self):
1169 y, _ = self.chunkpad.getyx() # cursor location
1169 y, _ = self.chunkpad.getyx() # cursor location
1170 # * 2 here works but an optimization would be the max number of
1170 # * 2 here works but an optimization would be the max number of
1171 # consecutive non selectable lines
1171 # consecutive non selectable lines
1172 # i.e the max number of context line for any hunk in the patch
1172 # i.e the max number of context line for any hunk in the patch
1173 miny = min(0, self.firstlineofpadtoprint - self.yscreensize)
1173 miny = min(0, self.firstlineofpadtoprint - self.yscreensize)
1174 maxy = self.firstlineofpadtoprint + self.yscreensize * 2
1174 maxy = self.firstlineofpadtoprint + self.yscreensize * 2
1175 return y < miny or y > maxy
1175 return y < miny or y > maxy
1176
1176
1177 def handleselection(self, item, recursechildren):
1177 def handleselection(self, item, recursechildren):
1178 selected = (item is self.currentselecteditem)
1178 selected = (item is self.currentselecteditem)
1179 if selected and recursechildren:
1179 if selected and recursechildren:
1180 # assumes line numbering starting from line 0
1180 # assumes line numbering starting from line 0
1181 self.selecteditemstartline = self.linesprintedtopadsofar
1181 self.selecteditemstartline = self.linesprintedtopadsofar
1182 selecteditemlines = self.getnumlinesdisplayed(item,
1182 selecteditemlines = self.getnumlinesdisplayed(item,
1183 recursechildren=False)
1183 recursechildren=False)
1184 self.selecteditemendline = (self.selecteditemstartline +
1184 self.selecteditemendline = (self.selecteditemstartline +
1185 selecteditemlines - 1)
1185 selecteditemlines - 1)
1186 return selected
1186 return selected
1187
1187
1188 def __printitem(self, item, ignorefolding, recursechildren, outstr,
1188 def __printitem(self, item, ignorefolding, recursechildren, outstr,
1189 towin=True):
1189 towin=True):
1190 """
1190 """
1191 recursive method for printing out patch/header/hunk/hunk-line data to
1191 recursive method for printing out patch/header/hunk/hunk-line data to
1192 screen. also returns a string with all of the content of the displayed
1192 screen. also returns a string with all of the content of the displayed
1193 patch (not including coloring, etc.).
1193 patch (not including coloring, etc.).
1194
1194
1195 if ignorefolding is True, then folded items are printed out.
1195 if ignorefolding is True, then folded items are printed out.
1196
1196
1197 if recursechildren is False, then only print the item without its
1197 if recursechildren is False, then only print the item without its
1198 child items.
1198 child items.
1199
1199
1200 """
1200 """
1201 if towin and self.outofdisplayedarea():
1201 if towin and self.outofdisplayedarea():
1202 return
1202 return
1203
1203
1204 selected = self.handleselection(item, recursechildren)
1204 selected = self.handleselection(item, recursechildren)
1205
1205
1206 # patch object is a list of headers
1206 # patch object is a list of headers
1207 if isinstance(item, patch):
1207 if isinstance(item, patch):
1208 if recursechildren:
1208 if recursechildren:
1209 for hdr in item:
1209 for hdr in item:
1210 self.__printitem(hdr, ignorefolding,
1210 self.__printitem(hdr, ignorefolding,
1211 recursechildren, outstr, towin)
1211 recursechildren, outstr, towin)
1212 # todo: eliminate all isinstance() calls
1212 # todo: eliminate all isinstance() calls
1213 if isinstance(item, uiheader):
1213 if isinstance(item, uiheader):
1214 outstr.append(self.printheader(item, selected, towin=towin,
1214 outstr.append(self.printheader(item, selected, towin=towin,
1215 ignorefolding=ignorefolding))
1215 ignorefolding=ignorefolding))
1216 if recursechildren:
1216 if recursechildren:
1217 for hnk in item.hunks:
1217 for hnk in item.hunks:
1218 self.__printitem(hnk, ignorefolding,
1218 self.__printitem(hnk, ignorefolding,
1219 recursechildren, outstr, towin)
1219 recursechildren, outstr, towin)
1220 elif (isinstance(item, uihunk) and
1220 elif (isinstance(item, uihunk) and
1221 ((not item.header.folded) or ignorefolding)):
1221 ((not item.header.folded) or ignorefolding)):
1222 # print the hunk data which comes before the changed-lines
1222 # print the hunk data which comes before the changed-lines
1223 outstr.append(self.printhunklinesbefore(item, selected, towin=towin,
1223 outstr.append(self.printhunklinesbefore(item, selected, towin=towin,
1224 ignorefolding=ignorefolding))
1224 ignorefolding=ignorefolding))
1225 if recursechildren:
1225 if recursechildren:
1226 for l in item.changedlines:
1226 for l in item.changedlines:
1227 self.__printitem(l, ignorefolding,
1227 self.__printitem(l, ignorefolding,
1228 recursechildren, outstr, towin)
1228 recursechildren, outstr, towin)
1229 outstr.append(self.printhunklinesafter(item, towin=towin,
1229 outstr.append(self.printhunklinesafter(item, towin=towin,
1230 ignorefolding=ignorefolding))
1230 ignorefolding=ignorefolding))
1231 elif (isinstance(item, uihunkline) and
1231 elif (isinstance(item, uihunkline) and
1232 ((not item.hunk.folded) or ignorefolding)):
1232 ((not item.hunk.folded) or ignorefolding)):
1233 outstr.append(self.printhunkchangedline(item, selected,
1233 outstr.append(self.printhunkchangedline(item, selected,
1234 towin=towin))
1234 towin=towin))
1235
1235
1236 return outstr
1236 return outstr
1237
1237
1238 def getnumlinesdisplayed(self, item=None, ignorefolding=False,
1238 def getnumlinesdisplayed(self, item=None, ignorefolding=False,
1239 recursechildren=True):
1239 recursechildren=True):
1240 """
1240 """
1241 return the number of lines which would be displayed if the item were
1241 return the number of lines which would be displayed if the item were
1242 to be printed to the display. the item will not be printed to the
1242 to be printed to the display. the item will not be printed to the
1243 display (pad).
1243 display (pad).
1244 if no item is given, assume the entire patch.
1244 if no item is given, assume the entire patch.
1245 if ignorefolding is True, folded items will be unfolded when counting
1245 if ignorefolding is True, folded items will be unfolded when counting
1246 the number of lines.
1246 the number of lines.
1247
1247
1248 """
1248 """
1249 # temporarily disable printing to windows by printstring
1249 # temporarily disable printing to windows by printstring
1250 patchdisplaystring = self.printitem(item, ignorefolding,
1250 patchdisplaystring = self.printitem(item, ignorefolding,
1251 recursechildren, towin=False)
1251 recursechildren, towin=False)
1252 numlines = len(patchdisplaystring) / self.xscreensize
1252 numlines = len(patchdisplaystring) / self.xscreensize
1253 return numlines
1253 return numlines
1254
1254
1255 def sigwinchhandler(self, n, frame):
1255 def sigwinchhandler(self, n, frame):
1256 "handle window resizing"
1256 "handle window resizing"
1257 try:
1257 try:
1258 curses.endwin()
1258 curses.endwin()
1259 self.yscreensize, self.xscreensize = gethw()
1259 self.yscreensize, self.xscreensize = gethw()
1260 self.statuswin.resize(self.numstatuslines, self.xscreensize)
1260 self.statuswin.resize(self.numstatuslines, self.xscreensize)
1261 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1261 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1262 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1262 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1263 # todo: try to resize commit message window if possible
1263 # todo: try to resize commit message window if possible
1264 except curses.error:
1264 except curses.error:
1265 pass
1265 pass
1266
1266
1267 def getcolorpair(self, fgcolor=None, bgcolor=None, name=None,
1267 def getcolorpair(self, fgcolor=None, bgcolor=None, name=None,
1268 attrlist=None):
1268 attrlist=None):
1269 """
1269 """
1270 get a curses color pair, adding it to self.colorpairs if it is not
1270 get a curses color pair, adding it to self.colorpairs if it is not
1271 already defined. an optional string, name, can be passed as a shortcut
1271 already defined. an optional string, name, can be passed as a shortcut
1272 for referring to the color-pair. by default, if no arguments are
1272 for referring to the color-pair. by default, if no arguments are
1273 specified, the white foreground / black background color-pair is
1273 specified, the white foreground / black background color-pair is
1274 returned.
1274 returned.
1275
1275
1276 it is expected that this function will be used exclusively for
1276 it is expected that this function will be used exclusively for
1277 initializing color pairs, and not curses.init_pair().
1277 initializing color pairs, and not curses.init_pair().
1278
1278
1279 attrlist is used to 'flavor' the returned color-pair. this information
1279 attrlist is used to 'flavor' the returned color-pair. this information
1280 is not stored in self.colorpairs. it contains attribute values like
1280 is not stored in self.colorpairs. it contains attribute values like
1281 curses.A_BOLD.
1281 curses.A_BOLD.
1282
1282
1283 """
1283 """
1284 if (name is not None) and name in self.colorpairnames:
1284 if (name is not None) and name in self.colorpairnames:
1285 # then get the associated color pair and return it
1285 # then get the associated color pair and return it
1286 colorpair = self.colorpairnames[name]
1286 colorpair = self.colorpairnames[name]
1287 else:
1287 else:
1288 if fgcolor is None:
1288 if fgcolor is None:
1289 fgcolor = -1
1289 fgcolor = -1
1290 if bgcolor is None:
1290 if bgcolor is None:
1291 bgcolor = -1
1291 bgcolor = -1
1292 if (fgcolor, bgcolor) in self.colorpairs:
1292 if (fgcolor, bgcolor) in self.colorpairs:
1293 colorpair = self.colorpairs[(fgcolor, bgcolor)]
1293 colorpair = self.colorpairs[(fgcolor, bgcolor)]
1294 else:
1294 else:
1295 pairindex = len(self.colorpairs) + 1
1295 pairindex = len(self.colorpairs) + 1
1296 curses.init_pair(pairindex, fgcolor, bgcolor)
1296 curses.init_pair(pairindex, fgcolor, bgcolor)
1297 colorpair = self.colorpairs[(fgcolor, bgcolor)] = (
1297 colorpair = self.colorpairs[(fgcolor, bgcolor)] = (
1298 curses.color_pair(pairindex))
1298 curses.color_pair(pairindex))
1299 if name is not None:
1299 if name is not None:
1300 self.colorpairnames[name] = curses.color_pair(pairindex)
1300 self.colorpairnames[name] = curses.color_pair(pairindex)
1301
1301
1302 # add attributes if possible
1302 # add attributes if possible
1303 if attrlist is None:
1303 if attrlist is None:
1304 attrlist = []
1304 attrlist = []
1305 if colorpair < 256:
1305 if colorpair < 256:
1306 # then it is safe to apply all attributes
1306 # then it is safe to apply all attributes
1307 for textattr in attrlist:
1307 for textattr in attrlist:
1308 colorpair |= textattr
1308 colorpair |= textattr
1309 else:
1309 else:
1310 # just apply a select few (safe?) attributes
1310 # just apply a select few (safe?) attributes
1311 for textattrib in (curses.A_UNDERLINE, curses.A_BOLD):
1311 for textattrib in (curses.A_UNDERLINE, curses.A_BOLD):
1312 if textattrib in attrlist:
1312 if textattrib in attrlist:
1313 colorpair |= textattrib
1313 colorpair |= textattrib
1314 return colorpair
1314 return colorpair
1315
1315
1316 def initcolorpair(self, *args, **kwargs):
1316 def initcolorpair(self, *args, **kwargs):
1317 "same as getcolorpair."
1317 "same as getcolorpair."
1318 self.getcolorpair(*args, **kwargs)
1318 self.getcolorpair(*args, **kwargs)
1319
1319
1320 def helpwindow(self):
1320 def helpwindow(self):
1321 "print a help window to the screen. exit after any keypress."
1321 "print a help window to the screen. exit after any keypress."
1322 helptext = """ [press any key to return to the patch-display]
1322 helptext = """ [press any key to return to the patch-display]
1323
1323
1324 crecord allows you to interactively choose among the changes you have made,
1324 crecord allows you to interactively choose among the changes you have made,
1325 and confirm only those changes you select for further processing by the command
1325 and confirm only those changes you select for further processing by the command
1326 you are running (commit/shelve/revert), after confirming the selected
1326 you are running (commit/shelve/revert), after confirming the selected
1327 changes, the unselected changes are still present in your working copy, so you
1327 changes, the unselected changes are still present in your working copy, so you
1328 can use crecord multiple times to split large changes into smaller changesets.
1328 can use crecord multiple times to split large changes into smaller changesets.
1329 the following are valid keystrokes:
1329 the following are valid keystrokes:
1330
1330
1331 [space] : (un-)select item ([~]/[x] = partly/fully applied)
1331 [space] : (un-)select item ([~]/[x] = partly/fully applied)
1332 a : (un-)select all items
1332 a : (un-)select all items
1333 up/down-arrow [k/j] : go to previous/next unfolded item
1333 up/down-arrow [k/j] : go to previous/next unfolded item
1334 pgup/pgdn [k/j] : go to previous/next item of same type
1334 pgup/pgdn [k/j] : go to previous/next item of same type
1335 right/left-arrow [l/h] : go to child item / parent item
1335 right/left-arrow [l/h] : go to child item / parent item
1336 shift-left-arrow [h] : go to parent header / fold selected header
1336 shift-left-arrow [h] : go to parent header / fold selected header
1337 f : fold / unfold item, hiding/revealing its children
1337 f : fold / unfold item, hiding/revealing its children
1338 f : fold / unfold parent item and all of its ancestors
1338 f : fold / unfold parent item and all of its ancestors
1339 m : edit / resume editing the commit message
1339 m : edit / resume editing the commit message
1340 e : edit the currently selected hunk
1340 e : edit the currently selected hunk
1341 a : toggle amend mode (hg rev >= 2.2)
1341 a : toggle amend mode (hg rev >= 2.2)
1342 c : confirm selected changes
1342 c : confirm selected changes
1343 r : review/edit and confirm selected changes
1343 r : review/edit and confirm selected changes
1344 q : quit without confirming (no changes will be made)
1344 q : quit without confirming (no changes will be made)
1345 ? : help (what you're currently reading)"""
1345 ? : help (what you're currently reading)"""
1346
1346
1347 helpwin = curses.newwin(self.yscreensize, 0, 0, 0)
1347 helpwin = curses.newwin(self.yscreensize, 0, 0, 0)
1348 helplines = helptext.split("\n")
1348 helplines = helptext.split("\n")
1349 helplines = helplines + [" "]*(
1349 helplines = helplines + [" "]*(
1350 self.yscreensize - self.numstatuslines - len(helplines) - 1)
1350 self.yscreensize - self.numstatuslines - len(helplines) - 1)
1351 try:
1351 try:
1352 for line in helplines:
1352 for line in helplines:
1353 self.printstring(helpwin, line, pairname="legend")
1353 self.printstring(helpwin, line, pairname="legend")
1354 except curses.error:
1354 except curses.error:
1355 pass
1355 pass
1356 helpwin.refresh()
1356 helpwin.refresh()
1357 try:
1357 try:
1358 helpwin.getkey()
1358 helpwin.getkey()
1359 except curses.error:
1359 except curses.error:
1360 pass
1360 pass
1361
1361
1362 def confirmationwindow(self, windowtext):
1362 def confirmationwindow(self, windowtext):
1363 "display an informational window, then wait for and return a keypress."
1363 "display an informational window, then wait for and return a keypress."
1364
1364
1365 confirmwin = curses.newwin(self.yscreensize, 0, 0, 0)
1365 confirmwin = curses.newwin(self.yscreensize, 0, 0, 0)
1366 try:
1366 try:
1367 lines = windowtext.split("\n")
1367 lines = windowtext.split("\n")
1368 for line in lines:
1368 for line in lines:
1369 self.printstring(confirmwin, line, pairname="selected")
1369 self.printstring(confirmwin, line, pairname="selected")
1370 except curses.error:
1370 except curses.error:
1371 pass
1371 pass
1372 self.stdscr.refresh()
1372 self.stdscr.refresh()
1373 confirmwin.refresh()
1373 confirmwin.refresh()
1374 try:
1374 try:
1375 response = chr(self.stdscr.getch())
1375 response = chr(self.stdscr.getch())
1376 except ValueError:
1376 except ValueError:
1377 response = None
1377 response = None
1378
1378
1379 return response
1379 return response
1380
1380
1381 def confirmcommit(self, review=False):
1381 def confirmcommit(self, review=False):
1382 """ask for 'y' to be pressed to confirm selected. return True if
1382 """ask for 'y' to be pressed to confirm selected. return True if
1383 confirmed."""
1383 confirmed."""
1384 if review:
1384 if review:
1385 confirmtext = (
1385 confirmtext = (
1386 """if you answer yes to the following, the your currently chosen patch chunks
1386 """if you answer yes to the following, the your currently chosen patch chunks
1387 will be loaded into an editor. you may modify the patch from the editor, and
1387 will be loaded into an editor. you may modify the patch from the editor, and
1388 save the changes if you wish to change the patch. otherwise, you can just
1388 save the changes if you wish to change the patch. otherwise, you can just
1389 close the editor without saving to accept the current patch as-is.
1389 close the editor without saving to accept the current patch as-is.
1390
1390
1391 note: don't add/remove lines unless you also modify the range information.
1391 note: don't add/remove lines unless you also modify the range information.
1392 failing to follow this rule will result in the commit aborting.
1392 failing to follow this rule will result in the commit aborting.
1393
1393
1394 are you sure you want to review/edit and confirm the selected changes [yn]?
1394 are you sure you want to review/edit and confirm the selected changes [yn]?
1395 """)
1395 """)
1396 else:
1396 else:
1397 confirmtext = (
1397 confirmtext = (
1398 "are you sure you want to confirm the selected changes [yn]? ")
1398 "are you sure you want to confirm the selected changes [yn]? ")
1399
1399
1400 response = self.confirmationwindow(confirmtext)
1400 response = self.confirmationwindow(confirmtext)
1401 if response is None:
1401 if response is None:
1402 response = "n"
1402 response = "n"
1403 if response.lower().startswith("y"):
1403 if response.lower().startswith("y"):
1404 return True
1404 return True
1405 else:
1405 else:
1406 return False
1406 return False
1407
1407
1408 def recenterdisplayedarea(self):
1408 def recenterdisplayedarea(self):
1409 """
1409 """
1410 once we scrolled with pg up pg down we can be pointing outside of the
1410 once we scrolled with pg up pg down we can be pointing outside of the
1411 display zone. we print the patch with towin=False to compute the
1411 display zone. we print the patch with towin=False to compute the
1412 location of the selected item eventhough it is outside of the displayed
1412 location of the selected item eventhough it is outside of the displayed
1413 zone and then update the scroll.
1413 zone and then update the scroll.
1414 """
1414 """
1415 self.printitem(towin=False)
1415 self.printitem(towin=False)
1416 self.updatescroll()
1416 self.updatescroll()
1417
1417
1418 def toggleedit(self, item=None, test=False):
1418 def toggleedit(self, item=None, test=False):
1419 """
1419 """
1420 edit the currently chelected chunk
1420 edit the currently chelected chunk
1421 """
1421 """
1422
1422
1423 def editpatchwitheditor(self, chunk):
1423 def editpatchwitheditor(self, chunk):
1424 if chunk is None:
1424 if chunk is None:
1425 self.ui.write(_('cannot edit patch for whole file'))
1425 self.ui.write(_('cannot edit patch for whole file'))
1426 self.ui.write("\n")
1426 self.ui.write("\n")
1427 return None
1427 return None
1428 if chunk.header.binary():
1428 if chunk.header.binary():
1429 self.ui.write(_('cannot edit patch for binary file'))
1429 self.ui.write(_('cannot edit patch for binary file'))
1430 self.ui.write("\n")
1430 self.ui.write("\n")
1431 return None
1431 return None
1432 # patch comment based on the git one (based on comment at end of
1432 # patch comment based on the git one (based on comment at end of
1433 # http://mercurial.selenic.com/wiki/recordextension)
1433 # http://mercurial.selenic.com/wiki/recordextension)
1434 phelp = '---' + _("""
1434 phelp = '---' + _("""
1435 to remove '-' lines, make them ' ' lines (context).
1435 to remove '-' lines, make them ' ' lines (context).
1436 to remove '+' lines, delete them.
1436 to remove '+' lines, delete them.
1437 lines starting with # will be removed from the patch.
1437 lines starting with # will be removed from the patch.
1438
1438
1439 if the patch applies cleanly, the edited hunk will immediately be
1439 if the patch applies cleanly, the edited hunk will immediately be
1440 added to the record list. if it does not apply cleanly, a rejects
1440 added to the record list. if it does not apply cleanly, a rejects
1441 file will be generated: you can use that when you try again. if
1441 file will be generated: you can use that when you try again. if
1442 all lines of the hunk are removed, then the edit is aborted and
1442 all lines of the hunk are removed, then the edit is aborted and
1443 the hunk is left unchanged.
1443 the hunk is left unchanged.
1444 """)
1444 """)
1445 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
1445 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
1446 suffix=".diff", text=True)
1446 suffix=".diff", text=True)
1447 ncpatchfp = None
1447 ncpatchfp = None
1448 try:
1448 try:
1449 # write the initial patch
1449 # write the initial patch
1450 f = os.fdopen(patchfd, "w")
1450 f = os.fdopen(patchfd, "w")
1451 chunk.header.write(f)
1451 chunk.header.write(f)
1452 chunk.write(f)
1452 chunk.write(f)
1453 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1453 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1454 f.close()
1454 f.close()
1455 # start the editor and wait for it to complete
1455 # start the editor and wait for it to complete
1456 editor = self.ui.geteditor()
1456 editor = self.ui.geteditor()
1457 self.ui.system("%s \"%s\"" % (editor, patchfn),
1457 self.ui.system("%s \"%s\"" % (editor, patchfn),
1458 environ={'hguser': self.ui.username()},
1458 environ={'hguser': self.ui.username()},
1459 onerr=util.Abort, errprefix=_("edit failed"))
1459 onerr=util.Abort, errprefix=_("edit failed"))
1460 # remove comment lines
1460 # remove comment lines
1461 patchfp = open(patchfn)
1461 patchfp = open(patchfn)
1462 ncpatchfp = cStringIO.StringIO()
1462 ncpatchfp = cStringIO.StringIO()
1463 for line in patchfp:
1463 for line in patchfp:
1464 if not line.startswith('#'):
1464 if not line.startswith('#'):
1465 ncpatchfp.write(line)
1465 ncpatchfp.write(line)
1466 patchfp.close()
1466 patchfp.close()
1467 ncpatchfp.seek(0)
1467 ncpatchfp.seek(0)
1468 newpatches = patchmod.parsepatch(ncpatchfp)
1468 newpatches = patchmod.parsepatch(ncpatchfp)
1469 finally:
1469 finally:
1470 os.unlink(patchfn)
1470 os.unlink(patchfn)
1471 del ncpatchfp
1471 del ncpatchfp
1472 return newpatches
1472 return newpatches
1473 if item is None:
1473 if item is None:
1474 item = self.currentselecteditem
1474 item = self.currentselecteditem
1475 if isinstance(item, uiheader):
1475 if isinstance(item, uiheader):
1476 return
1476 return
1477 if isinstance(item, uihunkline):
1477 if isinstance(item, uihunkline):
1478 item = item.parentitem()
1478 item = item.parentitem()
1479 if not isinstance(item, uihunk):
1479 if not isinstance(item, uihunk):
1480 return
1480 return
1481
1481
1482 beforeadded, beforeremoved = item.added, item.removed
1482 beforeadded, beforeremoved = item.added, item.removed
1483 newpatches = editpatchwitheditor(self, item)
1483 newpatches = editpatchwitheditor(self, item)
1484 header = item.header
1484 header = item.header
1485 editedhunkindex = header.hunks.index(item)
1485 editedhunkindex = header.hunks.index(item)
1486 hunksbefore = header.hunks[:editedhunkindex]
1486 hunksbefore = header.hunks[:editedhunkindex]
1487 hunksafter = header.hunks[editedhunkindex + 1:]
1487 hunksafter = header.hunks[editedhunkindex + 1:]
1488 newpatchheader = newpatches[0]
1488 newpatchheader = newpatches[0]
1489 newhunks = [uihunk(h, header) for h in newpatchheader.hunks]
1489 newhunks = [uihunk(h, header) for h in newpatchheader.hunks]
1490 newadded = sum([h.added for h in newhunks])
1490 newadded = sum([h.added for h in newhunks])
1491 newremoved = sum([h.removed for h in newhunks])
1491 newremoved = sum([h.removed for h in newhunks])
1492 offset = (newadded - beforeadded) - (newremoved - beforeremoved)
1492 offset = (newadded - beforeadded) - (newremoved - beforeremoved)
1493
1493
1494 for h in hunksafter:
1494 for h in hunksafter:
1495 h.toline += offset
1495 h.toline += offset
1496 for h in newhunks:
1496 for h in newhunks:
1497 h.folded = False
1497 h.folded = False
1498 header.hunks = hunksbefore + newhunks + hunksafter
1498 header.hunks = hunksbefore + newhunks + hunksafter
1499 if self.emptypatch():
1499 if self.emptypatch():
1500 header.hunks = hunksbefore + [item] + hunksafter
1500 header.hunks = hunksbefore + [item] + hunksafter
1501 self.currentselecteditem = header
1501 self.currentselecteditem = header
1502
1502
1503 if not test:
1503 if not test:
1504 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1504 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1505 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1505 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1506 self.updatescroll()
1506 self.updatescroll()
1507 self.stdscr.refresh()
1507 self.stdscr.refresh()
1508 self.statuswin.refresh()
1508 self.statuswin.refresh()
1509 self.stdscr.keypad(1)
1509 self.stdscr.keypad(1)
1510
1510
1511 def emptypatch(self):
1511 def emptypatch(self):
1512 item = self.headerlist
1512 item = self.headerlist
1513 if not item:
1513 if not item:
1514 return True
1514 return True
1515 for header in item:
1515 for header in item:
1516 if header.hunks:
1516 if header.hunks:
1517 return False
1517 return False
1518 return True
1518 return True
1519
1519
1520 def handlekeypressed(self, keypressed, test=False):
1520 def handlekeypressed(self, keypressed, test=False):
1521 if keypressed in ["k", "KEY_UP"]:
1521 if keypressed in ["k", "KEY_UP"]:
1522 self.uparrowevent()
1522 self.uparrowevent()
1523 if keypressed in ["k", "KEY_PPAGE"]:
1523 if keypressed in ["k", "KEY_PPAGE"]:
1524 self.uparrowshiftevent()
1524 self.uparrowshiftevent()
1525 elif keypressed in ["j", "KEY_DOWN"]:
1525 elif keypressed in ["j", "KEY_DOWN"]:
1526 self.downarrowevent()
1526 self.downarrowevent()
1527 elif keypressed in ["j", "KEY_NPAGE"]:
1527 elif keypressed in ["j", "KEY_NPAGE"]:
1528 self.downarrowshiftevent()
1528 self.downarrowshiftevent()
1529 elif keypressed in ["l", "KEY_RIGHT"]:
1529 elif keypressed in ["l", "KEY_RIGHT"]:
1530 self.rightarrowevent()
1530 self.rightarrowevent()
1531 elif keypressed in ["h", "KEY_LEFT"]:
1531 elif keypressed in ["h", "KEY_LEFT"]:
1532 self.leftarrowevent()
1532 self.leftarrowevent()
1533 elif keypressed in ["h", "KEY_SLEFT"]:
1533 elif keypressed in ["h", "KEY_SLEFT"]:
1534 self.leftarrowshiftevent()
1534 self.leftarrowshiftevent()
1535 elif keypressed in ["q"]:
1535 elif keypressed in ["q"]:
1536 raise util.Abort(_('user quit'))
1536 raise util.Abort(_('user quit'))
1537 elif keypressed in ["c"]:
1537 elif keypressed in ["c"]:
1538 if self.confirmcommit():
1538 if self.confirmcommit():
1539 return True
1539 return True
1540 elif keypressed in ["r"]:
1540 elif keypressed in ["r"]:
1541 if self.confirmcommit(review=True):
1541 if self.confirmcommit(review=True):
1542 return True
1542 return True
1543 elif test and keypressed in ['X']:
1543 elif test and keypressed in ['X']:
1544 return True
1544 return True
1545 elif keypressed in [' '] or (test and keypressed in ["TOGGLE"]):
1545 elif keypressed in [' '] or (test and keypressed in ["TOGGLE"]):
1546 self.toggleapply()
1546 self.toggleapply()
1547 elif keypressed in ['A']:
1547 elif keypressed in ['A']:
1548 self.toggleall()
1548 self.toggleall()
1549 elif keypressed in ['e']:
1549 elif keypressed in ['e']:
1550 self.toggleedit(test=test)
1550 self.toggleedit(test=test)
1551 elif keypressed in ["f"]:
1551 elif keypressed in ["f"]:
1552 self.togglefolded()
1552 self.togglefolded()
1553 elif keypressed in ["f"]:
1553 elif keypressed in ["f"]:
1554 self.togglefolded(foldparent=True)
1554 self.togglefolded(foldparent=True)
1555 elif keypressed in ["?"]:
1555 elif keypressed in ["?"]:
1556 self.helpwindow()
1556 self.helpwindow()
1557 self.stdscr.clear()
1558 self.stdscr.refresh()
1557
1559
1558 def main(self, stdscr):
1560 def main(self, stdscr):
1559 """
1561 """
1560 method to be wrapped by curses.wrapper() for selecting chunks.
1562 method to be wrapped by curses.wrapper() for selecting chunks.
1561
1563
1562 """
1564 """
1563 signal.signal(signal.SIGWINCH, self.sigwinchhandler)
1565 signal.signal(signal.SIGWINCH, self.sigwinchhandler)
1564 self.stdscr = stdscr
1566 self.stdscr = stdscr
1565 self.yscreensize, self.xscreensize = self.stdscr.getmaxyx()
1567 self.yscreensize, self.xscreensize = self.stdscr.getmaxyx()
1566
1568
1567 curses.start_color()
1569 curses.start_color()
1568 curses.use_default_colors()
1570 curses.use_default_colors()
1569
1571
1570 # available colors: black, blue, cyan, green, magenta, white, yellow
1572 # available colors: black, blue, cyan, green, magenta, white, yellow
1571 # init_pair(color_id, foreground_color, background_color)
1573 # init_pair(color_id, foreground_color, background_color)
1572 self.initcolorpair(None, None, name="normal")
1574 self.initcolorpair(None, None, name="normal")
1573 self.initcolorpair(curses.COLOR_WHITE, curses.COLOR_MAGENTA,
1575 self.initcolorpair(curses.COLOR_WHITE, curses.COLOR_MAGENTA,
1574 name="selected")
1576 name="selected")
1575 self.initcolorpair(curses.COLOR_RED, None, name="deletion")
1577 self.initcolorpair(curses.COLOR_RED, None, name="deletion")
1576 self.initcolorpair(curses.COLOR_GREEN, None, name="addition")
1578 self.initcolorpair(curses.COLOR_GREEN, None, name="addition")
1577 self.initcolorpair(curses.COLOR_WHITE, curses.COLOR_BLUE, name="legend")
1579 self.initcolorpair(curses.COLOR_WHITE, curses.COLOR_BLUE, name="legend")
1578 # newwin([height, width,] begin_y, begin_x)
1580 # newwin([height, width,] begin_y, begin_x)
1579 self.statuswin = curses.newwin(self.numstatuslines, 0, 0, 0)
1581 self.statuswin = curses.newwin(self.numstatuslines, 0, 0, 0)
1580 self.statuswin.keypad(1) # interpret arrow-key, etc. esc sequences
1582 self.statuswin.keypad(1) # interpret arrow-key, etc. esc sequences
1581
1583
1582 # figure out how much space to allocate for the chunk-pad which is
1584 # figure out how much space to allocate for the chunk-pad which is
1583 # used for displaying the patch
1585 # used for displaying the patch
1584
1586
1585 # stupid hack to prevent getnumlinesdisplayed from failing
1587 # stupid hack to prevent getnumlinesdisplayed from failing
1586 self.chunkpad = curses.newpad(1, self.xscreensize)
1588 self.chunkpad = curses.newpad(1, self.xscreensize)
1587
1589
1588 # add 1 so to account for last line text reaching end of line
1590 # add 1 so to account for last line text reaching end of line
1589 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1591 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1590 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1592 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1591
1593
1592 # initialize selecteitemendline (initial start-line is 0)
1594 # initialize selecteitemendline (initial start-line is 0)
1593 self.selecteditemendline = self.getnumlinesdisplayed(
1595 self.selecteditemendline = self.getnumlinesdisplayed(
1594 self.currentselecteditem, recursechildren=False)
1596 self.currentselecteditem, recursechildren=False)
1595
1597
1596 while True:
1598 while True:
1597 self.updatescreen()
1599 self.updatescreen()
1598 try:
1600 try:
1599 keypressed = self.statuswin.getkey()
1601 keypressed = self.statuswin.getkey()
1600 except curses.error:
1602 except curses.error:
1601 keypressed = "foobar"
1603 keypressed = "foobar"
1602 if self.handlekeypressed(keypressed):
1604 if self.handlekeypressed(keypressed):
1603 break
1605 break
General Comments 0
You need to be logged in to leave comments. Login now