33 the item, but this can be changed by adding either ``-<num>`` which |
33 the item, but this can be changed by adding either ``-<num>`` which |
34 would take the last num characters, or ``+<num>`` for the first num |
34 would take the last num characters, or ``+<num>`` for the first num |
35 characters. |
35 characters. |
36 """ |
36 """ |
37 |
37 |
38 import sys |
38 from mercurial import progress |
39 import time |
|
40 import threading |
|
41 |
|
42 from mercurial.i18n import _ |
|
43 # Note for extension authors: ONLY specify testedwith = 'internal' for |
|
44 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
|
45 # be specifying the version(s) of Mercurial they are tested with, or |
|
46 # leave the attribute unspecified. |
|
47 testedwith = 'internal' |
|
48 |
|
49 from mercurial import encoding |
|
50 |
|
51 def spacejoin(*args): |
|
52 return ' '.join(s for s in args if s) |
|
53 |
|
54 def shouldprint(ui): |
|
55 return not ui.plain() and (ui._isatty(sys.stderr) or |
|
56 ui.configbool('progress', 'assume-tty')) |
|
57 |
|
58 def fmtremaining(seconds): |
|
59 if seconds < 60: |
|
60 # i18n: format XX seconds as "XXs" |
|
61 return _("%02ds") % (seconds) |
|
62 minutes = seconds // 60 |
|
63 if minutes < 60: |
|
64 seconds -= minutes * 60 |
|
65 # i18n: format X minutes and YY seconds as "XmYYs" |
|
66 return _("%dm%02ds") % (minutes, seconds) |
|
67 # we're going to ignore seconds in this case |
|
68 minutes += 1 |
|
69 hours = minutes // 60 |
|
70 minutes -= hours * 60 |
|
71 if hours < 30: |
|
72 # i18n: format X hours and YY minutes as "XhYYm" |
|
73 return _("%dh%02dm") % (hours, minutes) |
|
74 # we're going to ignore minutes in this case |
|
75 hours += 1 |
|
76 days = hours // 24 |
|
77 hours -= days * 24 |
|
78 if days < 15: |
|
79 # i18n: format X days and YY hours as "XdYYh" |
|
80 return _("%dd%02dh") % (days, hours) |
|
81 # we're going to ignore hours in this case |
|
82 days += 1 |
|
83 weeks = days // 7 |
|
84 days -= weeks * 7 |
|
85 if weeks < 55: |
|
86 # i18n: format X weeks and YY days as "XwYYd" |
|
87 return _("%dw%02dd") % (weeks, days) |
|
88 # we're going to ignore days and treat a year as 52 weeks |
|
89 weeks += 1 |
|
90 years = weeks // 52 |
|
91 weeks -= years * 52 |
|
92 # i18n: format X years and YY weeks as "XyYYw" |
|
93 return _("%dy%02dw") % (years, weeks) |
|
94 |
|
95 class progbar(object): |
|
96 def __init__(self, ui): |
|
97 self.ui = ui |
|
98 self._refreshlock = threading.Lock() |
|
99 self.resetstate() |
|
100 |
|
101 def resetstate(self): |
|
102 self.topics = [] |
|
103 self.topicstates = {} |
|
104 self.starttimes = {} |
|
105 self.startvals = {} |
|
106 self.printed = False |
|
107 self.lastprint = time.time() + float(self.ui.config( |
|
108 'progress', 'delay', default=3)) |
|
109 self.curtopic = None |
|
110 self.lasttopic = None |
|
111 self.indetcount = 0 |
|
112 self.refresh = float(self.ui.config( |
|
113 'progress', 'refresh', default=0.1)) |
|
114 self.changedelay = max(3 * self.refresh, |
|
115 float(self.ui.config( |
|
116 'progress', 'changedelay', default=1))) |
|
117 self.order = self.ui.configlist( |
|
118 'progress', 'format', |
|
119 default=['topic', 'bar', 'number', 'estimate']) |
|
120 |
|
121 def show(self, now, topic, pos, item, unit, total): |
|
122 if not shouldprint(self.ui): |
|
123 return |
|
124 termwidth = self.width() |
|
125 self.printed = True |
|
126 head = '' |
|
127 needprogress = False |
|
128 tail = '' |
|
129 for indicator in self.order: |
|
130 add = '' |
|
131 if indicator == 'topic': |
|
132 add = topic |
|
133 elif indicator == 'number': |
|
134 if total: |
|
135 add = ('% ' + str(len(str(total))) + |
|
136 's/%s') % (pos, total) |
|
137 else: |
|
138 add = str(pos) |
|
139 elif indicator.startswith('item') and item: |
|
140 slice = 'end' |
|
141 if '-' in indicator: |
|
142 wid = int(indicator.split('-')[1]) |
|
143 elif '+' in indicator: |
|
144 slice = 'beginning' |
|
145 wid = int(indicator.split('+')[1]) |
|
146 else: |
|
147 wid = 20 |
|
148 if slice == 'end': |
|
149 add = encoding.trim(item, wid, leftside=True) |
|
150 else: |
|
151 add = encoding.trim(item, wid) |
|
152 add += (wid - encoding.colwidth(add)) * ' ' |
|
153 elif indicator == 'bar': |
|
154 add = '' |
|
155 needprogress = True |
|
156 elif indicator == 'unit' and unit: |
|
157 add = unit |
|
158 elif indicator == 'estimate': |
|
159 add = self.estimate(topic, pos, total, now) |
|
160 elif indicator == 'speed': |
|
161 add = self.speed(topic, pos, unit, now) |
|
162 if not needprogress: |
|
163 head = spacejoin(head, add) |
|
164 else: |
|
165 tail = spacejoin(tail, add) |
|
166 if needprogress: |
|
167 used = 0 |
|
168 if head: |
|
169 used += encoding.colwidth(head) + 1 |
|
170 if tail: |
|
171 used += encoding.colwidth(tail) + 1 |
|
172 progwidth = termwidth - used - 3 |
|
173 if total and pos <= total: |
|
174 amt = pos * progwidth // total |
|
175 bar = '=' * (amt - 1) |
|
176 if amt > 0: |
|
177 bar += '>' |
|
178 bar += ' ' * (progwidth - amt) |
|
179 else: |
|
180 progwidth -= 3 |
|
181 self.indetcount += 1 |
|
182 # mod the count by twice the width so we can make the |
|
183 # cursor bounce between the right and left sides |
|
184 amt = self.indetcount % (2 * progwidth) |
|
185 amt -= progwidth |
|
186 bar = (' ' * int(progwidth - abs(amt)) + '<=>' + |
|
187 ' ' * int(abs(amt))) |
|
188 prog = ''.join(('[', bar , ']')) |
|
189 out = spacejoin(head, prog, tail) |
|
190 else: |
|
191 out = spacejoin(head, tail) |
|
192 sys.stderr.write('\r' + encoding.trim(out, termwidth)) |
|
193 self.lasttopic = topic |
|
194 sys.stderr.flush() |
|
195 |
|
196 def clear(self): |
|
197 if not shouldprint(self.ui): |
|
198 return |
|
199 sys.stderr.write('\r%s\r' % (' ' * self.width())) |
|
200 |
|
201 def complete(self): |
|
202 if not shouldprint(self.ui): |
|
203 return |
|
204 if self.ui.configbool('progress', 'clear-complete', default=True): |
|
205 self.clear() |
|
206 else: |
|
207 sys.stderr.write('\n') |
|
208 sys.stderr.flush() |
|
209 |
|
210 def width(self): |
|
211 tw = self.ui.termwidth() |
|
212 return min(int(self.ui.config('progress', 'width', default=tw)), tw) |
|
213 |
|
214 def estimate(self, topic, pos, total, now): |
|
215 if total is None: |
|
216 return '' |
|
217 initialpos = self.startvals[topic] |
|
218 target = total - initialpos |
|
219 delta = pos - initialpos |
|
220 if delta > 0: |
|
221 elapsed = now - self.starttimes[topic] |
|
222 if elapsed > float( |
|
223 self.ui.config('progress', 'estimate', default=2)): |
|
224 seconds = (elapsed * (target - delta)) // delta + 1 |
|
225 return fmtremaining(seconds) |
|
226 return '' |
|
227 |
|
228 def speed(self, topic, pos, unit, now): |
|
229 initialpos = self.startvals[topic] |
|
230 delta = pos - initialpos |
|
231 elapsed = now - self.starttimes[topic] |
|
232 if elapsed > float( |
|
233 self.ui.config('progress', 'estimate', default=2)): |
|
234 return _('%d %s/sec') % (delta / elapsed, unit) |
|
235 return '' |
|
236 |
|
237 def _oktoprint(self, now): |
|
238 '''Check if conditions are met to print - e.g. changedelay elapsed''' |
|
239 if (self.lasttopic is None # first time we printed |
|
240 # not a topic change |
|
241 or self.curtopic == self.lasttopic |
|
242 # it's been long enough we should print anyway |
|
243 or now - self.lastprint >= self.changedelay): |
|
244 return True |
|
245 else: |
|
246 return False |
|
247 |
|
248 def progress(self, topic, pos, item='', unit='', total=None): |
|
249 now = time.time() |
|
250 self._refreshlock.acquire() |
|
251 try: |
|
252 if pos is None: |
|
253 self.starttimes.pop(topic, None) |
|
254 self.startvals.pop(topic, None) |
|
255 self.topicstates.pop(topic, None) |
|
256 # reset the progress bar if this is the outermost topic |
|
257 if self.topics and self.topics[0] == topic and self.printed: |
|
258 self.complete() |
|
259 self.resetstate() |
|
260 # truncate the list of topics assuming all topics within |
|
261 # this one are also closed |
|
262 if topic in self.topics: |
|
263 self.topics = self.topics[:self.topics.index(topic)] |
|
264 # reset the last topic to the one we just unwound to, |
|
265 # so that higher-level topics will be stickier than |
|
266 # lower-level topics |
|
267 if self.topics: |
|
268 self.lasttopic = self.topics[-1] |
|
269 else: |
|
270 self.lasttopic = None |
|
271 else: |
|
272 if topic not in self.topics: |
|
273 self.starttimes[topic] = now |
|
274 self.startvals[topic] = pos |
|
275 self.topics.append(topic) |
|
276 self.topicstates[topic] = pos, item, unit, total |
|
277 self.curtopic = topic |
|
278 if now - self.lastprint >= self.refresh and self.topics: |
|
279 if self._oktoprint(now): |
|
280 self.lastprint = now |
|
281 self.show(now, topic, *self.topicstates[topic]) |
|
282 finally: |
|
283 self._refreshlock.release() |
|
284 |
39 |
285 _singleton = None |
40 _singleton = None |
286 |
41 |
287 def uisetup(ui): |
42 def uisetup(ui): |
288 global _singleton |
43 global _singleton |