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