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