1 //===-- IOHandlerCursesGUI.cpp --------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "lldb/Core/IOHandlerCursesGUI.h"
10 #include "lldb/Host/Config.h"
11 
12 #if LLDB_ENABLE_CURSES
13 #if CURSES_HAVE_NCURSES_CURSES_H
14 #include <ncurses/curses.h>
15 #include <ncurses/panel.h>
16 #else
17 #include <curses.h>
18 #include <panel.h>
19 #endif
20 #endif
21 
22 #if defined(__APPLE__)
23 #include <deque>
24 #endif
25 #include <string>
26 
27 #include "lldb/Core/Debugger.h"
28 #include "lldb/Core/StreamFile.h"
29 #include "lldb/Core/ValueObjectUpdater.h"
30 #include "lldb/Host/File.h"
31 #include "lldb/Utility/Predicate.h"
32 #include "lldb/Utility/Status.h"
33 #include "lldb/Utility/StreamString.h"
34 #include "lldb/Utility/StringList.h"
35 #include "lldb/lldb-forward.h"
36 
37 #include "lldb/Interpreter/CommandCompletions.h"
38 #include "lldb/Interpreter/CommandInterpreter.h"
39 
40 #if LLDB_ENABLE_CURSES
41 #include "lldb/Breakpoint/BreakpointLocation.h"
42 #include "lldb/Core/Module.h"
43 #include "lldb/Core/PluginManager.h"
44 #include "lldb/Core/ValueObject.h"
45 #include "lldb/Core/ValueObjectRegister.h"
46 #include "lldb/Symbol/Block.h"
47 #include "lldb/Symbol/Function.h"
48 #include "lldb/Symbol/Symbol.h"
49 #include "lldb/Symbol/VariableList.h"
50 #include "lldb/Target/Process.h"
51 #include "lldb/Target/RegisterContext.h"
52 #include "lldb/Target/StackFrame.h"
53 #include "lldb/Target/StopInfo.h"
54 #include "lldb/Target/Target.h"
55 #include "lldb/Target/Thread.h"
56 #include "lldb/Utility/State.h"
57 #endif
58 
59 #include "llvm/ADT/StringRef.h"
60 
61 #ifdef _WIN32
62 #include "lldb/Host/windows/windows.h"
63 #endif
64 
65 #include <memory>
66 #include <mutex>
67 
68 #include <cassert>
69 #include <cctype>
70 #include <cerrno>
71 #include <cstdint>
72 #include <cstdio>
73 #include <cstring>
74 #include <functional>
75 #include <type_traits>
76 
77 using namespace lldb;
78 using namespace lldb_private;
79 using llvm::None;
80 using llvm::Optional;
81 using llvm::StringRef;
82 
83 // we may want curses to be disabled for some builds for instance, windows
84 #if LLDB_ENABLE_CURSES
85 
86 #define KEY_RETURN 10
87 #define KEY_ESCAPE 27
88 
89 #define KEY_SHIFT_TAB (KEY_MAX + 1)
90 
91 namespace curses {
92 class Menu;
93 class MenuDelegate;
94 class Window;
95 class WindowDelegate;
96 typedef std::shared_ptr<Menu> MenuSP;
97 typedef std::shared_ptr<MenuDelegate> MenuDelegateSP;
98 typedef std::shared_ptr<Window> WindowSP;
99 typedef std::shared_ptr<WindowDelegate> WindowDelegateSP;
100 typedef std::vector<MenuSP> Menus;
101 typedef std::vector<WindowSP> Windows;
102 typedef std::vector<WindowDelegateSP> WindowDelegates;
103 
104 #if 0
105 type summary add -s "x=${var.x}, y=${var.y}" curses::Point
106 type summary add -s "w=${var.width}, h=${var.height}" curses::Size
107 type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect
108 #endif
109 
110 struct Point {
111   int x;
112   int y;
113 
Pointcurses::Point114   Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}
115 
Clearcurses::Point116   void Clear() {
117     x = 0;
118     y = 0;
119   }
120 
operator +=curses::Point121   Point &operator+=(const Point &rhs) {
122     x += rhs.x;
123     y += rhs.y;
124     return *this;
125   }
126 
Dumpcurses::Point127   void Dump() { printf("(x=%i, y=%i)\n", x, 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 
operator !=(const Point & lhs,const Point & rhs)134 bool operator!=(const Point &lhs, const Point &rhs) {
135   return lhs.x != rhs.x || lhs.y != rhs.y;
136 }
137 
138 struct Size {
139   int width;
140   int height;
Sizecurses::Size141   Size(int w = 0, int h = 0) : width(w), height(h) {}
142 
Clearcurses::Size143   void Clear() {
144     width = 0;
145     height = 0;
146   }
147 
Dumpcurses::Size148   void Dump() { printf("(w=%i, h=%i)\n", width, 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 
operator !=(const Size & lhs,const Size & rhs)155 bool operator!=(const Size &lhs, const Size &rhs) {
156   return lhs.width != rhs.width || lhs.height != rhs.height;
157 }
158 
159 struct Rect {
160   Point origin;
161   Size size;
162 
Rectcurses::Rect163   Rect() : origin(), size() {}
164 
Rectcurses::Rect165   Rect(const Point &p, const Size &s) : origin(p), size(s) {}
166 
Clearcurses::Rect167   void Clear() {
168     origin.Clear();
169     size.Clear();
170   }
171 
Dumpcurses::Rect172   void Dump() {
173     printf("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width,
174            size.height);
175   }
176 
Insetcurses::Rect177   void Inset(int w, int h) {
178     if (size.width > w * 2)
179       size.width -= w * 2;
180     origin.x += w;
181 
182     if (size.height > h * 2)
183       size.height -= h * 2;
184     origin.y += h;
185   }
186 
187   // Return a status bar rectangle which is the last line of this rectangle.
188   // This rectangle will be modified to not include the status bar area.
MakeStatusBarcurses::Rect189   Rect MakeStatusBar() {
190     Rect status_bar;
191     if (size.height > 1) {
192       status_bar.origin.x = origin.x;
193       status_bar.origin.y = size.height;
194       status_bar.size.width = size.width;
195       status_bar.size.height = 1;
196       --size.height;
197     }
198     return status_bar;
199   }
200 
201   // Return a menubar rectangle which is the first line of this rectangle. This
202   // rectangle will be modified to not include the menubar area.
MakeMenuBarcurses::Rect203   Rect MakeMenuBar() {
204     Rect menubar;
205     if (size.height > 1) {
206       menubar.origin.x = origin.x;
207       menubar.origin.y = origin.y;
208       menubar.size.width = size.width;
209       menubar.size.height = 1;
210       ++origin.y;
211       --size.height;
212     }
213     return menubar;
214   }
215 
HorizontalSplitPercentagecurses::Rect216   void HorizontalSplitPercentage(float top_percentage, Rect &top,
217                                  Rect &bottom) const {
218     float top_height = top_percentage * size.height;
219     HorizontalSplit(top_height, top, bottom);
220   }
221 
HorizontalSplitcurses::Rect222   void HorizontalSplit(int top_height, Rect &top, Rect &bottom) const {
223     top = *this;
224     if (top_height < size.height) {
225       top.size.height = top_height;
226       bottom.origin.x = origin.x;
227       bottom.origin.y = origin.y + top.size.height;
228       bottom.size.width = size.width;
229       bottom.size.height = size.height - top.size.height;
230     } else {
231       bottom.Clear();
232     }
233   }
234 
VerticalSplitPercentagecurses::Rect235   void VerticalSplitPercentage(float left_percentage, Rect &left,
236                                Rect &right) const {
237     float left_width = left_percentage * size.width;
238     VerticalSplit(left_width, left, right);
239   }
240 
VerticalSplitcurses::Rect241   void VerticalSplit(int left_width, Rect &left, Rect &right) const {
242     left = *this;
243     if (left_width < size.width) {
244       left.size.width = left_width;
245       right.origin.x = origin.x + left.size.width;
246       right.origin.y = origin.y;
247       right.size.width = size.width - left.size.width;
248       right.size.height = size.height;
249     } else {
250       right.Clear();
251     }
252   }
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 
operator !=(const Rect & lhs,const Rect & rhs)259 bool operator!=(const Rect &lhs, const Rect &rhs) {
260   return lhs.origin != rhs.origin || lhs.size != rhs.size;
261 }
262 
263 enum HandleCharResult {
264   eKeyNotHandled = 0,
265   eKeyHandled = 1,
266   eQuitApplication = 2
267 };
268 
269 enum class MenuActionResult {
270   Handled,
271   NotHandled,
272   Quit // Exit all menus and quit
273 };
274 
275 struct KeyHelp {
276   int ch;
277   const char *description;
278 };
279 
280 // COLOR_PAIR index names
281 enum {
282   // First 16 colors are 8 black background and 8 blue background colors,
283   // needed by OutputColoredStringTruncated().
284   BlackOnBlack = 1,
285   RedOnBlack,
286   GreenOnBlack,
287   YellowOnBlack,
288   BlueOnBlack,
289   MagentaOnBlack,
290   CyanOnBlack,
291   WhiteOnBlack,
292   BlackOnBlue,
293   RedOnBlue,
294   GreenOnBlue,
295   YellowOnBlue,
296   BlueOnBlue,
297   MagentaOnBlue,
298   CyanOnBlue,
299   WhiteOnBlue,
300   // Other colors, as needed.
301   BlackOnWhite,
302   MagentaOnWhite,
303   LastColorPairIndex = MagentaOnWhite
304 };
305 
306 class WindowDelegate {
307 public:
308   virtual ~WindowDelegate() = default;
309 
WindowDelegateDraw(Window & window,bool force)310   virtual bool WindowDelegateDraw(Window &window, bool force) {
311     return false; // Drawing not handled
312   }
313 
WindowDelegateHandleChar(Window & window,int key)314   virtual HandleCharResult WindowDelegateHandleChar(Window &window, int key) {
315     return eKeyNotHandled;
316   }
317 
WindowDelegateGetHelpText()318   virtual const char *WindowDelegateGetHelpText() { return nullptr; }
319 
WindowDelegateGetKeyHelp()320   virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; }
321 };
322 
323 class HelpDialogDelegate : public WindowDelegate {
324 public:
325   HelpDialogDelegate(const char *text, KeyHelp *key_help_array);
326 
327   ~HelpDialogDelegate() override;
328 
329   bool WindowDelegateDraw(Window &window, bool force) override;
330 
331   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
332 
GetNumLines() const333   size_t GetNumLines() const { return m_text.GetSize(); }
334 
GetMaxLineLength() const335   size_t GetMaxLineLength() const { return m_text.GetMaxStringLength(); }
336 
337 protected:
338   StringList m_text;
339   int m_first_visible_line;
340 };
341 
342 // A surface is an abstraction for something than can be drawn on. The surface
343 // have a width, a height, a cursor position, and a multitude of drawing
344 // operations. This type should be sub-classed to get an actually useful ncurses
345 // object, such as a Window, SubWindow, Pad, or a SubPad.
346 class Surface {
347 public:
Surface()348   Surface() : m_window(nullptr) {}
349 
get()350   WINDOW *get() { return m_window; }
351 
operator WINDOW*()352   operator WINDOW *() { return m_window; }
353 
354   // Copy a region of the surface to another surface.
CopyToSurface(Surface & target,Point source_origin,Point target_origin,Size size)355   void CopyToSurface(Surface &target, Point source_origin, Point target_origin,
356                      Size size) {
357     ::copywin(m_window, target.get(), source_origin.y, source_origin.x,
358               target_origin.y, target_origin.x,
359               target_origin.y + size.height - 1,
360               target_origin.x + size.width - 1, false);
361   }
362 
GetCursorX() const363   int GetCursorX() const { return getcurx(m_window); }
GetCursorY() const364   int GetCursorY() const { return getcury(m_window); }
MoveCursor(int x,int y)365   void MoveCursor(int x, int y) { ::wmove(m_window, y, x); }
366 
AttributeOn(attr_t attr)367   void AttributeOn(attr_t attr) { ::wattron(m_window, attr); }
AttributeOff(attr_t attr)368   void AttributeOff(attr_t attr) { ::wattroff(m_window, attr); }
369 
GetMaxX() const370   int GetMaxX() const { return getmaxx(m_window); }
GetMaxY() const371   int GetMaxY() const { return getmaxy(m_window); }
GetWidth() const372   int GetWidth() const { return GetMaxX(); }
GetHeight() const373   int GetHeight() const { return GetMaxY(); }
GetSize() const374   Size GetSize() const { return Size(GetWidth(), GetHeight()); }
375   // Get a zero origin rectangle width the surface size.
GetFrame() const376   Rect GetFrame() const { return Rect(Point(), GetSize()); }
377 
Clear()378   void Clear() { ::wclear(m_window); }
Erase()379   void Erase() { ::werase(m_window); }
380 
SetBackground(int color_pair_idx)381   void SetBackground(int color_pair_idx) {
382     ::wbkgd(m_window, COLOR_PAIR(color_pair_idx));
383   }
384 
PutChar(int ch)385   void PutChar(int ch) { ::waddch(m_window, ch); }
PutCString(const char * s,int len=-1)386   void PutCString(const char *s, int len = -1) { ::waddnstr(m_window, s, len); }
387 
PutCStringTruncated(int right_pad,const char * s,int len=-1)388   void PutCStringTruncated(int right_pad, const char *s, int len = -1) {
389     int bytes_left = GetWidth() - GetCursorX();
390     if (bytes_left > right_pad) {
391       bytes_left -= right_pad;
392       ::waddnstr(m_window, s, len < 0 ? bytes_left : std::min(bytes_left, len));
393     }
394   }
395 
Printf(const char * format,...)396   void Printf(const char *format, ...) __attribute__((format(printf, 2, 3))) {
397     va_list args;
398     va_start(args, format);
399     vw_printw(m_window, format, args);
400     va_end(args);
401   }
402 
PrintfTruncated(int right_pad,const char * format,...)403   void PrintfTruncated(int right_pad, const char *format, ...)
404       __attribute__((format(printf, 3, 4))) {
405     va_list args;
406     va_start(args, format);
407     StreamString strm;
408     strm.PrintfVarArg(format, args);
409     va_end(args);
410     PutCStringTruncated(right_pad, strm.GetData());
411   }
412 
VerticalLine(int n,chtype v_char=ACS_VLINE)413   void VerticalLine(int n, chtype v_char = ACS_VLINE) {
414     ::wvline(m_window, v_char, n);
415   }
HorizontalLine(int n,chtype h_char=ACS_HLINE)416   void HorizontalLine(int n, chtype h_char = ACS_HLINE) {
417     ::whline(m_window, h_char, n);
418   }
Box(chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)419   void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
420     ::box(m_window, v_char, h_char);
421   }
422 
TitledBox(const char * title,chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)423   void TitledBox(const char *title, chtype v_char = ACS_VLINE,
424                  chtype h_char = ACS_HLINE) {
425     Box(v_char, h_char);
426     int title_offset = 2;
427     MoveCursor(title_offset, 0);
428     PutChar('[');
429     PutCString(title, GetWidth() - title_offset);
430     PutChar(']');
431   }
432 
Box(const Rect & bounds,chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)433   void Box(const Rect &bounds, chtype v_char = ACS_VLINE,
434            chtype h_char = ACS_HLINE) {
435     MoveCursor(bounds.origin.x, bounds.origin.y);
436     VerticalLine(bounds.size.height);
437     HorizontalLine(bounds.size.width);
438     PutChar(ACS_ULCORNER);
439 
440     MoveCursor(bounds.origin.x + bounds.size.width - 1, bounds.origin.y);
441     VerticalLine(bounds.size.height);
442     PutChar(ACS_URCORNER);
443 
444     MoveCursor(bounds.origin.x, bounds.origin.y + bounds.size.height - 1);
445     HorizontalLine(bounds.size.width);
446     PutChar(ACS_LLCORNER);
447 
448     MoveCursor(bounds.origin.x + bounds.size.width - 1,
449                bounds.origin.y + bounds.size.height - 1);
450     PutChar(ACS_LRCORNER);
451   }
452 
TitledBox(const Rect & bounds,const char * title,chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)453   void TitledBox(const Rect &bounds, const char *title,
454                  chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
455     Box(bounds, v_char, h_char);
456     int title_offset = 2;
457     MoveCursor(bounds.origin.x + title_offset, bounds.origin.y);
458     PutChar('[');
459     PutCString(title, bounds.size.width - title_offset);
460     PutChar(']');
461   }
462 
463   // Curses doesn't allow direct output of color escape sequences, but that's
464   // how we get source lines from the Highligher class. Read the line and
465   // convert color escape sequences to curses color attributes. Use
466   // first_skip_count to skip leading visible characters. Returns false if all
467   // visible characters were skipped due to first_skip_count.
OutputColoredStringTruncated(int right_pad,StringRef string,size_t skip_first_count,bool use_blue_background)468   bool OutputColoredStringTruncated(int right_pad, StringRef string,
469                                     size_t skip_first_count,
470                                     bool use_blue_background) {
471     attr_t saved_attr;
472     short saved_pair;
473     bool result = false;
474     wattr_get(m_window, &saved_attr, &saved_pair, nullptr);
475     if (use_blue_background)
476       ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
477     while (!string.empty()) {
478       size_t esc_pos = string.find('\x1b');
479       if (esc_pos == StringRef::npos) {
480         string = string.substr(skip_first_count);
481         if (!string.empty()) {
482           PutCStringTruncated(right_pad, string.data(), string.size());
483           result = true;
484         }
485         break;
486       }
487       if (esc_pos > 0) {
488         if (skip_first_count > 0) {
489           int skip = std::min(esc_pos, skip_first_count);
490           string = string.substr(skip);
491           skip_first_count -= skip;
492           esc_pos -= skip;
493         }
494         if (esc_pos > 0) {
495           PutCStringTruncated(right_pad, string.data(), esc_pos);
496           result = true;
497           string = string.drop_front(esc_pos);
498         }
499       }
500       bool consumed = string.consume_front("\x1b");
501       assert(consumed);
502       UNUSED_IF_ASSERT_DISABLED(consumed);
503       // This is written to match our Highlighter classes, which seem to
504       // generate only foreground color escape sequences. If necessary, this
505       // will need to be extended.
506       if (!string.consume_front("[")) {
507         llvm::errs() << "Missing '[' in color escape sequence.\n";
508         continue;
509       }
510       // Only 8 basic foreground colors and reset, our Highlighter doesn't use
511       // anything else.
512       int value;
513       if (!!string.consumeInteger(10, value) || // Returns false on success.
514           !(value == 0 || (value >= 30 && value <= 37))) {
515         llvm::errs() << "No valid color code in color escape sequence.\n";
516         continue;
517       }
518       if (!string.consume_front("m")) {
519         llvm::errs() << "Missing 'm' in color escape sequence.\n";
520         continue;
521       }
522       if (value == 0) { // Reset.
523         wattr_set(m_window, saved_attr, saved_pair, nullptr);
524         if (use_blue_background)
525           ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
526       } else {
527         // Mapped directly to first 16 color pairs (black/blue background).
528         ::wattron(m_window,
529                   COLOR_PAIR(value - 30 + 1 + (use_blue_background ? 8 : 0)));
530       }
531     }
532     wattr_set(m_window, saved_attr, saved_pair, nullptr);
533     return result;
534   }
535 
536 protected:
537   WINDOW *m_window;
538 };
539 
540 class Pad : public Surface {
541 public:
Pad(Size size)542   Pad(Size size) { m_window = ::newpad(size.height, size.width); }
543 
~Pad()544   ~Pad() { ::delwin(m_window); }
545 };
546 
547 class SubPad : public Surface {
548 public:
SubPad(Pad & pad,Rect bounds)549   SubPad(Pad &pad, Rect bounds) {
550     m_window = ::subpad(pad.get(), bounds.size.height, bounds.size.width,
551                         bounds.origin.y, bounds.origin.x);
552   }
SubPad(SubPad & subpad,Rect bounds)553   SubPad(SubPad &subpad, Rect bounds) {
554     m_window = ::subpad(subpad.get(), bounds.size.height, bounds.size.width,
555                         bounds.origin.y, bounds.origin.x);
556   }
557 
~SubPad()558   ~SubPad() { ::delwin(m_window); }
559 };
560 
561 class Window : public Surface {
562 public:
Window(const char * name)563   Window(const char *name)
564       : m_name(name), m_panel(nullptr), m_parent(nullptr), m_subwindows(),
565         m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX),
566         m_prev_active_window_idx(UINT32_MAX), m_delete(false),
567         m_needs_update(true), m_can_activate(true), m_is_subwin(false) {}
568 
Window(const char * name,WINDOW * w,bool del=true)569   Window(const char *name, WINDOW *w, bool del = true)
570       : m_name(name), m_panel(nullptr), m_parent(nullptr), m_subwindows(),
571         m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX),
572         m_prev_active_window_idx(UINT32_MAX), m_delete(del),
573         m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
574     if (w)
575       Reset(w);
576   }
577 
Window(const char * name,const Rect & bounds)578   Window(const char *name, const Rect &bounds)
579       : m_name(name), m_parent(nullptr), m_subwindows(), m_delegate_sp(),
580         m_curr_active_window_idx(UINT32_MAX),
581         m_prev_active_window_idx(UINT32_MAX), m_delete(true),
582         m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
583     Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y,
584                    bounds.origin.y));
585   }
586 
~Window()587   virtual ~Window() {
588     RemoveSubWindows();
589     Reset();
590   }
591 
Reset(WINDOW * w=nullptr,bool del=true)592   void Reset(WINDOW *w = nullptr, bool del = true) {
593     if (m_window == w)
594       return;
595 
596     if (m_panel) {
597       ::del_panel(m_panel);
598       m_panel = nullptr;
599     }
600     if (m_window && m_delete) {
601       ::delwin(m_window);
602       m_window = nullptr;
603       m_delete = false;
604     }
605     if (w) {
606       m_window = w;
607       m_panel = ::new_panel(m_window);
608       m_delete = del;
609     }
610   }
611 
612   // Get the rectangle in our parent window
GetBounds() const613   Rect GetBounds() const { return Rect(GetParentOrigin(), GetSize()); }
614 
GetCenteredRect(int width,int height)615   Rect GetCenteredRect(int width, int height) {
616     Size size = GetSize();
617     width = std::min(size.width, width);
618     height = std::min(size.height, height);
619     int x = (size.width - width) / 2;
620     int y = (size.height - height) / 2;
621     return Rect(Point(x, y), Size(width, height));
622   }
623 
GetChar()624   int GetChar() { return ::wgetch(m_window); }
GetParentOrigin() const625   Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); }
GetParentX() const626   int GetParentX() const { return getparx(m_window); }
GetParentY() const627   int GetParentY() const { return getpary(m_window); }
MoveWindow(int x,int y)628   void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); }
Resize(int w,int h)629   void Resize(int w, int h) { ::wresize(m_window, h, w); }
Resize(const Size & size)630   void Resize(const Size &size) {
631     ::wresize(m_window, size.height, size.width);
632   }
MoveWindow(const Point & origin)633   void MoveWindow(const Point &origin) {
634     const bool moving_window = origin != GetParentOrigin();
635     if (m_is_subwin && moving_window) {
636       // Can't move subwindows, must delete and re-create
637       Size size = GetSize();
638       Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y,
639                      origin.x),
640             true);
641     } else {
642       ::mvwin(m_window, origin.y, origin.x);
643     }
644   }
645 
SetBounds(const Rect & bounds)646   void SetBounds(const Rect &bounds) {
647     const bool moving_window = bounds.origin != GetParentOrigin();
648     if (m_is_subwin && moving_window) {
649       // Can't move subwindows, must delete and re-create
650       Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width,
651                      bounds.origin.y, bounds.origin.x),
652             true);
653     } else {
654       if (moving_window)
655         MoveWindow(bounds.origin);
656       Resize(bounds.size);
657     }
658   }
659 
Touch()660   void Touch() {
661     ::touchwin(m_window);
662     if (m_parent)
663       m_parent->Touch();
664   }
665 
CreateSubWindow(const char * name,const Rect & bounds,bool make_active)666   WindowSP CreateSubWindow(const char *name, const Rect &bounds,
667                            bool make_active) {
668     auto get_window = [this, &bounds]() {
669       return m_window
670                  ? ::subwin(m_window, bounds.size.height, bounds.size.width,
671                             bounds.origin.y, bounds.origin.x)
672                  : ::newwin(bounds.size.height, bounds.size.width,
673                             bounds.origin.y, bounds.origin.x);
674     };
675     WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true);
676     subwindow_sp->m_is_subwin = subwindow_sp.operator bool();
677     subwindow_sp->m_parent = this;
678     if (make_active) {
679       m_prev_active_window_idx = m_curr_active_window_idx;
680       m_curr_active_window_idx = m_subwindows.size();
681     }
682     m_subwindows.push_back(subwindow_sp);
683     ::top_panel(subwindow_sp->m_panel);
684     m_needs_update = true;
685     return subwindow_sp;
686   }
687 
RemoveSubWindow(Window * window)688   bool RemoveSubWindow(Window *window) {
689     Windows::iterator pos, end = m_subwindows.end();
690     size_t i = 0;
691     for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
692       if ((*pos).get() == window) {
693         if (m_prev_active_window_idx == i)
694           m_prev_active_window_idx = UINT32_MAX;
695         else if (m_prev_active_window_idx != UINT32_MAX &&
696                  m_prev_active_window_idx > i)
697           --m_prev_active_window_idx;
698 
699         if (m_curr_active_window_idx == i)
700           m_curr_active_window_idx = UINT32_MAX;
701         else if (m_curr_active_window_idx != UINT32_MAX &&
702                  m_curr_active_window_idx > i)
703           --m_curr_active_window_idx;
704         window->Erase();
705         m_subwindows.erase(pos);
706         m_needs_update = true;
707         if (m_parent)
708           m_parent->Touch();
709         else
710           ::touchwin(stdscr);
711         return true;
712       }
713     }
714     return false;
715   }
716 
FindSubWindow(const char * name)717   WindowSP FindSubWindow(const char *name) {
718     Windows::iterator pos, end = m_subwindows.end();
719     size_t i = 0;
720     for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
721       if ((*pos)->m_name == name)
722         return *pos;
723     }
724     return WindowSP();
725   }
726 
RemoveSubWindows()727   void RemoveSubWindows() {
728     m_curr_active_window_idx = UINT32_MAX;
729     m_prev_active_window_idx = UINT32_MAX;
730     for (Windows::iterator pos = m_subwindows.begin();
731          pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) {
732       (*pos)->Erase();
733     }
734     if (m_parent)
735       m_parent->Touch();
736     else
737       ::touchwin(stdscr);
738   }
739 
740   // Window drawing utilities
DrawTitleBox(const char * title,const char * bottom_message=nullptr)741   void DrawTitleBox(const char *title, const char *bottom_message = nullptr) {
742     attr_t attr = 0;
743     if (IsActive())
744       attr = A_BOLD | COLOR_PAIR(BlackOnWhite);
745     else
746       attr = 0;
747     if (attr)
748       AttributeOn(attr);
749 
750     Box();
751     MoveCursor(3, 0);
752 
753     if (title && title[0]) {
754       PutChar('<');
755       PutCString(title);
756       PutChar('>');
757     }
758 
759     if (bottom_message && bottom_message[0]) {
760       int bottom_message_length = strlen(bottom_message);
761       int x = GetWidth() - 3 - (bottom_message_length + 2);
762 
763       if (x > 0) {
764         MoveCursor(x, GetHeight() - 1);
765         PutChar('[');
766         PutCString(bottom_message);
767         PutChar(']');
768       } else {
769         MoveCursor(1, GetHeight() - 1);
770         PutChar('[');
771         PutCStringTruncated(1, bottom_message);
772       }
773     }
774     if (attr)
775       AttributeOff(attr);
776   }
777 
Draw(bool force)778   virtual void Draw(bool force) {
779     if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force))
780       return;
781 
782     for (auto &subwindow_sp : m_subwindows)
783       subwindow_sp->Draw(force);
784   }
785 
CreateHelpSubwindow()786   bool CreateHelpSubwindow() {
787     if (m_delegate_sp) {
788       const char *text = m_delegate_sp->WindowDelegateGetHelpText();
789       KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp();
790       if ((text && text[0]) || key_help) {
791         std::unique_ptr<HelpDialogDelegate> help_delegate_up(
792             new HelpDialogDelegate(text, key_help));
793         const size_t num_lines = help_delegate_up->GetNumLines();
794         const size_t max_length = help_delegate_up->GetMaxLineLength();
795         Rect bounds = GetBounds();
796         bounds.Inset(1, 1);
797         if (max_length + 4 < static_cast<size_t>(bounds.size.width)) {
798           bounds.origin.x += (bounds.size.width - max_length + 4) / 2;
799           bounds.size.width = max_length + 4;
800         } else {
801           if (bounds.size.width > 100) {
802             const int inset_w = bounds.size.width / 4;
803             bounds.origin.x += inset_w;
804             bounds.size.width -= 2 * inset_w;
805           }
806         }
807 
808         if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) {
809           bounds.origin.y += (bounds.size.height - num_lines + 2) / 2;
810           bounds.size.height = num_lines + 2;
811         } else {
812           if (bounds.size.height > 100) {
813             const int inset_h = bounds.size.height / 4;
814             bounds.origin.y += inset_h;
815             bounds.size.height -= 2 * inset_h;
816           }
817         }
818         WindowSP help_window_sp;
819         Window *parent_window = GetParent();
820         if (parent_window)
821           help_window_sp = parent_window->CreateSubWindow("Help", bounds, true);
822         else
823           help_window_sp = CreateSubWindow("Help", bounds, true);
824         help_window_sp->SetDelegate(
825             WindowDelegateSP(help_delegate_up.release()));
826         return true;
827       }
828     }
829     return false;
830   }
831 
HandleChar(int key)832   virtual HandleCharResult HandleChar(int key) {
833     // Always check the active window first
834     HandleCharResult result = eKeyNotHandled;
835     WindowSP active_window_sp = GetActiveWindow();
836     if (active_window_sp) {
837       result = active_window_sp->HandleChar(key);
838       if (result != eKeyNotHandled)
839         return result;
840     }
841 
842     if (m_delegate_sp) {
843       result = m_delegate_sp->WindowDelegateHandleChar(*this, key);
844       if (result != eKeyNotHandled)
845         return result;
846     }
847 
848     // Then check for any windows that want any keys that weren't handled. This
849     // is typically only for a menubar. Make a copy of the subwindows in case
850     // any HandleChar() functions muck with the subwindows. If we don't do
851     // this, we can crash when iterating over the subwindows.
852     Windows subwindows(m_subwindows);
853     for (auto subwindow_sp : subwindows) {
854       if (!subwindow_sp->m_can_activate) {
855         HandleCharResult result = subwindow_sp->HandleChar(key);
856         if (result != eKeyNotHandled)
857           return result;
858       }
859     }
860 
861     return eKeyNotHandled;
862   }
863 
GetActiveWindow()864   WindowSP GetActiveWindow() {
865     if (!m_subwindows.empty()) {
866       if (m_curr_active_window_idx >= m_subwindows.size()) {
867         if (m_prev_active_window_idx < m_subwindows.size()) {
868           m_curr_active_window_idx = m_prev_active_window_idx;
869           m_prev_active_window_idx = UINT32_MAX;
870         } else if (IsActive()) {
871           m_prev_active_window_idx = UINT32_MAX;
872           m_curr_active_window_idx = UINT32_MAX;
873 
874           // Find first window that wants to be active if this window is active
875           const size_t num_subwindows = m_subwindows.size();
876           for (size_t i = 0; i < num_subwindows; ++i) {
877             if (m_subwindows[i]->GetCanBeActive()) {
878               m_curr_active_window_idx = i;
879               break;
880             }
881           }
882         }
883       }
884 
885       if (m_curr_active_window_idx < m_subwindows.size())
886         return m_subwindows[m_curr_active_window_idx];
887     }
888     return WindowSP();
889   }
890 
GetCanBeActive() const891   bool GetCanBeActive() const { return m_can_activate; }
892 
SetCanBeActive(bool b)893   void SetCanBeActive(bool b) { m_can_activate = b; }
894 
SetDelegate(const WindowDelegateSP & delegate_sp)895   void SetDelegate(const WindowDelegateSP &delegate_sp) {
896     m_delegate_sp = delegate_sp;
897   }
898 
GetParent() const899   Window *GetParent() const { return m_parent; }
900 
IsActive() const901   bool IsActive() const {
902     if (m_parent)
903       return m_parent->GetActiveWindow().get() == this;
904     else
905       return true; // Top level window is always active
906   }
907 
SelectNextWindowAsActive()908   void SelectNextWindowAsActive() {
909     // Move active focus to next window
910     const int num_subwindows = m_subwindows.size();
911     int start_idx = 0;
912     if (m_curr_active_window_idx != UINT32_MAX) {
913       m_prev_active_window_idx = m_curr_active_window_idx;
914       start_idx = m_curr_active_window_idx + 1;
915     }
916     for (int idx = start_idx; idx < num_subwindows; ++idx) {
917       if (m_subwindows[idx]->GetCanBeActive()) {
918         m_curr_active_window_idx = idx;
919         return;
920       }
921     }
922     for (int idx = 0; idx < start_idx; ++idx) {
923       if (m_subwindows[idx]->GetCanBeActive()) {
924         m_curr_active_window_idx = idx;
925         break;
926       }
927     }
928   }
929 
SelectPreviousWindowAsActive()930   void SelectPreviousWindowAsActive() {
931     // Move active focus to previous window
932     const int num_subwindows = m_subwindows.size();
933     int start_idx = num_subwindows - 1;
934     if (m_curr_active_window_idx != UINT32_MAX) {
935       m_prev_active_window_idx = m_curr_active_window_idx;
936       start_idx = m_curr_active_window_idx - 1;
937     }
938     for (int idx = start_idx; idx >= 0; --idx) {
939       if (m_subwindows[idx]->GetCanBeActive()) {
940         m_curr_active_window_idx = idx;
941         return;
942       }
943     }
944     for (int idx = num_subwindows - 1; idx > start_idx; --idx) {
945       if (m_subwindows[idx]->GetCanBeActive()) {
946         m_curr_active_window_idx = idx;
947         break;
948       }
949     }
950   }
951 
GetName() const952   const char *GetName() const { return m_name.c_str(); }
953 
954 protected:
955   std::string m_name;
956   PANEL *m_panel;
957   Window *m_parent;
958   Windows m_subwindows;
959   WindowDelegateSP m_delegate_sp;
960   uint32_t m_curr_active_window_idx;
961   uint32_t m_prev_active_window_idx;
962   bool m_delete;
963   bool m_needs_update;
964   bool m_can_activate;
965   bool m_is_subwin;
966 
967 private:
968   Window(const Window &) = delete;
969   const Window &operator=(const Window &) = delete;
970 };
971 
972 class DerivedWindow : public Surface {
973 public:
DerivedWindow(Window & window,Rect bounds)974   DerivedWindow(Window &window, Rect bounds) {
975     m_window = ::derwin(window.get(), bounds.size.height, bounds.size.width,
976                         bounds.origin.y, bounds.origin.x);
977   }
DerivedWindow(DerivedWindow & derived_window,Rect bounds)978   DerivedWindow(DerivedWindow &derived_window, Rect bounds) {
979     m_window = ::derwin(derived_window.get(), bounds.size.height,
980                         bounds.size.width, bounds.origin.y, bounds.origin.x);
981   }
982 
~DerivedWindow()983   ~DerivedWindow() { ::delwin(m_window); }
984 };
985 
986 /////////
987 // Forms
988 /////////
989 
990 // A scroll context defines a vertical region that needs to be visible in a
991 // scrolling area. The region is defined by the index of the start and end lines
992 // of the region. The start and end lines may be equal, in which case, the
993 // region is a single line.
994 struct ScrollContext {
995   int start;
996   int end;
997 
ScrollContextcurses::ScrollContext998   ScrollContext(int line) : start(line), end(line) {}
ScrollContextcurses::ScrollContext999   ScrollContext(int _start, int _end) : start(_start), end(_end) {}
1000 
Offsetcurses::ScrollContext1001   void Offset(int offset) {
1002     start += offset;
1003     end += offset;
1004   }
1005 };
1006 
1007 class FieldDelegate {
1008 public:
1009   virtual ~FieldDelegate() = default;
1010 
1011   // Returns the number of lines needed to draw the field. The draw method will
1012   // be given a surface that have exactly this number of lines.
1013   virtual int FieldDelegateGetHeight() = 0;
1014 
1015   // Returns the scroll context in the local coordinates of the field. By
1016   // default, the scroll context spans the whole field. Bigger fields with
1017   // internal navigation should override this method to provide a finer context.
1018   // Typical override methods would first get the scroll context of the internal
1019   // element then add the offset of the element in the field.
FieldDelegateGetScrollContext()1020   virtual ScrollContext FieldDelegateGetScrollContext() {
1021     return ScrollContext(0, FieldDelegateGetHeight() - 1);
1022   }
1023 
1024   // Draw the field in the given subpad surface. The surface have a height that
1025   // is equal to the height returned by FieldDelegateGetHeight(). If the field
1026   // is selected in the form window, then is_selected will be true.
1027   virtual void FieldDelegateDraw(SubPad &surface, bool is_selected) = 0;
1028 
1029   // Handle the key that wasn't handled by the form window or a container field.
FieldDelegateHandleChar(int key)1030   virtual HandleCharResult FieldDelegateHandleChar(int key) {
1031     return eKeyNotHandled;
1032   }
1033 
1034   // This is executed once the user exists the field, that is, once the user
1035   // navigates to the next or the previous field. This is particularly useful to
1036   // do in-field validation and error setting. Fields with internal navigation
1037   // should call this method on their fields.
FieldDelegateExitCallback()1038   virtual void FieldDelegateExitCallback() { return; }
1039 
1040   // Fields may have internal navigation, for instance, a List Field have
1041   // multiple internal elements, which needs to be navigated. To allow for this
1042   // mechanism, the window shouldn't handle the navigation keys all the time,
1043   // and instead call the key handing method of the selected field. It should
1044   // only handle the navigation keys when the field contains a single element or
1045   // have the last or first element selected depending on if the user is
1046   // navigating forward or backward. Additionally, once a field is selected in
1047   // the forward or backward direction, its first or last internal element
1048   // should be selected. The following methods implements those mechanisms.
1049 
1050   // Returns true if the first element in the field is selected or if the field
1051   // contains a single element.
FieldDelegateOnFirstOrOnlyElement()1052   virtual bool FieldDelegateOnFirstOrOnlyElement() { return true; }
1053 
1054   // Returns true if the last element in the field is selected or if the field
1055   // contains a single element.
FieldDelegateOnLastOrOnlyElement()1056   virtual bool FieldDelegateOnLastOrOnlyElement() { return true; }
1057 
1058   // Select the first element in the field if multiple elements exists.
FieldDelegateSelectFirstElement()1059   virtual void FieldDelegateSelectFirstElement() { return; }
1060 
1061   // Select the last element in the field if multiple elements exists.
FieldDelegateSelectLastElement()1062   virtual void FieldDelegateSelectLastElement() { return; }
1063 
1064   // Returns true if the field has an error, false otherwise.
FieldDelegateHasError()1065   virtual bool FieldDelegateHasError() { return false; }
1066 
FieldDelegateIsVisible()1067   bool FieldDelegateIsVisible() { return m_is_visible; }
1068 
FieldDelegateHide()1069   void FieldDelegateHide() { m_is_visible = false; }
1070 
FieldDelegateShow()1071   void FieldDelegateShow() { m_is_visible = true; }
1072 
1073 protected:
1074   bool m_is_visible = true;
1075 };
1076 
1077 typedef std::unique_ptr<FieldDelegate> FieldDelegateUP;
1078 
1079 class TextFieldDelegate : public FieldDelegate {
1080 public:
TextFieldDelegate(const char * label,const char * content,bool required)1081   TextFieldDelegate(const char *label, const char *content, bool required)
1082       : m_label(label), m_required(required), m_cursor_position(0),
1083         m_first_visibile_char(0) {
1084     if (content)
1085       m_content = content;
1086   }
1087 
1088   // Text fields are drawn as titled boxes of a single line, with a possible
1089   // error messages at the end.
1090   //
1091   // __[Label]___________
1092   // |                  |
1093   // |__________________|
1094   // - Error message if it exists.
1095 
1096   // The text field has a height of 3 lines. 2 lines for borders and 1 line for
1097   // the content.
GetFieldHeight()1098   int GetFieldHeight() { return 3; }
1099 
1100   // The text field has a full height of 3 or 4 lines. 3 lines for the actual
1101   // field and an optional line for an error if it exists.
FieldDelegateGetHeight()1102   int FieldDelegateGetHeight() override {
1103     int height = GetFieldHeight();
1104     if (FieldDelegateHasError())
1105       height++;
1106     return height;
1107   }
1108 
1109   // Get the cursor X position in the surface coordinate.
GetCursorXPosition()1110   int GetCursorXPosition() { return m_cursor_position - m_first_visibile_char; }
1111 
GetContentLength()1112   int GetContentLength() { return m_content.length(); }
1113 
DrawContent(SubPad & surface,bool is_selected)1114   void DrawContent(SubPad &surface, bool is_selected) {
1115     surface.MoveCursor(0, 0);
1116     const char *text = m_content.c_str() + m_first_visibile_char;
1117     surface.PutCString(text, surface.GetWidth());
1118     m_last_drawn_content_width = surface.GetWidth();
1119 
1120     // Highlight the cursor.
1121     surface.MoveCursor(GetCursorXPosition(), 0);
1122     if (is_selected)
1123       surface.AttributeOn(A_REVERSE);
1124     if (m_cursor_position == GetContentLength())
1125       // Cursor is past the last character. Highlight an empty space.
1126       surface.PutChar(' ');
1127     else
1128       surface.PutChar(m_content[m_cursor_position]);
1129     if (is_selected)
1130       surface.AttributeOff(A_REVERSE);
1131   }
1132 
DrawField(SubPad & surface,bool is_selected)1133   void DrawField(SubPad &surface, bool is_selected) {
1134     surface.TitledBox(m_label.c_str());
1135 
1136     Rect content_bounds = surface.GetFrame();
1137     content_bounds.Inset(1, 1);
1138     SubPad content_surface = SubPad(surface, content_bounds);
1139 
1140     DrawContent(content_surface, is_selected);
1141   }
1142 
DrawError(SubPad & surface)1143   void DrawError(SubPad &surface) {
1144     if (!FieldDelegateHasError())
1145       return;
1146     surface.MoveCursor(0, 0);
1147     surface.AttributeOn(COLOR_PAIR(RedOnBlack));
1148     surface.PutChar(ACS_DIAMOND);
1149     surface.PutChar(' ');
1150     surface.PutCStringTruncated(1, GetError().c_str());
1151     surface.AttributeOff(COLOR_PAIR(RedOnBlack));
1152   }
1153 
FieldDelegateDraw(SubPad & surface,bool is_selected)1154   void FieldDelegateDraw(SubPad &surface, bool is_selected) override {
1155     Rect frame = surface.GetFrame();
1156     Rect field_bounds, error_bounds;
1157     frame.HorizontalSplit(GetFieldHeight(), field_bounds, error_bounds);
1158     SubPad field_surface = SubPad(surface, field_bounds);
1159     SubPad error_surface = SubPad(surface, error_bounds);
1160 
1161     DrawField(field_surface, is_selected);
1162     DrawError(error_surface);
1163   }
1164 
1165   // The cursor is allowed to move one character past the string.
1166   // m_cursor_position is in range [0, GetContentLength()].
MoveCursorRight()1167   void MoveCursorRight() {
1168     if (m_cursor_position < GetContentLength())
1169       m_cursor_position++;
1170   }
1171 
MoveCursorLeft()1172   void MoveCursorLeft() {
1173     if (m_cursor_position > 0)
1174       m_cursor_position--;
1175   }
1176 
1177   // If the cursor moved past the last visible character, scroll right by one
1178   // character.
ScrollRightIfNeeded()1179   void ScrollRightIfNeeded() {
1180     if (m_cursor_position - m_first_visibile_char == m_last_drawn_content_width)
1181       m_first_visibile_char++;
1182   }
1183 
ScrollLeft()1184   void ScrollLeft() {
1185     if (m_first_visibile_char > 0)
1186       m_first_visibile_char--;
1187   }
1188 
1189   // If the cursor moved past the first visible character, scroll left by one
1190   // character.
ScrollLeftIfNeeded()1191   void ScrollLeftIfNeeded() {
1192     if (m_cursor_position < m_first_visibile_char)
1193       m_first_visibile_char--;
1194   }
1195 
1196   // Insert a character at the current cursor position, advance the cursor
1197   // position, and make sure to scroll right if needed.
InsertChar(char character)1198   void InsertChar(char character) {
1199     m_content.insert(m_cursor_position, 1, character);
1200     m_cursor_position++;
1201     ScrollRightIfNeeded();
1202   }
1203 
1204   // Remove the character before the cursor position, retreat the cursor
1205   // position, and make sure to scroll left if needed.
RemoveChar()1206   void RemoveChar() {
1207     if (m_cursor_position == 0)
1208       return;
1209 
1210     m_content.erase(m_cursor_position - 1, 1);
1211     m_cursor_position--;
1212     ScrollLeft();
1213   }
1214 
1215   // True if the key represents a char that can be inserted in the field
1216   // content, false otherwise.
IsAcceptableChar(int key)1217   virtual bool IsAcceptableChar(int key) { return isprint(key); }
1218 
FieldDelegateHandleChar(int key)1219   HandleCharResult FieldDelegateHandleChar(int key) override {
1220     if (IsAcceptableChar(key)) {
1221       ClearError();
1222       InsertChar((char)key);
1223       return eKeyHandled;
1224     }
1225 
1226     switch (key) {
1227     case KEY_RIGHT:
1228       MoveCursorRight();
1229       ScrollRightIfNeeded();
1230       return eKeyHandled;
1231     case KEY_LEFT:
1232       MoveCursorLeft();
1233       ScrollLeftIfNeeded();
1234       return eKeyHandled;
1235     case KEY_BACKSPACE:
1236       ClearError();
1237       RemoveChar();
1238       return eKeyHandled;
1239     default:
1240       break;
1241     }
1242     return eKeyNotHandled;
1243   }
1244 
FieldDelegateHasError()1245   bool FieldDelegateHasError() override { return !m_error.empty(); }
1246 
FieldDelegateExitCallback()1247   void FieldDelegateExitCallback() override {
1248     if (!IsSpecified() && m_required)
1249       SetError("This field is required!");
1250   }
1251 
IsSpecified()1252   bool IsSpecified() { return !m_content.empty(); }
1253 
ClearError()1254   void ClearError() { m_error.clear(); }
1255 
GetError()1256   const std::string &GetError() { return m_error; }
1257 
SetError(const char * error)1258   void SetError(const char *error) { m_error = error; }
1259 
GetText()1260   const std::string &GetText() { return m_content; }
1261 
1262 protected:
1263   std::string m_label;
1264   bool m_required;
1265   // The position of the top left corner character of the border.
1266   std::string m_content;
1267   // The cursor position in the content string itself. Can be in the range
1268   // [0, GetContentLength()].
1269   int m_cursor_position;
1270   // The index of the first visible character in the content.
1271   int m_first_visibile_char;
1272   // The width of the fields content that was last drawn. Width can change, so
1273   // this is used to determine if scrolling is needed dynamically.
1274   int m_last_drawn_content_width;
1275   // Optional error message. If empty, field is considered to have no error.
1276   std::string m_error;
1277 };
1278 
1279 class IntegerFieldDelegate : public TextFieldDelegate {
1280 public:
IntegerFieldDelegate(const char * label,int content,bool required)1281   IntegerFieldDelegate(const char *label, int content, bool required)
1282       : TextFieldDelegate(label, std::to_string(content).c_str(), required) {}
1283 
1284   // Only accept digits.
IsAcceptableChar(int key)1285   bool IsAcceptableChar(int key) override { return isdigit(key); }
1286 
1287   // Returns the integer content of the field.
GetInteger()1288   int GetInteger() { return std::stoi(m_content); }
1289 };
1290 
1291 class FileFieldDelegate : public TextFieldDelegate {
1292 public:
FileFieldDelegate(const char * label,const char * content,bool need_to_exist,bool required)1293   FileFieldDelegate(const char *label, const char *content, bool need_to_exist,
1294                     bool required)
1295       : TextFieldDelegate(label, content, required),
1296         m_need_to_exist(need_to_exist) {}
1297 
FieldDelegateExitCallback()1298   void FieldDelegateExitCallback() override {
1299     TextFieldDelegate::FieldDelegateExitCallback();
1300     if (!IsSpecified())
1301       return;
1302 
1303     if (!m_need_to_exist)
1304       return;
1305 
1306     FileSpec file = GetResolvedFileSpec();
1307     if (!FileSystem::Instance().Exists(file)) {
1308       SetError("File doesn't exist!");
1309       return;
1310     }
1311     if (FileSystem::Instance().IsDirectory(file)) {
1312       SetError("Not a file!");
1313       return;
1314     }
1315   }
1316 
GetFileSpec()1317   FileSpec GetFileSpec() {
1318     FileSpec file_spec(GetPath());
1319     return file_spec;
1320   }
1321 
GetResolvedFileSpec()1322   FileSpec GetResolvedFileSpec() {
1323     FileSpec file_spec(GetPath());
1324     FileSystem::Instance().Resolve(file_spec);
1325     return file_spec;
1326   }
1327 
GetPath()1328   const std::string &GetPath() { return m_content; }
1329 
1330 protected:
1331   bool m_need_to_exist;
1332 };
1333 
1334 class DirectoryFieldDelegate : public TextFieldDelegate {
1335 public:
DirectoryFieldDelegate(const char * label,const char * content,bool need_to_exist,bool required)1336   DirectoryFieldDelegate(const char *label, const char *content,
1337                          bool need_to_exist, bool required)
1338       : TextFieldDelegate(label, content, required),
1339         m_need_to_exist(need_to_exist) {}
1340 
FieldDelegateExitCallback()1341   void FieldDelegateExitCallback() override {
1342     TextFieldDelegate::FieldDelegateExitCallback();
1343     if (!IsSpecified())
1344       return;
1345 
1346     if (!m_need_to_exist)
1347       return;
1348 
1349     FileSpec file = GetResolvedFileSpec();
1350     if (!FileSystem::Instance().Exists(file)) {
1351       SetError("Directory doesn't exist!");
1352       return;
1353     }
1354     if (!FileSystem::Instance().IsDirectory(file)) {
1355       SetError("Not a directory!");
1356       return;
1357     }
1358   }
1359 
GetFileSpec()1360   FileSpec GetFileSpec() {
1361     FileSpec file_spec(GetPath());
1362     return file_spec;
1363   }
1364 
GetResolvedFileSpec()1365   FileSpec GetResolvedFileSpec() {
1366     FileSpec file_spec(GetPath());
1367     FileSystem::Instance().Resolve(file_spec);
1368     return file_spec;
1369   }
1370 
GetPath()1371   const std::string &GetPath() { return m_content; }
1372 
1373 protected:
1374   bool m_need_to_exist;
1375 };
1376 
1377 class ArchFieldDelegate : public TextFieldDelegate {
1378 public:
ArchFieldDelegate(const char * label,const char * content,bool required)1379   ArchFieldDelegate(const char *label, const char *content, bool required)
1380       : TextFieldDelegate(label, content, required) {}
1381 
FieldDelegateExitCallback()1382   void FieldDelegateExitCallback() override {
1383     TextFieldDelegate::FieldDelegateExitCallback();
1384     if (!IsSpecified())
1385       return;
1386 
1387     if (!GetArchSpec().IsValid())
1388       SetError("Not a valid arch!");
1389   }
1390 
GetArchString()1391   const std::string &GetArchString() { return m_content; }
1392 
GetArchSpec()1393   ArchSpec GetArchSpec() { return ArchSpec(GetArchString()); }
1394 };
1395 
1396 class BooleanFieldDelegate : public FieldDelegate {
1397 public:
BooleanFieldDelegate(const char * label,bool content)1398   BooleanFieldDelegate(const char *label, bool content)
1399       : m_label(label), m_content(content) {}
1400 
1401   // Boolean fields are drawn as checkboxes.
1402   //
1403   // [X] Label  or [ ] Label
1404 
1405   // Boolean fields are have a single line.
FieldDelegateGetHeight()1406   int FieldDelegateGetHeight() override { return 1; }
1407 
FieldDelegateDraw(SubPad & surface,bool is_selected)1408   void FieldDelegateDraw(SubPad &surface, bool is_selected) override {
1409     surface.MoveCursor(0, 0);
1410     surface.PutChar('[');
1411     if (is_selected)
1412       surface.AttributeOn(A_REVERSE);
1413     surface.PutChar(m_content ? ACS_DIAMOND : ' ');
1414     if (is_selected)
1415       surface.AttributeOff(A_REVERSE);
1416     surface.PutChar(']');
1417     surface.PutChar(' ');
1418     surface.PutCString(m_label.c_str());
1419   }
1420 
ToggleContent()1421   void ToggleContent() { m_content = !m_content; }
1422 
SetContentToTrue()1423   void SetContentToTrue() { m_content = true; }
1424 
SetContentToFalse()1425   void SetContentToFalse() { m_content = false; }
1426 
FieldDelegateHandleChar(int key)1427   HandleCharResult FieldDelegateHandleChar(int key) override {
1428     switch (key) {
1429     case 't':
1430     case '1':
1431       SetContentToTrue();
1432       return eKeyHandled;
1433     case 'f':
1434     case '0':
1435       SetContentToFalse();
1436       return eKeyHandled;
1437     case ' ':
1438     case '\r':
1439     case '\n':
1440     case KEY_ENTER:
1441       ToggleContent();
1442       return eKeyHandled;
1443     default:
1444       break;
1445     }
1446     return eKeyNotHandled;
1447   }
1448 
1449   // Returns the boolean content of the field.
GetBoolean()1450   bool GetBoolean() { return m_content; }
1451 
1452 protected:
1453   std::string m_label;
1454   bool m_content;
1455 };
1456 
1457 class ChoicesFieldDelegate : public FieldDelegate {
1458 public:
ChoicesFieldDelegate(const char * label,int number_of_visible_choices,std::vector<std::string> choices)1459   ChoicesFieldDelegate(const char *label, int number_of_visible_choices,
1460                        std::vector<std::string> choices)
1461       : m_label(label), m_number_of_visible_choices(number_of_visible_choices),
1462         m_choices(choices), m_choice(0), m_first_visibile_choice(0) {}
1463 
1464   // Choices fields are drawn as titles boxses of a number of visible choices.
1465   // The rest of the choices become visible as the user scroll. The selected
1466   // choice is denoted by a diamond as the first character.
1467   //
1468   // __[Label]___________
1469   // |-Choice 1         |
1470   // | Choice 2         |
1471   // | Choice 3         |
1472   // |__________________|
1473 
1474   // Choices field have two border characters plus the number of visible
1475   // choices.
FieldDelegateGetHeight()1476   int FieldDelegateGetHeight() override {
1477     return m_number_of_visible_choices + 2;
1478   }
1479 
GetNumberOfChoices()1480   int GetNumberOfChoices() { return m_choices.size(); }
1481 
1482   // Get the index of the last visible choice.
GetLastVisibleChoice()1483   int GetLastVisibleChoice() {
1484     int index = m_first_visibile_choice + m_number_of_visible_choices;
1485     return std::min(index, GetNumberOfChoices()) - 1;
1486   }
1487 
DrawContent(SubPad & surface,bool is_selected)1488   void DrawContent(SubPad &surface, bool is_selected) {
1489     int choices_to_draw = GetLastVisibleChoice() - m_first_visibile_choice + 1;
1490     for (int i = 0; i < choices_to_draw; i++) {
1491       surface.MoveCursor(0, i);
1492       int current_choice = m_first_visibile_choice + i;
1493       const char *text = m_choices[current_choice].c_str();
1494       bool highlight = is_selected && current_choice == m_choice;
1495       if (highlight)
1496         surface.AttributeOn(A_REVERSE);
1497       surface.PutChar(current_choice == m_choice ? ACS_DIAMOND : ' ');
1498       surface.PutCString(text);
1499       if (highlight)
1500         surface.AttributeOff(A_REVERSE);
1501     }
1502   }
1503 
FieldDelegateDraw(SubPad & surface,bool is_selected)1504   void FieldDelegateDraw(SubPad &surface, bool is_selected) override {
1505     UpdateScrolling();
1506 
1507     surface.TitledBox(m_label.c_str());
1508 
1509     Rect content_bounds = surface.GetFrame();
1510     content_bounds.Inset(1, 1);
1511     SubPad content_surface = SubPad(surface, content_bounds);
1512 
1513     DrawContent(content_surface, is_selected);
1514   }
1515 
SelectPrevious()1516   void SelectPrevious() {
1517     if (m_choice > 0)
1518       m_choice--;
1519   }
1520 
SelectNext()1521   void SelectNext() {
1522     if (m_choice < GetNumberOfChoices() - 1)
1523       m_choice++;
1524   }
1525 
UpdateScrolling()1526   void UpdateScrolling() {
1527     if (m_choice > GetLastVisibleChoice()) {
1528       m_first_visibile_choice = m_choice - (m_number_of_visible_choices - 1);
1529       return;
1530     }
1531 
1532     if (m_choice < m_first_visibile_choice)
1533       m_first_visibile_choice = m_choice;
1534   }
1535 
FieldDelegateHandleChar(int key)1536   HandleCharResult FieldDelegateHandleChar(int key) override {
1537     switch (key) {
1538     case KEY_UP:
1539       SelectPrevious();
1540       return eKeyHandled;
1541     case KEY_DOWN:
1542       SelectNext();
1543       return eKeyHandled;
1544     default:
1545       break;
1546     }
1547     return eKeyNotHandled;
1548   }
1549 
1550   // Returns the content of the choice as a string.
GetChoiceContent()1551   std::string GetChoiceContent() { return m_choices[m_choice]; }
1552 
1553   // Returns the index of the choice.
GetChoice()1554   int GetChoice() { return m_choice; }
1555 
SetChoice(const std::string & choice)1556   void SetChoice(const std::string &choice) {
1557     for (int i = 0; i < GetNumberOfChoices(); i++) {
1558       if (choice == m_choices[i]) {
1559         m_choice = i;
1560         return;
1561       }
1562     }
1563   }
1564 
1565 protected:
1566   std::string m_label;
1567   int m_number_of_visible_choices;
1568   std::vector<std::string> m_choices;
1569   // The index of the selected choice.
1570   int m_choice;
1571   // The index of the first visible choice in the field.
1572   int m_first_visibile_choice;
1573 };
1574 
1575 class PlatformPluginFieldDelegate : public ChoicesFieldDelegate {
1576 public:
PlatformPluginFieldDelegate(Debugger & debugger)1577   PlatformPluginFieldDelegate(Debugger &debugger)
1578       : ChoicesFieldDelegate("Platform Plugin", 3, GetPossiblePluginNames()) {
1579     PlatformSP platform_sp = debugger.GetPlatformList().GetSelectedPlatform();
1580     if (platform_sp)
1581       SetChoice(platform_sp->GetName().AsCString());
1582   }
1583 
GetPossiblePluginNames()1584   std::vector<std::string> GetPossiblePluginNames() {
1585     std::vector<std::string> names;
1586     size_t i = 0;
1587     while (auto name = PluginManager::GetPlatformPluginNameAtIndex(i++))
1588       names.push_back(name);
1589     return names;
1590   }
1591 
GetPluginName()1592   std::string GetPluginName() {
1593     std::string plugin_name = GetChoiceContent();
1594     return plugin_name;
1595   }
1596 };
1597 
1598 class ProcessPluginFieldDelegate : public ChoicesFieldDelegate {
1599 public:
ProcessPluginFieldDelegate()1600   ProcessPluginFieldDelegate()
1601       : ChoicesFieldDelegate("Process Plugin", 3, GetPossiblePluginNames()) {}
1602 
GetPossiblePluginNames()1603   std::vector<std::string> GetPossiblePluginNames() {
1604     std::vector<std::string> names;
1605     names.push_back("<default>");
1606 
1607     size_t i = 0;
1608     while (auto name = PluginManager::GetProcessPluginNameAtIndex(i++))
1609       names.push_back(name);
1610     return names;
1611   }
1612 
GetPluginName()1613   std::string GetPluginName() {
1614     std::string plugin_name = GetChoiceContent();
1615     if (plugin_name == "<default>")
1616       return "";
1617     return plugin_name;
1618   }
1619 };
1620 
1621 template <class T> class ListFieldDelegate : public FieldDelegate {
1622 public:
ListFieldDelegate(const char * label,T default_field)1623   ListFieldDelegate(const char *label, T default_field)
1624       : m_label(label), m_default_field(default_field), m_selection_index(0),
1625         m_selection_type(SelectionType::NewButton) {}
1626 
1627   // Signify which element is selected. If a field or a remove button is
1628   // selected, then m_selection_index signifies the particular field that
1629   // is selected or the field that the remove button belongs to.
1630   enum class SelectionType { Field, RemoveButton, NewButton };
1631 
1632   // A List field is drawn as a titled box of a number of other fields of the
1633   // same type. Each field has a Remove button next to it that removes the
1634   // corresponding field. Finally, the last line contains a New button to add a
1635   // new field.
1636   //
1637   // __[Label]___________
1638   // | Field 0 [Remove] |
1639   // | Field 1 [Remove] |
1640   // | Field 2 [Remove] |
1641   // |      [New]       |
1642   // |__________________|
1643 
1644   // List fields have two lines for border characters, 1 line for the New
1645   // button, and the total height of the available fields.
FieldDelegateGetHeight()1646   int FieldDelegateGetHeight() override {
1647     // 2 border characters.
1648     int height = 2;
1649     // Total height of the fields.
1650     for (int i = 0; i < GetNumberOfFields(); i++) {
1651       height += m_fields[i].FieldDelegateGetHeight();
1652     }
1653     // A line for the New button.
1654     height++;
1655     return height;
1656   }
1657 
FieldDelegateGetScrollContext()1658   ScrollContext FieldDelegateGetScrollContext() override {
1659     int height = FieldDelegateGetHeight();
1660     if (m_selection_type == SelectionType::NewButton)
1661       return ScrollContext(height - 2, height - 1);
1662 
1663     FieldDelegate &field = m_fields[m_selection_index];
1664     ScrollContext context = field.FieldDelegateGetScrollContext();
1665 
1666     // Start at 1 because of the top border.
1667     int offset = 1;
1668     for (int i = 0; i < m_selection_index; i++) {
1669       offset += m_fields[i].FieldDelegateGetHeight();
1670     }
1671     context.Offset(offset);
1672 
1673     // If the scroll context is touching the top border, include it in the
1674     // context to show the label.
1675     if (context.start == 1)
1676       context.start--;
1677 
1678     // If the scroll context is touching the new button, include it as well as
1679     // the bottom border in the context.
1680     if (context.end == height - 3)
1681       context.end += 2;
1682 
1683     return context;
1684   }
1685 
DrawRemoveButton(SubPad & surface,int highlight)1686   void DrawRemoveButton(SubPad &surface, int highlight) {
1687     surface.MoveCursor(1, surface.GetHeight() / 2);
1688     if (highlight)
1689       surface.AttributeOn(A_REVERSE);
1690     surface.PutCString("[Remove]");
1691     if (highlight)
1692       surface.AttributeOff(A_REVERSE);
1693   }
1694 
DrawFields(SubPad & surface,bool is_selected)1695   void DrawFields(SubPad &surface, bool is_selected) {
1696     int line = 0;
1697     int width = surface.GetWidth();
1698     for (int i = 0; i < GetNumberOfFields(); i++) {
1699       int height = m_fields[i].FieldDelegateGetHeight();
1700       Rect bounds = Rect(Point(0, line), Size(width, height));
1701       Rect field_bounds, remove_button_bounds;
1702       bounds.VerticalSplit(bounds.size.width - sizeof(" [Remove]"),
1703                            field_bounds, remove_button_bounds);
1704       SubPad field_surface = SubPad(surface, field_bounds);
1705       SubPad remove_button_surface = SubPad(surface, remove_button_bounds);
1706 
1707       bool is_element_selected = m_selection_index == i && is_selected;
1708       bool is_field_selected =
1709           is_element_selected && m_selection_type == SelectionType::Field;
1710       bool is_remove_button_selected =
1711           is_element_selected &&
1712           m_selection_type == SelectionType::RemoveButton;
1713       m_fields[i].FieldDelegateDraw(field_surface, is_field_selected);
1714       DrawRemoveButton(remove_button_surface, is_remove_button_selected);
1715 
1716       line += height;
1717     }
1718   }
1719 
DrawNewButton(SubPad & surface,bool is_selected)1720   void DrawNewButton(SubPad &surface, bool is_selected) {
1721     const char *button_text = "[New]";
1722     int x = (surface.GetWidth() - sizeof(button_text) - 1) / 2;
1723     surface.MoveCursor(x, 0);
1724     bool highlight =
1725         is_selected && m_selection_type == SelectionType::NewButton;
1726     if (highlight)
1727       surface.AttributeOn(A_REVERSE);
1728     surface.PutCString(button_text);
1729     if (highlight)
1730       surface.AttributeOff(A_REVERSE);
1731   }
1732 
FieldDelegateDraw(SubPad & surface,bool is_selected)1733   void FieldDelegateDraw(SubPad &surface, bool is_selected) override {
1734     surface.TitledBox(m_label.c_str());
1735 
1736     Rect content_bounds = surface.GetFrame();
1737     content_bounds.Inset(1, 1);
1738     Rect fields_bounds, new_button_bounds;
1739     content_bounds.HorizontalSplit(content_bounds.size.height - 1,
1740                                    fields_bounds, new_button_bounds);
1741     SubPad fields_surface = SubPad(surface, fields_bounds);
1742     SubPad new_button_surface = SubPad(surface, new_button_bounds);
1743 
1744     DrawFields(fields_surface, is_selected);
1745     DrawNewButton(new_button_surface, is_selected);
1746   }
1747 
AddNewField()1748   void AddNewField() {
1749     m_fields.push_back(m_default_field);
1750     m_selection_index = GetNumberOfFields() - 1;
1751     m_selection_type = SelectionType::Field;
1752     FieldDelegate &field = m_fields[m_selection_index];
1753     field.FieldDelegateSelectFirstElement();
1754   }
1755 
RemoveField()1756   void RemoveField() {
1757     m_fields.erase(m_fields.begin() + m_selection_index);
1758     if (m_selection_index != 0)
1759       m_selection_index--;
1760 
1761     if (GetNumberOfFields() > 0) {
1762       m_selection_type = SelectionType::Field;
1763       FieldDelegate &field = m_fields[m_selection_index];
1764       field.FieldDelegateSelectFirstElement();
1765     } else
1766       m_selection_type = SelectionType::NewButton;
1767   }
1768 
SelectNext(int key)1769   HandleCharResult SelectNext(int key) {
1770     if (m_selection_type == SelectionType::NewButton)
1771       return eKeyNotHandled;
1772 
1773     if (m_selection_type == SelectionType::RemoveButton) {
1774       if (m_selection_index == GetNumberOfFields() - 1) {
1775         m_selection_type = SelectionType::NewButton;
1776         return eKeyHandled;
1777       }
1778       m_selection_index++;
1779       m_selection_type = SelectionType::Field;
1780       FieldDelegate &next_field = m_fields[m_selection_index];
1781       next_field.FieldDelegateSelectFirstElement();
1782       return eKeyHandled;
1783     }
1784 
1785     FieldDelegate &field = m_fields[m_selection_index];
1786     if (!field.FieldDelegateOnLastOrOnlyElement()) {
1787       return field.FieldDelegateHandleChar(key);
1788     }
1789 
1790     field.FieldDelegateExitCallback();
1791 
1792     m_selection_type = SelectionType::RemoveButton;
1793     return eKeyHandled;
1794   }
1795 
SelectPrevious(int key)1796   HandleCharResult SelectPrevious(int key) {
1797     if (FieldDelegateOnFirstOrOnlyElement())
1798       return eKeyNotHandled;
1799 
1800     if (m_selection_type == SelectionType::RemoveButton) {
1801       m_selection_type = SelectionType::Field;
1802       FieldDelegate &field = m_fields[m_selection_index];
1803       field.FieldDelegateSelectLastElement();
1804       return eKeyHandled;
1805     }
1806 
1807     if (m_selection_type == SelectionType::NewButton) {
1808       m_selection_type = SelectionType::RemoveButton;
1809       m_selection_index = GetNumberOfFields() - 1;
1810       return eKeyHandled;
1811     }
1812 
1813     FieldDelegate &field = m_fields[m_selection_index];
1814     if (!field.FieldDelegateOnFirstOrOnlyElement()) {
1815       return field.FieldDelegateHandleChar(key);
1816     }
1817 
1818     field.FieldDelegateExitCallback();
1819 
1820     m_selection_type = SelectionType::RemoveButton;
1821     m_selection_index--;
1822     return eKeyHandled;
1823   }
1824 
FieldDelegateHandleChar(int key)1825   HandleCharResult FieldDelegateHandleChar(int key) override {
1826     switch (key) {
1827     case '\r':
1828     case '\n':
1829     case KEY_ENTER:
1830       switch (m_selection_type) {
1831       case SelectionType::NewButton:
1832         AddNewField();
1833         return eKeyHandled;
1834       case SelectionType::RemoveButton:
1835         RemoveField();
1836         return eKeyHandled;
1837       default:
1838         break;
1839       }
1840       break;
1841     case '\t':
1842       SelectNext(key);
1843       return eKeyHandled;
1844     case KEY_SHIFT_TAB:
1845       SelectPrevious(key);
1846       return eKeyHandled;
1847     default:
1848       break;
1849     }
1850 
1851     // If the key wasn't handled and one of the fields is selected, pass the key
1852     // to that field.
1853     if (m_selection_type == SelectionType::Field) {
1854       return m_fields[m_selection_index].FieldDelegateHandleChar(key);
1855     }
1856 
1857     return eKeyNotHandled;
1858   }
1859 
FieldDelegateOnLastOrOnlyElement()1860   bool FieldDelegateOnLastOrOnlyElement() override {
1861     if (m_selection_type == SelectionType::NewButton) {
1862       return true;
1863     }
1864     return false;
1865   }
1866 
FieldDelegateOnFirstOrOnlyElement()1867   bool FieldDelegateOnFirstOrOnlyElement() override {
1868     if (m_selection_type == SelectionType::NewButton &&
1869         GetNumberOfFields() == 0)
1870       return true;
1871 
1872     if (m_selection_type == SelectionType::Field && m_selection_index == 0) {
1873       FieldDelegate &field = m_fields[m_selection_index];
1874       return field.FieldDelegateOnFirstOrOnlyElement();
1875     }
1876 
1877     return false;
1878   }
1879 
FieldDelegateSelectFirstElement()1880   void FieldDelegateSelectFirstElement() override {
1881     if (GetNumberOfFields() == 0) {
1882       m_selection_type = SelectionType::NewButton;
1883       return;
1884     }
1885 
1886     m_selection_type = SelectionType::Field;
1887     m_selection_index = 0;
1888   }
1889 
FieldDelegateSelectLastElement()1890   void FieldDelegateSelectLastElement() override {
1891     m_selection_type = SelectionType::NewButton;
1892     return;
1893   }
1894 
GetNumberOfFields()1895   int GetNumberOfFields() { return m_fields.size(); }
1896 
1897   // Returns the form delegate at the current index.
GetField(int index)1898   T &GetField(int index) { return m_fields[index]; }
1899 
1900 protected:
1901   std::string m_label;
1902   // The default field delegate instance from which new field delegates will be
1903   // created though a copy.
1904   T m_default_field;
1905   std::vector<T> m_fields;
1906   int m_selection_index;
1907   // See SelectionType class enum.
1908   SelectionType m_selection_type;
1909 };
1910 
1911 class FormAction {
1912 public:
FormAction(const char * label,std::function<void (Window &)> action)1913   FormAction(const char *label, std::function<void(Window &)> action)
1914       : m_action(action) {
1915     if (label)
1916       m_label = label;
1917   }
1918 
1919   // Draw a centered [Label].
Draw(SubPad & surface,bool is_selected)1920   void Draw(SubPad &surface, bool is_selected) {
1921     int x = (surface.GetWidth() - m_label.length()) / 2;
1922     surface.MoveCursor(x, 0);
1923     if (is_selected)
1924       surface.AttributeOn(A_REVERSE);
1925     surface.PutChar('[');
1926     surface.PutCString(m_label.c_str());
1927     surface.PutChar(']');
1928     if (is_selected)
1929       surface.AttributeOff(A_REVERSE);
1930   }
1931 
Execute(Window & window)1932   void Execute(Window &window) { m_action(window); }
1933 
GetLabel()1934   const std::string &GetLabel() { return m_label; }
1935 
1936 protected:
1937   std::string m_label;
1938   std::function<void(Window &)> m_action;
1939 };
1940 
1941 class FormDelegate {
1942 public:
FormDelegate()1943   FormDelegate() {}
1944 
1945   virtual ~FormDelegate() = default;
1946 
1947   virtual std::string GetName() = 0;
1948 
UpdateFieldsVisibility()1949   virtual void UpdateFieldsVisibility() { return; }
1950 
GetField(uint32_t field_index)1951   FieldDelegate *GetField(uint32_t field_index) {
1952     if (field_index < m_fields.size())
1953       return m_fields[field_index].get();
1954     return nullptr;
1955   }
1956 
GetAction(int action_index)1957   FormAction &GetAction(int action_index) { return m_actions[action_index]; }
1958 
GetNumberOfFields()1959   int GetNumberOfFields() { return m_fields.size(); }
1960 
GetNumberOfActions()1961   int GetNumberOfActions() { return m_actions.size(); }
1962 
HasError()1963   bool HasError() { return !m_error.empty(); }
1964 
ClearError()1965   void ClearError() { m_error.clear(); }
1966 
GetError()1967   const std::string &GetError() { return m_error; }
1968 
SetError(const char * error)1969   void SetError(const char *error) { m_error = error; }
1970 
1971   // If all fields are valid, true is returned. Otherwise, an error message is
1972   // set and false is returned. This method is usually called at the start of an
1973   // action that requires valid fields.
CheckFieldsValidity()1974   bool CheckFieldsValidity() {
1975     for (int i = 0; i < GetNumberOfFields(); i++) {
1976       if (GetField(i)->FieldDelegateHasError()) {
1977         SetError("Some fields are invalid!");
1978         return false;
1979       }
1980     }
1981     return true;
1982   }
1983 
1984   // Factory methods to create and add fields of specific types.
1985 
AddTextField(const char * label,const char * content,bool required)1986   TextFieldDelegate *AddTextField(const char *label, const char *content,
1987                                   bool required) {
1988     TextFieldDelegate *delegate =
1989         new TextFieldDelegate(label, content, required);
1990     m_fields.push_back(FieldDelegateUP(delegate));
1991     return delegate;
1992   }
1993 
AddFileField(const char * label,const char * content,bool need_to_exist,bool required)1994   FileFieldDelegate *AddFileField(const char *label, const char *content,
1995                                   bool need_to_exist, bool required) {
1996     FileFieldDelegate *delegate =
1997         new FileFieldDelegate(label, content, need_to_exist, required);
1998     m_fields.push_back(FieldDelegateUP(delegate));
1999     return delegate;
2000   }
2001 
AddDirectoryField(const char * label,const char * content,bool need_to_exist,bool required)2002   DirectoryFieldDelegate *AddDirectoryField(const char *label,
2003                                             const char *content,
2004                                             bool need_to_exist, bool required) {
2005     DirectoryFieldDelegate *delegate =
2006         new DirectoryFieldDelegate(label, content, need_to_exist, required);
2007     m_fields.push_back(FieldDelegateUP(delegate));
2008     return delegate;
2009   }
2010 
AddArchField(const char * label,const char * content,bool required)2011   ArchFieldDelegate *AddArchField(const char *label, const char *content,
2012                                   bool required) {
2013     ArchFieldDelegate *delegate =
2014         new ArchFieldDelegate(label, content, required);
2015     m_fields.push_back(FieldDelegateUP(delegate));
2016     return delegate;
2017   }
2018 
AddIntegerField(const char * label,int content,bool required)2019   IntegerFieldDelegate *AddIntegerField(const char *label, int content,
2020                                         bool required) {
2021     IntegerFieldDelegate *delegate =
2022         new IntegerFieldDelegate(label, content, required);
2023     m_fields.push_back(FieldDelegateUP(delegate));
2024     return delegate;
2025   }
2026 
AddBooleanField(const char * label,bool content)2027   BooleanFieldDelegate *AddBooleanField(const char *label, bool content) {
2028     BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content);
2029     m_fields.push_back(FieldDelegateUP(delegate));
2030     return delegate;
2031   }
2032 
AddChoicesField(const char * label,int height,std::vector<std::string> choices)2033   ChoicesFieldDelegate *AddChoicesField(const char *label, int height,
2034                                         std::vector<std::string> choices) {
2035     ChoicesFieldDelegate *delegate =
2036         new ChoicesFieldDelegate(label, height, choices);
2037     m_fields.push_back(FieldDelegateUP(delegate));
2038     return delegate;
2039   }
2040 
AddPlatformPluginField(Debugger & debugger)2041   PlatformPluginFieldDelegate *AddPlatformPluginField(Debugger &debugger) {
2042     PlatformPluginFieldDelegate *delegate =
2043         new PlatformPluginFieldDelegate(debugger);
2044     m_fields.push_back(FieldDelegateUP(delegate));
2045     return delegate;
2046   }
2047 
AddProcessPluginField()2048   ProcessPluginFieldDelegate *AddProcessPluginField() {
2049     ProcessPluginFieldDelegate *delegate = new ProcessPluginFieldDelegate();
2050     m_fields.push_back(FieldDelegateUP(delegate));
2051     return delegate;
2052   }
2053 
2054   template <class T>
AddListField(const char * label,T default_field)2055   ListFieldDelegate<T> *AddListField(const char *label, T default_field) {
2056     ListFieldDelegate<T> *delegate =
2057         new ListFieldDelegate<T>(label, default_field);
2058     m_fields.push_back(FieldDelegateUP(delegate));
2059     return delegate;
2060   }
2061 
2062   // Factory methods for adding actions.
2063 
AddAction(const char * label,std::function<void (Window &)> action)2064   void AddAction(const char *label, std::function<void(Window &)> action) {
2065     m_actions.push_back(FormAction(label, action));
2066   }
2067 
2068 protected:
2069   std::vector<FieldDelegateUP> m_fields;
2070   std::vector<FormAction> m_actions;
2071   // Optional error message. If empty, form is considered to have no error.
2072   std::string m_error;
2073 };
2074 
2075 typedef std::shared_ptr<FormDelegate> FormDelegateSP;
2076 
2077 class FormWindowDelegate : public WindowDelegate {
2078 public:
FormWindowDelegate(FormDelegateSP & delegate_sp)2079   FormWindowDelegate(FormDelegateSP &delegate_sp)
2080       : m_delegate_sp(delegate_sp), m_selection_index(0),
2081         m_first_visible_line(0) {
2082     assert(m_delegate_sp->GetNumberOfActions() > 0);
2083     if (m_delegate_sp->GetNumberOfFields() > 0)
2084       m_selection_type = SelectionType::Field;
2085     else
2086       m_selection_type = SelectionType::Action;
2087   }
2088 
2089   // Signify which element is selected. If a field or an action is selected,
2090   // then m_selection_index signifies the particular field or action that is
2091   // selected.
2092   enum class SelectionType { Field, Action };
2093 
2094   // A form window is padded by one character from all sides. First, if an error
2095   // message exists, it is drawn followed by a separator. Then one or more
2096   // fields are drawn. Finally, all available actions are drawn on a single
2097   // line.
2098   //
2099   // ___<Form Name>_________________________________________________
2100   // |                                                             |
2101   // | - Error message if it exists.                               |
2102   // |-------------------------------------------------------------|
2103   // | Form elements here.                                         |
2104   // |                       Form actions here.                    |
2105   // |                                                             |
2106   // |______________________________________[Press Esc to cancel]__|
2107   //
2108 
2109   // One line for the error and another for the horizontal line.
GetErrorHeight()2110   int GetErrorHeight() {
2111     if (m_delegate_sp->HasError())
2112       return 2;
2113     return 0;
2114   }
2115 
2116   // Actions span a single line.
GetActionsHeight()2117   int GetActionsHeight() {
2118     if (m_delegate_sp->GetNumberOfActions() > 0)
2119       return 1;
2120     return 0;
2121   }
2122 
2123   // Get the total number of needed lines to draw the contents.
GetContentHeight()2124   int GetContentHeight() {
2125     int height = 0;
2126     height += GetErrorHeight();
2127     for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2128       if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2129         continue;
2130       height += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2131     }
2132     height += GetActionsHeight();
2133     return height;
2134   }
2135 
GetScrollContext()2136   ScrollContext GetScrollContext() {
2137     if (m_selection_type == SelectionType::Action)
2138       return ScrollContext(GetContentHeight() - 1);
2139 
2140     FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2141     ScrollContext context = field->FieldDelegateGetScrollContext();
2142 
2143     int offset = GetErrorHeight();
2144     for (int i = 0; i < m_selection_index; i++) {
2145       if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2146         continue;
2147       offset += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2148     }
2149     context.Offset(offset);
2150 
2151     // If the context is touching the error, include the error in the context as
2152     // well.
2153     if (context.start == GetErrorHeight())
2154       context.start = 0;
2155 
2156     return context;
2157   }
2158 
UpdateScrolling(DerivedWindow & surface)2159   void UpdateScrolling(DerivedWindow &surface) {
2160     ScrollContext context = GetScrollContext();
2161     int content_height = GetContentHeight();
2162     int surface_height = surface.GetHeight();
2163     int visible_height = std::min(content_height, surface_height);
2164     int last_visible_line = m_first_visible_line + visible_height - 1;
2165 
2166     // If the last visible line is bigger than the content, then it is invalid
2167     // and needs to be set to the last line in the content. This can happen when
2168     // a field has shrunk in height.
2169     if (last_visible_line > content_height - 1) {
2170       m_first_visible_line = content_height - visible_height;
2171     }
2172 
2173     if (context.start < m_first_visible_line) {
2174       m_first_visible_line = context.start;
2175       return;
2176     }
2177 
2178     if (context.end > last_visible_line) {
2179       m_first_visible_line = context.end - visible_height + 1;
2180     }
2181   }
2182 
DrawError(SubPad & surface)2183   void DrawError(SubPad &surface) {
2184     if (!m_delegate_sp->HasError())
2185       return;
2186     surface.MoveCursor(0, 0);
2187     surface.AttributeOn(COLOR_PAIR(RedOnBlack));
2188     surface.PutChar(ACS_DIAMOND);
2189     surface.PutChar(' ');
2190     surface.PutCStringTruncated(1, m_delegate_sp->GetError().c_str());
2191     surface.AttributeOff(COLOR_PAIR(RedOnBlack));
2192 
2193     surface.MoveCursor(0, 1);
2194     surface.HorizontalLine(surface.GetWidth());
2195   }
2196 
DrawFields(SubPad & surface)2197   void DrawFields(SubPad &surface) {
2198     int line = 0;
2199     int width = surface.GetWidth();
2200     bool a_field_is_selected = m_selection_type == SelectionType::Field;
2201     for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2202       FieldDelegate *field = m_delegate_sp->GetField(i);
2203       if (!field->FieldDelegateIsVisible())
2204         continue;
2205       bool is_field_selected = a_field_is_selected && m_selection_index == i;
2206       int height = field->FieldDelegateGetHeight();
2207       Rect bounds = Rect(Point(0, line), Size(width, height));
2208       SubPad field_surface = SubPad(surface, bounds);
2209       field->FieldDelegateDraw(field_surface, is_field_selected);
2210       line += height;
2211     }
2212   }
2213 
DrawActions(SubPad & surface)2214   void DrawActions(SubPad &surface) {
2215     int number_of_actions = m_delegate_sp->GetNumberOfActions();
2216     int width = surface.GetWidth() / number_of_actions;
2217     bool an_action_is_selected = m_selection_type == SelectionType::Action;
2218     int x = 0;
2219     for (int i = 0; i < number_of_actions; i++) {
2220       bool is_action_selected = an_action_is_selected && m_selection_index == i;
2221       FormAction &action = m_delegate_sp->GetAction(i);
2222       Rect bounds = Rect(Point(x, 0), Size(width, 1));
2223       SubPad action_surface = SubPad(surface, bounds);
2224       action.Draw(action_surface, is_action_selected);
2225       x += width;
2226     }
2227   }
2228 
DrawElements(SubPad & surface)2229   void DrawElements(SubPad &surface) {
2230     Rect frame = surface.GetFrame();
2231     Rect fields_bounds, actions_bounds;
2232     frame.HorizontalSplit(surface.GetHeight() - GetActionsHeight(),
2233                           fields_bounds, actions_bounds);
2234     SubPad fields_surface = SubPad(surface, fields_bounds);
2235     SubPad actions_surface = SubPad(surface, actions_bounds);
2236 
2237     DrawFields(fields_surface);
2238     DrawActions(actions_surface);
2239   }
2240 
2241   // Contents are first drawn on a pad. Then a subset of that pad is copied to
2242   // the derived window starting at the first visible line. This essentially
2243   // provides scrolling functionality.
DrawContent(DerivedWindow & surface)2244   void DrawContent(DerivedWindow &surface) {
2245     UpdateScrolling(surface);
2246 
2247     int width = surface.GetWidth();
2248     int height = GetContentHeight();
2249     Pad pad = Pad(Size(width, height));
2250 
2251     Rect frame = pad.GetFrame();
2252     Rect error_bounds, elements_bounds;
2253     frame.HorizontalSplit(GetErrorHeight(), error_bounds, elements_bounds);
2254     SubPad error_surface = SubPad(pad, error_bounds);
2255     SubPad elements_surface = SubPad(pad, elements_bounds);
2256 
2257     DrawError(error_surface);
2258     DrawElements(elements_surface);
2259 
2260     int copy_height = std::min(surface.GetHeight(), pad.GetHeight());
2261     pad.CopyToSurface(surface, Point(0, m_first_visible_line), Point(),
2262                       Size(width, copy_height));
2263   }
2264 
WindowDelegateDraw(Window & window,bool force)2265   bool WindowDelegateDraw(Window &window, bool force) override {
2266     m_delegate_sp->UpdateFieldsVisibility();
2267 
2268     window.Erase();
2269 
2270     window.DrawTitleBox(m_delegate_sp->GetName().c_str(),
2271                         "Press Esc to cancel");
2272 
2273     Rect content_bounds = window.GetFrame();
2274     content_bounds.Inset(2, 2);
2275     DerivedWindow content_surface = DerivedWindow(window, content_bounds);
2276 
2277     DrawContent(content_surface);
2278     return true;
2279   }
2280 
SkipNextHiddenFields()2281   void SkipNextHiddenFields() {
2282     while (true) {
2283       if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2284         return;
2285 
2286       if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2287         m_selection_type = SelectionType::Action;
2288         m_selection_index = 0;
2289         return;
2290       }
2291 
2292       m_selection_index++;
2293     }
2294   }
2295 
SelectNext(int key)2296   HandleCharResult SelectNext(int key) {
2297     if (m_selection_type == SelectionType::Action) {
2298       if (m_selection_index < m_delegate_sp->GetNumberOfActions() - 1) {
2299         m_selection_index++;
2300         return eKeyHandled;
2301       }
2302 
2303       m_selection_index = 0;
2304       m_selection_type = SelectionType::Field;
2305       SkipNextHiddenFields();
2306       if (m_selection_type == SelectionType::Field) {
2307         FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2308         next_field->FieldDelegateSelectFirstElement();
2309       }
2310       return eKeyHandled;
2311     }
2312 
2313     FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2314     if (!field->FieldDelegateOnLastOrOnlyElement()) {
2315       return field->FieldDelegateHandleChar(key);
2316     }
2317 
2318     field->FieldDelegateExitCallback();
2319 
2320     if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2321       m_selection_type = SelectionType::Action;
2322       m_selection_index = 0;
2323       return eKeyHandled;
2324     }
2325 
2326     m_selection_index++;
2327     SkipNextHiddenFields();
2328 
2329     if (m_selection_type == SelectionType::Field) {
2330       FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2331       next_field->FieldDelegateSelectFirstElement();
2332     }
2333 
2334     return eKeyHandled;
2335   }
2336 
SkipPreviousHiddenFields()2337   void SkipPreviousHiddenFields() {
2338     while (true) {
2339       if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2340         return;
2341 
2342       if (m_selection_index == 0) {
2343         m_selection_type = SelectionType::Action;
2344         m_selection_index = 0;
2345         return;
2346       }
2347 
2348       m_selection_index--;
2349     }
2350   }
2351 
SelectPrevious(int key)2352   HandleCharResult SelectPrevious(int key) {
2353     if (m_selection_type == SelectionType::Action) {
2354       if (m_selection_index > 0) {
2355         m_selection_index--;
2356         return eKeyHandled;
2357       }
2358       m_selection_index = m_delegate_sp->GetNumberOfFields() - 1;
2359       m_selection_type = SelectionType::Field;
2360       SkipPreviousHiddenFields();
2361       if (m_selection_type == SelectionType::Field) {
2362         FieldDelegate *previous_field =
2363             m_delegate_sp->GetField(m_selection_index);
2364         previous_field->FieldDelegateSelectLastElement();
2365       }
2366       return eKeyHandled;
2367     }
2368 
2369     FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2370     if (!field->FieldDelegateOnFirstOrOnlyElement()) {
2371       return field->FieldDelegateHandleChar(key);
2372     }
2373 
2374     field->FieldDelegateExitCallback();
2375 
2376     if (m_selection_index == 0) {
2377       m_selection_type = SelectionType::Action;
2378       m_selection_index = m_delegate_sp->GetNumberOfActions() - 1;
2379       return eKeyHandled;
2380     }
2381 
2382     m_selection_index--;
2383     SkipPreviousHiddenFields();
2384 
2385     if (m_selection_type == SelectionType::Field) {
2386       FieldDelegate *previous_field =
2387           m_delegate_sp->GetField(m_selection_index);
2388       previous_field->FieldDelegateSelectLastElement();
2389     }
2390 
2391     return eKeyHandled;
2392   }
2393 
ExecuteAction(Window & window)2394   void ExecuteAction(Window &window) {
2395     FormAction &action = m_delegate_sp->GetAction(m_selection_index);
2396     action.Execute(window);
2397     if (m_delegate_sp->HasError()) {
2398       m_first_visible_line = 0;
2399       m_selection_index = 0;
2400       m_selection_type = SelectionType::Field;
2401     }
2402   }
2403 
WindowDelegateHandleChar(Window & window,int key)2404   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
2405     switch (key) {
2406     case '\r':
2407     case '\n':
2408     case KEY_ENTER:
2409       if (m_selection_type == SelectionType::Action) {
2410         ExecuteAction(window);
2411         return eKeyHandled;
2412       }
2413       break;
2414     case '\t':
2415       return SelectNext(key);
2416     case KEY_SHIFT_TAB:
2417       return SelectPrevious(key);
2418     case KEY_ESCAPE:
2419       window.GetParent()->RemoveSubWindow(&window);
2420       return eKeyHandled;
2421     default:
2422       break;
2423     }
2424 
2425     // If the key wasn't handled and one of the fields is selected, pass the key
2426     // to that field.
2427     if (m_selection_type == SelectionType::Field) {
2428       FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2429       return field->FieldDelegateHandleChar(key);
2430     }
2431 
2432     return eKeyNotHandled;
2433   }
2434 
2435 protected:
2436   FormDelegateSP m_delegate_sp;
2437   // The index of the currently selected SelectionType.
2438   int m_selection_index;
2439   // See SelectionType class enum.
2440   SelectionType m_selection_type;
2441   // The first visible line from the pad.
2442   int m_first_visible_line;
2443 };
2444 
2445 ///////////////////////////
2446 // Form Delegate Instances
2447 ///////////////////////////
2448 
2449 class DetachOrKillProcessFormDelegate : public FormDelegate {
2450 public:
DetachOrKillProcessFormDelegate(Process * process)2451   DetachOrKillProcessFormDelegate(Process *process) : m_process(process) {
2452     SetError("There is a running process, either detach or kill it.");
2453 
2454     m_keep_stopped_field =
2455         AddBooleanField("Keep process stopped when detaching.", false);
2456 
2457     AddAction("Detach", [this](Window &window) { Detach(window); });
2458     AddAction("Kill", [this](Window &window) { Kill(window); });
2459   }
2460 
GetName()2461   std::string GetName() override { return "Detach/Kill Process"; }
2462 
Kill(Window & window)2463   void Kill(Window &window) {
2464     Status destroy_status(m_process->Destroy(false));
2465     if (destroy_status.Fail()) {
2466       SetError("Failed to kill process.");
2467       return;
2468     }
2469     window.GetParent()->RemoveSubWindow(&window);
2470   }
2471 
Detach(Window & window)2472   void Detach(Window &window) {
2473     Status detach_status(m_process->Detach(m_keep_stopped_field->GetBoolean()));
2474     if (detach_status.Fail()) {
2475       SetError("Failed to detach from process.");
2476       return;
2477     }
2478     window.GetParent()->RemoveSubWindow(&window);
2479   }
2480 
2481 protected:
2482   Process *m_process;
2483   BooleanFieldDelegate *m_keep_stopped_field;
2484 };
2485 
2486 class ProcessAttachFormDelegate : public FormDelegate {
2487 public:
ProcessAttachFormDelegate(Debugger & debugger,WindowSP main_window_sp)2488   ProcessAttachFormDelegate(Debugger &debugger, WindowSP main_window_sp)
2489       : m_debugger(debugger), m_main_window_sp(main_window_sp) {
2490     std::vector<std::string> types;
2491     types.push_back(std::string("Name"));
2492     types.push_back(std::string("PID"));
2493     m_type_field = AddChoicesField("Attach By", 2, types);
2494     m_pid_field = AddIntegerField("PID", 0, true);
2495     m_name_field =
2496         AddTextField("Process Name", GetDefaultProcessName().c_str(), true);
2497     m_continue_field = AddBooleanField("Continue once attached.", false);
2498     m_wait_for_field = AddBooleanField("Wait for process to launch.", false);
2499     m_include_existing_field =
2500         AddBooleanField("Include existing processes.", false);
2501     m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
2502     m_plugin_field = AddProcessPluginField();
2503 
2504     AddAction("Attach", [this](Window &window) { Attach(window); });
2505   }
2506 
GetName()2507   std::string GetName() override { return "Attach Process"; }
2508 
UpdateFieldsVisibility()2509   void UpdateFieldsVisibility() override {
2510     if (m_type_field->GetChoiceContent() == "Name") {
2511       m_pid_field->FieldDelegateHide();
2512       m_name_field->FieldDelegateShow();
2513       m_wait_for_field->FieldDelegateShow();
2514       if (m_wait_for_field->GetBoolean())
2515         m_include_existing_field->FieldDelegateShow();
2516       else
2517         m_include_existing_field->FieldDelegateHide();
2518     } else {
2519       m_pid_field->FieldDelegateShow();
2520       m_name_field->FieldDelegateHide();
2521       m_wait_for_field->FieldDelegateHide();
2522       m_include_existing_field->FieldDelegateHide();
2523     }
2524     if (m_show_advanced_field->GetBoolean())
2525       m_plugin_field->FieldDelegateShow();
2526     else
2527       m_plugin_field->FieldDelegateHide();
2528   }
2529 
2530   // Get the basename of the target's main executable if available, empty string
2531   // otherwise.
GetDefaultProcessName()2532   std::string GetDefaultProcessName() {
2533     Target *target = m_debugger.GetSelectedTarget().get();
2534     if (target == nullptr)
2535       return "";
2536 
2537     ModuleSP module_sp = target->GetExecutableModule();
2538     if (!module_sp->IsExecutable())
2539       return "";
2540 
2541     return module_sp->GetFileSpec().GetFilename().AsCString();
2542   }
2543 
StopRunningProcess()2544   bool StopRunningProcess() {
2545     ExecutionContext exe_ctx =
2546         m_debugger.GetCommandInterpreter().GetExecutionContext();
2547 
2548     if (!exe_ctx.HasProcessScope())
2549       return false;
2550 
2551     Process *process = exe_ctx.GetProcessPtr();
2552     if (!(process && process->IsAlive()))
2553       return false;
2554 
2555     FormDelegateSP form_delegate_sp =
2556         FormDelegateSP(new DetachOrKillProcessFormDelegate(process));
2557     Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);
2558     WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(
2559         form_delegate_sp->GetName().c_str(), bounds, true);
2560     WindowDelegateSP window_delegate_sp =
2561         WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
2562     form_window_sp->SetDelegate(window_delegate_sp);
2563 
2564     return true;
2565   }
2566 
GetTarget()2567   Target *GetTarget() {
2568     Target *target = m_debugger.GetSelectedTarget().get();
2569 
2570     if (target != nullptr)
2571       return target;
2572 
2573     TargetSP new_target_sp;
2574     m_debugger.GetTargetList().CreateTarget(
2575         m_debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp);
2576 
2577     target = new_target_sp.get();
2578 
2579     if (target == nullptr)
2580       SetError("Failed to create target.");
2581 
2582     m_debugger.GetTargetList().SetSelectedTarget(new_target_sp);
2583 
2584     return target;
2585   }
2586 
GetAttachInfo()2587   ProcessAttachInfo GetAttachInfo() {
2588     ProcessAttachInfo attach_info;
2589     attach_info.SetContinueOnceAttached(m_continue_field->GetBoolean());
2590     if (m_type_field->GetChoiceContent() == "Name") {
2591       attach_info.GetExecutableFile().SetFile(m_name_field->GetText(),
2592                                               FileSpec::Style::native);
2593       attach_info.SetWaitForLaunch(m_wait_for_field->GetBoolean());
2594       if (m_wait_for_field->GetBoolean())
2595         attach_info.SetIgnoreExisting(!m_include_existing_field->GetBoolean());
2596     } else {
2597       attach_info.SetProcessID(m_pid_field->GetInteger());
2598     }
2599     attach_info.SetProcessPluginName(m_plugin_field->GetPluginName());
2600 
2601     return attach_info;
2602   }
2603 
Attach(Window & window)2604   void Attach(Window &window) {
2605     ClearError();
2606 
2607     bool all_fields_are_valid = CheckFieldsValidity();
2608     if (!all_fields_are_valid)
2609       return;
2610 
2611     bool process_is_running = StopRunningProcess();
2612     if (process_is_running)
2613       return;
2614 
2615     Target *target = GetTarget();
2616     if (HasError())
2617       return;
2618 
2619     StreamString stream;
2620     ProcessAttachInfo attach_info = GetAttachInfo();
2621     Status status = target->Attach(attach_info, &stream);
2622 
2623     if (status.Fail()) {
2624       SetError(status.AsCString());
2625       return;
2626     }
2627 
2628     ProcessSP process_sp(target->GetProcessSP());
2629     if (!process_sp) {
2630       SetError("Attached sucessfully but target has no process.");
2631       return;
2632     }
2633 
2634     if (attach_info.GetContinueOnceAttached())
2635       process_sp->Resume();
2636 
2637     window.GetParent()->RemoveSubWindow(&window);
2638   }
2639 
2640 protected:
2641   Debugger &m_debugger;
2642   WindowSP m_main_window_sp;
2643 
2644   ChoicesFieldDelegate *m_type_field;
2645   IntegerFieldDelegate *m_pid_field;
2646   TextFieldDelegate *m_name_field;
2647   BooleanFieldDelegate *m_continue_field;
2648   BooleanFieldDelegate *m_wait_for_field;
2649   BooleanFieldDelegate *m_include_existing_field;
2650   BooleanFieldDelegate *m_show_advanced_field;
2651   ProcessPluginFieldDelegate *m_plugin_field;
2652 };
2653 
2654 class MenuDelegate {
2655 public:
2656   virtual ~MenuDelegate() = default;
2657 
2658   virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0;
2659 };
2660 
2661 class Menu : public WindowDelegate {
2662 public:
2663   enum class Type { Invalid, Bar, Item, Separator };
2664 
2665   // Menubar or separator constructor
2666   Menu(Type type);
2667 
2668   // Menuitem constructor
2669   Menu(const char *name, const char *key_name, int key_value,
2670        uint64_t identifier);
2671 
2672   ~Menu() override = default;
2673 
GetDelegate() const2674   const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; }
2675 
SetDelegate(const MenuDelegateSP & delegate_sp)2676   void SetDelegate(const MenuDelegateSP &delegate_sp) {
2677     m_delegate_sp = delegate_sp;
2678   }
2679 
2680   void RecalculateNameLengths();
2681 
2682   void AddSubmenu(const MenuSP &menu_sp);
2683 
2684   int DrawAndRunMenu(Window &window);
2685 
2686   void DrawMenuTitle(Window &window, bool highlight);
2687 
2688   bool WindowDelegateDraw(Window &window, bool force) override;
2689 
2690   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
2691 
ActionPrivate(Menu & menu)2692   MenuActionResult ActionPrivate(Menu &menu) {
2693     MenuActionResult result = MenuActionResult::NotHandled;
2694     if (m_delegate_sp) {
2695       result = m_delegate_sp->MenuDelegateAction(menu);
2696       if (result != MenuActionResult::NotHandled)
2697         return result;
2698     } else if (m_parent) {
2699       result = m_parent->ActionPrivate(menu);
2700       if (result != MenuActionResult::NotHandled)
2701         return result;
2702     }
2703     return m_canned_result;
2704   }
2705 
Action()2706   MenuActionResult Action() {
2707     // Call the recursive action so it can try to handle it with the menu
2708     // delegate, and if not, try our parent menu
2709     return ActionPrivate(*this);
2710   }
2711 
SetCannedResult(MenuActionResult result)2712   void SetCannedResult(MenuActionResult result) { m_canned_result = result; }
2713 
GetSubmenus()2714   Menus &GetSubmenus() { return m_submenus; }
2715 
GetSubmenus() const2716   const Menus &GetSubmenus() const { return m_submenus; }
2717 
GetSelectedSubmenuIndex() const2718   int GetSelectedSubmenuIndex() const { return m_selected; }
2719 
SetSelectedSubmenuIndex(int idx)2720   void SetSelectedSubmenuIndex(int idx) { m_selected = idx; }
2721 
GetType() const2722   Type GetType() const { return m_type; }
2723 
GetStartingColumn() const2724   int GetStartingColumn() const { return m_start_col; }
2725 
SetStartingColumn(int col)2726   void SetStartingColumn(int col) { m_start_col = col; }
2727 
GetKeyValue() const2728   int GetKeyValue() const { return m_key_value; }
2729 
GetName()2730   std::string &GetName() { return m_name; }
2731 
GetDrawWidth() const2732   int GetDrawWidth() const {
2733     return m_max_submenu_name_length + m_max_submenu_key_name_length + 8;
2734   }
2735 
GetIdentifier() const2736   uint64_t GetIdentifier() const { return m_identifier; }
2737 
SetIdentifier(uint64_t identifier)2738   void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
2739 
2740 protected:
2741   std::string m_name;
2742   std::string m_key_name;
2743   uint64_t m_identifier;
2744   Type m_type;
2745   int m_key_value;
2746   int m_start_col;
2747   int m_max_submenu_name_length;
2748   int m_max_submenu_key_name_length;
2749   int m_selected;
2750   Menu *m_parent;
2751   Menus m_submenus;
2752   WindowSP m_menu_window_sp;
2753   MenuActionResult m_canned_result;
2754   MenuDelegateSP m_delegate_sp;
2755 };
2756 
2757 // Menubar or separator constructor
Menu(Type type)2758 Menu::Menu(Type type)
2759     : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0),
2760       m_start_col(0), m_max_submenu_name_length(0),
2761       m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
2762       m_submenus(), m_canned_result(MenuActionResult::NotHandled),
2763       m_delegate_sp() {}
2764 
2765 // Menuitem constructor
Menu(const char * name,const char * key_name,int key_value,uint64_t identifier)2766 Menu::Menu(const char *name, const char *key_name, int key_value,
2767            uint64_t identifier)
2768     : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid),
2769       m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0),
2770       m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
2771       m_submenus(), m_canned_result(MenuActionResult::NotHandled),
2772       m_delegate_sp() {
2773   if (name && name[0]) {
2774     m_name = name;
2775     m_type = Type::Item;
2776     if (key_name && key_name[0])
2777       m_key_name = key_name;
2778   } else {
2779     m_type = Type::Separator;
2780   }
2781 }
2782 
RecalculateNameLengths()2783 void Menu::RecalculateNameLengths() {
2784   m_max_submenu_name_length = 0;
2785   m_max_submenu_key_name_length = 0;
2786   Menus &submenus = GetSubmenus();
2787   const size_t num_submenus = submenus.size();
2788   for (size_t i = 0; i < num_submenus; ++i) {
2789     Menu *submenu = submenus[i].get();
2790     if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size())
2791       m_max_submenu_name_length = submenu->m_name.size();
2792     if (static_cast<size_t>(m_max_submenu_key_name_length) <
2793         submenu->m_key_name.size())
2794       m_max_submenu_key_name_length = submenu->m_key_name.size();
2795   }
2796 }
2797 
AddSubmenu(const MenuSP & menu_sp)2798 void Menu::AddSubmenu(const MenuSP &menu_sp) {
2799   menu_sp->m_parent = this;
2800   if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size())
2801     m_max_submenu_name_length = menu_sp->m_name.size();
2802   if (static_cast<size_t>(m_max_submenu_key_name_length) <
2803       menu_sp->m_key_name.size())
2804     m_max_submenu_key_name_length = menu_sp->m_key_name.size();
2805   m_submenus.push_back(menu_sp);
2806 }
2807 
DrawMenuTitle(Window & window,bool highlight)2808 void Menu::DrawMenuTitle(Window &window, bool highlight) {
2809   if (m_type == Type::Separator) {
2810     window.MoveCursor(0, window.GetCursorY());
2811     window.PutChar(ACS_LTEE);
2812     int width = window.GetWidth();
2813     if (width > 2) {
2814       width -= 2;
2815       for (int i = 0; i < width; ++i)
2816         window.PutChar(ACS_HLINE);
2817     }
2818     window.PutChar(ACS_RTEE);
2819   } else {
2820     const int shortcut_key = m_key_value;
2821     bool underlined_shortcut = false;
2822     const attr_t highlight_attr = A_REVERSE;
2823     if (highlight)
2824       window.AttributeOn(highlight_attr);
2825     if (llvm::isPrint(shortcut_key)) {
2826       size_t lower_pos = m_name.find(tolower(shortcut_key));
2827       size_t upper_pos = m_name.find(toupper(shortcut_key));
2828       const char *name = m_name.c_str();
2829       size_t pos = std::min<size_t>(lower_pos, upper_pos);
2830       if (pos != std::string::npos) {
2831         underlined_shortcut = true;
2832         if (pos > 0) {
2833           window.PutCString(name, pos);
2834           name += pos;
2835         }
2836         const attr_t shortcut_attr = A_UNDERLINE | A_BOLD;
2837         window.AttributeOn(shortcut_attr);
2838         window.PutChar(name[0]);
2839         window.AttributeOff(shortcut_attr);
2840         name++;
2841         if (name[0])
2842           window.PutCString(name);
2843       }
2844     }
2845 
2846     if (!underlined_shortcut) {
2847       window.PutCString(m_name.c_str());
2848     }
2849 
2850     if (highlight)
2851       window.AttributeOff(highlight_attr);
2852 
2853     if (m_key_name.empty()) {
2854       if (!underlined_shortcut && llvm::isPrint(m_key_value)) {
2855         window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
2856         window.Printf(" (%c)", m_key_value);
2857         window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
2858       }
2859     } else {
2860       window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
2861       window.Printf(" (%s)", m_key_name.c_str());
2862       window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
2863     }
2864   }
2865 }
2866 
WindowDelegateDraw(Window & window,bool force)2867 bool Menu::WindowDelegateDraw(Window &window, bool force) {
2868   Menus &submenus = GetSubmenus();
2869   const size_t num_submenus = submenus.size();
2870   const int selected_idx = GetSelectedSubmenuIndex();
2871   Menu::Type menu_type = GetType();
2872   switch (menu_type) {
2873   case Menu::Type::Bar: {
2874     window.SetBackground(BlackOnWhite);
2875     window.MoveCursor(0, 0);
2876     for (size_t i = 0; i < num_submenus; ++i) {
2877       Menu *menu = submenus[i].get();
2878       if (i > 0)
2879         window.PutChar(' ');
2880       menu->SetStartingColumn(window.GetCursorX());
2881       window.PutCString("| ");
2882       menu->DrawMenuTitle(window, false);
2883     }
2884     window.PutCString(" |");
2885   } break;
2886 
2887   case Menu::Type::Item: {
2888     int y = 1;
2889     int x = 3;
2890     // Draw the menu
2891     int cursor_x = 0;
2892     int cursor_y = 0;
2893     window.Erase();
2894     window.SetBackground(BlackOnWhite);
2895     window.Box();
2896     for (size_t i = 0; i < num_submenus; ++i) {
2897       const bool is_selected = (i == static_cast<size_t>(selected_idx));
2898       window.MoveCursor(x, y + i);
2899       if (is_selected) {
2900         // Remember where we want the cursor to be
2901         cursor_x = x - 1;
2902         cursor_y = y + i;
2903       }
2904       submenus[i]->DrawMenuTitle(window, is_selected);
2905     }
2906     window.MoveCursor(cursor_x, cursor_y);
2907   } break;
2908 
2909   default:
2910   case Menu::Type::Separator:
2911     break;
2912   }
2913   return true; // Drawing handled...
2914 }
2915 
WindowDelegateHandleChar(Window & window,int key)2916 HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) {
2917   HandleCharResult result = eKeyNotHandled;
2918 
2919   Menus &submenus = GetSubmenus();
2920   const size_t num_submenus = submenus.size();
2921   const int selected_idx = GetSelectedSubmenuIndex();
2922   Menu::Type menu_type = GetType();
2923   if (menu_type == Menu::Type::Bar) {
2924     MenuSP run_menu_sp;
2925     switch (key) {
2926     case KEY_DOWN:
2927     case KEY_UP:
2928       // Show last menu or first menu
2929       if (selected_idx < static_cast<int>(num_submenus))
2930         run_menu_sp = submenus[selected_idx];
2931       else if (!submenus.empty())
2932         run_menu_sp = submenus.front();
2933       result = eKeyHandled;
2934       break;
2935 
2936     case KEY_RIGHT:
2937       ++m_selected;
2938       if (m_selected >= static_cast<int>(num_submenus))
2939         m_selected = 0;
2940       if (m_selected < static_cast<int>(num_submenus))
2941         run_menu_sp = submenus[m_selected];
2942       else if (!submenus.empty())
2943         run_menu_sp = submenus.front();
2944       result = eKeyHandled;
2945       break;
2946 
2947     case KEY_LEFT:
2948       --m_selected;
2949       if (m_selected < 0)
2950         m_selected = num_submenus - 1;
2951       if (m_selected < static_cast<int>(num_submenus))
2952         run_menu_sp = submenus[m_selected];
2953       else if (!submenus.empty())
2954         run_menu_sp = submenus.front();
2955       result = eKeyHandled;
2956       break;
2957 
2958     default:
2959       for (size_t i = 0; i < num_submenus; ++i) {
2960         if (submenus[i]->GetKeyValue() == key) {
2961           SetSelectedSubmenuIndex(i);
2962           run_menu_sp = submenus[i];
2963           result = eKeyHandled;
2964           break;
2965         }
2966       }
2967       break;
2968     }
2969 
2970     if (run_menu_sp) {
2971       // Run the action on this menu in case we need to populate the menu with
2972       // dynamic content and also in case check marks, and any other menu
2973       // decorations need to be calculated
2974       if (run_menu_sp->Action() == MenuActionResult::Quit)
2975         return eQuitApplication;
2976 
2977       Rect menu_bounds;
2978       menu_bounds.origin.x = run_menu_sp->GetStartingColumn();
2979       menu_bounds.origin.y = 1;
2980       menu_bounds.size.width = run_menu_sp->GetDrawWidth();
2981       menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2;
2982       if (m_menu_window_sp)
2983         window.GetParent()->RemoveSubWindow(m_menu_window_sp.get());
2984 
2985       m_menu_window_sp = window.GetParent()->CreateSubWindow(
2986           run_menu_sp->GetName().c_str(), menu_bounds, true);
2987       m_menu_window_sp->SetDelegate(run_menu_sp);
2988     }
2989   } else if (menu_type == Menu::Type::Item) {
2990     switch (key) {
2991     case KEY_DOWN:
2992       if (m_submenus.size() > 1) {
2993         const int start_select = m_selected;
2994         while (++m_selected != start_select) {
2995           if (static_cast<size_t>(m_selected) >= num_submenus)
2996             m_selected = 0;
2997           if (m_submenus[m_selected]->GetType() == Type::Separator)
2998             continue;
2999           else
3000             break;
3001         }
3002         return eKeyHandled;
3003       }
3004       break;
3005 
3006     case KEY_UP:
3007       if (m_submenus.size() > 1) {
3008         const int start_select = m_selected;
3009         while (--m_selected != start_select) {
3010           if (m_selected < static_cast<int>(0))
3011             m_selected = num_submenus - 1;
3012           if (m_submenus[m_selected]->GetType() == Type::Separator)
3013             continue;
3014           else
3015             break;
3016         }
3017         return eKeyHandled;
3018       }
3019       break;
3020 
3021     case KEY_RETURN:
3022       if (static_cast<size_t>(selected_idx) < num_submenus) {
3023         if (submenus[selected_idx]->Action() == MenuActionResult::Quit)
3024           return eQuitApplication;
3025         window.GetParent()->RemoveSubWindow(&window);
3026         return eKeyHandled;
3027       }
3028       break;
3029 
3030     case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in
3031                      // case other chars are entered for escaped sequences
3032       window.GetParent()->RemoveSubWindow(&window);
3033       return eKeyHandled;
3034 
3035     default:
3036       for (size_t i = 0; i < num_submenus; ++i) {
3037         Menu *menu = submenus[i].get();
3038         if (menu->GetKeyValue() == key) {
3039           SetSelectedSubmenuIndex(i);
3040           window.GetParent()->RemoveSubWindow(&window);
3041           if (menu->Action() == MenuActionResult::Quit)
3042             return eQuitApplication;
3043           return eKeyHandled;
3044         }
3045       }
3046       break;
3047     }
3048   } else if (menu_type == Menu::Type::Separator) {
3049   }
3050   return result;
3051 }
3052 
3053 class Application {
3054 public:
Application(FILE * in,FILE * out)3055   Application(FILE *in, FILE *out)
3056       : m_window_sp(), m_screen(nullptr), m_in(in), m_out(out) {}
3057 
~Application()3058   ~Application() {
3059     m_window_delegates.clear();
3060     m_window_sp.reset();
3061     if (m_screen) {
3062       ::delscreen(m_screen);
3063       m_screen = nullptr;
3064     }
3065   }
3066 
Initialize()3067   void Initialize() {
3068     m_screen = ::newterm(nullptr, m_out, m_in);
3069     ::start_color();
3070     ::curs_set(0);
3071     ::noecho();
3072     ::keypad(stdscr, TRUE);
3073   }
3074 
Terminate()3075   void Terminate() { ::endwin(); }
3076 
Run(Debugger & debugger)3077   void Run(Debugger &debugger) {
3078     bool done = false;
3079     int delay_in_tenths_of_a_second = 1;
3080 
3081     // Alas the threading model in curses is a bit lame so we need to resort to
3082     // polling every 0.5 seconds. We could poll for stdin ourselves and then
3083     // pass the keys down but then we need to translate all of the escape
3084     // sequences ourselves. So we resort to polling for input because we need
3085     // to receive async process events while in this loop.
3086 
3087     halfdelay(delay_in_tenths_of_a_second); // Poll using some number of tenths
3088                                             // of seconds seconds when calling
3089                                             // Window::GetChar()
3090 
3091     ListenerSP listener_sp(
3092         Listener::MakeListener("lldb.IOHandler.curses.Application"));
3093     ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass());
3094     debugger.EnableForwardEvents(listener_sp);
3095 
3096     m_update_screen = true;
3097 #if defined(__APPLE__)
3098     std::deque<int> escape_chars;
3099 #endif
3100 
3101     while (!done) {
3102       if (m_update_screen) {
3103         m_window_sp->Draw(false);
3104         // All windows should be calling Window::DeferredRefresh() instead of
3105         // Window::Refresh() so we can do a single update and avoid any screen
3106         // blinking
3107         update_panels();
3108 
3109         // Cursor hiding isn't working on MacOSX, so hide it in the top left
3110         // corner
3111         m_window_sp->MoveCursor(0, 0);
3112 
3113         doupdate();
3114         m_update_screen = false;
3115       }
3116 
3117 #if defined(__APPLE__)
3118       // Terminal.app doesn't map its function keys correctly, F1-F4 default
3119       // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if
3120       // possible
3121       int ch;
3122       if (escape_chars.empty())
3123         ch = m_window_sp->GetChar();
3124       else {
3125         ch = escape_chars.front();
3126         escape_chars.pop_front();
3127       }
3128       if (ch == KEY_ESCAPE) {
3129         int ch2 = m_window_sp->GetChar();
3130         if (ch2 == 'O') {
3131           int ch3 = m_window_sp->GetChar();
3132           switch (ch3) {
3133           case 'P':
3134             ch = KEY_F(1);
3135             break;
3136           case 'Q':
3137             ch = KEY_F(2);
3138             break;
3139           case 'R':
3140             ch = KEY_F(3);
3141             break;
3142           case 'S':
3143             ch = KEY_F(4);
3144             break;
3145           default:
3146             escape_chars.push_back(ch2);
3147             if (ch3 != -1)
3148               escape_chars.push_back(ch3);
3149             break;
3150           }
3151         } else if (ch2 != -1)
3152           escape_chars.push_back(ch2);
3153       }
3154 #else
3155       int ch = m_window_sp->GetChar();
3156 
3157 #endif
3158       if (ch == -1) {
3159         if (feof(m_in) || ferror(m_in)) {
3160           done = true;
3161         } else {
3162           // Just a timeout from using halfdelay(), check for events
3163           EventSP event_sp;
3164           while (listener_sp->PeekAtNextEvent()) {
3165             listener_sp->GetEvent(event_sp, std::chrono::seconds(0));
3166 
3167             if (event_sp) {
3168               Broadcaster *broadcaster = event_sp->GetBroadcaster();
3169               if (broadcaster) {
3170                 // uint32_t event_type = event_sp->GetType();
3171                 ConstString broadcaster_class(
3172                     broadcaster->GetBroadcasterClass());
3173                 if (broadcaster_class == broadcaster_class_process) {
3174                   m_update_screen = true;
3175                   continue; // Don't get any key, just update our view
3176                 }
3177               }
3178             }
3179           }
3180         }
3181       } else {
3182         HandleCharResult key_result = m_window_sp->HandleChar(ch);
3183         switch (key_result) {
3184         case eKeyHandled:
3185           m_update_screen = true;
3186           break;
3187         case eKeyNotHandled:
3188           if (ch == 12) { // Ctrl+L, force full redraw
3189             redrawwin(m_window_sp->get());
3190             m_update_screen = true;
3191           }
3192           break;
3193         case eQuitApplication:
3194           done = true;
3195           break;
3196         }
3197       }
3198     }
3199 
3200     debugger.CancelForwardEvents(listener_sp);
3201   }
3202 
GetMainWindow()3203   WindowSP &GetMainWindow() {
3204     if (!m_window_sp)
3205       m_window_sp = std::make_shared<Window>("main", stdscr, false);
3206     return m_window_sp;
3207   }
3208 
TerminalSizeChanged()3209   void TerminalSizeChanged() {
3210     ::endwin();
3211     ::refresh();
3212     Rect content_bounds = m_window_sp->GetFrame();
3213     m_window_sp->SetBounds(content_bounds);
3214     if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar"))
3215       menubar_window_sp->SetBounds(content_bounds.MakeMenuBar());
3216     if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status"))
3217       status_window_sp->SetBounds(content_bounds.MakeStatusBar());
3218 
3219     WindowSP source_window_sp = m_window_sp->FindSubWindow("Source");
3220     WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables");
3221     WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers");
3222     WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads");
3223 
3224     Rect threads_bounds;
3225     Rect source_variables_bounds;
3226     content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
3227                                            threads_bounds);
3228     if (threads_window_sp)
3229       threads_window_sp->SetBounds(threads_bounds);
3230     else
3231       source_variables_bounds = content_bounds;
3232 
3233     Rect source_bounds;
3234     Rect variables_registers_bounds;
3235     source_variables_bounds.HorizontalSplitPercentage(
3236         0.70, source_bounds, variables_registers_bounds);
3237     if (variables_window_sp || registers_window_sp) {
3238       if (variables_window_sp && registers_window_sp) {
3239         Rect variables_bounds;
3240         Rect registers_bounds;
3241         variables_registers_bounds.VerticalSplitPercentage(
3242             0.50, variables_bounds, registers_bounds);
3243         variables_window_sp->SetBounds(variables_bounds);
3244         registers_window_sp->SetBounds(registers_bounds);
3245       } else if (variables_window_sp) {
3246         variables_window_sp->SetBounds(variables_registers_bounds);
3247       } else {
3248         registers_window_sp->SetBounds(variables_registers_bounds);
3249       }
3250     } else {
3251       source_bounds = source_variables_bounds;
3252     }
3253 
3254     source_window_sp->SetBounds(source_bounds);
3255 
3256     touchwin(stdscr);
3257     redrawwin(m_window_sp->get());
3258     m_update_screen = true;
3259   }
3260 
3261 protected:
3262   WindowSP m_window_sp;
3263   WindowDelegates m_window_delegates;
3264   SCREEN *m_screen;
3265   FILE *m_in;
3266   FILE *m_out;
3267   bool m_update_screen = false;
3268 };
3269 
3270 } // namespace curses
3271 
3272 using namespace curses;
3273 
3274 struct Row {
3275   ValueObjectUpdater value;
3276   Row *parent;
3277   // The process stop ID when the children were calculated.
3278   uint32_t children_stop_id = 0;
3279   int row_idx = 0;
3280   int x = 1;
3281   int y = 1;
3282   bool might_have_children;
3283   bool expanded = false;
3284   bool calculated_children = false;
3285   std::vector<Row> children;
3286 
RowRow3287   Row(const ValueObjectSP &v, Row *p)
3288       : value(v), parent(p),
3289         might_have_children(v ? v->MightHaveChildren() : false) {}
3290 
GetDepthRow3291   size_t GetDepth() const {
3292     if (parent)
3293       return 1 + parent->GetDepth();
3294     return 0;
3295   }
3296 
ExpandRow3297   void Expand() { expanded = true; }
3298 
GetChildrenRow3299   std::vector<Row> &GetChildren() {
3300     ProcessSP process_sp = value.GetProcessSP();
3301     auto stop_id = process_sp->GetStopID();
3302     if (process_sp && stop_id != children_stop_id) {
3303       children_stop_id = stop_id;
3304       calculated_children = false;
3305     }
3306     if (!calculated_children) {
3307       children.clear();
3308       calculated_children = true;
3309       ValueObjectSP valobj = value.GetSP();
3310       if (valobj) {
3311         const size_t num_children = valobj->GetNumChildren();
3312         for (size_t i = 0; i < num_children; ++i) {
3313           children.push_back(Row(valobj->GetChildAtIndex(i, true), this));
3314         }
3315       }
3316     }
3317     return children;
3318   }
3319 
UnexpandRow3320   void Unexpand() {
3321     expanded = false;
3322     calculated_children = false;
3323     children.clear();
3324   }
3325 
DrawTreeRow3326   void DrawTree(Window &window) {
3327     if (parent)
3328       parent->DrawTreeForChild(window, this, 0);
3329 
3330     if (might_have_children) {
3331       // It we can get UTF8 characters to work we should try to use the
3332       // "symbol" UTF8 string below
3333       //            const char *symbol = "";
3334       //            if (row.expanded)
3335       //                symbol = "\xe2\x96\xbd ";
3336       //            else
3337       //                symbol = "\xe2\x96\xb7 ";
3338       //            window.PutCString (symbol);
3339 
3340       // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v'
3341       // or '>' character...
3342       //            if (expanded)
3343       //                window.PutChar (ACS_DARROW);
3344       //            else
3345       //                window.PutChar (ACS_RARROW);
3346       // Since we can't find any good looking right arrow/down arrow symbols,
3347       // just use a diamond...
3348       window.PutChar(ACS_DIAMOND);
3349       window.PutChar(ACS_HLINE);
3350     }
3351   }
3352 
DrawTreeForChildRow3353   void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) {
3354     if (parent)
3355       parent->DrawTreeForChild(window, this, reverse_depth + 1);
3356 
3357     if (&GetChildren().back() == child) {
3358       // Last child
3359       if (reverse_depth == 0) {
3360         window.PutChar(ACS_LLCORNER);
3361         window.PutChar(ACS_HLINE);
3362       } else {
3363         window.PutChar(' ');
3364         window.PutChar(' ');
3365       }
3366     } else {
3367       if (reverse_depth == 0) {
3368         window.PutChar(ACS_LTEE);
3369         window.PutChar(ACS_HLINE);
3370       } else {
3371         window.PutChar(ACS_VLINE);
3372         window.PutChar(' ');
3373       }
3374     }
3375   }
3376 };
3377 
3378 struct DisplayOptions {
3379   bool show_types;
3380 };
3381 
3382 class TreeItem;
3383 
3384 class TreeDelegate {
3385 public:
3386   TreeDelegate() = default;
3387   virtual ~TreeDelegate() = default;
3388 
3389   virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0;
3390   virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0;
TreeDelegateUpdateSelection(TreeItem & root,int & selection_index,TreeItem * & selected_item)3391   virtual void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
3392                                            TreeItem *&selected_item) {
3393     return;
3394   }
3395   virtual bool TreeDelegateItemSelected(
3396       TreeItem &item) = 0; // Return true if we need to update views
TreeDelegateExpandRootByDefault()3397   virtual bool TreeDelegateExpandRootByDefault() { return false; }
3398 };
3399 
3400 typedef std::shared_ptr<TreeDelegate> TreeDelegateSP;
3401 
3402 class TreeItem {
3403 public:
TreeItem(TreeItem * parent,TreeDelegate & delegate,bool might_have_children)3404   TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children)
3405       : m_parent(parent), m_delegate(delegate), m_user_data(nullptr),
3406         m_identifier(0), m_row_idx(-1), m_children(),
3407         m_might_have_children(might_have_children), m_is_expanded(false) {
3408     if (m_parent == nullptr)
3409       m_is_expanded = m_delegate.TreeDelegateExpandRootByDefault();
3410   }
3411 
operator =(const TreeItem & rhs)3412   TreeItem &operator=(const TreeItem &rhs) {
3413     if (this != &rhs) {
3414       m_parent = rhs.m_parent;
3415       m_delegate = rhs.m_delegate;
3416       m_user_data = rhs.m_user_data;
3417       m_identifier = rhs.m_identifier;
3418       m_row_idx = rhs.m_row_idx;
3419       m_children = rhs.m_children;
3420       m_might_have_children = rhs.m_might_have_children;
3421       m_is_expanded = rhs.m_is_expanded;
3422     }
3423     return *this;
3424   }
3425 
3426   TreeItem(const TreeItem &) = default;
3427 
GetDepth() const3428   size_t GetDepth() const {
3429     if (m_parent)
3430       return 1 + m_parent->GetDepth();
3431     return 0;
3432   }
3433 
GetRowIndex() const3434   int GetRowIndex() const { return m_row_idx; }
3435 
ClearChildren()3436   void ClearChildren() { m_children.clear(); }
3437 
Resize(size_t n,const TreeItem & t)3438   void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); }
3439 
operator [](size_t i)3440   TreeItem &operator[](size_t i) { return m_children[i]; }
3441 
SetRowIndex(int row_idx)3442   void SetRowIndex(int row_idx) { m_row_idx = row_idx; }
3443 
GetNumChildren()3444   size_t GetNumChildren() {
3445     m_delegate.TreeDelegateGenerateChildren(*this);
3446     return m_children.size();
3447   }
3448 
ItemWasSelected()3449   void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); }
3450 
CalculateRowIndexes(int & row_idx)3451   void CalculateRowIndexes(int &row_idx) {
3452     SetRowIndex(row_idx);
3453     ++row_idx;
3454 
3455     const bool expanded = IsExpanded();
3456 
3457     // The root item must calculate its children, or we must calculate the
3458     // number of children if the item is expanded
3459     if (m_parent == nullptr || expanded)
3460       GetNumChildren();
3461 
3462     for (auto &item : m_children) {
3463       if (expanded)
3464         item.CalculateRowIndexes(row_idx);
3465       else
3466         item.SetRowIndex(-1);
3467     }
3468   }
3469 
GetParent()3470   TreeItem *GetParent() { return m_parent; }
3471 
IsExpanded() const3472   bool IsExpanded() const { return m_is_expanded; }
3473 
Expand()3474   void Expand() { m_is_expanded = true; }
3475 
Unexpand()3476   void Unexpand() { m_is_expanded = false; }
3477 
Draw(Window & window,const int first_visible_row,const uint32_t selected_row_idx,int & row_idx,int & num_rows_left)3478   bool Draw(Window &window, const int first_visible_row,
3479             const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) {
3480     if (num_rows_left <= 0)
3481       return false;
3482 
3483     if (m_row_idx >= first_visible_row) {
3484       window.MoveCursor(2, row_idx + 1);
3485 
3486       if (m_parent)
3487         m_parent->DrawTreeForChild(window, this, 0);
3488 
3489       if (m_might_have_children) {
3490         // It we can get UTF8 characters to work we should try to use the
3491         // "symbol" UTF8 string below
3492         //            const char *symbol = "";
3493         //            if (row.expanded)
3494         //                symbol = "\xe2\x96\xbd ";
3495         //            else
3496         //                symbol = "\xe2\x96\xb7 ";
3497         //            window.PutCString (symbol);
3498 
3499         // The ACS_DARROW and ACS_RARROW don't look very nice they are just a
3500         // 'v' or '>' character...
3501         //            if (expanded)
3502         //                window.PutChar (ACS_DARROW);
3503         //            else
3504         //                window.PutChar (ACS_RARROW);
3505         // Since we can't find any good looking right arrow/down arrow symbols,
3506         // just use a diamond...
3507         window.PutChar(ACS_DIAMOND);
3508         window.PutChar(ACS_HLINE);
3509       }
3510       bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) &&
3511                        window.IsActive();
3512 
3513       if (highlight)
3514         window.AttributeOn(A_REVERSE);
3515 
3516       m_delegate.TreeDelegateDrawTreeItem(*this, window);
3517 
3518       if (highlight)
3519         window.AttributeOff(A_REVERSE);
3520       ++row_idx;
3521       --num_rows_left;
3522     }
3523 
3524     if (num_rows_left <= 0)
3525       return false; // We are done drawing...
3526 
3527     if (IsExpanded()) {
3528       for (auto &item : m_children) {
3529         // If we displayed all the rows and item.Draw() returns false we are
3530         // done drawing and can exit this for loop
3531         if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx,
3532                        num_rows_left))
3533           break;
3534       }
3535     }
3536     return num_rows_left >= 0; // Return true if not done drawing yet
3537   }
3538 
DrawTreeForChild(Window & window,TreeItem * child,uint32_t reverse_depth)3539   void DrawTreeForChild(Window &window, TreeItem *child,
3540                         uint32_t reverse_depth) {
3541     if (m_parent)
3542       m_parent->DrawTreeForChild(window, this, reverse_depth + 1);
3543 
3544     if (&m_children.back() == child) {
3545       // Last child
3546       if (reverse_depth == 0) {
3547         window.PutChar(ACS_LLCORNER);
3548         window.PutChar(ACS_HLINE);
3549       } else {
3550         window.PutChar(' ');
3551         window.PutChar(' ');
3552       }
3553     } else {
3554       if (reverse_depth == 0) {
3555         window.PutChar(ACS_LTEE);
3556         window.PutChar(ACS_HLINE);
3557       } else {
3558         window.PutChar(ACS_VLINE);
3559         window.PutChar(' ');
3560       }
3561     }
3562   }
3563 
GetItemForRowIndex(uint32_t row_idx)3564   TreeItem *GetItemForRowIndex(uint32_t row_idx) {
3565     if (static_cast<uint32_t>(m_row_idx) == row_idx)
3566       return this;
3567     if (m_children.empty())
3568       return nullptr;
3569     if (IsExpanded()) {
3570       for (auto &item : m_children) {
3571         TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx);
3572         if (selected_item_ptr)
3573           return selected_item_ptr;
3574       }
3575     }
3576     return nullptr;
3577   }
3578 
GetUserData() const3579   void *GetUserData() const { return m_user_data; }
3580 
SetUserData(void * user_data)3581   void SetUserData(void *user_data) { m_user_data = user_data; }
3582 
GetIdentifier() const3583   uint64_t GetIdentifier() const { return m_identifier; }
3584 
SetIdentifier(uint64_t identifier)3585   void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
3586 
SetMightHaveChildren(bool b)3587   void SetMightHaveChildren(bool b) { m_might_have_children = b; }
3588 
3589 protected:
3590   TreeItem *m_parent;
3591   TreeDelegate &m_delegate;
3592   void *m_user_data;
3593   uint64_t m_identifier;
3594   int m_row_idx; // Zero based visible row index, -1 if not visible or for the
3595                  // root item
3596   std::vector<TreeItem> m_children;
3597   bool m_might_have_children;
3598   bool m_is_expanded;
3599 };
3600 
3601 class TreeWindowDelegate : public WindowDelegate {
3602 public:
TreeWindowDelegate(Debugger & debugger,const TreeDelegateSP & delegate_sp)3603   TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp)
3604       : m_debugger(debugger), m_delegate_sp(delegate_sp),
3605         m_root(nullptr, *delegate_sp, true), m_selected_item(nullptr),
3606         m_num_rows(0), m_selected_row_idx(0), m_first_visible_row(0),
3607         m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {}
3608 
NumVisibleRows() const3609   int NumVisibleRows() const { return m_max_y - m_min_y; }
3610 
WindowDelegateDraw(Window & window,bool force)3611   bool WindowDelegateDraw(Window &window, bool force) override {
3612     ExecutionContext exe_ctx(
3613         m_debugger.GetCommandInterpreter().GetExecutionContext());
3614     Process *process = exe_ctx.GetProcessPtr();
3615 
3616     bool display_content = false;
3617     if (process) {
3618       StateType state = process->GetState();
3619       if (StateIsStoppedState(state, true)) {
3620         // We are stopped, so it is ok to
3621         display_content = true;
3622       } else if (StateIsRunningState(state)) {
3623         return true; // Don't do any updating when we are running
3624       }
3625     }
3626 
3627     m_min_x = 2;
3628     m_min_y = 1;
3629     m_max_x = window.GetWidth() - 1;
3630     m_max_y = window.GetHeight() - 1;
3631 
3632     window.Erase();
3633     window.DrawTitleBox(window.GetName());
3634 
3635     if (display_content) {
3636       const int num_visible_rows = NumVisibleRows();
3637       m_num_rows = 0;
3638       m_root.CalculateRowIndexes(m_num_rows);
3639       m_delegate_sp->TreeDelegateUpdateSelection(m_root, m_selected_row_idx,
3640                                                  m_selected_item);
3641 
3642       // If we unexpanded while having something selected our total number of
3643       // rows is less than the num visible rows, then make sure we show all the
3644       // rows by setting the first visible row accordingly.
3645       if (m_first_visible_row > 0 && m_num_rows < num_visible_rows)
3646         m_first_visible_row = 0;
3647 
3648       // Make sure the selected row is always visible
3649       if (m_selected_row_idx < m_first_visible_row)
3650         m_first_visible_row = m_selected_row_idx;
3651       else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
3652         m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
3653 
3654       int row_idx = 0;
3655       int num_rows_left = num_visible_rows;
3656       m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx,
3657                   num_rows_left);
3658       // Get the selected row
3659       m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3660     } else {
3661       m_selected_item = nullptr;
3662     }
3663 
3664     return true; // Drawing handled
3665   }
3666 
WindowDelegateGetHelpText()3667   const char *WindowDelegateGetHelpText() override {
3668     return "Thread window keyboard shortcuts:";
3669   }
3670 
WindowDelegateGetKeyHelp()3671   KeyHelp *WindowDelegateGetKeyHelp() override {
3672     static curses::KeyHelp g_source_view_key_help[] = {
3673         {KEY_UP, "Select previous item"},
3674         {KEY_DOWN, "Select next item"},
3675         {KEY_RIGHT, "Expand the selected item"},
3676         {KEY_LEFT,
3677          "Unexpand the selected item or select parent if not expanded"},
3678         {KEY_PPAGE, "Page up"},
3679         {KEY_NPAGE, "Page down"},
3680         {'h', "Show help dialog"},
3681         {' ', "Toggle item expansion"},
3682         {',', "Page up"},
3683         {'.', "Page down"},
3684         {'\0', nullptr}};
3685     return g_source_view_key_help;
3686   }
3687 
WindowDelegateHandleChar(Window & window,int c)3688   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
3689     switch (c) {
3690     case ',':
3691     case KEY_PPAGE:
3692       // Page up key
3693       if (m_first_visible_row > 0) {
3694         if (m_first_visible_row > m_max_y)
3695           m_first_visible_row -= m_max_y;
3696         else
3697           m_first_visible_row = 0;
3698         m_selected_row_idx = m_first_visible_row;
3699         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3700         if (m_selected_item)
3701           m_selected_item->ItemWasSelected();
3702       }
3703       return eKeyHandled;
3704 
3705     case '.':
3706     case KEY_NPAGE:
3707       // Page down key
3708       if (m_num_rows > m_max_y) {
3709         if (m_first_visible_row + m_max_y < m_num_rows) {
3710           m_first_visible_row += m_max_y;
3711           m_selected_row_idx = m_first_visible_row;
3712           m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3713           if (m_selected_item)
3714             m_selected_item->ItemWasSelected();
3715         }
3716       }
3717       return eKeyHandled;
3718 
3719     case KEY_UP:
3720       if (m_selected_row_idx > 0) {
3721         --m_selected_row_idx;
3722         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3723         if (m_selected_item)
3724           m_selected_item->ItemWasSelected();
3725       }
3726       return eKeyHandled;
3727 
3728     case KEY_DOWN:
3729       if (m_selected_row_idx + 1 < m_num_rows) {
3730         ++m_selected_row_idx;
3731         m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3732         if (m_selected_item)
3733           m_selected_item->ItemWasSelected();
3734       }
3735       return eKeyHandled;
3736 
3737     case KEY_RIGHT:
3738       if (m_selected_item) {
3739         if (!m_selected_item->IsExpanded())
3740           m_selected_item->Expand();
3741       }
3742       return eKeyHandled;
3743 
3744     case KEY_LEFT:
3745       if (m_selected_item) {
3746         if (m_selected_item->IsExpanded())
3747           m_selected_item->Unexpand();
3748         else if (m_selected_item->GetParent()) {
3749           m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex();
3750           m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
3751           if (m_selected_item)
3752             m_selected_item->ItemWasSelected();
3753         }
3754       }
3755       return eKeyHandled;
3756 
3757     case ' ':
3758       // Toggle expansion state when SPACE is pressed
3759       if (m_selected_item) {
3760         if (m_selected_item->IsExpanded())
3761           m_selected_item->Unexpand();
3762         else
3763           m_selected_item->Expand();
3764       }
3765       return eKeyHandled;
3766 
3767     case 'h':
3768       window.CreateHelpSubwindow();
3769       return eKeyHandled;
3770 
3771     default:
3772       break;
3773     }
3774     return eKeyNotHandled;
3775   }
3776 
3777 protected:
3778   Debugger &m_debugger;
3779   TreeDelegateSP m_delegate_sp;
3780   TreeItem m_root;
3781   TreeItem *m_selected_item;
3782   int m_num_rows;
3783   int m_selected_row_idx;
3784   int m_first_visible_row;
3785   int m_min_x;
3786   int m_min_y;
3787   int m_max_x;
3788   int m_max_y;
3789 };
3790 
3791 class FrameTreeDelegate : public TreeDelegate {
3792 public:
FrameTreeDelegate()3793   FrameTreeDelegate() : TreeDelegate() {
3794     FormatEntity::Parse(
3795         "frame #${frame.index}: {${function.name}${function.pc-offset}}}",
3796         m_format);
3797   }
3798 
3799   ~FrameTreeDelegate() override = default;
3800 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)3801   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
3802     Thread *thread = (Thread *)item.GetUserData();
3803     if (thread) {
3804       const uint64_t frame_idx = item.GetIdentifier();
3805       StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx);
3806       if (frame_sp) {
3807         StreamString strm;
3808         const SymbolContext &sc =
3809             frame_sp->GetSymbolContext(eSymbolContextEverything);
3810         ExecutionContext exe_ctx(frame_sp);
3811         if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr,
3812                                  nullptr, false, false)) {
3813           int right_pad = 1;
3814           window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
3815         }
3816       }
3817     }
3818   }
3819 
TreeDelegateGenerateChildren(TreeItem & item)3820   void TreeDelegateGenerateChildren(TreeItem &item) override {
3821     // No children for frames yet...
3822   }
3823 
TreeDelegateItemSelected(TreeItem & item)3824   bool TreeDelegateItemSelected(TreeItem &item) override {
3825     Thread *thread = (Thread *)item.GetUserData();
3826     if (thread) {
3827       thread->GetProcess()->GetThreadList().SetSelectedThreadByID(
3828           thread->GetID());
3829       const uint64_t frame_idx = item.GetIdentifier();
3830       thread->SetSelectedFrameByIndex(frame_idx);
3831       return true;
3832     }
3833     return false;
3834   }
3835 
3836 protected:
3837   FormatEntity::Entry m_format;
3838 };
3839 
3840 class ThreadTreeDelegate : public TreeDelegate {
3841 public:
ThreadTreeDelegate(Debugger & debugger)3842   ThreadTreeDelegate(Debugger &debugger)
3843       : TreeDelegate(), m_debugger(debugger), m_tid(LLDB_INVALID_THREAD_ID),
3844         m_stop_id(UINT32_MAX) {
3845     FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop "
3846                         "reason = ${thread.stop-reason}}",
3847                         m_format);
3848   }
3849 
3850   ~ThreadTreeDelegate() override = default;
3851 
GetProcess()3852   ProcessSP GetProcess() {
3853     return m_debugger.GetCommandInterpreter()
3854         .GetExecutionContext()
3855         .GetProcessSP();
3856   }
3857 
GetThread(const TreeItem & item)3858   ThreadSP GetThread(const TreeItem &item) {
3859     ProcessSP process_sp = GetProcess();
3860     if (process_sp)
3861       return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier());
3862     return ThreadSP();
3863   }
3864 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)3865   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
3866     ThreadSP thread_sp = GetThread(item);
3867     if (thread_sp) {
3868       StreamString strm;
3869       ExecutionContext exe_ctx(thread_sp);
3870       if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
3871                                nullptr, false, false)) {
3872         int right_pad = 1;
3873         window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
3874       }
3875     }
3876   }
3877 
TreeDelegateGenerateChildren(TreeItem & item)3878   void TreeDelegateGenerateChildren(TreeItem &item) override {
3879     ProcessSP process_sp = GetProcess();
3880     if (process_sp && process_sp->IsAlive()) {
3881       StateType state = process_sp->GetState();
3882       if (StateIsStoppedState(state, true)) {
3883         ThreadSP thread_sp = GetThread(item);
3884         if (thread_sp) {
3885           if (m_stop_id == process_sp->GetStopID() &&
3886               thread_sp->GetID() == m_tid)
3887             return; // Children are already up to date
3888           if (!m_frame_delegate_sp) {
3889             // Always expand the thread item the first time we show it
3890             m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>();
3891           }
3892 
3893           m_stop_id = process_sp->GetStopID();
3894           m_tid = thread_sp->GetID();
3895 
3896           TreeItem t(&item, *m_frame_delegate_sp, false);
3897           size_t num_frames = thread_sp->GetStackFrameCount();
3898           item.Resize(num_frames, t);
3899           for (size_t i = 0; i < num_frames; ++i) {
3900             item[i].SetUserData(thread_sp.get());
3901             item[i].SetIdentifier(i);
3902           }
3903         }
3904         return;
3905       }
3906     }
3907     item.ClearChildren();
3908   }
3909 
TreeDelegateItemSelected(TreeItem & item)3910   bool TreeDelegateItemSelected(TreeItem &item) override {
3911     ProcessSP process_sp = GetProcess();
3912     if (process_sp && process_sp->IsAlive()) {
3913       StateType state = process_sp->GetState();
3914       if (StateIsStoppedState(state, true)) {
3915         ThreadSP thread_sp = GetThread(item);
3916         if (thread_sp) {
3917           ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList();
3918           std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex());
3919           ThreadSP selected_thread_sp = thread_list.GetSelectedThread();
3920           if (selected_thread_sp->GetID() != thread_sp->GetID()) {
3921             thread_list.SetSelectedThreadByID(thread_sp->GetID());
3922             return true;
3923           }
3924         }
3925       }
3926     }
3927     return false;
3928   }
3929 
3930 protected:
3931   Debugger &m_debugger;
3932   std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp;
3933   lldb::user_id_t m_tid;
3934   uint32_t m_stop_id;
3935   FormatEntity::Entry m_format;
3936 };
3937 
3938 class ThreadsTreeDelegate : public TreeDelegate {
3939 public:
ThreadsTreeDelegate(Debugger & debugger)3940   ThreadsTreeDelegate(Debugger &debugger)
3941       : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger),
3942         m_stop_id(UINT32_MAX), m_update_selection(false) {
3943     FormatEntity::Parse("process ${process.id}{, name = ${process.name}}",
3944                         m_format);
3945   }
3946 
3947   ~ThreadsTreeDelegate() override = default;
3948 
GetProcess()3949   ProcessSP GetProcess() {
3950     return m_debugger.GetCommandInterpreter()
3951         .GetExecutionContext()
3952         .GetProcessSP();
3953   }
3954 
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)3955   void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
3956     ProcessSP process_sp = GetProcess();
3957     if (process_sp && process_sp->IsAlive()) {
3958       StreamString strm;
3959       ExecutionContext exe_ctx(process_sp);
3960       if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
3961                                nullptr, false, false)) {
3962         int right_pad = 1;
3963         window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
3964       }
3965     }
3966   }
3967 
TreeDelegateGenerateChildren(TreeItem & item)3968   void TreeDelegateGenerateChildren(TreeItem &item) override {
3969     ProcessSP process_sp = GetProcess();
3970     m_update_selection = false;
3971     if (process_sp && process_sp->IsAlive()) {
3972       StateType state = process_sp->GetState();
3973       if (StateIsStoppedState(state, true)) {
3974         const uint32_t stop_id = process_sp->GetStopID();
3975         if (m_stop_id == stop_id)
3976           return; // Children are already up to date
3977 
3978         m_stop_id = stop_id;
3979         m_update_selection = true;
3980 
3981         if (!m_thread_delegate_sp) {
3982           // Always expand the thread item the first time we show it
3983           // item.Expand();
3984           m_thread_delegate_sp =
3985               std::make_shared<ThreadTreeDelegate>(m_debugger);
3986         }
3987 
3988         TreeItem t(&item, *m_thread_delegate_sp, false);
3989         ThreadList &threads = process_sp->GetThreadList();
3990         std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
3991         ThreadSP selected_thread = threads.GetSelectedThread();
3992         size_t num_threads = threads.GetSize();
3993         item.Resize(num_threads, t);
3994         for (size_t i = 0; i < num_threads; ++i) {
3995           ThreadSP thread = threads.GetThreadAtIndex(i);
3996           item[i].SetIdentifier(thread->GetID());
3997           item[i].SetMightHaveChildren(true);
3998           if (selected_thread->GetID() == thread->GetID())
3999             item[i].Expand();
4000         }
4001         return;
4002       }
4003     }
4004     item.ClearChildren();
4005   }
4006 
TreeDelegateUpdateSelection(TreeItem & root,int & selection_index,TreeItem * & selected_item)4007   void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
4008                                    TreeItem *&selected_item) override {
4009     if (!m_update_selection)
4010       return;
4011 
4012     ProcessSP process_sp = GetProcess();
4013     if (!(process_sp && process_sp->IsAlive()))
4014       return;
4015 
4016     StateType state = process_sp->GetState();
4017     if (!StateIsStoppedState(state, true))
4018       return;
4019 
4020     ThreadList &threads = process_sp->GetThreadList();
4021     std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
4022     ThreadSP selected_thread = threads.GetSelectedThread();
4023     size_t num_threads = threads.GetSize();
4024     for (size_t i = 0; i < num_threads; ++i) {
4025       ThreadSP thread = threads.GetThreadAtIndex(i);
4026       if (selected_thread->GetID() == thread->GetID()) {
4027         selected_item = &root[i][thread->GetSelectedFrameIndex()];
4028         selection_index = selected_item->GetRowIndex();
4029         return;
4030       }
4031     }
4032   }
4033 
TreeDelegateItemSelected(TreeItem & item)4034   bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
4035 
TreeDelegateExpandRootByDefault()4036   bool TreeDelegateExpandRootByDefault() override { return true; }
4037 
4038 protected:
4039   std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp;
4040   Debugger &m_debugger;
4041   uint32_t m_stop_id;
4042   bool m_update_selection;
4043   FormatEntity::Entry m_format;
4044 };
4045 
4046 class ValueObjectListDelegate : public WindowDelegate {
4047 public:
ValueObjectListDelegate()4048   ValueObjectListDelegate() : m_rows() {}
4049 
ValueObjectListDelegate(ValueObjectList & valobj_list)4050   ValueObjectListDelegate(ValueObjectList &valobj_list)
4051       : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0),
4052         m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) {
4053     SetValues(valobj_list);
4054   }
4055 
4056   ~ValueObjectListDelegate() override = default;
4057 
SetValues(ValueObjectList & valobj_list)4058   void SetValues(ValueObjectList &valobj_list) {
4059     m_selected_row = nullptr;
4060     m_selected_row_idx = 0;
4061     m_first_visible_row = 0;
4062     m_num_rows = 0;
4063     m_rows.clear();
4064     for (auto &valobj_sp : valobj_list.GetObjects())
4065       m_rows.push_back(Row(valobj_sp, nullptr));
4066   }
4067 
WindowDelegateDraw(Window & window,bool force)4068   bool WindowDelegateDraw(Window &window, bool force) override {
4069     m_num_rows = 0;
4070     m_min_x = 2;
4071     m_min_y = 1;
4072     m_max_x = window.GetWidth() - 1;
4073     m_max_y = window.GetHeight() - 1;
4074 
4075     window.Erase();
4076     window.DrawTitleBox(window.GetName());
4077 
4078     const int num_visible_rows = NumVisibleRows();
4079     const int num_rows = CalculateTotalNumberRows(m_rows);
4080 
4081     // If we unexpanded while having something selected our total number of
4082     // rows is less than the num visible rows, then make sure we show all the
4083     // rows by setting the first visible row accordingly.
4084     if (m_first_visible_row > 0 && num_rows < num_visible_rows)
4085       m_first_visible_row = 0;
4086 
4087     // Make sure the selected row is always visible
4088     if (m_selected_row_idx < m_first_visible_row)
4089       m_first_visible_row = m_selected_row_idx;
4090     else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
4091       m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
4092 
4093     DisplayRows(window, m_rows, g_options);
4094 
4095     // Get the selected row
4096     m_selected_row = GetRowForRowIndex(m_selected_row_idx);
4097     // Keep the cursor on the selected row so the highlight and the cursor are
4098     // always on the same line
4099     if (m_selected_row)
4100       window.MoveCursor(m_selected_row->x, m_selected_row->y);
4101 
4102     return true; // Drawing handled
4103   }
4104 
WindowDelegateGetKeyHelp()4105   KeyHelp *WindowDelegateGetKeyHelp() override {
4106     static curses::KeyHelp g_source_view_key_help[] = {
4107         {KEY_UP, "Select previous item"},
4108         {KEY_DOWN, "Select next item"},
4109         {KEY_RIGHT, "Expand selected item"},
4110         {KEY_LEFT, "Unexpand selected item or select parent if not expanded"},
4111         {KEY_PPAGE, "Page up"},
4112         {KEY_NPAGE, "Page down"},
4113         {'A', "Format as annotated address"},
4114         {'b', "Format as binary"},
4115         {'B', "Format as hex bytes with ASCII"},
4116         {'c', "Format as character"},
4117         {'d', "Format as a signed integer"},
4118         {'D', "Format selected value using the default format for the type"},
4119         {'f', "Format as float"},
4120         {'h', "Show help dialog"},
4121         {'i', "Format as instructions"},
4122         {'o', "Format as octal"},
4123         {'p', "Format as pointer"},
4124         {'s', "Format as C string"},
4125         {'t', "Toggle showing/hiding type names"},
4126         {'u', "Format as an unsigned integer"},
4127         {'x', "Format as hex"},
4128         {'X', "Format as uppercase hex"},
4129         {' ', "Toggle item expansion"},
4130         {',', "Page up"},
4131         {'.', "Page down"},
4132         {'\0', nullptr}};
4133     return g_source_view_key_help;
4134   }
4135 
WindowDelegateHandleChar(Window & window,int c)4136   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
4137     switch (c) {
4138     case 'x':
4139     case 'X':
4140     case 'o':
4141     case 's':
4142     case 'u':
4143     case 'd':
4144     case 'D':
4145     case 'i':
4146     case 'A':
4147     case 'p':
4148     case 'c':
4149     case 'b':
4150     case 'B':
4151     case 'f':
4152       // Change the format for the currently selected item
4153       if (m_selected_row) {
4154         auto valobj_sp = m_selected_row->value.GetSP();
4155         if (valobj_sp)
4156           valobj_sp->SetFormat(FormatForChar(c));
4157       }
4158       return eKeyHandled;
4159 
4160     case 't':
4161       // Toggle showing type names
4162       g_options.show_types = !g_options.show_types;
4163       return eKeyHandled;
4164 
4165     case ',':
4166     case KEY_PPAGE:
4167       // Page up key
4168       if (m_first_visible_row > 0) {
4169         if (static_cast<int>(m_first_visible_row) > m_max_y)
4170           m_first_visible_row -= m_max_y;
4171         else
4172           m_first_visible_row = 0;
4173         m_selected_row_idx = m_first_visible_row;
4174       }
4175       return eKeyHandled;
4176 
4177     case '.':
4178     case KEY_NPAGE:
4179       // Page down key
4180       if (m_num_rows > static_cast<size_t>(m_max_y)) {
4181         if (m_first_visible_row + m_max_y < m_num_rows) {
4182           m_first_visible_row += m_max_y;
4183           m_selected_row_idx = m_first_visible_row;
4184         }
4185       }
4186       return eKeyHandled;
4187 
4188     case KEY_UP:
4189       if (m_selected_row_idx > 0)
4190         --m_selected_row_idx;
4191       return eKeyHandled;
4192 
4193     case KEY_DOWN:
4194       if (m_selected_row_idx + 1 < m_num_rows)
4195         ++m_selected_row_idx;
4196       return eKeyHandled;
4197 
4198     case KEY_RIGHT:
4199       if (m_selected_row) {
4200         if (!m_selected_row->expanded)
4201           m_selected_row->Expand();
4202       }
4203       return eKeyHandled;
4204 
4205     case KEY_LEFT:
4206       if (m_selected_row) {
4207         if (m_selected_row->expanded)
4208           m_selected_row->Unexpand();
4209         else if (m_selected_row->parent)
4210           m_selected_row_idx = m_selected_row->parent->row_idx;
4211       }
4212       return eKeyHandled;
4213 
4214     case ' ':
4215       // Toggle expansion state when SPACE is pressed
4216       if (m_selected_row) {
4217         if (m_selected_row->expanded)
4218           m_selected_row->Unexpand();
4219         else
4220           m_selected_row->Expand();
4221       }
4222       return eKeyHandled;
4223 
4224     case 'h':
4225       window.CreateHelpSubwindow();
4226       return eKeyHandled;
4227 
4228     default:
4229       break;
4230     }
4231     return eKeyNotHandled;
4232   }
4233 
4234 protected:
4235   std::vector<Row> m_rows;
4236   Row *m_selected_row = nullptr;
4237   uint32_t m_selected_row_idx = 0;
4238   uint32_t m_first_visible_row = 0;
4239   uint32_t m_num_rows = 0;
4240   int m_min_x;
4241   int m_min_y;
4242   int m_max_x = 0;
4243   int m_max_y = 0;
4244 
FormatForChar(int c)4245   static Format FormatForChar(int c) {
4246     switch (c) {
4247     case 'x':
4248       return eFormatHex;
4249     case 'X':
4250       return eFormatHexUppercase;
4251     case 'o':
4252       return eFormatOctal;
4253     case 's':
4254       return eFormatCString;
4255     case 'u':
4256       return eFormatUnsigned;
4257     case 'd':
4258       return eFormatDecimal;
4259     case 'D':
4260       return eFormatDefault;
4261     case 'i':
4262       return eFormatInstruction;
4263     case 'A':
4264       return eFormatAddressInfo;
4265     case 'p':
4266       return eFormatPointer;
4267     case 'c':
4268       return eFormatChar;
4269     case 'b':
4270       return eFormatBinary;
4271     case 'B':
4272       return eFormatBytesWithASCII;
4273     case 'f':
4274       return eFormatFloat;
4275     }
4276     return eFormatDefault;
4277   }
4278 
DisplayRowObject(Window & window,Row & row,DisplayOptions & options,bool highlight,bool last_child)4279   bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options,
4280                         bool highlight, bool last_child) {
4281     ValueObject *valobj = row.value.GetSP().get();
4282 
4283     if (valobj == nullptr)
4284       return false;
4285 
4286     const char *type_name =
4287         options.show_types ? valobj->GetTypeName().GetCString() : nullptr;
4288     const char *name = valobj->GetName().GetCString();
4289     const char *value = valobj->GetValueAsCString();
4290     const char *summary = valobj->GetSummaryAsCString();
4291 
4292     window.MoveCursor(row.x, row.y);
4293 
4294     row.DrawTree(window);
4295 
4296     if (highlight)
4297       window.AttributeOn(A_REVERSE);
4298 
4299     if (type_name && type_name[0])
4300       window.PrintfTruncated(1, "(%s) ", type_name);
4301 
4302     if (name && name[0])
4303       window.PutCStringTruncated(1, name);
4304 
4305     attr_t changd_attr = 0;
4306     if (valobj->GetValueDidChange())
4307       changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD;
4308 
4309     if (value && value[0]) {
4310       window.PutCStringTruncated(1, " = ");
4311       if (changd_attr)
4312         window.AttributeOn(changd_attr);
4313       window.PutCStringTruncated(1, value);
4314       if (changd_attr)
4315         window.AttributeOff(changd_attr);
4316     }
4317 
4318     if (summary && summary[0]) {
4319       window.PutCStringTruncated(1, " ");
4320       if (changd_attr)
4321         window.AttributeOn(changd_attr);
4322       window.PutCStringTruncated(1, summary);
4323       if (changd_attr)
4324         window.AttributeOff(changd_attr);
4325     }
4326 
4327     if (highlight)
4328       window.AttributeOff(A_REVERSE);
4329 
4330     return true;
4331   }
4332 
DisplayRows(Window & window,std::vector<Row> & rows,DisplayOptions & options)4333   void DisplayRows(Window &window, std::vector<Row> &rows,
4334                    DisplayOptions &options) {
4335     // >   0x25B7
4336     // \/  0x25BD
4337 
4338     bool window_is_active = window.IsActive();
4339     for (auto &row : rows) {
4340       const bool last_child = row.parent && &rows[rows.size() - 1] == &row;
4341       // Save the row index in each Row structure
4342       row.row_idx = m_num_rows;
4343       if ((m_num_rows >= m_first_visible_row) &&
4344           ((m_num_rows - m_first_visible_row) <
4345            static_cast<size_t>(NumVisibleRows()))) {
4346         row.x = m_min_x;
4347         row.y = m_num_rows - m_first_visible_row + 1;
4348         if (DisplayRowObject(window, row, options,
4349                              window_is_active &&
4350                                  m_num_rows == m_selected_row_idx,
4351                              last_child)) {
4352           ++m_num_rows;
4353         } else {
4354           row.x = 0;
4355           row.y = 0;
4356         }
4357       } else {
4358         row.x = 0;
4359         row.y = 0;
4360         ++m_num_rows;
4361       }
4362 
4363       auto &children = row.GetChildren();
4364       if (row.expanded && !children.empty()) {
4365         DisplayRows(window, children, options);
4366       }
4367     }
4368   }
4369 
CalculateTotalNumberRows(std::vector<Row> & rows)4370   int CalculateTotalNumberRows(std::vector<Row> &rows) {
4371     int row_count = 0;
4372     for (auto &row : rows) {
4373       ++row_count;
4374       if (row.expanded)
4375         row_count += CalculateTotalNumberRows(row.GetChildren());
4376     }
4377     return row_count;
4378   }
4379 
GetRowForRowIndexImpl(std::vector<Row> & rows,size_t & row_index)4380   static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) {
4381     for (auto &row : rows) {
4382       if (row_index == 0)
4383         return &row;
4384       else {
4385         --row_index;
4386         auto &children = row.GetChildren();
4387         if (row.expanded && !children.empty()) {
4388           Row *result = GetRowForRowIndexImpl(children, row_index);
4389           if (result)
4390             return result;
4391         }
4392       }
4393     }
4394     return nullptr;
4395   }
4396 
GetRowForRowIndex(size_t row_index)4397   Row *GetRowForRowIndex(size_t row_index) {
4398     return GetRowForRowIndexImpl(m_rows, row_index);
4399   }
4400 
NumVisibleRows() const4401   int NumVisibleRows() const { return m_max_y - m_min_y; }
4402 
4403   static DisplayOptions g_options;
4404 };
4405 
4406 class FrameVariablesWindowDelegate : public ValueObjectListDelegate {
4407 public:
FrameVariablesWindowDelegate(Debugger & debugger)4408   FrameVariablesWindowDelegate(Debugger &debugger)
4409       : ValueObjectListDelegate(), m_debugger(debugger),
4410         m_frame_block(nullptr) {}
4411 
4412   ~FrameVariablesWindowDelegate() override = default;
4413 
WindowDelegateGetHelpText()4414   const char *WindowDelegateGetHelpText() override {
4415     return "Frame variable window keyboard shortcuts:";
4416   }
4417 
WindowDelegateDraw(Window & window,bool force)4418   bool WindowDelegateDraw(Window &window, bool force) override {
4419     ExecutionContext exe_ctx(
4420         m_debugger.GetCommandInterpreter().GetExecutionContext());
4421     Process *process = exe_ctx.GetProcessPtr();
4422     Block *frame_block = nullptr;
4423     StackFrame *frame = nullptr;
4424 
4425     if (process) {
4426       StateType state = process->GetState();
4427       if (StateIsStoppedState(state, true)) {
4428         frame = exe_ctx.GetFramePtr();
4429         if (frame)
4430           frame_block = frame->GetFrameBlock();
4431       } else if (StateIsRunningState(state)) {
4432         return true; // Don't do any updating when we are running
4433       }
4434     }
4435 
4436     ValueObjectList local_values;
4437     if (frame_block) {
4438       // Only update the variables if they have changed
4439       if (m_frame_block != frame_block) {
4440         m_frame_block = frame_block;
4441 
4442         VariableList *locals = frame->GetVariableList(true);
4443         if (locals) {
4444           const DynamicValueType use_dynamic = eDynamicDontRunTarget;
4445           for (const VariableSP &local_sp : *locals) {
4446             ValueObjectSP value_sp =
4447                 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic);
4448             if (value_sp) {
4449               ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue();
4450               if (synthetic_value_sp)
4451                 local_values.Append(synthetic_value_sp);
4452               else
4453                 local_values.Append(value_sp);
4454             }
4455           }
4456           // Update the values
4457           SetValues(local_values);
4458         }
4459       }
4460     } else {
4461       m_frame_block = nullptr;
4462       // Update the values with an empty list if there is no frame
4463       SetValues(local_values);
4464     }
4465 
4466     return ValueObjectListDelegate::WindowDelegateDraw(window, force);
4467   }
4468 
4469 protected:
4470   Debugger &m_debugger;
4471   Block *m_frame_block;
4472 };
4473 
4474 class RegistersWindowDelegate : public ValueObjectListDelegate {
4475 public:
RegistersWindowDelegate(Debugger & debugger)4476   RegistersWindowDelegate(Debugger &debugger)
4477       : ValueObjectListDelegate(), m_debugger(debugger) {}
4478 
4479   ~RegistersWindowDelegate() override = default;
4480 
WindowDelegateGetHelpText()4481   const char *WindowDelegateGetHelpText() override {
4482     return "Register window keyboard shortcuts:";
4483   }
4484 
WindowDelegateDraw(Window & window,bool force)4485   bool WindowDelegateDraw(Window &window, bool force) override {
4486     ExecutionContext exe_ctx(
4487         m_debugger.GetCommandInterpreter().GetExecutionContext());
4488     StackFrame *frame = exe_ctx.GetFramePtr();
4489 
4490     ValueObjectList value_list;
4491     if (frame) {
4492       if (frame->GetStackID() != m_stack_id) {
4493         m_stack_id = frame->GetStackID();
4494         RegisterContextSP reg_ctx(frame->GetRegisterContext());
4495         if (reg_ctx) {
4496           const uint32_t num_sets = reg_ctx->GetRegisterSetCount();
4497           for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) {
4498             value_list.Append(
4499                 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx));
4500           }
4501         }
4502         SetValues(value_list);
4503       }
4504     } else {
4505       Process *process = exe_ctx.GetProcessPtr();
4506       if (process && process->IsAlive())
4507         return true; // Don't do any updating if we are running
4508       else {
4509         // Update the values with an empty list if there is no process or the
4510         // process isn't alive anymore
4511         SetValues(value_list);
4512       }
4513     }
4514     return ValueObjectListDelegate::WindowDelegateDraw(window, force);
4515   }
4516 
4517 protected:
4518   Debugger &m_debugger;
4519   StackID m_stack_id;
4520 };
4521 
CursesKeyToCString(int ch)4522 static const char *CursesKeyToCString(int ch) {
4523   static char g_desc[32];
4524   if (ch >= KEY_F0 && ch < KEY_F0 + 64) {
4525     snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0);
4526     return g_desc;
4527   }
4528   switch (ch) {
4529   case KEY_DOWN:
4530     return "down";
4531   case KEY_UP:
4532     return "up";
4533   case KEY_LEFT:
4534     return "left";
4535   case KEY_RIGHT:
4536     return "right";
4537   case KEY_HOME:
4538     return "home";
4539   case KEY_BACKSPACE:
4540     return "backspace";
4541   case KEY_DL:
4542     return "delete-line";
4543   case KEY_IL:
4544     return "insert-line";
4545   case KEY_DC:
4546     return "delete-char";
4547   case KEY_IC:
4548     return "insert-char";
4549   case KEY_CLEAR:
4550     return "clear";
4551   case KEY_EOS:
4552     return "clear-to-eos";
4553   case KEY_EOL:
4554     return "clear-to-eol";
4555   case KEY_SF:
4556     return "scroll-forward";
4557   case KEY_SR:
4558     return "scroll-backward";
4559   case KEY_NPAGE:
4560     return "page-down";
4561   case KEY_PPAGE:
4562     return "page-up";
4563   case KEY_STAB:
4564     return "set-tab";
4565   case KEY_CTAB:
4566     return "clear-tab";
4567   case KEY_CATAB:
4568     return "clear-all-tabs";
4569   case KEY_ENTER:
4570     return "enter";
4571   case KEY_PRINT:
4572     return "print";
4573   case KEY_LL:
4574     return "lower-left key";
4575   case KEY_A1:
4576     return "upper left of keypad";
4577   case KEY_A3:
4578     return "upper right of keypad";
4579   case KEY_B2:
4580     return "center of keypad";
4581   case KEY_C1:
4582     return "lower left of keypad";
4583   case KEY_C3:
4584     return "lower right of keypad";
4585   case KEY_BTAB:
4586     return "back-tab key";
4587   case KEY_BEG:
4588     return "begin key";
4589   case KEY_CANCEL:
4590     return "cancel key";
4591   case KEY_CLOSE:
4592     return "close key";
4593   case KEY_COMMAND:
4594     return "command key";
4595   case KEY_COPY:
4596     return "copy key";
4597   case KEY_CREATE:
4598     return "create key";
4599   case KEY_END:
4600     return "end key";
4601   case KEY_EXIT:
4602     return "exit key";
4603   case KEY_FIND:
4604     return "find key";
4605   case KEY_HELP:
4606     return "help key";
4607   case KEY_MARK:
4608     return "mark key";
4609   case KEY_MESSAGE:
4610     return "message key";
4611   case KEY_MOVE:
4612     return "move key";
4613   case KEY_NEXT:
4614     return "next key";
4615   case KEY_OPEN:
4616     return "open key";
4617   case KEY_OPTIONS:
4618     return "options key";
4619   case KEY_PREVIOUS:
4620     return "previous key";
4621   case KEY_REDO:
4622     return "redo key";
4623   case KEY_REFERENCE:
4624     return "reference key";
4625   case KEY_REFRESH:
4626     return "refresh key";
4627   case KEY_REPLACE:
4628     return "replace key";
4629   case KEY_RESTART:
4630     return "restart key";
4631   case KEY_RESUME:
4632     return "resume key";
4633   case KEY_SAVE:
4634     return "save key";
4635   case KEY_SBEG:
4636     return "shifted begin key";
4637   case KEY_SCANCEL:
4638     return "shifted cancel key";
4639   case KEY_SCOMMAND:
4640     return "shifted command key";
4641   case KEY_SCOPY:
4642     return "shifted copy key";
4643   case KEY_SCREATE:
4644     return "shifted create key";
4645   case KEY_SDC:
4646     return "shifted delete-character key";
4647   case KEY_SDL:
4648     return "shifted delete-line key";
4649   case KEY_SELECT:
4650     return "select key";
4651   case KEY_SEND:
4652     return "shifted end key";
4653   case KEY_SEOL:
4654     return "shifted clear-to-end-of-line key";
4655   case KEY_SEXIT:
4656     return "shifted exit key";
4657   case KEY_SFIND:
4658     return "shifted find key";
4659   case KEY_SHELP:
4660     return "shifted help key";
4661   case KEY_SHOME:
4662     return "shifted home key";
4663   case KEY_SIC:
4664     return "shifted insert-character key";
4665   case KEY_SLEFT:
4666     return "shifted left-arrow key";
4667   case KEY_SMESSAGE:
4668     return "shifted message key";
4669   case KEY_SMOVE:
4670     return "shifted move key";
4671   case KEY_SNEXT:
4672     return "shifted next key";
4673   case KEY_SOPTIONS:
4674     return "shifted options key";
4675   case KEY_SPREVIOUS:
4676     return "shifted previous key";
4677   case KEY_SPRINT:
4678     return "shifted print key";
4679   case KEY_SREDO:
4680     return "shifted redo key";
4681   case KEY_SREPLACE:
4682     return "shifted replace key";
4683   case KEY_SRIGHT:
4684     return "shifted right-arrow key";
4685   case KEY_SRSUME:
4686     return "shifted resume key";
4687   case KEY_SSAVE:
4688     return "shifted save key";
4689   case KEY_SSUSPEND:
4690     return "shifted suspend key";
4691   case KEY_SUNDO:
4692     return "shifted undo key";
4693   case KEY_SUSPEND:
4694     return "suspend key";
4695   case KEY_UNDO:
4696     return "undo key";
4697   case KEY_MOUSE:
4698     return "Mouse event has occurred";
4699   case KEY_RESIZE:
4700     return "Terminal resize event";
4701 #ifdef KEY_EVENT
4702   case KEY_EVENT:
4703     return "We were interrupted by an event";
4704 #endif
4705   case KEY_RETURN:
4706     return "return";
4707   case ' ':
4708     return "space";
4709   case '\t':
4710     return "tab";
4711   case KEY_ESCAPE:
4712     return "escape";
4713   default:
4714     if (llvm::isPrint(ch))
4715       snprintf(g_desc, sizeof(g_desc), "%c", ch);
4716     else
4717       snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch);
4718     return g_desc;
4719   }
4720   return nullptr;
4721 }
4722 
HelpDialogDelegate(const char * text,KeyHelp * key_help_array)4723 HelpDialogDelegate::HelpDialogDelegate(const char *text,
4724                                        KeyHelp *key_help_array)
4725     : m_text(), m_first_visible_line(0) {
4726   if (text && text[0]) {
4727     m_text.SplitIntoLines(text);
4728     m_text.AppendString("");
4729   }
4730   if (key_help_array) {
4731     for (KeyHelp *key = key_help_array; key->ch; ++key) {
4732       StreamString key_description;
4733       key_description.Printf("%10s - %s", CursesKeyToCString(key->ch),
4734                              key->description);
4735       m_text.AppendString(key_description.GetString());
4736     }
4737   }
4738 }
4739 
4740 HelpDialogDelegate::~HelpDialogDelegate() = default;
4741 
WindowDelegateDraw(Window & window,bool force)4742 bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) {
4743   window.Erase();
4744   const int window_height = window.GetHeight();
4745   int x = 2;
4746   int y = 1;
4747   const int min_y = y;
4748   const int max_y = window_height - 1 - y;
4749   const size_t num_visible_lines = max_y - min_y + 1;
4750   const size_t num_lines = m_text.GetSize();
4751   const char *bottom_message;
4752   if (num_lines <= num_visible_lines)
4753     bottom_message = "Press any key to exit";
4754   else
4755     bottom_message = "Use arrows to scroll, any other key to exit";
4756   window.DrawTitleBox(window.GetName(), bottom_message);
4757   while (y <= max_y) {
4758     window.MoveCursor(x, y);
4759     window.PutCStringTruncated(
4760         1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y));
4761     ++y;
4762   }
4763   return true;
4764 }
4765 
WindowDelegateHandleChar(Window & window,int key)4766 HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window,
4767                                                               int key) {
4768   bool done = false;
4769   const size_t num_lines = m_text.GetSize();
4770   const size_t num_visible_lines = window.GetHeight() - 2;
4771 
4772   if (num_lines <= num_visible_lines) {
4773     done = true;
4774     // If we have all lines visible and don't need scrolling, then any key
4775     // press will cause us to exit
4776   } else {
4777     switch (key) {
4778     case KEY_UP:
4779       if (m_first_visible_line > 0)
4780         --m_first_visible_line;
4781       break;
4782 
4783     case KEY_DOWN:
4784       if (m_first_visible_line + num_visible_lines < num_lines)
4785         ++m_first_visible_line;
4786       break;
4787 
4788     case KEY_PPAGE:
4789     case ',':
4790       if (m_first_visible_line > 0) {
4791         if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines)
4792           m_first_visible_line -= num_visible_lines;
4793         else
4794           m_first_visible_line = 0;
4795       }
4796       break;
4797 
4798     case KEY_NPAGE:
4799     case '.':
4800       if (m_first_visible_line + num_visible_lines < num_lines) {
4801         m_first_visible_line += num_visible_lines;
4802         if (static_cast<size_t>(m_first_visible_line) > num_lines)
4803           m_first_visible_line = num_lines - num_visible_lines;
4804       }
4805       break;
4806 
4807     default:
4808       done = true;
4809       break;
4810     }
4811   }
4812   if (done)
4813     window.GetParent()->RemoveSubWindow(&window);
4814   return eKeyHandled;
4815 }
4816 
4817 class ApplicationDelegate : public WindowDelegate, public MenuDelegate {
4818 public:
4819   enum {
4820     eMenuID_LLDB = 1,
4821     eMenuID_LLDBAbout,
4822     eMenuID_LLDBExit,
4823 
4824     eMenuID_Target,
4825     eMenuID_TargetCreate,
4826     eMenuID_TargetDelete,
4827 
4828     eMenuID_Process,
4829     eMenuID_ProcessAttach,
4830     eMenuID_ProcessDetachResume,
4831     eMenuID_ProcessDetachSuspended,
4832     eMenuID_ProcessLaunch,
4833     eMenuID_ProcessContinue,
4834     eMenuID_ProcessHalt,
4835     eMenuID_ProcessKill,
4836 
4837     eMenuID_Thread,
4838     eMenuID_ThreadStepIn,
4839     eMenuID_ThreadStepOver,
4840     eMenuID_ThreadStepOut,
4841 
4842     eMenuID_View,
4843     eMenuID_ViewBacktrace,
4844     eMenuID_ViewRegisters,
4845     eMenuID_ViewSource,
4846     eMenuID_ViewVariables,
4847 
4848     eMenuID_Help,
4849     eMenuID_HelpGUIHelp
4850   };
4851 
ApplicationDelegate(Application & app,Debugger & debugger)4852   ApplicationDelegate(Application &app, Debugger &debugger)
4853       : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {}
4854 
4855   ~ApplicationDelegate() override = default;
4856 
WindowDelegateDraw(Window & window,bool force)4857   bool WindowDelegateDraw(Window &window, bool force) override {
4858     return false; // Drawing not handled, let standard window drawing happen
4859   }
4860 
WindowDelegateHandleChar(Window & window,int key)4861   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
4862     switch (key) {
4863     case '\t':
4864       window.SelectNextWindowAsActive();
4865       return eKeyHandled;
4866 
4867     case KEY_SHIFT_TAB:
4868       window.SelectPreviousWindowAsActive();
4869       return eKeyHandled;
4870 
4871     case 'h':
4872       window.CreateHelpSubwindow();
4873       return eKeyHandled;
4874 
4875     case KEY_ESCAPE:
4876       return eQuitApplication;
4877 
4878     default:
4879       break;
4880     }
4881     return eKeyNotHandled;
4882   }
4883 
WindowDelegateGetHelpText()4884   const char *WindowDelegateGetHelpText() override {
4885     return "Welcome to the LLDB curses GUI.\n\n"
4886            "Press the TAB key to change the selected view.\n"
4887            "Each view has its own keyboard shortcuts, press 'h' to open a "
4888            "dialog to display them.\n\n"
4889            "Common key bindings for all views:";
4890   }
4891 
WindowDelegateGetKeyHelp()4892   KeyHelp *WindowDelegateGetKeyHelp() override {
4893     static curses::KeyHelp g_source_view_key_help[] = {
4894         {'\t', "Select next view"},
4895         {KEY_BTAB, "Select previous view"},
4896         {'h', "Show help dialog with view specific key bindings"},
4897         {',', "Page up"},
4898         {'.', "Page down"},
4899         {KEY_UP, "Select previous"},
4900         {KEY_DOWN, "Select next"},
4901         {KEY_LEFT, "Unexpand or select parent"},
4902         {KEY_RIGHT, "Expand"},
4903         {KEY_PPAGE, "Page up"},
4904         {KEY_NPAGE, "Page down"},
4905         {'\0', nullptr}};
4906     return g_source_view_key_help;
4907   }
4908 
MenuDelegateAction(Menu & menu)4909   MenuActionResult MenuDelegateAction(Menu &menu) override {
4910     switch (menu.GetIdentifier()) {
4911     case eMenuID_ThreadStepIn: {
4912       ExecutionContext exe_ctx =
4913           m_debugger.GetCommandInterpreter().GetExecutionContext();
4914       if (exe_ctx.HasThreadScope()) {
4915         Process *process = exe_ctx.GetProcessPtr();
4916         if (process && process->IsAlive() &&
4917             StateIsStoppedState(process->GetState(), true))
4918           exe_ctx.GetThreadRef().StepIn(true);
4919       }
4920     }
4921       return MenuActionResult::Handled;
4922 
4923     case eMenuID_ThreadStepOut: {
4924       ExecutionContext exe_ctx =
4925           m_debugger.GetCommandInterpreter().GetExecutionContext();
4926       if (exe_ctx.HasThreadScope()) {
4927         Process *process = exe_ctx.GetProcessPtr();
4928         if (process && process->IsAlive() &&
4929             StateIsStoppedState(process->GetState(), true))
4930           exe_ctx.GetThreadRef().StepOut();
4931       }
4932     }
4933       return MenuActionResult::Handled;
4934 
4935     case eMenuID_ThreadStepOver: {
4936       ExecutionContext exe_ctx =
4937           m_debugger.GetCommandInterpreter().GetExecutionContext();
4938       if (exe_ctx.HasThreadScope()) {
4939         Process *process = exe_ctx.GetProcessPtr();
4940         if (process && process->IsAlive() &&
4941             StateIsStoppedState(process->GetState(), true))
4942           exe_ctx.GetThreadRef().StepOver(true);
4943       }
4944     }
4945       return MenuActionResult::Handled;
4946 
4947     case eMenuID_ProcessAttach: {
4948       WindowSP main_window_sp = m_app.GetMainWindow();
4949       FormDelegateSP form_delegate_sp = FormDelegateSP(
4950           new ProcessAttachFormDelegate(m_debugger, main_window_sp));
4951       Rect bounds = main_window_sp->GetCenteredRect(80, 22);
4952       WindowSP form_window_sp = main_window_sp->CreateSubWindow(
4953           form_delegate_sp->GetName().c_str(), bounds, true);
4954       WindowDelegateSP window_delegate_sp =
4955           WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
4956       form_window_sp->SetDelegate(window_delegate_sp);
4957       return MenuActionResult::Handled;
4958     }
4959 
4960     case eMenuID_ProcessContinue: {
4961       ExecutionContext exe_ctx =
4962           m_debugger.GetCommandInterpreter().GetExecutionContext();
4963       if (exe_ctx.HasProcessScope()) {
4964         Process *process = exe_ctx.GetProcessPtr();
4965         if (process && process->IsAlive() &&
4966             StateIsStoppedState(process->GetState(), true))
4967           process->Resume();
4968       }
4969     }
4970       return MenuActionResult::Handled;
4971 
4972     case eMenuID_ProcessKill: {
4973       ExecutionContext exe_ctx =
4974           m_debugger.GetCommandInterpreter().GetExecutionContext();
4975       if (exe_ctx.HasProcessScope()) {
4976         Process *process = exe_ctx.GetProcessPtr();
4977         if (process && process->IsAlive())
4978           process->Destroy(false);
4979       }
4980     }
4981       return MenuActionResult::Handled;
4982 
4983     case eMenuID_ProcessHalt: {
4984       ExecutionContext exe_ctx =
4985           m_debugger.GetCommandInterpreter().GetExecutionContext();
4986       if (exe_ctx.HasProcessScope()) {
4987         Process *process = exe_ctx.GetProcessPtr();
4988         if (process && process->IsAlive())
4989           process->Halt();
4990       }
4991     }
4992       return MenuActionResult::Handled;
4993 
4994     case eMenuID_ProcessDetachResume:
4995     case eMenuID_ProcessDetachSuspended: {
4996       ExecutionContext exe_ctx =
4997           m_debugger.GetCommandInterpreter().GetExecutionContext();
4998       if (exe_ctx.HasProcessScope()) {
4999         Process *process = exe_ctx.GetProcessPtr();
5000         if (process && process->IsAlive())
5001           process->Detach(menu.GetIdentifier() ==
5002                           eMenuID_ProcessDetachSuspended);
5003       }
5004     }
5005       return MenuActionResult::Handled;
5006 
5007     case eMenuID_Process: {
5008       // Populate the menu with all of the threads if the process is stopped
5009       // when the Process menu gets selected and is about to display its
5010       // submenu.
5011       Menus &submenus = menu.GetSubmenus();
5012       ExecutionContext exe_ctx =
5013           m_debugger.GetCommandInterpreter().GetExecutionContext();
5014       Process *process = exe_ctx.GetProcessPtr();
5015       if (process && process->IsAlive() &&
5016           StateIsStoppedState(process->GetState(), true)) {
5017         if (submenus.size() == 7)
5018           menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
5019         else if (submenus.size() > 8)
5020           submenus.erase(submenus.begin() + 8, submenus.end());
5021 
5022         ThreadList &threads = process->GetThreadList();
5023         std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
5024         size_t num_threads = threads.GetSize();
5025         for (size_t i = 0; i < num_threads; ++i) {
5026           ThreadSP thread_sp = threads.GetThreadAtIndex(i);
5027           char menu_char = '\0';
5028           if (i < 9)
5029             menu_char = '1' + i;
5030           StreamString thread_menu_title;
5031           thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID());
5032           const char *thread_name = thread_sp->GetName();
5033           if (thread_name && thread_name[0])
5034             thread_menu_title.Printf(" %s", thread_name);
5035           else {
5036             const char *queue_name = thread_sp->GetQueueName();
5037             if (queue_name && queue_name[0])
5038               thread_menu_title.Printf(" %s", queue_name);
5039           }
5040           menu.AddSubmenu(
5041               MenuSP(new Menu(thread_menu_title.GetString().str().c_str(),
5042                               nullptr, menu_char, thread_sp->GetID())));
5043         }
5044       } else if (submenus.size() > 7) {
5045         // Remove the separator and any other thread submenu items that were
5046         // previously added
5047         submenus.erase(submenus.begin() + 7, submenus.end());
5048       }
5049       // Since we are adding and removing items we need to recalculate the name
5050       // lengths
5051       menu.RecalculateNameLengths();
5052     }
5053       return MenuActionResult::Handled;
5054 
5055     case eMenuID_ViewVariables: {
5056       WindowSP main_window_sp = m_app.GetMainWindow();
5057       WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
5058       WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
5059       WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
5060       const Rect source_bounds = source_window_sp->GetBounds();
5061 
5062       if (variables_window_sp) {
5063         const Rect variables_bounds = variables_window_sp->GetBounds();
5064 
5065         main_window_sp->RemoveSubWindow(variables_window_sp.get());
5066 
5067         if (registers_window_sp) {
5068           // We have a registers window, so give all the area back to the
5069           // registers window
5070           Rect registers_bounds = variables_bounds;
5071           registers_bounds.size.width = source_bounds.size.width;
5072           registers_window_sp->SetBounds(registers_bounds);
5073         } else {
5074           // We have no registers window showing so give the bottom area back
5075           // to the source view
5076           source_window_sp->Resize(source_bounds.size.width,
5077                                    source_bounds.size.height +
5078                                        variables_bounds.size.height);
5079         }
5080       } else {
5081         Rect new_variables_rect;
5082         if (registers_window_sp) {
5083           // We have a registers window so split the area of the registers
5084           // window into two columns where the left hand side will be the
5085           // variables and the right hand side will be the registers
5086           const Rect variables_bounds = registers_window_sp->GetBounds();
5087           Rect new_registers_rect;
5088           variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect,
5089                                                    new_registers_rect);
5090           registers_window_sp->SetBounds(new_registers_rect);
5091         } else {
5092           // No registers window, grab the bottom part of the source window
5093           Rect new_source_rect;
5094           source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
5095                                                   new_variables_rect);
5096           source_window_sp->SetBounds(new_source_rect);
5097         }
5098         WindowSP new_window_sp = main_window_sp->CreateSubWindow(
5099             "Variables", new_variables_rect, false);
5100         new_window_sp->SetDelegate(
5101             WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
5102       }
5103       touchwin(stdscr);
5104     }
5105       return MenuActionResult::Handled;
5106 
5107     case eMenuID_ViewRegisters: {
5108       WindowSP main_window_sp = m_app.GetMainWindow();
5109       WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
5110       WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
5111       WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
5112       const Rect source_bounds = source_window_sp->GetBounds();
5113 
5114       if (registers_window_sp) {
5115         if (variables_window_sp) {
5116           const Rect variables_bounds = variables_window_sp->GetBounds();
5117 
5118           // We have a variables window, so give all the area back to the
5119           // variables window
5120           variables_window_sp->Resize(variables_bounds.size.width +
5121                                           registers_window_sp->GetWidth(),
5122                                       variables_bounds.size.height);
5123         } else {
5124           // We have no variables window showing so give the bottom area back
5125           // to the source view
5126           source_window_sp->Resize(source_bounds.size.width,
5127                                    source_bounds.size.height +
5128                                        registers_window_sp->GetHeight());
5129         }
5130         main_window_sp->RemoveSubWindow(registers_window_sp.get());
5131       } else {
5132         Rect new_regs_rect;
5133         if (variables_window_sp) {
5134           // We have a variables window, split it into two columns where the
5135           // left hand side will be the variables and the right hand side will
5136           // be the registers
5137           const Rect variables_bounds = variables_window_sp->GetBounds();
5138           Rect new_vars_rect;
5139           variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect,
5140                                                    new_regs_rect);
5141           variables_window_sp->SetBounds(new_vars_rect);
5142         } else {
5143           // No variables window, grab the bottom part of the source window
5144           Rect new_source_rect;
5145           source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
5146                                                   new_regs_rect);
5147           source_window_sp->SetBounds(new_source_rect);
5148         }
5149         WindowSP new_window_sp =
5150             main_window_sp->CreateSubWindow("Registers", new_regs_rect, false);
5151         new_window_sp->SetDelegate(
5152             WindowDelegateSP(new RegistersWindowDelegate(m_debugger)));
5153       }
5154       touchwin(stdscr);
5155     }
5156       return MenuActionResult::Handled;
5157 
5158     case eMenuID_HelpGUIHelp:
5159       m_app.GetMainWindow()->CreateHelpSubwindow();
5160       return MenuActionResult::Handled;
5161 
5162     default:
5163       break;
5164     }
5165 
5166     return MenuActionResult::NotHandled;
5167   }
5168 
5169 protected:
5170   Application &m_app;
5171   Debugger &m_debugger;
5172 };
5173 
5174 class StatusBarWindowDelegate : public WindowDelegate {
5175 public:
StatusBarWindowDelegate(Debugger & debugger)5176   StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) {
5177     FormatEntity::Parse("Thread: ${thread.id%tid}", m_format);
5178   }
5179 
5180   ~StatusBarWindowDelegate() override = default;
5181 
WindowDelegateDraw(Window & window,bool force)5182   bool WindowDelegateDraw(Window &window, bool force) override {
5183     ExecutionContext exe_ctx =
5184         m_debugger.GetCommandInterpreter().GetExecutionContext();
5185     Process *process = exe_ctx.GetProcessPtr();
5186     Thread *thread = exe_ctx.GetThreadPtr();
5187     StackFrame *frame = exe_ctx.GetFramePtr();
5188     window.Erase();
5189     window.SetBackground(BlackOnWhite);
5190     window.MoveCursor(0, 0);
5191     if (process) {
5192       const StateType state = process->GetState();
5193       window.Printf("Process: %5" PRIu64 " %10s", process->GetID(),
5194                     StateAsCString(state));
5195 
5196       if (StateIsStoppedState(state, true)) {
5197         StreamString strm;
5198         if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx,
5199                                            nullptr, nullptr, false, false)) {
5200           window.MoveCursor(40, 0);
5201           window.PutCStringTruncated(1, strm.GetString().str().c_str());
5202         }
5203 
5204         window.MoveCursor(60, 0);
5205         if (frame)
5206           window.Printf("Frame: %3u  PC = 0x%16.16" PRIx64,
5207                         frame->GetFrameIndex(),
5208                         frame->GetFrameCodeAddress().GetOpcodeLoadAddress(
5209                             exe_ctx.GetTargetPtr()));
5210       } else if (state == eStateExited) {
5211         const char *exit_desc = process->GetExitDescription();
5212         const int exit_status = process->GetExitStatus();
5213         if (exit_desc && exit_desc[0])
5214           window.Printf(" with status = %i (%s)", exit_status, exit_desc);
5215         else
5216           window.Printf(" with status = %i", exit_status);
5217       }
5218     }
5219     return true;
5220   }
5221 
5222 protected:
5223   Debugger &m_debugger;
5224   FormatEntity::Entry m_format;
5225 };
5226 
5227 class SourceFileWindowDelegate : public WindowDelegate {
5228 public:
SourceFileWindowDelegate(Debugger & debugger)5229   SourceFileWindowDelegate(Debugger &debugger)
5230       : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(),
5231         m_disassembly_scope(nullptr), m_disassembly_sp(), m_disassembly_range(),
5232         m_title(), m_tid(LLDB_INVALID_THREAD_ID), m_line_width(4),
5233         m_selected_line(0), m_pc_line(0), m_stop_id(0), m_frame_idx(UINT32_MAX),
5234         m_first_visible_line(0), m_first_visible_column(0), m_min_x(0),
5235         m_min_y(0), m_max_x(0), m_max_y(0) {}
5236 
5237   ~SourceFileWindowDelegate() override = default;
5238 
Update(const SymbolContext & sc)5239   void Update(const SymbolContext &sc) { m_sc = sc; }
5240 
NumVisibleLines() const5241   uint32_t NumVisibleLines() const { return m_max_y - m_min_y; }
5242 
WindowDelegateGetHelpText()5243   const char *WindowDelegateGetHelpText() override {
5244     return "Source/Disassembly window keyboard shortcuts:";
5245   }
5246 
WindowDelegateGetKeyHelp()5247   KeyHelp *WindowDelegateGetKeyHelp() override {
5248     static curses::KeyHelp g_source_view_key_help[] = {
5249         {KEY_RETURN, "Run to selected line with one shot breakpoint"},
5250         {KEY_UP, "Select previous source line"},
5251         {KEY_DOWN, "Select next source line"},
5252         {KEY_LEFT, "Scroll to the left"},
5253         {KEY_RIGHT, "Scroll to the right"},
5254         {KEY_PPAGE, "Page up"},
5255         {KEY_NPAGE, "Page down"},
5256         {'b', "Set breakpoint on selected source/disassembly line"},
5257         {'c', "Continue process"},
5258         {'D', "Detach with process suspended"},
5259         {'h', "Show help dialog"},
5260         {'n', "Step over (source line)"},
5261         {'N', "Step over (single instruction)"},
5262         {'f', "Step out (finish)"},
5263         {'s', "Step in (source line)"},
5264         {'S', "Step in (single instruction)"},
5265         {'u', "Frame up"},
5266         {'d', "Frame down"},
5267         {',', "Page up"},
5268         {'.', "Page down"},
5269         {'\0', nullptr}};
5270     return g_source_view_key_help;
5271   }
5272 
WindowDelegateDraw(Window & window,bool force)5273   bool WindowDelegateDraw(Window &window, bool force) override {
5274     ExecutionContext exe_ctx =
5275         m_debugger.GetCommandInterpreter().GetExecutionContext();
5276     Process *process = exe_ctx.GetProcessPtr();
5277     Thread *thread = nullptr;
5278 
5279     bool update_location = false;
5280     if (process) {
5281       StateType state = process->GetState();
5282       if (StateIsStoppedState(state, true)) {
5283         // We are stopped, so it is ok to
5284         update_location = true;
5285       }
5286     }
5287 
5288     m_min_x = 1;
5289     m_min_y = 2;
5290     m_max_x = window.GetMaxX() - 1;
5291     m_max_y = window.GetMaxY() - 1;
5292 
5293     const uint32_t num_visible_lines = NumVisibleLines();
5294     StackFrameSP frame_sp;
5295     bool set_selected_line_to_pc = false;
5296 
5297     if (update_location) {
5298       const bool process_alive = process ? process->IsAlive() : false;
5299       bool thread_changed = false;
5300       if (process_alive) {
5301         thread = exe_ctx.GetThreadPtr();
5302         if (thread) {
5303           frame_sp = thread->GetSelectedFrame();
5304           auto tid = thread->GetID();
5305           thread_changed = tid != m_tid;
5306           m_tid = tid;
5307         } else {
5308           if (m_tid != LLDB_INVALID_THREAD_ID) {
5309             thread_changed = true;
5310             m_tid = LLDB_INVALID_THREAD_ID;
5311           }
5312         }
5313       }
5314       const uint32_t stop_id = process ? process->GetStopID() : 0;
5315       const bool stop_id_changed = stop_id != m_stop_id;
5316       bool frame_changed = false;
5317       m_stop_id = stop_id;
5318       m_title.Clear();
5319       if (frame_sp) {
5320         m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything);
5321         if (m_sc.module_sp) {
5322           m_title.Printf(
5323               "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString());
5324           ConstString func_name = m_sc.GetFunctionName();
5325           if (func_name)
5326             m_title.Printf("`%s", func_name.GetCString());
5327         }
5328         const uint32_t frame_idx = frame_sp->GetFrameIndex();
5329         frame_changed = frame_idx != m_frame_idx;
5330         m_frame_idx = frame_idx;
5331       } else {
5332         m_sc.Clear(true);
5333         frame_changed = m_frame_idx != UINT32_MAX;
5334         m_frame_idx = UINT32_MAX;
5335       }
5336 
5337       const bool context_changed =
5338           thread_changed || frame_changed || stop_id_changed;
5339 
5340       if (process_alive) {
5341         if (m_sc.line_entry.IsValid()) {
5342           m_pc_line = m_sc.line_entry.line;
5343           if (m_pc_line != UINT32_MAX)
5344             --m_pc_line; // Convert to zero based line number...
5345           // Update the selected line if the stop ID changed...
5346           if (context_changed)
5347             m_selected_line = m_pc_line;
5348 
5349           if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) {
5350             // Same file, nothing to do, we should either have the lines or not
5351             // (source file missing)
5352             if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) {
5353               if (m_selected_line >= m_first_visible_line + num_visible_lines)
5354                 m_first_visible_line = m_selected_line - 10;
5355             } else {
5356               if (m_selected_line > 10)
5357                 m_first_visible_line = m_selected_line - 10;
5358               else
5359                 m_first_visible_line = 0;
5360             }
5361           } else {
5362             // File changed, set selected line to the line with the PC
5363             m_selected_line = m_pc_line;
5364             m_file_sp =
5365                 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file);
5366             if (m_file_sp) {
5367               const size_t num_lines = m_file_sp->GetNumLines();
5368               m_line_width = 1;
5369               for (size_t n = num_lines; n >= 10; n = n / 10)
5370                 ++m_line_width;
5371 
5372               if (num_lines < num_visible_lines ||
5373                   m_selected_line < num_visible_lines)
5374                 m_first_visible_line = 0;
5375               else
5376                 m_first_visible_line = m_selected_line - 10;
5377             }
5378           }
5379         } else {
5380           m_file_sp.reset();
5381         }
5382 
5383         if (!m_file_sp || m_file_sp->GetNumLines() == 0) {
5384           // Show disassembly
5385           bool prefer_file_cache = false;
5386           if (m_sc.function) {
5387             if (m_disassembly_scope != m_sc.function) {
5388               m_disassembly_scope = m_sc.function;
5389               m_disassembly_sp = m_sc.function->GetInstructions(
5390                   exe_ctx, nullptr, !prefer_file_cache);
5391               if (m_disassembly_sp) {
5392                 set_selected_line_to_pc = true;
5393                 m_disassembly_range = m_sc.function->GetAddressRange();
5394               } else {
5395                 m_disassembly_range.Clear();
5396               }
5397             } else {
5398               set_selected_line_to_pc = context_changed;
5399             }
5400           } else if (m_sc.symbol) {
5401             if (m_disassembly_scope != m_sc.symbol) {
5402               m_disassembly_scope = m_sc.symbol;
5403               m_disassembly_sp = m_sc.symbol->GetInstructions(
5404                   exe_ctx, nullptr, prefer_file_cache);
5405               if (m_disassembly_sp) {
5406                 set_selected_line_to_pc = true;
5407                 m_disassembly_range.GetBaseAddress() =
5408                     m_sc.symbol->GetAddress();
5409                 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize());
5410               } else {
5411                 m_disassembly_range.Clear();
5412               }
5413             } else {
5414               set_selected_line_to_pc = context_changed;
5415             }
5416           }
5417         }
5418       } else {
5419         m_pc_line = UINT32_MAX;
5420       }
5421     }
5422 
5423     const int window_width = window.GetWidth();
5424     window.Erase();
5425     window.DrawTitleBox("Sources");
5426     if (!m_title.GetString().empty()) {
5427       window.AttributeOn(A_REVERSE);
5428       window.MoveCursor(1, 1);
5429       window.PutChar(' ');
5430       window.PutCStringTruncated(1, m_title.GetString().str().c_str());
5431       int x = window.GetCursorX();
5432       if (x < window_width - 1) {
5433         window.Printf("%*s", window_width - x - 1, "");
5434       }
5435       window.AttributeOff(A_REVERSE);
5436     }
5437 
5438     Target *target = exe_ctx.GetTargetPtr();
5439     const size_t num_source_lines = GetNumSourceLines();
5440     if (num_source_lines > 0) {
5441       // Display source
5442       BreakpointLines bp_lines;
5443       if (target) {
5444         BreakpointList &bp_list = target->GetBreakpointList();
5445         const size_t num_bps = bp_list.GetSize();
5446         for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
5447           BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
5448           const size_t num_bps_locs = bp_sp->GetNumLocations();
5449           for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
5450             BreakpointLocationSP bp_loc_sp =
5451                 bp_sp->GetLocationAtIndex(bp_loc_idx);
5452             LineEntry bp_loc_line_entry;
5453             if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
5454                     bp_loc_line_entry)) {
5455               if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) {
5456                 bp_lines.insert(bp_loc_line_entry.line);
5457               }
5458             }
5459           }
5460         }
5461       }
5462 
5463       const attr_t selected_highlight_attr = A_REVERSE;
5464       const attr_t pc_highlight_attr = COLOR_PAIR(BlackOnBlue);
5465 
5466       for (size_t i = 0; i < num_visible_lines; ++i) {
5467         const uint32_t curr_line = m_first_visible_line + i;
5468         if (curr_line < num_source_lines) {
5469           const int line_y = m_min_y + i;
5470           window.MoveCursor(1, line_y);
5471           const bool is_pc_line = curr_line == m_pc_line;
5472           const bool line_is_selected = m_selected_line == curr_line;
5473           // Highlight the line as the PC line first, then if the selected line
5474           // isn't the same as the PC line, highlight it differently
5475           attr_t highlight_attr = 0;
5476           attr_t bp_attr = 0;
5477           if (is_pc_line)
5478             highlight_attr = pc_highlight_attr;
5479           else if (line_is_selected)
5480             highlight_attr = selected_highlight_attr;
5481 
5482           if (bp_lines.find(curr_line + 1) != bp_lines.end())
5483             bp_attr = COLOR_PAIR(BlackOnWhite);
5484 
5485           if (bp_attr)
5486             window.AttributeOn(bp_attr);
5487 
5488           window.Printf(" %*u ", m_line_width, curr_line + 1);
5489 
5490           if (bp_attr)
5491             window.AttributeOff(bp_attr);
5492 
5493           window.PutChar(ACS_VLINE);
5494           // Mark the line with the PC with a diamond
5495           if (is_pc_line)
5496             window.PutChar(ACS_DIAMOND);
5497           else
5498             window.PutChar(' ');
5499 
5500           if (highlight_attr)
5501             window.AttributeOn(highlight_attr);
5502 
5503           StreamString lineStream;
5504           m_file_sp->DisplaySourceLines(curr_line + 1, {}, 0, 0, &lineStream);
5505           StringRef line = lineStream.GetString();
5506           if (line.endswith("\n"))
5507             line = line.drop_back();
5508           bool wasWritten = window.OutputColoredStringTruncated(
5509               1, line, m_first_visible_column, line_is_selected);
5510           if (line_is_selected && !wasWritten) {
5511             // Draw an empty space to show the selected line if empty,
5512             // or draw '<' if nothing is visible because of scrolling too much
5513             // to the right.
5514             window.PutCStringTruncated(
5515                 1, line.empty() && m_first_visible_column == 0 ? " " : "<");
5516           }
5517 
5518           if (is_pc_line && frame_sp &&
5519               frame_sp->GetConcreteFrameIndex() == 0) {
5520             StopInfoSP stop_info_sp;
5521             if (thread)
5522               stop_info_sp = thread->GetStopInfo();
5523             if (stop_info_sp) {
5524               const char *stop_description = stop_info_sp->GetDescription();
5525               if (stop_description && stop_description[0]) {
5526                 size_t stop_description_len = strlen(stop_description);
5527                 int desc_x = window_width - stop_description_len - 16;
5528                 if (desc_x - window.GetCursorX() > 0)
5529                   window.Printf("%*s", desc_x - window.GetCursorX(), "");
5530                 window.MoveCursor(window_width - stop_description_len - 16,
5531                                   line_y);
5532                 const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue);
5533                 window.AttributeOn(stop_reason_attr);
5534                 window.PrintfTruncated(1, " <<< Thread %u: %s ",
5535                                        thread->GetIndexID(), stop_description);
5536                 window.AttributeOff(stop_reason_attr);
5537               }
5538             } else {
5539               window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
5540             }
5541           }
5542           if (highlight_attr)
5543             window.AttributeOff(highlight_attr);
5544         } else {
5545           break;
5546         }
5547       }
5548     } else {
5549       size_t num_disassembly_lines = GetNumDisassemblyLines();
5550       if (num_disassembly_lines > 0) {
5551         // Display disassembly
5552         BreakpointAddrs bp_file_addrs;
5553         Target *target = exe_ctx.GetTargetPtr();
5554         if (target) {
5555           BreakpointList &bp_list = target->GetBreakpointList();
5556           const size_t num_bps = bp_list.GetSize();
5557           for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
5558             BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
5559             const size_t num_bps_locs = bp_sp->GetNumLocations();
5560             for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs;
5561                  ++bp_loc_idx) {
5562               BreakpointLocationSP bp_loc_sp =
5563                   bp_sp->GetLocationAtIndex(bp_loc_idx);
5564               LineEntry bp_loc_line_entry;
5565               const lldb::addr_t file_addr =
5566                   bp_loc_sp->GetAddress().GetFileAddress();
5567               if (file_addr != LLDB_INVALID_ADDRESS) {
5568                 if (m_disassembly_range.ContainsFileAddress(file_addr))
5569                   bp_file_addrs.insert(file_addr);
5570               }
5571             }
5572           }
5573         }
5574 
5575         const attr_t selected_highlight_attr = A_REVERSE;
5576         const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue);
5577 
5578         StreamString strm;
5579 
5580         InstructionList &insts = m_disassembly_sp->GetInstructionList();
5581         Address pc_address;
5582 
5583         if (frame_sp)
5584           pc_address = frame_sp->GetFrameCodeAddress();
5585         const uint32_t pc_idx =
5586             pc_address.IsValid()
5587                 ? insts.GetIndexOfInstructionAtAddress(pc_address)
5588                 : UINT32_MAX;
5589         if (set_selected_line_to_pc) {
5590           m_selected_line = pc_idx;
5591         }
5592 
5593         const uint32_t non_visible_pc_offset = (num_visible_lines / 5);
5594         if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines)
5595           m_first_visible_line = 0;
5596 
5597         if (pc_idx < num_disassembly_lines) {
5598           if (pc_idx < static_cast<uint32_t>(m_first_visible_line) ||
5599               pc_idx >= m_first_visible_line + num_visible_lines)
5600             m_first_visible_line = pc_idx - non_visible_pc_offset;
5601         }
5602 
5603         for (size_t i = 0; i < num_visible_lines; ++i) {
5604           const uint32_t inst_idx = m_first_visible_line + i;
5605           Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get();
5606           if (!inst)
5607             break;
5608 
5609           const int line_y = m_min_y + i;
5610           window.MoveCursor(1, line_y);
5611           const bool is_pc_line = frame_sp && inst_idx == pc_idx;
5612           const bool line_is_selected = m_selected_line == inst_idx;
5613           // Highlight the line as the PC line first, then if the selected line
5614           // isn't the same as the PC line, highlight it differently
5615           attr_t highlight_attr = 0;
5616           attr_t bp_attr = 0;
5617           if (is_pc_line)
5618             highlight_attr = pc_highlight_attr;
5619           else if (line_is_selected)
5620             highlight_attr = selected_highlight_attr;
5621 
5622           if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) !=
5623               bp_file_addrs.end())
5624             bp_attr = COLOR_PAIR(BlackOnWhite);
5625 
5626           if (bp_attr)
5627             window.AttributeOn(bp_attr);
5628 
5629           window.Printf(" 0x%16.16llx ",
5630                         static_cast<unsigned long long>(
5631                             inst->GetAddress().GetLoadAddress(target)));
5632 
5633           if (bp_attr)
5634             window.AttributeOff(bp_attr);
5635 
5636           window.PutChar(ACS_VLINE);
5637           // Mark the line with the PC with a diamond
5638           if (is_pc_line)
5639             window.PutChar(ACS_DIAMOND);
5640           else
5641             window.PutChar(' ');
5642 
5643           if (highlight_attr)
5644             window.AttributeOn(highlight_attr);
5645 
5646           const char *mnemonic = inst->GetMnemonic(&exe_ctx);
5647           const char *operands = inst->GetOperands(&exe_ctx);
5648           const char *comment = inst->GetComment(&exe_ctx);
5649 
5650           if (mnemonic != nullptr && mnemonic[0] == '\0')
5651             mnemonic = nullptr;
5652           if (operands != nullptr && operands[0] == '\0')
5653             operands = nullptr;
5654           if (comment != nullptr && comment[0] == '\0')
5655             comment = nullptr;
5656 
5657           strm.Clear();
5658 
5659           if (mnemonic != nullptr && operands != nullptr && comment != nullptr)
5660             strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment);
5661           else if (mnemonic != nullptr && operands != nullptr)
5662             strm.Printf("%-8s %s", mnemonic, operands);
5663           else if (mnemonic != nullptr)
5664             strm.Printf("%s", mnemonic);
5665 
5666           int right_pad = 1;
5667           window.PutCStringTruncated(
5668               right_pad,
5669               strm.GetString().substr(m_first_visible_column).data());
5670 
5671           if (is_pc_line && frame_sp &&
5672               frame_sp->GetConcreteFrameIndex() == 0) {
5673             StopInfoSP stop_info_sp;
5674             if (thread)
5675               stop_info_sp = thread->GetStopInfo();
5676             if (stop_info_sp) {
5677               const char *stop_description = stop_info_sp->GetDescription();
5678               if (stop_description && stop_description[0]) {
5679                 size_t stop_description_len = strlen(stop_description);
5680                 int desc_x = window_width - stop_description_len - 16;
5681                 if (desc_x - window.GetCursorX() > 0)
5682                   window.Printf("%*s", desc_x - window.GetCursorX(), "");
5683                 window.MoveCursor(window_width - stop_description_len - 15,
5684                                   line_y);
5685                 window.PrintfTruncated(1, "<<< Thread %u: %s ",
5686                                        thread->GetIndexID(), stop_description);
5687               }
5688             } else {
5689               window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
5690             }
5691           }
5692           if (highlight_attr)
5693             window.AttributeOff(highlight_attr);
5694         }
5695       }
5696     }
5697     return true; // Drawing handled
5698   }
5699 
GetNumLines()5700   size_t GetNumLines() {
5701     size_t num_lines = GetNumSourceLines();
5702     if (num_lines == 0)
5703       num_lines = GetNumDisassemblyLines();
5704     return num_lines;
5705   }
5706 
GetNumSourceLines() const5707   size_t GetNumSourceLines() const {
5708     if (m_file_sp)
5709       return m_file_sp->GetNumLines();
5710     return 0;
5711   }
5712 
GetNumDisassemblyLines() const5713   size_t GetNumDisassemblyLines() const {
5714     if (m_disassembly_sp)
5715       return m_disassembly_sp->GetInstructionList().GetSize();
5716     return 0;
5717   }
5718 
WindowDelegateHandleChar(Window & window,int c)5719   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
5720     const uint32_t num_visible_lines = NumVisibleLines();
5721     const size_t num_lines = GetNumLines();
5722 
5723     switch (c) {
5724     case ',':
5725     case KEY_PPAGE:
5726       // Page up key
5727       if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines)
5728         m_first_visible_line -= num_visible_lines;
5729       else
5730         m_first_visible_line = 0;
5731       m_selected_line = m_first_visible_line;
5732       return eKeyHandled;
5733 
5734     case '.':
5735     case KEY_NPAGE:
5736       // Page down key
5737       {
5738         if (m_first_visible_line + num_visible_lines < num_lines)
5739           m_first_visible_line += num_visible_lines;
5740         else if (num_lines < num_visible_lines)
5741           m_first_visible_line = 0;
5742         else
5743           m_first_visible_line = num_lines - num_visible_lines;
5744         m_selected_line = m_first_visible_line;
5745       }
5746       return eKeyHandled;
5747 
5748     case KEY_UP:
5749       if (m_selected_line > 0) {
5750         m_selected_line--;
5751         if (static_cast<size_t>(m_first_visible_line) > m_selected_line)
5752           m_first_visible_line = m_selected_line;
5753       }
5754       return eKeyHandled;
5755 
5756     case KEY_DOWN:
5757       if (m_selected_line + 1 < num_lines) {
5758         m_selected_line++;
5759         if (m_first_visible_line + num_visible_lines < m_selected_line)
5760           m_first_visible_line++;
5761       }
5762       return eKeyHandled;
5763 
5764     case KEY_LEFT:
5765       if (m_first_visible_column > 0)
5766         --m_first_visible_column;
5767       return eKeyHandled;
5768 
5769     case KEY_RIGHT:
5770       ++m_first_visible_column;
5771       return eKeyHandled;
5772 
5773     case '\r':
5774     case '\n':
5775     case KEY_ENTER:
5776       // Set a breakpoint and run to the line using a one shot breakpoint
5777       if (GetNumSourceLines() > 0) {
5778         ExecutionContext exe_ctx =
5779             m_debugger.GetCommandInterpreter().GetExecutionContext();
5780         if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) {
5781           BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
5782               nullptr, // Don't limit the breakpoint to certain modules
5783               m_file_sp->GetFileSpec(), // Source file
5784               m_selected_line +
5785                   1, // Source line number (m_selected_line is zero based)
5786               0,     // Unspecified column.
5787               0,     // No offset
5788               eLazyBoolCalculate,  // Check inlines using global setting
5789               eLazyBoolCalculate,  // Skip prologue using global setting,
5790               false,               // internal
5791               false,               // request_hardware
5792               eLazyBoolCalculate); // move_to_nearest_code
5793           // Make breakpoint one shot
5794           bp_sp->GetOptions().SetOneShot(true);
5795           exe_ctx.GetProcessRef().Resume();
5796         }
5797       } else if (m_selected_line < GetNumDisassemblyLines()) {
5798         const Instruction *inst = m_disassembly_sp->GetInstructionList()
5799                                       .GetInstructionAtIndex(m_selected_line)
5800                                       .get();
5801         ExecutionContext exe_ctx =
5802             m_debugger.GetCommandInterpreter().GetExecutionContext();
5803         if (exe_ctx.HasTargetScope()) {
5804           Address addr = inst->GetAddress();
5805           BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
5806               addr,   // lldb_private::Address
5807               false,  // internal
5808               false); // request_hardware
5809           // Make breakpoint one shot
5810           bp_sp->GetOptions().SetOneShot(true);
5811           exe_ctx.GetProcessRef().Resume();
5812         }
5813       }
5814       return eKeyHandled;
5815 
5816     case 'b': // 'b' == toggle breakpoint on currently selected line
5817       ToggleBreakpointOnSelectedLine();
5818       return eKeyHandled;
5819 
5820     case 'D': // 'D' == detach and keep stopped
5821     {
5822       ExecutionContext exe_ctx =
5823           m_debugger.GetCommandInterpreter().GetExecutionContext();
5824       if (exe_ctx.HasProcessScope())
5825         exe_ctx.GetProcessRef().Detach(true);
5826     }
5827       return eKeyHandled;
5828 
5829     case 'c':
5830       // 'c' == continue
5831       {
5832         ExecutionContext exe_ctx =
5833             m_debugger.GetCommandInterpreter().GetExecutionContext();
5834         if (exe_ctx.HasProcessScope())
5835           exe_ctx.GetProcessRef().Resume();
5836       }
5837       return eKeyHandled;
5838 
5839     case 'f':
5840       // 'f' == step out (finish)
5841       {
5842         ExecutionContext exe_ctx =
5843             m_debugger.GetCommandInterpreter().GetExecutionContext();
5844         if (exe_ctx.HasThreadScope() &&
5845             StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
5846           exe_ctx.GetThreadRef().StepOut();
5847         }
5848       }
5849       return eKeyHandled;
5850 
5851     case 'n': // 'n' == step over
5852     case 'N': // 'N' == step over instruction
5853     {
5854       ExecutionContext exe_ctx =
5855           m_debugger.GetCommandInterpreter().GetExecutionContext();
5856       if (exe_ctx.HasThreadScope() &&
5857           StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
5858         bool source_step = (c == 'n');
5859         exe_ctx.GetThreadRef().StepOver(source_step);
5860       }
5861     }
5862       return eKeyHandled;
5863 
5864     case 's': // 's' == step into
5865     case 'S': // 'S' == step into instruction
5866     {
5867       ExecutionContext exe_ctx =
5868           m_debugger.GetCommandInterpreter().GetExecutionContext();
5869       if (exe_ctx.HasThreadScope() &&
5870           StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
5871         bool source_step = (c == 's');
5872         exe_ctx.GetThreadRef().StepIn(source_step);
5873       }
5874     }
5875       return eKeyHandled;
5876 
5877     case 'u': // 'u' == frame up
5878     case 'd': // 'd' == frame down
5879     {
5880       ExecutionContext exe_ctx =
5881           m_debugger.GetCommandInterpreter().GetExecutionContext();
5882       if (exe_ctx.HasThreadScope()) {
5883         Thread *thread = exe_ctx.GetThreadPtr();
5884         uint32_t frame_idx = thread->GetSelectedFrameIndex();
5885         if (frame_idx == UINT32_MAX)
5886           frame_idx = 0;
5887         if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount())
5888           ++frame_idx;
5889         else if (c == 'd' && frame_idx > 0)
5890           --frame_idx;
5891         if (thread->SetSelectedFrameByIndex(frame_idx, true))
5892           exe_ctx.SetFrameSP(thread->GetSelectedFrame());
5893       }
5894     }
5895       return eKeyHandled;
5896 
5897     case 'h':
5898       window.CreateHelpSubwindow();
5899       return eKeyHandled;
5900 
5901     default:
5902       break;
5903     }
5904     return eKeyNotHandled;
5905   }
5906 
ToggleBreakpointOnSelectedLine()5907   void ToggleBreakpointOnSelectedLine() {
5908     ExecutionContext exe_ctx =
5909         m_debugger.GetCommandInterpreter().GetExecutionContext();
5910     if (!exe_ctx.HasTargetScope())
5911       return;
5912     if (GetNumSourceLines() > 0) {
5913       // Source file breakpoint.
5914       BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
5915       const size_t num_bps = bp_list.GetSize();
5916       for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
5917         BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
5918         const size_t num_bps_locs = bp_sp->GetNumLocations();
5919         for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
5920           BreakpointLocationSP bp_loc_sp =
5921               bp_sp->GetLocationAtIndex(bp_loc_idx);
5922           LineEntry bp_loc_line_entry;
5923           if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
5924                   bp_loc_line_entry)) {
5925             if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file &&
5926                 m_selected_line + 1 == bp_loc_line_entry.line) {
5927               bool removed =
5928                   exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
5929               assert(removed);
5930               UNUSED_IF_ASSERT_DISABLED(removed);
5931               return; // Existing breakpoint removed.
5932             }
5933           }
5934         }
5935       }
5936       // No breakpoint found on the location, add it.
5937       BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
5938           nullptr, // Don't limit the breakpoint to certain modules
5939           m_file_sp->GetFileSpec(), // Source file
5940           m_selected_line +
5941               1, // Source line number (m_selected_line is zero based)
5942           0,     // No column specified.
5943           0,     // No offset
5944           eLazyBoolCalculate,  // Check inlines using global setting
5945           eLazyBoolCalculate,  // Skip prologue using global setting,
5946           false,               // internal
5947           false,               // request_hardware
5948           eLazyBoolCalculate); // move_to_nearest_code
5949     } else {
5950       // Disassembly breakpoint.
5951       assert(GetNumDisassemblyLines() > 0);
5952       assert(m_selected_line < GetNumDisassemblyLines());
5953       const Instruction *inst = m_disassembly_sp->GetInstructionList()
5954                                     .GetInstructionAtIndex(m_selected_line)
5955                                     .get();
5956       Address addr = inst->GetAddress();
5957       // Try to find it.
5958       BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
5959       const size_t num_bps = bp_list.GetSize();
5960       for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
5961         BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
5962         const size_t num_bps_locs = bp_sp->GetNumLocations();
5963         for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
5964           BreakpointLocationSP bp_loc_sp =
5965               bp_sp->GetLocationAtIndex(bp_loc_idx);
5966           LineEntry bp_loc_line_entry;
5967           const lldb::addr_t file_addr =
5968               bp_loc_sp->GetAddress().GetFileAddress();
5969           if (file_addr == addr.GetFileAddress()) {
5970             bool removed =
5971                 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
5972             assert(removed);
5973             UNUSED_IF_ASSERT_DISABLED(removed);
5974             return; // Existing breakpoint removed.
5975           }
5976         }
5977       }
5978       // No breakpoint found on the address, add it.
5979       BreakpointSP bp_sp =
5980           exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address
5981                                                   false,  // internal
5982                                                   false); // request_hardware
5983     }
5984   }
5985 
5986 protected:
5987   typedef std::set<uint32_t> BreakpointLines;
5988   typedef std::set<lldb::addr_t> BreakpointAddrs;
5989 
5990   Debugger &m_debugger;
5991   SymbolContext m_sc;
5992   SourceManager::FileSP m_file_sp;
5993   SymbolContextScope *m_disassembly_scope;
5994   lldb::DisassemblerSP m_disassembly_sp;
5995   AddressRange m_disassembly_range;
5996   StreamString m_title;
5997   lldb::user_id_t m_tid;
5998   int m_line_width;
5999   uint32_t m_selected_line; // The selected line
6000   uint32_t m_pc_line;       // The line with the PC
6001   uint32_t m_stop_id;
6002   uint32_t m_frame_idx;
6003   int m_first_visible_line;
6004   int m_first_visible_column;
6005   int m_min_x;
6006   int m_min_y;
6007   int m_max_x;
6008   int m_max_y;
6009 };
6010 
6011 DisplayOptions ValueObjectListDelegate::g_options = {true};
6012 
IOHandlerCursesGUI(Debugger & debugger)6013 IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger)
6014     : IOHandler(debugger, IOHandler::Type::Curses) {}
6015 
Activate()6016 void IOHandlerCursesGUI::Activate() {
6017   IOHandler::Activate();
6018   if (!m_app_ap) {
6019     m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE());
6020 
6021     // This is both a window and a menu delegate
6022     std::shared_ptr<ApplicationDelegate> app_delegate_sp(
6023         new ApplicationDelegate(*m_app_ap, m_debugger));
6024 
6025     MenuDelegateSP app_menu_delegate_sp =
6026         std::static_pointer_cast<MenuDelegate>(app_delegate_sp);
6027     MenuSP lldb_menu_sp(
6028         new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB));
6029     MenuSP exit_menuitem_sp(
6030         new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit));
6031     exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit);
6032     lldb_menu_sp->AddSubmenu(MenuSP(new Menu(
6033         "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout)));
6034     lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
6035     lldb_menu_sp->AddSubmenu(exit_menuitem_sp);
6036 
6037     MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2),
6038                                    ApplicationDelegate::eMenuID_Target));
6039     target_menu_sp->AddSubmenu(MenuSP(new Menu(
6040         "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate)));
6041     target_menu_sp->AddSubmenu(MenuSP(new Menu(
6042         "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete)));
6043 
6044     MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3),
6045                                     ApplicationDelegate::eMenuID_Process));
6046     process_menu_sp->AddSubmenu(MenuSP(new Menu(
6047         "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach)));
6048     process_menu_sp->AddSubmenu(
6049         MenuSP(new Menu("Detach and resume", nullptr, 'd',
6050                         ApplicationDelegate::eMenuID_ProcessDetachResume)));
6051     process_menu_sp->AddSubmenu(
6052         MenuSP(new Menu("Detach suspended", nullptr, 's',
6053                         ApplicationDelegate::eMenuID_ProcessDetachSuspended)));
6054     process_menu_sp->AddSubmenu(MenuSP(new Menu(
6055         "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch)));
6056     process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
6057     process_menu_sp->AddSubmenu(
6058         MenuSP(new Menu("Continue", nullptr, 'c',
6059                         ApplicationDelegate::eMenuID_ProcessContinue)));
6060     process_menu_sp->AddSubmenu(MenuSP(new Menu(
6061         "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt)));
6062     process_menu_sp->AddSubmenu(MenuSP(new Menu(
6063         "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill)));
6064 
6065     MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4),
6066                                    ApplicationDelegate::eMenuID_Thread));
6067     thread_menu_sp->AddSubmenu(MenuSP(new Menu(
6068         "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn)));
6069     thread_menu_sp->AddSubmenu(
6070         MenuSP(new Menu("Step Over", nullptr, 'v',
6071                         ApplicationDelegate::eMenuID_ThreadStepOver)));
6072     thread_menu_sp->AddSubmenu(MenuSP(new Menu(
6073         "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut)));
6074 
6075     MenuSP view_menu_sp(
6076         new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View));
6077     view_menu_sp->AddSubmenu(
6078         MenuSP(new Menu("Backtrace", nullptr, 'b',
6079                         ApplicationDelegate::eMenuID_ViewBacktrace)));
6080     view_menu_sp->AddSubmenu(
6081         MenuSP(new Menu("Registers", nullptr, 'r',
6082                         ApplicationDelegate::eMenuID_ViewRegisters)));
6083     view_menu_sp->AddSubmenu(MenuSP(new Menu(
6084         "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource)));
6085     view_menu_sp->AddSubmenu(
6086         MenuSP(new Menu("Variables", nullptr, 'v',
6087                         ApplicationDelegate::eMenuID_ViewVariables)));
6088 
6089     MenuSP help_menu_sp(
6090         new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help));
6091     help_menu_sp->AddSubmenu(MenuSP(new Menu(
6092         "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp)));
6093 
6094     m_app_ap->Initialize();
6095     WindowSP &main_window_sp = m_app_ap->GetMainWindow();
6096 
6097     MenuSP menubar_sp(new Menu(Menu::Type::Bar));
6098     menubar_sp->AddSubmenu(lldb_menu_sp);
6099     menubar_sp->AddSubmenu(target_menu_sp);
6100     menubar_sp->AddSubmenu(process_menu_sp);
6101     menubar_sp->AddSubmenu(thread_menu_sp);
6102     menubar_sp->AddSubmenu(view_menu_sp);
6103     menubar_sp->AddSubmenu(help_menu_sp);
6104     menubar_sp->SetDelegate(app_menu_delegate_sp);
6105 
6106     Rect content_bounds = main_window_sp->GetFrame();
6107     Rect menubar_bounds = content_bounds.MakeMenuBar();
6108     Rect status_bounds = content_bounds.MakeStatusBar();
6109     Rect source_bounds;
6110     Rect variables_bounds;
6111     Rect threads_bounds;
6112     Rect source_variables_bounds;
6113     content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
6114                                            threads_bounds);
6115     source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds,
6116                                                       variables_bounds);
6117 
6118     WindowSP menubar_window_sp =
6119         main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false);
6120     // Let the menubar get keys if the active window doesn't handle the keys
6121     // that are typed so it can respond to menubar key presses.
6122     menubar_window_sp->SetCanBeActive(
6123         false); // Don't let the menubar become the active window
6124     menubar_window_sp->SetDelegate(menubar_sp);
6125 
6126     WindowSP source_window_sp(
6127         main_window_sp->CreateSubWindow("Source", source_bounds, true));
6128     WindowSP variables_window_sp(
6129         main_window_sp->CreateSubWindow("Variables", variables_bounds, false));
6130     WindowSP threads_window_sp(
6131         main_window_sp->CreateSubWindow("Threads", threads_bounds, false));
6132     WindowSP status_window_sp(
6133         main_window_sp->CreateSubWindow("Status", status_bounds, false));
6134     status_window_sp->SetCanBeActive(
6135         false); // Don't let the status bar become the active window
6136     main_window_sp->SetDelegate(
6137         std::static_pointer_cast<WindowDelegate>(app_delegate_sp));
6138     source_window_sp->SetDelegate(
6139         WindowDelegateSP(new SourceFileWindowDelegate(m_debugger)));
6140     variables_window_sp->SetDelegate(
6141         WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
6142     TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger));
6143     threads_window_sp->SetDelegate(WindowDelegateSP(
6144         new TreeWindowDelegate(m_debugger, thread_delegate_sp)));
6145     status_window_sp->SetDelegate(
6146         WindowDelegateSP(new StatusBarWindowDelegate(m_debugger)));
6147 
6148     // Show the main help window once the first time the curses GUI is launched
6149     static bool g_showed_help = false;
6150     if (!g_showed_help) {
6151       g_showed_help = true;
6152       main_window_sp->CreateHelpSubwindow();
6153     }
6154 
6155     // All colors with black background.
6156     init_pair(1, COLOR_BLACK, COLOR_BLACK);
6157     init_pair(2, COLOR_RED, COLOR_BLACK);
6158     init_pair(3, COLOR_GREEN, COLOR_BLACK);
6159     init_pair(4, COLOR_YELLOW, COLOR_BLACK);
6160     init_pair(5, COLOR_BLUE, COLOR_BLACK);
6161     init_pair(6, COLOR_MAGENTA, COLOR_BLACK);
6162     init_pair(7, COLOR_CYAN, COLOR_BLACK);
6163     init_pair(8, COLOR_WHITE, COLOR_BLACK);
6164     // All colors with blue background.
6165     init_pair(9, COLOR_BLACK, COLOR_BLUE);
6166     init_pair(10, COLOR_RED, COLOR_BLUE);
6167     init_pair(11, COLOR_GREEN, COLOR_BLUE);
6168     init_pair(12, COLOR_YELLOW, COLOR_BLUE);
6169     init_pair(13, COLOR_BLUE, COLOR_BLUE);
6170     init_pair(14, COLOR_MAGENTA, COLOR_BLUE);
6171     init_pair(15, COLOR_CYAN, COLOR_BLUE);
6172     init_pair(16, COLOR_WHITE, COLOR_BLUE);
6173     // These must match the order in the color indexes enum.
6174     init_pair(17, COLOR_BLACK, COLOR_WHITE);
6175     init_pair(18, COLOR_MAGENTA, COLOR_WHITE);
6176     static_assert(LastColorPairIndex == 18, "Color indexes do not match.");
6177 
6178     define_key("\033[Z", KEY_SHIFT_TAB);
6179   }
6180 }
6181 
Deactivate()6182 void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); }
6183 
Run()6184 void IOHandlerCursesGUI::Run() {
6185   m_app_ap->Run(m_debugger);
6186   SetIsDone(true);
6187 }
6188 
6189 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default;
6190 
Cancel()6191 void IOHandlerCursesGUI::Cancel() {}
6192 
Interrupt()6193 bool IOHandlerCursesGUI::Interrupt() { return false; }
6194 
GotEOF()6195 void IOHandlerCursesGUI::GotEOF() {}
6196 
TerminalSizeChanged()6197 void IOHandlerCursesGUI::TerminalSizeChanged() {
6198   m_app_ap->TerminalSizeChanged();
6199 }
6200 
6201 #endif // LLDB_ENABLE_CURSES
6202