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