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