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