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