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