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