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
35void
36cli_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
48void
49cli_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
60void
61cli_ui_out::do_table_end ()
62{
63  m_suppress_output = false;
64}
65
66/* Specify table header */
67
68void
69cli_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
82void
83cli_ui_out::do_begin (ui_out_type type, const char *id)
84{
85}
86
87/* Mark end of a list */
88
89void
90cli_ui_out::do_end (ui_out_type type)
91{
92}
93
94/* output an int field */
95
96void
97cli_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
109void
110cli_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
122void
123cli_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
136void
137cli_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
190void
191cli_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
203void
204cli_ui_out::do_spaces (int numspaces)
205{
206  if (m_suppress_output)
207    return;
208
209  print_spaces (numspaces, m_streams.back ());
210}
211
212void
213cli_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
221void
222cli_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
238void
239cli_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
247void
248cli_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
257void
258cli_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
269void
270cli_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
296void
297cli_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
384void
385cli_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
409void
410cli_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
421void
422cli_ui_out::field_separator ()
423{
424  gdb_putc (' ', m_streams.back ());
425}
426
427/* Constructor for cli_ui_out.  */
428
429cli_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
438cli_ui_out::~cli_ui_out ()
439{
440}
441
442ui_file *
443cli_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
453bool
454cli_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
463static void
464cli_mld_crlf (const struct match_list_displayer *displayer)
465{
466  rl_crlf ();
467}
468
469/* CLI version of displayer.putch.  */
470
471static void
472cli_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
479static void
480cli_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
487static void
488cli_mld_flush (const struct match_list_displayer *displayer)
489{
490  fflush (rl_outstream);
491}
492
493EXTERN_C void _rl_erase_entire_line (void);
494
495/* CLI version of displayer.erase_entire_line.  */
496
497static void
498cli_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
505static void
506cli_mld_beep (const struct match_list_displayer *displayer)
507{
508  rl_ding ();
509}
510
511/* CLI version of displayer.read_key.  */
512
513static int
514cli_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
522void
523cli_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