xref: /netbsd-src/external/gpl3/gdb.old/dist/gdb/cli-out.c (revision 8b657b0747480f8989760d71343d6dd33f8d4cf9)
1 /* Output generating routines for GDB CLI.
2 
3    Copyright (C) 1999-2023 Free Software Foundation, Inc.
4 
5    Contributed by Cygnus Solutions.
6    Written by Fernando Nasser for Cygnus.
7 
8    This file is part of GDB.
9 
10    This program is free software; you can redistribute it and/or modify
11    it under the terms of the GNU General Public License as published by
12    the Free Software Foundation; either version 3 of the License, or
13    (at your option) any later version.
14 
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License for more details.
19 
20    You should have received a copy of the GNU General Public License
21    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
22 
23 #include "defs.h"
24 #include "ui-out.h"
25 #include "cli-out.h"
26 #include "completer.h"
27 #include "readline/readline.h"
28 #include "cli/cli-style.h"
29 #include "top.h"
30 
31 /* These are the CLI output functions */
32 
33 /* Mark beginning of a table */
34 
35 void
36 cli_ui_out::do_table_begin (int nbrofcols, int nr_rows, const char *tblid)
37 {
38   if (nr_rows == 0)
39     m_suppress_output = true;
40   else
41     /* Only the table suppresses the output and, fortunately, a table
42        is not a recursive data structure.  */
43     gdb_assert (!m_suppress_output);
44 }
45 
46 /* Mark beginning of a table body */
47 
48 void
49 cli_ui_out::do_table_body ()
50 {
51   if (m_suppress_output)
52     return;
53 
54   /* first, close the table header line */
55   text ("\n");
56 }
57 
58 /* Mark end of a table */
59 
60 void
61 cli_ui_out::do_table_end ()
62 {
63   m_suppress_output = false;
64 }
65 
66 /* Specify table header */
67 
68 void
69 cli_ui_out::do_table_header (int width, ui_align alignment,
70 			     const std::string &col_name,
71 			     const std::string &col_hdr)
72 {
73   if (m_suppress_output)
74     return;
75 
76   do_field_string (0, width, alignment, 0, col_hdr.c_str (),
77 		   ui_file_style ());
78 }
79 
80 /* Mark beginning of a list */
81 
82 void
83 cli_ui_out::do_begin (ui_out_type type, const char *id)
84 {
85 }
86 
87 /* Mark end of a list */
88 
89 void
90 cli_ui_out::do_end (ui_out_type type)
91 {
92 }
93 
94 /* output an int field */
95 
96 void
97 cli_ui_out::do_field_signed (int fldno, int width, ui_align alignment,
98 			     const char *fldname, LONGEST value)
99 {
100   if (m_suppress_output)
101     return;
102 
103   do_field_string (fldno, width, alignment, fldname, plongest (value),
104 		   ui_file_style ());
105 }
106 
107 /* output an unsigned field */
108 
109 void
110 cli_ui_out::do_field_unsigned (int fldno, int width, ui_align alignment,
111 			       const char *fldname, ULONGEST value)
112 {
113   if (m_suppress_output)
114     return;
115 
116   do_field_string (fldno, width, alignment, fldname, pulongest (value),
117 		   ui_file_style ());
118 }
119 
120 /* used to omit a field */
121 
122 void
123 cli_ui_out::do_field_skip (int fldno, int width, ui_align alignment,
124 			   const char *fldname)
125 {
126   if (m_suppress_output)
127     return;
128 
129   do_field_string (fldno, width, alignment, fldname, "",
130 		   ui_file_style ());
131 }
132 
133 /* other specific cli_field_* end up here so alignment and field
134    separators are both handled by cli_field_string */
135 
136 void
137 cli_ui_out::do_field_string (int fldno, int width, ui_align align,
138 			     const char *fldname, const char *string,
139 			     const ui_file_style &style)
140 {
141   int before = 0;
142   int after = 0;
143 
144   if (m_suppress_output)
145     return;
146 
147   if ((align != ui_noalign) && string)
148     {
149       before = width - strlen (string);
150       if (before <= 0)
151 	before = 0;
152       else
153 	{
154 	  if (align == ui_right)
155 	    after = 0;
156 	  else if (align == ui_left)
157 	    {
158 	      after = before;
159 	      before = 0;
160 	    }
161 	  else
162 	    /* ui_center */
163 	    {
164 	      after = before / 2;
165 	      before -= after;
166 	    }
167 	}
168     }
169 
170   if (before)
171     spaces (before);
172 
173   if (string)
174     {
175       ui_file *stream = m_streams.back ();
176       stream->emit_style_escape (style);
177       stream->puts (string);
178       stream->emit_style_escape (ui_file_style ());
179     }
180 
181   if (after)
182     spaces (after);
183 
184   if (align != ui_noalign)
185     field_separator ();
186 }
187 
188 /* Output field containing ARGS using printf formatting in FORMAT.  */
189 
190 void
191 cli_ui_out::do_field_fmt (int fldno, int width, ui_align align,
192 			  const char *fldname, const ui_file_style &style,
193 			  const char *format, va_list args)
194 {
195   if (m_suppress_output)
196     return;
197 
198   std::string str = string_vprintf (format, args);
199 
200   do_field_string (fldno, width, align, fldname, str.c_str (), style);
201 }
202 
203 void
204 cli_ui_out::do_spaces (int numspaces)
205 {
206   if (m_suppress_output)
207     return;
208 
209   print_spaces (numspaces, m_streams.back ());
210 }
211 
212 void
213 cli_ui_out::do_text (const char *string)
214 {
215   if (m_suppress_output)
216     return;
217 
218   gdb_puts (string, m_streams.back ());
219 }
220 
221 void
222 cli_ui_out::do_message (const ui_file_style &style,
223 			const char *format, va_list args)
224 {
225   if (m_suppress_output)
226     return;
227 
228   std::string str = string_vprintf (format, args);
229   if (!str.empty ())
230     {
231       ui_file *stream = m_streams.back ();
232       stream->emit_style_escape (style);
233       stream->puts (str.c_str ());
234       stream->emit_style_escape (ui_file_style ());
235     }
236 }
237 
238 void
239 cli_ui_out::do_wrap_hint (int indent)
240 {
241   if (m_suppress_output)
242     return;
243 
244   m_streams.back ()->wrap_here (indent);
245 }
246 
247 void
248 cli_ui_out::do_flush ()
249 {
250   gdb_flush (m_streams.back ());
251 }
252 
253 /* OUTSTREAM as non-NULL will push OUTSTREAM on the stack of output streams
254    and make it therefore active.  OUTSTREAM as NULL will pop the last pushed
255    output stream; it is an internal error if it does not exist.  */
256 
257 void
258 cli_ui_out::do_redirect (ui_file *outstream)
259 {
260   if (outstream != NULL)
261     m_streams.push_back (outstream);
262   else
263     m_streams.pop_back ();
264 }
265 
266 /* Initialize a progress update to be displayed with
267    cli_ui_out::do_progress_notify.  */
268 
269 void
270 cli_ui_out::do_progress_start ()
271 {
272   m_progress_info.emplace_back ();
273 }
274 
275 #define MIN_CHARS_PER_LINE 50
276 #define MAX_CHARS_PER_LINE 4096
277 
278 /* Print a progress update.  MSG is a string to be printed on the line above
279    the progress bar.  TOTAL is the size of the download whose progress is
280    being displayed.  UNIT should be the unit of TOTAL (ex. "K"). If HOWMUCH
281    is between 0.0 and 1.0, a progress bar is displayed indicating the percentage
282    of completion and the download size.  If HOWMUCH is negative, a progress
283    indicator will tick across the screen.  If the output stream is not a tty
284    then only MSG is printed.
285 
286    - printed for tty, HOWMUCH between 0.0 and 1.0:
287 	<MSG
288 	[#########                  ]  HOWMUCH*100% (TOTAL UNIT)\r>
289    - printed for tty, HOWMUCH < 0.0:
290 	<MSG
291 	[    ###                    ]\r>
292    - printed for not-a-tty:
293 	<MSG...\n>
294 */
295 
296 void
297 cli_ui_out::do_progress_notify (const std::string &msg,
298 				const char *unit,
299 				double howmuch, double total)
300 {
301   int chars_per_line = get_chars_per_line ();
302   struct ui_file *stream = m_streams.back ();
303   cli_progress_info &info (m_progress_info.back ());
304 
305   if (chars_per_line > MAX_CHARS_PER_LINE)
306     chars_per_line = MAX_CHARS_PER_LINE;
307 
308   if (info.state == progress_update::START)
309     {
310       if (stream->isatty ()
311 	  && current_ui->input_interactive_p ()
312 	  && chars_per_line >= MIN_CHARS_PER_LINE)
313 	{
314 	  gdb_printf (stream, "%s\n", msg.c_str ());
315 	  info.state = progress_update::BAR;
316 	}
317       else
318 	{
319 	  gdb_printf (stream, "%s...\n", msg.c_str ());
320 	  info.state = progress_update::WORKING;
321 	}
322     }
323 
324   if (info.state != progress_update::BAR
325       || chars_per_line < MIN_CHARS_PER_LINE)
326     return;
327 
328   if (total > 0 && howmuch >= 0 && howmuch <= 1.0)
329     {
330       std::string progress = string_printf (" %3.f%% (%.2f %s)",
331 					    howmuch * 100, total,
332 					    unit);
333       int width = chars_per_line - progress.size () - 4;
334       int max = width * howmuch;
335 
336       std::string display = "\r[";
337 
338       for (int i = 0; i < width; ++i)
339 	if (i < max)
340 	  display += "#";
341 	else
342 	  display += " ";
343 
344       display += "]" + progress;
345       gdb_printf (stream, "%s", display.c_str ());
346       gdb_flush (stream);
347     }
348   else
349     {
350       using namespace std::chrono;
351       milliseconds diff = duration_cast<milliseconds>
352 	(steady_clock::now () - info.last_update);
353 
354       /* Advance the progress indicator at a rate of 1 tick every
355 	 every 0.5 seconds.  */
356       if (diff.count () >= 500)
357 	{
358 	  int width = chars_per_line - 4;
359 
360 	  gdb_printf (stream, "\r[");
361 	  for (int i = 0; i < width; ++i)
362 	    {
363 	      if (i == info.pos % width
364 		  || i == (info.pos + 1) % width
365 		  || i == (info.pos + 2) % width)
366 		gdb_printf (stream, "#");
367 	      else
368 		gdb_printf (stream, " ");
369 	    }
370 
371 	  gdb_printf (stream, "]");
372 	  gdb_flush (stream);
373 	  info.last_update = steady_clock::now ();
374 	  info.pos++;
375 	}
376     }
377 
378   return;
379 }
380 
381 /* Clear the current line of the most recent progress update.  Overwrites
382    the current line with whitespace.  */
383 
384 void
385 cli_ui_out::clear_current_line ()
386 {
387   struct ui_file *stream = m_streams.back ();
388   int chars_per_line = get_chars_per_line ();
389 
390   if (!stream->isatty ()
391       || !current_ui->input_interactive_p ()
392       || chars_per_line < MIN_CHARS_PER_LINE)
393     return;
394 
395   if (chars_per_line > MAX_CHARS_PER_LINE)
396     chars_per_line = MAX_CHARS_PER_LINE;
397 
398   gdb_printf (stream, "\r");
399   for (int i = 0; i < chars_per_line; ++i)
400     gdb_printf (stream, " ");
401   gdb_printf (stream, "\r");
402 
403   gdb_flush (stream);
404 }
405 
406 /* Remove the most recent progress update from the progress_info stack
407    and overwrite the current line with whitespace.  */
408 
409 void
410 cli_ui_out::do_progress_end ()
411 {
412   struct ui_file *stream = m_streams.back ();
413   m_progress_info.pop_back ();
414 
415   if (stream->isatty ())
416     clear_current_line ();
417 }
418 
419 /* local functions */
420 
421 void
422 cli_ui_out::field_separator ()
423 {
424   gdb_putc (' ', m_streams.back ());
425 }
426 
427 /* Constructor for cli_ui_out.  */
428 
429 cli_ui_out::cli_ui_out (ui_file *stream, ui_out_flags flags)
430 : ui_out (flags),
431   m_suppress_output (false)
432 {
433   gdb_assert (stream != NULL);
434 
435   m_streams.push_back (stream);
436 }
437 
438 cli_ui_out::~cli_ui_out ()
439 {
440 }
441 
442 ui_file *
443 cli_ui_out::set_stream (struct ui_file *stream)
444 {
445   ui_file *old;
446 
447   old = m_streams.back ();
448   m_streams.back () = stream;
449 
450   return old;
451 }
452 
453 bool
454 cli_ui_out::can_emit_style_escape () const
455 {
456   return m_streams.back ()->can_emit_style_escape ();
457 }
458 
459 /* CLI interface to display tab-completion matches.  */
460 
461 /* CLI version of displayer.crlf.  */
462 
463 static void
464 cli_mld_crlf (const struct match_list_displayer *displayer)
465 {
466   rl_crlf ();
467 }
468 
469 /* CLI version of displayer.putch.  */
470 
471 static void
472 cli_mld_putch (const struct match_list_displayer *displayer, int ch)
473 {
474   putc (ch, rl_outstream);
475 }
476 
477 /* CLI version of displayer.puts.  */
478 
479 static void
480 cli_mld_puts (const struct match_list_displayer *displayer, const char *s)
481 {
482   fputs (s, rl_outstream);
483 }
484 
485 /* CLI version of displayer.flush.  */
486 
487 static void
488 cli_mld_flush (const struct match_list_displayer *displayer)
489 {
490   fflush (rl_outstream);
491 }
492 
493 EXTERN_C void _rl_erase_entire_line (void);
494 
495 /* CLI version of displayer.erase_entire_line.  */
496 
497 static void
498 cli_mld_erase_entire_line (const struct match_list_displayer *displayer)
499 {
500   _rl_erase_entire_line ();
501 }
502 
503 /* CLI version of displayer.beep.  */
504 
505 static void
506 cli_mld_beep (const struct match_list_displayer *displayer)
507 {
508   rl_ding ();
509 }
510 
511 /* CLI version of displayer.read_key.  */
512 
513 static int
514 cli_mld_read_key (const struct match_list_displayer *displayer)
515 {
516   return rl_read_key ();
517 }
518 
519 /* CLI version of rl_completion_display_matches_hook.
520    See gdb_display_match_list for a description of the arguments.  */
521 
522 void
523 cli_display_match_list (char **matches, int len, int max)
524 {
525   struct match_list_displayer displayer;
526 
527   rl_get_screen_size (&displayer.height, &displayer.width);
528   displayer.crlf = cli_mld_crlf;
529   displayer.putch = cli_mld_putch;
530   displayer.puts = cli_mld_puts;
531   displayer.flush = cli_mld_flush;
532   displayer.erase_entire_line = cli_mld_erase_entire_line;
533   displayer.beep = cli_mld_beep;
534   displayer.read_key = cli_mld_read_key;
535 
536   gdb_display_match_list (matches, len, max, &displayer);
537   rl_forced_update_display ();
538 }
539