monotone

monotone Mtn Source Tree

Root/ui.cc

1// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
2//
3// This program is made available under the GNU GPL version 2.0 or
4// greater. See the accompanying file COPYING for details.
5//
6// This program is distributed WITHOUT ANY WARRANTY; without even the
7// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8// PURPOSE.
9
10// this file contains a couple utilities to deal with the user
11// interface. the global user_interface object 'ui' owns clog, so no
12// writing to it directly!
13
14#include "config.h"
15
16#include "platform.hh"
17#include "sanity.hh"
18#include "ui.hh"
19#include "charset.hh"
20#include "simplestring_xform.hh"
21#include "constants.hh"
22
23#include <iostream>
24#include <fstream>
25#include <iomanip>
26#include <algorithm>
27#include <boost/lexical_cast.hpp>
28
29using std::clog;
30using std::cout;
31using std::endl;
32using std::ios_base;
33using std::locale;
34using std::make_pair;
35using std::map;
36using std::max;
37using std::ofstream;
38using std::string;
39using std::vector;
40
41using boost::lexical_cast;
42
43struct user_interface ui;
44
45ticker::ticker(string const & tickname, string const & s, size_t mod,
46 bool kilocount) :
47 ticks(0),
48 mod(mod),
49 total(0),
50 previous_total(0),
51 kilocount(kilocount),
52 use_total(false),
53 keyname(tickname),
54 name(_(tickname.c_str())),
55 shortname(s),
56 count_size(0)
57{
58 I(ui.tickers.find(keyname) == ui.tickers.end());
59 ui.tickers.insert(make_pair(keyname, this));
60}
61
62ticker::~ticker()
63{
64 I(ui.tickers.find(keyname) != ui.tickers.end());
65 if (ui.some_tick_is_dirty)
66 {
67 ui.write_ticks();
68 }
69 ui.tickers.erase(keyname);
70 ui.finish_ticking();
71}
72
73void
74ticker::operator++()
75{
76 I(ui.tickers.find(keyname) != ui.tickers.end());
77 ticks++;
78 ui.some_tick_is_dirty = true;
79 if (ticks % mod == 0)
80 ui.write_ticks();
81}
82
83void
84ticker::operator+=(size_t t)
85{
86 I(ui.tickers.find(keyname) != ui.tickers.end());
87 size_t old = ticks;
88
89 ticks += t;
90 if (t != 0)
91 {
92 ui.some_tick_is_dirty = true;
93 if (ticks % mod == 0 || (ticks / mod) > (old / mod))
94 ui.write_ticks();
95 }
96}
97
98
99tick_write_count::tick_write_count() : last_tick_len(0)
100{
101}
102
103tick_write_count::~tick_write_count()
104{
105}
106
107static string compose_count(ticker *tick, size_t ticks=0)
108{
109 string count;
110
111 if (ticks == 0)
112 {
113 ticks = tick->ticks;
114 }
115
116 if (tick->kilocount && ticks)
117 {
118 // automatic unit conversion is enabled
119 float div = 1.0;
120 const char *message;
121
122 if (ticks >= 1073741824)
123 {
124 div = 1073741824;
125 // xgettext: gibibytes (2^30 bytes)
126 message = N_("%.1f G");
127 }
128 else if (ticks >= 1048576)
129 {
130 div = 1048576;
131 // xgettext: mebibytes (2^20 bytes)
132 message = N_("%.1f M");
133 }
134 else if (ticks >= 1024)
135 {
136 div = 1024;
137 // xgettext: kibibytes (2^10 bytes)
138 message = N_("%.1f k");
139 }
140 else
141 {
142 div = 1;
143 message = "%.0f";
144 }
145 // We reset the mod to the divider, to avoid spurious screen updates.
146 tick->mod = max(static_cast<int>(div / 10.0), 1);
147 count = (F(message) % (ticks / div)).str();
148 }
149 else if (tick->use_total)
150 {
151 count = (F("%d/%d") % ticks % tick->total).str();
152 }
153 else
154 {
155 // xgettext: bytes
156 count = (F("%d") % ticks).str();
157 }
158
159 return count;
160}
161
162void tick_write_count::write_ticks()
163{
164 vector<size_t> tick_widths;
165 vector<string> tick_title_strings;
166 vector<string> tick_count_strings;
167
168 for (map<string,ticker *>::const_iterator i = ui.tickers.begin();
169 i != ui.tickers.end(); ++i)
170 {
171 ticker * tick = i->second;
172
173 if ((tick->count_size == 0 && tick->kilocount)
174 ||
175 (tick->use_total && tick->previous_total != tick->total))
176 {
177 if (!tick->kilocount && tick->use_total)
178 {
179 // We know that we're going to eventually have 'total'
180 // displayed twice on screen, plus a slash. So we should
181 // pad out this field to that eventual size to avoid
182 // spurious re-issuing of the tick titles as we expand to
183 // the goal.
184 tick->set_count_size(display_width(utf8(compose_count(tick,
185 tick->total))));
186 tick->previous_total = tick->total;
187 }
188 else
189 {
190 // To find out what the maximum size can be, choose one the
191 // the dividers from compose_count, subtract one and have
192 // compose_count create the count string for that. Use the
193 // size of the returned count string as an initial size for
194 // this tick.
195 tick->set_count_size(display_width(utf8(compose_count(tick,
196 1048575))));
197 }
198 }
199
200 string count(compose_count(tick));
201
202 size_t title_width = display_width(utf8(tick->name));
203 size_t count_width = display_width(utf8(count));
204
205 if (count_width > tick->count_size)
206 {
207 tick->set_count_size(count_width);
208 }
209
210 size_t max_width = max(title_width, tick->count_size);
211
212 string name;
213 name.append(max_width - title_width, ' ');
214 name.append(tick->name);
215
216 string count2;
217 count2.append(max_width - count_width, ' ');
218 count2.append(count);
219
220 tick_title_strings.push_back(name);
221 tick_count_strings.push_back(count2);
222 tick_widths.push_back(max_width);
223 }
224
225 string tickline1;
226 bool write_tickline1 = !(ui.last_write_was_a_tick
227 && (tick_widths == last_tick_widths));
228 if (write_tickline1)
229 {
230 // Reissue the titles if the widths have changed.
231 tickline1 = ui.output_prefix();
232 for (size_t i = 0; i < tick_widths.size(); ++i)
233 {
234 if (i != 0)
235 tickline1.append(" | ");
236 tickline1.append(idx(tick_title_strings, i));
237 }
238 last_tick_widths = tick_widths;
239 write_tickline1 = true;
240 }
241
242 // Always reissue the counts.
243 string tickline2 = ui.output_prefix();
244 for (size_t i = 0; i < tick_widths.size(); ++i)
245 {
246 if (i != 0)
247 tickline2.append(" | ");
248 tickline2.append(idx(tick_count_strings, i));
249 }
250
251 if (!ui.tick_trailer.empty())
252 {
253 tickline2 += " ";
254 tickline2 += ui.tick_trailer;
255 }
256
257 size_t curr_sz = display_width(utf8(tickline2));
258 if (curr_sz < last_tick_len)
259 tickline2.append(last_tick_len - curr_sz, ' ');
260 last_tick_len = curr_sz;
261
262 unsigned int tw = terminal_width();
263 if(write_tickline1)
264 {
265 if (ui.last_write_was_a_tick)
266 clog << "\n";
267
268 if (tw && display_width(utf8(tickline1)) > tw)
269 {
270 // FIXME: may chop off more than necessary (because we chop by
271 // bytes, not by characters)
272 tickline1.resize(tw);
273 }
274 clog << tickline1 << "\n";
275 }
276 if (tw && display_width(utf8(tickline2)) > tw)
277 {
278 // FIXME: may chop off more than necessary (because we chop by
279 // bytes, not by characters)
280 tickline2.resize(tw);
281 }
282 clog << "\r" << tickline2;
283 clog.flush();
284}
285
286void tick_write_count::clear_line()
287{
288 clog << endl;
289}
290
291
292tick_write_dot::tick_write_dot()
293{
294}
295
296tick_write_dot::~tick_write_dot()
297{
298}
299
300void tick_write_dot::write_ticks()
301{
302 static const string tickline_prefix = ui.output_prefix();
303 string tickline1, tickline2;
304 bool first_tick = true;
305
306 if (ui.last_write_was_a_tick)
307 {
308 tickline1 = "";
309 tickline2 = "";
310 }
311 else
312 {
313 tickline1 = ui.output_prefix() + "ticks: ";
314 tickline2 = "\n" + tickline_prefix;
315 chars_on_line = tickline_prefix.size();
316 }
317
318 for (map<string,ticker *>::const_iterator i = ui.tickers.begin();
319 i != ui.tickers.end(); ++i)
320 {
321 map<string,size_t>::const_iterator old = last_ticks.find(i->first);
322
323 if (!ui.last_write_was_a_tick)
324 {
325 if (!first_tick)
326 tickline1 += ", ";
327
328 tickline1 +=
329 i->second->shortname + "=\"" + i->second->name + "\""
330 + "/" + lexical_cast<string>(i->second->mod);
331 first_tick = false;
332 }
333
334 if (old == last_ticks.end()
335 || ((i->second->ticks / i->second->mod)
336 > (old->second / i->second->mod)))
337 {
338 chars_on_line += i->second->shortname.size();
339 if (chars_on_line > guess_terminal_width())
340 {
341 chars_on_line = tickline_prefix.size() + i->second->shortname.size();
342 tickline2 += "\n" + tickline_prefix;
343 }
344 tickline2 += i->second->shortname;
345
346 if (old == last_ticks.end())
347 last_ticks.insert(make_pair(i->first, i->second->ticks));
348 else
349 last_ticks[i->first] = i->second->ticks;
350 }
351 }
352
353 clog << tickline1 << tickline2;
354 clog.flush();
355}
356
357void tick_write_dot::clear_line()
358{
359 clog << endl;
360}
361
362
363user_interface::user_interface() :
364 last_write_was_a_tick(false),
365 t_writer(0)
366{
367 cout.exceptions(ios_base::badbit);
368#ifdef SYNC_WITH_STDIO_WORKS
369 clog.sync_with_stdio(false);
370#endif
371 clog.unsetf(ios_base::unitbuf);
372 if (have_smart_terminal())
373 set_tick_writer(new tick_write_count);
374 else
375 set_tick_writer(new tick_write_dot);
376}
377
378user_interface::~user_interface()
379{
380 delete t_writer;
381}
382
383void
384user_interface::finish_ticking()
385{
386 if (tickers.size() == 0 &&
387 last_write_was_a_tick)
388 {
389 tick_trailer = "";
390 t_writer->clear_line();
391 last_write_was_a_tick = false;
392 }
393}
394
395void
396user_interface::set_tick_trailer(string const & t)
397{
398 tick_trailer = t;
399}
400
401void
402user_interface::set_tick_writer(tick_writer * t)
403{
404 if (t_writer != 0)
405 delete t_writer;
406 t_writer = t;
407}
408
409void
410user_interface::write_ticks()
411{
412 t_writer->write_ticks();
413 last_write_was_a_tick = true;
414 some_tick_is_dirty = false;
415}
416
417void
418user_interface::warn(string const & warning)
419{
420 if (issued_warnings.find(warning) == issued_warnings.end())
421 {
422 string message;
423 prefix_lines_with(_("warning: "), warning, message);
424 inform(message);
425 }
426 issued_warnings.insert(warning);
427}
428
429void
430user_interface::fatal(string const & fatal)
431{
432 inform(F("fatal: %s\n"
433 "this is almost certainly a bug in monotone.\n"
434 "please send this error message, the output of '%s --full-version',\n"
435 "and a description of what you were doing to %s.\n")
436 % fatal % prog_name % PACKAGE_BUGREPORT);
437}
438
439void
440user_interface::set_prog_name(string const & name)
441{
442 prog_name = name;
443 I(!prog_name.empty());
444}
445
446string
447user_interface::output_prefix()
448{
449 if (prog_name.empty()) {
450 return "?: ";
451 }
452 return prog_name + ": ";
453}
454
455static inline string
456sanitize(string const & line)
457{
458 // FIXME: you might want to adjust this if you're using a charset
459 // which has safe values in the sub-0x20 range. ASCII, UTF-8,
460 // and most ISO8859-x sets do not.
461 string tmp;
462 tmp.reserve(line.size());
463 for (size_t i = 0; i < line.size(); ++i)
464 {
465 if ((line[i] == '\n')
466 || (static_cast<unsigned char>(line[i]) >= static_cast<unsigned char>(0x20)
467 && line[i] != static_cast<char>(0x7F)))
468 tmp += line[i];
469 else
470 tmp += ' ';
471 }
472 return tmp;
473}
474
475void
476user_interface::ensure_clean_line()
477{
478 if (last_write_was_a_tick)
479 {
480 write_ticks();
481 t_writer->clear_line();
482 }
483 last_write_was_a_tick = false;
484}
485
486void
487user_interface::redirect_log_to(system_path const & filename)
488{
489 static ofstream filestr;
490 if (filestr.is_open())
491 filestr.close();
492 filestr.open(filename.as_external().c_str(), ofstream::out | ofstream::app);
493 E(filestr.is_open(), F("failed to open log file '%s'") % filename);
494 clog.rdbuf(filestr.rdbuf());
495}
496
497void
498user_interface::inform(string const & line)
499{
500 string prefixedLine;
501 prefix_lines_with(output_prefix(), line, prefixedLine);
502 ensure_clean_line();
503 clog << sanitize(prefixedLine) << endl;
504 clog.flush();
505}
506
507unsigned int
508guess_terminal_width()
509{
510 unsigned int w = terminal_width();
511 if (!w)
512 w = constants::default_terminal_width;
513 return w;
514}
515
516const locale &
517get_user_locale()
518{
519 // this is awkward because if LC_CTYPE is set to something the
520 // runtime doesn't know about, it will fail. in that case,
521 // the default will have to do.
522 static bool init = false;
523 static locale user_locale;
524 if (!init)
525 {
526 init = true;
527 try
528 {
529 user_locale = locale("");
530 }
531 catch( ... )
532 {}
533 }
534 return user_locale;
535}
536
537// Local Variables:
538// mode: C++
539// fill-column: 76
540// c-file-style: "gnu"
541// indent-tabs-mode: nil
542// End:
543// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status