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