[gdb/tui] Don't include border_width in left_margin
[binutils-gdb.git] / gdb / tui / tui-winsource.c
1 /* TUI display source/assembly window.
2
3 Copyright (C) 1998-2023 Free Software Foundation, Inc.
4
5 Contributed by Hewlett-Packard Company.
6
7 This file is part of GDB.
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 3 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>. */
21
22 #include "defs.h"
23 #include <ctype.h>
24 #include "symtab.h"
25 #include "frame.h"
26 #include "breakpoint.h"
27 #include "value.h"
28 #include "source.h"
29 #include "objfiles.h"
30 #include "filenames.h"
31 #include "gdbsupport/gdb-safe-ctype.h"
32
33 #include "tui/tui.h"
34 #include "tui/tui-data.h"
35 #include "tui/tui-io.h"
36 #include "tui/tui-stack.h"
37 #include "tui/tui-win.h"
38 #include "tui/tui-wingeneral.h"
39 #include "tui/tui-winsource.h"
40 #include "tui/tui-source.h"
41 #include "tui/tui-disasm.h"
42 #include "tui/tui-location.h"
43 #include "gdb_curses.h"
44
45 /* Function to display the "main" routine. */
46 void
47 tui_display_main ()
48 {
49 auto adapter = tui_source_windows ();
50 if (adapter.begin () != adapter.end ())
51 {
52 struct gdbarch *gdbarch;
53 CORE_ADDR addr;
54
55 tui_get_begin_asm_address (&gdbarch, &addr);
56 if (addr != (CORE_ADDR) 0)
57 {
58 struct symtab *s;
59
60 tui_update_source_windows_with_addr (gdbarch, addr);
61 s = find_pc_line_symtab (addr);
62 tui_location.set_location (s);
63 }
64 }
65 }
66
67 /* See tui-winsource.h. */
68
69 std::string
70 tui_copy_source_line (const char **ptr, int *length)
71 {
72 const char *lineptr = *ptr;
73
74 /* Init the line with the line number. */
75 std::string result;
76
77 int column = 0;
78 char c;
79 do
80 {
81 int skip_bytes;
82
83 c = *lineptr;
84 if (c == '\033' && skip_ansi_escape (lineptr, &skip_bytes))
85 {
86 /* We always have to preserve escapes. */
87 result.append (lineptr, lineptr + skip_bytes);
88 lineptr += skip_bytes;
89 continue;
90 }
91 if (c == '\0')
92 break;
93
94 ++lineptr;
95 ++column;
96
97 auto process_tab = [&] ()
98 {
99 int max_tab_len = tui_tab_width;
100
101 --column;
102 for (int j = column % max_tab_len;
103 j < max_tab_len;
104 column++, j++)
105 result.push_back (' ');
106 };
107
108 if (c == '\n' || c == '\r' || c == '\0')
109 {
110 /* Nothing. */
111 }
112 else if (c == '\t')
113 process_tab ();
114 else if (ISCNTRL (c))
115 {
116 result.push_back ('^');
117 result.push_back (c + 0100);
118 ++column;
119 }
120 else if (c == 0177)
121 {
122 result.push_back ('^');
123 result.push_back ('?');
124 ++column;
125 }
126 else
127 result.push_back (c);
128 }
129 while (c != '\0' && c != '\n' && c != '\r');
130
131 if (c == '\r' && *lineptr == '\n')
132 ++lineptr;
133 *ptr = lineptr;
134
135 if (length != nullptr)
136 *length = column;
137
138 return result;
139 }
140
141 void
142 tui_source_window_base::style_changed ()
143 {
144 if (tui_active && is_visible ())
145 refill ();
146 }
147
148 /* Function to display source in the source window. This function
149 initializes the horizontal scroll to 0. */
150 void
151 tui_source_window_base::update_source_window
152 (struct gdbarch *gdbarch,
153 const struct symtab_and_line &sal)
154 {
155 m_horizontal_offset = 0;
156 update_source_window_as_is (gdbarch, sal);
157 }
158
159
160 /* Function to display source in the source/asm window. This function
161 shows the source as specified by the horizontal offset. */
162 void
163 tui_source_window_base::update_source_window_as_is
164 (struct gdbarch *gdbarch,
165 const struct symtab_and_line &sal)
166 {
167 bool ret = set_contents (gdbarch, sal);
168
169 if (!ret)
170 erase_source_content ();
171 else
172 {
173 validate_scroll_offsets ();
174 update_breakpoint_info (nullptr, false);
175 update_exec_info (false);
176 show_source_content ();
177 }
178 }
179
180
181 /* Function to ensure that the source and/or disassembly windows
182 reflect the input address. */
183 void
184 tui_update_source_windows_with_addr (struct gdbarch *gdbarch, CORE_ADDR addr)
185 {
186 struct symtab_and_line sal {};
187 if (addr != 0)
188 sal = find_pc_line (addr, 0);
189
190 for (struct tui_source_window_base *win_info : tui_source_windows ())
191 win_info->update_source_window (gdbarch, sal);
192 }
193
194 /* Function to ensure that the source and/or disassembly windows
195 reflect the symtab and line. */
196 void
197 tui_update_source_windows_with_line (struct symtab_and_line sal)
198 {
199 struct gdbarch *gdbarch = nullptr;
200 if (sal.symtab != nullptr)
201 {
202 find_line_pc (sal.symtab, sal.line, &sal.pc);
203 gdbarch = sal.symtab->compunit ()->objfile ()->arch ();
204 }
205
206 for (struct tui_source_window_base *win_info : tui_source_windows ())
207 win_info->update_source_window (gdbarch, sal);
208 }
209
210 void
211 tui_source_window_base::do_erase_source_content (const char *str)
212 {
213 int x_pos;
214 int half_width = (width - box_size ()) / 2;
215
216 m_content.clear ();
217 if (handle != NULL)
218 {
219 werase (handle.get ());
220 check_and_display_highlight_if_needed ();
221
222 if (strlen (str) >= half_width)
223 x_pos = 1;
224 else
225 x_pos = half_width - strlen (str);
226 mvwaddstr (handle.get (),
227 (height / 2),
228 x_pos,
229 (char *) str);
230
231 refresh_window ();
232 }
233 }
234
235 /* See tui-winsource.h. */
236
237 void
238 tui_source_window_base::puts_to_pad_with_skip (const char *string, int skip)
239 {
240 gdb_assert (m_pad.get () != nullptr);
241 WINDOW *w = m_pad.get ();
242
243 while (skip > 0)
244 {
245 const char *next = strpbrk (string, "\033");
246
247 /* Print the plain text prefix. */
248 size_t n_chars = next == nullptr ? strlen (string) : next - string;
249 if (n_chars > 0)
250 {
251 if (skip > 0)
252 {
253 if (skip < n_chars)
254 {
255 string += skip;
256 n_chars -= skip;
257 skip = 0;
258 }
259 else
260 {
261 skip -= n_chars;
262 string += n_chars;
263 n_chars = 0;
264 }
265 }
266
267 if (n_chars > 0)
268 {
269 std::string copy (string, n_chars);
270 tui_puts (copy.c_str (), w);
271 }
272 }
273
274 /* We finished. */
275 if (next == nullptr)
276 break;
277
278 gdb_assert (*next == '\033');
279
280 int n_read;
281 if (skip_ansi_escape (next, &n_read))
282 {
283 std::string copy (next, n_read);
284 tui_puts (copy.c_str (), w);
285 next += n_read;
286 }
287 else
288 gdb_assert_not_reached ("unhandled escape");
289
290 string = next;
291 }
292
293 if (*string != '\0')
294 tui_puts (string, w);
295 }
296
297 /* Redraw the complete line of a source or disassembly window. */
298 void
299 tui_source_window_base::show_source_line (int lineno)
300 {
301 struct tui_source_element *line;
302
303 line = &m_content[lineno];
304 if (line->is_exec_point)
305 tui_set_reverse_mode (m_pad.get (), true);
306
307 wmove (m_pad.get (), lineno, 0);
308 puts_to_pad_with_skip (line->line.c_str (), m_pad_offset);
309
310 if (line->is_exec_point)
311 tui_set_reverse_mode (m_pad.get (), false);
312 }
313
314 /* See tui-winsource.h. */
315
316 void
317 tui_source_window_base::refresh_window ()
318 {
319 TUI_SCOPED_DEBUG_START_END ("window `%s`", name ());
320
321 /* tui_win_info::refresh_window would draw the empty background window to
322 the screen, potentially creating a flicker. */
323 wnoutrefresh (handle.get ());
324
325 int pad_width = getmaxx (m_pad.get ());
326 int left_margin = this->left_margin ();
327 int view_width = this->view_width ();
328 int content_width = m_max_length;
329 int pad_x = m_horizontal_offset - m_pad_offset;
330
331 tui_debug_printf ("pad_width = %d, left_margin = %d, view_width = %d",
332 pad_width, left_margin, view_width);
333 tui_debug_printf ("content_width = %d, pad_x = %d, m_horizontal_offset = %d",
334 content_width, pad_x, m_horizontal_offset);
335 tui_debug_printf ("m_pad_offset = %d", m_pad_offset);
336
337 gdb_assert (m_pad_offset >= 0);
338 gdb_assert (m_horizontal_offset + view_width
339 <= std::max (content_width, view_width));
340 gdb_assert (pad_x >= 0);
341 gdb_assert (m_horizontal_offset >= 0);
342
343 /* This function can be called before the pad has been allocated, this
344 should only occur during the initial startup. In this case the first
345 condition in the following asserts will not be true, but the nullptr
346 check will. */
347 gdb_assert (pad_width > 0 || m_pad.get () == nullptr);
348 gdb_assert (pad_x + view_width <= pad_width || m_pad.get () == nullptr);
349
350 int sminrow = y + box_width ();
351 int smincol = x + box_width () + left_margin;
352 int smaxrow = sminrow + m_content.size () - 1;
353 int smaxcol = smincol + view_width - 1;
354 prefresh (m_pad.get (), 0, pad_x, sminrow, smincol, smaxrow, smaxcol);
355 }
356
357 void
358 tui_source_window_base::show_source_content ()
359 {
360 TUI_SCOPED_DEBUG_START_END ("window `%s`", name ());
361
362 gdb_assert (!m_content.empty ());
363
364 /* The pad should be at least as wide as the window, but ideally, as wide
365 as the content, however, for some very wide content this might not be
366 possible. */
367 int required_pad_width = std::max (m_max_length, width);
368 int required_pad_height = m_content.size ();
369
370 /* If the required pad width is wider than the previously requested pad
371 width, then we might want to grow the pad. */
372 if (required_pad_width > m_pad_requested_width
373 || required_pad_height > getmaxy (m_pad.get ()))
374 {
375 /* The current pad width. */
376 int pad_width = m_pad == nullptr ? 0 : getmaxx (m_pad.get ());
377
378 gdb_assert (pad_width <= m_pad_requested_width);
379
380 /* If the current pad width is smaller than the previously requested
381 pad width, then this means we previously failed to allocate a
382 bigger pad. There's no point asking again, so we'll just make so
383 with the pad we currently have. */
384 if (pad_width == m_pad_requested_width
385 || required_pad_height > getmaxy (m_pad.get ()))
386 {
387 pad_width = required_pad_width;
388
389 do
390 {
391 /* Try to allocate a new pad. */
392 m_pad.reset (newpad (required_pad_height, pad_width));
393
394 if (m_pad == nullptr)
395 {
396 int reduced_width = std::max (pad_width / 2, width);
397 if (reduced_width == pad_width)
398 error (_("failed to setup source window"));
399 pad_width = reduced_width;
400 }
401 }
402 while (m_pad == nullptr);
403 }
404
405 m_pad_requested_width = required_pad_width;
406 tui_debug_printf ("requested width %d, allocated width %d",
407 required_pad_width, getmaxx (m_pad.get ()));
408 }
409
410 gdb_assert (m_pad != nullptr);
411 werase (m_pad.get ());
412 for (int lineno = 0; lineno < m_content.size (); lineno++)
413 show_source_line (lineno);
414
415 if (can_box ())
416 {
417 /* Calling check_and_display_highlight_if_needed will call refresh_window
418 (so long as the current window can be boxed), which will ensure that
419 the newly loaded window content is copied to the screen. */
420 check_and_display_highlight_if_needed ();
421 }
422 else
423 refresh_window ();
424 }
425
426 tui_source_window_base::tui_source_window_base ()
427 {
428 m_start_line_or_addr.loa = LOA_ADDRESS;
429 m_start_line_or_addr.u.addr = 0;
430
431 gdb::observers::styling_changed.attach
432 (std::bind (&tui_source_window::style_changed, this),
433 m_observable, "tui-winsource");
434 }
435
436 tui_source_window_base::~tui_source_window_base ()
437 {
438 gdb::observers::styling_changed.detach (m_observable);
439 }
440
441 /* See tui-data.h. */
442
443 void
444 tui_source_window_base::update_tab_width ()
445 {
446 werase (handle.get ());
447 rerender ();
448 }
449
450 void
451 tui_source_window_base::rerender ()
452 {
453 TUI_SCOPED_DEBUG_START_END ("window `%s`", name ());
454
455 if (!m_content.empty ())
456 {
457 struct symtab_and_line cursal
458 = get_current_source_symtab_and_line ();
459
460 if (m_start_line_or_addr.loa == LOA_LINE)
461 cursal.line = m_start_line_or_addr.u.line_no;
462 else
463 cursal.pc = m_start_line_or_addr.u.addr;
464 update_source_window (m_gdbarch, cursal);
465 }
466 else if (deprecated_safe_get_selected_frame () != NULL)
467 {
468 struct symtab_and_line cursal
469 = get_current_source_symtab_and_line ();
470 frame_info_ptr frame = deprecated_safe_get_selected_frame ();
471 struct gdbarch *gdbarch = get_frame_arch (frame);
472
473 struct symtab *s = find_pc_line_symtab (get_frame_pc (frame));
474 if (this != TUI_SRC_WIN)
475 find_line_pc (s, cursal.line, &cursal.pc);
476 update_source_window (gdbarch, cursal);
477 }
478 else
479 erase_source_content ();
480 }
481
482 /* See tui-data.h. */
483
484 void
485 tui_source_window_base::refill ()
486 {
487 symtab_and_line sal {};
488
489 if (this == TUI_SRC_WIN)
490 {
491 sal = get_current_source_symtab_and_line ();
492 if (sal.symtab == NULL)
493 {
494 frame_info_ptr fi = deprecated_safe_get_selected_frame ();
495 if (fi != nullptr)
496 sal = find_pc_line (get_frame_pc (fi), 0);
497 }
498 }
499
500 if (sal.pspace == nullptr)
501 sal.pspace = current_program_space;
502
503 if (m_start_line_or_addr.loa == LOA_LINE)
504 sal.line = m_start_line_or_addr.u.line_no;
505 else
506 sal.pc = m_start_line_or_addr.u.addr;
507
508 update_source_window_as_is (m_gdbarch, sal);
509 }
510
511 /* See tui-winsource.h. */
512
513 bool
514 tui_source_window_base::validate_scroll_offsets ()
515 {
516 TUI_SCOPED_DEBUG_START_END ("window `%s`", name ());
517
518 int original_pad_offset = m_pad_offset;
519
520 if (m_horizontal_offset < 0)
521 m_horizontal_offset = 0;
522
523 int content_width = m_max_length;
524 int pad_width = getmaxx (m_pad.get ());
525 int view_width = this->view_width ();
526
527 tui_debug_printf ("pad_width = %d, view_width = %d, content_width = %d",
528 pad_width, view_width, content_width);
529 tui_debug_printf ("original_pad_offset = %d, m_horizontal_offset = %d",
530 original_pad_offset, m_horizontal_offset);
531
532 if (m_horizontal_offset + view_width > content_width)
533 m_horizontal_offset = std::max (content_width - view_width, 0);
534
535 if ((m_horizontal_offset + view_width) > (m_pad_offset + pad_width))
536 {
537 m_pad_offset = std::min (m_horizontal_offset, content_width - pad_width);
538 m_pad_offset = std::max (m_pad_offset, 0);
539 }
540 else if (m_horizontal_offset < m_pad_offset)
541 m_pad_offset = std::max (m_horizontal_offset + view_width - pad_width, 0);
542
543 gdb_assert (m_pad_offset >= 0);
544 return (original_pad_offset != m_pad_offset);
545 }
546
547 /* Scroll the source forward or backward horizontally. */
548
549 void
550 tui_source_window_base::do_scroll_horizontal (int num_to_scroll)
551 {
552 if (!m_content.empty ())
553 {
554 m_horizontal_offset += num_to_scroll;
555
556 if (validate_scroll_offsets ())
557 show_source_content ();
558
559 refresh_window ();
560 }
561 }
562
563
564 /* Set or clear the is_exec_point flag in the line whose line is
565 line_no. */
566
567 void
568 tui_source_window_base::set_is_exec_point_at (struct tui_line_or_address l)
569 {
570 bool changed = false;
571 int i;
572
573 i = 0;
574 while (i < m_content.size ())
575 {
576 bool new_state;
577 struct tui_line_or_address content_loa =
578 m_content[i].line_or_addr;
579
580 if (content_loa.loa == l.loa
581 && ((l.loa == LOA_LINE && content_loa.u.line_no == l.u.line_no)
582 || (l.loa == LOA_ADDRESS && content_loa.u.addr == l.u.addr)))
583 new_state = true;
584 else
585 new_state = false;
586 if (new_state != m_content[i].is_exec_point)
587 {
588 changed = true;
589 m_content[i].is_exec_point = new_state;
590 }
591 i++;
592 }
593 if (changed)
594 refill ();
595 }
596
597 /* See tui-winsource.h. */
598
599 void
600 tui_update_all_breakpoint_info (struct breakpoint *being_deleted)
601 {
602 for (tui_source_window_base *win : tui_source_windows ())
603 {
604 if (win->update_breakpoint_info (being_deleted, false))
605 win->update_exec_info ();
606 }
607 }
608
609
610 /* Scan the source window and the breakpoints to update the break_mode
611 information for each line.
612
613 Returns true if something changed and the execution window must be
614 refreshed. */
615
616 bool
617 tui_source_window_base::update_breakpoint_info
618 (struct breakpoint *being_deleted, bool current_only)
619 {
620 int i;
621 bool need_refresh = false;
622
623 for (i = 0; i < m_content.size (); i++)
624 {
625 struct tui_source_element *line;
626
627 line = &m_content[i];
628 if (current_only && !line->is_exec_point)
629 continue;
630
631 /* Scan each breakpoint to see if the current line has something to
632 do with it. Identify enable/disabled breakpoints as well as
633 those that we already hit. */
634 tui_bp_flags mode = 0;
635 for (breakpoint &bp : all_breakpoints ())
636 {
637 if (&bp == being_deleted)
638 continue;
639
640 for (bp_location &loc : bp.locations ())
641 {
642 if (location_matches_p (&loc, i))
643 {
644 if (bp.enable_state == bp_disabled)
645 mode |= TUI_BP_DISABLED;
646 else
647 mode |= TUI_BP_ENABLED;
648 if (bp.hit_count)
649 mode |= TUI_BP_HIT;
650 if (bp.first_loc ().cond)
651 mode |= TUI_BP_CONDITIONAL;
652 if (bp.type == bp_hardware_breakpoint)
653 mode |= TUI_BP_HARDWARE;
654 }
655 }
656 }
657
658 if (line->break_mode != mode)
659 {
660 line->break_mode = mode;
661 need_refresh = true;
662 }
663 }
664 return need_refresh;
665 }
666
667 /* See tui-winsource.h. */
668
669 void
670 tui_source_window_base::update_exec_info (bool refresh_p)
671 {
672 update_breakpoint_info (nullptr, true);
673 for (int i = 0; i < m_content.size (); i++)
674 {
675 struct tui_source_element *src_element = &m_content[i];
676 /* Add 1 for '\0'. */
677 char element[TUI_EXECINFO_SIZE + 1];
678 /* Initialize all but last element. */
679 char space = tui_left_margin_verbose ? '_' : ' ';
680 memset (element, space, TUI_EXECINFO_SIZE);
681 /* Initialize last element. */
682 element[TUI_EXECINFO_SIZE] = '\0';
683
684 /* Now update the exec info content based upon the state
685 of each line as indicated by the source content. */
686 tui_bp_flags mode = src_element->break_mode;
687 if (mode & TUI_BP_HIT)
688 element[TUI_BP_HIT_POS] = (mode & TUI_BP_HARDWARE) ? 'H' : 'B';
689 else if (mode & (TUI_BP_ENABLED | TUI_BP_DISABLED))
690 element[TUI_BP_HIT_POS] = (mode & TUI_BP_HARDWARE) ? 'h' : 'b';
691
692 if (mode & TUI_BP_ENABLED)
693 element[TUI_BP_BREAK_POS] = '+';
694 else if (mode & TUI_BP_DISABLED)
695 element[TUI_BP_BREAK_POS] = '-';
696
697 if (src_element->is_exec_point)
698 element[TUI_EXEC_POS] = '>';
699
700 mvwaddstr (handle.get (), i + box_width (), box_width (), element);
701
702 show_line_number (i);
703 }
704 if (refresh_p)
705 refresh_window ();
706 }