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