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