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