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