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