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