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