1 //===-- IOHandlerCursesGUI.cpp --------------------------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "lldb/Core/IOHandlerCursesGUI.h"
10 #include "lldb/Host/Config.h"
12 #if LLDB_ENABLE_CURSES
13 #if CURSES_HAVE_NCURSES_CURSES_H
14 #include <ncurses/curses.h>
15 #include <ncurses/panel.h>
22 #if defined(__APPLE__)
27 #include "lldb/Core/Debugger.h"
28 #include "lldb/Core/ValueObjectUpdater.h"
29 #include "lldb/Host/File.h"
30 #include "lldb/Utility/AnsiTerminal.h"
31 #include "lldb/Utility/Predicate.h"
32 #include "lldb/Utility/Status.h"
33 #include "lldb/Utility/StreamString.h"
34 #include "lldb/Utility/StringList.h"
35 #include "lldb/lldb-forward.h"
37 #include "lldb/Interpreter/CommandCompletions.h"
38 #include "lldb/Interpreter/CommandInterpreter.h"
39 #include "lldb/Interpreter/OptionGroupPlatform.h"
41 #if LLDB_ENABLE_CURSES
42 #include "lldb/Breakpoint/BreakpointLocation.h"
43 #include "lldb/Core/Module.h"
44 #include "lldb/Core/PluginManager.h"
45 #include "lldb/Core/ValueObject.h"
46 #include "lldb/Core/ValueObjectRegister.h"
47 #include "lldb/Symbol/Block.h"
48 #include "lldb/Symbol/CompileUnit.h"
49 #include "lldb/Symbol/Function.h"
50 #include "lldb/Symbol/Symbol.h"
51 #include "lldb/Symbol/VariableList.h"
52 #include "lldb/Target/Process.h"
53 #include "lldb/Target/RegisterContext.h"
54 #include "lldb/Target/StackFrame.h"
55 #include "lldb/Target/StopInfo.h"
56 #include "lldb/Target/Target.h"
57 #include "lldb/Target/Thread.h"
58 #include "lldb/Utility/State.h"
61 #include "llvm/ADT/StringRef.h"
64 #include "lldb/Host/windows/windows.h"
78 #include <type_traits>
81 using namespace lldb_private
;
82 using llvm::StringRef
;
84 // we may want curses to be disabled for some builds for instance, windows
85 #if LLDB_ENABLE_CURSES
92 #define KEY_DELETE 127
94 #define KEY_SHIFT_TAB (KEY_MAX + 1)
95 #define KEY_ALT_ENTER (KEY_MAX + 2)
101 class WindowDelegate
;
102 typedef std::shared_ptr
<Menu
> MenuSP
;
103 typedef std::shared_ptr
<MenuDelegate
> MenuDelegateSP
;
104 typedef std::shared_ptr
<Window
> WindowSP
;
105 typedef std::shared_ptr
<WindowDelegate
> WindowDelegateSP
;
106 typedef std::vector
<MenuSP
> Menus
;
107 typedef std::vector
<WindowSP
> Windows
;
108 typedef std::vector
<WindowDelegateSP
> WindowDelegates
;
111 type summary add
-s
"x=${var.x}, y=${var.y}" curses::Point
112 type summary add
-s
"w=${var.width}, h=${var.height}" curses::Size
113 type summary add
-s
"${var.origin%S} ${var.size%S}" curses::Rect
120 Point(int _x
= 0, int _y
= 0) : x(_x
), y(_y
) {}
127 Point
&operator+=(const Point
&rhs
) {
133 void Dump() { printf("(x=%i, y=%i)\n", x
, y
); }
136 bool operator==(const Point
&lhs
, const Point
&rhs
) {
137 return lhs
.x
== rhs
.x
&& lhs
.y
== rhs
.y
;
140 bool operator!=(const Point
&lhs
, const Point
&rhs
) {
141 return lhs
.x
!= rhs
.x
|| lhs
.y
!= rhs
.y
;
147 Size(int w
= 0, int h
= 0) : width(w
), height(h
) {}
154 void Dump() { printf("(w=%i, h=%i)\n", width
, height
); }
157 bool operator==(const Size
&lhs
, const Size
&rhs
) {
158 return lhs
.width
== rhs
.width
&& lhs
.height
== rhs
.height
;
161 bool operator!=(const Size
&lhs
, const Size
&rhs
) {
162 return lhs
.width
!= rhs
.width
|| lhs
.height
!= rhs
.height
;
169 Rect() : origin(), size() {}
171 Rect(const Point
&p
, const Size
&s
) : origin(p
), size(s
) {}
179 printf("(x=%i, y=%i), w=%i, h=%i)\n", origin
.x
, origin
.y
, size
.width
,
183 void Inset(int w
, int h
) {
184 if (size
.width
> w
* 2)
188 if (size
.height
> h
* 2)
189 size
.height
-= h
* 2;
193 // Return a status bar rectangle which is the last line of this rectangle.
194 // This rectangle will be modified to not include the status bar area.
195 Rect
MakeStatusBar() {
197 if (size
.height
> 1) {
198 status_bar
.origin
.x
= origin
.x
;
199 status_bar
.origin
.y
= size
.height
;
200 status_bar
.size
.width
= size
.width
;
201 status_bar
.size
.height
= 1;
207 // Return a menubar rectangle which is the first line of this rectangle. This
208 // rectangle will be modified to not include the menubar area.
211 if (size
.height
> 1) {
212 menubar
.origin
.x
= origin
.x
;
213 menubar
.origin
.y
= origin
.y
;
214 menubar
.size
.width
= size
.width
;
215 menubar
.size
.height
= 1;
222 void HorizontalSplitPercentage(float top_percentage
, Rect
&top
,
223 Rect
&bottom
) const {
224 float top_height
= top_percentage
* size
.height
;
225 HorizontalSplit(top_height
, top
, bottom
);
228 void HorizontalSplit(int top_height
, Rect
&top
, Rect
&bottom
) const {
230 if (top_height
< size
.height
) {
231 top
.size
.height
= top_height
;
232 bottom
.origin
.x
= origin
.x
;
233 bottom
.origin
.y
= origin
.y
+ top
.size
.height
;
234 bottom
.size
.width
= size
.width
;
235 bottom
.size
.height
= size
.height
- top
.size
.height
;
241 void VerticalSplitPercentage(float left_percentage
, Rect
&left
,
243 float left_width
= left_percentage
* size
.width
;
244 VerticalSplit(left_width
, left
, right
);
247 void VerticalSplit(int left_width
, Rect
&left
, Rect
&right
) const {
249 if (left_width
< size
.width
) {
250 left
.size
.width
= left_width
;
251 right
.origin
.x
= origin
.x
+ left
.size
.width
;
252 right
.origin
.y
= origin
.y
;
253 right
.size
.width
= size
.width
- left
.size
.width
;
254 right
.size
.height
= size
.height
;
261 bool operator==(const Rect
&lhs
, const Rect
&rhs
) {
262 return lhs
.origin
== rhs
.origin
&& lhs
.size
== rhs
.size
;
265 bool operator!=(const Rect
&lhs
, const Rect
&rhs
) {
266 return lhs
.origin
!= rhs
.origin
|| lhs
.size
!= rhs
.size
;
269 enum HandleCharResult
{
275 enum class MenuActionResult
{
278 Quit
// Exit all menus and quit
283 const char *description
;
286 // COLOR_PAIR index names
288 // First 16 colors are 8 black background and 8 blue background colors,
289 // needed by OutputColoredStringTruncated().
306 // Other colors, as needed.
309 LastColorPairIndex
= MagentaOnWhite
312 class WindowDelegate
{
314 virtual ~WindowDelegate() = default;
316 virtual bool WindowDelegateDraw(Window
&window
, bool force
) {
317 return false; // Drawing not handled
320 virtual HandleCharResult
WindowDelegateHandleChar(Window
&window
, int key
) {
321 return eKeyNotHandled
;
324 virtual const char *WindowDelegateGetHelpText() { return nullptr; }
326 virtual KeyHelp
*WindowDelegateGetKeyHelp() { return nullptr; }
329 class HelpDialogDelegate
: public WindowDelegate
{
331 HelpDialogDelegate(const char *text
, KeyHelp
*key_help_array
);
333 ~HelpDialogDelegate() override
;
335 bool WindowDelegateDraw(Window
&window
, bool force
) override
;
337 HandleCharResult
WindowDelegateHandleChar(Window
&window
, int key
) override
;
339 size_t GetNumLines() const { return m_text
.GetSize(); }
341 size_t GetMaxLineLength() const { return m_text
.GetMaxStringLength(); }
345 int m_first_visible_line
= 0;
348 // A surface is an abstraction for something than can be drawn on. The surface
349 // have a width, a height, a cursor position, and a multitude of drawing
350 // operations. This type should be sub-classed to get an actually useful ncurses
351 // object, such as a Window or a Pad.
354 enum class Type
{ Window
, Pad
};
356 Surface(Surface::Type type
) : m_type(type
) {}
358 WINDOW
*get() { return m_window
; }
360 operator WINDOW
*() { return m_window
; }
362 Surface
SubSurface(Rect bounds
) {
363 Surface
subSurface(m_type
);
364 if (m_type
== Type::Pad
)
365 subSurface
.m_window
=
366 ::subpad(m_window
, bounds
.size
.height
, bounds
.size
.width
,
367 bounds
.origin
.y
, bounds
.origin
.x
);
369 subSurface
.m_window
=
370 ::derwin(m_window
, bounds
.size
.height
, bounds
.size
.width
,
371 bounds
.origin
.y
, bounds
.origin
.x
);
375 // Copy a region of the surface to another surface.
376 void CopyToSurface(Surface
&target
, Point source_origin
, Point target_origin
,
378 ::copywin(m_window
, target
.get(), source_origin
.y
, source_origin
.x
,
379 target_origin
.y
, target_origin
.x
,
380 target_origin
.y
+ size
.height
- 1,
381 target_origin
.x
+ size
.width
- 1, false);
384 int GetCursorX() const { return getcurx(m_window
); }
385 int GetCursorY() const { return getcury(m_window
); }
386 void MoveCursor(int x
, int y
) { ::wmove(m_window
, y
, x
); }
388 void AttributeOn(attr_t attr
) { ::wattron(m_window
, attr
); }
389 void AttributeOff(attr_t attr
) { ::wattroff(m_window
, attr
); }
391 int GetMaxX() const { return getmaxx(m_window
); }
392 int GetMaxY() const { return getmaxy(m_window
); }
393 int GetWidth() const { return GetMaxX(); }
394 int GetHeight() const { return GetMaxY(); }
395 Size
GetSize() const { return Size(GetWidth(), GetHeight()); }
396 // Get a zero origin rectangle width the surface size.
397 Rect
GetFrame() const { return Rect(Point(), GetSize()); }
399 void Clear() { ::wclear(m_window
); }
400 void Erase() { ::werase(m_window
); }
402 void SetBackground(int color_pair_idx
) {
403 ::wbkgd(m_window
, COLOR_PAIR(color_pair_idx
));
406 void PutChar(int ch
) { ::waddch(m_window
, ch
); }
407 void PutCString(const char *s
, int len
= -1) { ::waddnstr(m_window
, s
, len
); }
409 void PutCStringTruncated(int right_pad
, const char *s
, int len
= -1) {
410 int bytes_left
= GetWidth() - GetCursorX();
411 if (bytes_left
> right_pad
) {
412 bytes_left
-= right_pad
;
413 ::waddnstr(m_window
, s
, len
< 0 ? bytes_left
: std::min(bytes_left
, len
));
417 void Printf(const char *format
, ...) __attribute__((format(printf
, 2, 3))) {
419 va_start(args
, format
);
420 vw_printw(m_window
, format
, args
);
424 void PrintfTruncated(int right_pad
, const char *format
, ...)
425 __attribute__((format(printf
, 3, 4))) {
427 va_start(args
, format
);
429 strm
.PrintfVarArg(format
, args
);
431 PutCStringTruncated(right_pad
, strm
.GetData());
434 void VerticalLine(int n
, chtype v_char
= ACS_VLINE
) {
435 ::wvline(m_window
, v_char
, n
);
437 void HorizontalLine(int n
, chtype h_char
= ACS_HLINE
) {
438 ::whline(m_window
, h_char
, n
);
440 void Box(chtype v_char
= ACS_VLINE
, chtype h_char
= ACS_HLINE
) {
441 ::box(m_window
, v_char
, h_char
);
444 void TitledBox(const char *title
, chtype v_char
= ACS_VLINE
,
445 chtype h_char
= ACS_HLINE
) {
447 int title_offset
= 2;
448 MoveCursor(title_offset
, 0);
450 PutCString(title
, GetWidth() - title_offset
);
454 void Box(const Rect
&bounds
, chtype v_char
= ACS_VLINE
,
455 chtype h_char
= ACS_HLINE
) {
456 MoveCursor(bounds
.origin
.x
, bounds
.origin
.y
);
457 VerticalLine(bounds
.size
.height
);
458 HorizontalLine(bounds
.size
.width
);
459 PutChar(ACS_ULCORNER
);
461 MoveCursor(bounds
.origin
.x
+ bounds
.size
.width
- 1, bounds
.origin
.y
);
462 VerticalLine(bounds
.size
.height
);
463 PutChar(ACS_URCORNER
);
465 MoveCursor(bounds
.origin
.x
, bounds
.origin
.y
+ bounds
.size
.height
- 1);
466 HorizontalLine(bounds
.size
.width
);
467 PutChar(ACS_LLCORNER
);
469 MoveCursor(bounds
.origin
.x
+ bounds
.size
.width
- 1,
470 bounds
.origin
.y
+ bounds
.size
.height
- 1);
471 PutChar(ACS_LRCORNER
);
474 void TitledBox(const Rect
&bounds
, const char *title
,
475 chtype v_char
= ACS_VLINE
, chtype h_char
= ACS_HLINE
) {
476 Box(bounds
, v_char
, h_char
);
477 int title_offset
= 2;
478 MoveCursor(bounds
.origin
.x
+ title_offset
, bounds
.origin
.y
);
480 PutCString(title
, bounds
.size
.width
- title_offset
);
484 // Curses doesn't allow direct output of color escape sequences, but that's
485 // how we get source lines from the Highligher class. Read the line and
486 // convert color escape sequences to curses color attributes. Use
487 // first_skip_count to skip leading visible characters. Returns false if all
488 // visible characters were skipped due to first_skip_count.
489 bool OutputColoredStringTruncated(int right_pad
, StringRef string
,
490 size_t skip_first_count
,
491 bool use_blue_background
) {
495 wattr_get(m_window
, &saved_attr
, &saved_pair
, nullptr);
496 if (use_blue_background
)
497 ::wattron(m_window
, COLOR_PAIR(WhiteOnBlue
));
498 while (!string
.empty()) {
499 size_t esc_pos
= string
.find(ANSI_ESC_START
);
500 if (esc_pos
== StringRef::npos
) {
501 string
= string
.substr(skip_first_count
);
502 if (!string
.empty()) {
503 PutCStringTruncated(right_pad
, string
.data(), string
.size());
509 if (skip_first_count
> 0) {
510 int skip
= std::min(esc_pos
, skip_first_count
);
511 string
= string
.substr(skip
);
512 skip_first_count
-= skip
;
516 PutCStringTruncated(right_pad
, string
.data(), esc_pos
);
518 string
= string
.drop_front(esc_pos
);
521 bool consumed
= string
.consume_front(ANSI_ESC_START
);
523 UNUSED_IF_ASSERT_DISABLED(consumed
);
524 // This is written to match our Highlighter classes, which seem to
525 // generate only foreground color escape sequences. If necessary, this
526 // will need to be extended.
527 // Only 8 basic foreground colors, underline and reset, our Highlighter
528 // doesn't use anything else.
530 if (!!string
.consumeInteger(10, value
) || // Returns false on success.
531 !(value
== 0 || value
== ANSI_CTRL_UNDERLINE
||
532 (value
>= ANSI_FG_COLOR_BLACK
&& value
<= ANSI_FG_COLOR_WHITE
))) {
533 llvm::errs() << "No valid color code in color escape sequence.\n";
536 if (!string
.consume_front(ANSI_ESC_END
)) {
537 llvm::errs() << "Missing '" << ANSI_ESC_END
538 << "' in color escape sequence.\n";
541 if (value
== 0) { // Reset.
542 wattr_set(m_window
, saved_attr
, saved_pair
, nullptr);
543 if (use_blue_background
)
544 ::wattron(m_window
, COLOR_PAIR(WhiteOnBlue
));
545 } else if (value
== ANSI_CTRL_UNDERLINE
) {
546 ::wattron(m_window
, A_UNDERLINE
);
548 // Mapped directly to first 16 color pairs (black/blue background).
549 ::wattron(m_window
, COLOR_PAIR(value
- ANSI_FG_COLOR_BLACK
+ 1 +
550 (use_blue_background
? 8 : 0)));
553 wattr_set(m_window
, saved_attr
, saved_pair
, nullptr);
559 WINDOW
*m_window
= nullptr;
562 class Pad
: public Surface
{
564 Pad(Size size
) : Surface(Surface::Type::Pad
) {
565 m_window
= ::newpad(size
.height
, size
.width
);
568 ~Pad() { ::delwin(m_window
); }
571 class Window
: public Surface
{
573 Window(const char *name
)
574 : Surface(Surface::Type::Window
), m_name(name
), m_panel(nullptr),
575 m_parent(nullptr), m_subwindows(), m_delegate_sp(),
576 m_curr_active_window_idx(UINT32_MAX
),
577 m_prev_active_window_idx(UINT32_MAX
), m_delete(false),
578 m_needs_update(true), m_can_activate(true), m_is_subwin(false) {}
580 Window(const char *name
, WINDOW
*w
, bool del
= true)
581 : Surface(Surface::Type::Window
), m_name(name
), m_panel(nullptr),
582 m_parent(nullptr), m_subwindows(), m_delegate_sp(),
583 m_curr_active_window_idx(UINT32_MAX
),
584 m_prev_active_window_idx(UINT32_MAX
), m_delete(del
),
585 m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
590 Window(const char *name
, const Rect
&bounds
)
591 : Surface(Surface::Type::Window
), m_name(name
), m_panel(nullptr),
592 m_parent(nullptr), m_subwindows(), m_delegate_sp(),
593 m_curr_active_window_idx(UINT32_MAX
),
594 m_prev_active_window_idx(UINT32_MAX
), m_delete(false),
595 m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
596 Reset(::newwin(bounds
.size
.height
, bounds
.size
.width
, bounds
.origin
.y
,
605 void Reset(WINDOW
*w
= nullptr, bool del
= true) {
610 ::del_panel(m_panel
);
613 if (m_window
&& m_delete
) {
620 m_panel
= ::new_panel(m_window
);
625 // Get the rectangle in our parent window
626 Rect
GetBounds() const { return Rect(GetParentOrigin(), GetSize()); }
628 Rect
GetCenteredRect(int width
, int height
) {
629 Size size
= GetSize();
630 width
= std::min(size
.width
, width
);
631 height
= std::min(size
.height
, height
);
632 int x
= (size
.width
- width
) / 2;
633 int y
= (size
.height
- height
) / 2;
634 return Rect(Point(x
, y
), Size(width
, height
));
637 int GetChar() { return ::wgetch(m_window
); }
638 Point
GetParentOrigin() const { return Point(GetParentX(), GetParentY()); }
639 int GetParentX() const { return getparx(m_window
); }
640 int GetParentY() const { return getpary(m_window
); }
641 void MoveWindow(int x
, int y
) { MoveWindow(Point(x
, y
)); }
642 void Resize(int w
, int h
) { ::wresize(m_window
, h
, w
); }
643 void Resize(const Size
&size
) {
644 ::wresize(m_window
, size
.height
, size
.width
);
646 void MoveWindow(const Point
&origin
) {
647 const bool moving_window
= origin
!= GetParentOrigin();
648 if (m_is_subwin
&& moving_window
) {
649 // Can't move subwindows, must delete and re-create
650 Size size
= GetSize();
651 Reset(::subwin(m_parent
->m_window
, size
.height
, size
.width
, origin
.y
,
655 ::mvwin(m_window
, origin
.y
, origin
.x
);
659 void SetBounds(const Rect
&bounds
) {
660 const bool moving_window
= bounds
.origin
!= GetParentOrigin();
661 if (m_is_subwin
&& moving_window
) {
662 // Can't move subwindows, must delete and re-create
663 Reset(::subwin(m_parent
->m_window
, bounds
.size
.height
, bounds
.size
.width
,
664 bounds
.origin
.y
, bounds
.origin
.x
),
668 MoveWindow(bounds
.origin
);
674 ::touchwin(m_window
);
679 WindowSP
CreateSubWindow(const char *name
, const Rect
&bounds
,
681 auto get_window
= [this, &bounds
]() {
683 ? ::subwin(m_window
, bounds
.size
.height
, bounds
.size
.width
,
684 bounds
.origin
.y
, bounds
.origin
.x
)
685 : ::newwin(bounds
.size
.height
, bounds
.size
.width
,
686 bounds
.origin
.y
, bounds
.origin
.x
);
688 WindowSP subwindow_sp
= std::make_shared
<Window
>(name
, get_window(), true);
689 subwindow_sp
->m_is_subwin
= subwindow_sp
.operator bool();
690 subwindow_sp
->m_parent
= this;
692 m_prev_active_window_idx
= m_curr_active_window_idx
;
693 m_curr_active_window_idx
= m_subwindows
.size();
695 m_subwindows
.push_back(subwindow_sp
);
696 ::top_panel(subwindow_sp
->m_panel
);
697 m_needs_update
= true;
701 bool RemoveSubWindow(Window
*window
) {
702 Windows::iterator pos
, end
= m_subwindows
.end();
704 for (pos
= m_subwindows
.begin(); pos
!= end
; ++pos
, ++i
) {
705 if ((*pos
).get() == window
) {
706 if (m_prev_active_window_idx
== i
)
707 m_prev_active_window_idx
= UINT32_MAX
;
708 else if (m_prev_active_window_idx
!= UINT32_MAX
&&
709 m_prev_active_window_idx
> i
)
710 --m_prev_active_window_idx
;
712 if (m_curr_active_window_idx
== i
)
713 m_curr_active_window_idx
= UINT32_MAX
;
714 else if (m_curr_active_window_idx
!= UINT32_MAX
&&
715 m_curr_active_window_idx
> i
)
716 --m_curr_active_window_idx
;
718 m_subwindows
.erase(pos
);
719 m_needs_update
= true;
730 WindowSP
FindSubWindow(const char *name
) {
731 Windows::iterator pos
, end
= m_subwindows
.end();
733 for (pos
= m_subwindows
.begin(); pos
!= end
; ++pos
, ++i
) {
734 if ((*pos
)->m_name
== name
)
740 void RemoveSubWindows() {
741 m_curr_active_window_idx
= UINT32_MAX
;
742 m_prev_active_window_idx
= UINT32_MAX
;
743 for (Windows::iterator pos
= m_subwindows
.begin();
744 pos
!= m_subwindows
.end(); pos
= m_subwindows
.erase(pos
)) {
753 // Window drawing utilities
754 void DrawTitleBox(const char *title
, const char *bottom_message
= nullptr) {
757 attr
= A_BOLD
| COLOR_PAIR(BlackOnWhite
);
766 if (title
&& title
[0]) {
772 if (bottom_message
&& bottom_message
[0]) {
773 int bottom_message_length
= strlen(bottom_message
);
774 int x
= GetWidth() - 3 - (bottom_message_length
+ 2);
777 MoveCursor(x
, GetHeight() - 1);
779 PutCString(bottom_message
);
782 MoveCursor(1, GetHeight() - 1);
784 PutCStringTruncated(1, bottom_message
);
791 virtual void Draw(bool force
) {
792 if (m_delegate_sp
&& m_delegate_sp
->WindowDelegateDraw(*this, force
))
795 for (auto &subwindow_sp
: m_subwindows
)
796 subwindow_sp
->Draw(force
);
799 bool CreateHelpSubwindow() {
801 const char *text
= m_delegate_sp
->WindowDelegateGetHelpText();
802 KeyHelp
*key_help
= m_delegate_sp
->WindowDelegateGetKeyHelp();
803 if ((text
&& text
[0]) || key_help
) {
804 std::unique_ptr
<HelpDialogDelegate
> help_delegate_up(
805 new HelpDialogDelegate(text
, key_help
));
806 const size_t num_lines
= help_delegate_up
->GetNumLines();
807 const size_t max_length
= help_delegate_up
->GetMaxLineLength();
808 Rect bounds
= GetBounds();
810 if (max_length
+ 4 < static_cast<size_t>(bounds
.size
.width
)) {
811 bounds
.origin
.x
+= (bounds
.size
.width
- max_length
+ 4) / 2;
812 bounds
.size
.width
= max_length
+ 4;
814 if (bounds
.size
.width
> 100) {
815 const int inset_w
= bounds
.size
.width
/ 4;
816 bounds
.origin
.x
+= inset_w
;
817 bounds
.size
.width
-= 2 * inset_w
;
821 if (num_lines
+ 2 < static_cast<size_t>(bounds
.size
.height
)) {
822 bounds
.origin
.y
+= (bounds
.size
.height
- num_lines
+ 2) / 2;
823 bounds
.size
.height
= num_lines
+ 2;
825 if (bounds
.size
.height
> 100) {
826 const int inset_h
= bounds
.size
.height
/ 4;
827 bounds
.origin
.y
+= inset_h
;
828 bounds
.size
.height
-= 2 * inset_h
;
831 WindowSP help_window_sp
;
832 Window
*parent_window
= GetParent();
834 help_window_sp
= parent_window
->CreateSubWindow("Help", bounds
, true);
836 help_window_sp
= CreateSubWindow("Help", bounds
, true);
837 help_window_sp
->SetDelegate(
838 WindowDelegateSP(help_delegate_up
.release()));
845 virtual HandleCharResult
HandleChar(int key
) {
846 // Always check the active window first
847 HandleCharResult result
= eKeyNotHandled
;
848 WindowSP active_window_sp
= GetActiveWindow();
849 if (active_window_sp
) {
850 result
= active_window_sp
->HandleChar(key
);
851 if (result
!= eKeyNotHandled
)
856 result
= m_delegate_sp
->WindowDelegateHandleChar(*this, key
);
857 if (result
!= eKeyNotHandled
)
861 // Then check for any windows that want any keys that weren't handled. This
862 // is typically only for a menubar. Make a copy of the subwindows in case
863 // any HandleChar() functions muck with the subwindows. If we don't do
864 // this, we can crash when iterating over the subwindows.
865 Windows
subwindows(m_subwindows
);
866 for (auto subwindow_sp
: subwindows
) {
867 if (!subwindow_sp
->m_can_activate
) {
868 HandleCharResult result
= subwindow_sp
->HandleChar(key
);
869 if (result
!= eKeyNotHandled
)
874 return eKeyNotHandled
;
877 WindowSP
GetActiveWindow() {
878 if (!m_subwindows
.empty()) {
879 if (m_curr_active_window_idx
>= m_subwindows
.size()) {
880 if (m_prev_active_window_idx
< m_subwindows
.size()) {
881 m_curr_active_window_idx
= m_prev_active_window_idx
;
882 m_prev_active_window_idx
= UINT32_MAX
;
883 } else if (IsActive()) {
884 m_prev_active_window_idx
= UINT32_MAX
;
885 m_curr_active_window_idx
= UINT32_MAX
;
887 // Find first window that wants to be active if this window is active
888 const size_t num_subwindows
= m_subwindows
.size();
889 for (size_t i
= 0; i
< num_subwindows
; ++i
) {
890 if (m_subwindows
[i
]->GetCanBeActive()) {
891 m_curr_active_window_idx
= i
;
898 if (m_curr_active_window_idx
< m_subwindows
.size())
899 return m_subwindows
[m_curr_active_window_idx
];
904 bool GetCanBeActive() const { return m_can_activate
; }
906 void SetCanBeActive(bool b
) { m_can_activate
= b
; }
908 void SetDelegate(const WindowDelegateSP
&delegate_sp
) {
909 m_delegate_sp
= delegate_sp
;
912 Window
*GetParent() const { return m_parent
; }
914 bool IsActive() const {
916 return m_parent
->GetActiveWindow().get() == this;
918 return true; // Top level window is always active
921 void SelectNextWindowAsActive() {
922 // Move active focus to next window
923 const int num_subwindows
= m_subwindows
.size();
925 if (m_curr_active_window_idx
!= UINT32_MAX
) {
926 m_prev_active_window_idx
= m_curr_active_window_idx
;
927 start_idx
= m_curr_active_window_idx
+ 1;
929 for (int idx
= start_idx
; idx
< num_subwindows
; ++idx
) {
930 if (m_subwindows
[idx
]->GetCanBeActive()) {
931 m_curr_active_window_idx
= idx
;
935 for (int idx
= 0; idx
< start_idx
; ++idx
) {
936 if (m_subwindows
[idx
]->GetCanBeActive()) {
937 m_curr_active_window_idx
= idx
;
943 void SelectPreviousWindowAsActive() {
944 // Move active focus to previous window
945 const int num_subwindows
= m_subwindows
.size();
946 int start_idx
= num_subwindows
- 1;
947 if (m_curr_active_window_idx
!= UINT32_MAX
) {
948 m_prev_active_window_idx
= m_curr_active_window_idx
;
949 start_idx
= m_curr_active_window_idx
- 1;
951 for (int idx
= start_idx
; idx
>= 0; --idx
) {
952 if (m_subwindows
[idx
]->GetCanBeActive()) {
953 m_curr_active_window_idx
= idx
;
957 for (int idx
= num_subwindows
- 1; idx
> start_idx
; --idx
) {
958 if (m_subwindows
[idx
]->GetCanBeActive()) {
959 m_curr_active_window_idx
= idx
;
965 const char *GetName() const { return m_name
.c_str(); }
971 Windows m_subwindows
;
972 WindowDelegateSP m_delegate_sp
;
973 uint32_t m_curr_active_window_idx
;
974 uint32_t m_prev_active_window_idx
;
981 Window(const Window
&) = delete;
982 const Window
&operator=(const Window
&) = delete;
989 // A scroll context defines a vertical region that needs to be visible in a
990 // scrolling area. The region is defined by the index of the start and end lines
991 // of the region. The start and end lines may be equal, in which case, the
992 // region is a single line.
993 struct ScrollContext
{
997 ScrollContext(int line
) : start(line
), end(line
) {}
998 ScrollContext(int _start
, int _end
) : start(_start
), end(_end
) {}
1000 void Offset(int offset
) {
1006 class FieldDelegate
{
1008 virtual ~FieldDelegate() = default;
1010 // Returns the number of lines needed to draw the field. The draw method will
1011 // be given a surface that have exactly this number of lines.
1012 virtual int FieldDelegateGetHeight() = 0;
1014 // Returns the scroll context in the local coordinates of the field. By
1015 // default, the scroll context spans the whole field. Bigger fields with
1016 // internal navigation should override this method to provide a finer context.
1017 // Typical override methods would first get the scroll context of the internal
1018 // element then add the offset of the element in the field.
1019 virtual ScrollContext
FieldDelegateGetScrollContext() {
1020 return ScrollContext(0, FieldDelegateGetHeight() - 1);
1023 // Draw the field in the given subpad surface. The surface have a height that
1024 // is equal to the height returned by FieldDelegateGetHeight(). If the field
1025 // is selected in the form window, then is_selected will be true.
1026 virtual void FieldDelegateDraw(Surface
&surface
, bool is_selected
) = 0;
1028 // Handle the key that wasn't handled by the form window or a container field.
1029 virtual HandleCharResult
FieldDelegateHandleChar(int key
) {
1030 return eKeyNotHandled
;
1033 // This is executed once the user exists the field, that is, once the user
1034 // navigates to the next or the previous field. This is particularly useful to
1035 // do in-field validation and error setting. Fields with internal navigation
1036 // should call this method on their fields.
1037 virtual void FieldDelegateExitCallback() {}
1039 // Fields may have internal navigation, for instance, a List Field have
1040 // multiple internal elements, which needs to be navigated. To allow for this
1041 // mechanism, the window shouldn't handle the navigation keys all the time,
1042 // and instead call the key handing method of the selected field. It should
1043 // only handle the navigation keys when the field contains a single element or
1044 // have the last or first element selected depending on if the user is
1045 // navigating forward or backward. Additionally, once a field is selected in
1046 // the forward or backward direction, its first or last internal element
1047 // should be selected. The following methods implements those mechanisms.
1049 // Returns true if the first element in the field is selected or if the field
1050 // contains a single element.
1051 virtual bool FieldDelegateOnFirstOrOnlyElement() { return true; }
1053 // Returns true if the last element in the field is selected or if the field
1054 // contains a single element.
1055 virtual bool FieldDelegateOnLastOrOnlyElement() { return true; }
1057 // Select the first element in the field if multiple elements exists.
1058 virtual void FieldDelegateSelectFirstElement() {}
1060 // Select the last element in the field if multiple elements exists.
1061 virtual void FieldDelegateSelectLastElement() {}
1063 // Returns true if the field has an error, false otherwise.
1064 virtual bool FieldDelegateHasError() { return false; }
1066 bool FieldDelegateIsVisible() { return m_is_visible
; }
1068 void FieldDelegateHide() { m_is_visible
= false; }
1070 void FieldDelegateShow() { m_is_visible
= true; }
1073 bool m_is_visible
= true;
1076 typedef std::unique_ptr
<FieldDelegate
> FieldDelegateUP
;
1078 class TextFieldDelegate
: public FieldDelegate
{
1080 TextFieldDelegate(const char *label
, const char *content
, bool required
)
1081 : m_label(label
), m_required(required
) {
1083 m_content
= content
;
1086 // Text fields are drawn as titled boxes of a single line, with a possible
1087 // error messages at the end.
1089 // __[Label]___________
1091 // |__________________|
1092 // - Error message if it exists.
1094 // The text field has a height of 3 lines. 2 lines for borders and 1 line for
1096 int GetFieldHeight() { return 3; }
1098 // The text field has a full height of 3 or 4 lines. 3 lines for the actual
1099 // field and an optional line for an error if it exists.
1100 int FieldDelegateGetHeight() override
{
1101 int height
= GetFieldHeight();
1102 if (FieldDelegateHasError())
1107 // Get the cursor X position in the surface coordinate.
1108 int GetCursorXPosition() { return m_cursor_position
- m_first_visibile_char
; }
1110 int GetContentLength() { return m_content
.length(); }
1112 void DrawContent(Surface
&surface
, bool is_selected
) {
1113 UpdateScrolling(surface
.GetWidth());
1115 surface
.MoveCursor(0, 0);
1116 const char *text
= m_content
.c_str() + m_first_visibile_char
;
1117 surface
.PutCString(text
, surface
.GetWidth());
1119 // Highlight the cursor.
1120 surface
.MoveCursor(GetCursorXPosition(), 0);
1122 surface
.AttributeOn(A_REVERSE
);
1123 if (m_cursor_position
== GetContentLength())
1124 // Cursor is past the last character. Highlight an empty space.
1125 surface
.PutChar(' ');
1127 surface
.PutChar(m_content
[m_cursor_position
]);
1129 surface
.AttributeOff(A_REVERSE
);
1132 void DrawField(Surface
&surface
, bool is_selected
) {
1133 surface
.TitledBox(m_label
.c_str());
1135 Rect content_bounds
= surface
.GetFrame();
1136 content_bounds
.Inset(1, 1);
1137 Surface content_surface
= surface
.SubSurface(content_bounds
);
1139 DrawContent(content_surface
, is_selected
);
1142 void DrawError(Surface
&surface
) {
1143 if (!FieldDelegateHasError())
1145 surface
.MoveCursor(0, 0);
1146 surface
.AttributeOn(COLOR_PAIR(RedOnBlack
));
1147 surface
.PutChar(ACS_DIAMOND
);
1148 surface
.PutChar(' ');
1149 surface
.PutCStringTruncated(1, GetError().c_str());
1150 surface
.AttributeOff(COLOR_PAIR(RedOnBlack
));
1153 void FieldDelegateDraw(Surface
&surface
, bool is_selected
) override
{
1154 Rect frame
= surface
.GetFrame();
1155 Rect field_bounds
, error_bounds
;
1156 frame
.HorizontalSplit(GetFieldHeight(), field_bounds
, error_bounds
);
1157 Surface field_surface
= surface
.SubSurface(field_bounds
);
1158 Surface error_surface
= surface
.SubSurface(error_bounds
);
1160 DrawField(field_surface
, is_selected
);
1161 DrawError(error_surface
);
1164 // Get the position of the last visible character.
1165 int GetLastVisibleCharPosition(int width
) {
1166 int position
= m_first_visibile_char
+ width
- 1;
1167 return std::min(position
, GetContentLength());
1170 void UpdateScrolling(int width
) {
1171 if (m_cursor_position
< m_first_visibile_char
) {
1172 m_first_visibile_char
= m_cursor_position
;
1176 if (m_cursor_position
> GetLastVisibleCharPosition(width
))
1177 m_first_visibile_char
= m_cursor_position
- (width
- 1);
1180 // The cursor is allowed to move one character past the string.
1181 // m_cursor_position is in range [0, GetContentLength()].
1182 void MoveCursorRight() {
1183 if (m_cursor_position
< GetContentLength())
1184 m_cursor_position
++;
1187 void MoveCursorLeft() {
1188 if (m_cursor_position
> 0)
1189 m_cursor_position
--;
1192 void MoveCursorToStart() { m_cursor_position
= 0; }
1194 void MoveCursorToEnd() { m_cursor_position
= GetContentLength(); }
1197 if (m_first_visibile_char
> 0)
1198 m_first_visibile_char
--;
1201 // Insert a character at the current cursor position and advance the cursor
1203 void InsertChar(char character
) {
1204 m_content
.insert(m_cursor_position
, 1, character
);
1205 m_cursor_position
++;
1209 // Remove the character before the cursor position, retreat the cursor
1210 // position, and scroll left.
1211 void RemovePreviousChar() {
1212 if (m_cursor_position
== 0)
1215 m_content
.erase(m_cursor_position
- 1, 1);
1216 m_cursor_position
--;
1221 // Remove the character after the cursor position.
1222 void RemoveNextChar() {
1223 if (m_cursor_position
== GetContentLength())
1226 m_content
.erase(m_cursor_position
, 1);
1230 // Clear characters from the current cursor position to the end.
1232 m_content
.erase(m_cursor_position
);
1238 m_cursor_position
= 0;
1242 // True if the key represents a char that can be inserted in the field
1243 // content, false otherwise.
1244 virtual bool IsAcceptableChar(int key
) {
1245 // The behavior of isprint is undefined when the value is not representable
1246 // as an unsigned char. So explicitly check for non-ascii key codes.
1249 return isprint(key
);
1252 HandleCharResult
FieldDelegateHandleChar(int key
) override
{
1253 if (IsAcceptableChar(key
)) {
1255 InsertChar((char)key
);
1262 MoveCursorToStart();
1278 RemovePreviousChar();
1294 return eKeyNotHandled
;
1297 bool FieldDelegateHasError() override
{ return !m_error
.empty(); }
1299 void FieldDelegateExitCallback() override
{
1300 if (!IsSpecified() && m_required
)
1301 SetError("This field is required!");
1304 bool IsSpecified() { return !m_content
.empty(); }
1306 void ClearError() { m_error
.clear(); }
1308 const std::string
&GetError() { return m_error
; }
1310 void SetError(const char *error
) { m_error
= error
; }
1312 const std::string
&GetText() { return m_content
; }
1314 void SetText(const char *text
) {
1315 if (text
== nullptr) {
1323 std::string m_label
;
1325 // The position of the top left corner character of the border.
1326 std::string m_content
;
1327 // The cursor position in the content string itself. Can be in the range
1328 // [0, GetContentLength()].
1329 int m_cursor_position
= 0;
1330 // The index of the first visible character in the content.
1331 int m_first_visibile_char
= 0;
1332 // Optional error message. If empty, field is considered to have no error.
1333 std::string m_error
;
1336 class IntegerFieldDelegate
: public TextFieldDelegate
{
1338 IntegerFieldDelegate(const char *label
, int content
, bool required
)
1339 : TextFieldDelegate(label
, std::to_string(content
).c_str(), required
) {}
1341 // Only accept digits.
1342 bool IsAcceptableChar(int key
) override
{ return isdigit(key
); }
1344 // Returns the integer content of the field.
1345 int GetInteger() { return std::stoi(m_content
); }
1348 class FileFieldDelegate
: public TextFieldDelegate
{
1350 FileFieldDelegate(const char *label
, const char *content
, bool need_to_exist
,
1352 : TextFieldDelegate(label
, content
, required
),
1353 m_need_to_exist(need_to_exist
) {}
1355 void FieldDelegateExitCallback() override
{
1356 TextFieldDelegate::FieldDelegateExitCallback();
1360 if (!m_need_to_exist
)
1363 FileSpec file
= GetResolvedFileSpec();
1364 if (!FileSystem::Instance().Exists(file
)) {
1365 SetError("File doesn't exist!");
1368 if (FileSystem::Instance().IsDirectory(file
)) {
1369 SetError("Not a file!");
1374 FileSpec
GetFileSpec() {
1375 FileSpec
file_spec(GetPath());
1379 FileSpec
GetResolvedFileSpec() {
1380 FileSpec
file_spec(GetPath());
1381 FileSystem::Instance().Resolve(file_spec
);
1385 const std::string
&GetPath() { return m_content
; }
1388 bool m_need_to_exist
;
1391 class DirectoryFieldDelegate
: public TextFieldDelegate
{
1393 DirectoryFieldDelegate(const char *label
, const char *content
,
1394 bool need_to_exist
, bool required
)
1395 : TextFieldDelegate(label
, content
, required
),
1396 m_need_to_exist(need_to_exist
) {}
1398 void FieldDelegateExitCallback() override
{
1399 TextFieldDelegate::FieldDelegateExitCallback();
1403 if (!m_need_to_exist
)
1406 FileSpec file
= GetResolvedFileSpec();
1407 if (!FileSystem::Instance().Exists(file
)) {
1408 SetError("Directory doesn't exist!");
1411 if (!FileSystem::Instance().IsDirectory(file
)) {
1412 SetError("Not a directory!");
1417 FileSpec
GetFileSpec() {
1418 FileSpec
file_spec(GetPath());
1422 FileSpec
GetResolvedFileSpec() {
1423 FileSpec
file_spec(GetPath());
1424 FileSystem::Instance().Resolve(file_spec
);
1428 const std::string
&GetPath() { return m_content
; }
1431 bool m_need_to_exist
;
1434 class ArchFieldDelegate
: public TextFieldDelegate
{
1436 ArchFieldDelegate(const char *label
, const char *content
, bool required
)
1437 : TextFieldDelegate(label
, content
, required
) {}
1439 void FieldDelegateExitCallback() override
{
1440 TextFieldDelegate::FieldDelegateExitCallback();
1444 if (!GetArchSpec().IsValid())
1445 SetError("Not a valid arch!");
1448 const std::string
&GetArchString() { return m_content
; }
1450 ArchSpec
GetArchSpec() { return ArchSpec(GetArchString()); }
1453 class BooleanFieldDelegate
: public FieldDelegate
{
1455 BooleanFieldDelegate(const char *label
, bool content
)
1456 : m_label(label
), m_content(content
) {}
1458 // Boolean fields are drawn as checkboxes.
1460 // [X] Label or [ ] Label
1462 // Boolean fields are have a single line.
1463 int FieldDelegateGetHeight() override
{ return 1; }
1465 void FieldDelegateDraw(Surface
&surface
, bool is_selected
) override
{
1466 surface
.MoveCursor(0, 0);
1467 surface
.PutChar('[');
1469 surface
.AttributeOn(A_REVERSE
);
1470 surface
.PutChar(m_content
? ACS_DIAMOND
: ' ');
1472 surface
.AttributeOff(A_REVERSE
);
1473 surface
.PutChar(']');
1474 surface
.PutChar(' ');
1475 surface
.PutCString(m_label
.c_str());
1478 void ToggleContent() { m_content
= !m_content
; }
1480 void SetContentToTrue() { m_content
= true; }
1482 void SetContentToFalse() { m_content
= false; }
1484 HandleCharResult
FieldDelegateHandleChar(int key
) override
{
1492 SetContentToFalse();
1503 return eKeyNotHandled
;
1506 // Returns the boolean content of the field.
1507 bool GetBoolean() { return m_content
; }
1510 std::string m_label
;
1514 class ChoicesFieldDelegate
: public FieldDelegate
{
1516 ChoicesFieldDelegate(const char *label
, int number_of_visible_choices
,
1517 std::vector
<std::string
> choices
)
1518 : m_label(label
), m_number_of_visible_choices(number_of_visible_choices
),
1519 m_choices(choices
) {}
1521 // Choices fields are drawn as titles boxses of a number of visible choices.
1522 // The rest of the choices become visible as the user scroll. The selected
1523 // choice is denoted by a diamond as the first character.
1525 // __[Label]___________
1529 // |__________________|
1531 // Choices field have two border characters plus the number of visible
1533 int FieldDelegateGetHeight() override
{
1534 return m_number_of_visible_choices
+ 2;
1537 int GetNumberOfChoices() { return m_choices
.size(); }
1539 // Get the index of the last visible choice.
1540 int GetLastVisibleChoice() {
1541 int index
= m_first_visibile_choice
+ m_number_of_visible_choices
;
1542 return std::min(index
, GetNumberOfChoices()) - 1;
1545 void DrawContent(Surface
&surface
, bool is_selected
) {
1546 int choices_to_draw
= GetLastVisibleChoice() - m_first_visibile_choice
+ 1;
1547 for (int i
= 0; i
< choices_to_draw
; i
++) {
1548 surface
.MoveCursor(0, i
);
1549 int current_choice
= m_first_visibile_choice
+ i
;
1550 const char *text
= m_choices
[current_choice
].c_str();
1551 bool highlight
= is_selected
&& current_choice
== m_choice
;
1553 surface
.AttributeOn(A_REVERSE
);
1554 surface
.PutChar(current_choice
== m_choice
? ACS_DIAMOND
: ' ');
1555 surface
.PutCString(text
);
1557 surface
.AttributeOff(A_REVERSE
);
1561 void FieldDelegateDraw(Surface
&surface
, bool is_selected
) override
{
1564 surface
.TitledBox(m_label
.c_str());
1566 Rect content_bounds
= surface
.GetFrame();
1567 content_bounds
.Inset(1, 1);
1568 Surface content_surface
= surface
.SubSurface(content_bounds
);
1570 DrawContent(content_surface
, is_selected
);
1573 void SelectPrevious() {
1579 if (m_choice
< GetNumberOfChoices() - 1)
1583 void UpdateScrolling() {
1584 if (m_choice
> GetLastVisibleChoice()) {
1585 m_first_visibile_choice
= m_choice
- (m_number_of_visible_choices
- 1);
1589 if (m_choice
< m_first_visibile_choice
)
1590 m_first_visibile_choice
= m_choice
;
1593 HandleCharResult
FieldDelegateHandleChar(int key
) override
{
1604 return eKeyNotHandled
;
1607 // Returns the content of the choice as a string.
1608 std::string
GetChoiceContent() { return m_choices
[m_choice
]; }
1610 // Returns the index of the choice.
1611 int GetChoice() { return m_choice
; }
1613 void SetChoice(llvm::StringRef choice
) {
1614 for (int i
= 0; i
< GetNumberOfChoices(); i
++) {
1615 if (choice
== m_choices
[i
]) {
1623 std::string m_label
;
1624 int m_number_of_visible_choices
;
1625 std::vector
<std::string
> m_choices
;
1626 // The index of the selected choice.
1628 // The index of the first visible choice in the field.
1629 int m_first_visibile_choice
= 0;
1632 class PlatformPluginFieldDelegate
: public ChoicesFieldDelegate
{
1634 PlatformPluginFieldDelegate(Debugger
&debugger
)
1635 : ChoicesFieldDelegate("Platform Plugin", 3, GetPossiblePluginNames()) {
1636 PlatformSP platform_sp
= debugger
.GetPlatformList().GetSelectedPlatform();
1638 SetChoice(platform_sp
->GetPluginName());
1641 std::vector
<std::string
> GetPossiblePluginNames() {
1642 std::vector
<std::string
> names
;
1644 for (llvm::StringRef name
=
1645 PluginManager::GetPlatformPluginNameAtIndex(i
++);
1646 !name
.empty(); name
= PluginManager::GetProcessPluginNameAtIndex(i
++))
1647 names
.push_back(name
.str());
1651 std::string
GetPluginName() {
1652 std::string plugin_name
= GetChoiceContent();
1657 class ProcessPluginFieldDelegate
: public ChoicesFieldDelegate
{
1659 ProcessPluginFieldDelegate()
1660 : ChoicesFieldDelegate("Process Plugin", 3, GetPossiblePluginNames()) {}
1662 std::vector
<std::string
> GetPossiblePluginNames() {
1663 std::vector
<std::string
> names
;
1664 names
.push_back("<default>");
1667 for (llvm::StringRef name
= PluginManager::GetProcessPluginNameAtIndex(i
++);
1668 !name
.empty(); name
= PluginManager::GetProcessPluginNameAtIndex(i
++))
1669 names
.push_back(name
.str());
1673 std::string
GetPluginName() {
1674 std::string plugin_name
= GetChoiceContent();
1675 if (plugin_name
== "<default>")
1681 class LazyBooleanFieldDelegate
: public ChoicesFieldDelegate
{
1683 LazyBooleanFieldDelegate(const char *label
, const char *calculate_label
)
1684 : ChoicesFieldDelegate(label
, 3, GetPossibleOptions(calculate_label
)) {}
1686 static constexpr const char *kNo
= "No";
1687 static constexpr const char *kYes
= "Yes";
1689 std::vector
<std::string
> GetPossibleOptions(const char *calculate_label
) {
1690 std::vector
<std::string
> options
;
1691 options
.push_back(calculate_label
);
1692 options
.push_back(kYes
);
1693 options
.push_back(kNo
);
1697 LazyBool
GetLazyBoolean() {
1698 std::string choice
= GetChoiceContent();
1701 else if (choice
== kYes
)
1702 return eLazyBoolYes
;
1704 return eLazyBoolCalculate
;
1708 template <class T
> class ListFieldDelegate
: public FieldDelegate
{
1710 ListFieldDelegate(const char *label
, T default_field
)
1711 : m_label(label
), m_default_field(default_field
),
1712 m_selection_type(SelectionType::NewButton
) {}
1714 // Signify which element is selected. If a field or a remove button is
1715 // selected, then m_selection_index signifies the particular field that
1716 // is selected or the field that the remove button belongs to.
1717 enum class SelectionType
{ Field
, RemoveButton
, NewButton
};
1719 // A List field is drawn as a titled box of a number of other fields of the
1720 // same type. Each field has a Remove button next to it that removes the
1721 // corresponding field. Finally, the last line contains a New button to add a
1724 // __[Label]___________
1725 // | Field 0 [Remove] |
1726 // | Field 1 [Remove] |
1727 // | Field 2 [Remove] |
1729 // |__________________|
1731 // List fields have two lines for border characters, 1 line for the New
1732 // button, and the total height of the available fields.
1733 int FieldDelegateGetHeight() override
{
1734 // 2 border characters.
1736 // Total height of the fields.
1737 for (int i
= 0; i
< GetNumberOfFields(); i
++) {
1738 height
+= m_fields
[i
].FieldDelegateGetHeight();
1740 // A line for the New button.
1745 ScrollContext
FieldDelegateGetScrollContext() override
{
1746 int height
= FieldDelegateGetHeight();
1747 if (m_selection_type
== SelectionType::NewButton
)
1748 return ScrollContext(height
- 2, height
- 1);
1750 FieldDelegate
&field
= m_fields
[m_selection_index
];
1751 ScrollContext context
= field
.FieldDelegateGetScrollContext();
1753 // Start at 1 because of the top border.
1755 for (int i
= 0; i
< m_selection_index
; i
++) {
1756 offset
+= m_fields
[i
].FieldDelegateGetHeight();
1758 context
.Offset(offset
);
1760 // If the scroll context is touching the top border, include it in the
1761 // context to show the label.
1762 if (context
.start
== 1)
1765 // If the scroll context is touching the new button, include it as well as
1766 // the bottom border in the context.
1767 if (context
.end
== height
- 3)
1773 void DrawRemoveButton(Surface
&surface
, int highlight
) {
1774 surface
.MoveCursor(1, surface
.GetHeight() / 2);
1776 surface
.AttributeOn(A_REVERSE
);
1777 surface
.PutCString("[Remove]");
1779 surface
.AttributeOff(A_REVERSE
);
1782 void DrawFields(Surface
&surface
, bool is_selected
) {
1784 int width
= surface
.GetWidth();
1785 for (int i
= 0; i
< GetNumberOfFields(); i
++) {
1786 int height
= m_fields
[i
].FieldDelegateGetHeight();
1787 Rect bounds
= Rect(Point(0, line
), Size(width
, height
));
1788 Rect field_bounds
, remove_button_bounds
;
1789 bounds
.VerticalSplit(bounds
.size
.width
- sizeof(" [Remove]"),
1790 field_bounds
, remove_button_bounds
);
1791 Surface field_surface
= surface
.SubSurface(field_bounds
);
1792 Surface remove_button_surface
= surface
.SubSurface(remove_button_bounds
);
1794 bool is_element_selected
= m_selection_index
== i
&& is_selected
;
1795 bool is_field_selected
=
1796 is_element_selected
&& m_selection_type
== SelectionType::Field
;
1797 bool is_remove_button_selected
=
1798 is_element_selected
&&
1799 m_selection_type
== SelectionType::RemoveButton
;
1800 m_fields
[i
].FieldDelegateDraw(field_surface
, is_field_selected
);
1801 DrawRemoveButton(remove_button_surface
, is_remove_button_selected
);
1807 void DrawNewButton(Surface
&surface
, bool is_selected
) {
1808 const char *button_text
= "[New]";
1809 int x
= (surface
.GetWidth() - sizeof(button_text
) - 1) / 2;
1810 surface
.MoveCursor(x
, 0);
1812 is_selected
&& m_selection_type
== SelectionType::NewButton
;
1814 surface
.AttributeOn(A_REVERSE
);
1815 surface
.PutCString(button_text
);
1817 surface
.AttributeOff(A_REVERSE
);
1820 void FieldDelegateDraw(Surface
&surface
, bool is_selected
) override
{
1821 surface
.TitledBox(m_label
.c_str());
1823 Rect content_bounds
= surface
.GetFrame();
1824 content_bounds
.Inset(1, 1);
1825 Rect fields_bounds
, new_button_bounds
;
1826 content_bounds
.HorizontalSplit(content_bounds
.size
.height
- 1,
1827 fields_bounds
, new_button_bounds
);
1828 Surface fields_surface
= surface
.SubSurface(fields_bounds
);
1829 Surface new_button_surface
= surface
.SubSurface(new_button_bounds
);
1831 DrawFields(fields_surface
, is_selected
);
1832 DrawNewButton(new_button_surface
, is_selected
);
1835 void AddNewField() {
1836 m_fields
.push_back(m_default_field
);
1837 m_selection_index
= GetNumberOfFields() - 1;
1838 m_selection_type
= SelectionType::Field
;
1839 FieldDelegate
&field
= m_fields
[m_selection_index
];
1840 field
.FieldDelegateSelectFirstElement();
1843 void RemoveField() {
1844 m_fields
.erase(m_fields
.begin() + m_selection_index
);
1845 if (m_selection_index
!= 0)
1846 m_selection_index
--;
1848 if (GetNumberOfFields() > 0) {
1849 m_selection_type
= SelectionType::Field
;
1850 FieldDelegate
&field
= m_fields
[m_selection_index
];
1851 field
.FieldDelegateSelectFirstElement();
1853 m_selection_type
= SelectionType::NewButton
;
1856 HandleCharResult
SelectNext(int key
) {
1857 if (m_selection_type
== SelectionType::NewButton
)
1858 return eKeyNotHandled
;
1860 if (m_selection_type
== SelectionType::RemoveButton
) {
1861 if (m_selection_index
== GetNumberOfFields() - 1) {
1862 m_selection_type
= SelectionType::NewButton
;
1865 m_selection_index
++;
1866 m_selection_type
= SelectionType::Field
;
1867 FieldDelegate
&next_field
= m_fields
[m_selection_index
];
1868 next_field
.FieldDelegateSelectFirstElement();
1872 FieldDelegate
&field
= m_fields
[m_selection_index
];
1873 if (!field
.FieldDelegateOnLastOrOnlyElement()) {
1874 return field
.FieldDelegateHandleChar(key
);
1877 field
.FieldDelegateExitCallback();
1879 m_selection_type
= SelectionType::RemoveButton
;
1883 HandleCharResult
SelectPrevious(int key
) {
1884 if (FieldDelegateOnFirstOrOnlyElement())
1885 return eKeyNotHandled
;
1887 if (m_selection_type
== SelectionType::RemoveButton
) {
1888 m_selection_type
= SelectionType::Field
;
1889 FieldDelegate
&field
= m_fields
[m_selection_index
];
1890 field
.FieldDelegateSelectLastElement();
1894 if (m_selection_type
== SelectionType::NewButton
) {
1895 m_selection_type
= SelectionType::RemoveButton
;
1896 m_selection_index
= GetNumberOfFields() - 1;
1900 FieldDelegate
&field
= m_fields
[m_selection_index
];
1901 if (!field
.FieldDelegateOnFirstOrOnlyElement()) {
1902 return field
.FieldDelegateHandleChar(key
);
1905 field
.FieldDelegateExitCallback();
1907 m_selection_type
= SelectionType::RemoveButton
;
1908 m_selection_index
--;
1912 // If the last element of the field is selected and it didn't handle the key.
1913 // Select the next field or new button if the selected field is the last one.
1914 HandleCharResult
SelectNextInList(int key
) {
1915 assert(m_selection_type
== SelectionType::Field
);
1917 FieldDelegate
&field
= m_fields
[m_selection_index
];
1918 if (field
.FieldDelegateHandleChar(key
) == eKeyHandled
)
1921 if (!field
.FieldDelegateOnLastOrOnlyElement())
1922 return eKeyNotHandled
;
1924 field
.FieldDelegateExitCallback();
1926 if (m_selection_index
== GetNumberOfFields() - 1) {
1927 m_selection_type
= SelectionType::NewButton
;
1931 m_selection_index
++;
1932 FieldDelegate
&next_field
= m_fields
[m_selection_index
];
1933 next_field
.FieldDelegateSelectFirstElement();
1937 HandleCharResult
FieldDelegateHandleChar(int key
) override
{
1942 switch (m_selection_type
) {
1943 case SelectionType::NewButton
:
1946 case SelectionType::RemoveButton
:
1949 case SelectionType::Field
:
1950 return SelectNextInList(key
);
1954 return SelectNext(key
);
1956 return SelectPrevious(key
);
1961 // If the key wasn't handled and one of the fields is selected, pass the key
1963 if (m_selection_type
== SelectionType::Field
) {
1964 return m_fields
[m_selection_index
].FieldDelegateHandleChar(key
);
1967 return eKeyNotHandled
;
1970 bool FieldDelegateOnLastOrOnlyElement() override
{
1971 if (m_selection_type
== SelectionType::NewButton
) {
1977 bool FieldDelegateOnFirstOrOnlyElement() override
{
1978 if (m_selection_type
== SelectionType::NewButton
&&
1979 GetNumberOfFields() == 0)
1982 if (m_selection_type
== SelectionType::Field
&& m_selection_index
== 0) {
1983 FieldDelegate
&field
= m_fields
[m_selection_index
];
1984 return field
.FieldDelegateOnFirstOrOnlyElement();
1990 void FieldDelegateSelectFirstElement() override
{
1991 if (GetNumberOfFields() == 0) {
1992 m_selection_type
= SelectionType::NewButton
;
1996 m_selection_type
= SelectionType::Field
;
1997 m_selection_index
= 0;
2000 void FieldDelegateSelectLastElement() override
{
2001 m_selection_type
= SelectionType::NewButton
;
2004 int GetNumberOfFields() { return m_fields
.size(); }
2006 // Returns the form delegate at the current index.
2007 T
&GetField(int index
) { return m_fields
[index
]; }
2010 std::string m_label
;
2011 // The default field delegate instance from which new field delegates will be
2012 // created though a copy.
2014 std::vector
<T
> m_fields
;
2015 int m_selection_index
= 0;
2016 // See SelectionType class enum.
2017 SelectionType m_selection_type
;
2020 class ArgumentsFieldDelegate
: public ListFieldDelegate
<TextFieldDelegate
> {
2022 ArgumentsFieldDelegate()
2023 : ListFieldDelegate("Arguments",
2024 TextFieldDelegate("Argument", "", false)) {}
2026 Args
GetArguments() {
2028 for (int i
= 0; i
< GetNumberOfFields(); i
++) {
2029 arguments
.AppendArgument(GetField(i
).GetText());
2034 void AddArguments(const Args
&arguments
) {
2035 for (size_t i
= 0; i
< arguments
.GetArgumentCount(); i
++) {
2037 TextFieldDelegate
&field
= GetField(GetNumberOfFields() - 1);
2038 field
.SetText(arguments
.GetArgumentAtIndex(i
));
2043 template <class KeyFieldDelegateType
, class ValueFieldDelegateType
>
2044 class MappingFieldDelegate
: public FieldDelegate
{
2046 MappingFieldDelegate(KeyFieldDelegateType key_field
,
2047 ValueFieldDelegateType value_field
)
2048 : m_key_field(key_field
), m_value_field(value_field
),
2049 m_selection_type(SelectionType::Key
) {}
2051 // Signify which element is selected. The key field or its value field.
2052 enum class SelectionType
{ Key
, Value
};
2054 // A mapping field is drawn as two text fields with a right arrow in between.
2055 // The first field stores the key of the mapping and the second stores the
2056 // value if the mapping.
2058 // __[Key]_____________ __[Value]___________
2060 // |__________________| |__________________|
2061 // - Error message if it exists.
2063 // The mapping field has a height that is equal to the maximum height between
2064 // the key and value fields.
2065 int FieldDelegateGetHeight() override
{
2066 return std::max(m_key_field
.FieldDelegateGetHeight(),
2067 m_value_field
.FieldDelegateGetHeight());
2070 void DrawArrow(Surface
&surface
) {
2071 surface
.MoveCursor(0, 1);
2072 surface
.PutChar(ACS_RARROW
);
2075 void FieldDelegateDraw(Surface
&surface
, bool is_selected
) override
{
2076 Rect bounds
= surface
.GetFrame();
2077 Rect key_field_bounds
, arrow_and_value_field_bounds
;
2078 bounds
.VerticalSplit(bounds
.size
.width
/ 2, key_field_bounds
,
2079 arrow_and_value_field_bounds
);
2080 Rect arrow_bounds
, value_field_bounds
;
2081 arrow_and_value_field_bounds
.VerticalSplit(1, arrow_bounds
,
2082 value_field_bounds
);
2084 Surface key_field_surface
= surface
.SubSurface(key_field_bounds
);
2085 Surface arrow_surface
= surface
.SubSurface(arrow_bounds
);
2086 Surface value_field_surface
= surface
.SubSurface(value_field_bounds
);
2088 bool key_is_selected
=
2089 m_selection_type
== SelectionType::Key
&& is_selected
;
2090 m_key_field
.FieldDelegateDraw(key_field_surface
, key_is_selected
);
2091 DrawArrow(arrow_surface
);
2092 bool value_is_selected
=
2093 m_selection_type
== SelectionType::Value
&& is_selected
;
2094 m_value_field
.FieldDelegateDraw(value_field_surface
, value_is_selected
);
2097 HandleCharResult
SelectNext(int key
) {
2098 if (FieldDelegateOnLastOrOnlyElement())
2099 return eKeyNotHandled
;
2101 if (!m_key_field
.FieldDelegateOnLastOrOnlyElement()) {
2102 return m_key_field
.FieldDelegateHandleChar(key
);
2105 m_key_field
.FieldDelegateExitCallback();
2106 m_selection_type
= SelectionType::Value
;
2107 m_value_field
.FieldDelegateSelectFirstElement();
2111 HandleCharResult
SelectPrevious(int key
) {
2112 if (FieldDelegateOnFirstOrOnlyElement())
2113 return eKeyNotHandled
;
2115 if (!m_value_field
.FieldDelegateOnFirstOrOnlyElement()) {
2116 return m_value_field
.FieldDelegateHandleChar(key
);
2119 m_value_field
.FieldDelegateExitCallback();
2120 m_selection_type
= SelectionType::Key
;
2121 m_key_field
.FieldDelegateSelectLastElement();
2125 // If the value field is selected, pass the key to it. If the key field is
2126 // selected, its last element is selected, and it didn't handle the key, then
2127 // select its corresponding value field.
2128 HandleCharResult
SelectNextField(int key
) {
2129 if (m_selection_type
== SelectionType::Value
) {
2130 return m_value_field
.FieldDelegateHandleChar(key
);
2133 if (m_key_field
.FieldDelegateHandleChar(key
) == eKeyHandled
)
2136 if (!m_key_field
.FieldDelegateOnLastOrOnlyElement())
2137 return eKeyNotHandled
;
2139 m_key_field
.FieldDelegateExitCallback();
2140 m_selection_type
= SelectionType::Value
;
2141 m_value_field
.FieldDelegateSelectFirstElement();
2145 HandleCharResult
FieldDelegateHandleChar(int key
) override
{
2148 return SelectNextField(key
);
2150 return SelectNext(key
);
2152 return SelectPrevious(key
);
2157 // If the key wasn't handled, pass the key to the selected field.
2158 if (m_selection_type
== SelectionType::Key
)
2159 return m_key_field
.FieldDelegateHandleChar(key
);
2161 return m_value_field
.FieldDelegateHandleChar(key
);
2163 return eKeyNotHandled
;
2166 bool FieldDelegateOnFirstOrOnlyElement() override
{
2167 return m_selection_type
== SelectionType::Key
;
2170 bool FieldDelegateOnLastOrOnlyElement() override
{
2171 return m_selection_type
== SelectionType::Value
;
2174 void FieldDelegateSelectFirstElement() override
{
2175 m_selection_type
= SelectionType::Key
;
2178 void FieldDelegateSelectLastElement() override
{
2179 m_selection_type
= SelectionType::Value
;
2182 bool FieldDelegateHasError() override
{
2183 return m_key_field
.FieldDelegateHasError() ||
2184 m_value_field
.FieldDelegateHasError();
2187 KeyFieldDelegateType
&GetKeyField() { return m_key_field
; }
2189 ValueFieldDelegateType
&GetValueField() { return m_value_field
; }
2192 KeyFieldDelegateType m_key_field
;
2193 ValueFieldDelegateType m_value_field
;
2194 // See SelectionType class enum.
2195 SelectionType m_selection_type
;
2198 class EnvironmentVariableNameFieldDelegate
: public TextFieldDelegate
{
2200 EnvironmentVariableNameFieldDelegate(const char *content
)
2201 : TextFieldDelegate("Name", content
, true) {}
2203 // Environment variable names can't contain an equal sign.
2204 bool IsAcceptableChar(int key
) override
{
2205 return TextFieldDelegate::IsAcceptableChar(key
) && key
!= '=';
2208 const std::string
&GetName() { return m_content
; }
2211 class EnvironmentVariableFieldDelegate
2212 : public MappingFieldDelegate
<EnvironmentVariableNameFieldDelegate
,
2213 TextFieldDelegate
> {
2215 EnvironmentVariableFieldDelegate()
2216 : MappingFieldDelegate(
2217 EnvironmentVariableNameFieldDelegate(""),
2218 TextFieldDelegate("Value", "", /*required=*/false)) {}
2220 const std::string
&GetName() { return GetKeyField().GetName(); }
2222 const std::string
&GetValue() { return GetValueField().GetText(); }
2224 void SetName(const char *name
) { return GetKeyField().SetText(name
); }
2226 void SetValue(const char *value
) { return GetValueField().SetText(value
); }
2229 class EnvironmentVariableListFieldDelegate
2230 : public ListFieldDelegate
<EnvironmentVariableFieldDelegate
> {
2232 EnvironmentVariableListFieldDelegate(const char *label
)
2233 : ListFieldDelegate(label
, EnvironmentVariableFieldDelegate()) {}
2235 Environment
GetEnvironment() {
2236 Environment environment
;
2237 for (int i
= 0; i
< GetNumberOfFields(); i
++) {
2239 std::make_pair(GetField(i
).GetName(), GetField(i
).GetValue()));
2244 void AddEnvironmentVariables(const Environment
&environment
) {
2245 for (auto &variable
: environment
) {
2247 EnvironmentVariableFieldDelegate
&field
=
2248 GetField(GetNumberOfFields() - 1);
2249 field
.SetName(variable
.getKey().str().c_str());
2250 field
.SetValue(variable
.getValue().c_str());
2257 FormAction(const char *label
, std::function
<void(Window
&)> action
)
2258 : m_action(action
) {
2263 // Draw a centered [Label].
2264 void Draw(Surface
&surface
, bool is_selected
) {
2265 int x
= (surface
.GetWidth() - m_label
.length()) / 2;
2266 surface
.MoveCursor(x
, 0);
2268 surface
.AttributeOn(A_REVERSE
);
2269 surface
.PutChar('[');
2270 surface
.PutCString(m_label
.c_str());
2271 surface
.PutChar(']');
2273 surface
.AttributeOff(A_REVERSE
);
2276 void Execute(Window
&window
) { m_action(window
); }
2278 const std::string
&GetLabel() { return m_label
; }
2281 std::string m_label
;
2282 std::function
<void(Window
&)> m_action
;
2285 class FormDelegate
{
2287 FormDelegate() = default;
2289 virtual ~FormDelegate() = default;
2291 virtual std::string
GetName() = 0;
2293 virtual void UpdateFieldsVisibility() {}
2295 FieldDelegate
*GetField(uint32_t field_index
) {
2296 if (field_index
< m_fields
.size())
2297 return m_fields
[field_index
].get();
2301 FormAction
&GetAction(int action_index
) { return m_actions
[action_index
]; }
2303 int GetNumberOfFields() { return m_fields
.size(); }
2305 int GetNumberOfActions() { return m_actions
.size(); }
2307 bool HasError() { return !m_error
.empty(); }
2309 void ClearError() { m_error
.clear(); }
2311 const std::string
&GetError() { return m_error
; }
2313 void SetError(const char *error
) { m_error
= error
; }
2315 // If all fields are valid, true is returned. Otherwise, an error message is
2316 // set and false is returned. This method is usually called at the start of an
2317 // action that requires valid fields.
2318 bool CheckFieldsValidity() {
2319 for (int i
= 0; i
< GetNumberOfFields(); i
++) {
2320 GetField(i
)->FieldDelegateExitCallback();
2321 if (GetField(i
)->FieldDelegateHasError()) {
2322 SetError("Some fields are invalid!");
2329 // Factory methods to create and add fields of specific types.
2331 TextFieldDelegate
*AddTextField(const char *label
, const char *content
,
2333 TextFieldDelegate
*delegate
=
2334 new TextFieldDelegate(label
, content
, required
);
2335 m_fields
.push_back(FieldDelegateUP(delegate
));
2339 FileFieldDelegate
*AddFileField(const char *label
, const char *content
,
2340 bool need_to_exist
, bool required
) {
2341 FileFieldDelegate
*delegate
=
2342 new FileFieldDelegate(label
, content
, need_to_exist
, required
);
2343 m_fields
.push_back(FieldDelegateUP(delegate
));
2347 DirectoryFieldDelegate
*AddDirectoryField(const char *label
,
2348 const char *content
,
2349 bool need_to_exist
, bool required
) {
2350 DirectoryFieldDelegate
*delegate
=
2351 new DirectoryFieldDelegate(label
, content
, need_to_exist
, required
);
2352 m_fields
.push_back(FieldDelegateUP(delegate
));
2356 ArchFieldDelegate
*AddArchField(const char *label
, const char *content
,
2358 ArchFieldDelegate
*delegate
=
2359 new ArchFieldDelegate(label
, content
, required
);
2360 m_fields
.push_back(FieldDelegateUP(delegate
));
2364 IntegerFieldDelegate
*AddIntegerField(const char *label
, int content
,
2366 IntegerFieldDelegate
*delegate
=
2367 new IntegerFieldDelegate(label
, content
, required
);
2368 m_fields
.push_back(FieldDelegateUP(delegate
));
2372 BooleanFieldDelegate
*AddBooleanField(const char *label
, bool content
) {
2373 BooleanFieldDelegate
*delegate
= new BooleanFieldDelegate(label
, content
);
2374 m_fields
.push_back(FieldDelegateUP(delegate
));
2378 LazyBooleanFieldDelegate
*AddLazyBooleanField(const char *label
,
2379 const char *calculate_label
) {
2380 LazyBooleanFieldDelegate
*delegate
=
2381 new LazyBooleanFieldDelegate(label
, calculate_label
);
2382 m_fields
.push_back(FieldDelegateUP(delegate
));
2386 ChoicesFieldDelegate
*AddChoicesField(const char *label
, int height
,
2387 std::vector
<std::string
> choices
) {
2388 ChoicesFieldDelegate
*delegate
=
2389 new ChoicesFieldDelegate(label
, height
, choices
);
2390 m_fields
.push_back(FieldDelegateUP(delegate
));
2394 PlatformPluginFieldDelegate
*AddPlatformPluginField(Debugger
&debugger
) {
2395 PlatformPluginFieldDelegate
*delegate
=
2396 new PlatformPluginFieldDelegate(debugger
);
2397 m_fields
.push_back(FieldDelegateUP(delegate
));
2401 ProcessPluginFieldDelegate
*AddProcessPluginField() {
2402 ProcessPluginFieldDelegate
*delegate
= new ProcessPluginFieldDelegate();
2403 m_fields
.push_back(FieldDelegateUP(delegate
));
2408 ListFieldDelegate
<T
> *AddListField(const char *label
, T default_field
) {
2409 ListFieldDelegate
<T
> *delegate
=
2410 new ListFieldDelegate
<T
>(label
, default_field
);
2411 m_fields
.push_back(FieldDelegateUP(delegate
));
2415 ArgumentsFieldDelegate
*AddArgumentsField() {
2416 ArgumentsFieldDelegate
*delegate
= new ArgumentsFieldDelegate();
2417 m_fields
.push_back(FieldDelegateUP(delegate
));
2421 template <class K
, class V
>
2422 MappingFieldDelegate
<K
, V
> *AddMappingField(K key_field
, V value_field
) {
2423 MappingFieldDelegate
<K
, V
> *delegate
=
2424 new MappingFieldDelegate
<K
, V
>(key_field
, value_field
);
2425 m_fields
.push_back(FieldDelegateUP(delegate
));
2429 EnvironmentVariableNameFieldDelegate
*
2430 AddEnvironmentVariableNameField(const char *content
) {
2431 EnvironmentVariableNameFieldDelegate
*delegate
=
2432 new EnvironmentVariableNameFieldDelegate(content
);
2433 m_fields
.push_back(FieldDelegateUP(delegate
));
2437 EnvironmentVariableFieldDelegate
*AddEnvironmentVariableField() {
2438 EnvironmentVariableFieldDelegate
*delegate
=
2439 new EnvironmentVariableFieldDelegate();
2440 m_fields
.push_back(FieldDelegateUP(delegate
));
2444 EnvironmentVariableListFieldDelegate
*
2445 AddEnvironmentVariableListField(const char *label
) {
2446 EnvironmentVariableListFieldDelegate
*delegate
=
2447 new EnvironmentVariableListFieldDelegate(label
);
2448 m_fields
.push_back(FieldDelegateUP(delegate
));
2452 // Factory methods for adding actions.
2454 void AddAction(const char *label
, std::function
<void(Window
&)> action
) {
2455 m_actions
.push_back(FormAction(label
, action
));
2459 std::vector
<FieldDelegateUP
> m_fields
;
2460 std::vector
<FormAction
> m_actions
;
2461 // Optional error message. If empty, form is considered to have no error.
2462 std::string m_error
;
2465 typedef std::shared_ptr
<FormDelegate
> FormDelegateSP
;
2467 class FormWindowDelegate
: public WindowDelegate
{
2469 FormWindowDelegate(FormDelegateSP
&delegate_sp
) : m_delegate_sp(delegate_sp
) {
2470 assert(m_delegate_sp
->GetNumberOfActions() > 0);
2471 if (m_delegate_sp
->GetNumberOfFields() > 0)
2472 m_selection_type
= SelectionType::Field
;
2474 m_selection_type
= SelectionType::Action
;
2477 // Signify which element is selected. If a field or an action is selected,
2478 // then m_selection_index signifies the particular field or action that is
2480 enum class SelectionType
{ Field
, Action
};
2482 // A form window is padded by one character from all sides. First, if an error
2483 // message exists, it is drawn followed by a separator. Then one or more
2484 // fields are drawn. Finally, all available actions are drawn on a single
2487 // ___<Form Name>_________________________________________________
2489 // | - Error message if it exists. |
2490 // |-------------------------------------------------------------|
2491 // | Form elements here. |
2492 // | Form actions here. |
2494 // |______________________________________[Press Esc to cancel]__|
2497 // One line for the error and another for the horizontal line.
2498 int GetErrorHeight() {
2499 if (m_delegate_sp
->HasError())
2504 // Actions span a single line.
2505 int GetActionsHeight() {
2506 if (m_delegate_sp
->GetNumberOfActions() > 0)
2511 // Get the total number of needed lines to draw the contents.
2512 int GetContentHeight() {
2514 height
+= GetErrorHeight();
2515 for (int i
= 0; i
< m_delegate_sp
->GetNumberOfFields(); i
++) {
2516 if (!m_delegate_sp
->GetField(i
)->FieldDelegateIsVisible())
2518 height
+= m_delegate_sp
->GetField(i
)->FieldDelegateGetHeight();
2520 height
+= GetActionsHeight();
2524 ScrollContext
GetScrollContext() {
2525 if (m_selection_type
== SelectionType::Action
)
2526 return ScrollContext(GetContentHeight() - 1);
2528 FieldDelegate
*field
= m_delegate_sp
->GetField(m_selection_index
);
2529 ScrollContext context
= field
->FieldDelegateGetScrollContext();
2531 int offset
= GetErrorHeight();
2532 for (int i
= 0; i
< m_selection_index
; i
++) {
2533 if (!m_delegate_sp
->GetField(i
)->FieldDelegateIsVisible())
2535 offset
+= m_delegate_sp
->GetField(i
)->FieldDelegateGetHeight();
2537 context
.Offset(offset
);
2539 // If the context is touching the error, include the error in the context as
2541 if (context
.start
== GetErrorHeight())
2547 void UpdateScrolling(Surface
&surface
) {
2548 ScrollContext context
= GetScrollContext();
2549 int content_height
= GetContentHeight();
2550 int surface_height
= surface
.GetHeight();
2551 int visible_height
= std::min(content_height
, surface_height
);
2552 int last_visible_line
= m_first_visible_line
+ visible_height
- 1;
2554 // If the last visible line is bigger than the content, then it is invalid
2555 // and needs to be set to the last line in the content. This can happen when
2556 // a field has shrunk in height.
2557 if (last_visible_line
> content_height
- 1) {
2558 m_first_visible_line
= content_height
- visible_height
;
2561 if (context
.start
< m_first_visible_line
) {
2562 m_first_visible_line
= context
.start
;
2566 if (context
.end
> last_visible_line
) {
2567 m_first_visible_line
= context
.end
- visible_height
+ 1;
2571 void DrawError(Surface
&surface
) {
2572 if (!m_delegate_sp
->HasError())
2574 surface
.MoveCursor(0, 0);
2575 surface
.AttributeOn(COLOR_PAIR(RedOnBlack
));
2576 surface
.PutChar(ACS_DIAMOND
);
2577 surface
.PutChar(' ');
2578 surface
.PutCStringTruncated(1, m_delegate_sp
->GetError().c_str());
2579 surface
.AttributeOff(COLOR_PAIR(RedOnBlack
));
2581 surface
.MoveCursor(0, 1);
2582 surface
.HorizontalLine(surface
.GetWidth());
2585 void DrawFields(Surface
&surface
) {
2587 int width
= surface
.GetWidth();
2588 bool a_field_is_selected
= m_selection_type
== SelectionType::Field
;
2589 for (int i
= 0; i
< m_delegate_sp
->GetNumberOfFields(); i
++) {
2590 FieldDelegate
*field
= m_delegate_sp
->GetField(i
);
2591 if (!field
->FieldDelegateIsVisible())
2593 bool is_field_selected
= a_field_is_selected
&& m_selection_index
== i
;
2594 int height
= field
->FieldDelegateGetHeight();
2595 Rect bounds
= Rect(Point(0, line
), Size(width
, height
));
2596 Surface field_surface
= surface
.SubSurface(bounds
);
2597 field
->FieldDelegateDraw(field_surface
, is_field_selected
);
2602 void DrawActions(Surface
&surface
) {
2603 int number_of_actions
= m_delegate_sp
->GetNumberOfActions();
2604 int width
= surface
.GetWidth() / number_of_actions
;
2605 bool an_action_is_selected
= m_selection_type
== SelectionType::Action
;
2607 for (int i
= 0; i
< number_of_actions
; i
++) {
2608 bool is_action_selected
= an_action_is_selected
&& m_selection_index
== i
;
2609 FormAction
&action
= m_delegate_sp
->GetAction(i
);
2610 Rect bounds
= Rect(Point(x
, 0), Size(width
, 1));
2611 Surface action_surface
= surface
.SubSurface(bounds
);
2612 action
.Draw(action_surface
, is_action_selected
);
2617 void DrawElements(Surface
&surface
) {
2618 Rect frame
= surface
.GetFrame();
2619 Rect fields_bounds
, actions_bounds
;
2620 frame
.HorizontalSplit(surface
.GetHeight() - GetActionsHeight(),
2621 fields_bounds
, actions_bounds
);
2622 Surface fields_surface
= surface
.SubSurface(fields_bounds
);
2623 Surface actions_surface
= surface
.SubSurface(actions_bounds
);
2625 DrawFields(fields_surface
);
2626 DrawActions(actions_surface
);
2629 // Contents are first drawn on a pad. Then a subset of that pad is copied to
2630 // the derived window starting at the first visible line. This essentially
2631 // provides scrolling functionality.
2632 void DrawContent(Surface
&surface
) {
2633 UpdateScrolling(surface
);
2635 int width
= surface
.GetWidth();
2636 int height
= GetContentHeight();
2637 Pad pad
= Pad(Size(width
, height
));
2639 Rect frame
= pad
.GetFrame();
2640 Rect error_bounds
, elements_bounds
;
2641 frame
.HorizontalSplit(GetErrorHeight(), error_bounds
, elements_bounds
);
2642 Surface error_surface
= pad
.SubSurface(error_bounds
);
2643 Surface elements_surface
= pad
.SubSurface(elements_bounds
);
2645 DrawError(error_surface
);
2646 DrawElements(elements_surface
);
2648 int copy_height
= std::min(surface
.GetHeight(), pad
.GetHeight());
2649 pad
.CopyToSurface(surface
, Point(0, m_first_visible_line
), Point(),
2650 Size(width
, copy_height
));
2653 void DrawSubmitHint(Surface
&surface
, bool is_active
) {
2654 surface
.MoveCursor(2, surface
.GetHeight() - 1);
2656 surface
.AttributeOn(A_BOLD
| COLOR_PAIR(BlackOnWhite
));
2657 surface
.Printf("[Press Alt+Enter to %s]",
2658 m_delegate_sp
->GetAction(0).GetLabel().c_str());
2660 surface
.AttributeOff(A_BOLD
| COLOR_PAIR(BlackOnWhite
));
2663 bool WindowDelegateDraw(Window
&window
, bool force
) override
{
2664 m_delegate_sp
->UpdateFieldsVisibility();
2668 window
.DrawTitleBox(m_delegate_sp
->GetName().c_str(),
2669 "Press Esc to Cancel");
2670 DrawSubmitHint(window
, window
.IsActive());
2672 Rect content_bounds
= window
.GetFrame();
2673 content_bounds
.Inset(2, 2);
2674 Surface content_surface
= window
.SubSurface(content_bounds
);
2676 DrawContent(content_surface
);
2680 void SkipNextHiddenFields() {
2682 if (m_delegate_sp
->GetField(m_selection_index
)->FieldDelegateIsVisible())
2685 if (m_selection_index
== m_delegate_sp
->GetNumberOfFields() - 1) {
2686 m_selection_type
= SelectionType::Action
;
2687 m_selection_index
= 0;
2691 m_selection_index
++;
2695 HandleCharResult
SelectNext(int key
) {
2696 if (m_selection_type
== SelectionType::Action
) {
2697 if (m_selection_index
< m_delegate_sp
->GetNumberOfActions() - 1) {
2698 m_selection_index
++;
2702 m_selection_index
= 0;
2703 m_selection_type
= SelectionType::Field
;
2704 SkipNextHiddenFields();
2705 if (m_selection_type
== SelectionType::Field
) {
2706 FieldDelegate
*next_field
= m_delegate_sp
->GetField(m_selection_index
);
2707 next_field
->FieldDelegateSelectFirstElement();
2712 FieldDelegate
*field
= m_delegate_sp
->GetField(m_selection_index
);
2713 if (!field
->FieldDelegateOnLastOrOnlyElement()) {
2714 return field
->FieldDelegateHandleChar(key
);
2717 field
->FieldDelegateExitCallback();
2719 if (m_selection_index
== m_delegate_sp
->GetNumberOfFields() - 1) {
2720 m_selection_type
= SelectionType::Action
;
2721 m_selection_index
= 0;
2725 m_selection_index
++;
2726 SkipNextHiddenFields();
2728 if (m_selection_type
== SelectionType::Field
) {
2729 FieldDelegate
*next_field
= m_delegate_sp
->GetField(m_selection_index
);
2730 next_field
->FieldDelegateSelectFirstElement();
2736 void SkipPreviousHiddenFields() {
2738 if (m_delegate_sp
->GetField(m_selection_index
)->FieldDelegateIsVisible())
2741 if (m_selection_index
== 0) {
2742 m_selection_type
= SelectionType::Action
;
2743 m_selection_index
= 0;
2747 m_selection_index
--;
2751 HandleCharResult
SelectPrevious(int key
) {
2752 if (m_selection_type
== SelectionType::Action
) {
2753 if (m_selection_index
> 0) {
2754 m_selection_index
--;
2757 m_selection_index
= m_delegate_sp
->GetNumberOfFields() - 1;
2758 m_selection_type
= SelectionType::Field
;
2759 SkipPreviousHiddenFields();
2760 if (m_selection_type
== SelectionType::Field
) {
2761 FieldDelegate
*previous_field
=
2762 m_delegate_sp
->GetField(m_selection_index
);
2763 previous_field
->FieldDelegateSelectLastElement();
2768 FieldDelegate
*field
= m_delegate_sp
->GetField(m_selection_index
);
2769 if (!field
->FieldDelegateOnFirstOrOnlyElement()) {
2770 return field
->FieldDelegateHandleChar(key
);
2773 field
->FieldDelegateExitCallback();
2775 if (m_selection_index
== 0) {
2776 m_selection_type
= SelectionType::Action
;
2777 m_selection_index
= m_delegate_sp
->GetNumberOfActions() - 1;
2781 m_selection_index
--;
2782 SkipPreviousHiddenFields();
2784 if (m_selection_type
== SelectionType::Field
) {
2785 FieldDelegate
*previous_field
=
2786 m_delegate_sp
->GetField(m_selection_index
);
2787 previous_field
->FieldDelegateSelectLastElement();
2793 void ExecuteAction(Window
&window
, int index
) {
2794 FormAction
&action
= m_delegate_sp
->GetAction(index
);
2795 action
.Execute(window
);
2796 if (m_delegate_sp
->HasError()) {
2797 m_first_visible_line
= 0;
2798 m_selection_index
= 0;
2799 m_selection_type
= SelectionType::Field
;
2803 // Always return eKeyHandled to absorb all events since forms are always
2804 // added as pop-ups that should take full control until canceled or submitted.
2805 HandleCharResult
WindowDelegateHandleChar(Window
&window
, int key
) override
{
2810 if (m_selection_type
== SelectionType::Action
) {
2811 ExecuteAction(window
, m_selection_index
);
2816 ExecuteAction(window
, 0);
2822 SelectPrevious(key
);
2825 window
.GetParent()->RemoveSubWindow(&window
);
2831 // If the key wasn't handled and one of the fields is selected, pass the key
2833 if (m_selection_type
== SelectionType::Field
) {
2834 FieldDelegate
*field
= m_delegate_sp
->GetField(m_selection_index
);
2835 if (field
->FieldDelegateHandleChar(key
) == eKeyHandled
)
2839 // If the key wasn't handled by the possibly selected field, handle some
2840 // extra keys for navigation.
2846 SelectPrevious(key
);
2856 FormDelegateSP m_delegate_sp
;
2857 // The index of the currently selected SelectionType.
2858 int m_selection_index
= 0;
2859 // See SelectionType class enum.
2860 SelectionType m_selection_type
;
2861 // The first visible line from the pad.
2862 int m_first_visible_line
= 0;
2865 ///////////////////////////
2866 // Form Delegate Instances
2867 ///////////////////////////
2869 class DetachOrKillProcessFormDelegate
: public FormDelegate
{
2871 DetachOrKillProcessFormDelegate(Process
*process
) : m_process(process
) {
2872 SetError("There is a running process, either detach or kill it.");
2874 m_keep_stopped_field
=
2875 AddBooleanField("Keep process stopped when detaching.", false);
2877 AddAction("Detach", [this](Window
&window
) { Detach(window
); });
2878 AddAction("Kill", [this](Window
&window
) { Kill(window
); });
2881 std::string
GetName() override
{ return "Detach/Kill Process"; }
2883 void Kill(Window
&window
) {
2884 Status
destroy_status(m_process
->Destroy(false));
2885 if (destroy_status
.Fail()) {
2886 SetError("Failed to kill process.");
2889 window
.GetParent()->RemoveSubWindow(&window
);
2892 void Detach(Window
&window
) {
2893 Status
detach_status(m_process
->Detach(m_keep_stopped_field
->GetBoolean()));
2894 if (detach_status
.Fail()) {
2895 SetError("Failed to detach from process.");
2898 window
.GetParent()->RemoveSubWindow(&window
);
2903 BooleanFieldDelegate
*m_keep_stopped_field
;
2906 class ProcessAttachFormDelegate
: public FormDelegate
{
2908 ProcessAttachFormDelegate(Debugger
&debugger
, WindowSP main_window_sp
)
2909 : m_debugger(debugger
), m_main_window_sp(main_window_sp
) {
2910 std::vector
<std::string
> types
;
2911 types
.push_back(std::string("Name"));
2912 types
.push_back(std::string("PID"));
2913 m_type_field
= AddChoicesField("Attach By", 2, types
);
2914 m_pid_field
= AddIntegerField("PID", 0, true);
2916 AddTextField("Process Name", GetDefaultProcessName().c_str(), true);
2917 m_continue_field
= AddBooleanField("Continue once attached.", false);
2918 m_wait_for_field
= AddBooleanField("Wait for process to launch.", false);
2919 m_include_existing_field
=
2920 AddBooleanField("Include existing processes.", false);
2921 m_show_advanced_field
= AddBooleanField("Show advanced settings.", false);
2922 m_plugin_field
= AddProcessPluginField();
2924 AddAction("Attach", [this](Window
&window
) { Attach(window
); });
2927 std::string
GetName() override
{ return "Attach Process"; }
2929 void UpdateFieldsVisibility() override
{
2930 if (m_type_field
->GetChoiceContent() == "Name") {
2931 m_pid_field
->FieldDelegateHide();
2932 m_name_field
->FieldDelegateShow();
2933 m_wait_for_field
->FieldDelegateShow();
2934 if (m_wait_for_field
->GetBoolean())
2935 m_include_existing_field
->FieldDelegateShow();
2937 m_include_existing_field
->FieldDelegateHide();
2939 m_pid_field
->FieldDelegateShow();
2940 m_name_field
->FieldDelegateHide();
2941 m_wait_for_field
->FieldDelegateHide();
2942 m_include_existing_field
->FieldDelegateHide();
2944 if (m_show_advanced_field
->GetBoolean())
2945 m_plugin_field
->FieldDelegateShow();
2947 m_plugin_field
->FieldDelegateHide();
2950 // Get the basename of the target's main executable if available, empty string
2952 std::string
GetDefaultProcessName() {
2953 Target
*target
= m_debugger
.GetSelectedTarget().get();
2954 if (target
== nullptr)
2957 ModuleSP module_sp
= target
->GetExecutableModule();
2958 if (!module_sp
->IsExecutable())
2961 return module_sp
->GetFileSpec().GetFilename().AsCString();
2964 bool StopRunningProcess() {
2965 ExecutionContext exe_ctx
=
2966 m_debugger
.GetCommandInterpreter().GetExecutionContext();
2968 if (!exe_ctx
.HasProcessScope())
2971 Process
*process
= exe_ctx
.GetProcessPtr();
2972 if (!(process
&& process
->IsAlive()))
2975 FormDelegateSP form_delegate_sp
=
2976 FormDelegateSP(new DetachOrKillProcessFormDelegate(process
));
2977 Rect bounds
= m_main_window_sp
->GetCenteredRect(85, 8);
2978 WindowSP form_window_sp
= m_main_window_sp
->CreateSubWindow(
2979 form_delegate_sp
->GetName().c_str(), bounds
, true);
2980 WindowDelegateSP window_delegate_sp
=
2981 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp
));
2982 form_window_sp
->SetDelegate(window_delegate_sp
);
2987 Target
*GetTarget() {
2988 Target
*target
= m_debugger
.GetSelectedTarget().get();
2990 if (target
!= nullptr)
2993 TargetSP new_target_sp
;
2994 m_debugger
.GetTargetList().CreateTarget(
2995 m_debugger
, "", "", eLoadDependentsNo
, nullptr, new_target_sp
);
2997 target
= new_target_sp
.get();
2999 if (target
== nullptr)
3000 SetError("Failed to create target.");
3002 m_debugger
.GetTargetList().SetSelectedTarget(new_target_sp
);
3007 ProcessAttachInfo
GetAttachInfo() {
3008 ProcessAttachInfo attach_info
;
3009 attach_info
.SetContinueOnceAttached(m_continue_field
->GetBoolean());
3010 if (m_type_field
->GetChoiceContent() == "Name") {
3011 attach_info
.GetExecutableFile().SetFile(m_name_field
->GetText(),
3012 FileSpec::Style::native
);
3013 attach_info
.SetWaitForLaunch(m_wait_for_field
->GetBoolean());
3014 if (m_wait_for_field
->GetBoolean())
3015 attach_info
.SetIgnoreExisting(!m_include_existing_field
->GetBoolean());
3017 attach_info
.SetProcessID(m_pid_field
->GetInteger());
3019 attach_info
.SetProcessPluginName(m_plugin_field
->GetPluginName());
3024 void Attach(Window
&window
) {
3027 bool all_fields_are_valid
= CheckFieldsValidity();
3028 if (!all_fields_are_valid
)
3031 bool process_is_running
= StopRunningProcess();
3032 if (process_is_running
)
3035 Target
*target
= GetTarget();
3039 StreamString stream
;
3040 ProcessAttachInfo attach_info
= GetAttachInfo();
3041 Status status
= target
->Attach(attach_info
, &stream
);
3043 if (status
.Fail()) {
3044 SetError(status
.AsCString());
3048 ProcessSP
process_sp(target
->GetProcessSP());
3050 SetError("Attached sucessfully but target has no process.");
3054 if (attach_info
.GetContinueOnceAttached())
3055 process_sp
->Resume();
3057 window
.GetParent()->RemoveSubWindow(&window
);
3061 Debugger
&m_debugger
;
3062 WindowSP m_main_window_sp
;
3064 ChoicesFieldDelegate
*m_type_field
;
3065 IntegerFieldDelegate
*m_pid_field
;
3066 TextFieldDelegate
*m_name_field
;
3067 BooleanFieldDelegate
*m_continue_field
;
3068 BooleanFieldDelegate
*m_wait_for_field
;
3069 BooleanFieldDelegate
*m_include_existing_field
;
3070 BooleanFieldDelegate
*m_show_advanced_field
;
3071 ProcessPluginFieldDelegate
*m_plugin_field
;
3074 class TargetCreateFormDelegate
: public FormDelegate
{
3076 TargetCreateFormDelegate(Debugger
&debugger
) : m_debugger(debugger
) {
3077 m_executable_field
= AddFileField("Executable", "", /*need_to_exist=*/true,
3079 m_core_file_field
= AddFileField("Core File", "", /*need_to_exist=*/true,
3080 /*required=*/false);
3081 m_symbol_file_field
= AddFileField(
3082 "Symbol File", "", /*need_to_exist=*/true, /*required=*/false);
3083 m_show_advanced_field
= AddBooleanField("Show advanced settings.", false);
3084 m_remote_file_field
= AddFileField(
3085 "Remote File", "", /*need_to_exist=*/false, /*required=*/false);
3086 m_arch_field
= AddArchField("Architecture", "", /*required=*/false);
3087 m_platform_field
= AddPlatformPluginField(debugger
);
3088 m_load_dependent_files_field
=
3089 AddChoicesField("Load Dependents", 3, GetLoadDependentFilesChoices());
3091 AddAction("Create", [this](Window
&window
) { CreateTarget(window
); });
3094 std::string
GetName() override
{ return "Create Target"; }
3096 void UpdateFieldsVisibility() override
{
3097 if (m_show_advanced_field
->GetBoolean()) {
3098 m_remote_file_field
->FieldDelegateShow();
3099 m_arch_field
->FieldDelegateShow();
3100 m_platform_field
->FieldDelegateShow();
3101 m_load_dependent_files_field
->FieldDelegateShow();
3103 m_remote_file_field
->FieldDelegateHide();
3104 m_arch_field
->FieldDelegateHide();
3105 m_platform_field
->FieldDelegateHide();
3106 m_load_dependent_files_field
->FieldDelegateHide();
3110 static constexpr const char *kLoadDependentFilesNo
= "No";
3111 static constexpr const char *kLoadDependentFilesYes
= "Yes";
3112 static constexpr const char *kLoadDependentFilesExecOnly
= "Executable only";
3114 std::vector
<std::string
> GetLoadDependentFilesChoices() {
3115 std::vector
<std::string
> load_dependents_options
;
3116 load_dependents_options
.push_back(kLoadDependentFilesExecOnly
);
3117 load_dependents_options
.push_back(kLoadDependentFilesYes
);
3118 load_dependents_options
.push_back(kLoadDependentFilesNo
);
3119 return load_dependents_options
;
3122 LoadDependentFiles
GetLoadDependentFiles() {
3123 std::string choice
= m_load_dependent_files_field
->GetChoiceContent();
3124 if (choice
== kLoadDependentFilesNo
)
3125 return eLoadDependentsNo
;
3126 if (choice
== kLoadDependentFilesYes
)
3127 return eLoadDependentsYes
;
3128 return eLoadDependentsDefault
;
3131 OptionGroupPlatform
GetPlatformOptions() {
3132 OptionGroupPlatform
platform_options(false);
3133 platform_options
.SetPlatformName(m_platform_field
->GetPluginName().c_str());
3134 return platform_options
;
3137 TargetSP
GetTarget() {
3138 OptionGroupPlatform platform_options
= GetPlatformOptions();
3140 Status status
= m_debugger
.GetTargetList().CreateTarget(
3141 m_debugger
, m_executable_field
->GetPath(),
3142 m_arch_field
->GetArchString(), GetLoadDependentFiles(),
3143 &platform_options
, target_sp
);
3145 if (status
.Fail()) {
3146 SetError(status
.AsCString());
3150 m_debugger
.GetTargetList().SetSelectedTarget(target_sp
);
3155 void SetSymbolFile(TargetSP target_sp
) {
3156 if (!m_symbol_file_field
->IsSpecified())
3159 ModuleSP
module_sp(target_sp
->GetExecutableModule());
3163 module_sp
->SetSymbolFileFileSpec(
3164 m_symbol_file_field
->GetResolvedFileSpec());
3167 void SetCoreFile(TargetSP target_sp
) {
3168 if (!m_core_file_field
->IsSpecified())
3171 FileSpec core_file_spec
= m_core_file_field
->GetResolvedFileSpec();
3173 FileSpec core_file_directory_spec
;
3174 core_file_directory_spec
.SetDirectory(core_file_spec
.GetDirectory());
3175 target_sp
->AppendExecutableSearchPaths(core_file_directory_spec
);
3177 ProcessSP
process_sp(target_sp
->CreateProcess(
3178 m_debugger
.GetListener(), llvm::StringRef(), &core_file_spec
, false));
3181 SetError("Unable to find process plug-in for core file!");
3185 Status status
= process_sp
->LoadCore();
3186 if (status
.Fail()) {
3187 SetError("Can't find plug-in for core file!");
3192 void SetRemoteFile(TargetSP target_sp
) {
3193 if (!m_remote_file_field
->IsSpecified())
3196 ModuleSP
module_sp(target_sp
->GetExecutableModule());
3200 FileSpec remote_file_spec
= m_remote_file_field
->GetFileSpec();
3201 module_sp
->SetPlatformFileSpec(remote_file_spec
);
3204 void RemoveTarget(TargetSP target_sp
) {
3205 m_debugger
.GetTargetList().DeleteTarget(target_sp
);
3208 void CreateTarget(Window
&window
) {
3211 bool all_fields_are_valid
= CheckFieldsValidity();
3212 if (!all_fields_are_valid
)
3215 TargetSP target_sp
= GetTarget();
3219 SetSymbolFile(target_sp
);
3221 RemoveTarget(target_sp
);
3225 SetCoreFile(target_sp
);
3227 RemoveTarget(target_sp
);
3231 SetRemoteFile(target_sp
);
3233 RemoveTarget(target_sp
);
3237 window
.GetParent()->RemoveSubWindow(&window
);
3241 Debugger
&m_debugger
;
3243 FileFieldDelegate
*m_executable_field
;
3244 FileFieldDelegate
*m_core_file_field
;
3245 FileFieldDelegate
*m_symbol_file_field
;
3246 BooleanFieldDelegate
*m_show_advanced_field
;
3247 FileFieldDelegate
*m_remote_file_field
;
3248 ArchFieldDelegate
*m_arch_field
;
3249 PlatformPluginFieldDelegate
*m_platform_field
;
3250 ChoicesFieldDelegate
*m_load_dependent_files_field
;
3253 class ProcessLaunchFormDelegate
: public FormDelegate
{
3255 ProcessLaunchFormDelegate(Debugger
&debugger
, WindowSP main_window_sp
)
3256 : m_debugger(debugger
), m_main_window_sp(main_window_sp
) {
3258 m_arguments_field
= AddArgumentsField();
3259 SetArgumentsFieldDefaultValue();
3260 m_target_environment_field
=
3261 AddEnvironmentVariableListField("Target Environment Variables");
3262 SetTargetEnvironmentFieldDefaultValue();
3263 m_working_directory_field
= AddDirectoryField(
3264 "Working Directory", GetDefaultWorkingDirectory().c_str(), true, false);
3266 m_show_advanced_field
= AddBooleanField("Show advanced settings.", false);
3268 m_stop_at_entry_field
= AddBooleanField("Stop at entry point.", false);
3269 m_detach_on_error_field
=
3270 AddBooleanField("Detach on error.", GetDefaultDetachOnError());
3271 m_disable_aslr_field
=
3272 AddBooleanField("Disable ASLR", GetDefaultDisableASLR());
3273 m_plugin_field
= AddProcessPluginField();
3274 m_arch_field
= AddArchField("Architecture", "", false);
3275 m_shell_field
= AddFileField("Shell", "", true, false);
3276 m_expand_shell_arguments_field
=
3277 AddBooleanField("Expand shell arguments.", false);
3279 m_disable_standard_io_field
=
3280 AddBooleanField("Disable Standard IO", GetDefaultDisableStandardIO());
3281 m_standard_output_field
=
3282 AddFileField("Standard Output File", "", /*need_to_exist=*/false,
3283 /*required=*/false);
3284 m_standard_error_field
=
3285 AddFileField("Standard Error File", "", /*need_to_exist=*/false,
3286 /*required=*/false);
3287 m_standard_input_field
=
3288 AddFileField("Standard Input File", "", /*need_to_exist=*/false,
3289 /*required=*/false);
3291 m_show_inherited_environment_field
=
3292 AddBooleanField("Show inherited environment variables.", false);
3293 m_inherited_environment_field
=
3294 AddEnvironmentVariableListField("Inherited Environment Variables");
3295 SetInheritedEnvironmentFieldDefaultValue();
3297 AddAction("Launch", [this](Window
&window
) { Launch(window
); });
3300 std::string
GetName() override
{ return "Launch Process"; }
3302 void UpdateFieldsVisibility() override
{
3303 if (m_show_advanced_field
->GetBoolean()) {
3304 m_stop_at_entry_field
->FieldDelegateShow();
3305 m_detach_on_error_field
->FieldDelegateShow();
3306 m_disable_aslr_field
->FieldDelegateShow();
3307 m_plugin_field
->FieldDelegateShow();
3308 m_arch_field
->FieldDelegateShow();
3309 m_shell_field
->FieldDelegateShow();
3310 m_expand_shell_arguments_field
->FieldDelegateShow();
3311 m_disable_standard_io_field
->FieldDelegateShow();
3312 if (m_disable_standard_io_field
->GetBoolean()) {
3313 m_standard_input_field
->FieldDelegateHide();
3314 m_standard_output_field
->FieldDelegateHide();
3315 m_standard_error_field
->FieldDelegateHide();
3317 m_standard_input_field
->FieldDelegateShow();
3318 m_standard_output_field
->FieldDelegateShow();
3319 m_standard_error_field
->FieldDelegateShow();
3321 m_show_inherited_environment_field
->FieldDelegateShow();
3322 if (m_show_inherited_environment_field
->GetBoolean())
3323 m_inherited_environment_field
->FieldDelegateShow();
3325 m_inherited_environment_field
->FieldDelegateHide();
3327 m_stop_at_entry_field
->FieldDelegateHide();
3328 m_detach_on_error_field
->FieldDelegateHide();
3329 m_disable_aslr_field
->FieldDelegateHide();
3330 m_plugin_field
->FieldDelegateHide();
3331 m_arch_field
->FieldDelegateHide();
3332 m_shell_field
->FieldDelegateHide();
3333 m_expand_shell_arguments_field
->FieldDelegateHide();
3334 m_disable_standard_io_field
->FieldDelegateHide();
3335 m_standard_input_field
->FieldDelegateHide();
3336 m_standard_output_field
->FieldDelegateHide();
3337 m_standard_error_field
->FieldDelegateHide();
3338 m_show_inherited_environment_field
->FieldDelegateHide();
3339 m_inherited_environment_field
->FieldDelegateHide();
3343 // Methods for setting the default value of the fields.
3345 void SetArgumentsFieldDefaultValue() {
3346 TargetSP target
= m_debugger
.GetSelectedTarget();
3347 if (target
== nullptr)
3350 const Args
&target_arguments
=
3351 target
->GetProcessLaunchInfo().GetArguments();
3352 m_arguments_field
->AddArguments(target_arguments
);
3355 void SetTargetEnvironmentFieldDefaultValue() {
3356 TargetSP target
= m_debugger
.GetSelectedTarget();
3357 if (target
== nullptr)
3360 const Environment
&target_environment
= target
->GetTargetEnvironment();
3361 m_target_environment_field
->AddEnvironmentVariables(target_environment
);
3364 void SetInheritedEnvironmentFieldDefaultValue() {
3365 TargetSP target
= m_debugger
.GetSelectedTarget();
3366 if (target
== nullptr)
3369 const Environment
&inherited_environment
=
3370 target
->GetInheritedEnvironment();
3371 m_inherited_environment_field
->AddEnvironmentVariables(
3372 inherited_environment
);
3375 std::string
GetDefaultWorkingDirectory() {
3376 TargetSP target
= m_debugger
.GetSelectedTarget();
3377 if (target
== nullptr)
3380 PlatformSP platform
= target
->GetPlatform();
3381 return platform
->GetWorkingDirectory().GetPath();
3384 bool GetDefaultDisableASLR() {
3385 TargetSP target
= m_debugger
.GetSelectedTarget();
3386 if (target
== nullptr)
3389 return target
->GetDisableASLR();
3392 bool GetDefaultDisableStandardIO() {
3393 TargetSP target
= m_debugger
.GetSelectedTarget();
3394 if (target
== nullptr)
3397 return target
->GetDisableSTDIO();
3400 bool GetDefaultDetachOnError() {
3401 TargetSP target
= m_debugger
.GetSelectedTarget();
3402 if (target
== nullptr)
3405 return target
->GetDetachOnError();
3408 // Methods for getting the necessary information and setting them to the
3409 // ProcessLaunchInfo.
3411 void GetExecutableSettings(ProcessLaunchInfo
&launch_info
) {
3412 TargetSP target
= m_debugger
.GetSelectedTarget();
3413 ModuleSP executable_module
= target
->GetExecutableModule();
3414 llvm::StringRef target_settings_argv0
= target
->GetArg0();
3416 if (!target_settings_argv0
.empty()) {
3417 launch_info
.GetArguments().AppendArgument(target_settings_argv0
);
3418 launch_info
.SetExecutableFile(executable_module
->GetPlatformFileSpec(),
3423 launch_info
.SetExecutableFile(executable_module
->GetPlatformFileSpec(),
3427 void GetArguments(ProcessLaunchInfo
&launch_info
) {
3428 TargetSP target
= m_debugger
.GetSelectedTarget();
3429 Args arguments
= m_arguments_field
->GetArguments();
3430 launch_info
.GetArguments().AppendArguments(arguments
);
3433 void GetEnvironment(ProcessLaunchInfo
&launch_info
) {
3434 Environment target_environment
=
3435 m_target_environment_field
->GetEnvironment();
3436 Environment inherited_environment
=
3437 m_inherited_environment_field
->GetEnvironment();
3438 launch_info
.GetEnvironment().insert(target_environment
.begin(),
3439 target_environment
.end());
3440 launch_info
.GetEnvironment().insert(inherited_environment
.begin(),
3441 inherited_environment
.end());
3444 void GetWorkingDirectory(ProcessLaunchInfo
&launch_info
) {
3445 if (m_working_directory_field
->IsSpecified())
3446 launch_info
.SetWorkingDirectory(
3447 m_working_directory_field
->GetResolvedFileSpec());
3450 void GetStopAtEntry(ProcessLaunchInfo
&launch_info
) {
3451 if (m_stop_at_entry_field
->GetBoolean())
3452 launch_info
.GetFlags().Set(eLaunchFlagStopAtEntry
);
3454 launch_info
.GetFlags().Clear(eLaunchFlagStopAtEntry
);
3457 void GetDetachOnError(ProcessLaunchInfo
&launch_info
) {
3458 if (m_detach_on_error_field
->GetBoolean())
3459 launch_info
.GetFlags().Set(eLaunchFlagDetachOnError
);
3461 launch_info
.GetFlags().Clear(eLaunchFlagDetachOnError
);
3464 void GetDisableASLR(ProcessLaunchInfo
&launch_info
) {
3465 if (m_disable_aslr_field
->GetBoolean())
3466 launch_info
.GetFlags().Set(eLaunchFlagDisableASLR
);
3468 launch_info
.GetFlags().Clear(eLaunchFlagDisableASLR
);
3471 void GetPlugin(ProcessLaunchInfo
&launch_info
) {
3472 launch_info
.SetProcessPluginName(m_plugin_field
->GetPluginName());
3475 void GetArch(ProcessLaunchInfo
&launch_info
) {
3476 if (!m_arch_field
->IsSpecified())
3479 TargetSP target_sp
= m_debugger
.GetSelectedTarget();
3480 PlatformSP platform_sp
=
3481 target_sp
? target_sp
->GetPlatform() : PlatformSP();
3482 launch_info
.GetArchitecture() = Platform::GetAugmentedArchSpec(
3483 platform_sp
.get(), m_arch_field
->GetArchString());
3486 void GetShell(ProcessLaunchInfo
&launch_info
) {
3487 if (!m_shell_field
->IsSpecified())
3490 launch_info
.SetShell(m_shell_field
->GetResolvedFileSpec());
3491 launch_info
.SetShellExpandArguments(
3492 m_expand_shell_arguments_field
->GetBoolean());
3495 void GetStandardIO(ProcessLaunchInfo
&launch_info
) {
3496 if (m_disable_standard_io_field
->GetBoolean()) {
3497 launch_info
.GetFlags().Set(eLaunchFlagDisableSTDIO
);
3502 if (m_standard_input_field
->IsSpecified()) {
3503 if (action
.Open(STDIN_FILENO
, m_standard_input_field
->GetFileSpec(), true,
3505 launch_info
.AppendFileAction(action
);
3507 if (m_standard_output_field
->IsSpecified()) {
3508 if (action
.Open(STDOUT_FILENO
, m_standard_output_field
->GetFileSpec(),
3510 launch_info
.AppendFileAction(action
);
3512 if (m_standard_error_field
->IsSpecified()) {
3513 if (action
.Open(STDERR_FILENO
, m_standard_error_field
->GetFileSpec(),
3515 launch_info
.AppendFileAction(action
);
3519 void GetInheritTCC(ProcessLaunchInfo
&launch_info
) {
3520 if (m_debugger
.GetSelectedTarget()->GetInheritTCC())
3521 launch_info
.GetFlags().Set(eLaunchFlagInheritTCCFromParent
);
3524 ProcessLaunchInfo
GetLaunchInfo() {
3525 ProcessLaunchInfo launch_info
;
3527 GetExecutableSettings(launch_info
);
3528 GetArguments(launch_info
);
3529 GetEnvironment(launch_info
);
3530 GetWorkingDirectory(launch_info
);
3531 GetStopAtEntry(launch_info
);
3532 GetDetachOnError(launch_info
);
3533 GetDisableASLR(launch_info
);
3534 GetPlugin(launch_info
);
3535 GetArch(launch_info
);
3536 GetShell(launch_info
);
3537 GetStandardIO(launch_info
);
3538 GetInheritTCC(launch_info
);
3543 bool StopRunningProcess() {
3544 ExecutionContext exe_ctx
=
3545 m_debugger
.GetCommandInterpreter().GetExecutionContext();
3547 if (!exe_ctx
.HasProcessScope())
3550 Process
*process
= exe_ctx
.GetProcessPtr();
3551 if (!(process
&& process
->IsAlive()))
3554 FormDelegateSP form_delegate_sp
=
3555 FormDelegateSP(new DetachOrKillProcessFormDelegate(process
));
3556 Rect bounds
= m_main_window_sp
->GetCenteredRect(85, 8);
3557 WindowSP form_window_sp
= m_main_window_sp
->CreateSubWindow(
3558 form_delegate_sp
->GetName().c_str(), bounds
, true);
3559 WindowDelegateSP window_delegate_sp
=
3560 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp
));
3561 form_window_sp
->SetDelegate(window_delegate_sp
);
3566 Target
*GetTarget() {
3567 Target
*target
= m_debugger
.GetSelectedTarget().get();
3569 if (target
== nullptr) {
3570 SetError("No target exists!");
3574 ModuleSP exe_module_sp
= target
->GetExecutableModule();
3576 if (exe_module_sp
== nullptr) {
3577 SetError("No executable in target!");
3584 void Launch(Window
&window
) {
3587 bool all_fields_are_valid
= CheckFieldsValidity();
3588 if (!all_fields_are_valid
)
3591 bool process_is_running
= StopRunningProcess();
3592 if (process_is_running
)
3595 Target
*target
= GetTarget();
3599 StreamString stream
;
3600 ProcessLaunchInfo launch_info
= GetLaunchInfo();
3601 Status status
= target
->Launch(launch_info
, &stream
);
3603 if (status
.Fail()) {
3604 SetError(status
.AsCString());
3608 ProcessSP
process_sp(target
->GetProcessSP());
3610 SetError("Launched successfully but target has no process!");
3614 window
.GetParent()->RemoveSubWindow(&window
);
3618 Debugger
&m_debugger
;
3619 WindowSP m_main_window_sp
;
3621 ArgumentsFieldDelegate
*m_arguments_field
;
3622 EnvironmentVariableListFieldDelegate
*m_target_environment_field
;
3623 DirectoryFieldDelegate
*m_working_directory_field
;
3625 BooleanFieldDelegate
*m_show_advanced_field
;
3627 BooleanFieldDelegate
*m_stop_at_entry_field
;
3628 BooleanFieldDelegate
*m_detach_on_error_field
;
3629 BooleanFieldDelegate
*m_disable_aslr_field
;
3630 ProcessPluginFieldDelegate
*m_plugin_field
;
3631 ArchFieldDelegate
*m_arch_field
;
3632 FileFieldDelegate
*m_shell_field
;
3633 BooleanFieldDelegate
*m_expand_shell_arguments_field
;
3634 BooleanFieldDelegate
*m_disable_standard_io_field
;
3635 FileFieldDelegate
*m_standard_input_field
;
3636 FileFieldDelegate
*m_standard_output_field
;
3637 FileFieldDelegate
*m_standard_error_field
;
3639 BooleanFieldDelegate
*m_show_inherited_environment_field
;
3640 EnvironmentVariableListFieldDelegate
*m_inherited_environment_field
;
3647 class SearcherDelegate
{
3649 SearcherDelegate() = default;
3651 virtual ~SearcherDelegate() = default;
3653 virtual int GetNumberOfMatches() = 0;
3655 // Get the string that will be displayed for the match at the input index.
3656 virtual const std::string
&GetMatchTextAtIndex(int index
) = 0;
3658 // Update the matches of the search. This is executed every time the text
3659 // field handles an event.
3660 virtual void UpdateMatches(const std::string
&text
) = 0;
3662 // Execute the user callback given the index of some match. This is executed
3663 // once the user selects a match.
3664 virtual void ExecuteCallback(int match_index
) = 0;
3667 typedef std::shared_ptr
<SearcherDelegate
> SearcherDelegateSP
;
3669 class SearcherWindowDelegate
: public WindowDelegate
{
3671 SearcherWindowDelegate(SearcherDelegateSP
&delegate_sp
)
3672 : m_delegate_sp(delegate_sp
), m_text_field("Search", "", false) {
3676 // A completion window is padded by one character from all sides. A text field
3677 // is first drawn for inputting the searcher request, then a list of matches
3678 // are displayed in a scrollable list.
3680 // ___<Searcher Window Name>____________________________
3682 // | __[Search]_______________________________________ |
3684 // | |_______________________________________________| |
3689 // |____________________________[Press Esc to Cancel]__|
3692 // Get the index of the last visible match. Assuming at least one match
3694 int GetLastVisibleMatch(int height
) {
3695 int index
= m_first_visible_match
+ height
;
3696 return std::min(index
, m_delegate_sp
->GetNumberOfMatches()) - 1;
3699 int GetNumberOfVisibleMatches(int height
) {
3700 return GetLastVisibleMatch(height
) - m_first_visible_match
+ 1;
3703 void UpdateScrolling(Surface
&surface
) {
3704 if (m_selected_match
< m_first_visible_match
) {
3705 m_first_visible_match
= m_selected_match
;
3709 int height
= surface
.GetHeight();
3710 int last_visible_match
= GetLastVisibleMatch(height
);
3711 if (m_selected_match
> last_visible_match
) {
3712 m_first_visible_match
= m_selected_match
- height
+ 1;
3716 void DrawMatches(Surface
&surface
) {
3717 if (m_delegate_sp
->GetNumberOfMatches() == 0)
3720 UpdateScrolling(surface
);
3722 int count
= GetNumberOfVisibleMatches(surface
.GetHeight());
3723 for (int i
= 0; i
< count
; i
++) {
3724 surface
.MoveCursor(1, i
);
3725 int current_match
= m_first_visible_match
+ i
;
3726 if (current_match
== m_selected_match
)
3727 surface
.AttributeOn(A_REVERSE
);
3729 m_delegate_sp
->GetMatchTextAtIndex(current_match
).c_str());
3730 if (current_match
== m_selected_match
)
3731 surface
.AttributeOff(A_REVERSE
);
3735 void DrawContent(Surface
&surface
) {
3736 Rect content_bounds
= surface
.GetFrame();
3737 Rect text_field_bounds
, matchs_bounds
;
3738 content_bounds
.HorizontalSplit(m_text_field
.FieldDelegateGetHeight(),
3739 text_field_bounds
, matchs_bounds
);
3740 Surface text_field_surface
= surface
.SubSurface(text_field_bounds
);
3741 Surface matches_surface
= surface
.SubSurface(matchs_bounds
);
3743 m_text_field
.FieldDelegateDraw(text_field_surface
, true);
3744 DrawMatches(matches_surface
);
3747 bool WindowDelegateDraw(Window
&window
, bool force
) override
{
3750 window
.DrawTitleBox(window
.GetName(), "Press Esc to Cancel");
3752 Rect content_bounds
= window
.GetFrame();
3753 content_bounds
.Inset(2, 2);
3754 Surface content_surface
= window
.SubSurface(content_bounds
);
3756 DrawContent(content_surface
);
3761 if (m_selected_match
!= m_delegate_sp
->GetNumberOfMatches() - 1)
3765 void SelectPrevious() {
3766 if (m_selected_match
!= 0)
3770 void ExecuteCallback(Window
&window
) {
3771 m_delegate_sp
->ExecuteCallback(m_selected_match
);
3772 window
.GetParent()->RemoveSubWindow(&window
);
3775 void UpdateMatches() {
3776 m_delegate_sp
->UpdateMatches(m_text_field
.GetText());
3777 m_selected_match
= 0;
3780 HandleCharResult
WindowDelegateHandleChar(Window
&window
, int key
) override
{
3785 ExecuteCallback(window
);
3796 window
.GetParent()->RemoveSubWindow(&window
);
3802 if (m_text_field
.FieldDelegateHandleChar(key
) == eKeyHandled
)
3809 SearcherDelegateSP m_delegate_sp
;
3810 TextFieldDelegate m_text_field
;
3811 // The index of the currently selected match.
3812 int m_selected_match
= 0;
3813 // The index of the first visible match.
3814 int m_first_visible_match
= 0;
3817 //////////////////////////////
3818 // Searcher Delegate Instances
3819 //////////////////////////////
3821 // This is a searcher delegate wrapper around CommandCompletions common
3822 // callbacks. The callbacks are only given the match string. The completion_mask
3823 // can be a combination of lldb::CompletionType.
3824 class CommonCompletionSearcherDelegate
: public SearcherDelegate
{
3826 typedef std::function
<void(const std::string
&)> CallbackType
;
3828 CommonCompletionSearcherDelegate(Debugger
&debugger
, uint32_t completion_mask
,
3829 CallbackType callback
)
3830 : m_debugger(debugger
), m_completion_mask(completion_mask
),
3831 m_callback(callback
) {}
3833 int GetNumberOfMatches() override
{ return m_matches
.GetSize(); }
3835 const std::string
&GetMatchTextAtIndex(int index
) override
{
3836 return m_matches
[index
];
3839 void UpdateMatches(const std::string
&text
) override
{
3840 CompletionResult result
;
3841 CompletionRequest
request(text
.c_str(), text
.size(), result
);
3842 lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(
3843 m_debugger
.GetCommandInterpreter(), m_completion_mask
, request
,
3845 result
.GetMatches(m_matches
);
3848 void ExecuteCallback(int match_index
) override
{
3849 m_callback(m_matches
[match_index
]);
3853 Debugger
&m_debugger
;
3854 // A compound mask from lldb::CompletionType.
3855 uint32_t m_completion_mask
;
3856 // A callback to execute once the user selects a match. The match is passed to
3857 // the callback as a string.
3858 CallbackType m_callback
;
3859 StringList m_matches
;
3866 class MenuDelegate
{
3868 virtual ~MenuDelegate() = default;
3870 virtual MenuActionResult
MenuDelegateAction(Menu
&menu
) = 0;
3873 class Menu
: public WindowDelegate
{
3875 enum class Type
{ Invalid
, Bar
, Item
, Separator
};
3877 // Menubar or separator constructor
3880 // Menuitem constructor
3881 Menu(const char *name
, const char *key_name
, int key_value
,
3882 uint64_t identifier
);
3884 ~Menu() override
= default;
3886 const MenuDelegateSP
&GetDelegate() const { return m_delegate_sp
; }
3888 void SetDelegate(const MenuDelegateSP
&delegate_sp
) {
3889 m_delegate_sp
= delegate_sp
;
3892 void RecalculateNameLengths();
3894 void AddSubmenu(const MenuSP
&menu_sp
);
3896 int DrawAndRunMenu(Window
&window
);
3898 void DrawMenuTitle(Window
&window
, bool highlight
);
3900 bool WindowDelegateDraw(Window
&window
, bool force
) override
;
3902 HandleCharResult
WindowDelegateHandleChar(Window
&window
, int key
) override
;
3904 MenuActionResult
ActionPrivate(Menu
&menu
) {
3905 MenuActionResult result
= MenuActionResult::NotHandled
;
3906 if (m_delegate_sp
) {
3907 result
= m_delegate_sp
->MenuDelegateAction(menu
);
3908 if (result
!= MenuActionResult::NotHandled
)
3910 } else if (m_parent
) {
3911 result
= m_parent
->ActionPrivate(menu
);
3912 if (result
!= MenuActionResult::NotHandled
)
3915 return m_canned_result
;
3918 MenuActionResult
Action() {
3919 // Call the recursive action so it can try to handle it with the menu
3920 // delegate, and if not, try our parent menu
3921 return ActionPrivate(*this);
3924 void SetCannedResult(MenuActionResult result
) { m_canned_result
= result
; }
3926 Menus
&GetSubmenus() { return m_submenus
; }
3928 const Menus
&GetSubmenus() const { return m_submenus
; }
3930 int GetSelectedSubmenuIndex() const { return m_selected
; }
3932 void SetSelectedSubmenuIndex(int idx
) { m_selected
= idx
; }
3934 Type
GetType() const { return m_type
; }
3936 int GetStartingColumn() const { return m_start_col
; }
3938 void SetStartingColumn(int col
) { m_start_col
= col
; }
3940 int GetKeyValue() const { return m_key_value
; }
3942 std::string
&GetName() { return m_name
; }
3944 int GetDrawWidth() const {
3945 return m_max_submenu_name_length
+ m_max_submenu_key_name_length
+ 8;
3948 uint64_t GetIdentifier() const { return m_identifier
; }
3950 void SetIdentifier(uint64_t identifier
) { m_identifier
= identifier
; }
3954 std::string m_key_name
;
3955 uint64_t m_identifier
;
3959 int m_max_submenu_name_length
;
3960 int m_max_submenu_key_name_length
;
3964 WindowSP m_menu_window_sp
;
3965 MenuActionResult m_canned_result
;
3966 MenuDelegateSP m_delegate_sp
;
3969 // Menubar or separator constructor
3970 Menu::Menu(Type type
)
3971 : m_name(), m_key_name(), m_identifier(0), m_type(type
), m_key_value(0),
3972 m_start_col(0), m_max_submenu_name_length(0),
3973 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
3974 m_submenus(), m_canned_result(MenuActionResult::NotHandled
),
3977 // Menuitem constructor
3978 Menu::Menu(const char *name
, const char *key_name
, int key_value
,
3979 uint64_t identifier
)
3980 : m_name(), m_key_name(), m_identifier(identifier
), m_type(Type::Invalid
),
3981 m_key_value(key_value
), m_start_col(0), m_max_submenu_name_length(0),
3982 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
3983 m_submenus(), m_canned_result(MenuActionResult::NotHandled
),
3985 if (name
&& name
[0]) {
3987 m_type
= Type::Item
;
3988 if (key_name
&& key_name
[0])
3989 m_key_name
= key_name
;
3991 m_type
= Type::Separator
;
3995 void Menu::RecalculateNameLengths() {
3996 m_max_submenu_name_length
= 0;
3997 m_max_submenu_key_name_length
= 0;
3998 Menus
&submenus
= GetSubmenus();
3999 const size_t num_submenus
= submenus
.size();
4000 for (size_t i
= 0; i
< num_submenus
; ++i
) {
4001 Menu
*submenu
= submenus
[i
].get();
4002 if (static_cast<size_t>(m_max_submenu_name_length
) < submenu
->m_name
.size())
4003 m_max_submenu_name_length
= submenu
->m_name
.size();
4004 if (static_cast<size_t>(m_max_submenu_key_name_length
) <
4005 submenu
->m_key_name
.size())
4006 m_max_submenu_key_name_length
= submenu
->m_key_name
.size();
4010 void Menu::AddSubmenu(const MenuSP
&menu_sp
) {
4011 menu_sp
->m_parent
= this;
4012 if (static_cast<size_t>(m_max_submenu_name_length
) < menu_sp
->m_name
.size())
4013 m_max_submenu_name_length
= menu_sp
->m_name
.size();
4014 if (static_cast<size_t>(m_max_submenu_key_name_length
) <
4015 menu_sp
->m_key_name
.size())
4016 m_max_submenu_key_name_length
= menu_sp
->m_key_name
.size();
4017 m_submenus
.push_back(menu_sp
);
4020 void Menu::DrawMenuTitle(Window
&window
, bool highlight
) {
4021 if (m_type
== Type::Separator
) {
4022 window
.MoveCursor(0, window
.GetCursorY());
4023 window
.PutChar(ACS_LTEE
);
4024 int width
= window
.GetWidth();
4027 for (int i
= 0; i
< width
; ++i
)
4028 window
.PutChar(ACS_HLINE
);
4030 window
.PutChar(ACS_RTEE
);
4032 const int shortcut_key
= m_key_value
;
4033 bool underlined_shortcut
= false;
4034 const attr_t highlight_attr
= A_REVERSE
;
4036 window
.AttributeOn(highlight_attr
);
4037 if (llvm::isPrint(shortcut_key
)) {
4038 size_t lower_pos
= m_name
.find(tolower(shortcut_key
));
4039 size_t upper_pos
= m_name
.find(toupper(shortcut_key
));
4040 const char *name
= m_name
.c_str();
4041 size_t pos
= std::min
<size_t>(lower_pos
, upper_pos
);
4042 if (pos
!= std::string::npos
) {
4043 underlined_shortcut
= true;
4045 window
.PutCString(name
, pos
);
4048 const attr_t shortcut_attr
= A_UNDERLINE
| A_BOLD
;
4049 window
.AttributeOn(shortcut_attr
);
4050 window
.PutChar(name
[0]);
4051 window
.AttributeOff(shortcut_attr
);
4054 window
.PutCString(name
);
4058 if (!underlined_shortcut
) {
4059 window
.PutCString(m_name
.c_str());
4063 window
.AttributeOff(highlight_attr
);
4065 if (m_key_name
.empty()) {
4066 if (!underlined_shortcut
&& llvm::isPrint(m_key_value
)) {
4067 window
.AttributeOn(COLOR_PAIR(MagentaOnWhite
));
4068 window
.Printf(" (%c)", m_key_value
);
4069 window
.AttributeOff(COLOR_PAIR(MagentaOnWhite
));
4072 window
.AttributeOn(COLOR_PAIR(MagentaOnWhite
));
4073 window
.Printf(" (%s)", m_key_name
.c_str());
4074 window
.AttributeOff(COLOR_PAIR(MagentaOnWhite
));
4079 bool Menu::WindowDelegateDraw(Window
&window
, bool force
) {
4080 Menus
&submenus
= GetSubmenus();
4081 const size_t num_submenus
= submenus
.size();
4082 const int selected_idx
= GetSelectedSubmenuIndex();
4083 Menu::Type menu_type
= GetType();
4084 switch (menu_type
) {
4085 case Menu::Type::Bar
: {
4086 window
.SetBackground(BlackOnWhite
);
4087 window
.MoveCursor(0, 0);
4088 for (size_t i
= 0; i
< num_submenus
; ++i
) {
4089 Menu
*menu
= submenus
[i
].get();
4091 window
.PutChar(' ');
4092 menu
->SetStartingColumn(window
.GetCursorX());
4093 window
.PutCString("| ");
4094 menu
->DrawMenuTitle(window
, false);
4096 window
.PutCString(" |");
4099 case Menu::Type::Item
: {
4106 window
.SetBackground(BlackOnWhite
);
4108 for (size_t i
= 0; i
< num_submenus
; ++i
) {
4109 const bool is_selected
= (i
== static_cast<size_t>(selected_idx
));
4110 window
.MoveCursor(x
, y
+ i
);
4112 // Remember where we want the cursor to be
4116 submenus
[i
]->DrawMenuTitle(window
, is_selected
);
4118 window
.MoveCursor(cursor_x
, cursor_y
);
4122 case Menu::Type::Separator
:
4125 return true; // Drawing handled...
4128 HandleCharResult
Menu::WindowDelegateHandleChar(Window
&window
, int key
) {
4129 HandleCharResult result
= eKeyNotHandled
;
4131 Menus
&submenus
= GetSubmenus();
4132 const size_t num_submenus
= submenus
.size();
4133 const int selected_idx
= GetSelectedSubmenuIndex();
4134 Menu::Type menu_type
= GetType();
4135 if (menu_type
== Menu::Type::Bar
) {
4140 // Show last menu or first menu
4141 if (selected_idx
< static_cast<int>(num_submenus
))
4142 run_menu_sp
= submenus
[selected_idx
];
4143 else if (!submenus
.empty())
4144 run_menu_sp
= submenus
.front();
4145 result
= eKeyHandled
;
4150 if (m_selected
>= static_cast<int>(num_submenus
))
4152 if (m_selected
< static_cast<int>(num_submenus
))
4153 run_menu_sp
= submenus
[m_selected
];
4154 else if (!submenus
.empty())
4155 run_menu_sp
= submenus
.front();
4156 result
= eKeyHandled
;
4162 m_selected
= num_submenus
- 1;
4163 if (m_selected
< static_cast<int>(num_submenus
))
4164 run_menu_sp
= submenus
[m_selected
];
4165 else if (!submenus
.empty())
4166 run_menu_sp
= submenus
.front();
4167 result
= eKeyHandled
;
4171 for (size_t i
= 0; i
< num_submenus
; ++i
) {
4172 if (submenus
[i
]->GetKeyValue() == key
) {
4173 SetSelectedSubmenuIndex(i
);
4174 run_menu_sp
= submenus
[i
];
4175 result
= eKeyHandled
;
4183 // Run the action on this menu in case we need to populate the menu with
4184 // dynamic content and also in case check marks, and any other menu
4185 // decorations need to be calculated
4186 if (run_menu_sp
->Action() == MenuActionResult::Quit
)
4187 return eQuitApplication
;
4190 menu_bounds
.origin
.x
= run_menu_sp
->GetStartingColumn();
4191 menu_bounds
.origin
.y
= 1;
4192 menu_bounds
.size
.width
= run_menu_sp
->GetDrawWidth();
4193 menu_bounds
.size
.height
= run_menu_sp
->GetSubmenus().size() + 2;
4194 if (m_menu_window_sp
)
4195 window
.GetParent()->RemoveSubWindow(m_menu_window_sp
.get());
4197 m_menu_window_sp
= window
.GetParent()->CreateSubWindow(
4198 run_menu_sp
->GetName().c_str(), menu_bounds
, true);
4199 m_menu_window_sp
->SetDelegate(run_menu_sp
);
4201 } else if (menu_type
== Menu::Type::Item
) {
4204 if (m_submenus
.size() > 1) {
4205 const int start_select
= m_selected
;
4206 while (++m_selected
!= start_select
) {
4207 if (static_cast<size_t>(m_selected
) >= num_submenus
)
4209 if (m_submenus
[m_selected
]->GetType() == Type::Separator
)
4219 if (m_submenus
.size() > 1) {
4220 const int start_select
= m_selected
;
4221 while (--m_selected
!= start_select
) {
4222 if (m_selected
< static_cast<int>(0))
4223 m_selected
= num_submenus
- 1;
4224 if (m_submenus
[m_selected
]->GetType() == Type::Separator
)
4234 if (static_cast<size_t>(selected_idx
) < num_submenus
) {
4235 if (submenus
[selected_idx
]->Action() == MenuActionResult::Quit
)
4236 return eQuitApplication
;
4237 window
.GetParent()->RemoveSubWindow(&window
);
4242 case KEY_ESCAPE
: // Beware: pressing escape key has 1 to 2 second delay in
4243 // case other chars are entered for escaped sequences
4244 window
.GetParent()->RemoveSubWindow(&window
);
4248 for (size_t i
= 0; i
< num_submenus
; ++i
) {
4249 Menu
*menu
= submenus
[i
].get();
4250 if (menu
->GetKeyValue() == key
) {
4251 SetSelectedSubmenuIndex(i
);
4252 window
.GetParent()->RemoveSubWindow(&window
);
4253 if (menu
->Action() == MenuActionResult::Quit
)
4254 return eQuitApplication
;
4260 } else if (menu_type
== Menu::Type::Separator
) {
4267 Application(FILE *in
, FILE *out
) : m_window_sp(), m_in(in
), m_out(out
) {}
4270 m_window_delegates
.clear();
4271 m_window_sp
.reset();
4273 ::delscreen(m_screen
);
4279 m_screen
= ::newterm(nullptr, m_out
, m_in
);
4283 ::keypad(stdscr
, TRUE
);
4286 void Terminate() { ::endwin(); }
4288 void Run(Debugger
&debugger
) {
4290 int delay_in_tenths_of_a_second
= 1;
4292 // Alas the threading model in curses is a bit lame so we need to resort
4293 // to polling every 0.5 seconds. We could poll for stdin ourselves and
4294 // then pass the keys down but then we need to translate all of the escape
4295 // sequences ourselves. So we resort to polling for input because we need
4296 // to receive async process events while in this loop.
4298 halfdelay(delay_in_tenths_of_a_second
); // Poll using some number of
4299 // tenths of seconds seconds when
4300 // calling Window::GetChar()
4302 ListenerSP
listener_sp(
4303 Listener::MakeListener("lldb.IOHandler.curses.Application"));
4304 ConstString
broadcaster_class_process(Process::GetStaticBroadcasterClass());
4305 debugger
.EnableForwardEvents(listener_sp
);
4307 m_update_screen
= true;
4308 #if defined(__APPLE__)
4309 std::deque
<int> escape_chars
;
4313 if (m_update_screen
) {
4314 m_window_sp
->Draw(false);
4315 // All windows should be calling Window::DeferredRefresh() instead of
4316 // Window::Refresh() so we can do a single update and avoid any screen
4320 // Cursor hiding isn't working on MacOSX, so hide it in the top left
4322 m_window_sp
->MoveCursor(0, 0);
4325 m_update_screen
= false;
4328 #if defined(__APPLE__)
4329 // Terminal.app doesn't map its function keys correctly, F1-F4 default
4330 // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if
4333 if (escape_chars
.empty())
4334 ch
= m_window_sp
->GetChar();
4336 ch
= escape_chars
.front();
4337 escape_chars
.pop_front();
4339 if (ch
== KEY_ESCAPE
) {
4340 int ch2
= m_window_sp
->GetChar();
4342 int ch3
= m_window_sp
->GetChar();
4357 escape_chars
.push_back(ch2
);
4359 escape_chars
.push_back(ch3
);
4362 } else if (ch2
!= -1)
4363 escape_chars
.push_back(ch2
);
4366 int ch
= m_window_sp
->GetChar();
4370 if (feof(m_in
) || ferror(m_in
)) {
4373 // Just a timeout from using halfdelay(), check for events
4375 while (listener_sp
->PeekAtNextEvent()) {
4376 listener_sp
->GetEvent(event_sp
, std::chrono::seconds(0));
4379 Broadcaster
*broadcaster
= event_sp
->GetBroadcaster();
4381 // uint32_t event_type = event_sp->GetType();
4382 ConstString
broadcaster_class(
4383 broadcaster
->GetBroadcasterClass());
4384 if (broadcaster_class
== broadcaster_class_process
) {
4385 m_update_screen
= true;
4386 continue; // Don't get any key, just update our view
4393 HandleCharResult key_result
= m_window_sp
->HandleChar(ch
);
4394 switch (key_result
) {
4396 m_update_screen
= true;
4398 case eKeyNotHandled
:
4399 if (ch
== 12) { // Ctrl+L, force full redraw
4400 redrawwin(m_window_sp
->get());
4401 m_update_screen
= true;
4404 case eQuitApplication
:
4411 debugger
.CancelForwardEvents(listener_sp
);
4414 WindowSP
&GetMainWindow() {
4416 m_window_sp
= std::make_shared
<Window
>("main", stdscr
, false);
4420 void TerminalSizeChanged() {
4423 Rect content_bounds
= m_window_sp
->GetFrame();
4424 m_window_sp
->SetBounds(content_bounds
);
4425 if (WindowSP menubar_window_sp
= m_window_sp
->FindSubWindow("Menubar"))
4426 menubar_window_sp
->SetBounds(content_bounds
.MakeMenuBar());
4427 if (WindowSP status_window_sp
= m_window_sp
->FindSubWindow("Status"))
4428 status_window_sp
->SetBounds(content_bounds
.MakeStatusBar());
4430 WindowSP source_window_sp
= m_window_sp
->FindSubWindow("Source");
4431 WindowSP variables_window_sp
= m_window_sp
->FindSubWindow("Variables");
4432 WindowSP registers_window_sp
= m_window_sp
->FindSubWindow("Registers");
4433 WindowSP threads_window_sp
= m_window_sp
->FindSubWindow("Threads");
4435 Rect threads_bounds
;
4436 Rect source_variables_bounds
;
4437 content_bounds
.VerticalSplitPercentage(0.80, source_variables_bounds
,
4439 if (threads_window_sp
)
4440 threads_window_sp
->SetBounds(threads_bounds
);
4442 source_variables_bounds
= content_bounds
;
4445 Rect variables_registers_bounds
;
4446 source_variables_bounds
.HorizontalSplitPercentage(
4447 0.70, source_bounds
, variables_registers_bounds
);
4448 if (variables_window_sp
|| registers_window_sp
) {
4449 if (variables_window_sp
&& registers_window_sp
) {
4450 Rect variables_bounds
;
4451 Rect registers_bounds
;
4452 variables_registers_bounds
.VerticalSplitPercentage(
4453 0.50, variables_bounds
, registers_bounds
);
4454 variables_window_sp
->SetBounds(variables_bounds
);
4455 registers_window_sp
->SetBounds(registers_bounds
);
4456 } else if (variables_window_sp
) {
4457 variables_window_sp
->SetBounds(variables_registers_bounds
);
4459 registers_window_sp
->SetBounds(variables_registers_bounds
);
4462 source_bounds
= source_variables_bounds
;
4465 source_window_sp
->SetBounds(source_bounds
);
4468 redrawwin(m_window_sp
->get());
4469 m_update_screen
= true;
4473 WindowSP m_window_sp
;
4474 WindowDelegates m_window_delegates
;
4475 SCREEN
*m_screen
= nullptr;
4478 bool m_update_screen
= false;
4481 } // namespace curses
4483 using namespace curses
;
4486 ValueObjectUpdater value
;
4488 // The process stop ID when the children were calculated.
4489 uint32_t children_stop_id
= 0;
4493 bool might_have_children
;
4494 bool expanded
= false;
4495 bool calculated_children
= false;
4496 std::vector
<Row
> children
;
4498 Row(const ValueObjectSP
&v
, Row
*p
)
4499 : value(v
), parent(p
),
4500 might_have_children(v
? v
->MightHaveChildren() : false) {}
4502 size_t GetDepth() const {
4504 return 1 + parent
->GetDepth();
4508 void Expand() { expanded
= true; }
4510 std::vector
<Row
> &GetChildren() {
4511 ProcessSP process_sp
= value
.GetProcessSP();
4512 auto stop_id
= process_sp
->GetStopID();
4513 if (process_sp
&& stop_id
!= children_stop_id
) {
4514 children_stop_id
= stop_id
;
4515 calculated_children
= false;
4517 if (!calculated_children
) {
4519 calculated_children
= true;
4520 ValueObjectSP valobj
= value
.GetSP();
4522 const size_t num_children
= valobj
->GetNumChildren();
4523 for (size_t i
= 0; i
< num_children
; ++i
) {
4524 children
.push_back(Row(valobj
->GetChildAtIndex(i
), this));
4533 calculated_children
= false;
4537 void DrawTree(Window
&window
) {
4539 parent
->DrawTreeForChild(window
, this, 0);
4541 if (might_have_children
&&
4542 (!calculated_children
|| !GetChildren().empty())) {
4543 // It we can get UTF8 characters to work we should try to use the
4544 // "symbol" UTF8 string below
4545 // const char *symbol = "";
4546 // if (row.expanded)
4547 // symbol = "\xe2\x96\xbd ";
4549 // symbol = "\xe2\x96\xb7 ";
4550 // window.PutCString (symbol);
4552 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v'
4553 // or '>' character...
4555 // window.PutChar (ACS_DARROW);
4557 // window.PutChar (ACS_RARROW);
4558 // Since we can't find any good looking right arrow/down arrow symbols,
4559 // just use a diamond...
4560 window
.PutChar(ACS_DIAMOND
);
4561 window
.PutChar(ACS_HLINE
);
4565 void DrawTreeForChild(Window
&window
, Row
*child
, uint32_t reverse_depth
) {
4567 parent
->DrawTreeForChild(window
, this, reverse_depth
+ 1);
4569 if (&GetChildren().back() == child
) {
4571 if (reverse_depth
== 0) {
4572 window
.PutChar(ACS_LLCORNER
);
4573 window
.PutChar(ACS_HLINE
);
4575 window
.PutChar(' ');
4576 window
.PutChar(' ');
4579 if (reverse_depth
== 0) {
4580 window
.PutChar(ACS_LTEE
);
4581 window
.PutChar(ACS_HLINE
);
4583 window
.PutChar(ACS_VLINE
);
4584 window
.PutChar(' ');
4590 struct DisplayOptions
{
4596 class TreeDelegate
{
4598 TreeDelegate() = default;
4599 virtual ~TreeDelegate() = default;
4601 virtual void TreeDelegateDrawTreeItem(TreeItem
&item
, Window
&window
) = 0;
4602 virtual void TreeDelegateGenerateChildren(TreeItem
&item
) = 0;
4603 virtual void TreeDelegateUpdateSelection(TreeItem
&root
, int &selection_index
,
4604 TreeItem
*&selected_item
) {}
4605 // This is invoked when a tree item is selected. If true is returned, the
4606 // views are updated.
4607 virtual bool TreeDelegateItemSelected(TreeItem
&item
) = 0;
4608 virtual bool TreeDelegateExpandRootByDefault() { return false; }
4609 // This is mostly useful for root tree delegates. If false is returned,
4610 // drawing will be skipped completely. This is needed, for instance, in
4611 // skipping drawing of the threads tree if there is no running process.
4612 virtual bool TreeDelegateShouldDraw() { return true; }
4615 typedef std::shared_ptr
<TreeDelegate
> TreeDelegateSP
;
4617 struct TreeItemData
{
4618 TreeItemData(TreeItem
*parent
, TreeDelegate
&delegate
,
4619 bool might_have_children
, bool is_expanded
)
4620 : m_parent(parent
), m_delegate(&delegate
),
4621 m_might_have_children(might_have_children
), m_is_expanded(is_expanded
) {
4626 TreeDelegate
*m_delegate
;
4627 void *m_user_data
= nullptr;
4628 uint64_t m_identifier
= 0;
4630 int m_row_idx
= -1; // Zero based visible row index, -1 if not visible or for
4632 bool m_might_have_children
;
4633 bool m_is_expanded
= false;
4636 class TreeItem
: public TreeItemData
{
4638 TreeItem(TreeItem
*parent
, TreeDelegate
&delegate
, bool might_have_children
)
4639 : TreeItemData(parent
, delegate
, might_have_children
,
4641 ? delegate
.TreeDelegateExpandRootByDefault()
4645 TreeItem(const TreeItem
&) = delete;
4646 TreeItem
&operator=(const TreeItem
&rhs
) = delete;
4648 TreeItem
&operator=(TreeItem
&&rhs
) {
4650 TreeItemData::operator=(std::move(rhs
));
4651 AdoptChildren(rhs
.m_children
);
4656 TreeItem(TreeItem
&&rhs
) : TreeItemData(std::move(rhs
)) {
4657 AdoptChildren(rhs
.m_children
);
4660 size_t GetDepth() const {
4662 return 1 + m_parent
->GetDepth();
4666 int GetRowIndex() const { return m_row_idx
; }
4668 void ClearChildren() { m_children
.clear(); }
4670 void Resize(size_t n
, TreeDelegate
&delegate
, bool might_have_children
) {
4671 if (m_children
.size() >= n
) {
4672 m_children
.erase(m_children
.begin() + n
, m_children
.end());
4675 m_children
.reserve(n
);
4676 std::generate_n(std::back_inserter(m_children
), n
- m_children
.size(),
4677 [&, parent
= this]() {
4678 return TreeItem(parent
, delegate
, might_have_children
);
4682 TreeItem
&operator[](size_t i
) { return m_children
[i
]; }
4684 void SetRowIndex(int row_idx
) { m_row_idx
= row_idx
; }
4686 size_t GetNumChildren() {
4687 m_delegate
->TreeDelegateGenerateChildren(*this);
4688 return m_children
.size();
4691 void ItemWasSelected() { m_delegate
->TreeDelegateItemSelected(*this); }
4693 void CalculateRowIndexes(int &row_idx
) {
4694 SetRowIndex(row_idx
);
4697 const bool expanded
= IsExpanded();
4699 // The root item must calculate its children, or we must calculate the
4700 // number of children if the item is expanded
4701 if (m_parent
== nullptr || expanded
)
4704 for (auto &item
: m_children
) {
4706 item
.CalculateRowIndexes(row_idx
);
4708 item
.SetRowIndex(-1);
4712 TreeItem
*GetParent() { return m_parent
; }
4714 bool IsExpanded() const { return m_is_expanded
; }
4716 void Expand() { m_is_expanded
= true; }
4718 void Unexpand() { m_is_expanded
= false; }
4720 bool Draw(Window
&window
, const int first_visible_row
,
4721 const uint32_t selected_row_idx
, int &row_idx
, int &num_rows_left
) {
4722 if (num_rows_left
<= 0)
4725 if (m_row_idx
>= first_visible_row
) {
4726 window
.MoveCursor(2, row_idx
+ 1);
4729 m_parent
->DrawTreeForChild(window
, this, 0);
4731 if (m_might_have_children
) {
4732 // It we can get UTF8 characters to work we should try to use the
4733 // "symbol" UTF8 string below
4734 // const char *symbol = "";
4735 // if (row.expanded)
4736 // symbol = "\xe2\x96\xbd ";
4738 // symbol = "\xe2\x96\xb7 ";
4739 // window.PutCString (symbol);
4741 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a
4742 // 'v' or '>' character...
4744 // window.PutChar (ACS_DARROW);
4746 // window.PutChar (ACS_RARROW);
4747 // Since we can't find any good looking right arrow/down arrow symbols,
4748 // just use a diamond...
4749 window
.PutChar(ACS_DIAMOND
);
4750 window
.PutChar(ACS_HLINE
);
4752 bool highlight
= (selected_row_idx
== static_cast<size_t>(m_row_idx
)) &&
4756 window
.AttributeOn(A_REVERSE
);
4758 m_delegate
->TreeDelegateDrawTreeItem(*this, window
);
4761 window
.AttributeOff(A_REVERSE
);
4766 if (num_rows_left
<= 0)
4767 return false; // We are done drawing...
4770 for (auto &item
: m_children
) {
4771 // If we displayed all the rows and item.Draw() returns false we are
4772 // done drawing and can exit this for loop
4773 if (!item
.Draw(window
, first_visible_row
, selected_row_idx
, row_idx
,
4778 return num_rows_left
>= 0; // Return true if not done drawing yet
4781 void DrawTreeForChild(Window
&window
, TreeItem
*child
,
4782 uint32_t reverse_depth
) {
4784 m_parent
->DrawTreeForChild(window
, this, reverse_depth
+ 1);
4786 if (&m_children
.back() == child
) {
4788 if (reverse_depth
== 0) {
4789 window
.PutChar(ACS_LLCORNER
);
4790 window
.PutChar(ACS_HLINE
);
4792 window
.PutChar(' ');
4793 window
.PutChar(' ');
4796 if (reverse_depth
== 0) {
4797 window
.PutChar(ACS_LTEE
);
4798 window
.PutChar(ACS_HLINE
);
4800 window
.PutChar(ACS_VLINE
);
4801 window
.PutChar(' ');
4806 TreeItem
*GetItemForRowIndex(uint32_t row_idx
) {
4807 if (static_cast<uint32_t>(m_row_idx
) == row_idx
)
4809 if (m_children
.empty())
4812 for (auto &item
: m_children
) {
4813 TreeItem
*selected_item_ptr
= item
.GetItemForRowIndex(row_idx
);
4814 if (selected_item_ptr
)
4815 return selected_item_ptr
;
4821 void *GetUserData() const { return m_user_data
; }
4823 void SetUserData(void *user_data
) { m_user_data
= user_data
; }
4825 uint64_t GetIdentifier() const { return m_identifier
; }
4827 void SetIdentifier(uint64_t identifier
) { m_identifier
= identifier
; }
4829 const std::string
&GetText() const { return m_text
; }
4831 void SetText(const char *text
) {
4832 if (text
== nullptr) {
4839 void SetMightHaveChildren(bool b
) { m_might_have_children
= b
; }
4842 void AdoptChildren(std::vector
<TreeItem
> &children
) {
4843 m_children
= std::move(children
);
4844 for (auto &child
: m_children
)
4845 child
.m_parent
= this;
4848 std::vector
<TreeItem
> m_children
;
4851 class TreeWindowDelegate
: public WindowDelegate
{
4853 TreeWindowDelegate(Debugger
&debugger
, const TreeDelegateSP
&delegate_sp
)
4854 : m_debugger(debugger
), m_delegate_sp(delegate_sp
),
4855 m_root(nullptr, *delegate_sp
, true) {}
4857 int NumVisibleRows() const { return m_max_y
- m_min_y
; }
4859 bool WindowDelegateDraw(Window
&window
, bool force
) override
{
4862 m_max_x
= window
.GetWidth() - 1;
4863 m_max_y
= window
.GetHeight() - 1;
4866 window
.DrawTitleBox(window
.GetName());
4868 if (!m_delegate_sp
->TreeDelegateShouldDraw()) {
4869 m_selected_item
= nullptr;
4873 const int num_visible_rows
= NumVisibleRows();
4875 m_root
.CalculateRowIndexes(m_num_rows
);
4876 m_delegate_sp
->TreeDelegateUpdateSelection(m_root
, m_selected_row_idx
,
4879 // If we unexpanded while having something selected our total number of
4880 // rows is less than the num visible rows, then make sure we show all the
4881 // rows by setting the first visible row accordingly.
4882 if (m_first_visible_row
> 0 && m_num_rows
< num_visible_rows
)
4883 m_first_visible_row
= 0;
4885 // Make sure the selected row is always visible
4886 if (m_selected_row_idx
< m_first_visible_row
)
4887 m_first_visible_row
= m_selected_row_idx
;
4888 else if (m_first_visible_row
+ num_visible_rows
<= m_selected_row_idx
)
4889 m_first_visible_row
= m_selected_row_idx
- num_visible_rows
+ 1;
4892 int num_rows_left
= num_visible_rows
;
4893 m_root
.Draw(window
, m_first_visible_row
, m_selected_row_idx
, row_idx
,
4895 // Get the selected row
4896 m_selected_item
= m_root
.GetItemForRowIndex(m_selected_row_idx
);
4898 return true; // Drawing handled
4901 const char *WindowDelegateGetHelpText() override
{
4902 return "Thread window keyboard shortcuts:";
4905 KeyHelp
*WindowDelegateGetKeyHelp() override
{
4906 static curses::KeyHelp g_source_view_key_help
[] = {
4907 {KEY_UP
, "Select previous item"},
4908 {KEY_DOWN
, "Select next item"},
4909 {KEY_RIGHT
, "Expand the selected item"},
4911 "Unexpand the selected item or select parent if not expanded"},
4912 {KEY_PPAGE
, "Page up"},
4913 {KEY_NPAGE
, "Page down"},
4914 {'h', "Show help dialog"},
4915 {' ', "Toggle item expansion"},
4919 return g_source_view_key_help
;
4922 HandleCharResult
WindowDelegateHandleChar(Window
&window
, int c
) override
{
4927 if (m_first_visible_row
> 0) {
4928 if (m_first_visible_row
> m_max_y
)
4929 m_first_visible_row
-= m_max_y
;
4931 m_first_visible_row
= 0;
4932 m_selected_row_idx
= m_first_visible_row
;
4933 m_selected_item
= m_root
.GetItemForRowIndex(m_selected_row_idx
);
4934 if (m_selected_item
)
4935 m_selected_item
->ItemWasSelected();
4942 if (m_num_rows
> m_max_y
) {
4943 if (m_first_visible_row
+ m_max_y
< m_num_rows
) {
4944 m_first_visible_row
+= m_max_y
;
4945 m_selected_row_idx
= m_first_visible_row
;
4946 m_selected_item
= m_root
.GetItemForRowIndex(m_selected_row_idx
);
4947 if (m_selected_item
)
4948 m_selected_item
->ItemWasSelected();
4954 if (m_selected_row_idx
> 0) {
4955 --m_selected_row_idx
;
4956 m_selected_item
= m_root
.GetItemForRowIndex(m_selected_row_idx
);
4957 if (m_selected_item
)
4958 m_selected_item
->ItemWasSelected();
4963 if (m_selected_row_idx
+ 1 < m_num_rows
) {
4964 ++m_selected_row_idx
;
4965 m_selected_item
= m_root
.GetItemForRowIndex(m_selected_row_idx
);
4966 if (m_selected_item
)
4967 m_selected_item
->ItemWasSelected();
4972 if (m_selected_item
) {
4973 if (!m_selected_item
->IsExpanded())
4974 m_selected_item
->Expand();
4979 if (m_selected_item
) {
4980 if (m_selected_item
->IsExpanded())
4981 m_selected_item
->Unexpand();
4982 else if (m_selected_item
->GetParent()) {
4983 m_selected_row_idx
= m_selected_item
->GetParent()->GetRowIndex();
4984 m_selected_item
= m_root
.GetItemForRowIndex(m_selected_row_idx
);
4985 if (m_selected_item
)
4986 m_selected_item
->ItemWasSelected();
4992 // Toggle expansion state when SPACE is pressed
4993 if (m_selected_item
) {
4994 if (m_selected_item
->IsExpanded())
4995 m_selected_item
->Unexpand();
4997 m_selected_item
->Expand();
5002 window
.CreateHelpSubwindow();
5008 return eKeyNotHandled
;
5012 Debugger
&m_debugger
;
5013 TreeDelegateSP m_delegate_sp
;
5015 TreeItem
*m_selected_item
= nullptr;
5017 int m_selected_row_idx
= 0;
5018 int m_first_visible_row
= 0;
5025 // A tree delegate that just draws the text member of the tree item, it doesn't
5026 // have any children or actions.
5027 class TextTreeDelegate
: public TreeDelegate
{
5029 TextTreeDelegate() : TreeDelegate() {}
5031 ~TextTreeDelegate() override
= default;
5033 void TreeDelegateDrawTreeItem(TreeItem
&item
, Window
&window
) override
{
5034 window
.PutCStringTruncated(1, item
.GetText().c_str());
5037 void TreeDelegateGenerateChildren(TreeItem
&item
) override
{}
5039 bool TreeDelegateItemSelected(TreeItem
&item
) override
{ return false; }
5042 class FrameTreeDelegate
: public TreeDelegate
{
5044 FrameTreeDelegate() : TreeDelegate() {
5045 FormatEntity::Parse(
5046 "#${frame.index}: {${function.name}${function.pc-offset}}}", m_format
);
5049 ~FrameTreeDelegate() override
= default;
5051 void TreeDelegateDrawTreeItem(TreeItem
&item
, Window
&window
) override
{
5052 Thread
*thread
= (Thread
*)item
.GetUserData();
5054 const uint64_t frame_idx
= item
.GetIdentifier();
5055 StackFrameSP frame_sp
= thread
->GetStackFrameAtIndex(frame_idx
);
5058 const SymbolContext
&sc
=
5059 frame_sp
->GetSymbolContext(eSymbolContextEverything
);
5060 ExecutionContext
exe_ctx(frame_sp
);
5061 if (FormatEntity::Format(m_format
, strm
, &sc
, &exe_ctx
, nullptr,
5062 nullptr, false, false)) {
5064 window
.PutCStringTruncated(right_pad
, strm
.GetString().str().c_str());
5070 void TreeDelegateGenerateChildren(TreeItem
&item
) override
{
5071 // No children for frames yet...
5074 bool TreeDelegateItemSelected(TreeItem
&item
) override
{
5075 Thread
*thread
= (Thread
*)item
.GetUserData();
5077 thread
->GetProcess()->GetThreadList().SetSelectedThreadByID(
5079 const uint64_t frame_idx
= item
.GetIdentifier();
5080 thread
->SetSelectedFrameByIndex(frame_idx
);
5087 FormatEntity::Entry m_format
;
5090 class ThreadTreeDelegate
: public TreeDelegate
{
5092 ThreadTreeDelegate(Debugger
&debugger
)
5093 : TreeDelegate(), m_debugger(debugger
) {
5094 FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop "
5095 "reason = ${thread.stop-reason}}",
5099 ~ThreadTreeDelegate() override
= default;
5101 ProcessSP
GetProcess() {
5102 return m_debugger
.GetCommandInterpreter()
5103 .GetExecutionContext()
5107 ThreadSP
GetThread(const TreeItem
&item
) {
5108 ProcessSP process_sp
= GetProcess();
5110 return process_sp
->GetThreadList().FindThreadByID(item
.GetIdentifier());
5114 void TreeDelegateDrawTreeItem(TreeItem
&item
, Window
&window
) override
{
5115 ThreadSP thread_sp
= GetThread(item
);
5118 ExecutionContext
exe_ctx(thread_sp
);
5119 if (FormatEntity::Format(m_format
, strm
, nullptr, &exe_ctx
, nullptr,
5120 nullptr, false, false)) {
5122 window
.PutCStringTruncated(right_pad
, strm
.GetString().str().c_str());
5127 void TreeDelegateGenerateChildren(TreeItem
&item
) override
{
5128 ProcessSP process_sp
= GetProcess();
5129 if (process_sp
&& process_sp
->IsAlive()) {
5130 StateType state
= process_sp
->GetState();
5131 if (StateIsStoppedState(state
, true)) {
5132 ThreadSP thread_sp
= GetThread(item
);
5134 if (m_stop_id
== process_sp
->GetStopID() &&
5135 thread_sp
->GetID() == m_tid
)
5136 return; // Children are already up to date
5137 if (!m_frame_delegate_sp
) {
5138 // Always expand the thread item the first time we show it
5139 m_frame_delegate_sp
= std::make_shared
<FrameTreeDelegate
>();
5142 m_stop_id
= process_sp
->GetStopID();
5143 m_tid
= thread_sp
->GetID();
5145 size_t num_frames
= thread_sp
->GetStackFrameCount();
5146 item
.Resize(num_frames
, *m_frame_delegate_sp
, false);
5147 for (size_t i
= 0; i
< num_frames
; ++i
) {
5148 item
[i
].SetUserData(thread_sp
.get());
5149 item
[i
].SetIdentifier(i
);
5155 item
.ClearChildren();
5158 bool TreeDelegateItemSelected(TreeItem
&item
) override
{
5159 ProcessSP process_sp
= GetProcess();
5160 if (process_sp
&& process_sp
->IsAlive()) {
5161 StateType state
= process_sp
->GetState();
5162 if (StateIsStoppedState(state
, true)) {
5163 ThreadSP thread_sp
= GetThread(item
);
5165 ThreadList
&thread_list
= thread_sp
->GetProcess()->GetThreadList();
5166 std::lock_guard
<std::recursive_mutex
> guard(thread_list
.GetMutex());
5167 ThreadSP selected_thread_sp
= thread_list
.GetSelectedThread();
5168 if (selected_thread_sp
->GetID() != thread_sp
->GetID()) {
5169 thread_list
.SetSelectedThreadByID(thread_sp
->GetID());
5179 Debugger
&m_debugger
;
5180 std::shared_ptr
<FrameTreeDelegate
> m_frame_delegate_sp
;
5181 lldb::user_id_t m_tid
= LLDB_INVALID_THREAD_ID
;
5182 uint32_t m_stop_id
= UINT32_MAX
;
5183 FormatEntity::Entry m_format
;
5186 class ThreadsTreeDelegate
: public TreeDelegate
{
5188 ThreadsTreeDelegate(Debugger
&debugger
)
5189 : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger
) {
5190 FormatEntity::Parse("process ${process.id}{, name = ${process.name}}",
5194 ~ThreadsTreeDelegate() override
= default;
5196 ProcessSP
GetProcess() {
5197 return m_debugger
.GetCommandInterpreter()
5198 .GetExecutionContext()
5202 bool TreeDelegateShouldDraw() override
{
5203 ProcessSP process
= GetProcess();
5207 if (StateIsRunningState(process
->GetState()))
5213 void TreeDelegateDrawTreeItem(TreeItem
&item
, Window
&window
) override
{
5214 ProcessSP process_sp
= GetProcess();
5215 if (process_sp
&& process_sp
->IsAlive()) {
5217 ExecutionContext
exe_ctx(process_sp
);
5218 if (FormatEntity::Format(m_format
, strm
, nullptr, &exe_ctx
, nullptr,
5219 nullptr, false, false)) {
5221 window
.PutCStringTruncated(right_pad
, strm
.GetString().str().c_str());
5226 void TreeDelegateGenerateChildren(TreeItem
&item
) override
{
5227 ProcessSP process_sp
= GetProcess();
5228 m_update_selection
= false;
5229 if (process_sp
&& process_sp
->IsAlive()) {
5230 StateType state
= process_sp
->GetState();
5231 if (StateIsStoppedState(state
, true)) {
5232 const uint32_t stop_id
= process_sp
->GetStopID();
5233 if (m_stop_id
== stop_id
)
5234 return; // Children are already up to date
5236 m_stop_id
= stop_id
;
5237 m_update_selection
= true;
5239 if (!m_thread_delegate_sp
) {
5240 // Always expand the thread item the first time we show it
5242 m_thread_delegate_sp
=
5243 std::make_shared
<ThreadTreeDelegate
>(m_debugger
);
5246 ThreadList
&threads
= process_sp
->GetThreadList();
5247 std::lock_guard
<std::recursive_mutex
> guard(threads
.GetMutex());
5248 ThreadSP selected_thread
= threads
.GetSelectedThread();
5249 size_t num_threads
= threads
.GetSize();
5250 item
.Resize(num_threads
, *m_thread_delegate_sp
, false);
5251 for (size_t i
= 0; i
< num_threads
; ++i
) {
5252 ThreadSP thread
= threads
.GetThreadAtIndex(i
);
5253 item
[i
].SetIdentifier(thread
->GetID());
5254 item
[i
].SetMightHaveChildren(true);
5255 if (selected_thread
->GetID() == thread
->GetID())
5261 item
.ClearChildren();
5264 void TreeDelegateUpdateSelection(TreeItem
&root
, int &selection_index
,
5265 TreeItem
*&selected_item
) override
{
5266 if (!m_update_selection
)
5269 ProcessSP process_sp
= GetProcess();
5270 if (!(process_sp
&& process_sp
->IsAlive()))
5273 StateType state
= process_sp
->GetState();
5274 if (!StateIsStoppedState(state
, true))
5277 ThreadList
&threads
= process_sp
->GetThreadList();
5278 std::lock_guard
<std::recursive_mutex
> guard(threads
.GetMutex());
5279 ThreadSP selected_thread
= threads
.GetSelectedThread();
5280 size_t num_threads
= threads
.GetSize();
5281 for (size_t i
= 0; i
< num_threads
; ++i
) {
5282 ThreadSP thread
= threads
.GetThreadAtIndex(i
);
5283 if (selected_thread
->GetID() == thread
->GetID()) {
5285 &root
[i
][thread
->GetSelectedFrameIndex(SelectMostRelevantFrame
)];
5286 selection_index
= selected_item
->GetRowIndex();
5292 bool TreeDelegateItemSelected(TreeItem
&item
) override
{ return false; }
5294 bool TreeDelegateExpandRootByDefault() override
{ return true; }
5297 std::shared_ptr
<ThreadTreeDelegate
> m_thread_delegate_sp
;
5298 Debugger
&m_debugger
;
5299 uint32_t m_stop_id
= UINT32_MAX
;
5300 bool m_update_selection
= false;
5301 FormatEntity::Entry m_format
;
5304 class BreakpointLocationTreeDelegate
: public TreeDelegate
{
5306 BreakpointLocationTreeDelegate(Debugger
&debugger
)
5307 : TreeDelegate(), m_debugger(debugger
) {}
5309 ~BreakpointLocationTreeDelegate() override
= default;
5311 Process
*GetProcess() {
5312 ExecutionContext
exe_ctx(
5313 m_debugger
.GetCommandInterpreter().GetExecutionContext());
5314 return exe_ctx
.GetProcessPtr();
5317 BreakpointLocationSP
GetBreakpointLocation(const TreeItem
&item
) {
5318 Breakpoint
*breakpoint
= (Breakpoint
*)item
.GetUserData();
5319 return breakpoint
->GetLocationAtIndex(item
.GetIdentifier());
5322 void TreeDelegateDrawTreeItem(TreeItem
&item
, Window
&window
) override
{
5323 BreakpointLocationSP breakpoint_location
= GetBreakpointLocation(item
);
5324 Process
*process
= GetProcess();
5325 StreamString stream
;
5326 stream
.Printf("%i.%i: ", breakpoint_location
->GetBreakpoint().GetID(),
5327 breakpoint_location
->GetID());
5328 Address address
= breakpoint_location
->GetAddress();
5329 address
.Dump(&stream
, process
, Address::DumpStyleResolvedDescription
,
5330 Address::DumpStyleInvalid
);
5331 window
.PutCStringTruncated(1, stream
.GetString().str().c_str());
5334 StringList
ComputeDetailsList(BreakpointLocationSP breakpoint_location
) {
5337 Address address
= breakpoint_location
->GetAddress();
5338 SymbolContext symbol_context
;
5339 address
.CalculateSymbolContext(&symbol_context
);
5341 if (symbol_context
.module_sp
) {
5342 StreamString module_stream
;
5343 module_stream
.PutCString("module = ");
5344 symbol_context
.module_sp
->GetFileSpec().Dump(
5345 module_stream
.AsRawOstream());
5346 details
.AppendString(module_stream
.GetString());
5349 if (symbol_context
.comp_unit
!= nullptr) {
5350 StreamString compile_unit_stream
;
5351 compile_unit_stream
.PutCString("compile unit = ");
5352 symbol_context
.comp_unit
->GetPrimaryFile().GetFilename().Dump(
5353 &compile_unit_stream
);
5354 details
.AppendString(compile_unit_stream
.GetString());
5356 if (symbol_context
.function
!= nullptr) {
5357 StreamString function_stream
;
5358 function_stream
.PutCString("function = ");
5359 function_stream
.PutCString(
5360 symbol_context
.function
->GetName().AsCString("<unknown>"));
5361 details
.AppendString(function_stream
.GetString());
5364 if (symbol_context
.line_entry
.line
> 0) {
5365 StreamString location_stream
;
5366 location_stream
.PutCString("location = ");
5367 symbol_context
.line_entry
.DumpStopContext(&location_stream
, true);
5368 details
.AppendString(location_stream
.GetString());
5372 if (symbol_context
.symbol
) {
5373 StreamString symbol_stream
;
5374 if (breakpoint_location
->IsReExported())
5375 symbol_stream
.PutCString("re-exported target = ");
5377 symbol_stream
.PutCString("symbol = ");
5378 symbol_stream
.PutCString(
5379 symbol_context
.symbol
->GetName().AsCString("<unknown>"));
5380 details
.AppendString(symbol_stream
.GetString());
5384 Process
*process
= GetProcess();
5386 StreamString address_stream
;
5387 address
.Dump(&address_stream
, process
, Address::DumpStyleLoadAddress
,
5388 Address::DumpStyleModuleWithFileAddress
);
5389 details
.AppendString(address_stream
.GetString());
5391 BreakpointSiteSP breakpoint_site
= breakpoint_location
->GetBreakpointSite();
5392 if (breakpoint_location
->IsIndirect() && breakpoint_site
) {
5393 Address resolved_address
;
5394 resolved_address
.SetLoadAddress(breakpoint_site
->GetLoadAddress(),
5395 &breakpoint_location
->GetTarget());
5396 Symbol
*resolved_symbol
= resolved_address
.CalculateSymbolContextSymbol();
5397 if (resolved_symbol
) {
5398 StreamString indirect_target_stream
;
5399 indirect_target_stream
.PutCString("indirect target = ");
5400 indirect_target_stream
.PutCString(
5401 resolved_symbol
->GetName().GetCString());
5402 details
.AppendString(indirect_target_stream
.GetString());
5406 bool is_resolved
= breakpoint_location
->IsResolved();
5407 StreamString resolved_stream
;
5408 resolved_stream
.Printf("resolved = %s", is_resolved
? "true" : "false");
5409 details
.AppendString(resolved_stream
.GetString());
5411 bool is_hardware
= is_resolved
&& breakpoint_site
->IsHardware();
5412 StreamString hardware_stream
;
5413 hardware_stream
.Printf("hardware = %s", is_hardware
? "true" : "false");
5414 details
.AppendString(hardware_stream
.GetString());
5416 StreamString hit_count_stream
;
5417 hit_count_stream
.Printf("hit count = %-4u",
5418 breakpoint_location
->GetHitCount());
5419 details
.AppendString(hit_count_stream
.GetString());
5424 void TreeDelegateGenerateChildren(TreeItem
&item
) override
{
5425 BreakpointLocationSP breakpoint_location
= GetBreakpointLocation(item
);
5426 StringList details
= ComputeDetailsList(breakpoint_location
);
5428 if (!m_string_delegate_sp
)
5429 m_string_delegate_sp
= std::make_shared
<TextTreeDelegate
>();
5431 item
.Resize(details
.GetSize(), *m_string_delegate_sp
, false);
5432 for (size_t i
= 0; i
< details
.GetSize(); i
++) {
5433 item
[i
].SetText(details
.GetStringAtIndex(i
));
5437 bool TreeDelegateItemSelected(TreeItem
&item
) override
{ return false; }
5440 Debugger
&m_debugger
;
5441 std::shared_ptr
<TextTreeDelegate
> m_string_delegate_sp
;
5444 class BreakpointTreeDelegate
: public TreeDelegate
{
5446 BreakpointTreeDelegate(Debugger
&debugger
)
5447 : TreeDelegate(), m_debugger(debugger
),
5448 m_breakpoint_location_delegate_sp() {}
5450 ~BreakpointTreeDelegate() override
= default;
5452 BreakpointSP
GetBreakpoint(const TreeItem
&item
) {
5453 TargetSP target
= m_debugger
.GetSelectedTarget();
5454 BreakpointList
&breakpoints
= target
->GetBreakpointList(false);
5455 return breakpoints
.GetBreakpointAtIndex(item
.GetIdentifier());
5458 void TreeDelegateDrawTreeItem(TreeItem
&item
, Window
&window
) override
{
5459 BreakpointSP breakpoint
= GetBreakpoint(item
);
5460 StreamString stream
;
5461 stream
.Format("{0}: ", breakpoint
->GetID());
5462 breakpoint
->GetResolverDescription(&stream
);
5463 breakpoint
->GetFilterDescription(&stream
);
5464 window
.PutCStringTruncated(1, stream
.GetString().str().c_str());
5467 void TreeDelegateGenerateChildren(TreeItem
&item
) override
{
5468 BreakpointSP breakpoint
= GetBreakpoint(item
);
5470 if (!m_breakpoint_location_delegate_sp
)
5471 m_breakpoint_location_delegate_sp
=
5472 std::make_shared
<BreakpointLocationTreeDelegate
>(m_debugger
);
5474 item
.Resize(breakpoint
->GetNumLocations(),
5475 *m_breakpoint_location_delegate_sp
, true);
5476 for (size_t i
= 0; i
< breakpoint
->GetNumLocations(); i
++) {
5477 item
[i
].SetIdentifier(i
);
5478 item
[i
].SetUserData(breakpoint
.get());
5482 bool TreeDelegateItemSelected(TreeItem
&item
) override
{ return false; }
5485 Debugger
&m_debugger
;
5486 std::shared_ptr
<BreakpointLocationTreeDelegate
>
5487 m_breakpoint_location_delegate_sp
;
5490 class BreakpointsTreeDelegate
: public TreeDelegate
{
5492 BreakpointsTreeDelegate(Debugger
&debugger
)
5493 : TreeDelegate(), m_debugger(debugger
), m_breakpoint_delegate_sp() {}
5495 ~BreakpointsTreeDelegate() override
= default;
5497 bool TreeDelegateShouldDraw() override
{
5498 TargetSP target
= m_debugger
.GetSelectedTarget();
5505 void TreeDelegateDrawTreeItem(TreeItem
&item
, Window
&window
) override
{
5506 window
.PutCString("Breakpoints");
5509 void TreeDelegateGenerateChildren(TreeItem
&item
) override
{
5510 TargetSP target
= m_debugger
.GetSelectedTarget();
5512 BreakpointList
&breakpoints
= target
->GetBreakpointList(false);
5513 std::unique_lock
<std::recursive_mutex
> lock
;
5514 breakpoints
.GetListMutex(lock
);
5516 if (!m_breakpoint_delegate_sp
)
5517 m_breakpoint_delegate_sp
=
5518 std::make_shared
<BreakpointTreeDelegate
>(m_debugger
);
5520 item
.Resize(breakpoints
.GetSize(), *m_breakpoint_delegate_sp
, true);
5521 for (size_t i
= 0; i
< breakpoints
.GetSize(); i
++) {
5522 item
[i
].SetIdentifier(i
);
5526 bool TreeDelegateItemSelected(TreeItem
&item
) override
{ return false; }
5528 bool TreeDelegateExpandRootByDefault() override
{ return true; }
5531 Debugger
&m_debugger
;
5532 std::shared_ptr
<BreakpointTreeDelegate
> m_breakpoint_delegate_sp
;
5535 class ValueObjectListDelegate
: public WindowDelegate
{
5537 ValueObjectListDelegate() : m_rows() {}
5539 ValueObjectListDelegate(ValueObjectList
&valobj_list
) : m_rows() {
5540 SetValues(valobj_list
);
5543 ~ValueObjectListDelegate() override
= default;
5545 void SetValues(ValueObjectList
&valobj_list
) {
5546 m_selected_row
= nullptr;
5547 m_selected_row_idx
= 0;
5548 m_first_visible_row
= 0;
5551 for (auto &valobj_sp
: valobj_list
.GetObjects())
5552 m_rows
.push_back(Row(valobj_sp
, nullptr));
5555 bool WindowDelegateDraw(Window
&window
, bool force
) override
{
5559 m_max_x
= window
.GetWidth() - 1;
5560 m_max_y
= window
.GetHeight() - 1;
5563 window
.DrawTitleBox(window
.GetName());
5565 const int num_visible_rows
= NumVisibleRows();
5566 const int num_rows
= CalculateTotalNumberRows(m_rows
);
5568 // If we unexpanded while having something selected our total number of
5569 // rows is less than the num visible rows, then make sure we show all the
5570 // rows by setting the first visible row accordingly.
5571 if (m_first_visible_row
> 0 && num_rows
< num_visible_rows
)
5572 m_first_visible_row
= 0;
5574 // Make sure the selected row is always visible
5575 if (m_selected_row_idx
< m_first_visible_row
)
5576 m_first_visible_row
= m_selected_row_idx
;
5577 else if (m_first_visible_row
+ num_visible_rows
<= m_selected_row_idx
)
5578 m_first_visible_row
= m_selected_row_idx
- num_visible_rows
+ 1;
5580 DisplayRows(window
, m_rows
, g_options
);
5582 // Get the selected row
5583 m_selected_row
= GetRowForRowIndex(m_selected_row_idx
);
5584 // Keep the cursor on the selected row so the highlight and the cursor are
5585 // always on the same line
5587 window
.MoveCursor(m_selected_row
->x
, m_selected_row
->y
);
5589 return true; // Drawing handled
5592 KeyHelp
*WindowDelegateGetKeyHelp() override
{
5593 static curses::KeyHelp g_source_view_key_help
[] = {
5594 {KEY_UP
, "Select previous item"},
5595 {KEY_DOWN
, "Select next item"},
5596 {KEY_RIGHT
, "Expand selected item"},
5597 {KEY_LEFT
, "Unexpand selected item or select parent if not expanded"},
5598 {KEY_PPAGE
, "Page up"},
5599 {KEY_NPAGE
, "Page down"},
5600 {'A', "Format as annotated address"},
5601 {'b', "Format as binary"},
5602 {'B', "Format as hex bytes with ASCII"},
5603 {'c', "Format as character"},
5604 {'d', "Format as a signed integer"},
5605 {'D', "Format selected value using the default format for the type"},
5606 {'f', "Format as float"},
5607 {'h', "Show help dialog"},
5608 {'i', "Format as instructions"},
5609 {'o', "Format as octal"},
5610 {'p', "Format as pointer"},
5611 {'s', "Format as C string"},
5612 {'t', "Toggle showing/hiding type names"},
5613 {'u', "Format as an unsigned integer"},
5614 {'x', "Format as hex"},
5615 {'X', "Format as uppercase hex"},
5616 {' ', "Toggle item expansion"},
5620 return g_source_view_key_help
;
5623 HandleCharResult
WindowDelegateHandleChar(Window
&window
, int c
) override
{
5639 // Change the format for the currently selected item
5640 if (m_selected_row
) {
5641 auto valobj_sp
= m_selected_row
->value
.GetSP();
5643 valobj_sp
->SetFormat(FormatForChar(c
));
5648 // Toggle showing type names
5649 g_options
.show_types
= !g_options
.show_types
;
5655 if (m_first_visible_row
> 0) {
5656 if (static_cast<int>(m_first_visible_row
) > m_max_y
)
5657 m_first_visible_row
-= m_max_y
;
5659 m_first_visible_row
= 0;
5660 m_selected_row_idx
= m_first_visible_row
;
5667 if (m_num_rows
> static_cast<size_t>(m_max_y
)) {
5668 if (m_first_visible_row
+ m_max_y
< m_num_rows
) {
5669 m_first_visible_row
+= m_max_y
;
5670 m_selected_row_idx
= m_first_visible_row
;
5676 if (m_selected_row_idx
> 0)
5677 --m_selected_row_idx
;
5681 if (m_selected_row_idx
+ 1 < m_num_rows
)
5682 ++m_selected_row_idx
;
5686 if (m_selected_row
) {
5687 if (!m_selected_row
->expanded
)
5688 m_selected_row
->Expand();
5693 if (m_selected_row
) {
5694 if (m_selected_row
->expanded
)
5695 m_selected_row
->Unexpand();
5696 else if (m_selected_row
->parent
)
5697 m_selected_row_idx
= m_selected_row
->parent
->row_idx
;
5702 // Toggle expansion state when SPACE is pressed
5703 if (m_selected_row
) {
5704 if (m_selected_row
->expanded
)
5705 m_selected_row
->Unexpand();
5707 m_selected_row
->Expand();
5712 window
.CreateHelpSubwindow();
5718 return eKeyNotHandled
;
5722 std::vector
<Row
> m_rows
;
5723 Row
*m_selected_row
= nullptr;
5724 uint32_t m_selected_row_idx
= 0;
5725 uint32_t m_first_visible_row
= 0;
5726 uint32_t m_num_rows
= 0;
5732 static Format
FormatForChar(int c
) {
5737 return eFormatHexUppercase
;
5739 return eFormatOctal
;
5741 return eFormatCString
;
5743 return eFormatUnsigned
;
5745 return eFormatDecimal
;
5747 return eFormatDefault
;
5749 return eFormatInstruction
;
5751 return eFormatAddressInfo
;
5753 return eFormatPointer
;
5757 return eFormatBinary
;
5759 return eFormatBytesWithASCII
;
5761 return eFormatFloat
;
5763 return eFormatDefault
;
5766 bool DisplayRowObject(Window
&window
, Row
&row
, DisplayOptions
&options
,
5767 bool highlight
, bool last_child
) {
5768 ValueObject
*valobj
= row
.value
.GetSP().get();
5770 if (valobj
== nullptr)
5773 const char *type_name
=
5774 options
.show_types
? valobj
->GetTypeName().GetCString() : nullptr;
5775 const char *name
= valobj
->GetName().GetCString();
5776 const char *value
= valobj
->GetValueAsCString();
5777 const char *summary
= valobj
->GetSummaryAsCString();
5779 window
.MoveCursor(row
.x
, row
.y
);
5781 row
.DrawTree(window
);
5784 window
.AttributeOn(A_REVERSE
);
5786 if (type_name
&& type_name
[0])
5787 window
.PrintfTruncated(1, "(%s) ", type_name
);
5789 if (name
&& name
[0])
5790 window
.PutCStringTruncated(1, name
);
5792 attr_t changd_attr
= 0;
5793 if (valobj
->GetValueDidChange())
5794 changd_attr
= COLOR_PAIR(RedOnBlack
) | A_BOLD
;
5796 if (value
&& value
[0]) {
5797 window
.PutCStringTruncated(1, " = ");
5799 window
.AttributeOn(changd_attr
);
5800 window
.PutCStringTruncated(1, value
);
5802 window
.AttributeOff(changd_attr
);
5805 if (summary
&& summary
[0]) {
5806 window
.PutCStringTruncated(1, " ");
5808 window
.AttributeOn(changd_attr
);
5809 window
.PutCStringTruncated(1, summary
);
5811 window
.AttributeOff(changd_attr
);
5815 window
.AttributeOff(A_REVERSE
);
5820 void DisplayRows(Window
&window
, std::vector
<Row
> &rows
,
5821 DisplayOptions
&options
) {
5825 bool window_is_active
= window
.IsActive();
5826 for (auto &row
: rows
) {
5827 const bool last_child
= row
.parent
&& &rows
[rows
.size() - 1] == &row
;
5828 // Save the row index in each Row structure
5829 row
.row_idx
= m_num_rows
;
5830 if ((m_num_rows
>= m_first_visible_row
) &&
5831 ((m_num_rows
- m_first_visible_row
) <
5832 static_cast<size_t>(NumVisibleRows()))) {
5834 row
.y
= m_num_rows
- m_first_visible_row
+ 1;
5835 if (DisplayRowObject(window
, row
, options
,
5837 m_num_rows
== m_selected_row_idx
,
5851 auto &children
= row
.GetChildren();
5852 if (!children
.empty()) {
5853 DisplayRows(window
, children
, options
);
5859 int CalculateTotalNumberRows(std::vector
<Row
> &rows
) {
5861 for (auto &row
: rows
) {
5864 row_count
+= CalculateTotalNumberRows(row
.GetChildren());
5869 static Row
*GetRowForRowIndexImpl(std::vector
<Row
> &rows
, size_t &row_index
) {
5870 for (auto &row
: rows
) {
5876 auto &children
= row
.GetChildren();
5877 if (!children
.empty()) {
5878 Row
*result
= GetRowForRowIndexImpl(children
, row_index
);
5888 Row
*GetRowForRowIndex(size_t row_index
) {
5889 return GetRowForRowIndexImpl(m_rows
, row_index
);
5892 int NumVisibleRows() const { return m_max_y
- m_min_y
; }
5894 static DisplayOptions g_options
;
5897 class FrameVariablesWindowDelegate
: public ValueObjectListDelegate
{
5899 FrameVariablesWindowDelegate(Debugger
&debugger
)
5900 : ValueObjectListDelegate(), m_debugger(debugger
) {}
5902 ~FrameVariablesWindowDelegate() override
= default;
5904 const char *WindowDelegateGetHelpText() override
{
5905 return "Frame variable window keyboard shortcuts:";
5908 bool WindowDelegateDraw(Window
&window
, bool force
) override
{
5909 ExecutionContext
exe_ctx(
5910 m_debugger
.GetCommandInterpreter().GetExecutionContext());
5911 Process
*process
= exe_ctx
.GetProcessPtr();
5912 Block
*frame_block
= nullptr;
5913 StackFrame
*frame
= nullptr;
5916 StateType state
= process
->GetState();
5917 if (StateIsStoppedState(state
, true)) {
5918 frame
= exe_ctx
.GetFramePtr();
5920 frame_block
= frame
->GetFrameBlock();
5921 } else if (StateIsRunningState(state
)) {
5922 return true; // Don't do any updating when we are running
5926 ValueObjectList local_values
;
5928 // Only update the variables if they have changed
5929 if (m_frame_block
!= frame_block
) {
5930 m_frame_block
= frame_block
;
5932 VariableList
*locals
= frame
->GetVariableList(true, nullptr);
5934 const DynamicValueType use_dynamic
= eDynamicDontRunTarget
;
5935 for (const VariableSP
&local_sp
: *locals
) {
5936 ValueObjectSP value_sp
=
5937 frame
->GetValueObjectForFrameVariable(local_sp
, use_dynamic
);
5939 ValueObjectSP synthetic_value_sp
= value_sp
->GetSyntheticValue();
5940 if (synthetic_value_sp
)
5941 local_values
.Append(synthetic_value_sp
);
5943 local_values
.Append(value_sp
);
5946 // Update the values
5947 SetValues(local_values
);
5951 m_frame_block
= nullptr;
5952 // Update the values with an empty list if there is no frame
5953 SetValues(local_values
);
5956 return ValueObjectListDelegate::WindowDelegateDraw(window
, force
);
5960 Debugger
&m_debugger
;
5961 Block
*m_frame_block
= nullptr;
5964 class RegistersWindowDelegate
: public ValueObjectListDelegate
{
5966 RegistersWindowDelegate(Debugger
&debugger
)
5967 : ValueObjectListDelegate(), m_debugger(debugger
) {}
5969 ~RegistersWindowDelegate() override
= default;
5971 const char *WindowDelegateGetHelpText() override
{
5972 return "Register window keyboard shortcuts:";
5975 bool WindowDelegateDraw(Window
&window
, bool force
) override
{
5976 ExecutionContext
exe_ctx(
5977 m_debugger
.GetCommandInterpreter().GetExecutionContext());
5978 StackFrame
*frame
= exe_ctx
.GetFramePtr();
5980 ValueObjectList value_list
;
5982 if (frame
->GetStackID() != m_stack_id
) {
5983 m_stack_id
= frame
->GetStackID();
5984 RegisterContextSP
reg_ctx(frame
->GetRegisterContext());
5986 const uint32_t num_sets
= reg_ctx
->GetRegisterSetCount();
5987 for (uint32_t set_idx
= 0; set_idx
< num_sets
; ++set_idx
) {
5989 ValueObjectRegisterSet::Create(frame
, reg_ctx
, set_idx
));
5992 SetValues(value_list
);
5995 Process
*process
= exe_ctx
.GetProcessPtr();
5996 if (process
&& process
->IsAlive())
5997 return true; // Don't do any updating if we are running
5999 // Update the values with an empty list if there is no process or the
6000 // process isn't alive anymore
6001 SetValues(value_list
);
6004 return ValueObjectListDelegate::WindowDelegateDraw(window
, force
);
6008 Debugger
&m_debugger
;
6012 static const char *CursesKeyToCString(int ch
) {
6013 static char g_desc
[32];
6014 if (ch
>= KEY_F0
&& ch
< KEY_F0
+ 64) {
6015 snprintf(g_desc
, sizeof(g_desc
), "F%u", ch
- KEY_F0
);
6032 return "delete-line";
6034 return "insert-line";
6036 return "delete-char";
6038 return "insert-char";
6042 return "clear-to-eos";
6044 return "clear-to-eol";
6046 return "scroll-forward";
6048 return "scroll-backward";
6058 return "clear-all-tabs";
6064 return "lower-left key";
6066 return "upper left of keypad";
6068 return "upper right of keypad";
6070 return "center of keypad";
6072 return "lower left of keypad";
6074 return "lower right of keypad";
6076 return "back-tab key";
6080 return "cancel key";
6084 return "command key";
6088 return "create key";
6100 return "message key";
6108 return "options key";
6110 return "previous key";
6114 return "reference key";
6116 return "refresh key";
6118 return "replace key";
6120 return "restart key";
6122 return "resume key";
6126 return "shifted begin key";
6128 return "shifted cancel key";
6130 return "shifted command key";
6132 return "shifted copy key";
6134 return "shifted create key";
6136 return "shifted delete-character key";
6138 return "shifted delete-line key";
6140 return "select key";
6142 return "shifted end key";
6144 return "shifted clear-to-end-of-line key";
6146 return "shifted exit key";
6148 return "shifted find key";
6150 return "shifted help key";
6152 return "shifted home key";
6154 return "shifted insert-character key";
6156 return "shifted left-arrow key";
6158 return "shifted message key";
6160 return "shifted move key";
6162 return "shifted next key";
6164 return "shifted options key";
6166 return "shifted previous key";
6168 return "shifted print key";
6170 return "shifted redo key";
6172 return "shifted replace key";
6174 return "shifted right-arrow key";
6176 return "shifted resume key";
6178 return "shifted save key";
6180 return "shifted suspend key";
6182 return "shifted undo key";
6184 return "suspend key";
6188 return "Mouse event has occurred";
6190 return "Terminal resize event";
6193 return "We were interrupted by an event";
6204 if (llvm::isPrint(ch
))
6205 snprintf(g_desc
, sizeof(g_desc
), "%c", ch
);
6207 snprintf(g_desc
, sizeof(g_desc
), "\\x%2.2x", ch
);
6213 HelpDialogDelegate::HelpDialogDelegate(const char *text
,
6214 KeyHelp
*key_help_array
)
6216 if (text
&& text
[0]) {
6217 m_text
.SplitIntoLines(text
);
6218 m_text
.AppendString("");
6220 if (key_help_array
) {
6221 for (KeyHelp
*key
= key_help_array
; key
->ch
; ++key
) {
6222 StreamString key_description
;
6223 key_description
.Printf("%10s - %s", CursesKeyToCString(key
->ch
),
6225 m_text
.AppendString(key_description
.GetString());
6230 HelpDialogDelegate::~HelpDialogDelegate() = default;
6232 bool HelpDialogDelegate::WindowDelegateDraw(Window
&window
, bool force
) {
6234 const int window_height
= window
.GetHeight();
6237 const int min_y
= y
;
6238 const int max_y
= window_height
- 1 - y
;
6239 const size_t num_visible_lines
= max_y
- min_y
+ 1;
6240 const size_t num_lines
= m_text
.GetSize();
6241 const char *bottom_message
;
6242 if (num_lines
<= num_visible_lines
)
6243 bottom_message
= "Press any key to exit";
6245 bottom_message
= "Use arrows to scroll, any other key to exit";
6246 window
.DrawTitleBox(window
.GetName(), bottom_message
);
6247 while (y
<= max_y
) {
6248 window
.MoveCursor(x
, y
);
6249 window
.PutCStringTruncated(
6250 1, m_text
.GetStringAtIndex(m_first_visible_line
+ y
- min_y
));
6256 HandleCharResult
HelpDialogDelegate::WindowDelegateHandleChar(Window
&window
,
6259 const size_t num_lines
= m_text
.GetSize();
6260 const size_t num_visible_lines
= window
.GetHeight() - 2;
6262 if (num_lines
<= num_visible_lines
) {
6264 // If we have all lines visible and don't need scrolling, then any key
6265 // press will cause us to exit
6269 if (m_first_visible_line
> 0)
6270 --m_first_visible_line
;
6274 if (m_first_visible_line
+ num_visible_lines
< num_lines
)
6275 ++m_first_visible_line
;
6280 if (m_first_visible_line
> 0) {
6281 if (static_cast<size_t>(m_first_visible_line
) >= num_visible_lines
)
6282 m_first_visible_line
-= num_visible_lines
;
6284 m_first_visible_line
= 0;
6290 if (m_first_visible_line
+ num_visible_lines
< num_lines
) {
6291 m_first_visible_line
+= num_visible_lines
;
6292 if (static_cast<size_t>(m_first_visible_line
) > num_lines
)
6293 m_first_visible_line
= num_lines
- num_visible_lines
;
6303 window
.GetParent()->RemoveSubWindow(&window
);
6307 class ApplicationDelegate
: public WindowDelegate
, public MenuDelegate
{
6315 eMenuID_TargetCreate
,
6316 eMenuID_TargetDelete
,
6319 eMenuID_ProcessAttach
,
6320 eMenuID_ProcessDetachResume
,
6321 eMenuID_ProcessDetachSuspended
,
6322 eMenuID_ProcessLaunch
,
6323 eMenuID_ProcessContinue
,
6324 eMenuID_ProcessHalt
,
6325 eMenuID_ProcessKill
,
6328 eMenuID_ThreadStepIn
,
6329 eMenuID_ThreadStepOver
,
6330 eMenuID_ThreadStepOut
,
6333 eMenuID_ViewBacktrace
,
6334 eMenuID_ViewRegisters
,
6336 eMenuID_ViewVariables
,
6337 eMenuID_ViewBreakpoints
,
6343 ApplicationDelegate(Application
&app
, Debugger
&debugger
)
6344 : WindowDelegate(), MenuDelegate(), m_app(app
), m_debugger(debugger
) {}
6346 ~ApplicationDelegate() override
= default;
6348 bool WindowDelegateDraw(Window
&window
, bool force
) override
{
6349 return false; // Drawing not handled, let standard window drawing happen
6352 HandleCharResult
WindowDelegateHandleChar(Window
&window
, int key
) override
{
6355 window
.SelectNextWindowAsActive();
6359 window
.SelectPreviousWindowAsActive();
6363 window
.CreateHelpSubwindow();
6367 return eQuitApplication
;
6372 return eKeyNotHandled
;
6375 const char *WindowDelegateGetHelpText() override
{
6376 return "Welcome to the LLDB curses GUI.\n\n"
6377 "Press the TAB key to change the selected view.\n"
6378 "Each view has its own keyboard shortcuts, press 'h' to open a "
6379 "dialog to display them.\n\n"
6380 "Common key bindings for all views:";
6383 KeyHelp
*WindowDelegateGetKeyHelp() override
{
6384 static curses::KeyHelp g_source_view_key_help
[] = {
6385 {'\t', "Select next view"},
6386 {KEY_BTAB
, "Select previous view"},
6387 {'h', "Show help dialog with view specific key bindings"},
6390 {KEY_UP
, "Select previous"},
6391 {KEY_DOWN
, "Select next"},
6392 {KEY_LEFT
, "Unexpand or select parent"},
6393 {KEY_RIGHT
, "Expand"},
6394 {KEY_PPAGE
, "Page up"},
6395 {KEY_NPAGE
, "Page down"},
6397 return g_source_view_key_help
;
6400 MenuActionResult
MenuDelegateAction(Menu
&menu
) override
{
6401 switch (menu
.GetIdentifier()) {
6402 case eMenuID_TargetCreate
: {
6403 WindowSP main_window_sp
= m_app
.GetMainWindow();
6404 FormDelegateSP form_delegate_sp
=
6405 FormDelegateSP(new TargetCreateFormDelegate(m_debugger
));
6406 Rect bounds
= main_window_sp
->GetCenteredRect(80, 19);
6407 WindowSP form_window_sp
= main_window_sp
->CreateSubWindow(
6408 form_delegate_sp
->GetName().c_str(), bounds
, true);
6409 WindowDelegateSP window_delegate_sp
=
6410 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp
));
6411 form_window_sp
->SetDelegate(window_delegate_sp
);
6412 return MenuActionResult::Handled
;
6414 case eMenuID_ThreadStepIn
: {
6415 ExecutionContext exe_ctx
=
6416 m_debugger
.GetCommandInterpreter().GetExecutionContext();
6417 if (exe_ctx
.HasThreadScope()) {
6418 Process
*process
= exe_ctx
.GetProcessPtr();
6419 if (process
&& process
->IsAlive() &&
6420 StateIsStoppedState(process
->GetState(), true))
6421 exe_ctx
.GetThreadRef().StepIn(true);
6424 return MenuActionResult::Handled
;
6426 case eMenuID_ThreadStepOut
: {
6427 ExecutionContext exe_ctx
=
6428 m_debugger
.GetCommandInterpreter().GetExecutionContext();
6429 if (exe_ctx
.HasThreadScope()) {
6430 Process
*process
= exe_ctx
.GetProcessPtr();
6431 if (process
&& process
->IsAlive() &&
6432 StateIsStoppedState(process
->GetState(), true)) {
6433 Thread
*thread
= exe_ctx
.GetThreadPtr();
6434 uint32_t frame_idx
=
6435 thread
->GetSelectedFrameIndex(SelectMostRelevantFrame
);
6436 exe_ctx
.GetThreadRef().StepOut(frame_idx
);
6440 return MenuActionResult::Handled
;
6442 case eMenuID_ThreadStepOver
: {
6443 ExecutionContext exe_ctx
=
6444 m_debugger
.GetCommandInterpreter().GetExecutionContext();
6445 if (exe_ctx
.HasThreadScope()) {
6446 Process
*process
= exe_ctx
.GetProcessPtr();
6447 if (process
&& process
->IsAlive() &&
6448 StateIsStoppedState(process
->GetState(), true))
6449 exe_ctx
.GetThreadRef().StepOver(true);
6452 return MenuActionResult::Handled
;
6454 case eMenuID_ProcessAttach
: {
6455 WindowSP main_window_sp
= m_app
.GetMainWindow();
6456 FormDelegateSP form_delegate_sp
= FormDelegateSP(
6457 new ProcessAttachFormDelegate(m_debugger
, main_window_sp
));
6458 Rect bounds
= main_window_sp
->GetCenteredRect(80, 22);
6459 WindowSP form_window_sp
= main_window_sp
->CreateSubWindow(
6460 form_delegate_sp
->GetName().c_str(), bounds
, true);
6461 WindowDelegateSP window_delegate_sp
=
6462 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp
));
6463 form_window_sp
->SetDelegate(window_delegate_sp
);
6464 return MenuActionResult::Handled
;
6466 case eMenuID_ProcessLaunch
: {
6467 WindowSP main_window_sp
= m_app
.GetMainWindow();
6468 FormDelegateSP form_delegate_sp
= FormDelegateSP(
6469 new ProcessLaunchFormDelegate(m_debugger
, main_window_sp
));
6470 Rect bounds
= main_window_sp
->GetCenteredRect(80, 22);
6471 WindowSP form_window_sp
= main_window_sp
->CreateSubWindow(
6472 form_delegate_sp
->GetName().c_str(), bounds
, true);
6473 WindowDelegateSP window_delegate_sp
=
6474 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp
));
6475 form_window_sp
->SetDelegate(window_delegate_sp
);
6476 return MenuActionResult::Handled
;
6479 case eMenuID_ProcessContinue
: {
6480 ExecutionContext exe_ctx
=
6481 m_debugger
.GetCommandInterpreter().GetExecutionContext();
6482 if (exe_ctx
.HasProcessScope()) {
6483 Process
*process
= exe_ctx
.GetProcessPtr();
6484 if (process
&& process
->IsAlive() &&
6485 StateIsStoppedState(process
->GetState(), true))
6489 return MenuActionResult::Handled
;
6491 case eMenuID_ProcessKill
: {
6492 ExecutionContext exe_ctx
=
6493 m_debugger
.GetCommandInterpreter().GetExecutionContext();
6494 if (exe_ctx
.HasProcessScope()) {
6495 Process
*process
= exe_ctx
.GetProcessPtr();
6496 if (process
&& process
->IsAlive())
6497 process
->Destroy(false);
6500 return MenuActionResult::Handled
;
6502 case eMenuID_ProcessHalt
: {
6503 ExecutionContext exe_ctx
=
6504 m_debugger
.GetCommandInterpreter().GetExecutionContext();
6505 if (exe_ctx
.HasProcessScope()) {
6506 Process
*process
= exe_ctx
.GetProcessPtr();
6507 if (process
&& process
->IsAlive())
6511 return MenuActionResult::Handled
;
6513 case eMenuID_ProcessDetachResume
:
6514 case eMenuID_ProcessDetachSuspended
: {
6515 ExecutionContext exe_ctx
=
6516 m_debugger
.GetCommandInterpreter().GetExecutionContext();
6517 if (exe_ctx
.HasProcessScope()) {
6518 Process
*process
= exe_ctx
.GetProcessPtr();
6519 if (process
&& process
->IsAlive())
6520 process
->Detach(menu
.GetIdentifier() ==
6521 eMenuID_ProcessDetachSuspended
);
6524 return MenuActionResult::Handled
;
6526 case eMenuID_Process
: {
6527 // Populate the menu with all of the threads if the process is stopped
6528 // when the Process menu gets selected and is about to display its
6530 Menus
&submenus
= menu
.GetSubmenus();
6531 ExecutionContext exe_ctx
=
6532 m_debugger
.GetCommandInterpreter().GetExecutionContext();
6533 Process
*process
= exe_ctx
.GetProcessPtr();
6534 if (process
&& process
->IsAlive() &&
6535 StateIsStoppedState(process
->GetState(), true)) {
6536 if (submenus
.size() == 7)
6537 menu
.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator
)));
6538 else if (submenus
.size() > 8)
6539 submenus
.erase(submenus
.begin() + 8, submenus
.end());
6541 ThreadList
&threads
= process
->GetThreadList();
6542 std::lock_guard
<std::recursive_mutex
> guard(threads
.GetMutex());
6543 size_t num_threads
= threads
.GetSize();
6544 for (size_t i
= 0; i
< num_threads
; ++i
) {
6545 ThreadSP thread_sp
= threads
.GetThreadAtIndex(i
);
6546 char menu_char
= '\0';
6548 menu_char
= '1' + i
;
6549 StreamString thread_menu_title
;
6550 thread_menu_title
.Printf("Thread %u", thread_sp
->GetIndexID());
6551 const char *thread_name
= thread_sp
->GetName();
6552 if (thread_name
&& thread_name
[0])
6553 thread_menu_title
.Printf(" %s", thread_name
);
6555 const char *queue_name
= thread_sp
->GetQueueName();
6556 if (queue_name
&& queue_name
[0])
6557 thread_menu_title
.Printf(" %s", queue_name
);
6560 MenuSP(new Menu(thread_menu_title
.GetString().str().c_str(),
6561 nullptr, menu_char
, thread_sp
->GetID())));
6563 } else if (submenus
.size() > 7) {
6564 // Remove the separator and any other thread submenu items that were
6566 submenus
.erase(submenus
.begin() + 7, submenus
.end());
6568 // Since we are adding and removing items we need to recalculate the
6570 menu
.RecalculateNameLengths();
6572 return MenuActionResult::Handled
;
6574 case eMenuID_ViewVariables
: {
6575 WindowSP main_window_sp
= m_app
.GetMainWindow();
6576 WindowSP source_window_sp
= main_window_sp
->FindSubWindow("Source");
6577 WindowSP variables_window_sp
= main_window_sp
->FindSubWindow("Variables");
6578 WindowSP registers_window_sp
= main_window_sp
->FindSubWindow("Registers");
6579 const Rect source_bounds
= source_window_sp
->GetBounds();
6581 if (variables_window_sp
) {
6582 const Rect variables_bounds
= variables_window_sp
->GetBounds();
6584 main_window_sp
->RemoveSubWindow(variables_window_sp
.get());
6586 if (registers_window_sp
) {
6587 // We have a registers window, so give all the area back to the
6589 Rect registers_bounds
= variables_bounds
;
6590 registers_bounds
.size
.width
= source_bounds
.size
.width
;
6591 registers_window_sp
->SetBounds(registers_bounds
);
6593 // We have no registers window showing so give the bottom area back
6594 // to the source view
6595 source_window_sp
->Resize(source_bounds
.size
.width
,
6596 source_bounds
.size
.height
+
6597 variables_bounds
.size
.height
);
6600 Rect new_variables_rect
;
6601 if (registers_window_sp
) {
6602 // We have a registers window so split the area of the registers
6603 // window into two columns where the left hand side will be the
6604 // variables and the right hand side will be the registers
6605 const Rect variables_bounds
= registers_window_sp
->GetBounds();
6606 Rect new_registers_rect
;
6607 variables_bounds
.VerticalSplitPercentage(0.50, new_variables_rect
,
6608 new_registers_rect
);
6609 registers_window_sp
->SetBounds(new_registers_rect
);
6611 // No registers window, grab the bottom part of the source window
6612 Rect new_source_rect
;
6613 source_bounds
.HorizontalSplitPercentage(0.70, new_source_rect
,
6614 new_variables_rect
);
6615 source_window_sp
->SetBounds(new_source_rect
);
6617 WindowSP new_window_sp
= main_window_sp
->CreateSubWindow(
6618 "Variables", new_variables_rect
, false);
6619 new_window_sp
->SetDelegate(
6620 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger
)));
6624 return MenuActionResult::Handled
;
6626 case eMenuID_ViewRegisters
: {
6627 WindowSP main_window_sp
= m_app
.GetMainWindow();
6628 WindowSP source_window_sp
= main_window_sp
->FindSubWindow("Source");
6629 WindowSP variables_window_sp
= main_window_sp
->FindSubWindow("Variables");
6630 WindowSP registers_window_sp
= main_window_sp
->FindSubWindow("Registers");
6631 const Rect source_bounds
= source_window_sp
->GetBounds();
6633 if (registers_window_sp
) {
6634 if (variables_window_sp
) {
6635 const Rect variables_bounds
= variables_window_sp
->GetBounds();
6637 // We have a variables window, so give all the area back to the
6639 variables_window_sp
->Resize(variables_bounds
.size
.width
+
6640 registers_window_sp
->GetWidth(),
6641 variables_bounds
.size
.height
);
6643 // We have no variables window showing so give the bottom area back
6644 // to the source view
6645 source_window_sp
->Resize(source_bounds
.size
.width
,
6646 source_bounds
.size
.height
+
6647 registers_window_sp
->GetHeight());
6649 main_window_sp
->RemoveSubWindow(registers_window_sp
.get());
6652 if (variables_window_sp
) {
6653 // We have a variables window, split it into two columns where the
6654 // left hand side will be the variables and the right hand side will
6656 const Rect variables_bounds
= variables_window_sp
->GetBounds();
6658 variables_bounds
.VerticalSplitPercentage(0.50, new_vars_rect
,
6660 variables_window_sp
->SetBounds(new_vars_rect
);
6662 // No variables window, grab the bottom part of the source window
6663 Rect new_source_rect
;
6664 source_bounds
.HorizontalSplitPercentage(0.70, new_source_rect
,
6666 source_window_sp
->SetBounds(new_source_rect
);
6668 WindowSP new_window_sp
=
6669 main_window_sp
->CreateSubWindow("Registers", new_regs_rect
, false);
6670 new_window_sp
->SetDelegate(
6671 WindowDelegateSP(new RegistersWindowDelegate(m_debugger
)));
6675 return MenuActionResult::Handled
;
6677 case eMenuID_ViewBreakpoints
: {
6678 WindowSP main_window_sp
= m_app
.GetMainWindow();
6679 WindowSP threads_window_sp
= main_window_sp
->FindSubWindow("Threads");
6680 WindowSP breakpoints_window_sp
=
6681 main_window_sp
->FindSubWindow("Breakpoints");
6682 const Rect threads_bounds
= threads_window_sp
->GetBounds();
6684 // If a breakpoints window already exists, remove it and give the area
6685 // it used to occupy to the threads window. If it doesn't exist, split
6686 // the threads window horizontally into two windows where the top window
6687 // is the threads window and the bottom window is a newly added
6688 // breakpoints window.
6689 if (breakpoints_window_sp
) {
6690 threads_window_sp
->Resize(threads_bounds
.size
.width
,
6691 threads_bounds
.size
.height
+
6692 breakpoints_window_sp
->GetHeight());
6693 main_window_sp
->RemoveSubWindow(breakpoints_window_sp
.get());
6695 Rect new_threads_bounds
, breakpoints_bounds
;
6696 threads_bounds
.HorizontalSplitPercentage(0.70, new_threads_bounds
,
6697 breakpoints_bounds
);
6698 threads_window_sp
->SetBounds(new_threads_bounds
);
6699 breakpoints_window_sp
= main_window_sp
->CreateSubWindow(
6700 "Breakpoints", breakpoints_bounds
, false);
6701 TreeDelegateSP
breakpoints_delegate_sp(
6702 new BreakpointsTreeDelegate(m_debugger
));
6703 breakpoints_window_sp
->SetDelegate(WindowDelegateSP(
6704 new TreeWindowDelegate(m_debugger
, breakpoints_delegate_sp
)));
6707 return MenuActionResult::Handled
;
6710 case eMenuID_HelpGUIHelp
:
6711 m_app
.GetMainWindow()->CreateHelpSubwindow();
6712 return MenuActionResult::Handled
;
6718 return MenuActionResult::NotHandled
;
6723 Debugger
&m_debugger
;
6726 class StatusBarWindowDelegate
: public WindowDelegate
{
6728 StatusBarWindowDelegate(Debugger
&debugger
) : m_debugger(debugger
) {
6729 FormatEntity::Parse("Thread: ${thread.id%tid}", m_format
);
6732 ~StatusBarWindowDelegate() override
= default;
6734 bool WindowDelegateDraw(Window
&window
, bool force
) override
{
6735 ExecutionContext exe_ctx
=
6736 m_debugger
.GetCommandInterpreter().GetExecutionContext();
6737 Process
*process
= exe_ctx
.GetProcessPtr();
6738 Thread
*thread
= exe_ctx
.GetThreadPtr();
6739 StackFrame
*frame
= exe_ctx
.GetFramePtr();
6741 window
.SetBackground(BlackOnWhite
);
6742 window
.MoveCursor(0, 0);
6744 const StateType state
= process
->GetState();
6745 window
.Printf("Process: %5" PRIu64
" %10s", process
->GetID(),
6746 StateAsCString(state
));
6748 if (StateIsStoppedState(state
, true)) {
6750 if (thread
&& FormatEntity::Format(m_format
, strm
, nullptr, &exe_ctx
,
6751 nullptr, nullptr, false, false)) {
6752 window
.MoveCursor(40, 0);
6753 window
.PutCStringTruncated(1, strm
.GetString().str().c_str());
6756 window
.MoveCursor(60, 0);
6758 window
.Printf("Frame: %3u PC = 0x%16.16" PRIx64
,
6759 frame
->GetFrameIndex(),
6760 frame
->GetFrameCodeAddress().GetOpcodeLoadAddress(
6761 exe_ctx
.GetTargetPtr()));
6762 } else if (state
== eStateExited
) {
6763 const char *exit_desc
= process
->GetExitDescription();
6764 const int exit_status
= process
->GetExitStatus();
6765 if (exit_desc
&& exit_desc
[0])
6766 window
.Printf(" with status = %i (%s)", exit_status
, exit_desc
);
6768 window
.Printf(" with status = %i", exit_status
);
6775 Debugger
&m_debugger
;
6776 FormatEntity::Entry m_format
;
6779 class SourceFileWindowDelegate
: public WindowDelegate
{
6781 SourceFileWindowDelegate(Debugger
&debugger
)
6782 : WindowDelegate(), m_debugger(debugger
), m_sc(), m_file_sp(),
6783 m_disassembly_sp(), m_disassembly_range(), m_title() {}
6785 ~SourceFileWindowDelegate() override
= default;
6787 void Update(const SymbolContext
&sc
) { m_sc
= sc
; }
6789 uint32_t NumVisibleLines() const { return m_max_y
- m_min_y
; }
6791 const char *WindowDelegateGetHelpText() override
{
6792 return "Source/Disassembly window keyboard shortcuts:";
6795 KeyHelp
*WindowDelegateGetKeyHelp() override
{
6796 static curses::KeyHelp g_source_view_key_help
[] = {
6797 {KEY_RETURN
, "Run to selected line with one shot breakpoint"},
6798 {KEY_UP
, "Select previous source line"},
6799 {KEY_DOWN
, "Select next source line"},
6800 {KEY_LEFT
, "Scroll to the left"},
6801 {KEY_RIGHT
, "Scroll to the right"},
6802 {KEY_PPAGE
, "Page up"},
6803 {KEY_NPAGE
, "Page down"},
6804 {'b', "Set breakpoint on selected source/disassembly line"},
6805 {'c', "Continue process"},
6806 {'D', "Detach with process suspended"},
6807 {'h', "Show help dialog"},
6808 {'n', "Step over (source line)"},
6809 {'N', "Step over (single instruction)"},
6810 {'f', "Step out (finish)"},
6811 {'s', "Step in (source line)"},
6812 {'S', "Step in (single instruction)"},
6814 {'d', "Frame down"},
6818 return g_source_view_key_help
;
6821 bool WindowDelegateDraw(Window
&window
, bool force
) override
{
6822 ExecutionContext exe_ctx
=
6823 m_debugger
.GetCommandInterpreter().GetExecutionContext();
6824 Process
*process
= exe_ctx
.GetProcessPtr();
6825 Thread
*thread
= nullptr;
6827 bool update_location
= false;
6829 StateType state
= process
->GetState();
6830 if (StateIsStoppedState(state
, true)) {
6831 // We are stopped, so it is ok to
6832 update_location
= true;
6838 m_max_x
= window
.GetMaxX() - 1;
6839 m_max_y
= window
.GetMaxY() - 1;
6841 const uint32_t num_visible_lines
= NumVisibleLines();
6842 StackFrameSP frame_sp
;
6843 bool set_selected_line_to_pc
= false;
6845 if (update_location
) {
6846 const bool process_alive
= process
->IsAlive();
6847 bool thread_changed
= false;
6848 if (process_alive
) {
6849 thread
= exe_ctx
.GetThreadPtr();
6851 frame_sp
= thread
->GetSelectedFrame(SelectMostRelevantFrame
);
6852 auto tid
= thread
->GetID();
6853 thread_changed
= tid
!= m_tid
;
6856 if (m_tid
!= LLDB_INVALID_THREAD_ID
) {
6857 thread_changed
= true;
6858 m_tid
= LLDB_INVALID_THREAD_ID
;
6862 const uint32_t stop_id
= process
? process
->GetStopID() : 0;
6863 const bool stop_id_changed
= stop_id
!= m_stop_id
;
6864 bool frame_changed
= false;
6865 m_stop_id
= stop_id
;
6868 m_sc
= frame_sp
->GetSymbolContext(eSymbolContextEverything
);
6869 if (m_sc
.module_sp
) {
6871 "%s", m_sc
.module_sp
->GetFileSpec().GetFilename().GetCString());
6872 ConstString func_name
= m_sc
.GetFunctionName();
6874 m_title
.Printf("`%s", func_name
.GetCString());
6876 const uint32_t frame_idx
= frame_sp
->GetFrameIndex();
6877 frame_changed
= frame_idx
!= m_frame_idx
;
6878 m_frame_idx
= frame_idx
;
6881 frame_changed
= m_frame_idx
!= UINT32_MAX
;
6882 m_frame_idx
= UINT32_MAX
;
6885 const bool context_changed
=
6886 thread_changed
|| frame_changed
|| stop_id_changed
;
6888 if (process_alive
) {
6889 if (m_sc
.line_entry
.IsValid()) {
6890 m_pc_line
= m_sc
.line_entry
.line
;
6891 if (m_pc_line
!= UINT32_MAX
)
6892 --m_pc_line
; // Convert to zero based line number...
6893 // Update the selected line if the stop ID changed...
6894 if (context_changed
)
6895 m_selected_line
= m_pc_line
;
6897 if (m_file_sp
&& m_file_sp
->GetFileSpec() == m_sc
.line_entry
.file
) {
6898 // Same file, nothing to do, we should either have the lines or
6899 // not (source file missing)
6900 if (m_selected_line
>= static_cast<size_t>(m_first_visible_line
)) {
6901 if (m_selected_line
>= m_first_visible_line
+ num_visible_lines
)
6902 m_first_visible_line
= m_selected_line
- 10;
6904 if (m_selected_line
> 10)
6905 m_first_visible_line
= m_selected_line
- 10;
6907 m_first_visible_line
= 0;
6910 // File changed, set selected line to the line with the PC
6911 m_selected_line
= m_pc_line
;
6913 m_debugger
.GetSourceManager().GetFile(m_sc
.line_entry
.file
);
6915 const size_t num_lines
= m_file_sp
->GetNumLines();
6917 for (size_t n
= num_lines
; n
>= 10; n
= n
/ 10)
6920 if (num_lines
< num_visible_lines
||
6921 m_selected_line
< num_visible_lines
)
6922 m_first_visible_line
= 0;
6924 m_first_visible_line
= m_selected_line
- 10;
6931 if (!m_file_sp
|| m_file_sp
->GetNumLines() == 0) {
6933 bool prefer_file_cache
= false;
6934 if (m_sc
.function
) {
6935 if (m_disassembly_scope
!= m_sc
.function
) {
6936 m_disassembly_scope
= m_sc
.function
;
6937 m_disassembly_sp
= m_sc
.function
->GetInstructions(
6938 exe_ctx
, nullptr, !prefer_file_cache
);
6939 if (m_disassembly_sp
) {
6940 set_selected_line_to_pc
= true;
6941 m_disassembly_range
= m_sc
.function
->GetAddressRange();
6943 m_disassembly_range
.Clear();
6946 set_selected_line_to_pc
= context_changed
;
6948 } else if (m_sc
.symbol
) {
6949 if (m_disassembly_scope
!= m_sc
.symbol
) {
6950 m_disassembly_scope
= m_sc
.symbol
;
6951 m_disassembly_sp
= m_sc
.symbol
->GetInstructions(
6952 exe_ctx
, nullptr, prefer_file_cache
);
6953 if (m_disassembly_sp
) {
6954 set_selected_line_to_pc
= true;
6955 m_disassembly_range
.GetBaseAddress() =
6956 m_sc
.symbol
->GetAddress();
6957 m_disassembly_range
.SetByteSize(m_sc
.symbol
->GetByteSize());
6959 m_disassembly_range
.Clear();
6962 set_selected_line_to_pc
= context_changed
;
6967 m_pc_line
= UINT32_MAX
;
6971 const int window_width
= window
.GetWidth();
6973 window
.DrawTitleBox("Sources");
6974 if (!m_title
.GetString().empty()) {
6975 window
.AttributeOn(A_REVERSE
);
6976 window
.MoveCursor(1, 1);
6977 window
.PutChar(' ');
6978 window
.PutCStringTruncated(1, m_title
.GetString().str().c_str());
6979 int x
= window
.GetCursorX();
6980 if (x
< window_width
- 1) {
6981 window
.Printf("%*s", window_width
- x
- 1, "");
6983 window
.AttributeOff(A_REVERSE
);
6986 Target
*target
= exe_ctx
.GetTargetPtr();
6987 const size_t num_source_lines
= GetNumSourceLines();
6988 if (num_source_lines
> 0) {
6990 BreakpointLines bp_lines
;
6992 BreakpointList
&bp_list
= target
->GetBreakpointList();
6993 const size_t num_bps
= bp_list
.GetSize();
6994 for (size_t bp_idx
= 0; bp_idx
< num_bps
; ++bp_idx
) {
6995 BreakpointSP bp_sp
= bp_list
.GetBreakpointAtIndex(bp_idx
);
6996 const size_t num_bps_locs
= bp_sp
->GetNumLocations();
6997 for (size_t bp_loc_idx
= 0; bp_loc_idx
< num_bps_locs
; ++bp_loc_idx
) {
6998 BreakpointLocationSP bp_loc_sp
=
6999 bp_sp
->GetLocationAtIndex(bp_loc_idx
);
7000 LineEntry bp_loc_line_entry
;
7001 if (bp_loc_sp
->GetAddress().CalculateSymbolContextLineEntry(
7002 bp_loc_line_entry
)) {
7003 if (m_file_sp
->GetFileSpec() == bp_loc_line_entry
.file
) {
7004 bp_lines
.insert(bp_loc_line_entry
.line
);
7011 for (size_t i
= 0; i
< num_visible_lines
; ++i
) {
7012 const uint32_t curr_line
= m_first_visible_line
+ i
;
7013 if (curr_line
< num_source_lines
) {
7014 const int line_y
= m_min_y
+ i
;
7015 window
.MoveCursor(1, line_y
);
7016 const bool is_pc_line
= curr_line
== m_pc_line
;
7017 const bool line_is_selected
= m_selected_line
== curr_line
;
7018 // Highlight the line as the PC line first (done by passing
7019 // argument to OutputColoredStringTruncated()), then if the selected
7020 // line isn't the same as the PC line, highlight it differently.
7021 attr_t highlight_attr
= 0;
7023 if (line_is_selected
&& !is_pc_line
)
7024 highlight_attr
= A_REVERSE
;
7026 if (bp_lines
.find(curr_line
+ 1) != bp_lines
.end())
7027 bp_attr
= COLOR_PAIR(BlackOnWhite
);
7030 window
.AttributeOn(bp_attr
);
7032 window
.Printf(" %*u ", m_line_width
, curr_line
+ 1);
7035 window
.AttributeOff(bp_attr
);
7037 window
.PutChar(ACS_VLINE
);
7038 // Mark the line with the PC with a diamond
7040 window
.PutChar(ACS_DIAMOND
);
7042 window
.PutChar(' ');
7045 window
.AttributeOn(highlight_attr
);
7047 StreamString lineStream
;
7049 std::optional
<size_t> column
;
7050 if (is_pc_line
&& m_sc
.line_entry
.IsValid() && m_sc
.line_entry
.column
)
7051 column
= m_sc
.line_entry
.column
- 1;
7052 m_file_sp
->DisplaySourceLines(curr_line
+ 1, column
, 0, 0,
7054 StringRef line
= lineStream
.GetString();
7055 if (line
.endswith("\n"))
7056 line
= line
.drop_back();
7057 bool wasWritten
= window
.OutputColoredStringTruncated(
7058 1, line
, m_first_visible_column
, is_pc_line
);
7059 if (!wasWritten
&& (line_is_selected
|| is_pc_line
)) {
7060 // Draw an empty space to show the selected/PC line if empty,
7061 // or draw '<' if nothing is visible because of scrolling too much
7063 window
.PutCStringTruncated(
7064 1, line
.empty() && m_first_visible_column
== 0 ? " " : "<");
7067 if (is_pc_line
&& frame_sp
&&
7068 frame_sp
->GetConcreteFrameIndex() == 0) {
7069 StopInfoSP stop_info_sp
;
7071 stop_info_sp
= thread
->GetStopInfo();
7073 const char *stop_description
= stop_info_sp
->GetDescription();
7074 if (stop_description
&& stop_description
[0]) {
7075 size_t stop_description_len
= strlen(stop_description
);
7076 int desc_x
= window_width
- stop_description_len
- 16;
7077 if (desc_x
- window
.GetCursorX() > 0)
7078 window
.Printf("%*s", desc_x
- window
.GetCursorX(), "");
7079 window
.MoveCursor(window_width
- stop_description_len
- 16,
7081 const attr_t stop_reason_attr
= COLOR_PAIR(WhiteOnBlue
);
7082 window
.AttributeOn(stop_reason_attr
);
7083 window
.PrintfTruncated(1, " <<< Thread %u: %s ",
7084 thread
->GetIndexID(), stop_description
);
7085 window
.AttributeOff(stop_reason_attr
);
7088 window
.Printf("%*s", window_width
- window
.GetCursorX() - 1, "");
7092 window
.AttributeOff(highlight_attr
);
7098 size_t num_disassembly_lines
= GetNumDisassemblyLines();
7099 if (num_disassembly_lines
> 0) {
7100 // Display disassembly
7101 BreakpointAddrs bp_file_addrs
;
7102 Target
*target
= exe_ctx
.GetTargetPtr();
7104 BreakpointList
&bp_list
= target
->GetBreakpointList();
7105 const size_t num_bps
= bp_list
.GetSize();
7106 for (size_t bp_idx
= 0; bp_idx
< num_bps
; ++bp_idx
) {
7107 BreakpointSP bp_sp
= bp_list
.GetBreakpointAtIndex(bp_idx
);
7108 const size_t num_bps_locs
= bp_sp
->GetNumLocations();
7109 for (size_t bp_loc_idx
= 0; bp_loc_idx
< num_bps_locs
;
7111 BreakpointLocationSP bp_loc_sp
=
7112 bp_sp
->GetLocationAtIndex(bp_loc_idx
);
7113 LineEntry bp_loc_line_entry
;
7114 const lldb::addr_t file_addr
=
7115 bp_loc_sp
->GetAddress().GetFileAddress();
7116 if (file_addr
!= LLDB_INVALID_ADDRESS
) {
7117 if (m_disassembly_range
.ContainsFileAddress(file_addr
))
7118 bp_file_addrs
.insert(file_addr
);
7124 const attr_t selected_highlight_attr
= A_REVERSE
;
7125 const attr_t pc_highlight_attr
= COLOR_PAIR(WhiteOnBlue
);
7129 InstructionList
&insts
= m_disassembly_sp
->GetInstructionList();
7133 pc_address
= frame_sp
->GetFrameCodeAddress();
7134 const uint32_t pc_idx
=
7135 pc_address
.IsValid()
7136 ? insts
.GetIndexOfInstructionAtAddress(pc_address
)
7138 if (set_selected_line_to_pc
) {
7139 m_selected_line
= pc_idx
;
7142 const uint32_t non_visible_pc_offset
= (num_visible_lines
/ 5);
7143 if (static_cast<size_t>(m_first_visible_line
) >= num_disassembly_lines
)
7144 m_first_visible_line
= 0;
7146 if (pc_idx
< num_disassembly_lines
) {
7147 if (pc_idx
< static_cast<uint32_t>(m_first_visible_line
) ||
7148 pc_idx
>= m_first_visible_line
+ num_visible_lines
)
7149 m_first_visible_line
= pc_idx
- non_visible_pc_offset
;
7152 for (size_t i
= 0; i
< num_visible_lines
; ++i
) {
7153 const uint32_t inst_idx
= m_first_visible_line
+ i
;
7154 Instruction
*inst
= insts
.GetInstructionAtIndex(inst_idx
).get();
7158 const int line_y
= m_min_y
+ i
;
7159 window
.MoveCursor(1, line_y
);
7160 const bool is_pc_line
= frame_sp
&& inst_idx
== pc_idx
;
7161 const bool line_is_selected
= m_selected_line
== inst_idx
;
7162 // Highlight the line as the PC line first, then if the selected
7163 // line isn't the same as the PC line, highlight it differently
7164 attr_t highlight_attr
= 0;
7167 highlight_attr
= pc_highlight_attr
;
7168 else if (line_is_selected
)
7169 highlight_attr
= selected_highlight_attr
;
7171 if (bp_file_addrs
.find(inst
->GetAddress().GetFileAddress()) !=
7172 bp_file_addrs
.end())
7173 bp_attr
= COLOR_PAIR(BlackOnWhite
);
7176 window
.AttributeOn(bp_attr
);
7178 window
.Printf(" 0x%16.16llx ",
7179 static_cast<unsigned long long>(
7180 inst
->GetAddress().GetLoadAddress(target
)));
7183 window
.AttributeOff(bp_attr
);
7185 window
.PutChar(ACS_VLINE
);
7186 // Mark the line with the PC with a diamond
7188 window
.PutChar(ACS_DIAMOND
);
7190 window
.PutChar(' ');
7193 window
.AttributeOn(highlight_attr
);
7195 const char *mnemonic
= inst
->GetMnemonic(&exe_ctx
);
7196 const char *operands
= inst
->GetOperands(&exe_ctx
);
7197 const char *comment
= inst
->GetComment(&exe_ctx
);
7199 if (mnemonic
!= nullptr && mnemonic
[0] == '\0')
7201 if (operands
!= nullptr && operands
[0] == '\0')
7203 if (comment
!= nullptr && comment
[0] == '\0')
7208 if (mnemonic
!= nullptr && operands
!= nullptr && comment
!= nullptr)
7209 strm
.Printf("%-8s %-25s ; %s", mnemonic
, operands
, comment
);
7210 else if (mnemonic
!= nullptr && operands
!= nullptr)
7211 strm
.Printf("%-8s %s", mnemonic
, operands
);
7212 else if (mnemonic
!= nullptr)
7213 strm
.Printf("%s", mnemonic
);
7216 window
.PutCStringTruncated(
7218 strm
.GetString().substr(m_first_visible_column
).data());
7220 if (is_pc_line
&& frame_sp
&&
7221 frame_sp
->GetConcreteFrameIndex() == 0) {
7222 StopInfoSP stop_info_sp
;
7224 stop_info_sp
= thread
->GetStopInfo();
7226 const char *stop_description
= stop_info_sp
->GetDescription();
7227 if (stop_description
&& stop_description
[0]) {
7228 size_t stop_description_len
= strlen(stop_description
);
7229 int desc_x
= window_width
- stop_description_len
- 16;
7230 if (desc_x
- window
.GetCursorX() > 0)
7231 window
.Printf("%*s", desc_x
- window
.GetCursorX(), "");
7232 window
.MoveCursor(window_width
- stop_description_len
- 15,
7235 window
.PrintfTruncated(1, "<<< Thread %u: %s ",
7236 thread
->GetIndexID(),
7240 window
.Printf("%*s", window_width
- window
.GetCursorX() - 1, "");
7244 window
.AttributeOff(highlight_attr
);
7248 return true; // Drawing handled
7251 size_t GetNumLines() {
7252 size_t num_lines
= GetNumSourceLines();
7254 num_lines
= GetNumDisassemblyLines();
7258 size_t GetNumSourceLines() const {
7260 return m_file_sp
->GetNumLines();
7264 size_t GetNumDisassemblyLines() const {
7265 if (m_disassembly_sp
)
7266 return m_disassembly_sp
->GetInstructionList().GetSize();
7270 HandleCharResult
WindowDelegateHandleChar(Window
&window
, int c
) override
{
7271 const uint32_t num_visible_lines
= NumVisibleLines();
7272 const size_t num_lines
= GetNumLines();
7278 if (static_cast<uint32_t>(m_first_visible_line
) > num_visible_lines
)
7279 m_first_visible_line
-= num_visible_lines
;
7281 m_first_visible_line
= 0;
7282 m_selected_line
= m_first_visible_line
;
7289 if (m_first_visible_line
+ num_visible_lines
< num_lines
)
7290 m_first_visible_line
+= num_visible_lines
;
7291 else if (num_lines
< num_visible_lines
)
7292 m_first_visible_line
= 0;
7294 m_first_visible_line
= num_lines
- num_visible_lines
;
7295 m_selected_line
= m_first_visible_line
;
7300 if (m_selected_line
> 0) {
7302 if (static_cast<size_t>(m_first_visible_line
) > m_selected_line
)
7303 m_first_visible_line
= m_selected_line
;
7308 if (m_selected_line
+ 1 < num_lines
) {
7310 if (m_first_visible_line
+ num_visible_lines
< m_selected_line
)
7311 m_first_visible_line
++;
7316 if (m_first_visible_column
> 0)
7317 --m_first_visible_column
;
7321 ++m_first_visible_column
;
7327 // Set a breakpoint and run to the line using a one shot breakpoint
7328 if (GetNumSourceLines() > 0) {
7329 ExecutionContext exe_ctx
=
7330 m_debugger
.GetCommandInterpreter().GetExecutionContext();
7331 if (exe_ctx
.HasProcessScope() && exe_ctx
.GetProcessRef().IsAlive()) {
7332 BreakpointSP bp_sp
= exe_ctx
.GetTargetRef().CreateBreakpoint(
7333 nullptr, // Don't limit the breakpoint to certain modules
7334 m_file_sp
->GetFileSpec(), // Source file
7336 1, // Source line number (m_selected_line is zero based)
7337 0, // Unspecified column.
7339 eLazyBoolCalculate
, // Check inlines using global setting
7340 eLazyBoolCalculate
, // Skip prologue using global setting,
7342 false, // request_hardware
7343 eLazyBoolCalculate
); // move_to_nearest_code
7344 // Make breakpoint one shot
7345 bp_sp
->GetOptions().SetOneShot(true);
7346 exe_ctx
.GetProcessRef().Resume();
7348 } else if (m_selected_line
< GetNumDisassemblyLines()) {
7349 const Instruction
*inst
= m_disassembly_sp
->GetInstructionList()
7350 .GetInstructionAtIndex(m_selected_line
)
7352 ExecutionContext exe_ctx
=
7353 m_debugger
.GetCommandInterpreter().GetExecutionContext();
7354 if (exe_ctx
.HasTargetScope()) {
7355 Address addr
= inst
->GetAddress();
7356 BreakpointSP bp_sp
= exe_ctx
.GetTargetRef().CreateBreakpoint(
7357 addr
, // lldb_private::Address
7359 false); // request_hardware
7360 // Make breakpoint one shot
7361 bp_sp
->GetOptions().SetOneShot(true);
7362 exe_ctx
.GetProcessRef().Resume();
7367 case 'b': // 'b' == toggle breakpoint on currently selected line
7368 ToggleBreakpointOnSelectedLine();
7371 case 'D': // 'D' == detach and keep stopped
7373 ExecutionContext exe_ctx
=
7374 m_debugger
.GetCommandInterpreter().GetExecutionContext();
7375 if (exe_ctx
.HasProcessScope())
7376 exe_ctx
.GetProcessRef().Detach(true);
7383 ExecutionContext exe_ctx
=
7384 m_debugger
.GetCommandInterpreter().GetExecutionContext();
7385 if (exe_ctx
.HasProcessScope())
7386 exe_ctx
.GetProcessRef().Resume();
7391 // 'f' == step out (finish)
7393 ExecutionContext exe_ctx
=
7394 m_debugger
.GetCommandInterpreter().GetExecutionContext();
7395 if (exe_ctx
.HasThreadScope() &&
7396 StateIsStoppedState(exe_ctx
.GetProcessRef().GetState(), true)) {
7397 Thread
*thread
= exe_ctx
.GetThreadPtr();
7398 uint32_t frame_idx
=
7399 thread
->GetSelectedFrameIndex(SelectMostRelevantFrame
);
7400 exe_ctx
.GetThreadRef().StepOut(frame_idx
);
7405 case 'n': // 'n' == step over
7406 case 'N': // 'N' == step over instruction
7408 ExecutionContext exe_ctx
=
7409 m_debugger
.GetCommandInterpreter().GetExecutionContext();
7410 if (exe_ctx
.HasThreadScope() &&
7411 StateIsStoppedState(exe_ctx
.GetProcessRef().GetState(), true)) {
7412 bool source_step
= (c
== 'n');
7413 exe_ctx
.GetThreadRef().StepOver(source_step
);
7418 case 's': // 's' == step into
7419 case 'S': // 'S' == step into instruction
7421 ExecutionContext exe_ctx
=
7422 m_debugger
.GetCommandInterpreter().GetExecutionContext();
7423 if (exe_ctx
.HasThreadScope() &&
7424 StateIsStoppedState(exe_ctx
.GetProcessRef().GetState(), true)) {
7425 bool source_step
= (c
== 's');
7426 exe_ctx
.GetThreadRef().StepIn(source_step
);
7431 case 'u': // 'u' == frame up
7432 case 'd': // 'd' == frame down
7434 ExecutionContext exe_ctx
=
7435 m_debugger
.GetCommandInterpreter().GetExecutionContext();
7436 if (exe_ctx
.HasThreadScope()) {
7437 Thread
*thread
= exe_ctx
.GetThreadPtr();
7438 uint32_t frame_idx
=
7439 thread
->GetSelectedFrameIndex(SelectMostRelevantFrame
);
7440 if (frame_idx
== UINT32_MAX
)
7442 if (c
== 'u' && frame_idx
+ 1 < thread
->GetStackFrameCount())
7444 else if (c
== 'd' && frame_idx
> 0)
7446 if (thread
->SetSelectedFrameByIndex(frame_idx
, true))
7447 exe_ctx
.SetFrameSP(thread
->GetSelectedFrame(SelectMostRelevantFrame
));
7453 window
.CreateHelpSubwindow();
7459 return eKeyNotHandled
;
7462 void ToggleBreakpointOnSelectedLine() {
7463 ExecutionContext exe_ctx
=
7464 m_debugger
.GetCommandInterpreter().GetExecutionContext();
7465 if (!exe_ctx
.HasTargetScope())
7467 if (GetNumSourceLines() > 0) {
7468 // Source file breakpoint.
7469 BreakpointList
&bp_list
= exe_ctx
.GetTargetRef().GetBreakpointList();
7470 const size_t num_bps
= bp_list
.GetSize();
7471 for (size_t bp_idx
= 0; bp_idx
< num_bps
; ++bp_idx
) {
7472 BreakpointSP bp_sp
= bp_list
.GetBreakpointAtIndex(bp_idx
);
7473 const size_t num_bps_locs
= bp_sp
->GetNumLocations();
7474 for (size_t bp_loc_idx
= 0; bp_loc_idx
< num_bps_locs
; ++bp_loc_idx
) {
7475 BreakpointLocationSP bp_loc_sp
=
7476 bp_sp
->GetLocationAtIndex(bp_loc_idx
);
7477 LineEntry bp_loc_line_entry
;
7478 if (bp_loc_sp
->GetAddress().CalculateSymbolContextLineEntry(
7479 bp_loc_line_entry
)) {
7480 if (m_file_sp
->GetFileSpec() == bp_loc_line_entry
.file
&&
7481 m_selected_line
+ 1 == bp_loc_line_entry
.line
) {
7483 exe_ctx
.GetTargetRef().RemoveBreakpointByID(bp_sp
->GetID());
7485 UNUSED_IF_ASSERT_DISABLED(removed
);
7486 return; // Existing breakpoint removed.
7491 // No breakpoint found on the location, add it.
7492 BreakpointSP bp_sp
= exe_ctx
.GetTargetRef().CreateBreakpoint(
7493 nullptr, // Don't limit the breakpoint to certain modules
7494 m_file_sp
->GetFileSpec(), // Source file
7496 1, // Source line number (m_selected_line is zero based)
7497 0, // No column specified.
7499 eLazyBoolCalculate
, // Check inlines using global setting
7500 eLazyBoolCalculate
, // Skip prologue using global setting,
7502 false, // request_hardware
7503 eLazyBoolCalculate
); // move_to_nearest_code
7505 // Disassembly breakpoint.
7506 assert(GetNumDisassemblyLines() > 0);
7507 assert(m_selected_line
< GetNumDisassemblyLines());
7508 const Instruction
*inst
= m_disassembly_sp
->GetInstructionList()
7509 .GetInstructionAtIndex(m_selected_line
)
7511 Address addr
= inst
->GetAddress();
7513 BreakpointList
&bp_list
= exe_ctx
.GetTargetRef().GetBreakpointList();
7514 const size_t num_bps
= bp_list
.GetSize();
7515 for (size_t bp_idx
= 0; bp_idx
< num_bps
; ++bp_idx
) {
7516 BreakpointSP bp_sp
= bp_list
.GetBreakpointAtIndex(bp_idx
);
7517 const size_t num_bps_locs
= bp_sp
->GetNumLocations();
7518 for (size_t bp_loc_idx
= 0; bp_loc_idx
< num_bps_locs
; ++bp_loc_idx
) {
7519 BreakpointLocationSP bp_loc_sp
=
7520 bp_sp
->GetLocationAtIndex(bp_loc_idx
);
7521 LineEntry bp_loc_line_entry
;
7522 const lldb::addr_t file_addr
=
7523 bp_loc_sp
->GetAddress().GetFileAddress();
7524 if (file_addr
== addr
.GetFileAddress()) {
7526 exe_ctx
.GetTargetRef().RemoveBreakpointByID(bp_sp
->GetID());
7528 UNUSED_IF_ASSERT_DISABLED(removed
);
7529 return; // Existing breakpoint removed.
7533 // No breakpoint found on the address, add it.
7534 BreakpointSP bp_sp
=
7535 exe_ctx
.GetTargetRef().CreateBreakpoint(addr
, // lldb_private::Address
7537 false); // request_hardware
7542 typedef std::set
<uint32_t> BreakpointLines
;
7543 typedef std::set
<lldb::addr_t
> BreakpointAddrs
;
7545 Debugger
&m_debugger
;
7547 SourceManager::FileSP m_file_sp
;
7548 SymbolContextScope
*m_disassembly_scope
= nullptr;
7549 lldb::DisassemblerSP m_disassembly_sp
;
7550 AddressRange m_disassembly_range
;
7551 StreamString m_title
;
7552 lldb::user_id_t m_tid
= LLDB_INVALID_THREAD_ID
;
7553 int m_line_width
= 4;
7554 uint32_t m_selected_line
= 0; // The selected line
7555 uint32_t m_pc_line
= 0; // The line with the PC
7556 uint32_t m_stop_id
= 0;
7557 uint32_t m_frame_idx
= UINT32_MAX
;
7558 int m_first_visible_line
= 0;
7559 int m_first_visible_column
= 0;
7566 DisplayOptions
ValueObjectListDelegate::g_options
= {true};
7568 IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger
&debugger
)
7569 : IOHandler(debugger
, IOHandler::Type::Curses
) {}
7571 void IOHandlerCursesGUI::Activate() {
7572 IOHandler::Activate();
7574 m_app_ap
= std::make_unique
<Application
>(GetInputFILE(), GetOutputFILE());
7576 // This is both a window and a menu delegate
7577 std::shared_ptr
<ApplicationDelegate
> app_delegate_sp(
7578 new ApplicationDelegate(*m_app_ap
, m_debugger
));
7580 MenuDelegateSP app_menu_delegate_sp
=
7581 std::static_pointer_cast
<MenuDelegate
>(app_delegate_sp
);
7582 MenuSP
lldb_menu_sp(
7583 new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB
));
7584 MenuSP
exit_menuitem_sp(
7585 new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit
));
7586 exit_menuitem_sp
->SetCannedResult(MenuActionResult::Quit
);
7587 lldb_menu_sp
->AddSubmenu(MenuSP(new Menu(
7588 "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout
)));
7589 lldb_menu_sp
->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator
)));
7590 lldb_menu_sp
->AddSubmenu(exit_menuitem_sp
);
7592 MenuSP
target_menu_sp(new Menu("Target", "F2", KEY_F(2),
7593 ApplicationDelegate::eMenuID_Target
));
7594 target_menu_sp
->AddSubmenu(MenuSP(new Menu(
7595 "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate
)));
7596 target_menu_sp
->AddSubmenu(MenuSP(new Menu(
7597 "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete
)));
7599 MenuSP
process_menu_sp(new Menu("Process", "F3", KEY_F(3),
7600 ApplicationDelegate::eMenuID_Process
));
7601 process_menu_sp
->AddSubmenu(MenuSP(new Menu(
7602 "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach
)));
7603 process_menu_sp
->AddSubmenu(
7604 MenuSP(new Menu("Detach and resume", nullptr, 'd',
7605 ApplicationDelegate::eMenuID_ProcessDetachResume
)));
7606 process_menu_sp
->AddSubmenu(
7607 MenuSP(new Menu("Detach suspended", nullptr, 's',
7608 ApplicationDelegate::eMenuID_ProcessDetachSuspended
)));
7609 process_menu_sp
->AddSubmenu(MenuSP(new Menu(
7610 "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch
)));
7611 process_menu_sp
->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator
)));
7612 process_menu_sp
->AddSubmenu(
7613 MenuSP(new Menu("Continue", nullptr, 'c',
7614 ApplicationDelegate::eMenuID_ProcessContinue
)));
7615 process_menu_sp
->AddSubmenu(MenuSP(new Menu(
7616 "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt
)));
7617 process_menu_sp
->AddSubmenu(MenuSP(new Menu(
7618 "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill
)));
7620 MenuSP
thread_menu_sp(new Menu("Thread", "F4", KEY_F(4),
7621 ApplicationDelegate::eMenuID_Thread
));
7622 thread_menu_sp
->AddSubmenu(MenuSP(new Menu(
7623 "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn
)));
7624 thread_menu_sp
->AddSubmenu(
7625 MenuSP(new Menu("Step Over", nullptr, 'v',
7626 ApplicationDelegate::eMenuID_ThreadStepOver
)));
7627 thread_menu_sp
->AddSubmenu(MenuSP(new Menu(
7628 "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut
)));
7630 MenuSP
view_menu_sp(
7631 new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View
));
7632 view_menu_sp
->AddSubmenu(
7633 MenuSP(new Menu("Backtrace", nullptr, 't',
7634 ApplicationDelegate::eMenuID_ViewBacktrace
)));
7635 view_menu_sp
->AddSubmenu(
7636 MenuSP(new Menu("Registers", nullptr, 'r',
7637 ApplicationDelegate::eMenuID_ViewRegisters
)));
7638 view_menu_sp
->AddSubmenu(MenuSP(new Menu(
7639 "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource
)));
7640 view_menu_sp
->AddSubmenu(
7641 MenuSP(new Menu("Variables", nullptr, 'v',
7642 ApplicationDelegate::eMenuID_ViewVariables
)));
7643 view_menu_sp
->AddSubmenu(
7644 MenuSP(new Menu("Breakpoints", nullptr, 'b',
7645 ApplicationDelegate::eMenuID_ViewBreakpoints
)));
7647 MenuSP
help_menu_sp(
7648 new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help
));
7649 help_menu_sp
->AddSubmenu(MenuSP(new Menu(
7650 "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp
)));
7652 m_app_ap
->Initialize();
7653 WindowSP
&main_window_sp
= m_app_ap
->GetMainWindow();
7655 MenuSP
menubar_sp(new Menu(Menu::Type::Bar
));
7656 menubar_sp
->AddSubmenu(lldb_menu_sp
);
7657 menubar_sp
->AddSubmenu(target_menu_sp
);
7658 menubar_sp
->AddSubmenu(process_menu_sp
);
7659 menubar_sp
->AddSubmenu(thread_menu_sp
);
7660 menubar_sp
->AddSubmenu(view_menu_sp
);
7661 menubar_sp
->AddSubmenu(help_menu_sp
);
7662 menubar_sp
->SetDelegate(app_menu_delegate_sp
);
7664 Rect content_bounds
= main_window_sp
->GetFrame();
7665 Rect menubar_bounds
= content_bounds
.MakeMenuBar();
7666 Rect status_bounds
= content_bounds
.MakeStatusBar();
7668 Rect variables_bounds
;
7669 Rect threads_bounds
;
7670 Rect source_variables_bounds
;
7671 content_bounds
.VerticalSplitPercentage(0.80, source_variables_bounds
,
7673 source_variables_bounds
.HorizontalSplitPercentage(0.70, source_bounds
,
7676 WindowSP menubar_window_sp
=
7677 main_window_sp
->CreateSubWindow("Menubar", menubar_bounds
, false);
7678 // Let the menubar get keys if the active window doesn't handle the keys
7679 // that are typed so it can respond to menubar key presses.
7680 menubar_window_sp
->SetCanBeActive(
7681 false); // Don't let the menubar become the active window
7682 menubar_window_sp
->SetDelegate(menubar_sp
);
7684 WindowSP
source_window_sp(
7685 main_window_sp
->CreateSubWindow("Source", source_bounds
, true));
7686 WindowSP
variables_window_sp(
7687 main_window_sp
->CreateSubWindow("Variables", variables_bounds
, false));
7688 WindowSP
threads_window_sp(
7689 main_window_sp
->CreateSubWindow("Threads", threads_bounds
, false));
7690 WindowSP
status_window_sp(
7691 main_window_sp
->CreateSubWindow("Status", status_bounds
, false));
7692 status_window_sp
->SetCanBeActive(
7693 false); // Don't let the status bar become the active window
7694 main_window_sp
->SetDelegate(
7695 std::static_pointer_cast
<WindowDelegate
>(app_delegate_sp
));
7696 source_window_sp
->SetDelegate(
7697 WindowDelegateSP(new SourceFileWindowDelegate(m_debugger
)));
7698 variables_window_sp
->SetDelegate(
7699 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger
)));
7700 TreeDelegateSP
thread_delegate_sp(new ThreadsTreeDelegate(m_debugger
));
7701 threads_window_sp
->SetDelegate(WindowDelegateSP(
7702 new TreeWindowDelegate(m_debugger
, thread_delegate_sp
)));
7703 status_window_sp
->SetDelegate(
7704 WindowDelegateSP(new StatusBarWindowDelegate(m_debugger
)));
7706 // All colors with black background.
7707 init_pair(1, COLOR_BLACK
, COLOR_BLACK
);
7708 init_pair(2, COLOR_RED
, COLOR_BLACK
);
7709 init_pair(3, COLOR_GREEN
, COLOR_BLACK
);
7710 init_pair(4, COLOR_YELLOW
, COLOR_BLACK
);
7711 init_pair(5, COLOR_BLUE
, COLOR_BLACK
);
7712 init_pair(6, COLOR_MAGENTA
, COLOR_BLACK
);
7713 init_pair(7, COLOR_CYAN
, COLOR_BLACK
);
7714 init_pair(8, COLOR_WHITE
, COLOR_BLACK
);
7715 // All colors with blue background.
7716 init_pair(9, COLOR_BLACK
, COLOR_BLUE
);
7717 init_pair(10, COLOR_RED
, COLOR_BLUE
);
7718 init_pair(11, COLOR_GREEN
, COLOR_BLUE
);
7719 init_pair(12, COLOR_YELLOW
, COLOR_BLUE
);
7720 init_pair(13, COLOR_BLUE
, COLOR_BLUE
);
7721 init_pair(14, COLOR_MAGENTA
, COLOR_BLUE
);
7722 init_pair(15, COLOR_CYAN
, COLOR_BLUE
);
7723 init_pair(16, COLOR_WHITE
, COLOR_BLUE
);
7724 // These must match the order in the color indexes enum.
7725 init_pair(17, COLOR_BLACK
, COLOR_WHITE
);
7726 init_pair(18, COLOR_MAGENTA
, COLOR_WHITE
);
7727 static_assert(LastColorPairIndex
== 18, "Color indexes do not match.");
7729 define_key("\033[Z", KEY_SHIFT_TAB
);
7730 define_key("\033\015", KEY_ALT_ENTER
);
7734 void IOHandlerCursesGUI::Deactivate() { m_app_ap
->Terminate(); }
7736 void IOHandlerCursesGUI::Run() {
7737 m_app_ap
->Run(m_debugger
);
7741 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default;
7743 void IOHandlerCursesGUI::Cancel() {}
7745 bool IOHandlerCursesGUI::Interrupt() {
7746 return m_debugger
.GetCommandInterpreter().IOHandlerInterrupt(*this);
7749 void IOHandlerCursesGUI::GotEOF() {}
7751 void IOHandlerCursesGUI::TerminalSizeChanged() {
7752 m_app_ap
->TerminalSizeChanged();
7755 #endif // LLDB_ENABLE_CURSES