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