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
15#include "base.hh"
16#include "platform.hh"
17#include "paths.hh"
18#include "sanity.hh"
19#include "ui.hh"
20#include "charset.hh"
21#include "simplestring_xform.hh"
22#include "constants.hh"
23
24#include <iostream>
25#include <fstream>
26#include <iomanip>
27#include <algorithm>
28#include <map>
29#include <set>
30#include "lexical_cast.hh"
31#include "safe_map.hh"
32
33#include <cstring>
34
35#include "current_exception.hh"
36
37using std::clog;
38using std::cout;
39using std::endl;
40using std::ios_base;
41using std::locale;
42using std::make_pair;
43using std::map;
44using std::max;
45using std::ofstream;
46using std::string;
47using std::vector;
48
49using boost::lexical_cast;
50
51struct user_interface ui;
52
53struct user_interface::impl
54{
55 std::set<string> issued_warnings;
56
57 bool some_tick_is_dirty; // At least one tick needs being printed
58 bool last_write_was_a_tick;
59 map<string,ticker *> tickers;
60 tick_writer * t_writer;
61 string tick_trailer;
62
63 impl() : some_tick_is_dirty(false), last_write_was_a_tick(false),
64 t_writer(0) {}
65};
66
67ticker::ticker(string const & tickname, string const & s, size_t mod,
68 bool kilocount) :
69 ticks(0),
70 mod(mod),
71 total(0),
72 previous_total(0),
73 kilocount(kilocount),
74 use_total(false),
75 keyname(tickname),
76 name(_(tickname.c_str())),
77 shortname(s),
78 count_size(0)
79{
80 I(ui.imp);
81 safe_insert(ui.imp->tickers, make_pair(keyname, this));
82}
83
84ticker::~ticker()
85{
86 I(ui.imp);
87 safe_erase(ui.imp->tickers, keyname);
88
89 if (ui.imp->some_tick_is_dirty)
90 ui.write_ticks();
91 ui.finish_ticking();
92}
93
94void
95ticker::operator++()
96{
97 I(ui.imp);
98 I(ui.imp->tickers.find(keyname) != ui.imp->tickers.end());
99 ticks++;
100 ui.imp->some_tick_is_dirty = true;
101 if (ticks % mod == 0)
102 ui.write_ticks();
103}
104
105void
106ticker::operator+=(size_t t)
107{
108 I(ui.imp);
109 I(ui.imp->tickers.find(keyname) != ui.imp->tickers.end());
110 size_t old = ticks;
111
112 ticks += t;
113 if (t != 0)
114 {
115 ui.imp->some_tick_is_dirty = true;
116 if (ticks % mod == 0 || (ticks / mod) > (old / mod))
117 ui.write_ticks();
118 }
119}
120
121// We would like to put these in an anonymous namespace but we can't because
122// struct user_interface needs to make them friends.
123struct tick_writer
124{
125public:
126 tick_writer() {}
127 virtual ~tick_writer() {}
128 virtual void write_ticks() = 0;
129 virtual void clear_line() = 0;
130};
131
132struct tick_write_count : virtual public tick_writer
133{
134public:
135 tick_write_count();
136 ~tick_write_count();
137 void write_ticks();
138 void clear_line();
139private:
140 std::vector<size_t> last_tick_widths;
141 size_t last_tick_len;
142};
143
144struct tick_write_dot : virtual public tick_writer
145{
146public:
147 tick_write_dot();
148 ~tick_write_dot();
149 void write_ticks();
150 void clear_line();
151private:
152 std::map<std::string,size_t> last_ticks;
153 unsigned int chars_on_line;
154};
155
156struct tick_write_nothing : virtual public tick_writer
157{
158public:
159 void write_ticks() {}
160 void clear_line() {}
161};
162
163tick_write_count::tick_write_count() : last_tick_len(0)
164{
165}
166
167tick_write_count::~tick_write_count()
168{
169}
170
171static string compose_count(ticker *tick, size_t ticks=0)
172{
173 string count;
174
175 if (ticks == 0)
176 {
177 ticks = tick->ticks;
178 }
179
180 if (tick->kilocount && ticks)
181 {
182 // automatic unit conversion is enabled
183 float div = 1.0;
184 const char *message;
185
186 if (ticks >= 1073741824)
187 {
188 div = 1073741824;
189 // xgettext: gibibytes (2^30 bytes)
190 message = N_("%.1f G");
191 }
192 else if (ticks >= 1048576)
193 {
194 div = 1048576;
195 // xgettext: mebibytes (2^20 bytes)
196 message = N_("%.1f M");
197 }
198 else if (ticks >= 1024)
199 {
200 div = 1024;
201 // xgettext: kibibytes (2^10 bytes)
202 message = N_("%.1f k");
203 }
204 else
205 {
206 div = 1;
207 message = "%.0f";
208 }
209 // We reset the mod to the divider, to avoid spurious screen updates.
210 tick->mod = max(static_cast<int>(div / 10.0), 1);
211 count = (F(message) % (ticks / div)).str();
212 }
213 else if (tick->use_total)
214 {
215 count = (F("%d/%d") % ticks % tick->total).str();
216 }
217 else
218 {
219 // xgettext: bytes
220 count = (F("%d") % ticks).str();
221 }
222
223 return count;
224}
225
226void tick_write_count::write_ticks()
227{
228 vector<size_t> tick_widths;
229 vector<string> tick_title_strings;
230 vector<string> tick_count_strings;
231
232 I(ui.imp);
233 for (map<string,ticker *>::const_iterator i = ui.imp->tickers.begin();
234 i != ui.imp->tickers.end(); ++i)
235 {
236 ticker * tick = i->second;
237
238 if ((tick->count_size == 0 && tick->kilocount)
239 ||
240 (tick->use_total && tick->previous_total != tick->total))
241 {
242 if (!tick->kilocount && tick->use_total)
243 {
244 // We know that we're going to eventually have 'total'
245 // displayed twice on screen, plus a slash. So we should
246 // pad out this field to that eventual size to avoid
247 // spurious re-issuing of the tick titles as we expand to
248 // the goal.
249 tick->set_count_size(display_width(utf8(compose_count(tick,
250 tick->total))));
251 tick->previous_total = tick->total;
252 }
253 else
254 {
255 // To find out what the maximum size can be, choose one the
256 // the dividers from compose_count, subtract one and have
257 // compose_count create the count string for that. Use the
258 // size of the returned count string as an initial size for
259 // this tick.
260 tick->set_count_size(display_width(utf8(compose_count(tick,
261 1048575))));
262 }
263 }
264
265 string count(compose_count(tick));
266
267 size_t title_width = display_width(utf8(tick->name));
268 size_t count_width = display_width(utf8(count));
269
270 if (count_width > tick->count_size)
271 {
272 tick->set_count_size(count_width);
273 }
274
275 size_t max_width = max(title_width, tick->count_size);
276
277 string name;
278 name.append(max_width - title_width, ' ');
279 name.append(tick->name);
280
281 string count2;
282 count2.append(max_width - count_width, ' ');
283 count2.append(count);
284
285 tick_title_strings.push_back(name);
286 tick_count_strings.push_back(count2);
287 tick_widths.push_back(max_width);
288 }
289
290 string tickline1;
291 bool write_tickline1 = !(ui.imp->last_write_was_a_tick
292 && (tick_widths == last_tick_widths));
293 if (write_tickline1)
294 {
295 // Reissue the titles if the widths have changed.
296 tickline1 = ui.output_prefix();
297 for (size_t i = 0; i < tick_widths.size(); ++i)
298 {
299 if (i != 0)
300 tickline1.append(" | ");
301 tickline1.append(idx(tick_title_strings, i));
302 }
303 last_tick_widths = tick_widths;
304 write_tickline1 = true;
305 }
306
307 // Always reissue the counts.
308 string tickline2 = ui.output_prefix();
309 for (size_t i = 0; i < tick_widths.size(); ++i)
310 {
311 if (i != 0)
312 tickline2.append(" | ");
313 tickline2.append(idx(tick_count_strings, i));
314 }
315
316 if (!ui.imp->tick_trailer.empty())
317 {
318 tickline2 += " ";
319 tickline2 += ui.imp->tick_trailer;
320 }
321
322 size_t curr_sz = display_width(utf8(tickline2));
323 if (curr_sz < last_tick_len)
324 tickline2.append(last_tick_len - curr_sz, ' ');
325 last_tick_len = curr_sz;
326
327 unsigned int tw = terminal_width();
328 if(write_tickline1)
329 {
330 if (ui.imp->last_write_was_a_tick)
331 clog << '\n';
332
333 if (tw && display_width(utf8(tickline1)) > tw)
334 {
335 // FIXME: may chop off more than necessary (because we chop by
336 // bytes, not by characters)
337 tickline1.resize(tw);
338 }
339 clog << tickline1 << '\n';
340 }
341 if (tw && display_width(utf8(tickline2)) > tw)
342 {
343 // FIXME: may chop off more than necessary (because we chop by
344 // bytes, not by characters)
345 tickline2.resize(tw);
346 }
347 clog << '\r' << tickline2;
348 clog.flush();
349}
350
351void tick_write_count::clear_line()
352{
353 clog << endl;
354}
355
356
357tick_write_dot::tick_write_dot()
358{
359}
360
361tick_write_dot::~tick_write_dot()
362{
363}
364
365void tick_write_dot::write_ticks()
366{
367 I(ui.imp);
368 static const string tickline_prefix = ui.output_prefix();
369 string tickline1, tickline2;
370 bool first_tick = true;
371
372 if (ui.imp->last_write_was_a_tick)
373 {
374 tickline1 = "";
375 tickline2 = "";
376 }
377 else
378 {
379 tickline1 = ui.output_prefix() + "ticks: ";
380 tickline2 = "\n" + tickline_prefix;
381 chars_on_line = tickline_prefix.size();
382 }
383
384 for (map<string,ticker *>::const_iterator i = ui.imp->tickers.begin();
385 i != ui.imp->tickers.end(); ++i)
386 {
387 map<string,size_t>::const_iterator old = last_ticks.find(i->first);
388
389 if (!ui.imp->last_write_was_a_tick)
390 {
391 if (!first_tick)
392 tickline1 += ", ";
393
394 tickline1 +=
395 i->second->shortname + "=\"" + i->second->name + "\""
396 + "/" + lexical_cast<string>(i->second->mod);
397 first_tick = false;
398 }
399
400 if (old == last_ticks.end()
401 || ((i->second->ticks / i->second->mod)
402 > (old->second / i->second->mod)))
403 {
404 chars_on_line += i->second->shortname.size();
405 if (chars_on_line > guess_terminal_width())
406 {
407 chars_on_line = tickline_prefix.size() + i->second->shortname.size();
408 tickline2 += "\n" + tickline_prefix;
409 }
410 tickline2 += i->second->shortname;
411
412 if (old == last_ticks.end())
413 last_ticks.insert(make_pair(i->first, i->second->ticks));
414 else
415 last_ticks[i->first] = i->second->ticks;
416 }
417 }
418
419 clog << tickline1 << tickline2;
420 clog.flush();
421}
422
423void tick_write_dot::clear_line()
424{
425 clog << endl;
426}
427
428// user_interface has both constructor/destructor and initialize/
429// deinitialize because there's only one of these objects, it's
430// global, and we don't want global constructors/destructors doing
431// any real work. see monotone.cc for how this is handled.
432
433user_interface::user_interface() : prog_name("?"), imp(0) {}
434
435void user_interface::initialize()
436{
437 imp = new user_interface::impl;
438
439 cout.exceptions(ios_base::badbit);
440#ifdef SYNC_WITH_STDIO_WORKS
441 clog.sync_with_stdio(false);
442#endif
443 clog.unsetf(ios_base::unitbuf);
444 if (have_smart_terminal())
445 set_tick_write_count();
446 else
447 set_tick_write_dot();
448}
449
450user_interface::~user_interface()
451{}
452
453void user_interface::deinitialize()
454{
455 I(imp);
456 delete imp->t_writer;
457 delete imp;
458}
459
460void
461user_interface::finish_ticking()
462{
463 I(imp);
464 if (imp->tickers.empty() && imp->last_write_was_a_tick)
465 {
466 imp->tick_trailer = "";
467 imp->t_writer->clear_line();
468 imp->last_write_was_a_tick = false;
469 }
470}
471
472void
473user_interface::set_tick_trailer(string const & t)
474{
475 I(imp);
476 imp->tick_trailer = t;
477}
478
479void
480user_interface::set_tick_write_dot()
481{
482 I(imp);
483 if (imp->t_writer != 0)
484 delete imp->t_writer;
485 imp->t_writer = new tick_write_dot;
486}
487
488void
489user_interface::set_tick_write_count()
490{
491 I(imp);
492 if (imp->t_writer != 0)
493 delete imp->t_writer;
494 imp->t_writer = new tick_write_count;
495}
496
497void
498user_interface::set_tick_write_nothing()
499{
500 I(imp);
501 if (imp->t_writer != 0)
502 delete imp->t_writer;
503 imp->t_writer = new tick_write_nothing;
504}
505
506
507void
508user_interface::write_ticks()
509{
510 I(imp);
511 imp->t_writer->write_ticks();
512 imp->last_write_was_a_tick = true;
513 imp->some_tick_is_dirty = false;
514}
515
516void
517user_interface::warn(string const & warning)
518{
519 I(imp);
520 if (imp->issued_warnings.find(warning) == imp->issued_warnings.end())
521 {
522 string message;
523 prefix_lines_with(_("warning: "), warning, message);
524 inform(message);
525 }
526 imp->issued_warnings.insert(warning);
527}
528
529// this message should be kept consistent with unix/main.cc and
530// win32/main.cc ::bug_report_message (it is not exactly the same)
531void
532user_interface::fatal(string const & fatal)
533{
534 inform(F("fatal: %s\n"
535 "this is almost certainly a bug in monotone.\n"
536 "please send this error message, the output of '%s version --full',\n"
537 "and a description of what you were doing to %s.")
538 % fatal % prog_name % PACKAGE_BUGREPORT);
539 global_sanity.dump_buffer();
540}
541
542// Report what we can about a fatal exception (caught in the outermost catch
543// handlers) which is from the std::exception hierarchy. In this case we
544// can access the exception object.
545void
546user_interface::fatal_exception(std::exception const & ex)
547{
548 using std::strcmp;
549 using std::strncmp;
550 char const * ex_name = typeid(ex).name();
551 char const * ex_dem = demangle_typename(ex_name);
552 char const * ex_what = ex.what();
553
554 if (ex_dem == 0)
555 ex_dem = ex_name;
556
557 // some demanglers stick "class" at the beginning of their output,
558 // which looks dumb in this context
559 if (!strncmp(ex_dem, "class ", 6))
560 ex_dem += 6;
561
562 // only print what() if it's interesting, i.e. nonempty and different
563 // from the name (mangled or otherwise) of the exception type.
564 if (ex_what == 0 || ex_what[0] == 0
565 || !strcmp(ex_what, ex_name)
566 || !strcmp(ex_what, ex_dem))
567 this->fatal(ex_dem);
568 else
569 this->fatal(i18n_format("%s: %s") % ex_dem % ex_what);
570}
571
572// Report what we can about a fatal exception (caught in the outermost catch
573// handlers) which is of unknown type. If we have the <cxxabi.h> interfaces,
574// we can at least get the type_info object.
575void
576user_interface::fatal_exception()
577{
578 std::type_info *ex_type = get_current_exception_type();
579 if (ex_type)
580 {
581 char const * ex_name = ex_type->name();
582 char const * ex_dem = demangle_typename(ex_name);
583 if (ex_dem == 0)
584 ex_dem = ex_name;
585 this->fatal(ex_dem);
586 }
587 else
588 this->fatal("exception of unknown type");
589}
590
591string
592user_interface::output_prefix()
593{
594 if (prog_name.empty()) {
595 return "?: ";
596 }
597 return prog_name + ": ";
598}
599
600static inline string
601sanitize(string const & line)
602{
603 // FIXME: you might want to adjust this if you're using a charset
604 // which has safe values in the sub-0x20 range. ASCII, UTF-8,
605 // and most ISO8859-x sets do not.
606 string tmp;
607 tmp.reserve(line.size());
608 for (size_t i = 0; i < line.size(); ++i)
609 {
610 if ((line[i] == '\n')
611 || (static_cast<unsigned char>(line[i]) >= static_cast<unsigned char>(0x20)
612 && line[i] != static_cast<char>(0x7F)))
613 tmp += line[i];
614 else
615 tmp += ' ';
616 }
617 return tmp;
618}
619
620void
621user_interface::ensure_clean_line()
622{
623 I(imp);
624 if (imp->last_write_was_a_tick)
625 {
626 write_ticks();
627 imp->t_writer->clear_line();
628 }
629 imp->last_write_was_a_tick = false;
630}
631
632void
633user_interface::redirect_log_to(system_path const & filename)
634{
635 static ofstream filestr;
636 if (filestr.is_open())
637 filestr.close();
638 filestr.open(filename.as_external().c_str(), ofstream::out | ofstream::app);
639 E(filestr.is_open(), F("failed to open log file '%s'") % filename);
640 clog.rdbuf(filestr.rdbuf());
641}
642
643void
644user_interface::inform(string const & line)
645{
646 string prefixedLine;
647 prefix_lines_with(output_prefix(), line, prefixedLine);
648 ensure_clean_line();
649 clog << sanitize(prefixedLine) << endl; // flushes
650}
651
652unsigned int
653guess_terminal_width()
654{
655 unsigned int w = terminal_width();
656 if (!w)
657 w = constants::default_terminal_width;
658 return w;
659}
660
661// A very simple class that adds an operator() to a string that returns
662// the string itself. This is to make it compatible with, for example,
663// the utf8 class, allowing it to be usable in other contexts without
664// encoding conversions.
665class string_adaptor : public string
666{
667public:
668 string_adaptor(string const & str) : string(str) {}
669 string const & operator()(void) const { return *this; }
670};
671
672// See description for format_text below for more details.
673static string
674format_paragraph(string const & text, size_t const col, size_t curcol)
675{
676 I(text.find('\n') == string::npos);
677
678 string formatted;
679 if (curcol < col)
680 {
681 formatted = string(col - curcol, ' ');
682 curcol = col;
683 }
684
685 const size_t maxcol = guess_terminal_width();
686
687 vector< string_adaptor > words = split_into_words(string_adaptor(text));
688 for (vector< string_adaptor >::const_iterator iter = words.begin();
689 iter != words.end(); iter++)
690 {
691 string const & word = (*iter)();
692
693 if (iter != words.begin() &&
694 curcol + display_width(utf8(word)) + 1 > maxcol)
695 {
696 formatted += '\n' + string(col, ' ');
697 curcol = col;
698 }
699 else if (iter != words.begin())
700 {
701 formatted += ' ';
702 curcol++;
703 }
704
705 formatted += word;
706 curcol += display_width(utf8(word));
707 }
708
709 return formatted;
710}
711
712// Reformats the given text so that it fits in the current screen with no
713// wrapping.
714//
715// The input text is a series of words and sentences. Paragraphs may be
716// separated with a '\n' character, which is taken into account to do the
717// proper formatting. The text should not finish in '\n'.
718//
719// 'col' specifies the column where the text will start and 'curcol'
720// specifies the current position of the cursor.
721string
722format_text(string const & text, size_t const col, size_t curcol)
723{
724 I(curcol <= col);
725
726 string formatted;
727
728 vector< string > lines;
729 split_into_lines(text, lines);
730 for (vector< string >::const_iterator iter = lines.begin();
731 iter != lines.end(); iter++)
732 {
733 string const & line = *iter;
734
735 formatted += format_paragraph(line, col, curcol);
736 if (iter + 1 != lines.end())
737 formatted += "\n\n";
738 curcol = 0;
739 }
740
741 return formatted;
742}
743
744// See description for the other format_text below for more details.
745string
746format_text(i18n_format const & text, size_t const col, size_t curcol)
747{
748 return format_text(text.str(), col, curcol);
749}
750
751// Local Variables:
752// mode: C++
753// fill-column: 76
754// c-file-style: "gnu"
755// indent-tabs-mode: nil
756// End:
757// 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