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