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