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