1 //===-- IOHandlerCursesGUI.cpp --------------------------------------------===//
2 //
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
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "lldb/Core/IOHandlerCursesGUI.h"
10 #include "lldb/Host/Config.h"
11 
12 #if LLDB_ENABLE_CURSES
13 #if CURSES_HAVE_NCURSES_CURSES_H
14 #include <ncurses/curses.h>
15 #include <ncurses/panel.h>
16 #else
17 #include <curses.h>
18 #include <panel.h>
19 #endif
20 #endif
21 
22 #if defined(__APPLE__)
23 #include <deque>
24 #endif
25 #include <string>
26 
27 #include "lldb/Core/Debugger.h"
28 #include "lldb/Core/StreamFile.h"
29 #include "lldb/Host/File.h"
30 #include "lldb/Utility/Predicate.h"
31 #include "lldb/Utility/Status.h"
32 #include "lldb/Utility/StreamString.h"
33 #include "lldb/Utility/StringList.h"
34 #include "lldb/lldb-forward.h"
35 
36 #include "lldb/Interpreter/CommandCompletions.h"
37 #include "lldb/Interpreter/CommandInterpreter.h"
38 
39 #if LLDB_ENABLE_CURSES
40 #include "lldb/Breakpoint/BreakpointLocation.h"
41 #include "lldb/Core/Module.h"
42 #include "lldb/Core/ValueObject.h"
43 #include "lldb/Core/ValueObjectRegister.h"
44 #include "lldb/Symbol/Block.h"
45 #include "lldb/Symbol/Function.h"
46 #include "lldb/Symbol/Symbol.h"
47 #include "lldb/Symbol/VariableList.h"
48 #include "lldb/Target/Process.h"
49 #include "lldb/Target/RegisterContext.h"
50 #include "lldb/Target/StackFrame.h"
51 #include "lldb/Target/StopInfo.h"
52 #include "lldb/Target/Target.h"
53 #include "lldb/Target/Thread.h"
54 #include "lldb/Utility/State.h"
55 #endif
56 
57 #include "llvm/ADT/StringRef.h"
58 
59 #ifdef _WIN32
60 #include "lldb/Host/windows/windows.h"
61 #endif
62 
63 #include <memory>
64 #include <mutex>
65 
66 #include <assert.h>
67 #include <ctype.h>
68 #include <errno.h>
69 #include <locale.h>
70 #include <stdint.h>
71 #include <stdio.h>
72 #include <string.h>
73 #include <type_traits>
74 
75 using namespace lldb;
76 using namespace lldb_private;
77 using llvm::None;
78 using llvm::Optional;
79 using llvm::StringRef;
80 
81 // we may want curses to be disabled for some builds for instance, windows
82 #if LLDB_ENABLE_CURSES
83 
84 #define KEY_RETURN 10
85 #define KEY_ESCAPE 27
86 
87 namespace curses {
88 class Menu;
89 class MenuDelegate;
90 class Window;
91 class WindowDelegate;
92 typedef std::shared_ptr<Menu> MenuSP;
93 typedef std::shared_ptr<MenuDelegate> MenuDelegateSP;
94 typedef std::shared_ptr<Window> WindowSP;
95 typedef std::shared_ptr<WindowDelegate> WindowDelegateSP;
96 typedef std::vector<MenuSP> Menus;
97 typedef std::vector<WindowSP> Windows;
98 typedef std::vector<WindowDelegateSP> WindowDelegates;
99 
100 #if 0
101 type summary add -s "x=${var.x}, y=${var.y}" curses::Point
102 type summary add -s "w=${var.width}, h=${var.height}" curses::Size
103 type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect
104 #endif
105 
106 struct Point {
107   int x;
108   int y;
109 
110   Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}
111 
112   void Clear() {
113     x = 0;
114     y = 0;
115   }
116 
117   Point &operator+=(const Point &rhs) {
118     x += rhs.x;
119     y += rhs.y;
120     return *this;
121   }
122 
123   void Dump() { printf("(x=%i, y=%i)\n", x, y); }
124 };
125 
126 bool operator==(const Point &lhs, const Point &rhs) {
127   return lhs.x == rhs.x && lhs.y == rhs.y;
128 }
129 
130 bool operator!=(const Point &lhs, const Point &rhs) {
131   return lhs.x != rhs.x || lhs.y != rhs.y;
132 }
133 
134 struct Size {
135   int width;
136   int height;
137   Size(int w = 0, int h = 0) : width(w), height(h) {}
138 
139   void Clear() {
140     width = 0;
141     height = 0;
142   }
143 
144   void Dump() { printf("(w=%i, h=%i)\n", width, height); }
145 };
146 
147 bool operator==(const Size &lhs, const Size &rhs) {
148   return lhs.width == rhs.width && lhs.height == rhs.height;
149 }
150 
151 bool operator!=(const Size &lhs, const Size &rhs) {
152   return lhs.width != rhs.width || lhs.height != rhs.height;
153 }
154 
155 struct Rect {
156   Point origin;
157   Size size;
158 
159   Rect() : origin(), size() {}
160 
161   Rect(const Point &p, const Size &s) : origin(p), size(s) {}
162 
163   void Clear() {
164     origin.Clear();
165     size.Clear();
166   }
167 
168   void Dump() {
169     printf("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width,
170            size.height);
171   }
172 
173   void Inset(int w, int h) {
174     if (size.width > w * 2)
175       size.width -= w * 2;
176     origin.x += w;
177 
178     if (size.height > h * 2)
179       size.height -= h * 2;
180     origin.y += h;
181   }
182 
183   // Return a status bar rectangle which is the last line of this rectangle.
184   // This rectangle will be modified to not include the status bar area.
185   Rect MakeStatusBar() {
186     Rect status_bar;
187     if (size.height > 1) {
188       status_bar.origin.x = origin.x;
189       status_bar.origin.y = size.height;
190       status_bar.size.width = size.width;
191       status_bar.size.height = 1;
192       --size.height;
193     }
194     return status_bar;
195   }
196 
197   // Return a menubar rectangle which is the first line of this rectangle. This
198   // rectangle will be modified to not include the menubar area.
199   Rect MakeMenuBar() {
200     Rect menubar;
201     if (size.height > 1) {
202       menubar.origin.x = origin.x;
203       menubar.origin.y = origin.y;
204       menubar.size.width = size.width;
205       menubar.size.height = 1;
206       ++origin.y;
207       --size.height;
208     }
209     return menubar;
210   }
211 
212   void HorizontalSplitPercentage(float top_percentage, Rect &top,
213                                  Rect &bottom) const {
214     float top_height = top_percentage * size.height;
215     HorizontalSplit(top_height, top, bottom);
216   }
217 
218   void HorizontalSplit(int top_height, Rect &top, Rect &bottom) const {
219     top = *this;
220     if (top_height < size.height) {
221       top.size.height = top_height;
222       bottom.origin.x = origin.x;
223       bottom.origin.y = origin.y + top.size.height;
224       bottom.size.width = size.width;
225       bottom.size.height = size.height - top.size.height;
226     } else {
227       bottom.Clear();
228     }
229   }
230 
231   void VerticalSplitPercentage(float left_percentage, Rect &left,
232                                Rect &right) const {
233     float left_width = left_percentage * size.width;
234     VerticalSplit(left_width, left, right);
235   }
236 
237   void VerticalSplit(int left_width, Rect &left, Rect &right) const {
238     left = *this;
239     if (left_width < size.width) {
240       left.size.width = left_width;
241       right.origin.x = origin.x + left.size.width;
242       right.origin.y = origin.y;
243       right.size.width = size.width - left.size.width;
244       right.size.height = size.height;
245     } else {
246       right.Clear();
247     }
248   }
249 };
250 
251 bool operator==(const Rect &lhs, const Rect &rhs) {
252   return lhs.origin == rhs.origin && lhs.size == rhs.size;
253 }
254 
255 bool operator!=(const Rect &lhs, const Rect &rhs) {
256   return lhs.origin != rhs.origin || lhs.size != rhs.size;
257 }
258 
259 enum HandleCharResult {
260   eKeyNotHandled = 0,
261   eKeyHandled = 1,
262   eQuitApplication = 2
263 };
264 
265 enum class MenuActionResult {
266   Handled,
267   NotHandled,
268   Quit // Exit all menus and quit
269 };
270 
271 struct KeyHelp {
272   int ch;
273   const char *description;
274 };
275 
276 class WindowDelegate {
277 public:
278   virtual ~WindowDelegate() = default;
279 
280   virtual bool WindowDelegateDraw(Window &window, bool force) {
281     return false; // Drawing not handled
282   }
283 
284   virtual HandleCharResult WindowDelegateHandleChar(Window &window, int key) {
285     return eKeyNotHandled;
286   }
287 
288   virtual const char *WindowDelegateGetHelpText() { return nullptr; }
289 
290   virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; }
291 };
292 
293 class HelpDialogDelegate : public WindowDelegate {
294 public:
295   HelpDialogDelegate(const char *text, KeyHelp *key_help_array);
296 
297   ~HelpDialogDelegate() override;
298 
299   bool WindowDelegateDraw(Window &window, bool force) override;
300 
301   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
302 
303   size_t GetNumLines() const { return m_text.GetSize(); }
304 
305   size_t GetMaxLineLength() const { return m_text.GetMaxStringLength(); }
306 
307 protected:
308   StringList m_text;
309   int m_first_visible_line;
310 };
311 
312 class Window {
313 public:
314   Window(const char *name)
315       : m_name(name), m_window(nullptr), m_panel(nullptr), m_parent(nullptr),
316         m_subwindows(), m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX),
317         m_prev_active_window_idx(UINT32_MAX), m_delete(false),
318         m_needs_update(true), m_can_activate(true), m_is_subwin(false) {}
319 
320   Window(const char *name, WINDOW *w, bool del = true)
321       : m_name(name), m_window(nullptr), m_panel(nullptr), m_parent(nullptr),
322         m_subwindows(), m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX),
323         m_prev_active_window_idx(UINT32_MAX), m_delete(del),
324         m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
325     if (w)
326       Reset(w);
327   }
328 
329   Window(const char *name, const Rect &bounds)
330       : m_name(name), m_window(nullptr), m_parent(nullptr), m_subwindows(),
331         m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX),
332         m_prev_active_window_idx(UINT32_MAX), m_delete(true),
333         m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
334     Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y,
335                    bounds.origin.y));
336   }
337 
338   virtual ~Window() {
339     RemoveSubWindows();
340     Reset();
341   }
342 
343   void Reset(WINDOW *w = nullptr, bool del = true) {
344     if (m_window == w)
345       return;
346 
347     if (m_panel) {
348       ::del_panel(m_panel);
349       m_panel = nullptr;
350     }
351     if (m_window && m_delete) {
352       ::delwin(m_window);
353       m_window = nullptr;
354       m_delete = false;
355     }
356     if (w) {
357       m_window = w;
358       m_panel = ::new_panel(m_window);
359       m_delete = del;
360     }
361   }
362 
363   void AttributeOn(attr_t attr) { ::wattron(m_window, attr); }
364   void AttributeOff(attr_t attr) { ::wattroff(m_window, attr); }
365   void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
366     ::box(m_window, v_char, h_char);
367   }
368   void Clear() { ::wclear(m_window); }
369   void Erase() { ::werase(m_window); }
370   Rect GetBounds() {
371     return Rect(GetParentOrigin(), GetSize());
372   } // Get the rectangle in our parent window
373   int GetChar() { return ::wgetch(m_window); }
374   int GetCursorX() { return getcurx(m_window); }
375   int GetCursorY() { return getcury(m_window); }
376   Rect GetFrame() {
377     return Rect(Point(), GetSize());
378   } // Get our rectangle in our own coordinate system
379   Point GetParentOrigin() { return Point(GetParentX(), GetParentY()); }
380   Size GetSize() { return Size(GetWidth(), GetHeight()); }
381   int GetParentX() { return getparx(m_window); }
382   int GetParentY() { return getpary(m_window); }
383   int GetMaxX() { return getmaxx(m_window); }
384   int GetMaxY() { return getmaxy(m_window); }
385   int GetWidth() { return GetMaxX(); }
386   int GetHeight() { return GetMaxY(); }
387   void MoveCursor(int x, int y) { ::wmove(m_window, y, x); }
388   void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); }
389   void Resize(int w, int h) { ::wresize(m_window, h, w); }
390   void Resize(const Size &size) {
391     ::wresize(m_window, size.height, size.width);
392   }
393   void PutChar(int ch) { ::waddch(m_window, ch); }
394   void PutCString(const char *s, int len = -1) { ::waddnstr(m_window, s, len); }
395   void SetBackground(int color_pair_idx) {
396     ::wbkgd(m_window, COLOR_PAIR(color_pair_idx));
397   }
398 
399   void PutCStringTruncated(const char *s, int right_pad) {
400     int bytes_left = GetWidth() - GetCursorX();
401     if (bytes_left > right_pad) {
402       bytes_left -= right_pad;
403       ::waddnstr(m_window, s, bytes_left);
404     }
405   }
406 
407   void MoveWindow(const Point &origin) {
408     const bool moving_window = origin != GetParentOrigin();
409     if (m_is_subwin && moving_window) {
410       // Can't move subwindows, must delete and re-create
411       Size size = GetSize();
412       Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y,
413                      origin.x),
414             true);
415     } else {
416       ::mvwin(m_window, origin.y, origin.x);
417     }
418   }
419 
420   void SetBounds(const Rect &bounds) {
421     const bool moving_window = bounds.origin != GetParentOrigin();
422     if (m_is_subwin && moving_window) {
423       // Can't move subwindows, must delete and re-create
424       Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width,
425                      bounds.origin.y, bounds.origin.x),
426             true);
427     } else {
428       if (moving_window)
429         MoveWindow(bounds.origin);
430       Resize(bounds.size);
431     }
432   }
433 
434   void Printf(const char *format, ...) __attribute__((format(printf, 2, 3))) {
435     va_list args;
436     va_start(args, format);
437     vwprintw(m_window, format, args);
438     va_end(args);
439   }
440 
441   void Touch() {
442     ::touchwin(m_window);
443     if (m_parent)
444       m_parent->Touch();
445   }
446 
447   WindowSP CreateSubWindow(const char *name, const Rect &bounds,
448                            bool make_active) {
449     auto get_window = [this, &bounds]() {
450       return m_window
451                  ? ::subwin(m_window, bounds.size.height, bounds.size.width,
452                             bounds.origin.y, bounds.origin.x)
453                  : ::newwin(bounds.size.height, bounds.size.width,
454                             bounds.origin.y, bounds.origin.x);
455     };
456     WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true);
457     subwindow_sp->m_is_subwin = subwindow_sp.operator bool();
458     subwindow_sp->m_parent = this;
459     if (make_active) {
460       m_prev_active_window_idx = m_curr_active_window_idx;
461       m_curr_active_window_idx = m_subwindows.size();
462     }
463     m_subwindows.push_back(subwindow_sp);
464     ::top_panel(subwindow_sp->m_panel);
465     m_needs_update = true;
466     return subwindow_sp;
467   }
468 
469   bool RemoveSubWindow(Window *window) {
470     Windows::iterator pos, end = m_subwindows.end();
471     size_t i = 0;
472     for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
473       if ((*pos).get() == window) {
474         if (m_prev_active_window_idx == i)
475           m_prev_active_window_idx = UINT32_MAX;
476         else if (m_prev_active_window_idx != UINT32_MAX &&
477                  m_prev_active_window_idx > i)
478           --m_prev_active_window_idx;
479 
480         if (m_curr_active_window_idx == i)
481           m_curr_active_window_idx = UINT32_MAX;
482         else if (m_curr_active_window_idx != UINT32_MAX &&
483                  m_curr_active_window_idx > i)
484           --m_curr_active_window_idx;
485         window->Erase();
486         m_subwindows.erase(pos);
487         m_needs_update = true;
488         if (m_parent)
489           m_parent->Touch();
490         else
491           ::touchwin(stdscr);
492         return true;
493       }
494     }
495     return false;
496   }
497 
498   WindowSP FindSubWindow(const char *name) {
499     Windows::iterator pos, end = m_subwindows.end();
500     size_t i = 0;
501     for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
502       if ((*pos)->m_name == name)
503         return *pos;
504     }
505     return WindowSP();
506   }
507 
508   void RemoveSubWindows() {
509     m_curr_active_window_idx = UINT32_MAX;
510     m_prev_active_window_idx = UINT32_MAX;
511     for (Windows::iterator pos = m_subwindows.begin();
512          pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) {
513       (*pos)->Erase();
514     }
515     if (m_parent)
516       m_parent->Touch();
517     else
518       ::touchwin(stdscr);
519   }
520 
521   WINDOW *get() { return m_window; }
522 
523   operator WINDOW *() { return m_window; }
524 
525   // Window drawing utilities
526   void DrawTitleBox(const char *title, const char *bottom_message = nullptr) {
527     attr_t attr = 0;
528     if (IsActive())
529       attr = A_BOLD | COLOR_PAIR(2);
530     else
531       attr = 0;
532     if (attr)
533       AttributeOn(attr);
534 
535     Box();
536     MoveCursor(3, 0);
537 
538     if (title && title[0]) {
539       PutChar('<');
540       PutCString(title);
541       PutChar('>');
542     }
543 
544     if (bottom_message && bottom_message[0]) {
545       int bottom_message_length = strlen(bottom_message);
546       int x = GetWidth() - 3 - (bottom_message_length + 2);
547 
548       if (x > 0) {
549         MoveCursor(x, GetHeight() - 1);
550         PutChar('[');
551         PutCString(bottom_message);
552         PutChar(']');
553       } else {
554         MoveCursor(1, GetHeight() - 1);
555         PutChar('[');
556         PutCStringTruncated(bottom_message, 1);
557       }
558     }
559     if (attr)
560       AttributeOff(attr);
561   }
562 
563   virtual void Draw(bool force) {
564     if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force))
565       return;
566 
567     for (auto &subwindow_sp : m_subwindows)
568       subwindow_sp->Draw(force);
569   }
570 
571   bool CreateHelpSubwindow() {
572     if (m_delegate_sp) {
573       const char *text = m_delegate_sp->WindowDelegateGetHelpText();
574       KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp();
575       if ((text && text[0]) || key_help) {
576         std::unique_ptr<HelpDialogDelegate> help_delegate_up(
577             new HelpDialogDelegate(text, key_help));
578         const size_t num_lines = help_delegate_up->GetNumLines();
579         const size_t max_length = help_delegate_up->GetMaxLineLength();
580         Rect bounds = GetBounds();
581         bounds.Inset(1, 1);
582         if (max_length + 4 < static_cast<size_t>(bounds.size.width)) {
583           bounds.origin.x += (bounds.size.width - max_length + 4) / 2;
584           bounds.size.width = max_length + 4;
585         } else {
586           if (bounds.size.width > 100) {
587             const int inset_w = bounds.size.width / 4;
588             bounds.origin.x += inset_w;
589             bounds.size.width -= 2 * inset_w;
590           }
591         }
592 
593         if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) {
594           bounds.origin.y += (bounds.size.height - num_lines + 2) / 2;
595           bounds.size.height = num_lines + 2;
596         } else {
597           if (bounds.size.height > 100) {
598             const int inset_h = bounds.size.height / 4;
599             bounds.origin.y += inset_h;
600             bounds.size.height -= 2 * inset_h;
601           }
602         }
603         WindowSP help_window_sp;
604         Window *parent_window = GetParent();
605         if (parent_window)
606           help_window_sp = parent_window->CreateSubWindow("Help", bounds, true);
607         else
608           help_window_sp = CreateSubWindow("Help", bounds, true);
609         help_window_sp->SetDelegate(
610             WindowDelegateSP(help_delegate_up.release()));
611         return true;
612       }
613     }
614     return false;
615   }
616 
617   virtual HandleCharResult HandleChar(int key) {
618     // Always check the active window first
619     HandleCharResult result = eKeyNotHandled;
620     WindowSP active_window_sp = GetActiveWindow();
621     if (active_window_sp) {
622       result = active_window_sp->HandleChar(key);
623       if (result != eKeyNotHandled)
624         return result;
625     }
626 
627     if (m_delegate_sp) {
628       result = m_delegate_sp->WindowDelegateHandleChar(*this, key);
629       if (result != eKeyNotHandled)
630         return result;
631     }
632 
633     // Then check for any windows that want any keys that weren't handled. This
634     // is typically only for a menubar. Make a copy of the subwindows in case
635     // any HandleChar() functions muck with the subwindows. If we don't do
636     // this, we can crash when iterating over the subwindows.
637     Windows subwindows(m_subwindows);
638     for (auto subwindow_sp : subwindows) {
639       if (!subwindow_sp->m_can_activate) {
640         HandleCharResult result = subwindow_sp->HandleChar(key);
641         if (result != eKeyNotHandled)
642           return result;
643       }
644     }
645 
646     return eKeyNotHandled;
647   }
648 
649   WindowSP GetActiveWindow() {
650     if (!m_subwindows.empty()) {
651       if (m_curr_active_window_idx >= m_subwindows.size()) {
652         if (m_prev_active_window_idx < m_subwindows.size()) {
653           m_curr_active_window_idx = m_prev_active_window_idx;
654           m_prev_active_window_idx = UINT32_MAX;
655         } else if (IsActive()) {
656           m_prev_active_window_idx = UINT32_MAX;
657           m_curr_active_window_idx = UINT32_MAX;
658 
659           // Find first window that wants to be active if this window is active
660           const size_t num_subwindows = m_subwindows.size();
661           for (size_t i = 0; i < num_subwindows; ++i) {
662             if (m_subwindows[i]->GetCanBeActive()) {
663               m_curr_active_window_idx = i;
664               break;
665             }
666           }
667         }
668       }
669 
670       if (m_curr_active_window_idx < m_subwindows.size())
671         return m_subwindows[m_curr_active_window_idx];
672     }
673     return WindowSP();
674   }
675 
676   bool GetCanBeActive() const { return m_can_activate; }
677 
678   void SetCanBeActive(bool b) { m_can_activate = b; }
679 
680   void SetDelegate(const WindowDelegateSP &delegate_sp) {
681     m_delegate_sp = delegate_sp;
682   }
683 
684   Window *GetParent() const { return m_parent; }
685 
686   bool IsActive() const {
687     if (m_parent)
688       return m_parent->GetActiveWindow().get() == this;
689     else
690       return true; // Top level window is always active
691   }
692 
693   void SelectNextWindowAsActive() {
694     // Move active focus to next window
695     const int num_subwindows = m_subwindows.size();
696     int start_idx = 0;
697     if (m_curr_active_window_idx != UINT32_MAX) {
698       m_prev_active_window_idx = m_curr_active_window_idx;
699       start_idx = m_curr_active_window_idx + 1;
700     }
701     for (int idx = start_idx; idx < num_subwindows; ++idx) {
702       if (m_subwindows[idx]->GetCanBeActive()) {
703         m_curr_active_window_idx = idx;
704         return;
705       }
706     }
707     for (int idx = 0; idx < start_idx; ++idx) {
708       if (m_subwindows[idx]->GetCanBeActive()) {
709         m_curr_active_window_idx = idx;
710         break;
711       }
712     }
713   }
714 
715   void SelectPreviousWindowAsActive() {
716     // Move active focus to previous window
717     const int num_subwindows = m_subwindows.size();
718     int start_idx = num_subwindows - 1;
719     if (m_curr_active_window_idx != UINT32_MAX) {
720       m_prev_active_window_idx = m_curr_active_window_idx;
721       start_idx = m_curr_active_window_idx - 1;
722     }
723     for (int idx = start_idx; idx >= 0; --idx) {
724       if (m_subwindows[idx]->GetCanBeActive()) {
725         m_curr_active_window_idx = idx;
726         return;
727       }
728     }
729     for (int idx = num_subwindows - 1; idx > start_idx; --idx) {
730       if (m_subwindows[idx]->GetCanBeActive()) {
731         m_curr_active_window_idx = idx;
732         break;
733       }
734     }
735   }
736 
737   const char *GetName() const { return m_name.c_str(); }
738 
739 protected:
740   std::string m_name;
741   WINDOW *m_window;
742   PANEL *m_panel;
743   Window *m_parent;
744   Windows m_subwindows;
745   WindowDelegateSP m_delegate_sp;
746   uint32_t m_curr_active_window_idx;
747   uint32_t m_prev_active_window_idx;
748   bool m_delete;
749   bool m_needs_update;
750   bool m_can_activate;
751   bool m_is_subwin;
752 
753 private:
754   Window(const Window &) = delete;
755   const Window &operator=(const Window &) = delete;
756 };
757 
758 class MenuDelegate {
759 public:
760   virtual ~MenuDelegate() = default;
761 
762   virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0;
763 };
764 
765 class Menu : public WindowDelegate {
766 public:
767   enum class Type { Invalid, Bar, Item, Separator };
768 
769   // Menubar or separator constructor
770   Menu(Type type);
771 
772   // Menuitem constructor
773   Menu(const char *name, const char *key_name, int key_value,
774        uint64_t identifier);
775 
776   ~Menu() override = default;
777 
778   const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; }
779 
780   void SetDelegate(const MenuDelegateSP &delegate_sp) {
781     m_delegate_sp = delegate_sp;
782   }
783 
784   void RecalculateNameLengths();
785 
786   void AddSubmenu(const MenuSP &menu_sp);
787 
788   int DrawAndRunMenu(Window &window);
789 
790   void DrawMenuTitle(Window &window, bool highlight);
791 
792   bool WindowDelegateDraw(Window &window, bool force) override;
793 
794   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
795 
796   MenuActionResult ActionPrivate(Menu &menu) {
797     MenuActionResult result = MenuActionResult::NotHandled;
798     if (m_delegate_sp) {
799       result = m_delegate_sp->MenuDelegateAction(menu);
800       if (result != MenuActionResult::NotHandled)
801         return result;
802     } else if (m_parent) {
803       result = m_parent->ActionPrivate(menu);
804       if (result != MenuActionResult::NotHandled)
805         return result;
806     }
807     return m_canned_result;
808   }
809 
810   MenuActionResult Action() {
811     // Call the recursive action so it can try to handle it with the menu
812     // delegate, and if not, try our parent menu
813     return ActionPrivate(*this);
814   }
815 
816   void SetCannedResult(MenuActionResult result) { m_canned_result = result; }
817 
818   Menus &GetSubmenus() { return m_submenus; }
819 
820   const Menus &GetSubmenus() const { return m_submenus; }
821 
822   int GetSelectedSubmenuIndex() const { return m_selected; }
823 
824   void SetSelectedSubmenuIndex(int idx) { m_selected = idx; }
825 
826   Type GetType() const { return m_type; }
827 
828   int GetStartingColumn() const { return m_start_col; }
829 
830   void SetStartingColumn(int col) { m_start_col = col; }
831 
832   int GetKeyValue() const { return m_key_value; }
833 
834   std::string &GetName() { return m_name; }
835 
836   int GetDrawWidth() const {
837     return m_max_submenu_name_length + m_max_submenu_key_name_length + 8;
838   }
839 
840   uint64_t GetIdentifier() const { return m_identifier; }
841 
842   void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
843 
844 protected:
845   std::string m_name;
846   std::string m_key_name;
847   uint64_t m_identifier;
848   Type m_type;
849   int m_key_value;
850   int m_start_col;
851   int m_max_submenu_name_length;
852   int m_max_submenu_key_name_length;
853   int m_selected;
854   Menu *m_parent;
855   Menus m_submenus;
856   WindowSP m_menu_window_sp;
857   MenuActionResult m_canned_result;
858   MenuDelegateSP m_delegate_sp;
859 };
860 
861 // Menubar or separator constructor
862 Menu::Menu(Type type)
863     : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0),
864       m_start_col(0), m_max_submenu_name_length(0),
865       m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
866       m_submenus(), m_canned_result(MenuActionResult::NotHandled),
867       m_delegate_sp() {}
868 
869 // Menuitem constructor
870 Menu::Menu(const char *name, const char *key_name, int key_value,
871            uint64_t identifier)
872     : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid),
873       m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0),
874       m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
875       m_submenus(), m_canned_result(MenuActionResult::NotHandled),
876       m_delegate_sp() {
877   if (name && name[0]) {
878     m_name = name;
879     m_type = Type::Item;
880     if (key_name && key_name[0])
881       m_key_name = key_name;
882   } else {
883     m_type = Type::Separator;
884   }
885 }
886 
887 void Menu::RecalculateNameLengths() {
888   m_max_submenu_name_length = 0;
889   m_max_submenu_key_name_length = 0;
890   Menus &submenus = GetSubmenus();
891   const size_t num_submenus = submenus.size();
892   for (size_t i = 0; i < num_submenus; ++i) {
893     Menu *submenu = submenus[i].get();
894     if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size())
895       m_max_submenu_name_length = submenu->m_name.size();
896     if (static_cast<size_t>(m_max_submenu_key_name_length) <
897         submenu->m_key_name.size())
898       m_max_submenu_key_name_length = submenu->m_key_name.size();
899   }
900 }
901 
902 void Menu::AddSubmenu(const MenuSP &menu_sp) {
903   menu_sp->m_parent = this;
904   if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size())
905     m_max_submenu_name_length = menu_sp->m_name.size();
906   if (static_cast<size_t>(m_max_submenu_key_name_length) <
907       menu_sp->m_key_name.size())
908     m_max_submenu_key_name_length = menu_sp->m_key_name.size();
909   m_submenus.push_back(menu_sp);
910 }
911 
912 void Menu::DrawMenuTitle(Window &window, bool highlight) {
913   if (m_type == Type::Separator) {
914     window.MoveCursor(0, window.GetCursorY());
915     window.PutChar(ACS_LTEE);
916     int width = window.GetWidth();
917     if (width > 2) {
918       width -= 2;
919       for (int i = 0; i < width; ++i)
920         window.PutChar(ACS_HLINE);
921     }
922     window.PutChar(ACS_RTEE);
923   } else {
924     const int shortcut_key = m_key_value;
925     bool underlined_shortcut = false;
926     const attr_t highlight_attr = A_REVERSE;
927     if (highlight)
928       window.AttributeOn(highlight_attr);
929     if (llvm::isPrint(shortcut_key)) {
930       size_t lower_pos = m_name.find(tolower(shortcut_key));
931       size_t upper_pos = m_name.find(toupper(shortcut_key));
932       const char *name = m_name.c_str();
933       size_t pos = std::min<size_t>(lower_pos, upper_pos);
934       if (pos != std::string::npos) {
935         underlined_shortcut = true;
936         if (pos > 0) {
937           window.PutCString(name, pos);
938           name += pos;
939         }
940         const attr_t shortcut_attr = A_UNDERLINE | A_BOLD;
941         window.AttributeOn(shortcut_attr);
942         window.PutChar(name[0]);
943         window.AttributeOff(shortcut_attr);
944         name++;
945         if (name[0])
946           window.PutCString(name);
947       }
948     }
949 
950     if (!underlined_shortcut) {
951       window.PutCString(m_name.c_str());
952     }
953 
954     if (highlight)
955       window.AttributeOff(highlight_attr);
956 
957     if (m_key_name.empty()) {
958       if (!underlined_shortcut && llvm::isPrint(m_key_value)) {
959         window.AttributeOn(COLOR_PAIR(3));
960         window.Printf(" (%c)", m_key_value);
961         window.AttributeOff(COLOR_PAIR(3));
962       }
963     } else {
964       window.AttributeOn(COLOR_PAIR(3));
965       window.Printf(" (%s)", m_key_name.c_str());
966       window.AttributeOff(COLOR_PAIR(3));
967     }
968   }
969 }
970 
971 bool Menu::WindowDelegateDraw(Window &window, bool force) {
972   Menus &submenus = GetSubmenus();
973   const size_t num_submenus = submenus.size();
974   const int selected_idx = GetSelectedSubmenuIndex();
975   Menu::Type menu_type = GetType();
976   switch (menu_type) {
977   case Menu::Type::Bar: {
978     window.SetBackground(2);
979     window.MoveCursor(0, 0);
980     for (size_t i = 0; i < num_submenus; ++i) {
981       Menu *menu = submenus[i].get();
982       if (i > 0)
983         window.PutChar(' ');
984       menu->SetStartingColumn(window.GetCursorX());
985       window.PutCString("| ");
986       menu->DrawMenuTitle(window, false);
987     }
988     window.PutCString(" |");
989   } break;
990 
991   case Menu::Type::Item: {
992     int y = 1;
993     int x = 3;
994     // Draw the menu
995     int cursor_x = 0;
996     int cursor_y = 0;
997     window.Erase();
998     window.SetBackground(2);
999     window.Box();
1000     for (size_t i = 0; i < num_submenus; ++i) {
1001       const bool is_selected = (i == static_cast<size_t>(selected_idx));
1002       window.MoveCursor(x, y + i);
1003       if (is_selected) {
1004         // Remember where we want the cursor to be
1005         cursor_x = x - 1;
1006         cursor_y = y + i;
1007       }
1008       submenus[i]->DrawMenuTitle(window, is_selected);
1009     }
1010     window.MoveCursor(cursor_x, cursor_y);
1011   } break;
1012 
1013   default:
1014   case Menu::Type::Separator:
1015     break;
1016   }
1017   return true; // Drawing handled...
1018 }
1019 
1020 HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) {
1021   HandleCharResult result = eKeyNotHandled;
1022 
1023   Menus &submenus = GetSubmenus();
1024   const size_t num_submenus = submenus.size();
1025   const int selected_idx = GetSelectedSubmenuIndex();
1026   Menu::Type menu_type = GetType();
1027   if (menu_type == Menu::Type::Bar) {
1028     MenuSP run_menu_sp;
1029     switch (key) {
1030     case KEY_DOWN:
1031     case KEY_UP:
1032       // Show last menu or first menu
1033       if (selected_idx < static_cast<int>(num_submenus))
1034         run_menu_sp = submenus[selected_idx];
1035       else if (!submenus.empty())
1036         run_menu_sp = submenus.front();
1037       result = eKeyHandled;
1038       break;
1039 
1040     case KEY_RIGHT:
1041       ++m_selected;
1042       if (m_selected >= static_cast<int>(num_submenus))
1043         m_selected = 0;
1044       if (m_selected < static_cast<int>(num_submenus))
1045         run_menu_sp = submenus[m_selected];
1046       else if (!submenus.empty())
1047         run_menu_sp = submenus.front();
1048       result = eKeyHandled;
1049       break;
1050 
1051     case KEY_LEFT:
1052       --m_selected;
1053       if (m_selected < 0)
1054         m_selected = num_submenus - 1;
1055       if (m_selected < static_cast<int>(num_submenus))
1056         run_menu_sp = submenus[m_selected];
1057       else if (!submenus.empty())
1058         run_menu_sp = submenus.front();
1059       result = eKeyHandled;
1060       break;
1061 
1062     default:
1063       for (size_t i = 0; i < num_submenus; ++i) {
1064         if (submenus[i]->GetKeyValue() == key) {
1065           SetSelectedSubmenuIndex(i);
1066           run_menu_sp = submenus[i];
1067           result = eKeyHandled;
1068           break;
1069         }
1070       }
1071       break;
1072     }
1073 
1074     if (run_menu_sp) {
1075       // Run the action on this menu in case we need to populate the menu with
1076       // dynamic content and also in case check marks, and any other menu
1077       // decorations need to be calculated
1078       if (run_menu_sp->Action() == MenuActionResult::Quit)
1079         return eQuitApplication;
1080 
1081       Rect menu_bounds;
1082       menu_bounds.origin.x = run_menu_sp->GetStartingColumn();
1083       menu_bounds.origin.y = 1;
1084       menu_bounds.size.width = run_menu_sp->GetDrawWidth();
1085       menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2;
1086       if (m_menu_window_sp)
1087         window.GetParent()->RemoveSubWindow(m_menu_window_sp.get());
1088 
1089       m_menu_window_sp = window.GetParent()->CreateSubWindow(
1090           run_menu_sp->GetName().c_str(), menu_bounds, true);
1091       m_menu_window_sp->SetDelegate(run_menu_sp);
1092     }
1093   } else if (menu_type == Menu::Type::Item) {
1094     switch (key) {
1095     case KEY_DOWN:
1096       if (m_submenus.size() > 1) {
1097         const int start_select = m_selected;
1098         while (++m_selected != start_select) {
1099           if (static_cast<size_t>(m_selected) >= num_submenus)
1100             m_selected = 0;
1101           if (m_submenus[m_selected]->GetType() == Type::Separator)
1102             continue;
1103           else
1104             break;
1105         }
1106         return eKeyHandled;
1107       }
1108       break;
1109 
1110     case KEY_UP:
1111       if (m_submenus.size() > 1) {
1112         const int start_select = m_selected;
1113         while (--m_selected != start_select) {
1114           if (m_selected < static_cast<int>(0))
1115             m_selected = num_submenus - 1;
1116           if (m_submenus[m_selected]->GetType() == Type::Separator)
1117             continue;
1118           else
1119             break;
1120         }
1121         return eKeyHandled;
1122       }
1123       break;
1124 
1125     case KEY_RETURN:
1126       if (static_cast<size_t>(selected_idx) < num_submenus) {
1127         if (submenus[selected_idx]->Action() == MenuActionResult::Quit)
1128           return eQuitApplication;
1129         window.GetParent()->RemoveSubWindow(&window);
1130         return eKeyHandled;
1131       }
1132       break;
1133 
1134     case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in
1135                      // case other chars are entered for escaped sequences
1136       window.GetParent()->RemoveSubWindow(&window);
1137       return eKeyHandled;
1138 
1139     default:
1140       for (size_t i = 0; i < num_submenus; ++i) {
1141         Menu *menu = submenus[i].get();
1142         if (menu->GetKeyValue() == key) {
1143           SetSelectedSubmenuIndex(i);
1144           window.GetParent()->RemoveSubWindow(&window);
1145           if (menu->Action() == MenuActionResult::Quit)
1146             return eQuitApplication;
1147           return eKeyHandled;
1148         }
1149       }
1150       break;
1151     }
1152   } else if (menu_type == Menu::Type::Separator) {
1153   }
1154   return result;
1155 }
1156 
1157 class Application {
1158 public:
1159   Application(FILE *in, FILE *out)
1160       : m_window_sp(), m_screen(nullptr), m_in(in), m_out(out) {}
1161 
1162   ~Application() {
1163     m_window_delegates.clear();
1164     m_window_sp.reset();
1165     if (m_screen) {
1166       ::delscreen(m_screen);
1167       m_screen = nullptr;
1168     }
1169   }
1170 
1171   void Initialize() {
1172     ::setlocale(LC_ALL, "");
1173     ::setlocale(LC_CTYPE, "");
1174     m_screen = ::newterm(nullptr, m_out, m_in);
1175     ::start_color();
1176     ::curs_set(0);
1177     ::noecho();
1178     ::keypad(stdscr, TRUE);
1179   }
1180 
1181   void Terminate() { ::endwin(); }
1182 
1183   void Run(Debugger &debugger) {
1184     bool done = false;
1185     int delay_in_tenths_of_a_second = 1;
1186 
1187     // Alas the threading model in curses is a bit lame so we need to resort to
1188     // polling every 0.5 seconds. We could poll for stdin ourselves and then
1189     // pass the keys down but then we need to translate all of the escape
1190     // sequences ourselves. So we resort to polling for input because we need
1191     // to receive async process events while in this loop.
1192 
1193     halfdelay(delay_in_tenths_of_a_second); // Poll using some number of tenths
1194                                             // of seconds seconds when calling
1195                                             // Window::GetChar()
1196 
1197     ListenerSP listener_sp(
1198         Listener::MakeListener("lldb.IOHandler.curses.Application"));
1199     ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass());
1200     debugger.EnableForwardEvents(listener_sp);
1201 
1202     m_update_screen = true;
1203 #if defined(__APPLE__)
1204     std::deque<int> escape_chars;
1205 #endif
1206 
1207     while (!done) {
1208       if (m_update_screen) {
1209         m_window_sp->Draw(false);
1210         // All windows should be calling Window::DeferredRefresh() instead of
1211         // Window::Refresh() so we can do a single update and avoid any screen
1212         // blinking
1213         update_panels();
1214 
1215         // Cursor hiding isn't working on MacOSX, so hide it in the top left
1216         // corner
1217         m_window_sp->MoveCursor(0, 0);
1218 
1219         doupdate();
1220         m_update_screen = false;
1221       }
1222 
1223 #if defined(__APPLE__)
1224       // Terminal.app doesn't map its function keys correctly, F1-F4 default
1225       // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if
1226       // possible
1227       int ch;
1228       if (escape_chars.empty())
1229         ch = m_window_sp->GetChar();
1230       else {
1231         ch = escape_chars.front();
1232         escape_chars.pop_front();
1233       }
1234       if (ch == KEY_ESCAPE) {
1235         int ch2 = m_window_sp->GetChar();
1236         if (ch2 == 'O') {
1237           int ch3 = m_window_sp->GetChar();
1238           switch (ch3) {
1239           case 'P':
1240             ch = KEY_F(1);
1241             break;
1242           case 'Q':
1243             ch = KEY_F(2);
1244             break;
1245           case 'R':
1246             ch = KEY_F(3);
1247             break;
1248           case 'S':
1249             ch = KEY_F(4);
1250             break;
1251           default:
1252             escape_chars.push_back(ch2);
1253             if (ch3 != -1)
1254               escape_chars.push_back(ch3);
1255             break;
1256           }
1257         } else if (ch2 != -1)
1258           escape_chars.push_back(ch2);
1259       }
1260 #else
1261       int ch = m_window_sp->GetChar();
1262 
1263 #endif
1264       if (ch == -1) {
1265         if (feof(m_in) || ferror(m_in)) {
1266           done = true;
1267         } else {
1268           // Just a timeout from using halfdelay(), check for events
1269           EventSP event_sp;
1270           while (listener_sp->PeekAtNextEvent()) {
1271             listener_sp->GetEvent(event_sp, std::chrono::seconds(0));
1272 
1273             if (event_sp) {
1274               Broadcaster *broadcaster = event_sp->GetBroadcaster();
1275               if (broadcaster) {
1276                 // uint32_t event_type = event_sp->GetType();
1277                 ConstString broadcaster_class(
1278                     broadcaster->GetBroadcasterClass());
1279                 if (broadcaster_class == broadcaster_class_process) {
1280                   debugger.GetCommandInterpreter().UpdateExecutionContext(
1281                       nullptr);
1282                   m_update_screen = true;
1283                   continue; // Don't get any key, just update our view
1284                 }
1285               }
1286             }
1287           }
1288         }
1289       } else {
1290         HandleCharResult key_result = m_window_sp->HandleChar(ch);
1291         switch (key_result) {
1292         case eKeyHandled:
1293           debugger.GetCommandInterpreter().UpdateExecutionContext(nullptr);
1294           m_update_screen = true;
1295           break;
1296         case eKeyNotHandled:
1297           if (ch == 12) { // Ctrl+L, force full redraw
1298             redrawwin(m_window_sp->get());
1299             m_update_screen = true;
1300           }
1301           break;
1302         case eQuitApplication:
1303           done = true;
1304           break;
1305         }
1306       }
1307     }
1308 
1309     debugger.CancelForwardEvents(listener_sp);
1310   }
1311 
1312   WindowSP &GetMainWindow() {
1313     if (!m_window_sp)
1314       m_window_sp = std::make_shared<Window>("main", stdscr, false);
1315     return m_window_sp;
1316   }
1317 
1318   void TerminalSizeChanged() {
1319     ::endwin();
1320     ::refresh();
1321     Rect content_bounds = m_window_sp->GetFrame();
1322     m_window_sp->SetBounds(content_bounds);
1323     if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar"))
1324       menubar_window_sp->SetBounds(content_bounds.MakeMenuBar());
1325     if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status"))
1326       status_window_sp->SetBounds(content_bounds.MakeStatusBar());
1327 
1328     WindowSP source_window_sp = m_window_sp->FindSubWindow("Source");
1329     WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables");
1330     WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers");
1331     WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads");
1332 
1333     Rect threads_bounds;
1334     Rect source_variables_bounds;
1335     content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
1336                                            threads_bounds);
1337     if (threads_window_sp)
1338       threads_window_sp->SetBounds(threads_bounds);
1339     else
1340       source_variables_bounds = content_bounds;
1341 
1342     Rect source_bounds;
1343     Rect variables_registers_bounds;
1344     source_variables_bounds.HorizontalSplitPercentage(
1345         0.70, source_bounds, variables_registers_bounds);
1346     if (variables_window_sp || registers_window_sp) {
1347       if (variables_window_sp && registers_window_sp) {
1348         Rect variables_bounds;
1349         Rect registers_bounds;
1350         variables_registers_bounds.VerticalSplitPercentage(
1351             0.50, variables_bounds, registers_bounds);
1352         variables_window_sp->SetBounds(variables_bounds);
1353         registers_window_sp->SetBounds(registers_bounds);
1354       } else if (variables_window_sp) {
1355         variables_window_sp->SetBounds(variables_registers_bounds);
1356       } else {
1357         registers_window_sp->SetBounds(variables_registers_bounds);
1358       }
1359     } else {
1360       source_bounds = source_variables_bounds;
1361     }
1362 
1363     source_window_sp->SetBounds(source_bounds);
1364 
1365     touchwin(stdscr);
1366     redrawwin(m_window_sp->get());
1367     m_update_screen = true;
1368   }
1369 
1370 protected:
1371   WindowSP m_window_sp;
1372   WindowDelegates m_window_delegates;
1373   SCREEN *m_screen;
1374   FILE *m_in;
1375   FILE *m_out;
1376   bool m_update_screen = false;
1377 };
1378 
1379 } // namespace curses
1380 
1381 using namespace curses;
1382 
1383 struct Row {
1384   ValueObjectManager value;
1385   Row *parent;
1386   // The process stop ID when the children were calculated.
1387   uint32_t children_stop_id;
1388   int row_idx;
1389   int x;
1390   int y;
1391   bool might_have_children;
1392   bool expanded;
1393   bool calculated_children;
1394   std::vector<Row> children;
1395 
1396   Row(const ValueObjectSP &v, Row *p)
1397       : value(v, lldb::eDynamicDontRunTarget, true), parent(p), row_idx(0),
1398         x(1), y(1), might_have_children(v ? v->MightHaveChildren() : false),
1399         expanded(false), calculated_children(false), children() {}
1400 
1401   size_t GetDepth() const {
1402     if (parent)
1403       return 1 + parent->GetDepth();
1404     return 0;
1405   }
1406 
1407   void Expand() { expanded = true; }
1408 
1409   std::vector<Row> &GetChildren() {
1410     ProcessSP process_sp = value.GetProcessSP();
1411     auto stop_id = process_sp->GetStopID();
1412     if (process_sp && stop_id != children_stop_id) {
1413       children_stop_id = stop_id;
1414       calculated_children = false;
1415     }
1416     if (!calculated_children) {
1417       children.clear();
1418       calculated_children = true;
1419       ValueObjectSP valobj = value.GetSP();
1420       if (valobj) {
1421         const size_t num_children = valobj->GetNumChildren();
1422         for (size_t i = 0; i < num_children; ++i) {
1423           children.push_back(Row(valobj->GetChildAtIndex(i, true), this));
1424         }
1425       }
1426     }
1427     return children;
1428   }
1429 
1430   void Unexpand() {
1431     expanded = false;
1432     calculated_children = false;
1433     children.clear();
1434   }
1435 
1436   void DrawTree(Window &window) {
1437     if (parent)
1438       parent->DrawTreeForChild(window, this, 0);
1439 
1440     if (might_have_children) {
1441       // It we can get UTF8 characters to work we should try to use the
1442       // "symbol" UTF8 string below
1443       //            const char *symbol = "";
1444       //            if (row.expanded)
1445       //                symbol = "\xe2\x96\xbd ";
1446       //            else
1447       //                symbol = "\xe2\x96\xb7 ";
1448       //            window.PutCString (symbol);
1449 
1450       // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v'
1451       // or '>' character...
1452       //            if (expanded)
1453       //                window.PutChar (ACS_DARROW);
1454       //            else
1455       //                window.PutChar (ACS_RARROW);
1456       // Since we can't find any good looking right arrow/down arrow symbols,
1457       // just use a diamond...
1458       window.PutChar(ACS_DIAMOND);
1459       window.PutChar(ACS_HLINE);
1460     }
1461   }
1462 
1463   void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) {
1464     if (parent)
1465       parent->DrawTreeForChild(window, this, reverse_depth + 1);
1466 
1467     if (&GetChildren().back() == child) {
1468       // Last child
1469       if (reverse_depth == 0) {
1470         window.PutChar(ACS_LLCORNER);
1471         window.PutChar(ACS_HLINE);
1472       } else {
1473         window.PutChar(' ');
1474         window.PutChar(' ');
1475       }
1476     } else {
1477       if (reverse_depth == 0) {
1478         window.PutChar(ACS_LTEE);
1479         window.PutChar(ACS_HLINE);
1480       } else {
1481         window.PutChar(ACS_VLINE);
1482         window.PutChar(' ');
1483       }
1484     }
1485   }
1486 };
1487 
1488 struct DisplayOptions {
1489   bool show_types;
1490 };
1491 
1492 class TreeItem;
1493 
1494 class TreeDelegate {
1495 public:
1496   TreeDelegate() = default;
1497   virtual ~TreeDelegate() = default;
1498 
1499   virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0;
1500   virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0;
1501   virtual bool TreeDelegateItemSelected(
1502       TreeItem &item) = 0; // Return true if we need to update views
1503 };
1504 
1505 typedef std::shared_ptr<TreeDelegate> TreeDelegateSP;
1506 
1507 class TreeItem {
1508 public:
1509   TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children)
1510       : m_parent(parent), m_delegate(delegate), m_user_data(nullptr),
1511         m_identifier(0), m_row_idx(-1), m_children(),
1512         m_might_have_children(might_have_children), m_is_expanded(false) {}
1513 
1514   TreeItem &operator=(const TreeItem &rhs) {
1515     if (this != &rhs) {
1516       m_parent = rhs.m_parent;
1517       m_delegate = rhs.m_delegate;
1518       m_user_data = rhs.m_user_data;
1519       m_identifier = rhs.m_identifier;
1520       m_row_idx = rhs.m_row_idx;
1521       m_children = rhs.m_children;
1522       m_might_have_children = rhs.m_might_have_children;
1523       m_is_expanded = rhs.m_is_expanded;
1524     }
1525     return *this;
1526   }
1527 
1528   TreeItem(const TreeItem &) = default;
1529 
1530   size_t GetDepth() const {
1531     if (m_parent)
1532       return 1 + m_parent->GetDepth();
1533     return 0;
1534   }
1535 
1536   int GetRowIndex() const { return m_row_idx; }
1537 
1538   void ClearChildren() { m_children.clear(); }
1539 
1540   void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); }
1541 
1542   TreeItem &operator[](size_t i) { return m_children[i]; }
1543 
1544   void SetRowIndex(int row_idx) { m_row_idx = row_idx; }
1545 
1546   size_t GetNumChildren() {
1547     m_delegate.TreeDelegateGenerateChildren(*this);
1548     return m_children.size();
1549   }
1550 
1551   void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); }
1552 
1553   void CalculateRowIndexes(int &row_idx) {
1554     SetRowIndex(row_idx);
1555     ++row_idx;
1556 
1557     const bool expanded = IsExpanded();
1558 
1559     // The root item must calculate its children, or we must calculate the
1560     // number of children if the item is expanded
1561     if (m_parent == nullptr || expanded)
1562       GetNumChildren();
1563 
1564     for (auto &item : m_children) {
1565       if (expanded)
1566         item.CalculateRowIndexes(row_idx);
1567       else
1568         item.SetRowIndex(-1);
1569     }
1570   }
1571 
1572   TreeItem *GetParent() { return m_parent; }
1573 
1574   bool IsExpanded() const { return m_is_expanded; }
1575 
1576   void Expand() { m_is_expanded = true; }
1577 
1578   void Unexpand() { m_is_expanded = false; }
1579 
1580   bool Draw(Window &window, const int first_visible_row,
1581             const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) {
1582     if (num_rows_left <= 0)
1583       return false;
1584 
1585     if (m_row_idx >= first_visible_row) {
1586       window.MoveCursor(2, row_idx + 1);
1587 
1588       if (m_parent)
1589         m_parent->DrawTreeForChild(window, this, 0);
1590 
1591       if (m_might_have_children) {
1592         // It we can get UTF8 characters to work we should try to use the
1593         // "symbol" UTF8 string below
1594         //            const char *symbol = "";
1595         //            if (row.expanded)
1596         //                symbol = "\xe2\x96\xbd ";
1597         //            else
1598         //                symbol = "\xe2\x96\xb7 ";
1599         //            window.PutCString (symbol);
1600 
1601         // The ACS_DARROW and ACS_RARROW don't look very nice they are just a
1602         // 'v' or '>' character...
1603         //            if (expanded)
1604         //                window.PutChar (ACS_DARROW);
1605         //            else
1606         //                window.PutChar (ACS_RARROW);
1607         // Since we can't find any good looking right arrow/down arrow symbols,
1608         // just use a diamond...
1609         window.PutChar(ACS_DIAMOND);
1610         window.PutChar(ACS_HLINE);
1611       }
1612       bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) &&
1613                        window.IsActive();
1614 
1615       if (highlight)
1616         window.AttributeOn(A_REVERSE);
1617 
1618       m_delegate.TreeDelegateDrawTreeItem(*this, window);
1619 
1620       if (highlight)
1621         window.AttributeOff(A_REVERSE);
1622       ++row_idx;
1623       --num_rows_left;
1624     }
1625 
1626     if (num_rows_left <= 0)
1627       return false; // We are done drawing...
1628 
1629     if (IsExpanded()) {
1630       for (auto &item : m_children) {
1631         // If we displayed all the rows and item.Draw() returns false we are
1632         // done drawing and can exit this for loop
1633         if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx,
1634                        num_rows_left))
1635           break;
1636       }
1637     }
1638     return num_rows_left >= 0; // Return true if not done drawing yet
1639   }
1640 
1641   void DrawTreeForChild(Window &window, TreeItem *child,
1642                         uint32_t reverse_depth) {
1643     if (m_parent)
1644       m_parent->DrawTreeForChild(window, this, reverse_depth + 1);
1645 
1646     if (&m_children.back() == child) {
1647       // Last child
1648       if (reverse_depth == 0) {
1649         window.PutChar(ACS_LLCORNER);
1650         window.PutChar(ACS_HLINE);
1651       } else {
1652         window.PutChar(' ');
1653         window.PutChar(' ');
1654       }
1655     } else {
1656       if (reverse_depth == 0) {
1657         window.PutChar(ACS_LTEE);
1658         window.PutChar(ACS_HLINE);
1659       } else {
1660         window.PutChar(ACS_VLINE);
1661         window.PutChar(' ');
1662       }
1663     }
1664   }
1665 
1666   TreeItem *GetItemForRowIndex(uint32_t row_idx) {
1667     if (static_cast<uint32_t>(m_row_idx) == row_idx)
1668       return this;
1669     if (m_children.empty())
1670       return nullptr;
1671     if (IsExpanded()) {
1672       for (auto &item : m_children) {
1673         TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx);
1674         if (selected_item_ptr)
1675           return selected_item_ptr;
1676       }
1677     }
1678     return nullptr;
1679   }
1680 
1681   void *GetUserData() const { return m_user_data; }
1682 
1683   void SetUserData(void *user_data) { m_user_data = user_data; }
1684 
1685   uint64_t GetIdentifier() const { return m_identifier; }
1686 
1687   void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
1688 
1689   void SetMightHaveChildren(bool b) { m_might_have_children = b; }
1690 
1691 protected:
1692   TreeItem *m_parent;
1693   TreeDelegate &m_delegate;
1694   void *m_user_data;
1695   uint64_t m_identifier;
1696   int m_row_idx; // Zero based visible row index, -1 if not visible or for the
1697                  // root item
1698   std::vector<TreeItem> m_children;
1699   bool m_might_have_children;
1700   bool m_is_expanded;
1701 };
1702 
1703 class TreeWindowDelegate : public WindowDelegate {
1704 public:
1705   TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp)
1706       : m_debugger(debugger), m_delegate_sp(delegate_sp),
1707         m_root(nullptr, *delegate_sp, true), m_selected_item(nullptr),
1708         m_num_rows(0), m_selected_row_idx(0), m_first_visible_row(0),
1709         m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {}
1710 
1711   int NumVisibleRows() const { return m_max_y - m_min_y; }
1712 
1713   bool WindowDelegateDraw(Window &window, bool force) override {
1714     ExecutionContext exe_ctx(
1715         m_debugger.GetCommandInterpreter().GetExecutionContext());
1716     Process *process = exe_ctx.GetProcessPtr();
1717 
1718     bool display_content = false;
1719     if (process) {
1720       StateType state = process->GetState();
1721       if (StateIsStoppedState(state, true)) {
1722         // We are stopped, so it is ok to
1723         display_content = true;
1724       } else if (StateIsRunningState(state)) {
1725         return true; // Don't do any updating when we are running
1726       }
1727     }
1728 
1729     m_min_x = 2;
1730     m_min_y = 1;
1731     m_max_x = window.GetWidth() - 1;
1732     m_max_y = window.GetHeight() - 1;
1733 
1734     window.Erase();
1735     window.DrawTitleBox(window.GetName());
1736 
1737     if (display_content) {
1738       const int num_visible_rows = NumVisibleRows();
1739       m_num_rows = 0;
1740       m_root.CalculateRowIndexes(m_num_rows);
1741 
1742       // If we unexpanded while having something selected our total number of
1743       // rows is less than the num visible rows, then make sure we show all the
1744       // rows by setting the first visible row accordingly.
1745       if (m_first_visible_row > 0 && m_num_rows < num_visible_rows)
1746         m_first_visible_row = 0;
1747 
1748       // Make sure the selected row is always visible
1749       if (m_selected_row_idx < m_first_visible_row)
1750         m_first_visible_row = m_selected_row_idx;
1751       else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
1752         m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
1753 
1754       int row_idx = 0;
1755       int num_rows_left = num_visible_rows;
1756       m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx,
1757                   num_rows_left);
1758       // Get the selected row
1759       m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
1760     } else {
1761       m_selected_item = nullptr;
1762     }
1763 
1764     return true; // Drawing handled
1765   }
1766 
1767   const char *WindowDelegateGetHelpText() override {
1768     return "Thread window keyboard shortcuts:";
1769   }
1770 
1771   KeyHelp *WindowDelegateGetKeyHelp() override {
1772     static curses::KeyHelp g_source_view_key_help[] = {
1773         {KEY_UP, "Select previous item"},
1774         {KEY_DOWN, "Select next item"},
1775         {KEY_RIGHT, "Expand the selected item"},
1776         {KEY_LEFT,
1777          "Unexpand the selected item or select parent if not expanded"},
1778         {KEY_PPAGE, "Page up"},
1779         {KEY_NPAGE, "Page down"},
1780         {'h', "Show help dialog"},
1781         {' ', "Toggle item expansion"},
1782         {',', "Page up"},
1783         {'.', "Page down"},
1784         {'\0', nullptr}};
1785     return g_source_view_key_help;
1786   }
1787 
1788   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
1789     switch (c) {
1790     case ',':
1791     case KEY_PPAGE:
1792       // Page up key
1793       if (m_first_visible_row > 0) {
1794         if (m_first_visible_row > m_max_y)
1795           m_first_visible_row -= m_max_y;
1796         else
1797           m_first_visible_row = 0;
1798         m_selected_row_idx = m_first_visible_row;
1799         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
1800         if (m_selected_item)
1801           m_selected_item->ItemWasSelected();
1802       }
1803       return eKeyHandled;
1804 
1805     case '.':
1806     case KEY_NPAGE:
1807       // Page down key
1808       if (m_num_rows > m_max_y) {
1809         if (m_first_visible_row + m_max_y < m_num_rows) {
1810           m_first_visible_row += m_max_y;
1811           m_selected_row_idx = m_first_visible_row;
1812           m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
1813           if (m_selected_item)
1814             m_selected_item->ItemWasSelected();
1815         }
1816       }
1817       return eKeyHandled;
1818 
1819     case KEY_UP:
1820       if (m_selected_row_idx > 0) {
1821         --m_selected_row_idx;
1822         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
1823         if (m_selected_item)
1824           m_selected_item->ItemWasSelected();
1825       }
1826       return eKeyHandled;
1827 
1828     case KEY_DOWN:
1829       if (m_selected_row_idx + 1 < m_num_rows) {
1830         ++m_selected_row_idx;
1831         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
1832         if (m_selected_item)
1833           m_selected_item->ItemWasSelected();
1834       }
1835       return eKeyHandled;
1836 
1837     case KEY_RIGHT:
1838       if (m_selected_item) {
1839         if (!m_selected_item->IsExpanded())
1840           m_selected_item->Expand();
1841       }
1842       return eKeyHandled;
1843 
1844     case KEY_LEFT:
1845       if (m_selected_item) {
1846         if (m_selected_item->IsExpanded())
1847           m_selected_item->Unexpand();
1848         else if (m_selected_item->GetParent()) {
1849           m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex();
1850           m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
1851           if (m_selected_item)
1852             m_selected_item->ItemWasSelected();
1853         }
1854       }
1855       return eKeyHandled;
1856 
1857     case ' ':
1858       // Toggle expansion state when SPACE is pressed
1859       if (m_selected_item) {
1860         if (m_selected_item->IsExpanded())
1861           m_selected_item->Unexpand();
1862         else
1863           m_selected_item->Expand();
1864       }
1865       return eKeyHandled;
1866 
1867     case 'h':
1868       window.CreateHelpSubwindow();
1869       return eKeyHandled;
1870 
1871     default:
1872       break;
1873     }
1874     return eKeyNotHandled;
1875   }
1876 
1877 protected:
1878   Debugger &m_debugger;
1879   TreeDelegateSP m_delegate_sp;
1880   TreeItem m_root;
1881   TreeItem *m_selected_item;
1882   int m_num_rows;
1883   int m_selected_row_idx;
1884   int m_first_visible_row;
1885   int m_min_x;
1886   int m_min_y;
1887   int m_max_x;
1888   int m_max_y;
1889 };
1890 
1891 class FrameTreeDelegate : public TreeDelegate {
1892 public:
1893   FrameTreeDelegate() : TreeDelegate() {
1894     FormatEntity::Parse(
1895         "frame #${frame.index}: {${function.name}${function.pc-offset}}}",
1896         m_format);
1897   }
1898 
1899   ~FrameTreeDelegate() override = default;
1900 
1901   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
1902     Thread *thread = (Thread *)item.GetUserData();
1903     if (thread) {
1904       const uint64_t frame_idx = item.GetIdentifier();
1905       StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx);
1906       if (frame_sp) {
1907         StreamString strm;
1908         const SymbolContext &sc =
1909             frame_sp->GetSymbolContext(eSymbolContextEverything);
1910         ExecutionContext exe_ctx(frame_sp);
1911         if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr,
1912                                  nullptr, false, false)) {
1913           int right_pad = 1;
1914           window.PutCStringTruncated(strm.GetString().str().c_str(), right_pad);
1915         }
1916       }
1917     }
1918   }
1919 
1920   void TreeDelegateGenerateChildren(TreeItem &item) override {
1921     // No children for frames yet...
1922   }
1923 
1924   bool TreeDelegateItemSelected(TreeItem &item) override {
1925     Thread *thread = (Thread *)item.GetUserData();
1926     if (thread) {
1927       thread->GetProcess()->GetThreadList().SetSelectedThreadByID(
1928           thread->GetID());
1929       const uint64_t frame_idx = item.GetIdentifier();
1930       thread->SetSelectedFrameByIndex(frame_idx);
1931       return true;
1932     }
1933     return false;
1934   }
1935 
1936 protected:
1937   FormatEntity::Entry m_format;
1938 };
1939 
1940 class ThreadTreeDelegate : public TreeDelegate {
1941 public:
1942   ThreadTreeDelegate(Debugger &debugger)
1943       : TreeDelegate(), m_debugger(debugger), m_tid(LLDB_INVALID_THREAD_ID),
1944         m_stop_id(UINT32_MAX) {
1945     FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop "
1946                         "reason = ${thread.stop-reason}}",
1947                         m_format);
1948   }
1949 
1950   ~ThreadTreeDelegate() override = default;
1951 
1952   ProcessSP GetProcess() {
1953     return m_debugger.GetCommandInterpreter()
1954         .GetExecutionContext()
1955         .GetProcessSP();
1956   }
1957 
1958   ThreadSP GetThread(const TreeItem &item) {
1959     ProcessSP process_sp = GetProcess();
1960     if (process_sp)
1961       return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier());
1962     return ThreadSP();
1963   }
1964 
1965   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
1966     ThreadSP thread_sp = GetThread(item);
1967     if (thread_sp) {
1968       StreamString strm;
1969       ExecutionContext exe_ctx(thread_sp);
1970       if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
1971                                nullptr, false, false)) {
1972         int right_pad = 1;
1973         window.PutCStringTruncated(strm.GetString().str().c_str(), right_pad);
1974       }
1975     }
1976   }
1977 
1978   void TreeDelegateGenerateChildren(TreeItem &item) override {
1979     ProcessSP process_sp = GetProcess();
1980     if (process_sp && process_sp->IsAlive()) {
1981       StateType state = process_sp->GetState();
1982       if (StateIsStoppedState(state, true)) {
1983         ThreadSP thread_sp = GetThread(item);
1984         if (thread_sp) {
1985           if (m_stop_id == process_sp->GetStopID() &&
1986               thread_sp->GetID() == m_tid)
1987             return; // Children are already up to date
1988           if (!m_frame_delegate_sp) {
1989             // Always expand the thread item the first time we show it
1990             m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>();
1991           }
1992 
1993           m_stop_id = process_sp->GetStopID();
1994           m_tid = thread_sp->GetID();
1995 
1996           TreeItem t(&item, *m_frame_delegate_sp, false);
1997           size_t num_frames = thread_sp->GetStackFrameCount();
1998           item.Resize(num_frames, t);
1999           for (size_t i = 0; i < num_frames; ++i) {
2000             item[i].SetUserData(thread_sp.get());
2001             item[i].SetIdentifier(i);
2002           }
2003         }
2004         return;
2005       }
2006     }
2007     item.ClearChildren();
2008   }
2009 
2010   bool TreeDelegateItemSelected(TreeItem &item) override {
2011     ProcessSP process_sp = GetProcess();
2012     if (process_sp && process_sp->IsAlive()) {
2013       StateType state = process_sp->GetState();
2014       if (StateIsStoppedState(state, true)) {
2015         ThreadSP thread_sp = GetThread(item);
2016         if (thread_sp) {
2017           ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList();
2018           std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex());
2019           ThreadSP selected_thread_sp = thread_list.GetSelectedThread();
2020           if (selected_thread_sp->GetID() != thread_sp->GetID()) {
2021             thread_list.SetSelectedThreadByID(thread_sp->GetID());
2022             return true;
2023           }
2024         }
2025       }
2026     }
2027     return false;
2028   }
2029 
2030 protected:
2031   Debugger &m_debugger;
2032   std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp;
2033   lldb::user_id_t m_tid;
2034   uint32_t m_stop_id;
2035   FormatEntity::Entry m_format;
2036 };
2037 
2038 class ThreadsTreeDelegate : public TreeDelegate {
2039 public:
2040   ThreadsTreeDelegate(Debugger &debugger)
2041       : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger),
2042         m_stop_id(UINT32_MAX) {
2043     FormatEntity::Parse("process ${process.id}{, name = ${process.name}}",
2044                         m_format);
2045   }
2046 
2047   ~ThreadsTreeDelegate() override = default;
2048 
2049   ProcessSP GetProcess() {
2050     return m_debugger.GetCommandInterpreter()
2051         .GetExecutionContext()
2052         .GetProcessSP();
2053   }
2054 
2055   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
2056     ProcessSP process_sp = GetProcess();
2057     if (process_sp && process_sp->IsAlive()) {
2058       StreamString strm;
2059       ExecutionContext exe_ctx(process_sp);
2060       if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
2061                                nullptr, false, false)) {
2062         int right_pad = 1;
2063         window.PutCStringTruncated(strm.GetString().str().c_str(), right_pad);
2064       }
2065     }
2066   }
2067 
2068   void TreeDelegateGenerateChildren(TreeItem &item) override {
2069     ProcessSP process_sp = GetProcess();
2070     if (process_sp && process_sp->IsAlive()) {
2071       StateType state = process_sp->GetState();
2072       if (StateIsStoppedState(state, true)) {
2073         const uint32_t stop_id = process_sp->GetStopID();
2074         if (m_stop_id == stop_id)
2075           return; // Children are already up to date
2076 
2077         m_stop_id = stop_id;
2078 
2079         if (!m_thread_delegate_sp) {
2080           // Always expand the thread item the first time we show it
2081           // item.Expand();
2082           m_thread_delegate_sp =
2083               std::make_shared<ThreadTreeDelegate>(m_debugger);
2084         }
2085 
2086         TreeItem t(&item, *m_thread_delegate_sp, false);
2087         ThreadList &threads = process_sp->GetThreadList();
2088         std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
2089         size_t num_threads = threads.GetSize();
2090         item.Resize(num_threads, t);
2091         for (size_t i = 0; i < num_threads; ++i) {
2092           item[i].SetIdentifier(threads.GetThreadAtIndex(i)->GetID());
2093           item[i].SetMightHaveChildren(true);
2094         }
2095         return;
2096       }
2097     }
2098     item.ClearChildren();
2099   }
2100 
2101   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
2102 
2103 protected:
2104   std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp;
2105   Debugger &m_debugger;
2106   uint32_t m_stop_id;
2107   FormatEntity::Entry m_format;
2108 };
2109 
2110 class ValueObjectListDelegate : public WindowDelegate {
2111 public:
2112   ValueObjectListDelegate()
2113       : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0),
2114         m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) {}
2115 
2116   ValueObjectListDelegate(ValueObjectList &valobj_list)
2117       : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0),
2118         m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) {
2119     SetValues(valobj_list);
2120   }
2121 
2122   ~ValueObjectListDelegate() override = default;
2123 
2124   void SetValues(ValueObjectList &valobj_list) {
2125     m_selected_row = nullptr;
2126     m_selected_row_idx = 0;
2127     m_first_visible_row = 0;
2128     m_num_rows = 0;
2129     m_rows.clear();
2130     for (auto &valobj_sp : valobj_list.GetObjects())
2131       m_rows.push_back(Row(valobj_sp, nullptr));
2132   }
2133 
2134   bool WindowDelegateDraw(Window &window, bool force) override {
2135     m_num_rows = 0;
2136     m_min_x = 2;
2137     m_min_y = 1;
2138     m_max_x = window.GetWidth() - 1;
2139     m_max_y = window.GetHeight() - 1;
2140 
2141     window.Erase();
2142     window.DrawTitleBox(window.GetName());
2143 
2144     const int num_visible_rows = NumVisibleRows();
2145     const int num_rows = CalculateTotalNumberRows(m_rows);
2146 
2147     // If we unexpanded while having something selected our total number of
2148     // rows is less than the num visible rows, then make sure we show all the
2149     // rows by setting the first visible row accordingly.
2150     if (m_first_visible_row > 0 && num_rows < num_visible_rows)
2151       m_first_visible_row = 0;
2152 
2153     // Make sure the selected row is always visible
2154     if (m_selected_row_idx < m_first_visible_row)
2155       m_first_visible_row = m_selected_row_idx;
2156     else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
2157       m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
2158 
2159     DisplayRows(window, m_rows, g_options);
2160 
2161     // Get the selected row
2162     m_selected_row = GetRowForRowIndex(m_selected_row_idx);
2163     // Keep the cursor on the selected row so the highlight and the cursor are
2164     // always on the same line
2165     if (m_selected_row)
2166       window.MoveCursor(m_selected_row->x, m_selected_row->y);
2167 
2168     return true; // Drawing handled
2169   }
2170 
2171   KeyHelp *WindowDelegateGetKeyHelp() override {
2172     static curses::KeyHelp g_source_view_key_help[] = {
2173         {KEY_UP, "Select previous item"},
2174         {KEY_DOWN, "Select next item"},
2175         {KEY_RIGHT, "Expand selected item"},
2176         {KEY_LEFT, "Unexpand selected item or select parent if not expanded"},
2177         {KEY_PPAGE, "Page up"},
2178         {KEY_NPAGE, "Page down"},
2179         {'A', "Format as annotated address"},
2180         {'b', "Format as binary"},
2181         {'B', "Format as hex bytes with ASCII"},
2182         {'c', "Format as character"},
2183         {'d', "Format as a signed integer"},
2184         {'D', "Format selected value using the default format for the type"},
2185         {'f', "Format as float"},
2186         {'h', "Show help dialog"},
2187         {'i', "Format as instructions"},
2188         {'o', "Format as octal"},
2189         {'p', "Format as pointer"},
2190         {'s', "Format as C string"},
2191         {'t', "Toggle showing/hiding type names"},
2192         {'u', "Format as an unsigned integer"},
2193         {'x', "Format as hex"},
2194         {'X', "Format as uppercase hex"},
2195         {' ', "Toggle item expansion"},
2196         {',', "Page up"},
2197         {'.', "Page down"},
2198         {'\0', nullptr}};
2199     return g_source_view_key_help;
2200   }
2201 
2202   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
2203     switch (c) {
2204     case 'x':
2205     case 'X':
2206     case 'o':
2207     case 's':
2208     case 'u':
2209     case 'd':
2210     case 'D':
2211     case 'i':
2212     case 'A':
2213     case 'p':
2214     case 'c':
2215     case 'b':
2216     case 'B':
2217     case 'f':
2218       // Change the format for the currently selected item
2219       if (m_selected_row) {
2220         auto valobj_sp = m_selected_row->value.GetSP();
2221         if (valobj_sp)
2222           valobj_sp->SetFormat(FormatForChar(c));
2223       }
2224       return eKeyHandled;
2225 
2226     case 't':
2227       // Toggle showing type names
2228       g_options.show_types = !g_options.show_types;
2229       return eKeyHandled;
2230 
2231     case ',':
2232     case KEY_PPAGE:
2233       // Page up key
2234       if (m_first_visible_row > 0) {
2235         if (static_cast<int>(m_first_visible_row) > m_max_y)
2236           m_first_visible_row -= m_max_y;
2237         else
2238           m_first_visible_row = 0;
2239         m_selected_row_idx = m_first_visible_row;
2240       }
2241       return eKeyHandled;
2242 
2243     case '.':
2244     case KEY_NPAGE:
2245       // Page down key
2246       if (m_num_rows > static_cast<size_t>(m_max_y)) {
2247         if (m_first_visible_row + m_max_y < m_num_rows) {
2248           m_first_visible_row += m_max_y;
2249           m_selected_row_idx = m_first_visible_row;
2250         }
2251       }
2252       return eKeyHandled;
2253 
2254     case KEY_UP:
2255       if (m_selected_row_idx > 0)
2256         --m_selected_row_idx;
2257       return eKeyHandled;
2258 
2259     case KEY_DOWN:
2260       if (m_selected_row_idx + 1 < m_num_rows)
2261         ++m_selected_row_idx;
2262       return eKeyHandled;
2263 
2264     case KEY_RIGHT:
2265       if (m_selected_row) {
2266         if (!m_selected_row->expanded)
2267           m_selected_row->Expand();
2268       }
2269       return eKeyHandled;
2270 
2271     case KEY_LEFT:
2272       if (m_selected_row) {
2273         if (m_selected_row->expanded)
2274           m_selected_row->Unexpand();
2275         else if (m_selected_row->parent)
2276           m_selected_row_idx = m_selected_row->parent->row_idx;
2277       }
2278       return eKeyHandled;
2279 
2280     case ' ':
2281       // Toggle expansion state when SPACE is pressed
2282       if (m_selected_row) {
2283         if (m_selected_row->expanded)
2284           m_selected_row->Unexpand();
2285         else
2286           m_selected_row->Expand();
2287       }
2288       return eKeyHandled;
2289 
2290     case 'h':
2291       window.CreateHelpSubwindow();
2292       return eKeyHandled;
2293 
2294     default:
2295       break;
2296     }
2297     return eKeyNotHandled;
2298   }
2299 
2300 protected:
2301   std::vector<Row> m_rows;
2302   Row *m_selected_row;
2303   uint32_t m_selected_row_idx;
2304   uint32_t m_first_visible_row;
2305   uint32_t m_num_rows;
2306   int m_min_x;
2307   int m_min_y;
2308   int m_max_x;
2309   int m_max_y;
2310 
2311   static Format FormatForChar(int c) {
2312     switch (c) {
2313     case 'x':
2314       return eFormatHex;
2315     case 'X':
2316       return eFormatHexUppercase;
2317     case 'o':
2318       return eFormatOctal;
2319     case 's':
2320       return eFormatCString;
2321     case 'u':
2322       return eFormatUnsigned;
2323     case 'd':
2324       return eFormatDecimal;
2325     case 'D':
2326       return eFormatDefault;
2327     case 'i':
2328       return eFormatInstruction;
2329     case 'A':
2330       return eFormatAddressInfo;
2331     case 'p':
2332       return eFormatPointer;
2333     case 'c':
2334       return eFormatChar;
2335     case 'b':
2336       return eFormatBinary;
2337     case 'B':
2338       return eFormatBytesWithASCII;
2339     case 'f':
2340       return eFormatFloat;
2341     }
2342     return eFormatDefault;
2343   }
2344 
2345   bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options,
2346                         bool highlight, bool last_child) {
2347     ValueObject *valobj = row.value.GetSP().get();
2348 
2349     if (valobj == nullptr)
2350       return false;
2351 
2352     const char *type_name =
2353         options.show_types ? valobj->GetTypeName().GetCString() : nullptr;
2354     const char *name = valobj->GetName().GetCString();
2355     const char *value = valobj->GetValueAsCString();
2356     const char *summary = valobj->GetSummaryAsCString();
2357 
2358     window.MoveCursor(row.x, row.y);
2359 
2360     row.DrawTree(window);
2361 
2362     if (highlight)
2363       window.AttributeOn(A_REVERSE);
2364 
2365     if (type_name && type_name[0])
2366       window.Printf("(%s) ", type_name);
2367 
2368     if (name && name[0])
2369       window.PutCString(name);
2370 
2371     attr_t changd_attr = 0;
2372     if (valobj->GetValueDidChange())
2373       changd_attr = COLOR_PAIR(5) | A_BOLD;
2374 
2375     if (value && value[0]) {
2376       window.PutCString(" = ");
2377       if (changd_attr)
2378         window.AttributeOn(changd_attr);
2379       window.PutCString(value);
2380       if (changd_attr)
2381         window.AttributeOff(changd_attr);
2382     }
2383 
2384     if (summary && summary[0]) {
2385       window.PutChar(' ');
2386       if (changd_attr)
2387         window.AttributeOn(changd_attr);
2388       window.PutCString(summary);
2389       if (changd_attr)
2390         window.AttributeOff(changd_attr);
2391     }
2392 
2393     if (highlight)
2394       window.AttributeOff(A_REVERSE);
2395 
2396     return true;
2397   }
2398 
2399   void DisplayRows(Window &window, std::vector<Row> &rows,
2400                    DisplayOptions &options) {
2401     // >   0x25B7
2402     // \/  0x25BD
2403 
2404     bool window_is_active = window.IsActive();
2405     for (auto &row : rows) {
2406       const bool last_child = row.parent && &rows[rows.size() - 1] == &row;
2407       // Save the row index in each Row structure
2408       row.row_idx = m_num_rows;
2409       if ((m_num_rows >= m_first_visible_row) &&
2410           ((m_num_rows - m_first_visible_row) <
2411            static_cast<size_t>(NumVisibleRows()))) {
2412         row.x = m_min_x;
2413         row.y = m_num_rows - m_first_visible_row + 1;
2414         if (DisplayRowObject(window, row, options,
2415                              window_is_active &&
2416                                  m_num_rows == m_selected_row_idx,
2417                              last_child)) {
2418           ++m_num_rows;
2419         } else {
2420           row.x = 0;
2421           row.y = 0;
2422         }
2423       } else {
2424         row.x = 0;
2425         row.y = 0;
2426         ++m_num_rows;
2427       }
2428 
2429       auto &children = row.GetChildren();
2430       if (row.expanded && !children.empty()) {
2431         DisplayRows(window, children, options);
2432       }
2433     }
2434   }
2435 
2436   int CalculateTotalNumberRows(std::vector<Row> &rows) {
2437     int row_count = 0;
2438     for (auto &row : rows) {
2439       ++row_count;
2440       if (row.expanded)
2441         row_count += CalculateTotalNumberRows(row.GetChildren());
2442     }
2443     return row_count;
2444   }
2445 
2446   static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) {
2447     for (auto &row : rows) {
2448       if (row_index == 0)
2449         return &row;
2450       else {
2451         --row_index;
2452         auto &children = row.GetChildren();
2453         if (row.expanded && !children.empty()) {
2454           Row *result = GetRowForRowIndexImpl(children, row_index);
2455           if (result)
2456             return result;
2457         }
2458       }
2459     }
2460     return nullptr;
2461   }
2462 
2463   Row *GetRowForRowIndex(size_t row_index) {
2464     return GetRowForRowIndexImpl(m_rows, row_index);
2465   }
2466 
2467   int NumVisibleRows() const { return m_max_y - m_min_y; }
2468 
2469   static DisplayOptions g_options;
2470 };
2471 
2472 class FrameVariablesWindowDelegate : public ValueObjectListDelegate {
2473 public:
2474   FrameVariablesWindowDelegate(Debugger &debugger)
2475       : ValueObjectListDelegate(), m_debugger(debugger),
2476         m_frame_block(nullptr) {}
2477 
2478   ~FrameVariablesWindowDelegate() override = default;
2479 
2480   const char *WindowDelegateGetHelpText() override {
2481     return "Frame variable window keyboard shortcuts:";
2482   }
2483 
2484   bool WindowDelegateDraw(Window &window, bool force) override {
2485     ExecutionContext exe_ctx(
2486         m_debugger.GetCommandInterpreter().GetExecutionContext());
2487     Process *process = exe_ctx.GetProcessPtr();
2488     Block *frame_block = nullptr;
2489     StackFrame *frame = nullptr;
2490 
2491     if (process) {
2492       StateType state = process->GetState();
2493       if (StateIsStoppedState(state, true)) {
2494         frame = exe_ctx.GetFramePtr();
2495         if (frame)
2496           frame_block = frame->GetFrameBlock();
2497       } else if (StateIsRunningState(state)) {
2498         return true; // Don't do any updating when we are running
2499       }
2500     }
2501 
2502     ValueObjectList local_values;
2503     if (frame_block) {
2504       // Only update the variables if they have changed
2505       if (m_frame_block != frame_block) {
2506         m_frame_block = frame_block;
2507 
2508         VariableList *locals = frame->GetVariableList(true);
2509         if (locals) {
2510           const DynamicValueType use_dynamic = eDynamicDontRunTarget;
2511           for (const VariableSP &local_sp : *locals) {
2512             ValueObjectSP value_sp =
2513                 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic);
2514             if (value_sp) {
2515               ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue();
2516               if (synthetic_value_sp)
2517                 local_values.Append(synthetic_value_sp);
2518               else
2519                 local_values.Append(value_sp);
2520             }
2521           }
2522           // Update the values
2523           SetValues(local_values);
2524         }
2525       }
2526     } else {
2527       m_frame_block = nullptr;
2528       // Update the values with an empty list if there is no frame
2529       SetValues(local_values);
2530     }
2531 
2532     return ValueObjectListDelegate::WindowDelegateDraw(window, force);
2533   }
2534 
2535 protected:
2536   Debugger &m_debugger;
2537   Block *m_frame_block;
2538 };
2539 
2540 class RegistersWindowDelegate : public ValueObjectListDelegate {
2541 public:
2542   RegistersWindowDelegate(Debugger &debugger)
2543       : ValueObjectListDelegate(), m_debugger(debugger) {}
2544 
2545   ~RegistersWindowDelegate() override = default;
2546 
2547   const char *WindowDelegateGetHelpText() override {
2548     return "Register window keyboard shortcuts:";
2549   }
2550 
2551   bool WindowDelegateDraw(Window &window, bool force) override {
2552     ExecutionContext exe_ctx(
2553         m_debugger.GetCommandInterpreter().GetExecutionContext());
2554     StackFrame *frame = exe_ctx.GetFramePtr();
2555 
2556     ValueObjectList value_list;
2557     if (frame) {
2558       if (frame->GetStackID() != m_stack_id) {
2559         m_stack_id = frame->GetStackID();
2560         RegisterContextSP reg_ctx(frame->GetRegisterContext());
2561         if (reg_ctx) {
2562           const uint32_t num_sets = reg_ctx->GetRegisterSetCount();
2563           for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) {
2564             value_list.Append(
2565                 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx));
2566           }
2567         }
2568         SetValues(value_list);
2569       }
2570     } else {
2571       Process *process = exe_ctx.GetProcessPtr();
2572       if (process && process->IsAlive())
2573         return true; // Don't do any updating if we are running
2574       else {
2575         // Update the values with an empty list if there is no process or the
2576         // process isn't alive anymore
2577         SetValues(value_list);
2578       }
2579     }
2580     return ValueObjectListDelegate::WindowDelegateDraw(window, force);
2581   }
2582 
2583 protected:
2584   Debugger &m_debugger;
2585   StackID m_stack_id;
2586 };
2587 
2588 static const char *CursesKeyToCString(int ch) {
2589   static char g_desc[32];
2590   if (ch >= KEY_F0 && ch < KEY_F0 + 64) {
2591     snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0);
2592     return g_desc;
2593   }
2594   switch (ch) {
2595   case KEY_DOWN:
2596     return "down";
2597   case KEY_UP:
2598     return "up";
2599   case KEY_LEFT:
2600     return "left";
2601   case KEY_RIGHT:
2602     return "right";
2603   case KEY_HOME:
2604     return "home";
2605   case KEY_BACKSPACE:
2606     return "backspace";
2607   case KEY_DL:
2608     return "delete-line";
2609   case KEY_IL:
2610     return "insert-line";
2611   case KEY_DC:
2612     return "delete-char";
2613   case KEY_IC:
2614     return "insert-char";
2615   case KEY_CLEAR:
2616     return "clear";
2617   case KEY_EOS:
2618     return "clear-to-eos";
2619   case KEY_EOL:
2620     return "clear-to-eol";
2621   case KEY_SF:
2622     return "scroll-forward";
2623   case KEY_SR:
2624     return "scroll-backward";
2625   case KEY_NPAGE:
2626     return "page-down";
2627   case KEY_PPAGE:
2628     return "page-up";
2629   case KEY_STAB:
2630     return "set-tab";
2631   case KEY_CTAB:
2632     return "clear-tab";
2633   case KEY_CATAB:
2634     return "clear-all-tabs";
2635   case KEY_ENTER:
2636     return "enter";
2637   case KEY_PRINT:
2638     return "print";
2639   case KEY_LL:
2640     return "lower-left key";
2641   case KEY_A1:
2642     return "upper left of keypad";
2643   case KEY_A3:
2644     return "upper right of keypad";
2645   case KEY_B2:
2646     return "center of keypad";
2647   case KEY_C1:
2648     return "lower left of keypad";
2649   case KEY_C3:
2650     return "lower right of keypad";
2651   case KEY_BTAB:
2652     return "back-tab key";
2653   case KEY_BEG:
2654     return "begin key";
2655   case KEY_CANCEL:
2656     return "cancel key";
2657   case KEY_CLOSE:
2658     return "close key";
2659   case KEY_COMMAND:
2660     return "command key";
2661   case KEY_COPY:
2662     return "copy key";
2663   case KEY_CREATE:
2664     return "create key";
2665   case KEY_END:
2666     return "end key";
2667   case KEY_EXIT:
2668     return "exit key";
2669   case KEY_FIND:
2670     return "find key";
2671   case KEY_HELP:
2672     return "help key";
2673   case KEY_MARK:
2674     return "mark key";
2675   case KEY_MESSAGE:
2676     return "message key";
2677   case KEY_MOVE:
2678     return "move key";
2679   case KEY_NEXT:
2680     return "next key";
2681   case KEY_OPEN:
2682     return "open key";
2683   case KEY_OPTIONS:
2684     return "options key";
2685   case KEY_PREVIOUS:
2686     return "previous key";
2687   case KEY_REDO:
2688     return "redo key";
2689   case KEY_REFERENCE:
2690     return "reference key";
2691   case KEY_REFRESH:
2692     return "refresh key";
2693   case KEY_REPLACE:
2694     return "replace key";
2695   case KEY_RESTART:
2696     return "restart key";
2697   case KEY_RESUME:
2698     return "resume key";
2699   case KEY_SAVE:
2700     return "save key";
2701   case KEY_SBEG:
2702     return "shifted begin key";
2703   case KEY_SCANCEL:
2704     return "shifted cancel key";
2705   case KEY_SCOMMAND:
2706     return "shifted command key";
2707   case KEY_SCOPY:
2708     return "shifted copy key";
2709   case KEY_SCREATE:
2710     return "shifted create key";
2711   case KEY_SDC:
2712     return "shifted delete-character key";
2713   case KEY_SDL:
2714     return "shifted delete-line key";
2715   case KEY_SELECT:
2716     return "select key";
2717   case KEY_SEND:
2718     return "shifted end key";
2719   case KEY_SEOL:
2720     return "shifted clear-to-end-of-line key";
2721   case KEY_SEXIT:
2722     return "shifted exit key";
2723   case KEY_SFIND:
2724     return "shifted find key";
2725   case KEY_SHELP:
2726     return "shifted help key";
2727   case KEY_SHOME:
2728     return "shifted home key";
2729   case KEY_SIC:
2730     return "shifted insert-character key";
2731   case KEY_SLEFT:
2732     return "shifted left-arrow key";
2733   case KEY_SMESSAGE:
2734     return "shifted message key";
2735   case KEY_SMOVE:
2736     return "shifted move key";
2737   case KEY_SNEXT:
2738     return "shifted next key";
2739   case KEY_SOPTIONS:
2740     return "shifted options key";
2741   case KEY_SPREVIOUS:
2742     return "shifted previous key";
2743   case KEY_SPRINT:
2744     return "shifted print key";
2745   case KEY_SREDO:
2746     return "shifted redo key";
2747   case KEY_SREPLACE:
2748     return "shifted replace key";
2749   case KEY_SRIGHT:
2750     return "shifted right-arrow key";
2751   case KEY_SRSUME:
2752     return "shifted resume key";
2753   case KEY_SSAVE:
2754     return "shifted save key";
2755   case KEY_SSUSPEND:
2756     return "shifted suspend key";
2757   case KEY_SUNDO:
2758     return "shifted undo key";
2759   case KEY_SUSPEND:
2760     return "suspend key";
2761   case KEY_UNDO:
2762     return "undo key";
2763   case KEY_MOUSE:
2764     return "Mouse event has occurred";
2765   case KEY_RESIZE:
2766     return "Terminal resize event";
2767 #ifdef KEY_EVENT
2768   case KEY_EVENT:
2769     return "We were interrupted by an event";
2770 #endif
2771   case KEY_RETURN:
2772     return "return";
2773   case ' ':
2774     return "space";
2775   case '\t':
2776     return "tab";
2777   case KEY_ESCAPE:
2778     return "escape";
2779   default:
2780     if (llvm::isPrint(ch))
2781       snprintf(g_desc, sizeof(g_desc), "%c", ch);
2782     else
2783       snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch);
2784     return g_desc;
2785   }
2786   return nullptr;
2787 }
2788 
2789 HelpDialogDelegate::HelpDialogDelegate(const char *text,
2790                                        KeyHelp *key_help_array)
2791     : m_text(), m_first_visible_line(0) {
2792   if (text && text[0]) {
2793     m_text.SplitIntoLines(text);
2794     m_text.AppendString("");
2795   }
2796   if (key_help_array) {
2797     for (KeyHelp *key = key_help_array; key->ch; ++key) {
2798       StreamString key_description;
2799       key_description.Printf("%10s - %s", CursesKeyToCString(key->ch),
2800                              key->description);
2801       m_text.AppendString(key_description.GetString());
2802     }
2803   }
2804 }
2805 
2806 HelpDialogDelegate::~HelpDialogDelegate() = default;
2807 
2808 bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) {
2809   window.Erase();
2810   const int window_height = window.GetHeight();
2811   int x = 2;
2812   int y = 1;
2813   const int min_y = y;
2814   const int max_y = window_height - 1 - y;
2815   const size_t num_visible_lines = max_y - min_y + 1;
2816   const size_t num_lines = m_text.GetSize();
2817   const char *bottom_message;
2818   if (num_lines <= num_visible_lines)
2819     bottom_message = "Press any key to exit";
2820   else
2821     bottom_message = "Use arrows to scroll, any other key to exit";
2822   window.DrawTitleBox(window.GetName(), bottom_message);
2823   while (y <= max_y) {
2824     window.MoveCursor(x, y);
2825     window.PutCStringTruncated(
2826         m_text.GetStringAtIndex(m_first_visible_line + y - min_y), 1);
2827     ++y;
2828   }
2829   return true;
2830 }
2831 
2832 HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window,
2833                                                               int key) {
2834   bool done = false;
2835   const size_t num_lines = m_text.GetSize();
2836   const size_t num_visible_lines = window.GetHeight() - 2;
2837 
2838   if (num_lines <= num_visible_lines) {
2839     done = true;
2840     // If we have all lines visible and don't need scrolling, then any key
2841     // press will cause us to exit
2842   } else {
2843     switch (key) {
2844     case KEY_UP:
2845       if (m_first_visible_line > 0)
2846         --m_first_visible_line;
2847       break;
2848 
2849     case KEY_DOWN:
2850       if (m_first_visible_line + num_visible_lines < num_lines)
2851         ++m_first_visible_line;
2852       break;
2853 
2854     case KEY_PPAGE:
2855     case ',':
2856       if (m_first_visible_line > 0) {
2857         if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines)
2858           m_first_visible_line -= num_visible_lines;
2859         else
2860           m_first_visible_line = 0;
2861       }
2862       break;
2863 
2864     case KEY_NPAGE:
2865     case '.':
2866       if (m_first_visible_line + num_visible_lines < num_lines) {
2867         m_first_visible_line += num_visible_lines;
2868         if (static_cast<size_t>(m_first_visible_line) > num_lines)
2869           m_first_visible_line = num_lines - num_visible_lines;
2870       }
2871       break;
2872 
2873     default:
2874       done = true;
2875       break;
2876     }
2877   }
2878   if (done)
2879     window.GetParent()->RemoveSubWindow(&window);
2880   return eKeyHandled;
2881 }
2882 
2883 class ApplicationDelegate : public WindowDelegate, public MenuDelegate {
2884 public:
2885   enum {
2886     eMenuID_LLDB = 1,
2887     eMenuID_LLDBAbout,
2888     eMenuID_LLDBExit,
2889 
2890     eMenuID_Target,
2891     eMenuID_TargetCreate,
2892     eMenuID_TargetDelete,
2893 
2894     eMenuID_Process,
2895     eMenuID_ProcessAttach,
2896     eMenuID_ProcessDetachResume,
2897     eMenuID_ProcessDetachSuspended,
2898     eMenuID_ProcessLaunch,
2899     eMenuID_ProcessContinue,
2900     eMenuID_ProcessHalt,
2901     eMenuID_ProcessKill,
2902 
2903     eMenuID_Thread,
2904     eMenuID_ThreadStepIn,
2905     eMenuID_ThreadStepOver,
2906     eMenuID_ThreadStepOut,
2907 
2908     eMenuID_View,
2909     eMenuID_ViewBacktrace,
2910     eMenuID_ViewRegisters,
2911     eMenuID_ViewSource,
2912     eMenuID_ViewVariables,
2913 
2914     eMenuID_Help,
2915     eMenuID_HelpGUIHelp
2916   };
2917 
2918   ApplicationDelegate(Application &app, Debugger &debugger)
2919       : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {}
2920 
2921   ~ApplicationDelegate() override = default;
2922 
2923   bool WindowDelegateDraw(Window &window, bool force) override {
2924     return false; // Drawing not handled, let standard window drawing happen
2925   }
2926 
2927   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
2928     switch (key) {
2929     case '\t':
2930       window.SelectNextWindowAsActive();
2931       return eKeyHandled;
2932 
2933     case KEY_BTAB:
2934       window.SelectPreviousWindowAsActive();
2935       return eKeyHandled;
2936 
2937     case 'h':
2938       window.CreateHelpSubwindow();
2939       return eKeyHandled;
2940 
2941     case KEY_ESCAPE:
2942       return eQuitApplication;
2943 
2944     default:
2945       break;
2946     }
2947     return eKeyNotHandled;
2948   }
2949 
2950   const char *WindowDelegateGetHelpText() override {
2951     return "Welcome to the LLDB curses GUI.\n\n"
2952            "Press the TAB key to change the selected view.\n"
2953            "Each view has its own keyboard shortcuts, press 'h' to open a "
2954            "dialog to display them.\n\n"
2955            "Common key bindings for all views:";
2956   }
2957 
2958   KeyHelp *WindowDelegateGetKeyHelp() override {
2959     static curses::KeyHelp g_source_view_key_help[] = {
2960         {'\t', "Select next view"},
2961         {KEY_BTAB, "Select previous view"},
2962         {'h', "Show help dialog with view specific key bindings"},
2963         {',', "Page up"},
2964         {'.', "Page down"},
2965         {KEY_UP, "Select previous"},
2966         {KEY_DOWN, "Select next"},
2967         {KEY_LEFT, "Unexpand or select parent"},
2968         {KEY_RIGHT, "Expand"},
2969         {KEY_PPAGE, "Page up"},
2970         {KEY_NPAGE, "Page down"},
2971         {'\0', nullptr}};
2972     return g_source_view_key_help;
2973   }
2974 
2975   MenuActionResult MenuDelegateAction(Menu &menu) override {
2976     switch (menu.GetIdentifier()) {
2977     case eMenuID_ThreadStepIn: {
2978       ExecutionContext exe_ctx =
2979           m_debugger.GetCommandInterpreter().GetExecutionContext();
2980       if (exe_ctx.HasThreadScope()) {
2981         Process *process = exe_ctx.GetProcessPtr();
2982         if (process && process->IsAlive() &&
2983             StateIsStoppedState(process->GetState(), true))
2984           exe_ctx.GetThreadRef().StepIn(true);
2985       }
2986     }
2987       return MenuActionResult::Handled;
2988 
2989     case eMenuID_ThreadStepOut: {
2990       ExecutionContext exe_ctx =
2991           m_debugger.GetCommandInterpreter().GetExecutionContext();
2992       if (exe_ctx.HasThreadScope()) {
2993         Process *process = exe_ctx.GetProcessPtr();
2994         if (process && process->IsAlive() &&
2995             StateIsStoppedState(process->GetState(), true))
2996           exe_ctx.GetThreadRef().StepOut();
2997       }
2998     }
2999       return MenuActionResult::Handled;
3000 
3001     case eMenuID_ThreadStepOver: {
3002       ExecutionContext exe_ctx =
3003           m_debugger.GetCommandInterpreter().GetExecutionContext();
3004       if (exe_ctx.HasThreadScope()) {
3005         Process *process = exe_ctx.GetProcessPtr();
3006         if (process && process->IsAlive() &&
3007             StateIsStoppedState(process->GetState(), true))
3008           exe_ctx.GetThreadRef().StepOver(true);
3009       }
3010     }
3011       return MenuActionResult::Handled;
3012 
3013     case eMenuID_ProcessContinue: {
3014       ExecutionContext exe_ctx =
3015           m_debugger.GetCommandInterpreter().GetExecutionContext();
3016       if (exe_ctx.HasProcessScope()) {
3017         Process *process = exe_ctx.GetProcessPtr();
3018         if (process && process->IsAlive() &&
3019             StateIsStoppedState(process->GetState(), true))
3020           process->Resume();
3021       }
3022     }
3023       return MenuActionResult::Handled;
3024 
3025     case eMenuID_ProcessKill: {
3026       ExecutionContext exe_ctx =
3027           m_debugger.GetCommandInterpreter().GetExecutionContext();
3028       if (exe_ctx.HasProcessScope()) {
3029         Process *process = exe_ctx.GetProcessPtr();
3030         if (process && process->IsAlive())
3031           process->Destroy(false);
3032       }
3033     }
3034       return MenuActionResult::Handled;
3035 
3036     case eMenuID_ProcessHalt: {
3037       ExecutionContext exe_ctx =
3038           m_debugger.GetCommandInterpreter().GetExecutionContext();
3039       if (exe_ctx.HasProcessScope()) {
3040         Process *process = exe_ctx.GetProcessPtr();
3041         if (process && process->IsAlive())
3042           process->Halt();
3043       }
3044     }
3045       return MenuActionResult::Handled;
3046 
3047     case eMenuID_ProcessDetachResume:
3048     case eMenuID_ProcessDetachSuspended: {
3049       ExecutionContext exe_ctx =
3050           m_debugger.GetCommandInterpreter().GetExecutionContext();
3051       if (exe_ctx.HasProcessScope()) {
3052         Process *process = exe_ctx.GetProcessPtr();
3053         if (process && process->IsAlive())
3054           process->Detach(menu.GetIdentifier() ==
3055                           eMenuID_ProcessDetachSuspended);
3056       }
3057     }
3058       return MenuActionResult::Handled;
3059 
3060     case eMenuID_Process: {
3061       // Populate the menu with all of the threads if the process is stopped
3062       // when the Process menu gets selected and is about to display its
3063       // submenu.
3064       Menus &submenus = menu.GetSubmenus();
3065       ExecutionContext exe_ctx =
3066           m_debugger.GetCommandInterpreter().GetExecutionContext();
3067       Process *process = exe_ctx.GetProcessPtr();
3068       if (process && process->IsAlive() &&
3069           StateIsStoppedState(process->GetState(), true)) {
3070         if (submenus.size() == 7)
3071           menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
3072         else if (submenus.size() > 8)
3073           submenus.erase(submenus.begin() + 8, submenus.end());
3074 
3075         ThreadList &threads = process->GetThreadList();
3076         std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
3077         size_t num_threads = threads.GetSize();
3078         for (size_t i = 0; i < num_threads; ++i) {
3079           ThreadSP thread_sp = threads.GetThreadAtIndex(i);
3080           char menu_char = '\0';
3081           if (i < 9)
3082             menu_char = '1' + i;
3083           StreamString thread_menu_title;
3084           thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID());
3085           const char *thread_name = thread_sp->GetName();
3086           if (thread_name && thread_name[0])
3087             thread_menu_title.Printf(" %s", thread_name);
3088           else {
3089             const char *queue_name = thread_sp->GetQueueName();
3090             if (queue_name && queue_name[0])
3091               thread_menu_title.Printf(" %s", queue_name);
3092           }
3093           menu.AddSubmenu(
3094               MenuSP(new Menu(thread_menu_title.GetString().str().c_str(),
3095                               nullptr, menu_char, thread_sp->GetID())));
3096         }
3097       } else if (submenus.size() > 7) {
3098         // Remove the separator and any other thread submenu items that were
3099         // previously added
3100         submenus.erase(submenus.begin() + 7, submenus.end());
3101       }
3102       // Since we are adding and removing items we need to recalculate the name
3103       // lengths
3104       menu.RecalculateNameLengths();
3105     }
3106       return MenuActionResult::Handled;
3107 
3108     case eMenuID_ViewVariables: {
3109       WindowSP main_window_sp = m_app.GetMainWindow();
3110       WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
3111       WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
3112       WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
3113       const Rect source_bounds = source_window_sp->GetBounds();
3114 
3115       if (variables_window_sp) {
3116         const Rect variables_bounds = variables_window_sp->GetBounds();
3117 
3118         main_window_sp->RemoveSubWindow(variables_window_sp.get());
3119 
3120         if (registers_window_sp) {
3121           // We have a registers window, so give all the area back to the
3122           // registers window
3123           Rect registers_bounds = variables_bounds;
3124           registers_bounds.size.width = source_bounds.size.width;
3125           registers_window_sp->SetBounds(registers_bounds);
3126         } else {
3127           // We have no registers window showing so give the bottom area back
3128           // to the source view
3129           source_window_sp->Resize(source_bounds.size.width,
3130                                    source_bounds.size.height +
3131                                        variables_bounds.size.height);
3132         }
3133       } else {
3134         Rect new_variables_rect;
3135         if (registers_window_sp) {
3136           // We have a registers window so split the area of the registers
3137           // window into two columns where the left hand side will be the
3138           // variables and the right hand side will be the registers
3139           const Rect variables_bounds = registers_window_sp->GetBounds();
3140           Rect new_registers_rect;
3141           variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect,
3142                                                    new_registers_rect);
3143           registers_window_sp->SetBounds(new_registers_rect);
3144         } else {
3145           // No registers window, grab the bottom part of the source window
3146           Rect new_source_rect;
3147           source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
3148                                                   new_variables_rect);
3149           source_window_sp->SetBounds(new_source_rect);
3150         }
3151         WindowSP new_window_sp = main_window_sp->CreateSubWindow(
3152             "Variables", new_variables_rect, false);
3153         new_window_sp->SetDelegate(
3154             WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
3155       }
3156       touchwin(stdscr);
3157     }
3158       return MenuActionResult::Handled;
3159 
3160     case eMenuID_ViewRegisters: {
3161       WindowSP main_window_sp = m_app.GetMainWindow();
3162       WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
3163       WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
3164       WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
3165       const Rect source_bounds = source_window_sp->GetBounds();
3166 
3167       if (registers_window_sp) {
3168         if (variables_window_sp) {
3169           const Rect variables_bounds = variables_window_sp->GetBounds();
3170 
3171           // We have a variables window, so give all the area back to the
3172           // variables window
3173           variables_window_sp->Resize(variables_bounds.size.width +
3174                                           registers_window_sp->GetWidth(),
3175                                       variables_bounds.size.height);
3176         } else {
3177           // We have no variables window showing so give the bottom area back
3178           // to the source view
3179           source_window_sp->Resize(source_bounds.size.width,
3180                                    source_bounds.size.height +
3181                                        registers_window_sp->GetHeight());
3182         }
3183         main_window_sp->RemoveSubWindow(registers_window_sp.get());
3184       } else {
3185         Rect new_regs_rect;
3186         if (variables_window_sp) {
3187           // We have a variables window, split it into two columns where the
3188           // left hand side will be the variables and the right hand side will
3189           // be the registers
3190           const Rect variables_bounds = variables_window_sp->GetBounds();
3191           Rect new_vars_rect;
3192           variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect,
3193                                                    new_regs_rect);
3194           variables_window_sp->SetBounds(new_vars_rect);
3195         } else {
3196           // No variables window, grab the bottom part of the source window
3197           Rect new_source_rect;
3198           source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
3199                                                   new_regs_rect);
3200           source_window_sp->SetBounds(new_source_rect);
3201         }
3202         WindowSP new_window_sp =
3203             main_window_sp->CreateSubWindow("Registers", new_regs_rect, false);
3204         new_window_sp->SetDelegate(
3205             WindowDelegateSP(new RegistersWindowDelegate(m_debugger)));
3206       }
3207       touchwin(stdscr);
3208     }
3209       return MenuActionResult::Handled;
3210 
3211     case eMenuID_HelpGUIHelp:
3212       m_app.GetMainWindow()->CreateHelpSubwindow();
3213       return MenuActionResult::Handled;
3214 
3215     default:
3216       break;
3217     }
3218 
3219     return MenuActionResult::NotHandled;
3220   }
3221 
3222 protected:
3223   Application &m_app;
3224   Debugger &m_debugger;
3225 };
3226 
3227 class StatusBarWindowDelegate : public WindowDelegate {
3228 public:
3229   StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) {
3230     FormatEntity::Parse("Thread: ${thread.id%tid}", m_format);
3231   }
3232 
3233   ~StatusBarWindowDelegate() override = default;
3234 
3235   bool WindowDelegateDraw(Window &window, bool force) override {
3236     ExecutionContext exe_ctx =
3237         m_debugger.GetCommandInterpreter().GetExecutionContext();
3238     Process *process = exe_ctx.GetProcessPtr();
3239     Thread *thread = exe_ctx.GetThreadPtr();
3240     StackFrame *frame = exe_ctx.GetFramePtr();
3241     window.Erase();
3242     window.SetBackground(2);
3243     window.MoveCursor(0, 0);
3244     if (process) {
3245       const StateType state = process->GetState();
3246       window.Printf("Process: %5" PRIu64 " %10s", process->GetID(),
3247                     StateAsCString(state));
3248 
3249       if (StateIsStoppedState(state, true)) {
3250         StreamString strm;
3251         if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx,
3252                                            nullptr, nullptr, false, false)) {
3253           window.MoveCursor(40, 0);
3254           window.PutCStringTruncated(strm.GetString().str().c_str(), 1);
3255         }
3256 
3257         window.MoveCursor(60, 0);
3258         if (frame)
3259           window.Printf("Frame: %3u  PC = 0x%16.16" PRIx64,
3260                         frame->GetFrameIndex(),
3261                         frame->GetFrameCodeAddress().GetOpcodeLoadAddress(
3262                             exe_ctx.GetTargetPtr()));
3263       } else if (state == eStateExited) {
3264         const char *exit_desc = process->GetExitDescription();
3265         const int exit_status = process->GetExitStatus();
3266         if (exit_desc && exit_desc[0])
3267           window.Printf(" with status = %i (%s)", exit_status, exit_desc);
3268         else
3269           window.Printf(" with status = %i", exit_status);
3270       }
3271     }
3272     return true;
3273   }
3274 
3275 protected:
3276   Debugger &m_debugger;
3277   FormatEntity::Entry m_format;
3278 };
3279 
3280 class SourceFileWindowDelegate : public WindowDelegate {
3281 public:
3282   SourceFileWindowDelegate(Debugger &debugger)
3283       : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(),
3284         m_disassembly_scope(nullptr), m_disassembly_sp(), m_disassembly_range(),
3285         m_title(), m_line_width(4), m_selected_line(0), m_pc_line(0),
3286         m_stop_id(0), m_frame_idx(UINT32_MAX), m_first_visible_line(0),
3287         m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {}
3288 
3289   ~SourceFileWindowDelegate() override = default;
3290 
3291   void Update(const SymbolContext &sc) { m_sc = sc; }
3292 
3293   uint32_t NumVisibleLines() const { return m_max_y - m_min_y; }
3294 
3295   const char *WindowDelegateGetHelpText() override {
3296     return "Source/Disassembly window keyboard shortcuts:";
3297   }
3298 
3299   KeyHelp *WindowDelegateGetKeyHelp() override {
3300     static curses::KeyHelp g_source_view_key_help[] = {
3301         {KEY_RETURN, "Run to selected line with one shot breakpoint"},
3302         {KEY_UP, "Select previous source line"},
3303         {KEY_DOWN, "Select next source line"},
3304         {KEY_PPAGE, "Page up"},
3305         {KEY_NPAGE, "Page down"},
3306         {'b', "Set breakpoint on selected source/disassembly line"},
3307         {'c', "Continue process"},
3308         {'D', "Detach with process suspended"},
3309         {'h', "Show help dialog"},
3310         {'n', "Step over (source line)"},
3311         {'N', "Step over (single instruction)"},
3312         {'f', "Step out (finish)"},
3313         {'s', "Step in (source line)"},
3314         {'S', "Step in (single instruction)"},
3315         {'u', "Frame up"},
3316         {'d', "Frame down"},
3317         {',', "Page up"},
3318         {'.', "Page down"},
3319         {'\0', nullptr}};
3320     return g_source_view_key_help;
3321   }
3322 
3323   bool WindowDelegateDraw(Window &window, bool force) override {
3324     ExecutionContext exe_ctx =
3325         m_debugger.GetCommandInterpreter().GetExecutionContext();
3326     Process *process = exe_ctx.GetProcessPtr();
3327     Thread *thread = nullptr;
3328 
3329     bool update_location = false;
3330     if (process) {
3331       StateType state = process->GetState();
3332       if (StateIsStoppedState(state, true)) {
3333         // We are stopped, so it is ok to
3334         update_location = true;
3335       }
3336     }
3337 
3338     m_min_x = 1;
3339     m_min_y = 2;
3340     m_max_x = window.GetMaxX() - 1;
3341     m_max_y = window.GetMaxY() - 1;
3342 
3343     const uint32_t num_visible_lines = NumVisibleLines();
3344     StackFrameSP frame_sp;
3345     bool set_selected_line_to_pc = false;
3346 
3347     if (update_location) {
3348       const bool process_alive = process ? process->IsAlive() : false;
3349       bool thread_changed = false;
3350       if (process_alive) {
3351         thread = exe_ctx.GetThreadPtr();
3352         if (thread) {
3353           frame_sp = thread->GetSelectedFrame();
3354           auto tid = thread->GetID();
3355           thread_changed = tid != m_tid;
3356           m_tid = tid;
3357         } else {
3358           if (m_tid != LLDB_INVALID_THREAD_ID) {
3359             thread_changed = true;
3360             m_tid = LLDB_INVALID_THREAD_ID;
3361           }
3362         }
3363       }
3364       const uint32_t stop_id = process ? process->GetStopID() : 0;
3365       const bool stop_id_changed = stop_id != m_stop_id;
3366       bool frame_changed = false;
3367       m_stop_id = stop_id;
3368       m_title.Clear();
3369       if (frame_sp) {
3370         m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything);
3371         if (m_sc.module_sp) {
3372           m_title.Printf(
3373               "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString());
3374           ConstString func_name = m_sc.GetFunctionName();
3375           if (func_name)
3376             m_title.Printf("`%s", func_name.GetCString());
3377         }
3378         const uint32_t frame_idx = frame_sp->GetFrameIndex();
3379         frame_changed = frame_idx != m_frame_idx;
3380         m_frame_idx = frame_idx;
3381       } else {
3382         m_sc.Clear(true);
3383         frame_changed = m_frame_idx != UINT32_MAX;
3384         m_frame_idx = UINT32_MAX;
3385       }
3386 
3387       const bool context_changed =
3388           thread_changed || frame_changed || stop_id_changed;
3389 
3390       if (process_alive) {
3391         if (m_sc.line_entry.IsValid()) {
3392           m_pc_line = m_sc.line_entry.line;
3393           if (m_pc_line != UINT32_MAX)
3394             --m_pc_line; // Convert to zero based line number...
3395           // Update the selected line if the stop ID changed...
3396           if (context_changed)
3397             m_selected_line = m_pc_line;
3398 
3399           if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) {
3400             // Same file, nothing to do, we should either have the lines or not
3401             // (source file missing)
3402             if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) {
3403               if (m_selected_line >= m_first_visible_line + num_visible_lines)
3404                 m_first_visible_line = m_selected_line - 10;
3405             } else {
3406               if (m_selected_line > 10)
3407                 m_first_visible_line = m_selected_line - 10;
3408               else
3409                 m_first_visible_line = 0;
3410             }
3411           } else {
3412             // File changed, set selected line to the line with the PC
3413             m_selected_line = m_pc_line;
3414             m_file_sp =
3415                 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file);
3416             if (m_file_sp) {
3417               const size_t num_lines = m_file_sp->GetNumLines();
3418               m_line_width = 1;
3419               for (size_t n = num_lines; n >= 10; n = n / 10)
3420                 ++m_line_width;
3421 
3422               if (num_lines < num_visible_lines ||
3423                   m_selected_line < num_visible_lines)
3424                 m_first_visible_line = 0;
3425               else
3426                 m_first_visible_line = m_selected_line - 10;
3427             }
3428           }
3429         } else {
3430           m_file_sp.reset();
3431         }
3432 
3433         if (!m_file_sp || m_file_sp->GetNumLines() == 0) {
3434           // Show disassembly
3435           bool prefer_file_cache = false;
3436           if (m_sc.function) {
3437             if (m_disassembly_scope != m_sc.function) {
3438               m_disassembly_scope = m_sc.function;
3439               m_disassembly_sp = m_sc.function->GetInstructions(
3440                   exe_ctx, nullptr, prefer_file_cache);
3441               if (m_disassembly_sp) {
3442                 set_selected_line_to_pc = true;
3443                 m_disassembly_range = m_sc.function->GetAddressRange();
3444               } else {
3445                 m_disassembly_range.Clear();
3446               }
3447             } else {
3448               set_selected_line_to_pc = context_changed;
3449             }
3450           } else if (m_sc.symbol) {
3451             if (m_disassembly_scope != m_sc.symbol) {
3452               m_disassembly_scope = m_sc.symbol;
3453               m_disassembly_sp = m_sc.symbol->GetInstructions(
3454                   exe_ctx, nullptr, prefer_file_cache);
3455               if (m_disassembly_sp) {
3456                 set_selected_line_to_pc = true;
3457                 m_disassembly_range.GetBaseAddress() =
3458                     m_sc.symbol->GetAddress();
3459                 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize());
3460               } else {
3461                 m_disassembly_range.Clear();
3462               }
3463             } else {
3464               set_selected_line_to_pc = context_changed;
3465             }
3466           }
3467         }
3468       } else {
3469         m_pc_line = UINT32_MAX;
3470       }
3471     }
3472 
3473     const int window_width = window.GetWidth();
3474     window.Erase();
3475     window.DrawTitleBox("Sources");
3476     if (!m_title.GetString().empty()) {
3477       window.AttributeOn(A_REVERSE);
3478       window.MoveCursor(1, 1);
3479       window.PutChar(' ');
3480       window.PutCStringTruncated(m_title.GetString().str().c_str(), 1);
3481       int x = window.GetCursorX();
3482       if (x < window_width - 1) {
3483         window.Printf("%*s", window_width - x - 1, "");
3484       }
3485       window.AttributeOff(A_REVERSE);
3486     }
3487 
3488     Target *target = exe_ctx.GetTargetPtr();
3489     const size_t num_source_lines = GetNumSourceLines();
3490     if (num_source_lines > 0) {
3491       // Display source
3492       BreakpointLines bp_lines;
3493       if (target) {
3494         BreakpointList &bp_list = target->GetBreakpointList();
3495         const size_t num_bps = bp_list.GetSize();
3496         for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
3497           BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
3498           const size_t num_bps_locs = bp_sp->GetNumLocations();
3499           for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
3500             BreakpointLocationSP bp_loc_sp =
3501                 bp_sp->GetLocationAtIndex(bp_loc_idx);
3502             LineEntry bp_loc_line_entry;
3503             if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
3504                     bp_loc_line_entry)) {
3505               if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) {
3506                 bp_lines.insert(bp_loc_line_entry.line);
3507               }
3508             }
3509           }
3510         }
3511       }
3512 
3513       const attr_t selected_highlight_attr = A_REVERSE;
3514       const attr_t pc_highlight_attr = COLOR_PAIR(1);
3515 
3516       for (size_t i = 0; i < num_visible_lines; ++i) {
3517         const uint32_t curr_line = m_first_visible_line + i;
3518         if (curr_line < num_source_lines) {
3519           const int line_y = m_min_y + i;
3520           window.MoveCursor(1, line_y);
3521           const bool is_pc_line = curr_line == m_pc_line;
3522           const bool line_is_selected = m_selected_line == curr_line;
3523           // Highlight the line as the PC line first, then if the selected line
3524           // isn't the same as the PC line, highlight it differently
3525           attr_t highlight_attr = 0;
3526           attr_t bp_attr = 0;
3527           if (is_pc_line)
3528             highlight_attr = pc_highlight_attr;
3529           else if (line_is_selected)
3530             highlight_attr = selected_highlight_attr;
3531 
3532           if (bp_lines.find(curr_line + 1) != bp_lines.end())
3533             bp_attr = COLOR_PAIR(2);
3534 
3535           if (bp_attr)
3536             window.AttributeOn(bp_attr);
3537 
3538           window.Printf(" %*u ", m_line_width, curr_line + 1);
3539 
3540           if (bp_attr)
3541             window.AttributeOff(bp_attr);
3542 
3543           window.PutChar(ACS_VLINE);
3544           // Mark the line with the PC with a diamond
3545           if (is_pc_line)
3546             window.PutChar(ACS_DIAMOND);
3547           else
3548             window.PutChar(' ');
3549 
3550           if (highlight_attr)
3551             window.AttributeOn(highlight_attr);
3552           const uint32_t line_len =
3553               m_file_sp->GetLineLength(curr_line + 1, false);
3554           if (line_len > 0)
3555             window.PutCString(m_file_sp->PeekLineData(curr_line + 1), line_len);
3556 
3557           if (is_pc_line && frame_sp &&
3558               frame_sp->GetConcreteFrameIndex() == 0) {
3559             StopInfoSP stop_info_sp;
3560             if (thread)
3561               stop_info_sp = thread->GetStopInfo();
3562             if (stop_info_sp) {
3563               const char *stop_description = stop_info_sp->GetDescription();
3564               if (stop_description && stop_description[0]) {
3565                 size_t stop_description_len = strlen(stop_description);
3566                 int desc_x = window_width - stop_description_len - 16;
3567                 window.Printf("%*s", desc_x - window.GetCursorX(), "");
3568                 // window.MoveCursor(window_width - stop_description_len - 15,
3569                 // line_y);
3570                 window.Printf("<<< Thread %u: %s ", thread->GetIndexID(),
3571                               stop_description);
3572               }
3573             } else {
3574               window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
3575             }
3576           }
3577           if (highlight_attr)
3578             window.AttributeOff(highlight_attr);
3579         } else {
3580           break;
3581         }
3582       }
3583     } else {
3584       size_t num_disassembly_lines = GetNumDisassemblyLines();
3585       if (num_disassembly_lines > 0) {
3586         // Display disassembly
3587         BreakpointAddrs bp_file_addrs;
3588         Target *target = exe_ctx.GetTargetPtr();
3589         if (target) {
3590           BreakpointList &bp_list = target->GetBreakpointList();
3591           const size_t num_bps = bp_list.GetSize();
3592           for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
3593             BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
3594             const size_t num_bps_locs = bp_sp->GetNumLocations();
3595             for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs;
3596                  ++bp_loc_idx) {
3597               BreakpointLocationSP bp_loc_sp =
3598                   bp_sp->GetLocationAtIndex(bp_loc_idx);
3599               LineEntry bp_loc_line_entry;
3600               const lldb::addr_t file_addr =
3601                   bp_loc_sp->GetAddress().GetFileAddress();
3602               if (file_addr != LLDB_INVALID_ADDRESS) {
3603                 if (m_disassembly_range.ContainsFileAddress(file_addr))
3604                   bp_file_addrs.insert(file_addr);
3605               }
3606             }
3607           }
3608         }
3609 
3610         const attr_t selected_highlight_attr = A_REVERSE;
3611         const attr_t pc_highlight_attr = COLOR_PAIR(1);
3612 
3613         StreamString strm;
3614 
3615         InstructionList &insts = m_disassembly_sp->GetInstructionList();
3616         Address pc_address;
3617 
3618         if (frame_sp)
3619           pc_address = frame_sp->GetFrameCodeAddress();
3620         const uint32_t pc_idx =
3621             pc_address.IsValid()
3622                 ? insts.GetIndexOfInstructionAtAddress(pc_address)
3623                 : UINT32_MAX;
3624         if (set_selected_line_to_pc) {
3625           m_selected_line = pc_idx;
3626         }
3627 
3628         const uint32_t non_visible_pc_offset = (num_visible_lines / 5);
3629         if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines)
3630           m_first_visible_line = 0;
3631 
3632         if (pc_idx < num_disassembly_lines) {
3633           if (pc_idx < static_cast<uint32_t>(m_first_visible_line) ||
3634               pc_idx >= m_first_visible_line + num_visible_lines)
3635             m_first_visible_line = pc_idx - non_visible_pc_offset;
3636         }
3637 
3638         for (size_t i = 0; i < num_visible_lines; ++i) {
3639           const uint32_t inst_idx = m_first_visible_line + i;
3640           Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get();
3641           if (!inst)
3642             break;
3643 
3644           const int line_y = m_min_y + i;
3645           window.MoveCursor(1, line_y);
3646           const bool is_pc_line = frame_sp && inst_idx == pc_idx;
3647           const bool line_is_selected = m_selected_line == inst_idx;
3648           // Highlight the line as the PC line first, then if the selected line
3649           // isn't the same as the PC line, highlight it differently
3650           attr_t highlight_attr = 0;
3651           attr_t bp_attr = 0;
3652           if (is_pc_line)
3653             highlight_attr = pc_highlight_attr;
3654           else if (line_is_selected)
3655             highlight_attr = selected_highlight_attr;
3656 
3657           if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) !=
3658               bp_file_addrs.end())
3659             bp_attr = COLOR_PAIR(2);
3660 
3661           if (bp_attr)
3662             window.AttributeOn(bp_attr);
3663 
3664           window.Printf(" 0x%16.16llx ",
3665                         static_cast<unsigned long long>(
3666                             inst->GetAddress().GetLoadAddress(target)));
3667 
3668           if (bp_attr)
3669             window.AttributeOff(bp_attr);
3670 
3671           window.PutChar(ACS_VLINE);
3672           // Mark the line with the PC with a diamond
3673           if (is_pc_line)
3674             window.PutChar(ACS_DIAMOND);
3675           else
3676             window.PutChar(' ');
3677 
3678           if (highlight_attr)
3679             window.AttributeOn(highlight_attr);
3680 
3681           const char *mnemonic = inst->GetMnemonic(&exe_ctx);
3682           const char *operands = inst->GetOperands(&exe_ctx);
3683           const char *comment = inst->GetComment(&exe_ctx);
3684 
3685           if (mnemonic != nullptr && mnemonic[0] == '\0')
3686             mnemonic = nullptr;
3687           if (operands != nullptr && operands[0] == '\0')
3688             operands = nullptr;
3689           if (comment != nullptr && comment[0] == '\0')
3690             comment = nullptr;
3691 
3692           strm.Clear();
3693 
3694           if (mnemonic != nullptr && operands != nullptr && comment != nullptr)
3695             strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment);
3696           else if (mnemonic != nullptr && operands != nullptr)
3697             strm.Printf("%-8s %s", mnemonic, operands);
3698           else if (mnemonic != nullptr)
3699             strm.Printf("%s", mnemonic);
3700 
3701           int right_pad = 1;
3702           window.PutCStringTruncated(strm.GetData(), right_pad);
3703 
3704           if (is_pc_line && frame_sp &&
3705               frame_sp->GetConcreteFrameIndex() == 0) {
3706             StopInfoSP stop_info_sp;
3707             if (thread)
3708               stop_info_sp = thread->GetStopInfo();
3709             if (stop_info_sp) {
3710               const char *stop_description = stop_info_sp->GetDescription();
3711               if (stop_description && stop_description[0]) {
3712                 size_t stop_description_len = strlen(stop_description);
3713                 int desc_x = window_width - stop_description_len - 16;
3714                 window.Printf("%*s", desc_x - window.GetCursorX(), "");
3715                 // window.MoveCursor(window_width - stop_description_len - 15,
3716                 // line_y);
3717                 window.Printf("<<< Thread %u: %s ", thread->GetIndexID(),
3718                               stop_description);
3719               }
3720             } else {
3721               window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
3722             }
3723           }
3724           if (highlight_attr)
3725             window.AttributeOff(highlight_attr);
3726         }
3727       }
3728     }
3729     return true; // Drawing handled
3730   }
3731 
3732   size_t GetNumLines() {
3733     size_t num_lines = GetNumSourceLines();
3734     if (num_lines == 0)
3735       num_lines = GetNumDisassemblyLines();
3736     return num_lines;
3737   }
3738 
3739   size_t GetNumSourceLines() const {
3740     if (m_file_sp)
3741       return m_file_sp->GetNumLines();
3742     return 0;
3743   }
3744 
3745   size_t GetNumDisassemblyLines() const {
3746     if (m_disassembly_sp)
3747       return m_disassembly_sp->GetInstructionList().GetSize();
3748     return 0;
3749   }
3750 
3751   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
3752     const uint32_t num_visible_lines = NumVisibleLines();
3753     const size_t num_lines = GetNumLines();
3754 
3755     switch (c) {
3756     case ',':
3757     case KEY_PPAGE:
3758       // Page up key
3759       if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines)
3760         m_first_visible_line -= num_visible_lines;
3761       else
3762         m_first_visible_line = 0;
3763       m_selected_line = m_first_visible_line;
3764       return eKeyHandled;
3765 
3766     case '.':
3767     case KEY_NPAGE:
3768       // Page down key
3769       {
3770         if (m_first_visible_line + num_visible_lines < num_lines)
3771           m_first_visible_line += num_visible_lines;
3772         else if (num_lines < num_visible_lines)
3773           m_first_visible_line = 0;
3774         else
3775           m_first_visible_line = num_lines - num_visible_lines;
3776         m_selected_line = m_first_visible_line;
3777       }
3778       return eKeyHandled;
3779 
3780     case KEY_UP:
3781       if (m_selected_line > 0) {
3782         m_selected_line--;
3783         if (static_cast<size_t>(m_first_visible_line) > m_selected_line)
3784           m_first_visible_line = m_selected_line;
3785       }
3786       return eKeyHandled;
3787 
3788     case KEY_DOWN:
3789       if (m_selected_line + 1 < num_lines) {
3790         m_selected_line++;
3791         if (m_first_visible_line + num_visible_lines < m_selected_line)
3792           m_first_visible_line++;
3793       }
3794       return eKeyHandled;
3795 
3796     case '\r':
3797     case '\n':
3798     case KEY_ENTER:
3799       // Set a breakpoint and run to the line using a one shot breakpoint
3800       if (GetNumSourceLines() > 0) {
3801         ExecutionContext exe_ctx =
3802             m_debugger.GetCommandInterpreter().GetExecutionContext();
3803         if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) {
3804           BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
3805               nullptr, // Don't limit the breakpoint to certain modules
3806               m_file_sp->GetFileSpec(), // Source file
3807               m_selected_line +
3808                   1, // Source line number (m_selected_line is zero based)
3809               0,     // Unspecified column.
3810               0,     // No offset
3811               eLazyBoolCalculate,  // Check inlines using global setting
3812               eLazyBoolCalculate,  // Skip prologue using global setting,
3813               false,               // internal
3814               false,               // request_hardware
3815               eLazyBoolCalculate); // move_to_nearest_code
3816           // Make breakpoint one shot
3817           bp_sp->GetOptions()->SetOneShot(true);
3818           exe_ctx.GetProcessRef().Resume();
3819         }
3820       } else if (m_selected_line < GetNumDisassemblyLines()) {
3821         const Instruction *inst = m_disassembly_sp->GetInstructionList()
3822                                       .GetInstructionAtIndex(m_selected_line)
3823                                       .get();
3824         ExecutionContext exe_ctx =
3825             m_debugger.GetCommandInterpreter().GetExecutionContext();
3826         if (exe_ctx.HasTargetScope()) {
3827           Address addr = inst->GetAddress();
3828           BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
3829               addr,   // lldb_private::Address
3830               false,  // internal
3831               false); // request_hardware
3832           // Make breakpoint one shot
3833           bp_sp->GetOptions()->SetOneShot(true);
3834           exe_ctx.GetProcessRef().Resume();
3835         }
3836       }
3837       return eKeyHandled;
3838 
3839     case 'b': // 'b' == toggle breakpoint on currently selected line
3840       ToggleBreakpointOnSelectedLine();
3841       return eKeyHandled;
3842 
3843     case 'D': // 'D' == detach and keep stopped
3844     {
3845       ExecutionContext exe_ctx =
3846           m_debugger.GetCommandInterpreter().GetExecutionContext();
3847       if (exe_ctx.HasProcessScope())
3848         exe_ctx.GetProcessRef().Detach(true);
3849     }
3850       return eKeyHandled;
3851 
3852     case 'c':
3853       // 'c' == continue
3854       {
3855         ExecutionContext exe_ctx =
3856             m_debugger.GetCommandInterpreter().GetExecutionContext();
3857         if (exe_ctx.HasProcessScope())
3858           exe_ctx.GetProcessRef().Resume();
3859       }
3860       return eKeyHandled;
3861 
3862     case 'f':
3863       // 'f' == step out (finish)
3864       {
3865         ExecutionContext exe_ctx =
3866             m_debugger.GetCommandInterpreter().GetExecutionContext();
3867         if (exe_ctx.HasThreadScope() &&
3868             StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
3869           exe_ctx.GetThreadRef().StepOut();
3870         }
3871       }
3872       return eKeyHandled;
3873 
3874     case 'n': // 'n' == step over
3875     case 'N': // 'N' == step over instruction
3876     {
3877       ExecutionContext exe_ctx =
3878           m_debugger.GetCommandInterpreter().GetExecutionContext();
3879       if (exe_ctx.HasThreadScope() &&
3880           StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
3881         bool source_step = (c == 'n');
3882         exe_ctx.GetThreadRef().StepOver(source_step);
3883       }
3884     }
3885       return eKeyHandled;
3886 
3887     case 's': // 's' == step into
3888     case 'S': // 'S' == step into instruction
3889     {
3890       ExecutionContext exe_ctx =
3891           m_debugger.GetCommandInterpreter().GetExecutionContext();
3892       if (exe_ctx.HasThreadScope() &&
3893           StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
3894         bool source_step = (c == 's');
3895         exe_ctx.GetThreadRef().StepIn(source_step);
3896       }
3897     }
3898       return eKeyHandled;
3899 
3900     case 'u': // 'u' == frame up
3901     case 'd': // 'd' == frame down
3902     {
3903       ExecutionContext exe_ctx =
3904           m_debugger.GetCommandInterpreter().GetExecutionContext();
3905       if (exe_ctx.HasThreadScope()) {
3906         Thread *thread = exe_ctx.GetThreadPtr();
3907         uint32_t frame_idx = thread->GetSelectedFrameIndex();
3908         if (frame_idx == UINT32_MAX)
3909           frame_idx = 0;
3910         if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount())
3911           ++frame_idx;
3912         else if (c == 'd' && frame_idx > 0)
3913           --frame_idx;
3914         if (thread->SetSelectedFrameByIndex(frame_idx, true))
3915           exe_ctx.SetFrameSP(thread->GetSelectedFrame());
3916       }
3917     }
3918       return eKeyHandled;
3919 
3920     case 'h':
3921       window.CreateHelpSubwindow();
3922       return eKeyHandled;
3923 
3924     default:
3925       break;
3926     }
3927     return eKeyNotHandled;
3928   }
3929 
3930   void ToggleBreakpointOnSelectedLine() {
3931     ExecutionContext exe_ctx =
3932         m_debugger.GetCommandInterpreter().GetExecutionContext();
3933     if (!exe_ctx.HasTargetScope())
3934       return;
3935     if (GetNumSourceLines() > 0) {
3936       // Source file breakpoint.
3937       BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
3938       const size_t num_bps = bp_list.GetSize();
3939       for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
3940         BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
3941         const size_t num_bps_locs = bp_sp->GetNumLocations();
3942         for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
3943           BreakpointLocationSP bp_loc_sp =
3944               bp_sp->GetLocationAtIndex(bp_loc_idx);
3945           LineEntry bp_loc_line_entry;
3946           if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
3947                   bp_loc_line_entry)) {
3948             if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file &&
3949                 m_selected_line + 1 == bp_loc_line_entry.line) {
3950               bool removed =
3951                   exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
3952               assert(removed);
3953               UNUSED_IF_ASSERT_DISABLED(removed);
3954               return; // Existing breakpoint removed.
3955             }
3956           }
3957         }
3958       }
3959       // No breakpoint found on the location, add it.
3960       BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
3961           nullptr, // Don't limit the breakpoint to certain modules
3962           m_file_sp->GetFileSpec(), // Source file
3963           m_selected_line +
3964               1, // Source line number (m_selected_line is zero based)
3965           0,     // No column specified.
3966           0,     // No offset
3967           eLazyBoolCalculate,  // Check inlines using global setting
3968           eLazyBoolCalculate,  // Skip prologue using global setting,
3969           false,               // internal
3970           false,               // request_hardware
3971           eLazyBoolCalculate); // move_to_nearest_code
3972     } else {
3973       // Disassembly breakpoint.
3974       assert(GetNumDisassemblyLines() > 0);
3975       assert(m_selected_line < GetNumDisassemblyLines());
3976       const Instruction *inst = m_disassembly_sp->GetInstructionList()
3977                                     .GetInstructionAtIndex(m_selected_line)
3978                                     .get();
3979       Address addr = inst->GetAddress();
3980       // Try to find it.
3981       BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
3982       const size_t num_bps = bp_list.GetSize();
3983       for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
3984         BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
3985         const size_t num_bps_locs = bp_sp->GetNumLocations();
3986         for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
3987           BreakpointLocationSP bp_loc_sp =
3988               bp_sp->GetLocationAtIndex(bp_loc_idx);
3989           LineEntry bp_loc_line_entry;
3990           const lldb::addr_t file_addr =
3991               bp_loc_sp->GetAddress().GetFileAddress();
3992           if (file_addr == addr.GetFileAddress()) {
3993             bool removed =
3994                 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
3995             assert(removed);
3996             UNUSED_IF_ASSERT_DISABLED(removed);
3997             return; // Existing breakpoint removed.
3998           }
3999         }
4000       }
4001       // No breakpoint found on the address, add it.
4002       BreakpointSP bp_sp =
4003           exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address
4004                                                   false,  // internal
4005                                                   false); // request_hardware
4006     }
4007   }
4008 
4009 protected:
4010   typedef std::set<uint32_t> BreakpointLines;
4011   typedef std::set<lldb::addr_t> BreakpointAddrs;
4012 
4013   Debugger &m_debugger;
4014   SymbolContext m_sc;
4015   SourceManager::FileSP m_file_sp;
4016   SymbolContextScope *m_disassembly_scope;
4017   lldb::DisassemblerSP m_disassembly_sp;
4018   AddressRange m_disassembly_range;
4019   StreamString m_title;
4020   lldb::user_id_t m_tid;
4021   int m_line_width;
4022   uint32_t m_selected_line; // The selected line
4023   uint32_t m_pc_line;       // The line with the PC
4024   uint32_t m_stop_id;
4025   uint32_t m_frame_idx;
4026   int m_first_visible_line;
4027   int m_min_x;
4028   int m_min_y;
4029   int m_max_x;
4030   int m_max_y;
4031 };
4032 
4033 DisplayOptions ValueObjectListDelegate::g_options = {true};
4034 
4035 IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger)
4036     : IOHandler(debugger, IOHandler::Type::Curses) {}
4037 
4038 void IOHandlerCursesGUI::Activate() {
4039   IOHandler::Activate();
4040   if (!m_app_ap) {
4041     m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE());
4042 
4043     // This is both a window and a menu delegate
4044     std::shared_ptr<ApplicationDelegate> app_delegate_sp(
4045         new ApplicationDelegate(*m_app_ap, m_debugger));
4046 
4047     MenuDelegateSP app_menu_delegate_sp =
4048         std::static_pointer_cast<MenuDelegate>(app_delegate_sp);
4049     MenuSP lldb_menu_sp(
4050         new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB));
4051     MenuSP exit_menuitem_sp(
4052         new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit));
4053     exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit);
4054     lldb_menu_sp->AddSubmenu(MenuSP(new Menu(
4055         "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout)));
4056     lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
4057     lldb_menu_sp->AddSubmenu(exit_menuitem_sp);
4058 
4059     MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2),
4060                                    ApplicationDelegate::eMenuID_Target));
4061     target_menu_sp->AddSubmenu(MenuSP(new Menu(
4062         "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate)));
4063     target_menu_sp->AddSubmenu(MenuSP(new Menu(
4064         "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete)));
4065 
4066     MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3),
4067                                     ApplicationDelegate::eMenuID_Process));
4068     process_menu_sp->AddSubmenu(MenuSP(new Menu(
4069         "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach)));
4070     process_menu_sp->AddSubmenu(
4071         MenuSP(new Menu("Detach and resume", nullptr, 'd',
4072                         ApplicationDelegate::eMenuID_ProcessDetachResume)));
4073     process_menu_sp->AddSubmenu(
4074         MenuSP(new Menu("Detach suspended", nullptr, 's',
4075                         ApplicationDelegate::eMenuID_ProcessDetachSuspended)));
4076     process_menu_sp->AddSubmenu(MenuSP(new Menu(
4077         "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch)));
4078     process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
4079     process_menu_sp->AddSubmenu(
4080         MenuSP(new Menu("Continue", nullptr, 'c',
4081                         ApplicationDelegate::eMenuID_ProcessContinue)));
4082     process_menu_sp->AddSubmenu(MenuSP(new Menu(
4083         "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt)));
4084     process_menu_sp->AddSubmenu(MenuSP(new Menu(
4085         "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill)));
4086 
4087     MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4),
4088                                    ApplicationDelegate::eMenuID_Thread));
4089     thread_menu_sp->AddSubmenu(MenuSP(new Menu(
4090         "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn)));
4091     thread_menu_sp->AddSubmenu(
4092         MenuSP(new Menu("Step Over", nullptr, 'v',
4093                         ApplicationDelegate::eMenuID_ThreadStepOver)));
4094     thread_menu_sp->AddSubmenu(MenuSP(new Menu(
4095         "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut)));
4096 
4097     MenuSP view_menu_sp(
4098         new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View));
4099     view_menu_sp->AddSubmenu(
4100         MenuSP(new Menu("Backtrace", nullptr, 'b',
4101                         ApplicationDelegate::eMenuID_ViewBacktrace)));
4102     view_menu_sp->AddSubmenu(
4103         MenuSP(new Menu("Registers", nullptr, 'r',
4104                         ApplicationDelegate::eMenuID_ViewRegisters)));
4105     view_menu_sp->AddSubmenu(MenuSP(new Menu(
4106         "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource)));
4107     view_menu_sp->AddSubmenu(
4108         MenuSP(new Menu("Variables", nullptr, 'v',
4109                         ApplicationDelegate::eMenuID_ViewVariables)));
4110 
4111     MenuSP help_menu_sp(
4112         new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help));
4113     help_menu_sp->AddSubmenu(MenuSP(new Menu(
4114         "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp)));
4115 
4116     m_app_ap->Initialize();
4117     WindowSP &main_window_sp = m_app_ap->GetMainWindow();
4118 
4119     MenuSP menubar_sp(new Menu(Menu::Type::Bar));
4120     menubar_sp->AddSubmenu(lldb_menu_sp);
4121     menubar_sp->AddSubmenu(target_menu_sp);
4122     menubar_sp->AddSubmenu(process_menu_sp);
4123     menubar_sp->AddSubmenu(thread_menu_sp);
4124     menubar_sp->AddSubmenu(view_menu_sp);
4125     menubar_sp->AddSubmenu(help_menu_sp);
4126     menubar_sp->SetDelegate(app_menu_delegate_sp);
4127 
4128     Rect content_bounds = main_window_sp->GetFrame();
4129     Rect menubar_bounds = content_bounds.MakeMenuBar();
4130     Rect status_bounds = content_bounds.MakeStatusBar();
4131     Rect source_bounds;
4132     Rect variables_bounds;
4133     Rect threads_bounds;
4134     Rect source_variables_bounds;
4135     content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
4136                                            threads_bounds);
4137     source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds,
4138                                                       variables_bounds);
4139 
4140     WindowSP menubar_window_sp =
4141         main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false);
4142     // Let the menubar get keys if the active window doesn't handle the keys
4143     // that are typed so it can respond to menubar key presses.
4144     menubar_window_sp->SetCanBeActive(
4145         false); // Don't let the menubar become the active window
4146     menubar_window_sp->SetDelegate(menubar_sp);
4147 
4148     WindowSP source_window_sp(
4149         main_window_sp->CreateSubWindow("Source", source_bounds, true));
4150     WindowSP variables_window_sp(
4151         main_window_sp->CreateSubWindow("Variables", variables_bounds, false));
4152     WindowSP threads_window_sp(
4153         main_window_sp->CreateSubWindow("Threads", threads_bounds, false));
4154     WindowSP status_window_sp(
4155         main_window_sp->CreateSubWindow("Status", status_bounds, false));
4156     status_window_sp->SetCanBeActive(
4157         false); // Don't let the status bar become the active window
4158     main_window_sp->SetDelegate(
4159         std::static_pointer_cast<WindowDelegate>(app_delegate_sp));
4160     source_window_sp->SetDelegate(
4161         WindowDelegateSP(new SourceFileWindowDelegate(m_debugger)));
4162     variables_window_sp->SetDelegate(
4163         WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
4164     TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger));
4165     threads_window_sp->SetDelegate(WindowDelegateSP(
4166         new TreeWindowDelegate(m_debugger, thread_delegate_sp)));
4167     status_window_sp->SetDelegate(
4168         WindowDelegateSP(new StatusBarWindowDelegate(m_debugger)));
4169 
4170     // Show the main help window once the first time the curses GUI is launched
4171     static bool g_showed_help = false;
4172     if (!g_showed_help) {
4173       g_showed_help = true;
4174       main_window_sp->CreateHelpSubwindow();
4175     }
4176 
4177     init_pair(1, COLOR_WHITE, COLOR_BLUE);
4178     init_pair(2, COLOR_BLACK, COLOR_WHITE);
4179     init_pair(3, COLOR_MAGENTA, COLOR_WHITE);
4180     init_pair(4, COLOR_MAGENTA, COLOR_BLACK);
4181     init_pair(5, COLOR_RED, COLOR_BLACK);
4182   }
4183 }
4184 
4185 void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); }
4186 
4187 void IOHandlerCursesGUI::Run() {
4188   m_app_ap->Run(m_debugger);
4189   SetIsDone(true);
4190 }
4191 
4192 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default;
4193 
4194 void IOHandlerCursesGUI::Cancel() {}
4195 
4196 bool IOHandlerCursesGUI::Interrupt() { return false; }
4197 
4198 void IOHandlerCursesGUI::GotEOF() {}
4199 
4200 void IOHandlerCursesGUI::TerminalSizeChanged() {
4201   m_app_ap->TerminalSizeChanged();
4202 }
4203 
4204 #endif // LLDB_ENABLE_CURSES
4205